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