| 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 #--------------------------------------------------------
394
395 # placeholders
396 lastname = property(_get_lastname, _setter_noop)
397 firstname = property(_get_firstname, _setter_noop)
398 title = property(_get_title, _setter_noop)
399 date_of_birth = property(_get_dob, _setter_noop)
400
401 progress_notes = property(_get_progress_notes, _setter_noop)
402 soap = property(_get_progress_notes, _setter_noop)
403 soap_s = property(_get_soap_s, _setter_noop)
404 soap_o = property(_get_soap_o, _setter_noop)
405 soap_a = property(_get_soap_a, _setter_noop)
406 soap_p = property(_get_soap_p, _setter_noop)
407 soap_u = property(_get_soap_u, _setter_noop)
408 soap_admin = property(_get_soap_admin, _setter_noop)
409
410 allergy_state = property(_get_allergy_state, _setter_noop)
411
412 client_version = property(_get_client_version, _setter_noop)
413
414 current_provider = property(_get_current_provider, _setter_noop)
415 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
416 #--------------------------------------------------------
417 # variant handlers
418 #--------------------------------------------------------
420
421 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
422 if not encounters:
423 return u''
424
425 template = data
426
427 lines = []
428 for enc in encounters:
429 try:
430 lines.append(template % enc)
431 except:
432 lines.append(u'error formatting encounter')
433 _log.exception('problem formatting encounter list')
434 _log.error('template: %s', template)
435 _log.error('encounter: %s', encounter)
436
437 return u'\n'.join(lines)
438 #--------------------------------------------------------
440 """Select encounters from list and format SOAP thereof.
441
442 data: soap_cats (' ' -> None -> admin) // date format
443 """
444 # defaults
445 cats = None
446 date_format = None
447
448 if data is not None:
449 data_parts = data.split('//')
450
451 # part[0]: categories
452 if len(data_parts[0]) > 0:
453 cats = []
454 if u' ' in data_parts[0]:
455 cats.append(None)
456 data_parts[0] = data_parts[0].replace(u' ', u'')
457 cats.extend(list(data_parts[0]))
458
459 # part[1]: date format
460 if len(data_parts) > 1:
461 if len(data_parts[1]) > 0:
462 date_format = data_parts[1]
463
464 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
465 if not encounters:
466 return u''
467
468 chunks = []
469 for enc in encounters:
470 chunks.append(enc.format_latex (
471 date_format = date_format,
472 soap_cats = cats,
473 soap_order = u'soap_rank, date'
474 ))
475
476 return u''.join(chunks)
477 #--------------------------------------------------------
479 # default: all categories, neutral template
480 cats = list(u'soapu')
481 cats.append(None)
482 template = u'%s'
483 interactive = True
484 line_length = 9999
485 target_format = None
486 time_range = None
487
488 if data is not None:
489 data_parts = data.split('//')
490
491 # part[0]: categories
492 cats = []
493 # ' ' -> None == admin
494 for c in list(data_parts[0]):
495 if c == u' ':
496 c = None
497 cats.append(c)
498 # '' -> SOAP + None
499 if cats == u'':
500 cats = list(u'soapu').append(None)
501
502 # part[1]: template
503 if len(data_parts) > 1:
504 template = data_parts[1]
505
506 # part[2]: line length
507 if len(data_parts) > 2:
508 try:
509 line_length = int(data_parts[2])
510 except:
511 line_length = 9999
512
513 # part[3]: weeks going back in time
514 if len(data_parts) > 3:
515 try:
516 time_range = 7 * int(data_parts[3])
517 except:
518 time_range = None
519
520 # part[4]: output format
521 if len(data_parts) > 4:
522 target_format = data_parts[4]
523
524 # FIXME: will need to be a generator later on
525 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
526
527 if len(narr) == 0:
528 return u''
529
530 if target_format == u'tex':
531 keys = narr[0].keys()
532 lines = []
533 line_dict = {}
534 for n in narr:
535 for key in keys:
536 if isinstance(n[key], basestring):
537 line_dict[key] = gmTools.tex_escape_string(text = n[key])
538 continue
539 line_dict[key] = n[key]
540 try:
541 lines.append((template % line_dict)[:line_length])
542 except KeyError:
543 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
544 else:
545 try:
546 lines = [ (template % n)[:line_length] for n in narr ]
547 except KeyError:
548 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
549
550 return u'\n'.join(lines)
551 #--------------------------------------------------------
554 #--------------------------------------------------------
556
557 # default: all categories, neutral template
558 cats = list(u'soapu')
559 cats.append(None)
560 template = u'%s'
561
562 if data is not None:
563 data_parts = data.split('//')
564
565 # part[0]: categories
566 cats = []
567 # ' ' -> None == admin
568 for cat in list(data_parts[0]):
569 if cat == u' ':
570 cat = None
571 cats.append(cat)
572 # '' -> SOAP + None
573 if cats == u'':
574 cats = list(u'soapu')
575 cats.append(None)
576
577 # part[1]: template
578 if len(data_parts) > 1:
579 template = data_parts[1]
580
581 #narr = gmNarrativeWidgets.select_narrative_from_episodes_new(soap_cats = cats)
582 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
583
584 if narr is None:
585 return u''
586
587 if len(narr) == 0:
588 return u''
589
590 try:
591 narr = [ template % n['narrative'] for n in narr ]
592 except KeyError:
593 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
594
595 return u'\n'.join(narr)
596 #--------------------------------------------------------
598 if data is None:
599 return [_('template is missing')]
600
601 name = self.pat.get_active_name()
602
603 parts = {
604 'title': gmTools.coalesce(name['title'], u''),
605 'firstnames': name['firstnames'],
606 'lastnames': name['lastnames'],
607 'preferred': gmTools.coalesce (
608 initial = name['preferred'],
609 instead = u' ',
610 template_initial = u' "%s" '
611 )
612 }
613
614 return data % parts
615 #--------------------------------------------------------
618 #--------------------------------------------------------
619 # FIXME: extend to all supported genders
621 values = data.split('//', 2)
622
623 if len(values) == 2:
624 male_value, female_value = values
625 other_value = u'<unkown gender>'
626 elif len(values) == 3:
627 male_value, female_value, other_value = values
628 else:
629 return _('invalid gender mapping layout: [%s]') % data
630
631 if self.pat['gender'] == u'm':
632 return male_value
633
634 if self.pat['gender'] == u'f':
635 return female_value
636
637 return other_value
638 #--------------------------------------------------------
639 # address related placeholders
640 #--------------------------------------------------------
642
643 data_parts = data.split(u'//')
644
645 if data_parts[0].strip() == u'':
646 adr_type = u'home'
647 else:
648 adr_type = data_parts[0]
649
650 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
651 if len(data_parts) > 1:
652 if data_parts[1].strip() != u'':
653 template = data_parts[1]
654
655 adrs = self.pat.get_addresses(address_type = adr_type)
656 if len(adrs) == 0:
657 if self.debug:
658 return _('no address for type [%s]') % adr_type
659 return u''
660
661 adr = adrs[0]
662 data = {
663 'street': adr['street'],
664 'notes_street': gmTools.coalesce(adr['notes_street'], u''),
665 'postcode': adr['postcode'],
666 'number': adr['number'],
667 'subunit': gmTools.coalesce(adr['subunit'], u''),
668 'notes_subunit': gmTools.coalesce(adr['notes_subunit'], u''),
669 'urb': adr['urb'],
670 'suburb': gmTools.coalesce(adr['suburb'], u''),
671 'l10n_state': adr['l10n_state'],
672 'l10n_country': adr['l10n_country'],
673 'code_state': adr['code_state'],
674 'code_country': adr['code_country']
675 }
676
677 try:
678 return template % data
679 except StandardError:
680 _log.exception('error formatting address')
681 _log.error('template: %s', template)
682
683 return None
684 #--------------------------------------------------------
686 adrs = self.pat.get_addresses(address_type=data)
687 if len(adrs) == 0:
688 if self.debug:
689 return _('no street for address type [%s]') % data
690 return u''
691 return adrs[0]['street']
692 #--------------------------------------------------------
694 adrs = self.pat.get_addresses(address_type=data)
695 if len(adrs) == 0:
696 if self.debug:
697 return _('no number for address type [%s]') % data
698 return u''
699 return adrs[0]['number']
700 #--------------------------------------------------------
702 adrs = self.pat.get_addresses(address_type=data)
703 if len(adrs) == 0:
704 if self.debug:
705 return _('no location for address type [%s]') % data
706 return u''
707 return adrs[0]['urb']
708 #--------------------------------------------------------
710 adrs = self.pat.get_addresses(address_type = data)
711 if len(adrs) == 0:
712 if self.debug:
713 return _('no postcode for address type [%s]') % data
714 return u''
715 return adrs[0]['postcode']
716 #--------------------------------------------------------
718 adrs = self.pat.get_addresses(address_type = data)
719 if len(adrs) == 0:
720 if self.debug:
721 return _('no region for address type [%s]') % data
722 return u''
723 return adrs[0]['l10n_state']
724 #--------------------------------------------------------
726 adrs = self.pat.get_addresses(address_type = data)
727 if len(adrs) == 0:
728 if self.debug:
729 return _('no country for address type [%s]') % data
730 return u''
731 return adrs[0]['l10n_country']
732 #--------------------------------------------------------
734 comms = self.pat.get_comm_channels(comm_medium = data)
735 if len(comms) == 0:
736 if self.debug:
737 return _('no URL for comm channel [%s]') % data
738 return u''
739 return comms[0]['url']
740 #--------------------------------------------------------
742 data_parts = data.split(u'//')
743 if len(data_parts) < 2:
744 return u'current provider external ID: template is missing'
745
746 id_type = data_parts[0].strip()
747 if id_type == u'':
748 return u'current provider external ID: type is missing'
749
750 issuer = data_parts[1].strip()
751 if issuer == u'':
752 return u'current provider external ID: issuer is missing'
753
754 prov = gmStaff.gmCurrentProvider()
755 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
756
757 if len(ids) == 0:
758 if self.debug:
759 return _('no external ID [%s] by [%s]') % (id_type, issuer)
760 return u''
761
762 return ids[0]['value']
763 #--------------------------------------------------------
765 data_parts = data.split(u'//')
766 if len(data_parts) < 2:
767 return u'primary in-praxis provider external ID: template is missing'
768
769 id_type = data_parts[0].strip()
770 if id_type == u'':
771 return u'primary in-praxis provider external ID: type is missing'
772
773 issuer = data_parts[1].strip()
774 if issuer == u'':
775 return u'primary in-praxis provider external ID: issuer is missing'
776
777 prov = self.pat.primary_provider
778 if prov is None:
779 if self.debug:
780 return _('no primary in-praxis provider')
781 return u''
782
783 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
784
785 if len(ids) == 0:
786 if self.debug:
787 return _('no external ID [%s] by [%s]') % (id_type, issuer)
788 return u''
789
790 return ids[0]['value']
791 #--------------------------------------------------------
793 data_parts = data.split(u'//')
794 if len(data_parts) < 2:
795 return u'patient external ID: template is missing'
796
797 id_type = data_parts[0].strip()
798 if id_type == u'':
799 return u'patient external ID: type is missing'
800
801 issuer = data_parts[1].strip()
802 if issuer == u'':
803 return u'patient external ID: issuer is missing'
804
805 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
806
807 if len(ids) == 0:
808 if self.debug:
809 return _('no external ID [%s] by [%s]') % (id_type, issuer)
810 return u''
811
812 return ids[0]['value']
813 #--------------------------------------------------------
815 if data is None:
816 return [_('template is missing')]
817
818 template, separator = data.split('//', 2)
819
820 emr = self.pat.get_emr()
821 return separator.join([ template % a for a in emr.get_allergies() ])
822 #--------------------------------------------------------
824
825 if data is None:
826 return [_('template is missing')]
827
828 emr = self.pat.get_emr()
829 return u'\n'.join([ data % a for a in emr.get_allergies() ])
830 #--------------------------------------------------------
832
833 if data is None:
834 return [_('template is missing')]
835
836 emr = self.pat.get_emr()
837 current_meds = emr.get_current_substance_intake (
838 include_inactive = False,
839 include_unapproved = False,
840 order_by = u'brand, substance'
841 )
842
843 return u'\n'.join([ data % m.fields_as_dict(date_format = '%Y %B %d') for m in current_meds ])
844 #--------------------------------------------------------
846
847 options = data.split('//')
848
849 if u'latex' in options:
850 return gmMedication.format_substance_intake (
851 emr = self.pat.get_emr(),
852 output_format = u'latex',
853 table_type = u'by-brand'
854 )
855
856 _log.error('no known current medications table formatting style in [%s]', data)
857 return _('unknown current medication table formatting style')
858 #--------------------------------------------------------
860
861 options = data.split('//')
862
863 if u'latex' in options:
864 return gmMedication.format_substance_intake_notes (
865 emr = self.pat.get_emr(),
866 output_format = u'latex',
867 table_type = u'by-brand'
868 )
869
870 _log.error('no known current medications notes formatting style in [%s]', data)
871 return _('unknown current medication notes formatting style')
872 #--------------------------------------------------------
874
875 options = data.split('//')
876
877 emr = self.pat.get_emr()
878
879 if u'latex' in options:
880 return gmPathLab.format_test_results (
881 results = emr.get_test_results_by_date(),
882 output_format = u'latex'
883 )
884
885 _log.error('no known test results table formatting style in [%s]', data)
886 return _('unknown test results table formatting style [%s]') % data
887 #--------------------------------------------------------
889
890 options = data.split('//')
891
892 emr = self.pat.get_emr()
893
894 if u'latex' in options:
895 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr)
896
897 _log.error('no known vaccinations table formatting style in [%s]', data)
898 return _('unknown vaccinations table formatting style [%s]') % data
899 #--------------------------------------------------------
901
902 if data is None:
903 return [_('template is missing')]
904
905 probs = self.pat.get_emr().get_problems()
906
907 return u'\n'.join([ data % p for p in probs ])
908 #--------------------------------------------------------
911 #--------------------------------------------------------
914 #--------------------------------------------------------
916 # <data>:
917 # format: tex (only, currently)
918 # message: shown in input dialog, must not contain "//" or "::"
919
920 data_parts = data.split('//')
921 format = data_parts[0]
922 if len(data_parts) > 1:
923 msg = data_parts[1]
924 else:
925 msg = _('generic text')
926
927 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
928 None,
929 -1,
930 title = _('Replacing <free_text> placeholder'),
931 msg = _('Below you can enter free text.\n\n [%s]') % msg
932 )
933 dlg.enable_user_formatting = True
934 decision = dlg.ShowModal()
935
936 if decision != wx.ID_SAVE:
937 dlg.Destroy()
938 if self.debug:
939 return _('Text input cancelled by user.')
940 return u''
941
942 text = dlg.value.strip()
943 if dlg.is_user_formatted:
944 dlg.Destroy()
945 return text
946
947 dlg.Destroy()
948
949 if format == u'tex':
950 return gmTools.tex_escape_string(text = text)
951
952 return text
953 #--------------------------------------------------------
954 # internal helpers
955 #--------------------------------------------------------
956
957 #=====================================================================
959 """Functions a macro can legally use.
960
961 An instance of this class is passed to the GNUmed scripting
962 listener. Hence, all actions a macro can legally take must
963 be defined in this class. Thus we achieve some screening for
964 security and also thread safety handling.
965 """
966 #-----------------------------------------------------------------
968 if personality is None:
969 raise gmExceptions.ConstructorError, 'must specify personality'
970 self.__personality = personality
971 self.__attached = 0
972 self._get_source_personality = None
973 self.__user_done = False
974 self.__user_answer = 'no answer yet'
975 self.__pat = gmPerson.gmCurrentPatient()
976
977 self.__auth_cookie = str(random.random())
978 self.__pat_lock_cookie = str(random.random())
979 self.__lock_after_load_cookie = str(random.random())
980
981 _log.info('slave mode personality is [%s]', personality)
982 #-----------------------------------------------------------------
983 # public API
984 #-----------------------------------------------------------------
986 if self.__attached:
987 _log.error('attach with [%s] rejected, already serving a client', personality)
988 return (0, _('attach rejected, already serving a client'))
989 if personality != self.__personality:
990 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
991 return (0, _('attach to personality [%s] rejected') % personality)
992 self.__attached = 1
993 self.__auth_cookie = str(random.random())
994 return (1, self.__auth_cookie)
995 #-----------------------------------------------------------------
997 if not self.__attached:
998 return 1
999 if auth_cookie != self.__auth_cookie:
1000 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
1001 return 0
1002 self.__attached = 0
1003 return 1
1004 #-----------------------------------------------------------------
1006 if not self.__attached:
1007 return 1
1008 self.__user_done = False
1009 # FIXME: use self.__sync_cookie for syncing with user interaction
1010 wx.CallAfter(self._force_detach)
1011 return 1
1012 #-----------------------------------------------------------------
1014 ver = _cfg.get(option = u'client_version')
1015 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1016 #-----------------------------------------------------------------
1018 """Shuts down this client instance."""
1019 if not self.__attached:
1020 return 0
1021 if auth_cookie != self.__auth_cookie:
1022 _log.error('non-authenticated shutdown_gnumed()')
1023 return 0
1024 wx.CallAfter(self._shutdown_gnumed, forced)
1025 return 1
1026 #-----------------------------------------------------------------
1028 """Raise ourselves to the top of the desktop."""
1029 if not self.__attached:
1030 return 0
1031 if auth_cookie != self.__auth_cookie:
1032 _log.error('non-authenticated raise_gnumed()')
1033 return 0
1034 return "cMacroPrimitives.raise_gnumed() not implemented"
1035 #-----------------------------------------------------------------
1037 if not self.__attached:
1038 return 0
1039 if auth_cookie != self.__auth_cookie:
1040 _log.error('non-authenticated get_loaded_plugins()')
1041 return 0
1042 gb = gmGuiBroker.GuiBroker()
1043 return gb['horstspace.notebook.gui'].keys()
1044 #-----------------------------------------------------------------
1046 """Raise a notebook plugin within GNUmed."""
1047 if not self.__attached:
1048 return 0
1049 if auth_cookie != self.__auth_cookie:
1050 _log.error('non-authenticated raise_notebook_plugin()')
1051 return 0
1052 # FIXME: use semaphore
1053 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1054 return 1
1055 #-----------------------------------------------------------------
1057 """Load external patient, perhaps create it.
1058
1059 Callers must use get_user_answer() to get status information.
1060 It is unsafe to proceed without knowing the completion state as
1061 the controlled client may be waiting for user input from a
1062 patient selection list.
1063 """
1064 if not self.__attached:
1065 return (0, _('request rejected, you are not attach()ed'))
1066 if auth_cookie != self.__auth_cookie:
1067 _log.error('non-authenticated load_patient_from_external_source()')
1068 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1069 if self.__pat.locked:
1070 _log.error('patient is locked, cannot load from external source')
1071 return (0, _('current patient is locked'))
1072 self.__user_done = False
1073 wx.CallAfter(self._load_patient_from_external_source)
1074 self.__lock_after_load_cookie = str(random.random())
1075 return (1, self.__lock_after_load_cookie)
1076 #-----------------------------------------------------------------
1078 if not self.__attached:
1079 return (0, _('request rejected, you are not attach()ed'))
1080 if auth_cookie != self.__auth_cookie:
1081 _log.error('non-authenticated lock_load_patient()')
1082 return (0, _('rejected lock_load_patient(), not authenticated'))
1083 # FIXME: ask user what to do about wrong cookie
1084 if lock_after_load_cookie != self.__lock_after_load_cookie:
1085 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1086 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1087 self.__pat.locked = True
1088 self.__pat_lock_cookie = str(random.random())
1089 return (1, self.__pat_lock_cookie)
1090 #-----------------------------------------------------------------
1092 if not self.__attached:
1093 return (0, _('request rejected, you are not attach()ed'))
1094 if auth_cookie != self.__auth_cookie:
1095 _log.error('non-authenticated lock_into_patient()')
1096 return (0, _('rejected lock_into_patient(), not authenticated'))
1097 if self.__pat.locked:
1098 _log.error('patient is already locked')
1099 return (0, _('already locked into a patient'))
1100 searcher = gmPersonSearch.cPatientSearcher_SQL()
1101 if type(search_params) == types.DictType:
1102 idents = searcher.get_identities(search_dict=search_params)
1103 raise StandardError("must use dto, not search_dict")
1104 else:
1105 idents = searcher.get_identities(search_term=search_params)
1106 if idents is None:
1107 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1108 if len(idents) == 0:
1109 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1110 # FIXME: let user select patient
1111 if len(idents) > 1:
1112 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1113 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1114 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1115 self.__pat.locked = True
1116 self.__pat_lock_cookie = str(random.random())
1117 return (1, self.__pat_lock_cookie)
1118 #-----------------------------------------------------------------
1120 if not self.__attached:
1121 return (0, _('request rejected, you are not attach()ed'))
1122 if auth_cookie != self.__auth_cookie:
1123 _log.error('non-authenticated unlock_patient()')
1124 return (0, _('rejected unlock_patient, not authenticated'))
1125 # we ain't locked anyways, so succeed
1126 if not self.__pat.locked:
1127 return (1, '')
1128 # FIXME: ask user what to do about wrong cookie
1129 if unlock_cookie != self.__pat_lock_cookie:
1130 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1131 return (0, 'patient unlock request rejected, wrong cookie provided')
1132 self.__pat.locked = False
1133 return (1, '')
1134 #-----------------------------------------------------------------
1135 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
1136 if not self.__attached:
1137 return 0
1138 if auth_cookie != self.__auth_cookie:
1139 _log.error('non-authenticated select_identity()')
1140 return 0
1141 return "cMacroPrimitives.assume_staff_identity() not implemented"
1142 #-----------------------------------------------------------------
1144 if not self.__user_done:
1145 return (0, 'still waiting')
1146 self.__user_done = False
1147 return (1, self.__user_answer)
1148 #-----------------------------------------------------------------
1149 # internal API
1150 #-----------------------------------------------------------------
1152 msg = _(
1153 'Someone tries to forcibly break the existing\n'
1154 'controlling connection. This may or may not\n'
1155 'have legitimate reasons.\n\n'
1156 'Do you want to allow breaking the connection ?'
1157 )
1158 can_break_conn = gmGuiHelpers.gm_show_question (
1159 aMessage = msg,
1160 aTitle = _('forced detach attempt')
1161 )
1162 if can_break_conn:
1163 self.__user_answer = 1
1164 else:
1165 self.__user_answer = 0
1166 self.__user_done = True
1167 if can_break_conn:
1168 self.__pat.locked = False
1169 self.__attached = 0
1170 return 1
1171 #-----------------------------------------------------------------
1173 top_win = wx.GetApp().GetTopWindow()
1174 if forced:
1175 top_win.Destroy()
1176 else:
1177 top_win.Close()
1178 #-----------------------------------------------------------------
1180 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True)
1181 if patient is not None:
1182 self.__user_answer = 1
1183 else:
1184 self.__user_answer = 0
1185 self.__user_done = True
1186 return 1
1187 #=====================================================================
1188 # main
1189 #=====================================================================
1190 if __name__ == '__main__':
1191
1192 if len(sys.argv) < 2:
1193 sys.exit()
1194
1195 if sys.argv[1] != 'test':
1196 sys.exit()
1197
1198 gmI18N.activate_locale()
1199 gmI18N.install_domain()
1200
1201 #--------------------------------------------------------
1203 handler = gmPlaceholderHandler()
1204 handler.debug = True
1205
1206 for placeholder in ['a', 'b']:
1207 print handler[placeholder]
1208
1209 pat = gmPersonSearch.ask_for_patient()
1210 if pat is None:
1211 return
1212
1213 gmPatSearchWidgets.set_active_patient(patient = pat)
1214
1215 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1216
1217 app = wx.PyWidgetTester(size = (200, 50))
1218 for placeholder in known_placeholders:
1219 print placeholder, "=", handler[placeholder]
1220
1221 ph = 'progress_notes::ap'
1222 print '%s: %s' % (ph, handler[ph])
1223 #--------------------------------------------------------
1225
1226 tests = [
1227 # should work:
1228 '$<lastname>$',
1229 '$<lastname::::3>$',
1230 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1231
1232 # should fail:
1233 'lastname',
1234 '$<lastname',
1235 '$<lastname::',
1236 '$<lastname::>$',
1237 '$<lastname::abc>$',
1238 '$<lastname::abc::>$',
1239 '$<lastname::abc::3>$',
1240 '$<lastname::abc::xyz>$',
1241 '$<lastname::::>$',
1242 '$<lastname::::xyz>$',
1243
1244 '$<date_of_birth::%Y-%m-%d>$',
1245 '$<date_of_birth::%Y-%m-%d::3>$',
1246 '$<date_of_birth::%Y-%m-%d::>$',
1247
1248 # should work:
1249 '$<adr_location::home::35>$',
1250 '$<gender_mapper::male//female//other::5>$',
1251 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1252 '$<allergy_list::%(descriptor)s, >$',
1253 '$<current_meds_table::latex//by-brand>$'
1254
1255 # 'firstname',
1256 # 'title',
1257 # 'date_of_birth',
1258 # 'progress_notes',
1259 # 'soap',
1260 # 'soap_s',
1261 # 'soap_o',
1262 # 'soap_a',
1263 # 'soap_p',
1264
1265 # 'soap',
1266 # 'progress_notes',
1267 # 'date_of_birth'
1268 ]
1269
1270 tests = [
1271 '$<latest_vaccs_table::latex>$'
1272 ]
1273
1274 pat = gmPersonSearch.ask_for_patient()
1275 if pat is None:
1276 return
1277
1278 gmPatSearchWidgets.set_active_patient(patient = pat)
1279
1280 handler = gmPlaceholderHandler()
1281 handler.debug = True
1282
1283 for placeholder in tests:
1284 print placeholder, "=>", handler[placeholder]
1285 print "--------------"
1286 raw_input()
1287
1288 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1289
1290 # app = wx.PyWidgetTester(size = (200, 50))
1291 # for placeholder in known_placeholders:
1292 # print placeholder, "=", handler[placeholder]
1293
1294 # ph = 'progress_notes::ap'
1295 # print '%s: %s' % (ph, handler[ph])
1296
1297 #--------------------------------------------------------
1299 from Gnumed.pycommon import gmScriptingListener
1300 import xmlrpclib
1301 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1302
1303 s = xmlrpclib.ServerProxy('http://localhost:9999')
1304 print "should fail:", s.attach()
1305 print "should fail:", s.attach('wrong cookie')
1306 print "should work:", s.version()
1307 print "should fail:", s.raise_gnumed()
1308 print "should fail:", s.raise_notebook_plugin('test plugin')
1309 print "should fail:", s.lock_into_patient('kirk, james')
1310 print "should fail:", s.unlock_patient()
1311 status, conn_auth = s.attach('unit test')
1312 print "should work:", status, conn_auth
1313 print "should work:", s.version()
1314 print "should work:", s.raise_gnumed(conn_auth)
1315 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1316 print "should work:", status, pat_auth
1317 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1318 print "should work", s.unlock_patient(conn_auth, pat_auth)
1319 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1320 status, pat_auth = s.lock_into_patient(conn_auth, data)
1321 print "should work:", status, pat_auth
1322 print "should work", s.unlock_patient(conn_auth, pat_auth)
1323 print s.detach('bogus detach cookie')
1324 print s.detach(conn_auth)
1325 del s
1326
1327 listener.shutdown()
1328 #--------------------------------------------------------
1330
1331 import re as regex
1332
1333 tests = [
1334 ' $<lastname>$ ',
1335 ' $<lastname::::3>$ ',
1336
1337 # should fail:
1338 '$<date_of_birth::%Y-%m-%d>$',
1339 '$<date_of_birth::%Y-%m-%d::3>$',
1340 '$<date_of_birth::%Y-%m-%d::>$',
1341
1342 '$<adr_location::home::35>$',
1343 '$<gender_mapper::male//female//other::5>$',
1344 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1345 '$<allergy_list::%(descriptor)s, >$',
1346
1347 '\\noindent Patient: $<lastname>$, $<firstname>$',
1348 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1349 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1350 ]
1351
1352 tests = [
1353
1354 'junk $<lastname::::3>$ junk',
1355 'junk $<lastname::abc::3>$ junk',
1356 'junk $<lastname::abc>$ junk',
1357 'junk $<lastname>$ junk',
1358
1359 'junk $<lastname>$ junk $<firstname>$ junk',
1360 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1361 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1362 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1363
1364 ]
1365
1366 print "testing placeholder regex:", default_placeholder_regex
1367 print ""
1368
1369 for t in tests:
1370 print 'line: "%s"' % t
1371 print "placeholders:"
1372 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1373 print ' => "%s"' % p
1374 print " "
1375 #--------------------------------------------------------
1377
1378 phs = [
1379 #u'emr_journal::soapu //%(date)s %(modified_by)s %(soap_cat)s %(narrative)s//30::',
1380 #u'free_text::tex//placeholder test::9999',
1381 #u'soap_for_encounters:://::9999',
1382 #u'soap_a',,
1383 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30',
1384 #u'patient_comm::homephone::1234',
1385 #u'patient_address::home//::1234',
1386 #u'adr_region::home::1234',
1387 #u'adr_country::home::1234',
1388 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
1389 #u'primary_praxis_provider',
1390 #u'current_provider',
1391 #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
1392 #u'current_provider_external_id::LANR//LÄK::1234'
1393 #u'primary_praxis_provider_external_id::LANR//LÄK::1234'
1394 #u'form_name_long::::1234',
1395 #u'form_name_long::::5',
1396 #u'form_version::::5',
1397 u'$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s <==\\n::250>$'
1398 ]
1399
1400 handler = gmPlaceholderHandler()
1401 handler.debug = True
1402
1403 gmStaff.set_current_provider_to_logged_on_user()
1404 pat = gmPersonSearch.ask_for_patient()
1405 if pat is None:
1406 return
1407
1408 gmPatSearchWidgets.set_active_patient(patient = pat)
1409
1410 app = wx.PyWidgetTester(size = (200, 50))
1411 #handler.set_placeholder('form_name_long', 'ein Testformular')
1412 for ph in phs:
1413 print ph
1414 print ' => %s' % handler[ph]
1415 #handler.unset_placeholder('form_name_long')
1416 #--------------------------------------------------------
1417
1418 #test_placeholders()
1419 #test_new_variant_placeholders()
1420 #test_scripting()
1421 #test_placeholder_regex()
1422 test_placeholder()
1423
1424 #=====================================================================
1425
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Feb 9 04:00:51 2012 | http://epydoc.sourceforge.net |