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