1
2 """GNUmed macro primitives.
3
4 This module implements functions a macro can legally use.
5 """
6
7 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
8
9 import sys
10 import time
11 import random
12 import types
13 import logging
14 import os
15 import codecs
16
17
18 import wx
19
20
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23 from Gnumed.pycommon import gmI18N
24 if __name__ == '__main__':
25 gmI18N.activate_locale()
26 gmI18N.install_domain()
27 from Gnumed.pycommon import gmGuiBroker
28 from Gnumed.pycommon import gmTools
29 from Gnumed.pycommon import gmBorg
30 from Gnumed.pycommon import gmExceptions
31 from Gnumed.pycommon import gmCfg2
32 from Gnumed.pycommon import gmDateTime
33 from Gnumed.pycommon import gmMimeLib
34
35 from Gnumed.business import gmPerson
36 from Gnumed.business import gmStaff
37 from Gnumed.business import gmDemographicRecord
38 from Gnumed.business import gmMedication
39 from Gnumed.business import gmPathLab
40 from Gnumed.business import gmPersonSearch
41 from Gnumed.business import gmVaccination
42 from Gnumed.business import gmKeywordExpansion
43 from Gnumed.business import gmPraxis
44
45 from Gnumed.wxpython import gmGuiHelpers
46 from Gnumed.wxpython import gmNarrativeWidgets
47 from Gnumed.wxpython import gmPatSearchWidgets
48 from Gnumed.wxpython import gmPersonContactWidgets
49 from Gnumed.wxpython import gmPlugin
50 from Gnumed.wxpython import gmEMRStructWidgets
51 from Gnumed.wxpython import gmListWidgets
52 from Gnumed.wxpython import gmDemographicsWidgets
53 from Gnumed.wxpython import gmDocumentWidgets
54 from Gnumed.wxpython import gmKeywordExpansionWidgets
55 from Gnumed.wxpython import gmPraxisWidgets
56
57
58 _log = logging.getLogger('gm.scripting')
59 _cfg = gmCfg2.gmCfgData()
60
61
62
63
64
65 _injectable_placeholders = {
66 u'form_name_long': None,
67 u'form_name_short': None,
68 u'form_version': None
69 }
70
71
72
73 known_variant_placeholders = [
74
75 u'free_text',
76
77 u'text_snippet',
78
79 u'data_snippet',
80
81
82
83
84
85
86 u'tex_escape',
87 u'today',
88 u'gender_mapper',
89
90
91 u'client_version',
92
93
94 u'name',
95 u'date_of_birth',
96
97 u'patient_address',
98 u'adr_street',
99 u'adr_number',
100 u'adr_subunit',
101 u'adr_location',
102 u'adr_suburb',
103 u'adr_postcode',
104 u'adr_region',
105 u'adr_country',
106
107 u'patient_comm',
108 u'patient_tags',
109
110
111 u'patient_photo',
112
113
114
115
116
117
118
119 u'external_id',
120
121
122
123 u'soap',
124 u'soap_s',
125 u'soap_o',
126 u'soap_a',
127 u'soap_p',
128 u'soap_u',
129 u'soap_admin',
130 u'progress_notes',
131
132
133
134 u'soap_for_encounters',
135
136
137
138 u'soap_by_issue',
139
140
141
142 u'soap_by_episode',
143
144
145
146 u'emr_journal',
147
148
149
150
151
152
153
154
155
156 u'current_meds',
157
158 u'current_meds_for_rx',
159
160
161
162
163
164
165 u'current_meds_table',
166 u'current_meds_notes',
167
168 u'lab_table',
169 u'test_results',
170
171 u'latest_vaccs_table',
172 u'vaccination_history',
173
174 u'allergy_state',
175 u'allergies',
176 u'allergy_list',
177 u'problems',
178 u'PHX',
179 u'encounter_list',
180
181 u'documents',
182
183
184
185
186
187
188
189
190
191
192
193
194
195 u'reminders',
196
197
198
199
200 u'current_provider',
201 u'current_provider_external_id',
202 u'primary_praxis_provider',
203 u'primary_praxis_provider_external_id',
204
205
206 u'praxis',
207
208
209
210
211 u'praxis_address',
212
213
214 u'bill',
215 u'bill_item'
216 ]
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231 default_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$'
232 first_order_placeholder_regex = r'\$<<<[^<:]+?::.*::\d*?>>>\$'
233 second_order_placeholder_regex = r'\$<<[^<:]+?::.*::\d*?>>\$'
234 third_order_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$'
235
236
237
238
239 default_placeholder_start = u'$<'
240 default_placeholder_end = u'>$'
241
243 fname = gmTools.get_unique_filename(prefix = 'gm-placeholders-', suffix = '.txt')
244 ph_file = codecs.open(filename = fname, mode = 'wb', encoding = 'utf8', errors = 'replace')
245
246 ph_file.write(u'Here you can find some more documentation on placeholder use:\n')
247 ph_file.write(u'\n http://wiki.gnumed.de/bin/view/Gnumed/GmManualLettersForms\n\n\n')
248
249 ph_file.write(u'Variable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n')
250 for ph in known_variant_placeholders:
251 ph_file.write(u' %s\n' % ph)
252 ph_file.write(u'\n')
253
254 ph_file.write(u'Injectable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n')
255 for ph in _injectable_placeholders:
256 ph_file.write(u' %s\n' % ph)
257 ph_file.write(u'\n')
258
259 ph_file.close()
260 gmMimeLib.call_viewer_on_file(aFile = fname, block = False)
261
263 """Returns values for placeholders.
264
265 - patient related placeholders operate on the currently active patient
266 - is passed to the forms handling code, for example
267
268 Return values when .debug is False:
269 - errors with placeholders return None
270 - placeholders failing to resolve to a value return an empty string
271
272 Return values when .debug is True:
273 - errors with placeholders return an error string
274 - placeholders failing to resolve to a value return a warning string
275
276 There are several types of placeholders:
277
278 injectable placeholders
279 - they must be set up before use by set_placeholder()
280 - they should be removed after use by unset_placeholder()
281 - the syntax is like extended static placeholders
282 - they are listed in _injectable_placeholders
283
284 variant placeholders
285 - those are listed in known_variant_placeholders
286 - they are parsed into placeholder, data, and maximum length
287 - the length is optional
288 - data is passed to the handler
289
290 Note that this cannot be called from a non-gui thread unless
291 wrapped in wx.CallAfter().
292 """
294
295 self.pat = gmPerson.gmCurrentPatient()
296 self.debug = False
297
298 self.invalid_placeholder_template = _('invalid placeholder >>>>>%s<<<<<')
299
300 self.__cache = {}
301
302 self.__esc_style = None
303 self.__esc_func = lambda x:x
304
305
306
310
314
316 self.__cache[key] = value
317
319 del self.__cache[key]
320
324
325 escape_style = property(lambda x:x, _set_escape_style)
326
335
336 escape_function = property(lambda x:x, _set_escape_function)
337
338 placeholder_regex = property(lambda x: default_placeholder_regex, lambda x:x)
339
340 first_order_placeholder_regex = property(lambda x: first_order_placeholder_regex, lambda x:x)
341 second_order_placeholder_regex = property(lambda x: second_order_placeholder_regex, lambda x:x)
342 third_order_placeholder_regex = property(lambda x: third_order_placeholder_regex, lambda x:x)
343
344
345
347 """Map self['placeholder'] to self.placeholder.
348
349 This is useful for replacing placeholders parsed out
350 of documents as strings.
351
352 Unknown/invalid placeholders still deliver a result but
353 it will be glaringly obvious if debugging is enabled.
354 """
355 _log.debug('replacing [%s]', placeholder)
356
357 original_placeholder = placeholder
358
359
360 if placeholder.startswith(default_placeholder_start):
361 placeholder = placeholder.lstrip('$').lstrip('<')
362 if placeholder.endswith(default_placeholder_end):
363 placeholder = placeholder.rstrip('$').rstrip('>')
364 else:
365 _log.error('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
366 if self.debug:
367 return self._escape(self.invalid_placeholder_template % original_placeholder)
368 return None
369
370
371 parts = placeholder.split('::::', 1)
372 if len(parts) == 2:
373 name, lng = parts
374 is_an_injectable = True
375 try:
376 val = _injectable_placeholders[name]
377 except KeyError:
378 is_an_injectable = False
379 except:
380 _log.exception('injectable placeholder handling error: %s', original_placeholder)
381 if self.debug:
382 return self._escape(self.invalid_placeholder_template % original_placeholder)
383 return None
384 if is_an_injectable:
385 if val is None:
386 if self.debug:
387 return self._escape(u'injectable placeholder [%s]: no value available' % name)
388 return placeholder
389 try:
390 lng = int(lng)
391 except (TypeError, ValueError):
392 lng = len(val)
393 return val[:lng]
394
395
396 if len(placeholder.split('::', 2)) < 3:
397 _log.error('invalid placeholder structure: %s', original_placeholder)
398 if self.debug:
399 return self._escape(self.invalid_placeholder_template % original_placeholder)
400 return None
401
402 name, data = placeholder.split('::', 1)
403 data, lng_str = data.rsplit('::', 1)
404 _log.debug('placeholder parts: name=[%s]; length=[%s]; options=>>>%s<<<', name, lng_str, data)
405 try:
406 lng = int(lng_str)
407 except (TypeError, ValueError):
408 lng = None
409
410 handler = getattr(self, '_get_variant_%s' % name, None)
411 if handler is None:
412 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
413 if self.debug:
414 return self._escape(self.invalid_placeholder_template % original_placeholder)
415 return None
416
417 try:
418 if lng is None:
419 return handler(data = data)
420 return handler(data = data)[:lng]
421 except:
422 _log.exception('placeholder handling error: %s', original_placeholder)
423 if self.debug:
424 return self._escape(self.invalid_placeholder_template % original_placeholder)
425 return None
426
427 _log.error('something went wrong, should never get here')
428 return None
429
430
431
433 return self._escape (
434 gmTools.coalesce (
435 _cfg.get(option = u'client_version'),
436 u'%s' % self.__class__.__name__
437 )
438 )
439
441
442 from Gnumed.wxpython import gmProviderInboxWidgets
443
444 template = _('due %(due_date)s: %(comment)s (%(interval_due)s)')
445 date_format = '%Y %b %d'
446
447 data_parts = data.split('//')
448
449 if len(data_parts) > 0:
450 if data_parts[0].strip() != u'':
451 template = data_parts[0]
452
453 if len(data_parts) > 1:
454 if data_parts[1].strip() != u'':
455 date_format = data_parts[1]
456
457 reminders = gmProviderInboxWidgets.manage_reminders(patient = self.pat.ID)
458
459 if reminders is None:
460 return u''
461
462 if len(reminders) == 0:
463 return u''
464
465 lines = [ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in reminders ]
466
467 return u'\n'.join(lines)
468
470
471 select = False
472 include_descriptions = False
473 template = u'%s'
474 path_template = None
475 export_path = None
476
477 data_parts = data.split('//')
478
479 if u'select' in data_parts:
480 select = True
481 data_parts.remove(u'select')
482
483 if u'description' in data_parts:
484 include_descriptions = True
485 data_parts.remove(u'description')
486
487 template = data_parts[0]
488
489 if len(data_parts) > 1:
490 path_template = data_parts[1]
491
492 if len(data_parts) > 2:
493 export_path = data_parts[2]
494
495
496 if export_path is not None:
497 export_path = os.path.normcase(os.path.expanduser(export_path))
498 gmTools.mkdir(export_path)
499
500
501 if select:
502 docs = gmDocumentWidgets.manage_documents(msg = _('Select the patient documents to reference from the new document.'), single_selection = False)
503 else:
504 docs = self.pat.document_folder.documents
505
506 if docs is None:
507 return u''
508
509 lines = []
510 for doc in docs:
511 lines.append(template % doc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
512 if include_descriptions:
513 for desc in doc.get_descriptions(max_lng = None):
514 lines.append(self._escape(desc['text'] + u'\n'))
515 if path_template is not None:
516 for part_name in doc.export_parts_to_files(export_dir = export_path):
517 path, name = os.path.split(part_name)
518 lines.append(path_template % {'fullpath': part_name, 'name': name})
519
520 return u'\n'.join(lines)
521
523
524 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
525 if not encounters:
526 return u''
527
528 template = data
529
530 lines = []
531 for enc in encounters:
532 try:
533 lines.append(template % enc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
534 except:
535 lines.append(u'error formatting encounter')
536 _log.exception('problem formatting encounter list')
537 _log.error('template: %s', template)
538 _log.error('encounter: %s', encounter)
539
540 return u'\n'.join(lines)
541
543 """Select encounters from list and format SOAP thereof.
544
545 data: soap_cats (' ' -> None -> admin) // date format
546 """
547
548 cats = None
549 date_format = None
550
551 if data is not None:
552 data_parts = data.split('//')
553
554
555 if len(data_parts[0]) > 0:
556 cats = []
557 if u' ' in data_parts[0]:
558 cats.append(None)
559 data_parts[0] = data_parts[0].replace(u' ', u'')
560 cats.extend(list(data_parts[0]))
561
562
563 if len(data_parts) > 1:
564 if len(data_parts[1]) > 0:
565 date_format = data_parts[1]
566
567 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
568 if not encounters:
569 return u''
570
571 chunks = []
572 for enc in encounters:
573 chunks.append(enc.format_latex (
574 date_format = date_format,
575 soap_cats = cats,
576 soap_order = u'soap_rank, date'
577 ))
578
579 return u''.join(chunks)
580
582
583 cats = list(u'soapu')
584 cats.append(None)
585 template = u'%s'
586 interactive = True
587 line_length = 9999
588 target_format = None
589 time_range = None
590
591 if data is not None:
592 data_parts = data.split('//')
593
594
595 cats = []
596
597 for c in list(data_parts[0]):
598 if c == u' ':
599 c = None
600 cats.append(c)
601
602 if cats == u'':
603 cats = list(u'soapu').append(None)
604
605
606 if len(data_parts) > 1:
607 template = data_parts[1]
608
609
610 if len(data_parts) > 2:
611 try:
612 line_length = int(data_parts[2])
613 except:
614 line_length = 9999
615
616
617 if len(data_parts) > 3:
618 try:
619 time_range = 7 * int(data_parts[3])
620 except:
621
622
623 time_range = data_parts[3]
624
625
626 if len(data_parts) > 4:
627 target_format = data_parts[4]
628
629
630 narr = self.pat.emr.get_as_journal(soap_cats = cats, time_range = time_range)
631
632 if len(narr) == 0:
633 return u''
634
635 keys = narr[0].keys()
636 lines = []
637 line_dict = {}
638 for n in narr:
639 for key in keys:
640 if isinstance(n[key], basestring):
641 line_dict[key] = self._escape(text = n[key])
642 continue
643 line_dict[key] = n[key]
644 try:
645 lines.append((template % line_dict)[:line_length])
646 except KeyError:
647 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
648
649 return u'\n'.join(lines)
650
652 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = u'issue')
653
655 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = u'episode')
656
658
659
660 cats = list(u'soapu')
661 cats.append(None)
662
663 date_format = None
664 template = u'%s'
665
666 if data is not None:
667 data_parts = data.split('//')
668
669
670 if len(data_parts[0]) > 0:
671 cats = []
672 if u' ' in data_parts[0]:
673 cats.append(None)
674 cats.extend(list(data_parts[0].replace(u' ', u'')))
675
676
677 if len(data_parts) > 1:
678 if len(data_parts[1]) > 0:
679 date_format = data_parts[1]
680
681
682 if len(data_parts) > 2:
683 if len(data_parts[2]) > 0:
684 template = data_parts[2]
685
686 if mode == u'issue':
687 narr = gmNarrativeWidgets.select_narrative_by_issue(soap_cats = cats)
688 else:
689 narr = gmNarrativeWidgets.select_narrative_by_episode(soap_cats = cats)
690
691 if narr is None:
692 return u''
693
694 if len(narr) == 0:
695 return u''
696
697 try:
698 narr = [ template % n.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for n in narr ]
699 except KeyError:
700 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
701
702 return u'\n'.join(narr)
703
705 return self._get_variant_soap(data = data)
706
708 return self._get_variant_soap(data = u's')
709
711 return self._get_variant_soap(data = u'o')
712
714 return self._get_variant_soap(data = u'a')
715
717 return self._get_variant_soap(data = u'p')
718
720 return self._get_variant_soap(data = u'u')
721
723 return self._get_variant_soap(data = u' ')
724
726
727
728 cats = list(u'soapu')
729 cats.append(None)
730 template = u'%(narrative)s'
731
732 if data is not None:
733 data_parts = data.split('//')
734
735
736 cats = []
737
738 for cat in list(data_parts[0]):
739 if cat == u' ':
740 cat = None
741 cats.append(cat)
742
743 if cats == u'':
744 cats = list(u'soapu')
745 cats.append(None)
746
747
748 if len(data_parts) > 1:
749 template = data_parts[1]
750
751 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
752
753 if narr is None:
754 return u''
755
756 if len(narr) == 0:
757 return u''
758
759
760
761
762 if u'%s' in template:
763 narr = [ self._escape(n['narrative']) for n in narr ]
764 else:
765 narr = [ n.fields_as_dict(escape_style = self.__esc_style) for n in narr ]
766
767 try:
768 narr = [ template % n for n in narr ]
769 except KeyError:
770 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
771 except TypeError:
772 return u'cannot mix "%%s" and "%%(key)s" in template [%s]' % template
773
774 return u'\n'.join(narr)
775
777 return self._get_variant_name(data = u'%(title)s')
778
780 return self._get_variant_name(data = u'%(firstnames)s')
781
783 return self._get_variant_name(data = u'%(lastnames)s')
784
786 if data is None:
787 return [_('template is missing')]
788
789 name = self.pat.get_active_name()
790
791 parts = {
792 'title': self._escape(gmTools.coalesce(name['title'], u'')),
793 'firstnames': self._escape(name['firstnames']),
794 'lastnames': self._escape(name['lastnames']),
795 'preferred': self._escape(gmTools.coalesce (
796 initial = name['preferred'],
797 instead = u' ',
798 template_initial = u' "%s" '
799 ))
800 }
801
802 return data % parts
803
806
807
809
810 values = data.split('//', 2)
811
812 if len(values) == 2:
813 male_value, female_value = values
814 other_value = u'<unkown gender>'
815 elif len(values) == 3:
816 male_value, female_value, other_value = values
817 else:
818 return _('invalid gender mapping layout: [%s]') % data
819
820 if self.pat['gender'] == u'm':
821 return self._escape(male_value)
822
823 if self.pat['gender'] == u'f':
824 return self._escape(female_value)
825
826 return self._escape(other_value)
827
828
829
831
832 data_parts = data.split(u'//')
833
834
835 adr_type = data_parts[0].strip()
836 orig_type = adr_type
837 if adr_type != u'':
838 adrs = self.pat.get_addresses(address_type = adr_type)
839 if len(adrs) == 0:
840 _log.warning('no address for type [%s]', adr_type)
841 adr_type = u''
842 if adr_type == u'':
843 _log.debug('asking user for address type')
844 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
845 if adr is None:
846 if self.debug:
847 return _('no address type replacement selected')
848 return u''
849 adr_type = adr['address_type']
850 adr = self.pat.get_addresses(address_type = adr_type)[0]
851
852
853 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
854 if len(data_parts) > 1:
855 if data_parts[1].strip() != u'':
856 template = data_parts[1]
857
858 try:
859 return template % adr.fields_as_dict(escape_style = self.__esc_style)
860 except StandardError:
861 _log.exception('error formatting address')
862 _log.error('template: %s', template)
863
864 return None
865
867 requested_type = data.strip()
868 cache_key = 'adr-type-%s' % requested_type
869 try:
870 type2use = self.__cache[cache_key]
871 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
872 except KeyError:
873 type2use = requested_type
874 if type2use != u'':
875 adrs = self.pat.get_addresses(address_type = type2use)
876 if len(adrs) == 0:
877 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part)
878 type2use = u''
879 if type2use == u'':
880 _log.debug('asking user for replacement address type')
881 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat)
882 if adr is None:
883 _log.debug('no replacement selected')
884 if self.debug:
885 return self._escape(_('no address type replacement selected'))
886 return u''
887 type2use = adr['address_type']
888 self.__cache[cache_key] = type2use
889 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
890
891 return self._escape(self.pat.get_addresses(address_type = type2use)[0][part])
892
894 return self.__get_variant_adr_part(data = data, part = 'street')
895
897 return self.__get_variant_adr_part(data = data, part = 'number')
898
900 return self.__get_variant_adr_part(data = data, part = 'subunit')
901
903 return self.__get_variant_adr_part(data = data, part = 'urb')
904
906 return self.__get_variant_adr_part(data = data, part = 'suburb')
907
908 - def _get_variant_adr_postcode(self, data=u'?'):
909 return self.__get_variant_adr_part(data = data, part = 'postcode')
910
912 return self.__get_variant_adr_part(data = data, part = 'l10n_state')
913
915 return self.__get_variant_adr_part(data = data, part = 'l10n_country')
916
918 comm_type = None
919 template = u'%(url)s'
920 if data is not None:
921 data_parts = data.split(u'//')
922 if len(data_parts) > 0:
923 comm_type = data_parts[0]
924 if len(data_parts) > 1:
925 template = data_parts[1]
926
927 comms = self.pat.get_comm_channels(comm_medium = comm_type)
928 if len(comms) == 0:
929 if self.debug:
930 return template + u': ' + self._escape(_('no URL for comm channel [%s]') % data)
931 return u''
932
933 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
934
936
937 template = u'%s'
938 target_mime = None
939 target_ext = None
940 if data is not None:
941 parts = data.split(u'//')
942 template = parts[0]
943 if len(parts) > 1:
944 target_mime = parts[1].strip()
945 if len(parts) > 2:
946 target_ext = parts[2].strip()
947 if target_ext is None:
948 if target_mime is not None:
949 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
950
951 mugshot = self.pat.document_folder.latest_mugshot
952 if mugshot is None:
953 if self.debug:
954 return self._escape(_('no mugshot available'))
955 return u''
956
957 fname = mugshot.export_to_file (
958 target_mime = target_mime,
959 target_extension = target_ext,
960 ignore_conversion_problems = True
961 )
962 if fname is None:
963 if self.debug:
964 return self._escape(_('cannot export or convert latest mugshot'))
965 return u''
966
967 return template % fname
968
985
986
987
988
989
990
992 options = data.split(u'//')
993
994 if u'select' in options:
995 options.remove(u'select')
996 branch = 'select branch'
997 else:
998 branch = gmPraxis.cPraxisBranch(aPK_obj = gmPraxis.gmCurrentPraxisBranch()['pk_praxis_branch'])
999
1000 template = u'%s'
1001 if len(options) > 0:
1002 template = options[0]
1003 if template.strip() == u'':
1004 template = u'%s'
1005
1006 return template % branch.fields_as_dict(escape_style = self.__esc_style)
1007
1008
1010
1011 options = data.split(u'//')
1012
1013
1014 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
1015 if len(options) > 0:
1016 if options[0].strip() != u'':
1017 template = options[0]
1018
1019 adr = gmPraxis.gmCurrentPraxisBranch().address
1020 if adr is None:
1021 if self.debug:
1022 return _('no address recorded')
1023 return u''
1024 try:
1025 return template % adr.fields_as_dict(escape_style = self.__esc_style)
1026 except StandardError:
1027 _log.exception('error formatting address')
1028 _log.error('template: %s', template)
1029
1030 return None
1031
1033 options = data.split(u'//')
1034 comm_type = options[0]
1035 template = u'%(url)s'
1036 if len(options) > 1:
1037 template = options[1]
1038
1039 comms = gmPraxis.gmCurrentPraxisBranch().get_comm_channels(comm_medium = comm_type)
1040 if len(comms) == 0:
1041 if self.debug:
1042 return template + u': ' + self._escape(_('no URL for comm channel [%s]') % data)
1043 return u''
1044
1045 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1046
1047
1048
1063
1065 data_parts = data.split(u'//')
1066 if len(data_parts) < 2:
1067 return self._escape(u'current provider external ID: template is missing')
1068
1069 id_type = data_parts[0].strip()
1070 if id_type == u'':
1071 return self._escape(u'current provider external ID: type is missing')
1072
1073 issuer = data_parts[1].strip()
1074 if issuer == u'':
1075 return self._escape(u'current provider external ID: issuer is missing')
1076
1077 prov = gmStaff.gmCurrentProvider()
1078 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1079
1080 if len(ids) == 0:
1081 if self.debug:
1082 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1083 return u''
1084
1085 return self._escape(ids[0]['value'])
1086
1088 prov = self.pat.primary_provider
1089 if prov is None:
1090 return self._get_variant_current_provider()
1091
1092 title = gmTools.coalesce (
1093 prov['title'],
1094 gmPerson.map_gender2salutation(prov['gender'])
1095 )
1096
1097 tmp = u'%s %s. %s' % (
1098 title,
1099 prov['firstnames'][:1],
1100 prov['lastnames']
1101 )
1102 return self._escape(tmp)
1103
1105 data_parts = data.split(u'//')
1106 if len(data_parts) < 2:
1107 return self._escape(u'primary in-praxis provider external ID: template is missing')
1108
1109 id_type = data_parts[0].strip()
1110 if id_type == u'':
1111 return self._escape(u'primary in-praxis provider external ID: type is missing')
1112
1113 issuer = data_parts[1].strip()
1114 if issuer == u'':
1115 return self._escape(u'primary in-praxis provider external ID: issuer is missing')
1116
1117 prov = self.pat.primary_provider
1118 if prov is None:
1119 if self.debug:
1120 return self._escape(_('no primary in-praxis provider'))
1121 return u''
1122
1123 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1124
1125 if len(ids) == 0:
1126 if self.debug:
1127 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1128 return u''
1129
1130 return self._escape(ids[0]['value'])
1131
1133 data_parts = data.split(u'//')
1134 if len(data_parts) < 2:
1135 return self._escape(u'patient external ID: template is missing')
1136
1137 id_type = data_parts[0].strip()
1138 if id_type == u'':
1139 return self._escape(u'patient external ID: type is missing')
1140
1141 issuer = data_parts[1].strip()
1142 if issuer == u'':
1143 return self._escape(u'patient external ID: issuer is missing')
1144
1145 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
1146
1147 if len(ids) == 0:
1148 if self.debug:
1149 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1150 return u''
1151
1152 return self._escape(ids[0]['value'])
1153
1155 allg_state = self.pat.get_emr().allergy_state
1156
1157 if allg_state['last_confirmed'] is None:
1158 date_confirmed = u''
1159 else:
1160 date_confirmed = u' (%s)' % gmDateTime.pydt_strftime (
1161 allg_state['last_confirmed'],
1162 format = '%Y %B %d'
1163 )
1164
1165 tmp = u'%s%s' % (
1166 allg_state.state_string,
1167 date_confirmed
1168 )
1169 return self._escape(tmp)
1170
1172 if data is None:
1173 return self._escape(_('template is missing'))
1174
1175 template, separator = data.split('//', 2)
1176
1177 return separator.join([ template % a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for a in self.pat.emr.get_allergies() ])
1178
1185
1187 if data is None:
1188 return self._escape(_('current_meds_for_rx: template is missing'))
1189
1190 emr = self.pat.get_emr()
1191 from Gnumed.wxpython import gmMedicationWidgets
1192 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr)
1193 if current_meds is None:
1194 return u''
1195
1196 intakes2show = {}
1197 for intake in current_meds:
1198 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)
1199 if intake['pk_brand'] is None:
1200 fields_dict['brand'] = self._escape(_('generic %s') % fields_dict['substance'])
1201 fields_dict['contains'] = self._escape(u'%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit']))
1202 intakes2show[fields_dict['brand']] = fields_dict
1203 else:
1204 comps = [ c.split('::') for c in intake.containing_drug['components'] ]
1205 fields_dict['contains'] = self._escape(u'; '.join([ u'%s %s%s' % (c[0], c[1], c[2]) for c in comps ]))
1206 intakes2show[intake['brand']] = fields_dict
1207
1208 intakes2dispense = {}
1209 for brand, intake in intakes2show.items():
1210 msg = _('Dispense how much/many of "%(brand)s (%(contains)s)" ?') % intake
1211 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?'))
1212 if amount2dispense == u'':
1213 continue
1214 intake['amount2dispense'] = amount2dispense
1215 intakes2dispense[brand] = intake
1216
1217 return u'\n'.join([ data % intake for intake in intakes2dispense.values() ])
1218
1246
1255
1264
1272
1274
1275 template = u''
1276 date_format = '%Y %b %d %H:%M'
1277 separator = u'\n'
1278
1279 options = data.split(u'//')
1280 try:
1281 template = options[0].strip()
1282 date_format = options[1]
1283 separator = options[2]
1284 except IndexError:
1285 pass
1286
1287 if date_format.strip() == u'':
1288 date_format = '%Y %b %d %H:%M'
1289 if separator.strip() == u'':
1290 separator = u'\n'
1291
1292
1293 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements
1294 results = manage_measurements(single_selection = False, emr = self.pat.emr)
1295 if results is None:
1296 if self.debug:
1297 return self._escape(_('no results for this patient (available or selected)'))
1298 return u''
1299
1300 if template == u'':
1301 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results ])
1302
1303 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results ])
1304
1312
1314 options = data.split('//')
1315 template = options[0]
1316 if len(options) > 1:
1317 date_format = options[1]
1318 else:
1319 date_format = u'%Y %b %d'
1320
1321 vaccs = self.pat.emr.get_vaccinations(order_by = u'date_given DESC, vaccine')
1322
1323 return u'\n'.join([ template % v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for v in vaccs ])
1324
1326
1327 if data is None:
1328 if self.debug:
1329 _log.error('PHX: missing placeholder arguments')
1330 return self._escape(_('PHX: Invalid placeholder options.'))
1331 return u''
1332
1333 _log.debug('arguments: %s', data)
1334
1335 data_parts = data.split(u'//')
1336 template = u'%s'
1337 separator = u'\n'
1338 date_format = '%Y %b %d'
1339 esc_style = None
1340 try:
1341 template = data_parts[0]
1342 separator = data_parts[1]
1343 date_format = data_parts[2]
1344 esc_style = data_parts[3]
1345 except IndexError:
1346 pass
1347
1348 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr)
1349 if phxs is None:
1350 if self.debug:
1351 return self._escape(_('no PHX for this patient (available or selected)'))
1352 return u''
1353
1354 return separator.join ([
1355 template % phx.fields_as_dict (
1356 date_format = date_format,
1357
1358 escape_style = self.__esc_style,
1359 bool_strings = (self._escape(_('yes')), self._escape(_('no')))
1360 ) for phx in phxs
1361 ])
1362
1364
1365 if data is None:
1366 return self._escape(_('template is missing'))
1367
1368 probs = self.pat.emr.get_problems()
1369
1370 return u'\n'.join([ data % p.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for p in probs ])
1371
1374
1377
1378 - def _get_variant_text_snippet(self, data=None):
1379 data_parts = data.split(u'//')
1380 keyword = data_parts[0]
1381 template = u'%s'
1382 if len(data_parts) > 1:
1383 template = data_parts[1]
1384
1385 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list = True)
1386
1387 if expansion is None:
1388 if self.debug:
1389 return self._escape(_('no textual expansion found for keyword <%s>') % keyword)
1390 return u''
1391
1392
1393 return template % expansion
1394
1450
1451 - def _get_variant_free_text(self, data=None):
1452
1453 if data is None:
1454 msg = _('generic text')
1455 else:
1456 msg = data
1457
1458 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
1459 None,
1460 -1,
1461 title = _('Replacing <free_text> placeholder'),
1462 msg = _('Below you can enter free text.\n\n [%s]') % msg
1463 )
1464 dlg.enable_user_formatting = True
1465 decision = dlg.ShowModal()
1466
1467 if decision != wx.ID_SAVE:
1468 dlg.Destroy()
1469 if self.debug:
1470 return self._escape(_('Text input cancelled by user.'))
1471 return u''
1472
1473 text = dlg.value.strip()
1474 if dlg.is_user_formatted:
1475 dlg.Destroy()
1476 return text
1477
1478 dlg.Destroy()
1479
1480 return self._escape(text)
1481
1495
1509
1510
1511
1513 if self.__esc_func is None:
1514 return text
1515 return self.__esc_func(text)
1516
1518 """Functions a macro can legally use.
1519
1520 An instance of this class is passed to the GNUmed scripting
1521 listener. Hence, all actions a macro can legally take must
1522 be defined in this class. Thus we achieve some screening for
1523 security and also thread safety handling.
1524 """
1525
1526 - def __init__(self, personality = None):
1527 if personality is None:
1528 raise gmExceptions.ConstructorError, 'must specify personality'
1529 self.__personality = personality
1530 self.__attached = 0
1531 self._get_source_personality = None
1532 self.__user_done = False
1533 self.__user_answer = 'no answer yet'
1534 self.__pat = gmPerson.gmCurrentPatient()
1535
1536 self.__auth_cookie = str(random.random())
1537 self.__pat_lock_cookie = str(random.random())
1538 self.__lock_after_load_cookie = str(random.random())
1539
1540 _log.info('slave mode personality is [%s]', personality)
1541
1542
1543
1544 - def attach(self, personality = None):
1545 if self.__attached:
1546 _log.error('attach with [%s] rejected, already serving a client', personality)
1547 return (0, _('attach rejected, already serving a client'))
1548 if personality != self.__personality:
1549 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
1550 return (0, _('attach to personality [%s] rejected') % personality)
1551 self.__attached = 1
1552 self.__auth_cookie = str(random.random())
1553 return (1, self.__auth_cookie)
1554
1555 - def detach(self, auth_cookie=None):
1556 if not self.__attached:
1557 return 1
1558 if auth_cookie != self.__auth_cookie:
1559 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
1560 return 0
1561 self.__attached = 0
1562 return 1
1563
1565 if not self.__attached:
1566 return 1
1567 self.__user_done = False
1568
1569 wx.CallAfter(self._force_detach)
1570 return 1
1571
1573 ver = _cfg.get(option = u'client_version')
1574 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1575
1577 """Shuts down this client instance."""
1578 if not self.__attached:
1579 return 0
1580 if auth_cookie != self.__auth_cookie:
1581 _log.error('non-authenticated shutdown_gnumed()')
1582 return 0
1583 wx.CallAfter(self._shutdown_gnumed, forced)
1584 return 1
1585
1587 """Raise ourselves to the top of the desktop."""
1588 if not self.__attached:
1589 return 0
1590 if auth_cookie != self.__auth_cookie:
1591 _log.error('non-authenticated raise_gnumed()')
1592 return 0
1593 return "cMacroPrimitives.raise_gnumed() not implemented"
1594
1596 if not self.__attached:
1597 return 0
1598 if auth_cookie != self.__auth_cookie:
1599 _log.error('non-authenticated get_loaded_plugins()')
1600 return 0
1601 gb = gmGuiBroker.GuiBroker()
1602 return gb['horstspace.notebook.gui'].keys()
1603
1605 """Raise a notebook plugin within GNUmed."""
1606 if not self.__attached:
1607 return 0
1608 if auth_cookie != self.__auth_cookie:
1609 _log.error('non-authenticated raise_notebook_plugin()')
1610 return 0
1611
1612 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1613 return 1
1614
1616 """Load external patient, perhaps create it.
1617
1618 Callers must use get_user_answer() to get status information.
1619 It is unsafe to proceed without knowing the completion state as
1620 the controlled client may be waiting for user input from a
1621 patient selection list.
1622 """
1623 if not self.__attached:
1624 return (0, _('request rejected, you are not attach()ed'))
1625 if auth_cookie != self.__auth_cookie:
1626 _log.error('non-authenticated load_patient_from_external_source()')
1627 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1628 if self.__pat.locked:
1629 _log.error('patient is locked, cannot load from external source')
1630 return (0, _('current patient is locked'))
1631 self.__user_done = False
1632 wx.CallAfter(self._load_patient_from_external_source)
1633 self.__lock_after_load_cookie = str(random.random())
1634 return (1, self.__lock_after_load_cookie)
1635
1637 if not self.__attached:
1638 return (0, _('request rejected, you are not attach()ed'))
1639 if auth_cookie != self.__auth_cookie:
1640 _log.error('non-authenticated lock_load_patient()')
1641 return (0, _('rejected lock_load_patient(), not authenticated'))
1642
1643 if lock_after_load_cookie != self.__lock_after_load_cookie:
1644 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1645 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1646 self.__pat.locked = True
1647 self.__pat_lock_cookie = str(random.random())
1648 return (1, self.__pat_lock_cookie)
1649
1651 if not self.__attached:
1652 return (0, _('request rejected, you are not attach()ed'))
1653 if auth_cookie != self.__auth_cookie:
1654 _log.error('non-authenticated lock_into_patient()')
1655 return (0, _('rejected lock_into_patient(), not authenticated'))
1656 if self.__pat.locked:
1657 _log.error('patient is already locked')
1658 return (0, _('already locked into a patient'))
1659 searcher = gmPersonSearch.cPatientSearcher_SQL()
1660 if type(search_params) == types.DictType:
1661 idents = searcher.get_identities(search_dict=search_params)
1662 raise StandardError("must use dto, not search_dict")
1663 else:
1664 idents = searcher.get_identities(search_term=search_params)
1665 if idents is None:
1666 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1667 if len(idents) == 0:
1668 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1669
1670 if len(idents) > 1:
1671 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1672 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1673 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1674 self.__pat.locked = True
1675 self.__pat_lock_cookie = str(random.random())
1676 return (1, self.__pat_lock_cookie)
1677
1679 if not self.__attached:
1680 return (0, _('request rejected, you are not attach()ed'))
1681 if auth_cookie != self.__auth_cookie:
1682 _log.error('non-authenticated unlock_patient()')
1683 return (0, _('rejected unlock_patient, not authenticated'))
1684
1685 if not self.__pat.locked:
1686 return (1, '')
1687
1688 if unlock_cookie != self.__pat_lock_cookie:
1689 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1690 return (0, 'patient unlock request rejected, wrong cookie provided')
1691 self.__pat.locked = False
1692 return (1, '')
1693
1695 if not self.__attached:
1696 return 0
1697 if auth_cookie != self.__auth_cookie:
1698 _log.error('non-authenticated select_identity()')
1699 return 0
1700 return "cMacroPrimitives.assume_staff_identity() not implemented"
1701
1703 if not self.__user_done:
1704 return (0, 'still waiting')
1705 self.__user_done = False
1706 return (1, self.__user_answer)
1707
1708
1709
1711 msg = _(
1712 'Someone tries to forcibly break the existing\n'
1713 'controlling connection. This may or may not\n'
1714 'have legitimate reasons.\n\n'
1715 'Do you want to allow breaking the connection ?'
1716 )
1717 can_break_conn = gmGuiHelpers.gm_show_question (
1718 aMessage = msg,
1719 aTitle = _('forced detach attempt')
1720 )
1721 if can_break_conn:
1722 self.__user_answer = 1
1723 else:
1724 self.__user_answer = 0
1725 self.__user_done = True
1726 if can_break_conn:
1727 self.__pat.locked = False
1728 self.__attached = 0
1729 return 1
1730
1732 top_win = wx.GetApp().GetTopWindow()
1733 if forced:
1734 top_win.Destroy()
1735 else:
1736 top_win.Close()
1737
1746
1747
1748
1749 if __name__ == '__main__':
1750
1751 if len(sys.argv) < 2:
1752 sys.exit()
1753
1754 if sys.argv[1] != 'test':
1755 sys.exit()
1756
1757 gmI18N.activate_locale()
1758 gmI18N.install_domain()
1759
1760
1762 handler = gmPlaceholderHandler()
1763 handler.debug = True
1764
1765 for placeholder in ['a', 'b']:
1766 print handler[placeholder]
1767
1768 pat = gmPersonSearch.ask_for_patient()
1769 if pat is None:
1770 return
1771
1772 gmPatSearchWidgets.set_active_patient(patient = pat)
1773
1774 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1775
1776 app = wx.PyWidgetTester(size = (200, 50))
1777
1778 ph = 'progress_notes::ap'
1779 print '%s: %s' % (ph, handler[ph])
1780
1782
1783 tests = [
1784
1785 '$<lastname>$',
1786 '$<lastname::::3>$',
1787 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1788
1789
1790 'lastname',
1791 '$<lastname',
1792 '$<lastname::',
1793 '$<lastname::>$',
1794 '$<lastname::abc>$',
1795 '$<lastname::abc::>$',
1796 '$<lastname::abc::3>$',
1797 '$<lastname::abc::xyz>$',
1798 '$<lastname::::>$',
1799 '$<lastname::::xyz>$',
1800
1801 '$<date_of_birth::%Y-%m-%d>$',
1802 '$<date_of_birth::%Y-%m-%d::3>$',
1803 '$<date_of_birth::%Y-%m-%d::>$',
1804
1805
1806 '$<adr_location::home::35>$',
1807 '$<gender_mapper::male//female//other::5>$',
1808 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1809 '$<allergy_list::%(descriptor)s, >$',
1810 '$<current_meds_table::latex//by-brand>$'
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825 ]
1826
1827
1828
1829
1830
1831 pat = gmPersonSearch.ask_for_patient()
1832 if pat is None:
1833 return
1834
1835 gmPatSearchWidgets.set_active_patient(patient = pat)
1836
1837 handler = gmPlaceholderHandler()
1838 handler.debug = True
1839
1840 for placeholder in tests:
1841 print placeholder, "=>", handler[placeholder]
1842 print "--------------"
1843 raw_input()
1844
1845
1846
1847
1848
1849
1850
1851
1852
1854 from Gnumed.pycommon import gmScriptingListener
1855 import xmlrpclib
1856 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1857
1858 s = xmlrpclib.ServerProxy('http://localhost:9999')
1859 print "should fail:", s.attach()
1860 print "should fail:", s.attach('wrong cookie')
1861 print "should work:", s.version()
1862 print "should fail:", s.raise_gnumed()
1863 print "should fail:", s.raise_notebook_plugin('test plugin')
1864 print "should fail:", s.lock_into_patient('kirk, james')
1865 print "should fail:", s.unlock_patient()
1866 status, conn_auth = s.attach('unit test')
1867 print "should work:", status, conn_auth
1868 print "should work:", s.version()
1869 print "should work:", s.raise_gnumed(conn_auth)
1870 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1871 print "should work:", status, pat_auth
1872 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1873 print "should work", s.unlock_patient(conn_auth, pat_auth)
1874 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1875 status, pat_auth = s.lock_into_patient(conn_auth, data)
1876 print "should work:", status, pat_auth
1877 print "should work", s.unlock_patient(conn_auth, pat_auth)
1878 print s.detach('bogus detach cookie')
1879 print s.detach(conn_auth)
1880 del s
1881
1882 listener.shutdown()
1883
1885
1886 import re as regex
1887
1888 tests = [
1889 ' $<lastname>$ ',
1890 ' $<lastname::::3>$ ',
1891
1892
1893 '$<date_of_birth::%Y-%m-%d>$',
1894 '$<date_of_birth::%Y-%m-%d::3>$',
1895 '$<date_of_birth::%Y-%m-%d::>$',
1896
1897 '$<adr_location::home::35>$',
1898 '$<gender_mapper::male//female//other::5>$',
1899 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1900 '$<allergy_list::%(descriptor)s, >$',
1901
1902 '\\noindent Patient: $<lastname>$, $<firstname>$',
1903 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1904 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1905 ]
1906
1907 tests = [
1908
1909 'junk $<lastname::::3>$ junk',
1910 'junk $<lastname::abc::3>$ junk',
1911 'junk $<lastname::abc>$ junk',
1912 'junk $<lastname>$ junk',
1913
1914 'junk $<lastname>$ junk $<firstname>$ junk',
1915 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1916 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1917 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1918
1919 ]
1920
1921 tests = [
1922
1923
1924
1925
1926
1927 u'$<<<current_meds::%(brand)s (%(substance)s): Dispense $<free_text::Dispense how many of %(brand)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::250>>>$',
1928 ]
1929
1930 print "testing placeholder regex:", first_order_placeholder_regex
1931 print ""
1932
1933 for t in tests:
1934 print 'line: "%s"' % t
1935 phs = regex.findall(first_order_placeholder_regex, t, regex.IGNORECASE)
1936 print " %s placeholders:" % len(phs)
1937 for p in phs:
1938 print ' => "%s"' % p
1939 print " "
1940
2007
2008
2016
2019
2020
2021
2022
2023
2024
2025
2026 test_placeholder()
2027
2028
2029
2030