1 """GNUmed encounter related widgets.
2
3 This module contains widgets to manage encounters."""
4
5 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
6 __license__ = "GPL v2 or later"
7
8
9 import sys
10 import time
11 import logging
12 import datetime as pydt
13
14
15
16 import wx
17
18
19
20 if __name__ == '__main__':
21 sys.path.insert(0, '../../')
22 from Gnumed.pycommon import gmCfg
23 from Gnumed.pycommon import gmDateTime
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmDispatcher
26 from Gnumed.pycommon import gmMatchProvider
27
28 from Gnumed.business import gmEMRStructItems
29 from Gnumed.business import gmPraxis
30 from Gnumed.business import gmPerson
31 from Gnumed.business import gmStaff
32
33 from Gnumed.wxpython import gmPhraseWheel
34 from Gnumed.wxpython import gmGuiHelpers
35 from Gnumed.wxpython import gmListWidgets
36 from Gnumed.wxpython import gmEditArea
37
38
39 _log = logging.getLogger('gm.ui')
40
41
42
43
45 """This is used as the callback when the EMR detects that the
46 patient was here rather recently and wants to ask the
47 provider whether to continue the recent encounter.
48 """
49
50 if new_encounter['pk_patient'] != fairly_recent_encounter['pk_patient']:
51 raise ValueError('pk_patient values on new (enc=%s pat=%s) and fairly-recent (enc=%s pat=%s) encounter do not match' % (
52 new_encounter['pk_encounter'],
53 new_encounter['pk_patient'],
54 fairly_recent_encounter['pk_encounter'],
55 fairly_recent_encounter['pk_patient']
56 ))
57
58
59 curr_pat = gmPerson.gmCurrentPatient()
60 if new_encounter['pk_patient'] != curr_pat.ID:
61 return
62
63
64 msg = _(
65 '%s, %s [#%s]\n'
66 '\n'
67 "This patient's chart was worked on only recently:\n"
68 '\n'
69 ' %s'
70 '\n'
71 'Do you want to continue that consultation ?\n'
72 ' (If not a new one will be used.)\n'
73 ) % (
74 curr_pat.get_description_gender(with_nickname = False),
75 gmDateTime.pydt_strftime(curr_pat['dob'], '%Y %b %d'),
76 curr_pat.ID,
77 fairly_recent_encounter.format (
78 episodes = None,
79 with_soap = False,
80 left_margin = 1,
81 patient = None,
82 issues = None,
83 with_docs = False,
84 with_tests = False,
85 fancy_header = False,
86 with_vaccinations = False,
87 with_rfe_aoe = True,
88 with_family_history = False,
89 with_co_encountlet_hints = False,
90 by_episode = False
91 )
92 )
93 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
94 parent = None,
95 id = -1,
96 caption = _('Pulling chart'),
97 question = msg,
98 button_defs = [
99 {'label': _('Continue recent'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
100 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
101 ],
102 show_checkbox = False
103 )
104 result = dlg.ShowModal()
105 dlg.DestroyLater()
106
107
108 if result == wx.ID_YES:
109 _log.info('user wants to continue fairly-recent encounter')
110 curr_pat.emr.active_encounter = fairly_recent_encounter
111 if new_encounter.transfer_all_data_to_another_encounter(pk_target_encounter = fairly_recent_encounter['pk_encounter']):
112 if not gmEMRStructItems.delete_encounter(pk_encounter = new_encounter['pk_encounter']):
113 gmGuiHelpers.gm_show_info (
114 _('Properly switched to fairly recent encounter but unable to delete newly-created encounter.'),
115 _('Pulling chart')
116 )
117 else:
118 gmGuiHelpers.gm_show_info (
119 _('Unable to transfer the data from newly-created to fairly recent encounter.'),
120 _('Pulling chart')
121 )
122 return
123
124 _log.debug('stayed with newly created encounter')
125
126
128 try:
129 del kwargs['signal']
130 del kwargs['sender']
131 except KeyError:
132 pass
133 wx.CallAfter(_ask_for_encounter_continuation, **kwargs)
134
135
136
137 gmDispatcher.connect(signal = 'ask_for_encounter_continuation', receiver = __ask_for_encounter_continuation)
138
139
148
149
151
152
153
154 if gmStaff.gmCurrentProvider()['role'] == 'secretary':
155 return True
156
157 pat = gmPerson.gmCurrentPatient()
158 if not pat.connected:
159 return True
160
161 dbcfg = gmCfg.cCfgSQL()
162 check_enc = bool(dbcfg.get2 (
163 option = 'encounter.show_editor_before_patient_change',
164 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
165 bias = 'user',
166 default = True
167 ))
168
169 if not check_enc:
170 return True
171
172 emr = pat.emr
173 enc = emr.active_encounter
174
175
176 has_narr = enc.has_narrative()
177 has_docs = enc.has_documents()
178
179 if (not has_narr) and (not has_docs):
180 return True
181
182 empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == '')
183 zero_duration = (enc['last_affirmed'] == enc['started'])
184
185
186 if (not empty_aoe) and (not zero_duration):
187 return True
188
189 if zero_duration:
190 enc['last_affirmed'] = pydt.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)
191
192
193 if not has_narr:
194 if empty_aoe:
195 enc['assessment_of_encounter'] = _('only documents added')
196 enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk']
197
198 enc.save_payload()
199 return True
200
201
202 if empty_aoe:
203
204 epis = emr.get_episodes_by_encounter()
205 if len(epis) > 0:
206 enc_summary = ''
207 for epi in epis:
208 enc_summary += '%s; ' % epi['description']
209 enc['assessment_of_encounter'] = enc_summary
210
211 if msg is None:
212 msg = _('Edit the encounter details of the active patient before moving on:')
213 if parent is None:
214 parent = wx.GetApp().GetTopWindow()
215 _log.debug('sanity-check editing encounter [%s] for patient [%s]', enc['pk_encounter'], enc['pk_patient'])
216 edit_encounter(parent = parent, encounter = enc, msg = msg)
217
218 return True
219
220
221 -def edit_encounter(parent=None, encounter=None, msg=None, single_entry=False):
222 if parent is None:
223 parent = wx.GetApp().GetTopWindow()
224
225
226 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
227 if dlg.ShowModal() == wx.ID_OK:
228 dlg.DestroyLater()
229 return True
230 dlg.DestroyLater()
231 return False
232
233
236
237 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
269
270 def edit(enc=None):
271 if enc is None:
272 return False
273 return edit_encounter(parent = parent, encounter = enc)
274
275 def edit_active(enc=None):
276 return edit_encounter(parent = parent, encounter = emr.active_encounter)
277
278 def start_new(enc=None):
279 start_new_encounter(emr = emr)
280 return True
281
282 def delete(enc=None):
283 if enc is None:
284 return False
285 question = _(
286 'Really delete encounter [%s] ?\n'
287 '\n'
288 'Once deletion succeeds it cannot be undone.\n'
289 '\n'
290 'Note that it will only succeed if there\n'
291 'is no data attached to the encounter.'
292 ) % enc['pk_encounter']
293 delete_it = gmGuiHelpers.gm_show_question (
294 question = question,
295 title = _('Deleting encounter'),
296 cancel_button = False
297 )
298 if not delete_it:
299 return False
300 if gmEMRStructItems.delete_encounter(pk_encounter = enc['pk_encounter']):
301 return True
302 gmDispatcher.send (
303 signal = 'statustext',
304 msg = _('Cannot delete encounter [%s]. It is probably in use.') % enc['pk_encounter'],
305 beep = True
306 )
307 return False
308
309 def get_tooltip(data):
310 if data is None:
311 return None
312 return data.format (
313 patient = patient,
314 with_soap = False,
315 with_docs = False,
316 with_tests = False,
317 with_vaccinations = False,
318 with_rfe_aoe = True,
319 with_family_history = False,
320 by_episode=False,
321 fancy_header = True,
322 )
323
324 def refresh(lctrl):
325 if encounters is None:
326 encs = emr.get_encounters()
327 else:
328 encs = encounters
329
330 items = [
331 [
332 '%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'), e['last_affirmed'].strftime('%H:%M')),
333 e['l10n_type'],
334 gmTools.coalesce(e['praxis_branch'], ''),
335 gmTools.coalesce(e['reason_for_encounter'], ''),
336 gmTools.coalesce(e['assessment_of_encounter'], ''),
337 gmTools.bool2subst(e.has_clinical_data(), '', gmTools.u_checkmark_thin),
338 e['pk_encounter']
339 ] for e in encs
340 ]
341 lctrl.set_string_items(items = items)
342 lctrl.set_data(data = encs)
343 active_pk = emr.active_encounter['pk_encounter']
344 for idx in range(len(encs)):
345 e = encs[idx]
346 if e['pk_encounter'] == active_pk:
347 lctrl.SetItemTextColour(idx, wx.Colour('RED'))
348
349 return gmListWidgets.get_choices_from_list (
350 parent = parent,
351 msg = _("The patient's encounters.\n"),
352 caption = _('Encounters ...'),
353 columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
354 can_return_empty = False,
355 single_selection = single_selection,
356 refresh_callback = refresh,
357 edit_callback = edit,
358 new_callback = new,
359 delete_callback = delete,
360 list_tooltip_callback = get_tooltip,
361 ignore_OK_button = ignore_OK_button,
362 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
363 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
364 )
365
366
368
370 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
371
372 cmd = """
373 SELECT DISTINCT ON (list_label)
374 pk_encounter
375 AS data,
376 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
377 AS list_label,
378 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
379 AS field_label
380 FROM
381 clin.v_pat_encounters
382 WHERE
383 (
384 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
385 OR
386 l10n_type %(fragment_condition)s
387 OR
388 type %(fragment_condition)s
389 ) %(ctxt_patient)s
390 ORDER BY
391 list_label
392 LIMIT
393 30
394 """
395 context = {'ctxt_patient': {
396 'where_part': 'AND pk_patient = %(patient)s',
397 'placeholder': 'patient'
398 }}
399
400 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
401 self.matcher._SQL_data2match = """
402 SELECT
403 pk_encounter
404 AS data,
405 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
406 AS list_label,
407 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
408 AS field_label
409 FROM
410 clin.v_pat_encounters
411 WHERE
412 pk_encounter = %(pk)s
413 """
414 self.matcher.setThresholds(1, 3, 5)
415
416 self.selection_only = True
417
418 self.set_context(context = 'patient', val = None)
419
426
437
438
439 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
440
442
444 try:
445 self.__encounter = kwargs['encounter']
446 del kwargs['encounter']
447 except KeyError:
448 self.__encounter = None
449
450 try:
451 msg = kwargs['msg']
452 del kwargs['msg']
453 except KeyError:
454 msg = None
455
456 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
457
458 self.refresh(msg = msg)
459
460
461
462 - def refresh(self, encounter=None, msg=None):
463
464 if msg is not None:
465 self._LBL_instructions.SetLabel(msg)
466
467 if encounter is not None:
468 self.__encounter = encounter
469
470 if self.__encounter is None:
471 return True
472
473
474
475 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
476 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
477 curr_pat = gmPerson.gmCurrentPatient()
478 if curr_pat.connected:
479 if curr_pat.ID == self.__encounter['pk_patient']:
480 self._LBL_patient.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
481 else:
482 self._LBL_patient.SetForegroundColour('red')
483
484 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
485 self._PRW_location.Enable(True)
486 branch = self.__encounter.praxis_branch
487 if branch is None:
488 unit = self.__encounter.org_unit
489 if unit is None:
490 self._PRW_location.SetText('', data = None)
491 else:
492 self._PRW_location.Enable(False)
493 self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
494 else:
495 self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
496
497 fts = gmDateTime.cFuzzyTimestamp (
498 timestamp = self.__encounter['started'],
499 accuracy = gmDateTime.acc_minutes
500 )
501 self._PRW_start.SetText(fts.format_accurately(), data=fts)
502
503 fts = gmDateTime.cFuzzyTimestamp (
504 timestamp = self.__encounter['last_affirmed'],
505 accuracy = gmDateTime.acc_minutes
506 )
507 self._PRW_end.SetText(fts.format_accurately(), data=fts)
508
509
510 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
511 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
512 self._PRW_rfe_codes.SetText(val, data)
513
514
515 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
516 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
517 self._PRW_aoe_codes.SetText(val, data)
518
519
520 if self.__encounter['last_affirmed'] == self.__encounter['started']:
521 self._PRW_end.SetFocus()
522 else:
523 self._TCTRL_aoe.SetFocus()
524
525 return True
526
566
568 if not self.__is_valid_for_save():
569 return False
570
571 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
572 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
573 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
574 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), '')
575 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), '')
576 self.__encounter.save_payload()
577
578 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
579 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
580
581 return True
582
583
584 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
585
586
588
590 encounter = kwargs['encounter']
591 del kwargs['encounter']
592
593 try:
594 button_defs = kwargs['button_defs']
595 del kwargs['button_defs']
596 except KeyError:
597 button_defs = None
598
599 try:
600 msg = kwargs['msg']
601 del kwargs['msg']
602 except KeyError:
603 msg = None
604
605 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
606 self.SetSize((450, 280))
607 self.SetMinSize((450, 280))
608
609 if button_defs is not None:
610 self._BTN_save.SetLabel(button_defs[0][0])
611 self._BTN_save.SetToolTip(button_defs[0][1])
612 self._BTN_close.SetLabel(button_defs[1][0])
613 self._BTN_close.SetToolTip(button_defs[1][1])
614 self.Refresh()
615
616 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
617
618 self.Fit()
619
626
628 start = self._PRW_encounter_start.GetData()
629 if start is None:
630 return
631 start = start.get_pydt()
632
633 end = self._PRW_encounter_end.GetData()
634 if end is None:
635 fts = gmDateTime.cFuzzyTimestamp (
636 timestamp = start,
637 accuracy = gmDateTime.acc_minutes
638 )
639 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
640 return
641 end = end.get_pydt()
642
643 if start > end:
644 end = end.replace (
645 year = start.year,
646 month = start.month,
647 day = start.day
648 )
649 fts = gmDateTime.cFuzzyTimestamp (
650 timestamp = end,
651 accuracy = gmDateTime.acc_minutes
652 )
653 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
654 return
655
656 emr = self.__pat.emr
657 if start != emr.active_encounter['started']:
658 end = end.replace (
659 year = start.year,
660 month = start.month,
661 day = start.day
662 )
663 fts = gmDateTime.cFuzzyTimestamp (
664 timestamp = end,
665 accuracy = gmDateTime.acc_minutes
666 )
667 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
668 return
669
670 return
671
672
673 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
674
676
681
682
684 self._TCTRL_encounter.SetValue('')
685 self._TCTRL_encounter.SetToolTip('')
686 self._BTN_new.Enable(False)
687 self._BTN_list.Enable(False)
688
689
698
699
701 enc = gmPerson.gmCurrentPatient().emr.active_encounter
702 self._TCTRL_encounter.SetValue(enc.format (
703 with_docs = False,
704 with_tests = False,
705 fancy_header = False,
706 with_vaccinations = False,
707 with_family_history = False).strip('\n')
708 )
709 self._TCTRL_encounter.SetToolTip (
710 _('The active encounter of the current patient:\n\n%s') % enc.format(
711 with_docs = False,
712 with_tests = False,
713 fancy_header = True,
714 with_vaccinations = False,
715 with_rfe_aoe = True,
716 with_family_history = False).strip('\n')
717 )
718 self._BTN_new.Enable(True)
719 self._BTN_list.Enable(True)
720
721
731
732
733
734
737
738
744
745
751
752
757
758
759
760
770
771
780
781 def delete(enc_type=None):
782 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
783 return True
784 gmDispatcher.send (
785 signal = 'statustext',
786 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
787 beep = True
788 )
789 return False
790
791 def refresh(lctrl):
792 enc_types = gmEMRStructItems.get_encounter_types()
793 lctrl.set_string_items(items = enc_types)
794
795 gmListWidgets.get_choices_from_list (
796 parent = parent,
797 msg = _('\nSelect the encounter type you want to edit !\n'),
798 caption = _('Managing encounter types ...'),
799 columns = [_('Local name'), _('Encounter type')],
800 single_selection = True,
801 edit_callback = edit,
802 new_callback = edit,
803 delete_callback = delete,
804 refresh_callback = refresh
805 )
806
807
808 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
809
811
816
817
818
819
820
850
863
873
878
880 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
881 self._TCTRL_name.SetValue(self.data['description'])
882
883 self._TCTRL_name.Enable(False)
884
886 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
887 self._TCTRL_name.SetValue(self.data['description'])
888 self._TCTRL_name.Enable(True)
889
890
891
892
893
894
895
897 """Phrasewheel to allow selection of encounter type.
898
899 - user input interpreted as encounter type in English or local language
900 - data returned is pk of corresponding encounter type or None
901 """
903
904 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
905
906 mp = gmMatchProvider.cMatchProvider_SQL2 (
907 queries = [
908 """
909 SELECT
910 data,
911 field_label,
912 list_label
913 FROM (
914 SELECT DISTINCT ON (data) *
915 FROM (
916 SELECT
917 pk AS data,
918 _(description) AS field_label,
919 case
920 when _(description) = description then _(description)
921 else _(description) || ' (' || description || ')'
922 end AS list_label
923 FROM
924 clin.encounter_type
925 WHERE
926 _(description) %(fragment_condition)s
927 OR
928 description %(fragment_condition)s
929 ) AS q_distinct_pk
930 ) AS q_ordered
931 ORDER BY
932 list_label
933 """ ]
934 )
935 mp.setThresholds(2, 4, 6)
936
937 self.matcher = mp
938 self.selection_only = True
939 self.picklist_delay = 50
940
941
942
943
944 if __name__ == '__main__':
945
946 if len(sys.argv) < 2:
947 sys.exit()
948
949 if sys.argv[1] != 'test':
950 sys.exit()
951
952 from Gnumed.pycommon import gmI18N
953 gmI18N.activate_locale()
954 gmI18N.install_domain()
955
956
966
975
976
977
978
979
980
981
982
983