| 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 __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 gmEncounterWidgets
52 from Gnumed.wxpython import gmListWidgets
53 from Gnumed.wxpython import gmDemographicsWidgets
54 from Gnumed.wxpython import gmDocumentWidgets
55 from Gnumed.wxpython import gmKeywordExpansionWidgets
56 from Gnumed.wxpython import gmPraxisWidgets
57
58
59 _log = logging.getLogger('gm.scripting')
60 _cfg = gmCfg2.gmCfgData()
61
62 #=====================================================================
63 # values for the following placeholders must be injected from the outside before
64 # using them, in use they must conform to the "placeholder::::max length" syntax,
65 # as long as they resolve to None they return their respective names so the
66 # developers can know which placeholder was not set
67 _injectable_placeholders = {
68 u'form_name_long': None,
69 u'form_name_short': None,
70 u'form_version': None
71 }
72
73
74 # the following must satisfy the pattern "$<name::args::(optional) max string length>$" when used
75 __known_variant_placeholders = {
76 # generic:
77 u'free_text': u"""show a dialog for entering some free text:
78 args: <message> shown in input dialog, must not contain '//' or '::'""",
79 u'text_snippet': u"""a text snippet, taken from the keyword expansion mechanism:
80 args: <snippet name>//<template>""",
81 u'data_snippet': u"""a binary snippet, taken from the keyword expansion mechanism:
82 args: <snippet name>//<template>//<optional target mime type>//<optional target extension>
83 returns full path to an exported copy of the
84 data rather than the data itself,
85 template: string template for outputting the path
86 target mime type: a mime type into which to convert the image, no conversion if not given
87 target extension: target file name extension, derived from target mime type if not given
88 """,
89 u'tex_escape': u"args: string to escape",
90 u'today': u"args: strftime format",
91 u'gender_mapper': u"""maps gender of patient to a string:
92 args: <value when person is male> // <is female> // <is other>
93 eg. 'male//female//other'
94 or: 'Lieber Patient//Liebe Patientin'""",
95 u'client_version': u"the version of the current client as a string (no 'v' in front)",
96
97
98 # patient demographics:
99 u'name': u"args: template for name parts arrangement",
100 u'date_of_birth': u"args: strftime date/time format directive",
101
102 u'patient_address': u"args: <type of address>//<optional formatting template>",
103 u'adr_street': u"args: <type of address>, cached per type",
104 u'adr_number': u"args: <type of address>, cached per type",
105 u'adr_subunit': u"args: <type of address>, cached per type",
106 u'adr_location': u"args: <type of address>, cached per type",
107 u'adr_suburb': u"args: <type of address>, cached per type",
108 u'adr_postcode': u"args: <type of address>, cached per type",
109 u'adr_region': u"args: <type of address>, cached per type",
110 u'adr_country': u"args: <type of address>, cached per type",
111
112 u'patient_comm': u"args: <comm channel type as per database>//<%(field)s-template>",
113 u'patient_tags': u"args: <%(field)s-template>//<separator>",
114 #u'patient_tags_table': u"no args",
115 u'patient_photo': u"""outputs URL to exported patient photo:
116 args: <template>//<optional target mime type>//<optional target extension>,
117 returns full path to an exported copy of the
118 image rather than the image data itself,
119 returns u'' if no mugshot available,
120 template: string template for outputting the path
121 target mime type: a mime type into which to convert the image, no conversion if not given
122 target extension: target file name extension, derived from target mime type if not given""",
123 u'external_id': u"args: <type of ID>//<issuer of ID>",
124
125
126 # clinical record related:
127 u'soap': u"get all of SOAPU/ADMIN, no template in args needed",
128 u'soap_s': u"get subset of SOAPU/ADMIN, no template in args needed",
129 u'soap_o': u"get subset of SOAPU/ADMIN, no template in args needed",
130 u'soap_a': u"get subset of SOAPU/ADMIN, no template in args needed",
131 u'soap_p': u"get subset of SOAPU/ADMIN, no template in args needed",
132 u'soap_u': u"get subset of SOAPU/ADMIN, no template in args needed",
133 u'soap_admin': u"get subset of SOAPU/ADMIN, no template in args needed",
134
135 u'progress_notes': u"""get progress notes:
136 args: categories//template
137 categories: string with 'soapu '; ' ' == None == admin
138 template: u'something %s something' (do not include // in template !)""",
139
140 u'soap_for_encounters': u"""lets the user select a list of encounters for which:
141 LaTeX formatted progress notes are emitted,
142 args: soap categories // strftime date format""",
143
144 u'soap_by_issue': u"""lets the user select a list of issues and then SOAP entries from those issues:
145 args: soap categories // strftime date format // template""",
146
147 u'soap_by_episode': u"""lets the user select a list of episodes and then SOAP entries from those episodes:
148 args: soap categories // strftime date format // template""",
149
150 u'emr_journal': u"""returns EMR journal view entries:
151 args format: <categories>//<template>//<line length>//<time range>//<target format>
152 categories: string with any of "s", "o", "a", "p", "u", " "; (" " == None == admin category)
153 template: something %s something else (Do not include // in the template !)
154 line length: the maximum length of individual lines, not the total placeholder length
155 time range: the number of weeks going back in time if given as a single number, or else it must be a valid PostgreSQL interval definition (w/o the ::interval)""",
156
157 u'current_meds': u"""returns current medications:
158 args: line template//<select>
159 <select>: if this is present the user will be asked which meds to export""",
160
161 u'current_meds_for_rx': u"""formats substance intakes either by substance (non-brand intakes or by brand (once per brand intake, even if multi-component):
162 args: <line template>
163 <line_template>: template into which to insert each intake, keys from
164 clin.v_substance_intakes, special additional keys:
165 %(contains)s -- list of components
166 %(amount2dispense)s -- how much/many to dispense""",
167
168 u'current_meds_table': u"emits a LaTeX table, no arguments",
169 u'current_meds_notes': u"emits a LaTeX table, no arguments",
170 u'lab_table': u"emits a LaTeX table, no arguments",
171 u'test_results': u"args: <%(field)s-template>//<date format>//<line separator (EOL)>",
172 u'latest_vaccs_table': u"emits a LaTeX table, no arguments",
173 u'vaccination_history': u"args: <%(field)s-template//date format> to format one vaccination per line",
174 u'allergy_state': u"no arguments",
175 u'allergies': u"args: line template, one allergy per line",
176 u'allergy_list': u"args holds: template per allergy, all allergies on one line",
177 u'problems': u"args holds: line template, one problem per line",
178 u'PHX': u"Past medical HiXtory; args: line template//separator//strftime date format",
179 u'encounter_list': u"args: per-encounter template, each ends up on one line",
180
181 u'documents': u"""retrieves documents from the archive:
182 args: <select>//<description>//<template>//<path template>//<path>
183 select: let user select which documents to include, optional, if not given: all documents included
184 description: whether to include descriptions, optional
185 template: something %(field)s something else (do not include '//' or '::' itself in the template)
186 path template: the template for outputting the path to exported
187 copies of the document pages, if not given no pages are exported,
188 this template can contain "%(name)s" and/or "%(fullpath)s" which
189 is replaced by the appropriate value for each exported file
190 path: into which path to export copies of the document pages, temp dir if not given""",
191
192 u'reminders': u"""patient reminders:
193 args: <template>//<date format>
194 template: something %(field)s something else (do not include '//' or '::' itself in the template)""",
195
196
197 # provider related:
198 u'current_provider': u"no arguments",
199 u'current_provider_external_id': u"args: <type of ID>//<issuer of ID>",
200 u'primary_praxis_provider': u"primary provider for current patient in this praxis",
201 u'primary_praxis_provider_external_id': u"args: <type of ID>//<issuer of ID>",
202
203
204 # praxis related:
205 u'praxis': u"""retrieve current branch of your praxis:
206 args: <template>//select
207 template: something %(field)s something else (do not include '//' or '::' itself in the template)
208 select: if this is present allow selection of the branch rather than using the current branch""",
209
210 u'praxis_address': u"args: <optional formatting template>",
211
212
213 # billing related:
214 u'bill': u"args: template for string replacement",
215 u'bill_item': u"args: template for string replacement"
216 }
217
218 known_variant_placeholders = __known_variant_placeholders.keys()
219 known_variant_placeholders.sort()
220
221
222 # http://help.libreoffice.org/Common/List_of_Regular_Expressions
223 # except that OOo cannot be non-greedy |-(
224 #default_placeholder_regex = r'\$<.+?>\$' # previous working placeholder
225 # regex logic:
226 # starts with "$"
227 # followed by "<"
228 # followed by > 0 characters but NOT "<" but ONLY up to the NEXT ":"
229 # followed by "::"
230 # followed by any number of characters but ONLY up to the NEXT ":"
231 # followed by "::"
232 # followed by any number of numbers
233 # followed by ">"
234 # followed by "$"
235 default_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$' # this one works [except that OOo cannot be non-greedy |-( ]
236 first_order_placeholder_regex = r'\$<<<[^<:]+?::.*::\d*?>>>\$'
237 second_order_placeholder_regex = r'\$<<[^<:]+?::.*::\d*?>>\$'
238 third_order_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$'
239
240
241 #default_placeholder_regex = r'\$<(?:(?!\$<).)+>\$' # non-greedy equivalent, uses lookahead (but not supported by LO either |-o )
242
243 default_placeholder_start = u'$<'
244 default_placeholder_end = u'>$'
245 #=====================================================================
247 fname = gmTools.get_unique_filename(prefix = 'gm-placeholders-', suffix = '.txt')
248 ph_file = codecs.open(filename = fname, mode = 'wb', encoding = 'utf8', errors = 'replace')
249
250 ph_file.write(u'Here you can find some more documentation on placeholder use:\n')
251 ph_file.write(u'\n http://wiki.gnumed.de/bin/view/Gnumed/GmManualLettersForms\n\n\n')
252
253 ph_file.write(u'Variable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n')
254 for ph in known_variant_placeholders:
255 txt = __known_variant_placeholders[ph]
256 ph_file.write(u'\n')
257 ph_file.write(u' ---=== %s ===---\n' % ph)
258 ph_file.write(u'\n')
259 ph_file.write(txt)
260 ph_file.write('\n\n')
261 ph_file.write(u'\n')
262
263 ph_file.write(u'Injectable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n')
264 for ph in _injectable_placeholders:
265 ph_file.write(u' %s\n' % ph)
266 ph_file.write(u'\n')
267
268 ph_file.close()
269 gmMimeLib.call_viewer_on_file(aFile = fname, block = False)
270 #=====================================================================
272 """Returns values for placeholders.
273
274 - patient related placeholders operate on the currently active patient
275 - is passed to the forms handling code, for example
276
277 Return values when .debug is False:
278 - errors with placeholders return None
279 - placeholders failing to resolve to a value return an empty string
280
281 Return values when .debug is True:
282 - errors with placeholders return an error string
283 - placeholders failing to resolve to a value return a warning string
284
285 There are several types of placeholders:
286
287 injectable placeholders
288 - they must be set up before use by set_placeholder()
289 - they should be removed after use by unset_placeholder()
290 - the syntax is like extended static placeholders
291 - they are listed in _injectable_placeholders
292
293 variant placeholders
294 - those are listed in known_variant_placeholders
295 - they are parsed into placeholder, data, and maximum length
296 - the length is optional
297 - data is passed to the handler
298
299 Note that this cannot be called from a non-gui thread unless
300 wrapped in wx.CallAfter().
301 """
303
304 self.pat = gmPerson.gmCurrentPatient()
305 self.debug = False
306
307 self.invalid_placeholder_template = _('invalid placeholder >>>>>%s<<<<<')
308
309 self.__cache = {}
310
311 self.__esc_style = None
312 self.__esc_func = lambda x:x
313 #--------------------------------------------------------
314 # external API
315 #--------------------------------------------------------
319 #--------------------------------------------------------
323 #--------------------------------------------------------
325 self.__cache[key] = value
326 #--------------------------------------------------------
329 #--------------------------------------------------------
333
334 escape_style = property(lambda x:x, _set_escape_style)
335 #--------------------------------------------------------
337 if escape_function is None:
338 self.__esc_func = lambda x:x
339 return
340 if not callable(escape_function):
341 raise ValueError(u'[%s._set_escape_function]: <%s> not callable' % (self.__class__.__name__, escape_function))
342 self.__esc_func = escape_function
343 return
344
345 escape_function = property(lambda x:x, _set_escape_function)
346 #--------------------------------------------------------
347 placeholder_regex = property(lambda x: default_placeholder_regex, lambda x:x)
348
349 first_order_placeholder_regex = property(lambda x: first_order_placeholder_regex, lambda x:x)
350 second_order_placeholder_regex = property(lambda x: second_order_placeholder_regex, lambda x:x)
351 third_order_placeholder_regex = property(lambda x: third_order_placeholder_regex, lambda x:x)
352 #--------------------------------------------------------
353 # __getitem__ API
354 #--------------------------------------------------------
356 """Map self['placeholder'] to self.placeholder.
357
358 This is useful for replacing placeholders parsed out
359 of documents as strings.
360
361 Unknown/invalid placeholders still deliver a result but
362 it will be glaringly obvious if debugging is enabled.
363 """
364 _log.debug('replacing [%s]', placeholder)
365
366 original_placeholder = placeholder
367
368 # remove leading/trailing '$<(<<)' and '(>>)>$'
369 if placeholder.startswith(default_placeholder_start):
370 placeholder = placeholder.lstrip('$').lstrip('<')
371 if placeholder.endswith(default_placeholder_end):
372 placeholder = placeholder.rstrip('$').rstrip('>')
373 else:
374 _log.error('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
375 if self.debug:
376 return self._escape(self.invalid_placeholder_template % original_placeholder)
377 return None
378
379 # injectable placeholder ?
380 parts = placeholder.split('::::', 1)
381 if len(parts) == 2:
382 name, lng = parts
383 is_an_injectable = True
384 try:
385 val = _injectable_placeholders[name]
386 except KeyError:
387 is_an_injectable = False
388 except:
389 _log.exception('injectable placeholder handling error: %s', original_placeholder)
390 if self.debug:
391 return self._escape(self.invalid_placeholder_template % original_placeholder)
392 return None
393 if is_an_injectable:
394 if val is None:
395 if self.debug:
396 return self._escape(u'injectable placeholder [%s]: no value available' % name)
397 return placeholder
398 try:
399 lng = int(lng.strip())
400 except (TypeError, ValueError):
401 lng = len(val)
402 return val[:lng]
403
404 # variable placeholders
405 if len(placeholder.split('::', 2)) < 3:
406 _log.error('invalid placeholder structure: %s', original_placeholder)
407 if self.debug:
408 return self._escape(self.invalid_placeholder_template % original_placeholder)
409 return None
410
411 name, data = placeholder.split('::', 1)
412 data, lng_str = data.rsplit('::', 1)
413 _log.debug('placeholder parts: name=[%s]; length=[%s]; options=>>>%s<<<', name, lng_str, data)
414 try:
415 lng = int(lng_str.strip())
416 except (TypeError, ValueError):
417 lng = None
418
419 handler = getattr(self, '_get_variant_%s' % name, None)
420 if handler is None:
421 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
422 if self.debug:
423 return self._escape(self.invalid_placeholder_template % original_placeholder)
424 return None
425
426 try:
427 if lng is None:
428 return handler(data = data)
429 return handler(data = data)[:lng]
430 except:
431 _log.exception('placeholder handling error: %s', original_placeholder)
432 if self.debug:
433 return self._escape(self.invalid_placeholder_template % original_placeholder)
434 return None
435
436 _log.error('something went wrong, should never get here')
437 return None
438 #--------------------------------------------------------
439 # placeholder handlers
440 #--------------------------------------------------------
442 return self._escape (
443 gmTools.coalesce (
444 _cfg.get(option = u'client_version'),
445 u'%s' % self.__class__.__name__
446 )
447 )
448 #--------------------------------------------------------
450
451 from Gnumed.wxpython import gmProviderInboxWidgets
452
453 template = _('due %(due_date)s: %(comment)s (%(interval_due)s)')
454 date_format = '%Y %b %d'
455
456 data_parts = data.split('//')
457
458 if len(data_parts) > 0:
459 if data_parts[0].strip() != u'':
460 template = data_parts[0]
461
462 if len(data_parts) > 1:
463 if data_parts[1].strip() != u'':
464 date_format = data_parts[1]
465
466 reminders = gmProviderInboxWidgets.manage_reminders(patient = self.pat.ID)
467
468 if reminders is None:
469 return u''
470
471 if len(reminders) == 0:
472 return u''
473
474 lines = [ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in reminders ]
475
476 return u'\n'.join(lines)
477 #--------------------------------------------------------
479
480 select = False
481 include_descriptions = False
482 template = u'%s'
483 path_template = None
484 export_path = None
485
486 data_parts = data.split('//')
487
488 if u'select' in data_parts:
489 select = True
490 data_parts.remove(u'select')
491
492 if u'description' in data_parts:
493 include_descriptions = True
494 data_parts.remove(u'description')
495
496 template = data_parts[0]
497
498 if len(data_parts) > 1:
499 path_template = data_parts[1]
500
501 if len(data_parts) > 2:
502 export_path = data_parts[2]
503
504 # create path
505 if export_path is not None:
506 export_path = os.path.normcase(os.path.expanduser(export_path))
507 gmTools.mkdir(export_path)
508
509 # select docs
510 if select:
511 docs = gmDocumentWidgets.manage_documents(msg = _('Select the patient documents to reference from the new document.'), single_selection = False)
512 else:
513 docs = self.pat.document_folder.documents
514
515 if docs is None:
516 return u''
517
518 lines = []
519 for doc in docs:
520 lines.append(template % doc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
521 if include_descriptions:
522 for desc in doc.get_descriptions(max_lng = None):
523 lines.append(self._escape(desc['text'] + u'\n'))
524 if path_template is not None:
525 for part_name in doc.export_parts_to_files(export_dir = export_path):
526 path, name = os.path.split(part_name)
527 lines.append(path_template % {'fullpath': part_name, 'name': name})
528
529 return u'\n'.join(lines)
530 #--------------------------------------------------------
532
533 encounters = gmEncounterWidgets.select_encounters(single_selection = False)
534 if not encounters:
535 return u''
536
537 template = data
538
539 lines = []
540 for enc in encounters:
541 try:
542 lines.append(template % enc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
543 except:
544 lines.append(u'error formatting encounter')
545 _log.exception('problem formatting encounter list')
546 _log.error('template: %s', template)
547 _log.error('encounter: %s', encounter)
548
549 return u'\n'.join(lines)
550 #--------------------------------------------------------
552 """Select encounters from list and format SOAP thereof.
553
554 data: soap_cats (' ' -> None -> admin) // date format
555 """
556 # defaults
557 cats = None
558 date_format = None
559
560 if data is not None:
561 data_parts = data.split('//')
562
563 # part[0]: categories
564 if len(data_parts[0]) > 0:
565 cats = []
566 if u' ' in data_parts[0]:
567 cats.append(None)
568 data_parts[0] = data_parts[0].replace(u' ', u'')
569 cats.extend(list(data_parts[0]))
570
571 # part[1]: date format
572 if len(data_parts) > 1:
573 if len(data_parts[1]) > 0:
574 date_format = data_parts[1]
575
576 encounters = gmEncounterWidgets.select_encounters(single_selection = False)
577 if not encounters:
578 return u''
579
580 chunks = []
581 for enc in encounters:
582 chunks.append(enc.format_latex (
583 date_format = date_format,
584 soap_cats = cats,
585 soap_order = u'soap_rank, date'
586 ))
587
588 return u''.join(chunks)
589 #--------------------------------------------------------
591 # default: all categories, neutral template
592 cats = list(u'soapu')
593 cats.append(None)
594 template = u'%s'
595 interactive = True
596 line_length = 9999
597 time_range = None
598
599 if data is not None:
600 data_parts = data.split('//')
601
602 # part[0]: categories
603 cats = []
604 # ' ' -> None == admin
605 for c in list(data_parts[0]):
606 if c == u' ':
607 c = None
608 cats.append(c)
609 # '' -> SOAP + None
610 if cats == u'':
611 cats = list(u'soapu').append(None)
612
613 # part[1]: template
614 if len(data_parts) > 1:
615 template = data_parts[1]
616
617 # part[2]: line length
618 if len(data_parts) > 2:
619 try:
620 line_length = int(data_parts[2])
621 except:
622 line_length = 9999
623
624 # part[3]: weeks going back in time
625 if len(data_parts) > 3:
626 try:
627 time_range = 7 * int(data_parts[3])
628 except:
629 #time_range = None # infinite
630 # pass on literally, meaning it must be a valid PG interval string
631 time_range = data_parts[3]
632
633 # FIXME: will need to be a generator later on
634 narr = self.pat.emr.get_as_journal(soap_cats = cats, time_range = time_range)
635
636 if len(narr) == 0:
637 return u''
638
639 keys = narr[0].keys()
640 lines = []
641 line_dict = {}
642 for n in narr:
643 for key in keys:
644 if isinstance(n[key], basestring):
645 line_dict[key] = self._escape(text = n[key])
646 continue
647 line_dict[key] = n[key]
648 try:
649 lines.append((template % line_dict)[:line_length])
650 except KeyError:
651 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
652
653 return u'\n'.join(lines)
654 #--------------------------------------------------------
657 #--------------------------------------------------------
660 #--------------------------------------------------------
662
663 # default: all categories, neutral template
664 cats = list(u'soapu')
665 cats.append(None)
666
667 date_format = None
668 template = u'%s'
669
670 if data is not None:
671 data_parts = data.split('//')
672
673 # part[0]: categories
674 if len(data_parts[0]) > 0:
675 cats = []
676 if u' ' in data_parts[0]:
677 cats.append(None)
678 cats.extend(list(data_parts[0].replace(u' ', u'')))
679
680 # part[1]: date format
681 if len(data_parts) > 1:
682 if len(data_parts[1]) > 0:
683 date_format = data_parts[1]
684
685 # part[2]: template
686 if len(data_parts) > 2:
687 if len(data_parts[2]) > 0:
688 template = data_parts[2]
689
690 if mode == u'issue':
691 narr = gmNarrativeWidgets.select_narrative_by_issue(soap_cats = cats)
692 else:
693 narr = gmNarrativeWidgets.select_narrative_by_episode(soap_cats = cats)
694
695 if narr is None:
696 return u''
697
698 if len(narr) == 0:
699 return u''
700
701 try:
702 narr = [ template % n.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for n in narr ]
703 except KeyError:
704 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
705
706 return u'\n'.join(narr)
707 #--------------------------------------------------------
710 #--------------------------------------------------------
712 return self._get_variant_soap(data = u's')
713 #--------------------------------------------------------
715 return self._get_variant_soap(data = u'o')
716 #--------------------------------------------------------
718 return self._get_variant_soap(data = u'a')
719 #--------------------------------------------------------
721 return self._get_variant_soap(data = u'p')
722 #--------------------------------------------------------
724 return self._get_variant_soap(data = u'u')
725 #--------------------------------------------------------
727 return self._get_variant_soap(data = u' ')
728 #--------------------------------------------------------
730
731 # default: all categories, neutral template
732 cats = list(u'soapu')
733 cats.append(None)
734 template = u'%(narrative)s'
735
736 if data is not None:
737 data_parts = data.split('//')
738
739 # part[0]: categories
740 cats = []
741 # ' ' -> None == admin
742 for cat in list(data_parts[0]):
743 if cat == u' ':
744 cat = None
745 cats.append(cat)
746 # '' -> SOAP + None
747 if cats == u'':
748 cats = list(u'soapu')
749 cats.append(None)
750
751 # part[1]: template
752 if len(data_parts) > 1:
753 template = data_parts[1]
754
755 #narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
756 narr = gmNarrativeWidgets.select_narrative(soap_cats = cats)
757
758 if narr is None:
759 return u''
760
761 if len(narr) == 0:
762 return u''
763
764 # if any "%s" is in the template there cannot be any %(field)s
765 # and we also restrict the fields to .narrative (this is the
766 # old placeholder behaviour
767 if u'%s' in template:
768 narr = [ self._escape(n['narrative']) for n in narr ]
769 else:
770 narr = [ n.fields_as_dict(escape_style = self.__esc_style) for n in narr ]
771
772 try:
773 narr = [ template % n for n in narr ]
774 except KeyError:
775 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
776 except TypeError:
777 return u'cannot mix "%%s" and "%%(field)s" in template [%s]' % template
778
779 return u'\n'.join(narr)
780 #--------------------------------------------------------
782 return self._get_variant_name(data = u'%(title)s')
783 #--------------------------------------------------------
785 return self._get_variant_name(data = u'%(firstnames)s')
786 #--------------------------------------------------------
788 return self._get_variant_name(data = u'%(lastnames)s')
789 #--------------------------------------------------------
791 if data is None:
792 return [_('template is missing')]
793
794 name = self.pat.get_active_name()
795
796 parts = {
797 'title': self._escape(gmTools.coalesce(name['title'], u'')),
798 'firstnames': self._escape(name['firstnames']),
799 'lastnames': self._escape(name['lastnames']),
800 'preferred': self._escape(gmTools.coalesce (
801 initial = name['preferred'],
802 instead = u' ',
803 template_initial = u' "%s" '
804 ))
805 }
806
807 return data % parts
808 #--------------------------------------------------------
811 #--------------------------------------------------------
812 # FIXME: extend to all supported genders
814
815 values = data.split('//', 2)
816
817 if len(values) == 2:
818 male_value, female_value = values
819 other_value = u'<unkown gender>'
820 elif len(values) == 3:
821 male_value, female_value, other_value = values
822 else:
823 return _('invalid gender mapping layout: [%s]') % data
824
825 if self.pat['gender'] == u'm':
826 return self._escape(male_value)
827
828 if self.pat['gender'] == u'f':
829 return self._escape(female_value)
830
831 return self._escape(other_value)
832 #--------------------------------------------------------
833 # address related placeholders
834 #--------------------------------------------------------
836
837 data_parts = data.split(u'//')
838
839 # address type
840 adr_type = data_parts[0].strip()
841 orig_type = adr_type
842 if adr_type != u'':
843 adrs = self.pat.get_addresses(address_type = adr_type)
844 if len(adrs) == 0:
845 _log.warning('no address for type [%s]', adr_type)
846 adr_type = u''
847 if adr_type == u'':
848 _log.debug('asking user for address type')
849 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
850 if adr is None:
851 if self.debug:
852 return _('no address type replacement selected')
853 return u''
854 adr_type = adr['address_type']
855 adr = self.pat.get_addresses(address_type = adr_type)[0]
856
857 # formatting template
858 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
859 if len(data_parts) > 1:
860 if data_parts[1].strip() != u'':
861 template = data_parts[1]
862
863 try:
864 return template % adr.fields_as_dict(escape_style = self.__esc_style)
865 except StandardError:
866 _log.exception('error formatting address')
867 _log.error('template: %s', template)
868
869 return None
870 #--------------------------------------------------------
872 requested_type = data.strip()
873 cache_key = 'adr-type-%s' % requested_type
874 try:
875 type2use = self.__cache[cache_key]
876 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
877 except KeyError:
878 type2use = requested_type
879 if type2use != u'':
880 adrs = self.pat.get_addresses(address_type = type2use)
881 if len(adrs) == 0:
882 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part)
883 type2use = u''
884 if type2use == u'':
885 _log.debug('asking user for replacement address type')
886 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat)
887 if adr is None:
888 _log.debug('no replacement selected')
889 if self.debug:
890 return self._escape(_('no address type replacement selected'))
891 return u''
892 type2use = adr['address_type']
893 self.__cache[cache_key] = type2use
894 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
895
896 return self._escape(self.pat.get_addresses(address_type = type2use)[0][part])
897 #--------------------------------------------------------
900 #--------------------------------------------------------
903 #--------------------------------------------------------
906 #--------------------------------------------------------
909 #--------------------------------------------------------
912 #--------------------------------------------------------
915 #--------------------------------------------------------
918 #--------------------------------------------------------
921 #--------------------------------------------------------
923 comm_type = None
924 template = u'%(url)s'
925 if data is not None:
926 data_parts = data.split(u'//')
927 if len(data_parts) > 0:
928 comm_type = data_parts[0]
929 if len(data_parts) > 1:
930 template = data_parts[1]
931
932 comms = self.pat.get_comm_channels(comm_medium = comm_type)
933 if len(comms) == 0:
934 if self.debug:
935 return template + u': ' + self._escape(_('no URL for comm channel [%s]') % data)
936 return u''
937
938 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
939 #--------------------------------------------------------
941
942 template = u'%s'
943 target_mime = None
944 target_ext = None
945 if data is not None:
946 parts = data.split(u'//')
947 template = parts[0]
948 if len(parts) > 1:
949 target_mime = parts[1].strip()
950 if len(parts) > 2:
951 target_ext = parts[2].strip()
952 if target_ext is None:
953 if target_mime is not None:
954 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
955
956 mugshot = self.pat.document_folder.latest_mugshot
957 if mugshot is None:
958 if self.debug:
959 return self._escape(_('no mugshot available'))
960 return u''
961
962 fname = mugshot.export_to_file (
963 target_mime = target_mime,
964 target_extension = target_ext,
965 ignore_conversion_problems = True
966 )
967 if fname is None:
968 if self.debug:
969 return self._escape(_('cannot export or convert latest mugshot'))
970 return u''
971
972 return template % fname
973 #--------------------------------------------------------
990 # #--------------------------------------------------------
991 # def _get_variant_patient_tags_table(self, data=u'?'):
992 # pass
993 #--------------------------------------------------------
994 # praxis related placeholders
995 #--------------------------------------------------------
997 options = data.split(u'//')
998
999 if u'select' in options:
1000 options.remove(u'select')
1001 branch = 'select branch'
1002 else:
1003 branch = gmPraxis.cPraxisBranch(aPK_obj = gmPraxis.gmCurrentPraxisBranch()['pk_praxis_branch'])
1004
1005 template = u'%s'
1006 if len(options) > 0:
1007 template = options[0]
1008 if template.strip() == u'':
1009 template = u'%s'
1010
1011 return template % branch.fields_as_dict(escape_style = self.__esc_style)
1012
1013 #--------------------------------------------------------
1015
1016 options = data.split(u'//')
1017
1018 # formatting template
1019 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
1020 if len(options) > 0:
1021 if options[0].strip() != u'':
1022 template = options[0]
1023
1024 adr = gmPraxis.gmCurrentPraxisBranch().address
1025 if adr is None:
1026 if self.debug:
1027 return _('no address recorded')
1028 return u''
1029 try:
1030 return template % adr.fields_as_dict(escape_style = self.__esc_style)
1031 except StandardError:
1032 _log.exception('error formatting address')
1033 _log.error('template: %s', template)
1034
1035 return None
1036 #--------------------------------------------------------
1038 options = data.split(u'//')
1039 comm_type = options[0]
1040 template = u'%(url)s'
1041 if len(options) > 1:
1042 template = options[1]
1043
1044 comms = gmPraxis.gmCurrentPraxisBranch().get_comm_channels(comm_medium = comm_type)
1045 if len(comms) == 0:
1046 if self.debug:
1047 return template + u': ' + self._escape(_('no URL for comm channel [%s]') % data)
1048 return u''
1049
1050 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1051 #--------------------------------------------------------
1052 # provider related placeholders
1053 #--------------------------------------------------------
1055 prov = gmStaff.gmCurrentProvider()
1056
1057 title = gmTools.coalesce (
1058 prov['title'],
1059 gmPerson.map_gender2salutation(prov['gender'])
1060 )
1061
1062 tmp = u'%s %s. %s' % (
1063 title,
1064 prov['firstnames'][:1],
1065 prov['lastnames']
1066 )
1067 return self._escape(tmp)
1068 #--------------------------------------------------------
1070 data_parts = data.split(u'//')
1071 if len(data_parts) < 2:
1072 return self._escape(u'current provider external ID: template is missing')
1073
1074 id_type = data_parts[0].strip()
1075 if id_type == u'':
1076 return self._escape(u'current provider external ID: type is missing')
1077
1078 issuer = data_parts[1].strip()
1079 if issuer == u'':
1080 return self._escape(u'current provider external ID: issuer is missing')
1081
1082 prov = gmStaff.gmCurrentProvider()
1083 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1084
1085 if len(ids) == 0:
1086 if self.debug:
1087 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1088 return u''
1089
1090 return self._escape(ids[0]['value'])
1091 #--------------------------------------------------------
1093 prov = self.pat.primary_provider
1094 if prov is None:
1095 return self._get_variant_current_provider()
1096
1097 title = gmTools.coalesce (
1098 prov['title'],
1099 gmPerson.map_gender2salutation(prov['gender'])
1100 )
1101
1102 tmp = u'%s %s. %s' % (
1103 title,
1104 prov['firstnames'][:1],
1105 prov['lastnames']
1106 )
1107 return self._escape(tmp)
1108 #--------------------------------------------------------
1110 data_parts = data.split(u'//')
1111 if len(data_parts) < 2:
1112 return self._escape(u'primary in-praxis provider external ID: template is missing')
1113
1114 id_type = data_parts[0].strip()
1115 if id_type == u'':
1116 return self._escape(u'primary in-praxis provider external ID: type is missing')
1117
1118 issuer = data_parts[1].strip()
1119 if issuer == u'':
1120 return self._escape(u'primary in-praxis provider external ID: issuer is missing')
1121
1122 prov = self.pat.primary_provider
1123 if prov is None:
1124 if self.debug:
1125 return self._escape(_('no primary in-praxis provider'))
1126 return u''
1127
1128 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1129
1130 if len(ids) == 0:
1131 if self.debug:
1132 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1133 return u''
1134
1135 return self._escape(ids[0]['value'])
1136 #--------------------------------------------------------
1138 data_parts = data.split(u'//')
1139 if len(data_parts) < 2:
1140 return self._escape(u'patient external ID: template is missing')
1141
1142 id_type = data_parts[0].strip()
1143 if id_type == u'':
1144 return self._escape(u'patient external ID: type is missing')
1145
1146 issuer = data_parts[1].strip()
1147 if issuer == u'':
1148 return self._escape(u'patient external ID: issuer is missing')
1149
1150 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
1151
1152 if len(ids) == 0:
1153 if self.debug:
1154 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1155 return u''
1156
1157 return self._escape(ids[0]['value'])
1158 #--------------------------------------------------------
1160 allg_state = self.pat.get_emr().allergy_state
1161
1162 if allg_state['last_confirmed'] is None:
1163 date_confirmed = u''
1164 else:
1165 date_confirmed = u' (%s)' % gmDateTime.pydt_strftime (
1166 allg_state['last_confirmed'],
1167 format = '%Y %B %d'
1168 )
1169
1170 tmp = u'%s%s' % (
1171 allg_state.state_string,
1172 date_confirmed
1173 )
1174 return self._escape(tmp)
1175 #--------------------------------------------------------
1177 if data is None:
1178 return self._escape(_('template is missing'))
1179
1180 template, separator = data.split('//', 2)
1181
1182 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() ])
1183 #--------------------------------------------------------
1185
1186 if data is None:
1187 return self._escape(_('template is missing'))
1188
1189 return u'\n'.join([ data % a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for a in self.pat.emr.get_allergies() ])
1190 #--------------------------------------------------------
1192 if data is None:
1193 return self._escape(_('current_meds_for_rx: template is missing'))
1194
1195 emr = self.pat.get_emr()
1196 from Gnumed.wxpython import gmMedicationWidgets
1197 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr)
1198 if current_meds is None:
1199 return u''
1200
1201 intakes2show = {}
1202 for intake in current_meds:
1203 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)
1204 fields_dict['medically_formatted_start'] = self._escape(intake.medically_formatted_start)
1205 if intake['pk_brand'] is None:
1206 fields_dict['brand'] = self._escape(_('generic %s') % fields_dict['substance'])
1207 fields_dict['contains'] = self._escape(u'%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit']))
1208 intakes2show[fields_dict['brand']] = fields_dict
1209 else:
1210 comps = [ c.split('::') for c in intake.containing_drug['components'] ]
1211 fields_dict['contains'] = self._escape(u'; '.join([ u'%s %s%s' % (c[0], c[1], c[2]) for c in comps ]))
1212 intakes2show[intake['brand']] = fields_dict # this will make multi-component drugs unique
1213
1214 intakes2dispense = {}
1215 for brand, intake in intakes2show.items():
1216 msg = _('Dispense how much/many of "%(brand)s (%(contains)s)" ?') % intake
1217 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?'))
1218 if amount2dispense == u'':
1219 continue
1220 intake['amount2dispense'] = amount2dispense
1221 intakes2dispense[brand] = intake
1222
1223 return u'\n'.join([ data % intake for intake in intakes2dispense.values() ])
1224 #--------------------------------------------------------
1226
1227 if data is None:
1228 return self._escape(_('template is missing'))
1229
1230 parts = data.split(u'//')
1231 template = parts[0]
1232 ask_user = False
1233 if len(parts) > 1:
1234 ask_user = (parts[1] == u'select')
1235
1236 emr = self.pat.get_emr()
1237 if ask_user:
1238 from Gnumed.wxpython import gmMedicationWidgets
1239 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr)
1240 if current_meds is None:
1241 return u''
1242 else:
1243 current_meds = emr.get_current_substance_intakes (
1244 include_inactive = False,
1245 include_unapproved = True,
1246 order_by = u'brand, substance'
1247 )
1248 if len(current_meds) == 0:
1249 return u''
1250
1251 lines = []
1252 for m in current_meds:
1253 data = m.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)
1254 data['medically_formatted_start'] = self._escape(intake.medically_formatted_start)
1255 lines.append(template % data)
1256
1257 return u'\n'.join(lines)
1258 #--------------------------------------------------------
1260 return gmMedication.format_substance_intake (
1261 emr = self.pat.emr,
1262 output_format = self.__esc_style,
1263 table_type = u'by-brand'
1264 )
1265 #--------------------------------------------------------
1267 return gmMedication.format_substance_intake_notes (
1268 emr = self.pat.get_emr(),
1269 output_format = self.__esc_style,
1270 table_type = u'by-brand'
1271 )
1272 #--------------------------------------------------------
1274 return gmPathLab.format_test_results (
1275 results = self.pat.emr.get_test_results_by_date(),
1276 output_format = self.__esc_style
1277 )
1278 #--------------------------------------------------------
1280
1281 template = u''
1282 date_format = '%Y %b %d %H:%M'
1283 separator = u'\n'
1284
1285 options = data.split(u'//')
1286 try:
1287 template = options[0].strip()
1288 date_format = options[1]
1289 separator = options[2]
1290 except IndexError:
1291 pass
1292
1293 if date_format.strip() == u'':
1294 date_format = '%Y %b %d %H:%M'
1295 if separator.strip() == u'':
1296 separator = u'\n'
1297
1298 #results = gmMeasurementWidgets.manage_measurements(single_selection = False, emr = self.pat.emr)
1299 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements
1300 results = manage_measurements(single_selection = False, emr = self.pat.emr)
1301 if results is None:
1302 if self.debug:
1303 return self._escape(_('no results for this patient (available or selected)'))
1304 return u''
1305
1306 if template == u'':
1307 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results ])
1308
1309 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results ])
1310 #--------------------------------------------------------
1312 return gmVaccination.format_latest_vaccinations (
1313 output_format = self.__esc_style,
1314 emr = self.pat.emr
1315 )
1316 #--------------------------------------------------------
1318 options = data.split('//')
1319 template = options[0]
1320 if len(options) > 1:
1321 date_format = options[1]
1322 else:
1323 date_format = u'%Y %b %d'
1324
1325 vaccs = self.pat.emr.get_vaccinations(order_by = u'date_given DESC, vaccine')
1326
1327 return u'\n'.join([ template % v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for v in vaccs ])
1328 #--------------------------------------------------------
1330
1331 if data is None:
1332 if self.debug:
1333 _log.error('PHX: missing placeholder arguments')
1334 return self._escape(_('PHX: Invalid placeholder options.'))
1335 return u''
1336
1337 _log.debug('arguments: %s', data)
1338
1339 data_parts = data.split(u'//')
1340 template = u'%s'
1341 separator = u'\n'
1342 date_format = '%Y %b %d'
1343 try:
1344 template = data_parts[0]
1345 separator = data_parts[1]
1346 date_format = data_parts[2]
1347 except IndexError:
1348 pass
1349
1350 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr)
1351 if phxs is None:
1352 if self.debug:
1353 return self._escape(_('no PHX for this patient (available or selected)'))
1354 return u''
1355
1356 return separator.join ([
1357 template % phx.fields_as_dict (
1358 date_format = date_format,
1359 escape_style = self.__esc_style,
1360 bool_strings = (self._escape(_('yes')), self._escape(_('no')))
1361 ) for phx in phxs
1362 ])
1363 #--------------------------------------------------------
1365
1366 if data is None:
1367 return self._escape(_('template is missing'))
1368
1369 probs = self.pat.emr.get_problems()
1370
1371 return u'\n'.join([ data % p.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for p in probs ])
1372 #--------------------------------------------------------
1374 return self._escape(gmDateTime.pydt_now_here().strftime(str(data)).decode(gmI18N.get_encoding()))
1375 #--------------------------------------------------------
1378 #--------------------------------------------------------
1380 data_parts = data.split(u'//')
1381 keyword = data_parts[0]
1382 template = u'%s'
1383 if len(data_parts) > 1:
1384 template = data_parts[1]
1385
1386 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list = True)
1387
1388 if expansion is None:
1389 if self.debug:
1390 return self._escape(_('no textual expansion found for keyword <%s>') % keyword)
1391 return u''
1392
1393 #return template % self._escape(expansion)
1394 return template % expansion
1395 #--------------------------------------------------------
1397 parts = data.split(u'//')
1398 keyword = parts[0]
1399 template = u'%s'
1400 target_mime = None
1401 target_ext = None
1402 if len(parts) > 1:
1403 template = parts[1]
1404 if len(parts) > 2:
1405 target_mime = parts[2].strip()
1406 if len(parts) > 3:
1407 target_ext = parts[3].strip()
1408 if target_ext is None:
1409 if target_mime is not None:
1410 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
1411
1412 expansion = gmKeywordExpansion.get_expansion (
1413 keyword = keyword,
1414 textual_only = False,
1415 binary_only = True
1416 )
1417 if expansion is None:
1418 if self.debug:
1419 return self._escape(_('no binary expansion found for keyword <%s>') % keyword)
1420 return u''
1421
1422 filename = expansion.export_to_file()
1423 if filename is None:
1424 if self.debug:
1425 return self._escape(_('cannot export data of binary expansion keyword <%s>') % keyword)
1426 return u''
1427
1428 if expansion['is_encrypted']:
1429 pwd = wx.GetPasswordFromUser (
1430 message = _('Enter your GnuPG passphrase for decryption of [%s]') % expansion['keyword'],
1431 caption = _('GnuPG passphrase prompt'),
1432 default_value = u''
1433 )
1434 filename = gmTools.gpg_decrypt_file(filename = filename, passphrase = pwd)
1435 if filename is None:
1436 if self.debug:
1437 return self._escape(_('cannot decrypt data of binary expansion keyword <%s>') % keyword)
1438 return u''
1439
1440 target_fname = gmTools.get_unique_filename (
1441 prefix = '%s-converted-' % os.path.splitext(filename)[0],
1442 suffix = target_ext
1443 )
1444 if not gmMimeLib.convert_file(filename = filename, target_mime = target_mime, target_filename = target_fname):
1445 if self.debug:
1446 return self._escape(_('cannot convert data of binary expansion keyword <%s>') % keyword)
1447 # hoping that the target can cope:
1448 return template % filename
1449
1450 return template % target_fname
1451 #--------------------------------------------------------
1453
1454 if data is None:
1455 msg = _('generic text')
1456 else:
1457 msg = data
1458
1459 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
1460 None,
1461 -1,
1462 title = _('Replacing <free_text> placeholder'),
1463 msg = _('Below you can enter free text.\n\n [%s]') % msg
1464 )
1465 dlg.enable_user_formatting = True
1466 decision = dlg.ShowModal()
1467
1468 if decision != wx.ID_SAVE:
1469 dlg.Destroy()
1470 if self.debug:
1471 return self._escape(_('Text input cancelled by user.'))
1472 return u''
1473
1474 text = dlg.value.strip()
1475 if dlg.is_user_formatted:
1476 dlg.Destroy()
1477 return text
1478
1479 dlg.Destroy()
1480
1481 return self._escape(text)
1482 #--------------------------------------------------------
1484 try:
1485 bill = self.__cache['bill']
1486 except KeyError:
1487 from Gnumed.wxpython import gmBillingWidgets
1488 bill = gmBillingWidgets.manage_bills(patient = self.pat)
1489 if bill is None:
1490 if self.debug:
1491 return self._escape(_('no bill selected'))
1492 return u''
1493 self.__cache['bill'] = bill
1494
1495 return data % bill.fields_as_dict(date_format = '%Y %B %d', escape_style = self.__esc_style)
1496 #--------------------------------------------------------
1498 try:
1499 bill = self.__cache['bill']
1500 except KeyError:
1501 from Gnumed.wxpython import gmBillingWidgets
1502 bill = gmBillingWidgets.manage_bills(patient = self.pat)
1503 if bill is None:
1504 if self.debug:
1505 return self._escape(_('no bill selected'))
1506 return u''
1507 self.__cache['bill'] = bill
1508
1509 return u'\n'.join([ data % i.fields_as_dict(date_format = '%Y %B %d', escape_style = self.__esc_style) for i in bill.bill_items ])
1510 #--------------------------------------------------------
1511 # internal helpers
1512 #--------------------------------------------------------
1517 #=====================================================================
1519 """Functions a macro can legally use.
1520
1521 An instance of this class is passed to the GNUmed scripting
1522 listener. Hence, all actions a macro can legally take must
1523 be defined in this class. Thus we achieve some screening for
1524 security and also thread safety handling.
1525 """
1526 #-----------------------------------------------------------------
1528 if personality is None:
1529 raise gmExceptions.ConstructorError, 'must specify personality'
1530 self.__personality = personality
1531 self.__attached = 0
1532 self._get_source_personality = None
1533 self.__user_done = False
1534 self.__user_answer = 'no answer yet'
1535 self.__pat = gmPerson.gmCurrentPatient()
1536
1537 self.__auth_cookie = str(random.random())
1538 self.__pat_lock_cookie = str(random.random())
1539 self.__lock_after_load_cookie = str(random.random())
1540
1541 _log.info('slave mode personality is [%s]', personality)
1542 #-----------------------------------------------------------------
1543 # public API
1544 #-----------------------------------------------------------------
1546 if self.__attached:
1547 _log.error('attach with [%s] rejected, already serving a client', personality)
1548 return (0, _('attach rejected, already serving a client'))
1549 if personality != self.__personality:
1550 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
1551 return (0, _('attach to personality [%s] rejected') % personality)
1552 self.__attached = 1
1553 self.__auth_cookie = str(random.random())
1554 return (1, self.__auth_cookie)
1555 #-----------------------------------------------------------------
1557 if not self.__attached:
1558 return 1
1559 if auth_cookie != self.__auth_cookie:
1560 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
1561 return 0
1562 self.__attached = 0
1563 return 1
1564 #-----------------------------------------------------------------
1566 if not self.__attached:
1567 return 1
1568 self.__user_done = False
1569 # FIXME: use self.__sync_cookie for syncing with user interaction
1570 wx.CallAfter(self._force_detach)
1571 return 1
1572 #-----------------------------------------------------------------
1574 ver = _cfg.get(option = u'client_version')
1575 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1576 #-----------------------------------------------------------------
1578 """Shuts down this client instance."""
1579 if not self.__attached:
1580 return 0
1581 if auth_cookie != self.__auth_cookie:
1582 _log.error('non-authenticated shutdown_gnumed()')
1583 return 0
1584 wx.CallAfter(self._shutdown_gnumed, forced)
1585 return 1
1586 #-----------------------------------------------------------------
1588 """Raise ourselves to the top of the desktop."""
1589 if not self.__attached:
1590 return 0
1591 if auth_cookie != self.__auth_cookie:
1592 _log.error('non-authenticated raise_gnumed()')
1593 return 0
1594 return "cMacroPrimitives.raise_gnumed() not implemented"
1595 #-----------------------------------------------------------------
1597 if not self.__attached:
1598 return 0
1599 if auth_cookie != self.__auth_cookie:
1600 _log.error('non-authenticated get_loaded_plugins()')
1601 return 0
1602 gb = gmGuiBroker.GuiBroker()
1603 return gb['horstspace.notebook.gui'].keys()
1604 #-----------------------------------------------------------------
1606 """Raise a notebook plugin within GNUmed."""
1607 if not self.__attached:
1608 return 0
1609 if auth_cookie != self.__auth_cookie:
1610 _log.error('non-authenticated raise_notebook_plugin()')
1611 return 0
1612 # FIXME: use semaphore
1613 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1614 return 1
1615 #-----------------------------------------------------------------
1617 """Load external patient, perhaps create it.
1618
1619 Callers must use get_user_answer() to get status information.
1620 It is unsafe to proceed without knowing the completion state as
1621 the controlled client may be waiting for user input from a
1622 patient selection list.
1623 """
1624 if not self.__attached:
1625 return (0, _('request rejected, you are not attach()ed'))
1626 if auth_cookie != self.__auth_cookie:
1627 _log.error('non-authenticated load_patient_from_external_source()')
1628 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1629 if self.__pat.locked:
1630 _log.error('patient is locked, cannot load from external source')
1631 return (0, _('current patient is locked'))
1632 self.__user_done = False
1633 wx.CallAfter(self._load_patient_from_external_source)
1634 self.__lock_after_load_cookie = str(random.random())
1635 return (1, self.__lock_after_load_cookie)
1636 #-----------------------------------------------------------------
1638 if not self.__attached:
1639 return (0, _('request rejected, you are not attach()ed'))
1640 if auth_cookie != self.__auth_cookie:
1641 _log.error('non-authenticated lock_load_patient()')
1642 return (0, _('rejected lock_load_patient(), not authenticated'))
1643 # FIXME: ask user what to do about wrong cookie
1644 if lock_after_load_cookie != self.__lock_after_load_cookie:
1645 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1646 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1647 self.__pat.locked = True
1648 self.__pat_lock_cookie = str(random.random())
1649 return (1, self.__pat_lock_cookie)
1650 #-----------------------------------------------------------------
1652 if not self.__attached:
1653 return (0, _('request rejected, you are not attach()ed'))
1654 if auth_cookie != self.__auth_cookie:
1655 _log.error('non-authenticated lock_into_patient()')
1656 return (0, _('rejected lock_into_patient(), not authenticated'))
1657 if self.__pat.locked:
1658 _log.error('patient is already locked')
1659 return (0, _('already locked into a patient'))
1660 searcher = gmPersonSearch.cPatientSearcher_SQL()
1661 if type(search_params) == types.DictType:
1662 idents = searcher.get_identities(search_dict=search_params)
1663 raise StandardError("must use dto, not search_dict")
1664 else:
1665 idents = searcher.get_identities(search_term=search_params)
1666 if idents is None:
1667 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1668 if len(idents) == 0:
1669 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1670 # FIXME: let user select patient
1671 if len(idents) > 1:
1672 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1673 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1674 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1675 self.__pat.locked = True
1676 self.__pat_lock_cookie = str(random.random())
1677 return (1, self.__pat_lock_cookie)
1678 #-----------------------------------------------------------------
1680 if not self.__attached:
1681 return (0, _('request rejected, you are not attach()ed'))
1682 if auth_cookie != self.__auth_cookie:
1683 _log.error('non-authenticated unlock_patient()')
1684 return (0, _('rejected unlock_patient, not authenticated'))
1685 # we ain't locked anyways, so succeed
1686 if not self.__pat.locked:
1687 return (1, '')
1688 # FIXME: ask user what to do about wrong cookie
1689 if unlock_cookie != self.__pat_lock_cookie:
1690 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1691 return (0, 'patient unlock request rejected, wrong cookie provided')
1692 self.__pat.locked = False
1693 return (1, '')
1694 #-----------------------------------------------------------------
1695 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
1696 if not self.__attached:
1697 return 0
1698 if auth_cookie != self.__auth_cookie:
1699 _log.error('non-authenticated select_identity()')
1700 return 0
1701 return "cMacroPrimitives.assume_staff_identity() not implemented"
1702 #-----------------------------------------------------------------
1704 if not self.__user_done:
1705 return (0, 'still waiting')
1706 self.__user_done = False
1707 return (1, self.__user_answer)
1708 #-----------------------------------------------------------------
1709 # internal API
1710 #-----------------------------------------------------------------
1712 msg = _(
1713 'Someone tries to forcibly break the existing\n'
1714 'controlling connection. This may or may not\n'
1715 'have legitimate reasons.\n\n'
1716 'Do you want to allow breaking the connection ?'
1717 )
1718 can_break_conn = gmGuiHelpers.gm_show_question (
1719 aMessage = msg,
1720 aTitle = _('forced detach attempt')
1721 )
1722 if can_break_conn:
1723 self.__user_answer = 1
1724 else:
1725 self.__user_answer = 0
1726 self.__user_done = True
1727 if can_break_conn:
1728 self.__pat.locked = False
1729 self.__attached = 0
1730 return 1
1731 #-----------------------------------------------------------------
1733 top_win = wx.GetApp().GetTopWindow()
1734 if forced:
1735 top_win.Destroy()
1736 else:
1737 top_win.Close()
1738 #-----------------------------------------------------------------
1740 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True)
1741 if patient is not None:
1742 self.__user_answer = 1
1743 else:
1744 self.__user_answer = 0
1745 self.__user_done = True
1746 return 1
1747 #=====================================================================
1748 # main
1749 #=====================================================================
1750 if __name__ == '__main__':
1751
1752 if len(sys.argv) < 2:
1753 sys.exit()
1754
1755 if sys.argv[1] != 'test':
1756 sys.exit()
1757
1758 gmI18N.activate_locale()
1759 gmI18N.install_domain()
1760
1761 #--------------------------------------------------------
1763 handler = gmPlaceholderHandler()
1764 handler.debug = True
1765
1766 for placeholder in ['a', 'b']:
1767 print handler[placeholder]
1768
1769 pat = gmPersonSearch.ask_for_patient()
1770 if pat is None:
1771 return
1772
1773 gmPatSearchWidgets.set_active_patient(patient = pat)
1774
1775 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1776
1777 app = wx.PyWidgetTester(size = (200, 50))
1778
1779 ph = 'progress_notes::ap'
1780 print '%s: %s' % (ph, handler[ph])
1781 #--------------------------------------------------------
1783
1784 tests = [
1785 # should work:
1786 '$<lastname>$',
1787 '$<lastname::::3>$',
1788 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1789
1790 # should fail:
1791 'lastname',
1792 '$<lastname',
1793 '$<lastname::',
1794 '$<lastname::>$',
1795 '$<lastname::abc>$',
1796 '$<lastname::abc::>$',
1797 '$<lastname::abc::3>$',
1798 '$<lastname::abc::xyz>$',
1799 '$<lastname::::>$',
1800 '$<lastname::::xyz>$',
1801
1802 '$<date_of_birth::%Y-%m-%d>$',
1803 '$<date_of_birth::%Y-%m-%d::3>$',
1804 '$<date_of_birth::%Y-%m-%d::>$',
1805
1806 # should work:
1807 '$<adr_location::home::35>$',
1808 '$<gender_mapper::male//female//other::5>$',
1809 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1810 '$<allergy_list::%(descriptor)s, >$',
1811 '$<current_meds_table::latex//by-brand>$'
1812
1813 # 'firstname',
1814 # 'title',
1815 # 'date_of_birth',
1816 # 'progress_notes',
1817 # 'soap',
1818 # 'soap_s',
1819 # 'soap_o',
1820 # 'soap_a',
1821 # 'soap_p',
1822
1823 # 'soap',
1824 # 'progress_notes',
1825 # 'date_of_birth'
1826 ]
1827
1828 # tests = [
1829 # '$<latest_vaccs_table::latex>$'
1830 # ]
1831
1832 pat = gmPersonSearch.ask_for_patient()
1833 if pat is None:
1834 return
1835
1836 gmPatSearchWidgets.set_active_patient(patient = pat)
1837
1838 handler = gmPlaceholderHandler()
1839 handler.debug = True
1840
1841 for placeholder in tests:
1842 print placeholder, "=>", handler[placeholder]
1843 print "--------------"
1844 raw_input()
1845
1846 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1847
1848 # app = wx.PyWidgetTester(size = (200, 50))
1849
1850 # ph = 'progress_notes::ap'
1851 # print '%s: %s' % (ph, handler[ph])
1852
1853 #--------------------------------------------------------
1855 from Gnumed.pycommon import gmScriptingListener
1856 import xmlrpclib
1857 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1858
1859 s = xmlrpclib.ServerProxy('http://localhost:9999')
1860 print "should fail:", s.attach()
1861 print "should fail:", s.attach('wrong cookie')
1862 print "should work:", s.version()
1863 print "should fail:", s.raise_gnumed()
1864 print "should fail:", s.raise_notebook_plugin('test plugin')
1865 print "should fail:", s.lock_into_patient('kirk, james')
1866 print "should fail:", s.unlock_patient()
1867 status, conn_auth = s.attach('unit test')
1868 print "should work:", status, conn_auth
1869 print "should work:", s.version()
1870 print "should work:", s.raise_gnumed(conn_auth)
1871 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1872 print "should work:", status, pat_auth
1873 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1874 print "should work", s.unlock_patient(conn_auth, pat_auth)
1875 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1876 status, pat_auth = s.lock_into_patient(conn_auth, data)
1877 print "should work:", status, pat_auth
1878 print "should work", s.unlock_patient(conn_auth, pat_auth)
1879 print s.detach('bogus detach cookie')
1880 print s.detach(conn_auth)
1881 del s
1882
1883 listener.shutdown()
1884 #--------------------------------------------------------
1886
1887 import re as regex
1888
1889 tests = [
1890 ' $<lastname>$ ',
1891 ' $<lastname::::3>$ ',
1892
1893 # should fail:
1894 '$<date_of_birth::%Y-%m-%d>$',
1895 '$<date_of_birth::%Y-%m-%d::3>$',
1896 '$<date_of_birth::%Y-%m-%d::>$',
1897
1898 '$<adr_location::home::35>$',
1899 '$<gender_mapper::male//female//other::5>$',
1900 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1901 '$<allergy_list::%(descriptor)s, >$',
1902
1903 '\\noindent Patient: $<lastname>$, $<firstname>$',
1904 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1905 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1906 ]
1907
1908 tests = [
1909
1910 'junk $<lastname::::3>$ junk',
1911 'junk $<lastname::abc::3>$ junk',
1912 'junk $<lastname::abc>$ junk',
1913 'junk $<lastname>$ junk',
1914
1915 'junk $<lastname>$ junk $<firstname>$ junk',
1916 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1917 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1918 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1919
1920 ]
1921
1922 tests = [
1923 # u'junk $<<<date_of_birth::%Y %B %d $<inner placeholder::%Y %B %d::20>$::20>>>$ junk',
1924 # u'junk $<date_of_birth::%Y %B %d::20>$ $<date_of_birth::%Y %B %d::20>$',
1925 # u'junk $<date_of_birth::%Y %B %d::>$ $<date_of_birth::%Y %B %d::20>$ $<<date_of_birth::%Y %B %d::20>>$',
1926 # u'junk $<date_of_birth::::20>$',
1927 # u'junk $<date_of_birth::::>$',
1928 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>>>$',
1929 ]
1930
1931 print "testing placeholder regex:", first_order_placeholder_regex
1932 print ""
1933
1934 for t in tests:
1935 print 'line: "%s"' % t
1936 phs = regex.findall(first_order_placeholder_regex, t, regex.IGNORECASE)
1937 print " %s placeholders:" % len(phs)
1938 for p in phs:
1939 print ' => "%s"' % p
1940 print " "
1941 #--------------------------------------------------------
1943
1944 phs = [
1945 #u'emr_journal::soapu //%(clin_when)s %(modified_by)s %(soap_cat)s %(narrative)s//1000 days::',
1946 #u'free_text::placeholder test::9999',
1947 #u'soap_for_encounters:://::9999',
1948 #u'soap_p',
1949 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30',
1950 #u'patient_comm::homephone::1234',
1951 #u'$<patient_address::work::1234>$',
1952 #u'adr_region::home::1234',
1953 #u'adr_country::fehlt::1234',
1954 #u'adr_subunit::fehlt::1234',
1955 #u'adr_suburb::fehlt-auch::1234',
1956 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
1957 #u'primary_praxis_provider',
1958 #u'current_provider',
1959 #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
1960 #u'current_provider_external_id::LANR//LÄK::1234'
1961 #u'primary_praxis_provider_external_id::LANR//LÄK::1234'
1962 #u'form_name_long::::1234',
1963 #u'form_name_long::::5',
1964 #u'form_name_long::::',
1965 #u'form_version::::5',
1966 #u'$<current_meds::\item %(brand)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s\\n::250>$',
1967 #u'$<vaccination_history::%(date_given)s: %(vaccine)s [%(batch_no)s] %(l10n_indications)s::250>$',
1968 #u'$<date_of_birth::%Y %B %d::20>$',
1969 #u'$<date_of_birth::%Y %B %d::>$',
1970 #u'$<date_of_birth::::20>$',
1971 #u'$<date_of_birth::::>$',
1972 #u'$<patient_tags::Tag "%(l10n_description)s": %(comment)s//\\n- ::250>$',
1973 #u'$<PHX::%(description)s\n side: %(laterality)s, active: %(is_active)s, relevant: %(clinically_relevant)s, caused death: %(is_cause_of_death)s//\n//%Y %B %d//latex::250>$',
1974 #u'$<patient_photo::\includegraphics[width=60mm]{%s}//image/png//.png::250>$',
1975 #u'$<data_snippet::binary_test_snippet//path=<%s>//image/png//.png::250>$',
1976 #u'$<data_snippet::autograph-LMcC//path=<%s>//image/jpg//.jpg::250>$',
1977 #u'$<current_meds::%s ($<lastname::::50>$)//select::>$',
1978 #u'$<current_meds::%s//select::>$',
1979 #u'$<soap_by_issue::soapu //%Y %b %d//%(narrative)s::1000>$',
1980 #u'$<soap_by_episode::soapu //%Y %b %d//%(narrative)s::1000>$',
1981 #u'$<documents::select//description//document %(clin_when)s: %(l10n_type)s// file: %(fullpath)s (<some path>/%(name)s)//~/gnumed/export/::>$',
1982 #u'$<soap::soapu //%s::9999>$',
1983 #u'$<soap::soapu //%(soap_cat)s: %(date)s | %(provider)s | %(narrative)s::9999>$'
1984 #u'$<test_results:://%c::>$'
1985 #u'$<test_results::%(unified_abbrev)s: %(unified_val)s %(val_unit)s//%c::>$'
1986 #u'$<reminders:://::>$'
1987 #u'$<current_meds_for_rx::%(brand)s (%(contains)s): dispense %(amount2dispense)s ::>$'
1988 #u'$<praxis::%(branch)s (%(praxis)s)::>$'
1989 u'$<praxis_address::::120>$'
1990 ]
1991
1992 handler = gmPlaceholderHandler()
1993 handler.debug = True
1994
1995 gmStaff.set_current_provider_to_logged_on_user()
1996 gmPraxisWidgets.set_active_praxis_branch(no_parent = True)
1997 pat = gmPersonSearch.ask_for_patient()
1998 if pat is None:
1999 return
2000 gmPatSearchWidgets.set_active_patient(patient = pat)
2001
2002 app = wx.PyWidgetTester(size = (200, 50))
2003 #handler.set_placeholder('form_name_long', 'ein Testformular')
2004 for ph in phs:
2005 print ph
2006 print " result:"
2007 print ' %s' % handler[ph]
2008 #handler.unset_placeholder('form_name_long')
2009 #--------------------------------------------------------
2011 pat = gmPersonSearch.ask_for_patient()
2012 if pat is None:
2013 sys.exit()
2014 gmPerson.set_active_patient(patient = pat)
2015 from Gnumed.wxpython import gmMedicationWidgets
2016 gmMedicationWidgets.manage_substance_intakes()
2017 #--------------------------------------------------------
2019 show_placeholders()
2020 #--------------------------------------------------------
2021
2022 #test_placeholders()
2023 #test_new_variant_placeholders()
2024 #test_scripting()
2025 #test_placeholder_regex()
2026 #test()
2027 #test_placeholder()
2028 test_show_phs()
2029
2030 #=====================================================================
2031
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:57:23 2013 | http://epydoc.sourceforge.net |