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