| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed macro primitives.
2
3 This module implements functions a macro can legally use.
4 """
5 #=====================================================================
6 __version__ = "$Revision: 1.51 $"
7 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
8
9 import sys, time, random, types, logging
10
11
12 import wx
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmI18N
18 if __name__ == '__main__':
19 gmI18N.activate_locale()
20 gmI18N.install_domain()
21 from Gnumed.pycommon import gmGuiBroker
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmBorg
24 from Gnumed.pycommon import gmExceptions
25 from Gnumed.pycommon import gmCfg2
26 from Gnumed.pycommon import gmDateTime
27
28 from Gnumed.business import gmPerson
29 from Gnumed.business import gmDemographicRecord
30 from Gnumed.business import gmMedication
31 from Gnumed.business import gmPathLab
32 from Gnumed.business import gmPersonSearch
33 from Gnumed.business import gmVaccination
34 from Gnumed.business import gmPersonSearch
35
36 from Gnumed.wxpython import gmGuiHelpers
37 from Gnumed.wxpython import gmNarrativeWidgets
38 from Gnumed.wxpython import gmPatSearchWidgets
39 from Gnumed.wxpython import gmPlugin
40 from Gnumed.wxpython import gmEMRStructWidgets
41
42
43 _log = logging.getLogger('gm.scripting')
44 _cfg = gmCfg2.gmCfgData()
45
46 #=====================================================================
47 known_placeholders = [
48 'lastname',
49 'firstname',
50 'title',
51 'date_of_birth',
52 'progress_notes',
53 'soap',
54 'soap_s',
55 'soap_o',
56 'soap_a',
57 'soap_p',
58 u'client_version',
59 u'current_provider',
60 u'primary_praxis_provider', # primary provider for current patient in this praxis
61 u'allergy_state'
62 ]
63
64
65 # those must satisfy the pattern "$<name::args::(optional) max string length>$" when used
66 known_variant_placeholders = [
67 u'soap',
68 u'progress_notes', # "args" holds: categories//template
69 # categories: string with 'soap '; ' ' == None == admin
70 # template: u'something %s something' (do not include // in template !)
71 u'emr_journal', # "args" format: <categories>//<template>//<line length>//<time range>//<target format>
72 # categories: string with any of "s", "o", "a", "p", " ";
73 # (" " == None == admin category)
74 # template: something %s something else
75 # (Do not include // in the template !)
76 # line length: the length of individual lines, not the total placeholder length
77 # time range: the number of weeks going back in time
78 # target format: "tex" or anything else, if "tex", data will be tex-escaped
79 u'date_of_birth',
80
81 u'patient_address', # "args": <type of address>//<optional formatting template>
82 u'adr_street', # "args" holds: type of address
83 u'adr_number',
84 u'adr_location',
85 u'adr_postcode',
86 u'adr_region',
87 u'adr_country',
88
89 u'patient_comm', # args: comm channel type as per database
90 u'external_id', # args: <type of ID>//<issuer of ID>
91 u'gender_mapper', # "args" holds: <value when person is male> // <is female> // <is other>
92 # eg. "male//female//other"
93 # or: "Lieber Patient//Liebe Patientin"
94 u'current_meds', # "args" holds: line template
95 u'current_meds_table', # "args" holds: format, options
96 # currently only "latex"
97 u'current_meds_notes', # "args" holds: format, options
98 u'lab_table', # "args" holds: format (currently "latex" only)
99 u'latest_vaccs_table', # "args" holds: format, options
100 u'today', # "args" holds: strftime format
101 u'tex_escape', # "args" holds: string to escape
102 u'allergies', # "args" holds: line template, one allergy per line
103 u'allergy_list', # "args" holds: template per allergy, allergies on one line
104 u'problems', # "args" holds: line template, one problem per line
105 u'name', # "args" holds: template for name parts arrangement
106 u'free_text', # show a dialog for entering some free text
107 u'soap_for_encounters', # "args" holds: soap cats // strftime date format
108 u'encounter_list' # "args" holds: per-encounter template, each ends up on one line
109 ]
110
111 default_placeholder_regex = r'\$<.+?>\$' # this one works (except that OOo cannot be non-greedy |-( )
112
113 #_regex_parts = [
114 # r'\$<\w+::.*(?::)\d+>\$',
115 # r'\$<\w+::.+(?!>\$)>\$',
116 # r'\$<\w+?>\$'
117 #]
118 #default_placeholder_regex = r'|'.join(_regex_parts)
119
120 default_placeholder_start = u'$<'
121 default_placeholder_end = u'>$'
122 #=====================================================================
124 """Replaces placeholders in forms, fields, etc.
125
126 - patient related placeholders operate on the currently active patient
127 - is passed to the forms handling code, for example
128
129 Note that this cannot be called from a non-gui thread unless
130 wrapped in wx.CallAfter().
131
132 There are currently two types of placeholders:
133
134 simple static placeholders
135 - those are listed in known_placeholders
136 - they are used as-is
137
138 variant placeholders
139 - those are listed in known_variant_placeholders
140 - they are parsed into placeholder, data, and maximum length
141 - the length is optional
142 - data is passed to the handler
143 """
145
146 self.pat = gmPerson.gmCurrentPatient()
147 self.debug = False
148
149 self.invalid_placeholder_template = _('invalid placeholder [%s]')
150 #--------------------------------------------------------
151 # __getitem__ API
152 #--------------------------------------------------------
154 """Map self['placeholder'] to self.placeholder.
155
156 This is useful for replacing placeholders parsed out
157 of documents as strings.
158
159 Unknown/invalid placeholders still deliver a result but
160 it will be glaringly obvious if debugging is enabled.
161 """
162 _log.debug('replacing [%s]', placeholder)
163
164 original_placeholder = placeholder
165
166 if placeholder.startswith(default_placeholder_start):
167 placeholder = placeholder[len(default_placeholder_start):]
168 if placeholder.endswith(default_placeholder_end):
169 placeholder = placeholder[:-len(default_placeholder_end)]
170 else:
171 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
172 if self.debug:
173 return self.invalid_placeholder_template % original_placeholder
174 return None
175
176 # simple static placeholder ?
177 if placeholder in known_placeholders:
178 return getattr(self, placeholder)
179
180 # extended static placeholder ?
181 parts = placeholder.split('::::', 1)
182 if len(parts) == 2:
183 name, lng = parts
184 try:
185 return getattr(self, name)[:int(lng)]
186 except:
187 _log.exception('placeholder handling error: %s', original_placeholder)
188 if self.debug:
189 return self.invalid_placeholder_template % original_placeholder
190 return None
191
192 # variable placeholders
193 parts = placeholder.split('::')
194 if len(parts) == 2:
195 name, data = parts
196 lng = None
197 if len(parts) == 3:
198 name, data, lng = parts
199 try:
200 lng = int(lng)
201 except (TypeError, ValueError):
202 _log.error('placeholder length definition error: %s, discarding length: >%s<', original_placeholder, lng)
203 lng = None
204 if len(parts) > 3:
205 _log.warning('invalid placeholder layout: %s', original_placeholder)
206 if self.debug:
207 return self.invalid_placeholder_template % original_placeholder
208 return None
209
210 handler = getattr(self, '_get_variant_%s' % name, None)
211 if handler is None:
212 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
213 if self.debug:
214 return self.invalid_placeholder_template % original_placeholder
215 return None
216
217 try:
218 if lng is None:
219 return handler(data = data)
220 return handler(data = data)[:lng]
221 except:
222 _log.exception('placeholder handling error: %s', original_placeholder)
223 if self.debug:
224 return self.invalid_placeholder_template % original_placeholder
225 return None
226
227 _log.error('something went wrong, should never get here')
228 return None
229 #--------------------------------------------------------
230 # properties actually handling placeholders
231 #--------------------------------------------------------
232 # property helpers
233 #--------------------------------------------------------
237 #--------------------------------------------------------
239 return self.pat.get_active_name()['lastnames']
240 #--------------------------------------------------------
242 return self.pat.get_active_name()['firstnames']
243 #--------------------------------------------------------
246 #--------------------------------------------------------
248 return self._get_variant_date_of_birth(data='%x')
249 #--------------------------------------------------------
252 #--------------------------------------------------------
254 return self._get_variant_soap(data = u's')
255 #--------------------------------------------------------
257 return self._get_variant_soap(data = u'o')
258 #--------------------------------------------------------
260 return self._get_variant_soap(data = u'a')
261 #--------------------------------------------------------
263 return self._get_variant_soap(data = u'p')
264 #--------------------------------------------------------
267 #--------------------------------------------------------
269 return gmTools.coalesce (
270 _cfg.get(option = u'client_version'),
271 u'%s' % self.__class__.__name__
272 )
273 #--------------------------------------------------------
275 prov = self.pat.primary_provider
276 if prov is None:
277 return self._get_current_provider()
278
279 title = gmTools.coalesce (
280 prov['title'],
281 gmPerson.map_gender2salutation(prov['gender'])
282 )
283
284 tmp = u'%s %s. %s' % (
285 title,
286 prov['firstnames'][:1],
287 prov['lastnames']
288 )
289
290 return tmp
291 #--------------------------------------------------------
293 prov = gmPerson.gmCurrentProvider()
294
295 title = gmTools.coalesce (
296 prov['title'],
297 gmPerson.map_gender2salutation(prov['gender'])
298 )
299
300 tmp = u'%s %s. %s' % (
301 title,
302 prov['firstnames'][:1],
303 prov['lastnames']
304 )
305
306 return tmp
307 #--------------------------------------------------------
309 allg_state = self.pat.get_emr().allergy_state
310
311 if allg_state['last_confirmed'] is None:
312 date_confirmed = u''
313 else:
314 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
315
316 tmp = u'%s%s' % (
317 allg_state.state_string,
318 date_confirmed
319 )
320 return tmp
321 #--------------------------------------------------------
322 # property definitions for static placeholders
323 #--------------------------------------------------------
324 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
325
326 # placeholders
327 lastname = property(_get_lastname, _setter_noop)
328 firstname = property(_get_firstname, _setter_noop)
329 title = property(_get_title, _setter_noop)
330 date_of_birth = property(_get_dob, _setter_noop)
331
332 progress_notes = property(_get_progress_notes, _setter_noop)
333 soap = property(_get_progress_notes, _setter_noop)
334 soap_s = property(_get_soap_s, _setter_noop)
335 soap_o = property(_get_soap_o, _setter_noop)
336 soap_a = property(_get_soap_a, _setter_noop)
337 soap_p = property(_get_soap_p, _setter_noop)
338 soap_admin = property(_get_soap_admin, _setter_noop)
339
340 allergy_state = property(_get_allergy_state, _setter_noop)
341
342 client_version = property(_get_client_version, _setter_noop)
343
344 current_provider = property(_get_current_provider, _setter_noop)
345 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
346 #--------------------------------------------------------
347 # variant handlers
348 #--------------------------------------------------------
350
351 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
352 if not encounters:
353 return u''
354
355 template = data
356
357 lines = []
358 for enc in encounters:
359 try:
360 lines.append(template % enc)
361 except:
362 lines.append(u'error formatting encounter')
363 _log.exception('problem formatting encounter list')
364 _log.error('template: %s', template)
365 _log.error('encounter: %s', encounter)
366
367 return u'\n'.join(lines)
368 #--------------------------------------------------------
370 """Select encounters from list and format SOAP thereof.
371
372 data: soap_cats (' ' -> None -> admin) // date format
373 """
374 # defaults
375 cats = None
376 date_format = None
377
378 if data is not None:
379 data_parts = data.split('//')
380
381 # part[0]: categories
382 if len(data_parts[0]) > 0:
383 cats = []
384 if u' ' in data_parts[0]:
385 cats.append(None)
386 data_parts[0] = data_parts[0].replace(u' ', u'')
387 cats.extend(list(data_parts[0]))
388
389 # part[1]: date format
390 if len(data_parts) > 1:
391 if len(data_parts[1]) > 0:
392 date_format = data_parts[1]
393
394 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
395 if not encounters:
396 return u''
397
398 chunks = []
399 for enc in encounters:
400 chunks.append(enc.format_latex (
401 date_format = date_format,
402 soap_cats = cats,
403 soap_order = u'soap_rank, date'
404 ))
405
406 return u''.join(chunks)
407 #--------------------------------------------------------
409 # default: all categories, neutral template
410 cats = list(u'soap')
411 cats.append(None)
412 template = u'%s'
413 interactive = True
414 line_length = 9999
415 target_format = None
416 time_range = None
417
418 if data is not None:
419 data_parts = data.split('//')
420
421 # part[0]: categories
422 cats = []
423 # ' ' -> None == admin
424 for c in list(data_parts[0]):
425 if c == u' ':
426 c = None
427 cats.append(c)
428 # '' -> SOAP + None
429 if cats == u'':
430 cats = list(u'soap').append(None)
431
432 # part[1]: template
433 if len(data_parts) > 1:
434 template = data_parts[1]
435
436 # part[2]: line length
437 if len(data_parts) > 2:
438 try:
439 line_length = int(data_parts[2])
440 except:
441 line_length = 9999
442
443 # part[3]: weeks going back in time
444 if len(data_parts) > 3:
445 try:
446 time_range = 7 * int(data_parts[3])
447 except:
448 time_range = None
449
450 # part[4]: output format
451 if len(data_parts) > 4:
452 target_format = data_parts[4]
453
454 # FIXME: will need to be a generator later on
455 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
456
457 if len(narr) == 0:
458 return u''
459
460 if target_format == u'tex':
461 keys = narr[0].keys()
462 lines = []
463 line_dict = {}
464 for n in narr:
465 for key in keys:
466 if isinstance(n[key], basestring):
467 line_dict[key] = gmTools.tex_escape_string(text = n[key])
468 continue
469 line_dict[key] = n[key]
470 try:
471 lines.append((template % line_dict)[:line_length])
472 except KeyError:
473 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
474 else:
475 try:
476 lines = [ (template % n)[:line_length] for n in narr ]
477 except KeyError:
478 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
479
480 return u'\n'.join(lines)
481 #--------------------------------------------------------
484 #--------------------------------------------------------
486
487 # default: all categories, neutral template
488 cats = list(u'soap')
489 cats.append(None)
490 template = u'%s'
491
492 if data is not None:
493 data_parts = data.split('//')
494
495 # part[0]: categories
496 cats = []
497 # ' ' -> None == admin
498 for cat in list(data_parts[0]):
499 if cat == u' ':
500 cat = None
501 cats.append(cat)
502 # '' -> SOAP + None
503 if cats == u'':
504 cats = list(u'soap')
505 cats.append(None)
506
507 # part[1]: template
508 if len(data_parts) > 1:
509 template = data_parts[1]
510
511 #narr = gmNarrativeWidgets.select_narrative_from_episodes_new(soap_cats = cats)
512 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
513
514 if narr is None:
515 return u''
516
517 if len(narr) == 0:
518 return u''
519
520 try:
521 narr = [ template % n['narrative'] for n in narr ]
522 except KeyError:
523 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
524
525 return u'\n'.join(narr)
526 #--------------------------------------------------------
528 if data is None:
529 return [_('template is missing')]
530
531 name = self.pat.get_active_name()
532
533 parts = {
534 'title': gmTools.coalesce(name['title'], u''),
535 'firstnames': name['firstnames'],
536 'lastnames': name['lastnames'],
537 'preferred': gmTools.coalesce (
538 initial = name['preferred'],
539 instead = u' ',
540 template_initial = u' "%s" '
541 )
542 }
543
544 return data % parts
545 #--------------------------------------------------------
548 #--------------------------------------------------------
549 # FIXME: extend to all supported genders
551 values = data.split('//', 2)
552
553 if len(values) == 2:
554 male_value, female_value = values
555 other_value = u'<unkown gender>'
556 elif len(values) == 3:
557 male_value, female_value, other_value = values
558 else:
559 return _('invalid gender mapping layout: [%s]') % data
560
561 if self.pat['gender'] == u'm':
562 return male_value
563
564 if self.pat['gender'] == u'f':
565 return female_value
566
567 return other_value
568 #--------------------------------------------------------
569 # address related placeholders
570 #--------------------------------------------------------
572
573 data_parts = data.split(u'//')
574
575 if data_parts[0].strip() == u'':
576 adr_type = u'home'
577 else:
578 adr_type = data_parts[0]
579
580 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
581 if len(data_parts) > 1:
582 if data_parts[1].strip() != u'':
583 template = data_parts[1]
584
585 adrs = self.pat.get_addresses(address_type = adr_type)
586 if len(adrs) == 0:
587 return _('no address for type [%s]') % adr_type
588
589 adr = adrs[0]
590 data = {
591 'street': adr['street'],
592 'notes_street': gmTools.coalesce(adr['notes_street'], u''),
593 'postcode': adr['postcode'],
594 'number': adr['number'],
595 'subunit': gmTools.coalesce(adr['subunit'], u''),
596 'notes_subunit': gmTools.coalesce(adr['notes_subunit'], u''),
597 'urb': adr['urb'],
598 'suburb': gmTools.coalesce(adr['suburb'], u''),
599 'l10n_state': adr['l10n_state'],
600 'l10n_country': adr['l10n_country'],
601 'code_state': adr['code_state'],
602 'code_country': adr['code_country']
603 }
604
605 try:
606 return template % data
607 except StandardError:
608 _log.exception('error formatting address')
609 _log.error('template: %s', template)
610
611 return None
612 #--------------------------------------------------------
614 adrs = self.pat.get_addresses(address_type=data)
615 if len(adrs) == 0:
616 return _('no street for address type [%s]') % data
617 return adrs[0]['street']
618 #--------------------------------------------------------
620 adrs = self.pat.get_addresses(address_type=data)
621 if len(adrs) == 0:
622 return _('no number for address type [%s]') % data
623 return adrs[0]['number']
624 #--------------------------------------------------------
626 adrs = self.pat.get_addresses(address_type=data)
627 if len(adrs) == 0:
628 return _('no location for address type [%s]') % data
629 return adrs[0]['urb']
630 #--------------------------------------------------------
632 adrs = self.pat.get_addresses(address_type = data)
633 if len(adrs) == 0:
634 return _('no postcode for address type [%s]') % data
635 return adrs[0]['postcode']
636 #--------------------------------------------------------
638 adrs = self.pat.get_addresses(address_type = data)
639 if len(adrs) == 0:
640 return _('no region for address type [%s]') % data
641 return adrs[0]['l10n_state']
642 #--------------------------------------------------------
644 adrs = self.pat.get_addresses(address_type = data)
645 if len(adrs) == 0:
646 return _('no country for address type [%s]') % data
647 return adrs[0]['l10n_country']
648 #--------------------------------------------------------
650 comms = self.pat.get_comm_channels(comm_medium = data)
651 if len(comms) == 0:
652 return _('no URL for comm channel [%s]') % data
653 return comms[0]['url']
654 #--------------------------------------------------------
656 data_parts = data.split(u'//')
657 if len(data_parts) < 2:
658 return None
659
660 id_type = data_parts[0].strip()
661 if id_type == u'':
662 return None
663
664 issuer = data_parts[1].strip()
665 if issuer == u'':
666 return None
667
668 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
669
670 if len(ids) == 0:
671 return _('no external ID [%s] by [%s]') % (id_type, issuer)
672
673 return ids[0]['value']
674 #--------------------------------------------------------
676 if data is None:
677 return [_('template is missing')]
678
679 template, separator = data.split('//', 2)
680
681 emr = self.pat.get_emr()
682 return separator.join([ template % a for a in emr.get_allergies() ])
683 #--------------------------------------------------------
685
686 if data is None:
687 return [_('template is missing')]
688
689 emr = self.pat.get_emr()
690 return u'\n'.join([ data % a for a in emr.get_allergies() ])
691 #--------------------------------------------------------
693
694 if data is None:
695 return [_('template is missing')]
696
697 emr = self.pat.get_emr()
698 current_meds = emr.get_current_substance_intake (
699 include_inactive = False,
700 include_unapproved = False,
701 order_by = u'brand, substance'
702 )
703
704 # FIXME: we should be dealing with translating None to u'' here
705
706 return u'\n'.join([ data % m for m in current_meds ])
707 #--------------------------------------------------------
709
710 options = data.split('//')
711
712 if u'latex' in options:
713 return gmMedication.format_substance_intake (
714 emr = self.pat.get_emr(),
715 output_format = u'latex',
716 table_type = u'by-brand'
717 )
718
719 _log.error('no known current medications table formatting style in [%]', data)
720 return _('unknown current medication table formatting style')
721 #--------------------------------------------------------
723
724 options = data.split('//')
725
726 if u'latex' in options:
727 return gmMedication.format_substance_intake_notes (
728 emr = self.pat.get_emr(),
729 output_format = u'latex',
730 table_type = u'by-brand'
731 )
732
733 _log.error('no known current medications notes formatting style in [%]', data)
734 return _('unknown current medication notes formatting style')
735 #--------------------------------------------------------
737
738 options = data.split('//')
739
740 emr = self.pat.get_emr()
741
742 if u'latex' in options:
743 return gmPathLab.format_test_results (
744 results = emr.get_test_results_by_date(),
745 output_format = u'latex'
746 )
747
748 _log.error('no known test results table formatting style in [%s]', data)
749 return _('unknown test results table formatting style [%s]') % data
750 #--------------------------------------------------------
752
753 options = data.split('//')
754
755 emr = self.pat.get_emr()
756
757 if u'latex' in options:
758 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr)
759
760 _log.error('no known vaccinations table formatting style in [%s]', data)
761 return _('unknown vaccinations table formatting style [%s]') % data
762 #--------------------------------------------------------
764
765 if data is None:
766 return [_('template is missing')]
767
768 probs = self.pat.get_emr().get_problems()
769
770 return u'\n'.join([ data % p for p in probs ])
771 #--------------------------------------------------------
774 #--------------------------------------------------------
777 #--------------------------------------------------------
779 # <data>:
780 # format: tex (only, currently)
781 # message: shown in input dialog, must not contain "//" or "::"
782
783 data_parts = data.split('//')
784 format = data_parts[0]
785 if len(data_parts) > 1:
786 msg = data_parts[1]
787 else:
788 msg = _('generic text')
789
790 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
791 None,
792 -1,
793 title = _('Replacing <free_text> placeholder'),
794 msg = _('Below you can enter free text.\n\n [%s]') % msg
795 )
796 dlg.enable_user_formatting = True
797 decision = dlg.ShowModal()
798
799 if decision != wx.ID_SAVE:
800 dlg.Destroy()
801 return _('Text input cancelled by user.')
802
803 text = dlg.value.strip()
804 if dlg.is_user_formatted:
805 dlg.Destroy()
806 return text
807
808 dlg.Destroy()
809
810 if format == u'tex':
811 return gmTools.tex_escape_string(text = text)
812
813 return text
814 #--------------------------------------------------------
815 # internal helpers
816 #--------------------------------------------------------
817
818 #=====================================================================
820 """Functions a macro can legally use.
821
822 An instance of this class is passed to the GNUmed scripting
823 listener. Hence, all actions a macro can legally take must
824 be defined in this class. Thus we achieve some screening for
825 security and also thread safety handling.
826 """
827 #-----------------------------------------------------------------
829 if personality is None:
830 raise gmExceptions.ConstructorError, 'must specify personality'
831 self.__personality = personality
832 self.__attached = 0
833 self._get_source_personality = None
834 self.__user_done = False
835 self.__user_answer = 'no answer yet'
836 self.__pat = gmPerson.gmCurrentPatient()
837
838 self.__auth_cookie = str(random.random())
839 self.__pat_lock_cookie = str(random.random())
840 self.__lock_after_load_cookie = str(random.random())
841
842 _log.info('slave mode personality is [%s]', personality)
843 #-----------------------------------------------------------------
844 # public API
845 #-----------------------------------------------------------------
847 if self.__attached:
848 _log.error('attach with [%s] rejected, already serving a client', personality)
849 return (0, _('attach rejected, already serving a client'))
850 if personality != self.__personality:
851 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
852 return (0, _('attach to personality [%s] rejected') % personality)
853 self.__attached = 1
854 self.__auth_cookie = str(random.random())
855 return (1, self.__auth_cookie)
856 #-----------------------------------------------------------------
858 if not self.__attached:
859 return 1
860 if auth_cookie != self.__auth_cookie:
861 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
862 return 0
863 self.__attached = 0
864 return 1
865 #-----------------------------------------------------------------
867 if not self.__attached:
868 return 1
869 self.__user_done = False
870 # FIXME: use self.__sync_cookie for syncing with user interaction
871 wx.CallAfter(self._force_detach)
872 return 1
873 #-----------------------------------------------------------------
875 ver = _cfg.get(option = u'client_version')
876 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
877 #-----------------------------------------------------------------
879 """Shuts down this client instance."""
880 if not self.__attached:
881 return 0
882 if auth_cookie != self.__auth_cookie:
883 _log.error('non-authenticated shutdown_gnumed()')
884 return 0
885 wx.CallAfter(self._shutdown_gnumed, forced)
886 return 1
887 #-----------------------------------------------------------------
889 """Raise ourselves to the top of the desktop."""
890 if not self.__attached:
891 return 0
892 if auth_cookie != self.__auth_cookie:
893 _log.error('non-authenticated raise_gnumed()')
894 return 0
895 return "cMacroPrimitives.raise_gnumed() not implemented"
896 #-----------------------------------------------------------------
898 if not self.__attached:
899 return 0
900 if auth_cookie != self.__auth_cookie:
901 _log.error('non-authenticated get_loaded_plugins()')
902 return 0
903 gb = gmGuiBroker.GuiBroker()
904 return gb['horstspace.notebook.gui'].keys()
905 #-----------------------------------------------------------------
907 """Raise a notebook plugin within GNUmed."""
908 if not self.__attached:
909 return 0
910 if auth_cookie != self.__auth_cookie:
911 _log.error('non-authenticated raise_notebook_plugin()')
912 return 0
913 # FIXME: use semaphore
914 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
915 return 1
916 #-----------------------------------------------------------------
918 """Load external patient, perhaps create it.
919
920 Callers must use get_user_answer() to get status information.
921 It is unsafe to proceed without knowing the completion state as
922 the controlled client may be waiting for user input from a
923 patient selection list.
924 """
925 if not self.__attached:
926 return (0, _('request rejected, you are not attach()ed'))
927 if auth_cookie != self.__auth_cookie:
928 _log.error('non-authenticated load_patient_from_external_source()')
929 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
930 if self.__pat.locked:
931 _log.error('patient is locked, cannot load from external source')
932 return (0, _('current patient is locked'))
933 self.__user_done = False
934 wx.CallAfter(self._load_patient_from_external_source)
935 self.__lock_after_load_cookie = str(random.random())
936 return (1, self.__lock_after_load_cookie)
937 #-----------------------------------------------------------------
939 if not self.__attached:
940 return (0, _('request rejected, you are not attach()ed'))
941 if auth_cookie != self.__auth_cookie:
942 _log.error('non-authenticated lock_load_patient()')
943 return (0, _('rejected lock_load_patient(), not authenticated'))
944 # FIXME: ask user what to do about wrong cookie
945 if lock_after_load_cookie != self.__lock_after_load_cookie:
946 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
947 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
948 self.__pat.locked = True
949 self.__pat_lock_cookie = str(random.random())
950 return (1, self.__pat_lock_cookie)
951 #-----------------------------------------------------------------
953 if not self.__attached:
954 return (0, _('request rejected, you are not attach()ed'))
955 if auth_cookie != self.__auth_cookie:
956 _log.error('non-authenticated lock_into_patient()')
957 return (0, _('rejected lock_into_patient(), not authenticated'))
958 if self.__pat.locked:
959 _log.error('patient is already locked')
960 return (0, _('already locked into a patient'))
961 searcher = gmPersonSearch.cPatientSearcher_SQL()
962 if type(search_params) == types.DictType:
963 idents = searcher.get_identities(search_dict=search_params)
964 raise StandardError("must use dto, not search_dict")
965 else:
966 idents = searcher.get_identities(search_term=search_params)
967 if idents is None:
968 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
969 if len(idents) == 0:
970 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
971 # FIXME: let user select patient
972 if len(idents) > 1:
973 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
974 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
975 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
976 self.__pat.locked = True
977 self.__pat_lock_cookie = str(random.random())
978 return (1, self.__pat_lock_cookie)
979 #-----------------------------------------------------------------
981 if not self.__attached:
982 return (0, _('request rejected, you are not attach()ed'))
983 if auth_cookie != self.__auth_cookie:
984 _log.error('non-authenticated unlock_patient()')
985 return (0, _('rejected unlock_patient, not authenticated'))
986 # we ain't locked anyways, so succeed
987 if not self.__pat.locked:
988 return (1, '')
989 # FIXME: ask user what to do about wrong cookie
990 if unlock_cookie != self.__pat_lock_cookie:
991 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
992 return (0, 'patient unlock request rejected, wrong cookie provided')
993 self.__pat.locked = False
994 return (1, '')
995 #-----------------------------------------------------------------
996 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
997 if not self.__attached:
998 return 0
999 if auth_cookie != self.__auth_cookie:
1000 _log.error('non-authenticated select_identity()')
1001 return 0
1002 return "cMacroPrimitives.assume_staff_identity() not implemented"
1003 #-----------------------------------------------------------------
1005 if not self.__user_done:
1006 return (0, 'still waiting')
1007 self.__user_done = False
1008 return (1, self.__user_answer)
1009 #-----------------------------------------------------------------
1010 # internal API
1011 #-----------------------------------------------------------------
1013 msg = _(
1014 'Someone tries to forcibly break the existing\n'
1015 'controlling connection. This may or may not\n'
1016 'have legitimate reasons.\n\n'
1017 'Do you want to allow breaking the connection ?'
1018 )
1019 can_break_conn = gmGuiHelpers.gm_show_question (
1020 aMessage = msg,
1021 aTitle = _('forced detach attempt')
1022 )
1023 if can_break_conn:
1024 self.__user_answer = 1
1025 else:
1026 self.__user_answer = 0
1027 self.__user_done = True
1028 if can_break_conn:
1029 self.__pat.locked = False
1030 self.__attached = 0
1031 return 1
1032 #-----------------------------------------------------------------
1034 top_win = wx.GetApp().GetTopWindow()
1035 if forced:
1036 top_win.Destroy()
1037 else:
1038 top_win.Close()
1039 #-----------------------------------------------------------------
1041 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True)
1042 if patient is not None:
1043 self.__user_answer = 1
1044 else:
1045 self.__user_answer = 0
1046 self.__user_done = True
1047 return 1
1048 #=====================================================================
1049 # main
1050 #=====================================================================
1051 if __name__ == '__main__':
1052
1053 if len(sys.argv) < 2:
1054 sys.exit()
1055
1056 if sys.argv[1] != 'test':
1057 sys.exit()
1058
1059 gmI18N.activate_locale()
1060 gmI18N.install_domain()
1061
1062 #--------------------------------------------------------
1064 handler = gmPlaceholderHandler()
1065 handler.debug = True
1066
1067 for placeholder in ['a', 'b']:
1068 print handler[placeholder]
1069
1070 pat = gmPersonSearch.ask_for_patient()
1071 if pat is None:
1072 return
1073
1074 gmPatSearchWidgets.set_active_patient(patient = pat)
1075
1076 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1077
1078 app = wx.PyWidgetTester(size = (200, 50))
1079 for placeholder in known_placeholders:
1080 print placeholder, "=", handler[placeholder]
1081
1082 ph = 'progress_notes::ap'
1083 print '%s: %s' % (ph, handler[ph])
1084 #--------------------------------------------------------
1086
1087 tests = [
1088 # should work:
1089 '$<lastname>$',
1090 '$<lastname::::3>$',
1091 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1092
1093 # should fail:
1094 'lastname',
1095 '$<lastname',
1096 '$<lastname::',
1097 '$<lastname::>$',
1098 '$<lastname::abc>$',
1099 '$<lastname::abc::>$',
1100 '$<lastname::abc::3>$',
1101 '$<lastname::abc::xyz>$',
1102 '$<lastname::::>$',
1103 '$<lastname::::xyz>$',
1104
1105 '$<date_of_birth::%Y-%m-%d>$',
1106 '$<date_of_birth::%Y-%m-%d::3>$',
1107 '$<date_of_birth::%Y-%m-%d::>$',
1108
1109 # should work:
1110 '$<adr_location::home::35>$',
1111 '$<gender_mapper::male//female//other::5>$',
1112 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1113 '$<allergy_list::%(descriptor)s, >$',
1114 '$<current_meds_table::latex//by-brand>$'
1115
1116 # 'firstname',
1117 # 'title',
1118 # 'date_of_birth',
1119 # 'progress_notes',
1120 # 'soap',
1121 # 'soap_s',
1122 # 'soap_o',
1123 # 'soap_a',
1124 # 'soap_p',
1125
1126 # 'soap',
1127 # 'progress_notes',
1128 # 'date_of_birth'
1129 ]
1130
1131 tests = [
1132 '$<latest_vaccs_table::latex>$'
1133 ]
1134
1135 pat = gmPersonSearch.ask_for_patient()
1136 if pat is None:
1137 return
1138
1139 gmPatSearchWidgets.set_active_patient(patient = pat)
1140
1141 handler = gmPlaceholderHandler()
1142 handler.debug = True
1143
1144 for placeholder in tests:
1145 print placeholder, "=>", handler[placeholder]
1146 print "--------------"
1147 raw_input()
1148
1149 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1150
1151 # app = wx.PyWidgetTester(size = (200, 50))
1152 # for placeholder in known_placeholders:
1153 # print placeholder, "=", handler[placeholder]
1154
1155 # ph = 'progress_notes::ap'
1156 # print '%s: %s' % (ph, handler[ph])
1157
1158 #--------------------------------------------------------
1160 from Gnumed.pycommon import gmScriptingListener
1161 import xmlrpclib
1162 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1163
1164 s = xmlrpclib.ServerProxy('http://localhost:9999')
1165 print "should fail:", s.attach()
1166 print "should fail:", s.attach('wrong cookie')
1167 print "should work:", s.version()
1168 print "should fail:", s.raise_gnumed()
1169 print "should fail:", s.raise_notebook_plugin('test plugin')
1170 print "should fail:", s.lock_into_patient('kirk, james')
1171 print "should fail:", s.unlock_patient()
1172 status, conn_auth = s.attach('unit test')
1173 print "should work:", status, conn_auth
1174 print "should work:", s.version()
1175 print "should work:", s.raise_gnumed(conn_auth)
1176 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1177 print "should work:", status, pat_auth
1178 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1179 print "should work", s.unlock_patient(conn_auth, pat_auth)
1180 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1181 status, pat_auth = s.lock_into_patient(conn_auth, data)
1182 print "should work:", status, pat_auth
1183 print "should work", s.unlock_patient(conn_auth, pat_auth)
1184 print s.detach('bogus detach cookie')
1185 print s.detach(conn_auth)
1186 del s
1187
1188 listener.shutdown()
1189 #--------------------------------------------------------
1191
1192 import re as regex
1193
1194 tests = [
1195 ' $<lastname>$ ',
1196 ' $<lastname::::3>$ ',
1197
1198 # should fail:
1199 '$<date_of_birth::%Y-%m-%d>$',
1200 '$<date_of_birth::%Y-%m-%d::3>$',
1201 '$<date_of_birth::%Y-%m-%d::>$',
1202
1203 '$<adr_location::home::35>$',
1204 '$<gender_mapper::male//female//other::5>$',
1205 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1206 '$<allergy_list::%(descriptor)s, >$',
1207
1208 '\\noindent Patient: $<lastname>$, $<firstname>$',
1209 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1210 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1211 ]
1212
1213 tests = [
1214
1215 'junk $<lastname::::3>$ junk',
1216 'junk $<lastname::abc::3>$ junk',
1217 'junk $<lastname::abc>$ junk',
1218 'junk $<lastname>$ junk',
1219
1220 'junk $<lastname>$ junk $<firstname>$ junk',
1221 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1222 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1223 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1224
1225 ]
1226
1227 print "testing placeholder regex:", default_placeholder_regex
1228 print ""
1229
1230 for t in tests:
1231 print 'line: "%s"' % t
1232 print "placeholders:"
1233 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1234 print ' => "%s"' % p
1235 print " "
1236 #--------------------------------------------------------
1238
1239 phs = [
1240 #u'emr_journal::soap //%(date)s %(modified_by)s %(soap_cat)s %(narrative)s//30::',
1241 #u'free_text::tex//placeholder test::9999',
1242 #u'soap_for_encounters:://::9999',
1243 #u'soap_a',,
1244 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30',
1245 u'patient_comm::homephone::1234',
1246 #u'patient_address::home//::1234',
1247 #u'adr_region::home::1234',
1248 #u'adr_country::home::1234',
1249 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
1250 #u'primary_praxis_provider',
1251 #u'current_provider'
1252 ]
1253
1254 handler = gmPlaceholderHandler()
1255 handler.debug = True
1256
1257 gmPerson.set_current_provider_to_logged_on_user()
1258 pat = gmPersonSearch.ask_for_patient()
1259 if pat is None:
1260 return
1261
1262 gmPatSearchWidgets.set_active_patient(patient = pat)
1263
1264 app = wx.PyWidgetTester(size = (200, 50))
1265 for ph in phs:
1266 print u'%s => %s' % (ph, handler[ph])
1267 #--------------------------------------------------------
1268
1269 #test_placeholders()
1270 #test_new_variant_placeholders()
1271 #test_scripting()
1272 #test_placeholder_regex()
1273 test_placeholder()
1274
1275 #=====================================================================
1276
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Oct 18 04:00:32 2011 | http://epydoc.sourceforge.net |