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