1 """GNUmed EMR structure editors
2
3 This module contains widgets to create and edit EMR structural
4 elements (issues, enconters, episodes).
5
6 This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
7 and Karsten <Karsten.Hilbert@gmx.net>.
8 """
9
10 __version__ = "$Revision: 1.114 $"
11 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
12 __license__ = "GPL"
13
14
15 import sys, re, datetime as pydt, logging, time
16
17
18
19 import wx
20 import wx.lib.pubsub as wxps
21
22
23
24 if __name__ == '__main__':
25 sys.path.insert(0, '../../')
26 from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions
27 from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery, gmPersonSearch
28 from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets
29 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg
30 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
31
32
33 _log = logging.getLogger('gm.ui')
34 _log.info(__version__)
35
36
37
48
49 def delete(procedure=None):
50 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
51 return True
52
53 gmDispatcher.send (
54 signal = u'statustext',
55 msg = _('Cannot delete performed procedure.'),
56 beep = True
57 )
58 return False
59
60 def refresh(lctrl):
61 procs = emr.get_performed_procedures()
62
63 items = [
64 [
65 u'%s%s' % (
66 p['clin_when'].strftime('%Y-%m-%d'),
67 gmTools.bool2subst (
68 p['is_ongoing'],
69 _(' (ongoing)'),
70 gmTools.coalesce (
71 initial = p['clin_end'],
72 instead = u'',
73 template_initial = u' - %s',
74 function_initial = ('strftime', u'%Y-%m-%d')
75 )
76 )
77 ),
78 p['clin_where'],
79 p['episode'],
80 p['performed_procedure']
81 ] for p in procs
82 ]
83 lctrl.set_string_items(items = items)
84 lctrl.set_data(data = procs)
85
86 gmListWidgets.get_choices_from_list (
87 parent = parent,
88 msg = _('\nSelect the procedure you want to edit !\n'),
89 caption = _('Editing performed procedures ...'),
90 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
91 single_selection = True,
92 edit_callback = edit,
93 new_callback = edit,
94 delete_callback = delete,
95 refresh_callback = refresh
96 )
97
109
110 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
111
112 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
113
122
124 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
125 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
126 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
127 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
128 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
129
130
131 mp = gmMatchProvider.cMatchProvider_SQL2 (
132 queries = [
133 u"""
134 SELECT DISTINCT ON (data) data, location
135 FROM (
136 SELECT
137 clin_where as data,
138 clin_where as location
139 FROM
140 clin.procedure
141 WHERE
142 clin_where %(fragment_condition)s
143
144 UNION ALL
145
146 SELECT
147 narrative as data,
148 narrative as location
149 FROM
150 clin.hospital_stay
151 WHERE
152 narrative %(fragment_condition)s
153 ) as union_result
154 ORDER BY data
155 LIMIT 25"""
156 ]
157 )
158 mp.setThresholds(2, 4, 6)
159 self._PRW_location.matcher = mp
160
161
162 mp = gmMatchProvider.cMatchProvider_SQL2 (
163 queries = [
164 u"""
165 select distinct on (narrative) narrative, narrative
166 from clin.procedure
167 where narrative %(fragment_condition)s
168 order by narrative
169 limit 25
170 """ ]
171 )
172 mp.setThresholds(2, 4, 6)
173 self._PRW_procedure.matcher = mp
174
176 stay = self._PRW_hospital_stay.GetData()
177 if stay is None:
178 self._PRW_hospital_stay.SetText()
179 self._PRW_location.Enable(True)
180 self._PRW_episode.Enable(True)
181 self._LBL_hospital_details.SetLabel(u'')
182 else:
183 self._PRW_location.SetText()
184 self._PRW_location.Enable(False)
185 self._PRW_episode.SetText()
186 self._PRW_episode.Enable(False)
187 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
188
190 if self._PRW_location.GetValue().strip() == u'':
191 self._PRW_hospital_stay.Enable(True)
192
193 else:
194 self._PRW_hospital_stay.SetText()
195 self._PRW_hospital_stay.Enable(False)
196 self._PRW_hospital_stay.display_as_valid(True)
197
198
210
233
234
235
237
238 has_errors = False
239
240 if not self._DPRW_date.is_valid_timestamp():
241 self._DPRW_date.display_as_valid(False)
242 has_errors = True
243 else:
244 self._DPRW_date.display_as_valid(True)
245
246 end = self._DPRW_end.GetData()
247 self._DPRW_end.display_as_valid(True)
248 if end is not None:
249 end = end.get_pydt()
250 start = self._DPRW_end.GetData()
251 if start is not None:
252 start = start.get_pydt()
253 if end < start:
254 has_errors = True
255 self._DPRW_end.display_as_valid(False)
256 if self._CHBOX_ongoing.IsChecked():
257 now = gmDateTime.pydt_now_here()
258 if end < now:
259 has_errors = True
260 self._DPRW_end.display_as_valid(False)
261
262 if self._PRW_hospital_stay.GetData() is None:
263 if self._PRW_episode.GetData() is None:
264 self._PRW_episode.display_as_valid(False)
265 has_errors = True
266 else:
267 self._PRW_episode.display_as_valid(True)
268 else:
269 self._PRW_episode.display_as_valid(True)
270
271 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
272 self._PRW_procedure.display_as_valid(False)
273 has_errors = True
274 else:
275 self._PRW_procedure.display_as_valid(True)
276
277 invalid_location = (
278 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'')
279 or
280 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'')
281 )
282 if invalid_location:
283 self._PRW_hospital_stay.display_as_valid(False)
284 self._PRW_location.display_as_valid(False)
285 has_errors = True
286 else:
287 self._PRW_hospital_stay.display_as_valid(True)
288 self._PRW_location.display_as_valid(True)
289
290 wxps.Publisher().sendMessage (
291 topic = 'statustext',
292 data = {'msg': _('Cannot save procedure.'), 'beep': True}
293 )
294
295 return (has_errors is False)
296
298
299 pat = gmPerson.gmCurrentPatient()
300 emr = pat.get_emr()
301
302 if self._PRW_hospital_stay.GetData() is None:
303 stay = None
304 epi = self._PRW_episode.GetData()
305 loc = self._PRW_location.GetValue().strip()
306 else:
307 stay = self._PRW_hospital_stay.GetData()
308 epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode']
309 loc = None
310
311 proc = emr.add_performed_procedure (
312 episode = epi,
313 location = loc,
314 hospital_stay = stay,
315 procedure = self._PRW_procedure.GetValue().strip()
316 )
317
318 proc['clin_when'] = self._DPRW_date.GetData().get_pydt()
319 if self._DPRW_end.GetData() is None:
320 proc['clin_end'] = None
321 else:
322 proc['clin_end'] = self._DPRW_end.GetData().get_pydt()
323 proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
324 proc.save()
325
326 proc.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
327
328 self.data = proc
329
330 return True
331
333 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
334
335 if self._DPRW_end.GetData() is None:
336 self.data['clin_end'] = None
337 else:
338 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
339
340 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
341
342 if self._PRW_hospital_stay.GetData() is None:
343 self.data['pk_hospital_stay'] = None
344 self.data['clin_where'] = self._PRW_location.GetValue().strip()
345 self.data['pk_episode'] = self._PRW_episode.GetData()
346 else:
347 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
348 self.data['clin_where'] = None
349 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
350 self.data['pk_episode'] = stay['pk_episode']
351
352 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
353
354 self.data.save()
355 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
356
357 return True
358
360 self._DPRW_date.SetText()
361 self._DPRW_end.SetText()
362 self._CHBOX_ongoing.SetValue(False)
363 self._CHBOX_ongoing.Enable(True)
364 self._PRW_hospital_stay.SetText()
365 self._PRW_location.SetText()
366 self._PRW_episode.SetText()
367 self._PRW_procedure.SetText()
368 self._PRW_codes.SetText()
369
370 self._PRW_procedure.SetFocus()
371
402
414
415
416
421
437
438
439
450
451 def delete(stay=None):
452 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
453 return True
454 gmDispatcher.send (
455 signal = u'statustext',
456 msg = _('Cannot delete hospital stay.'),
457 beep = True
458 )
459 return False
460
461 def refresh(lctrl):
462 stays = emr.get_hospital_stays()
463 items = [
464 [
465 s['admission'].strftime('%Y-%m-%d'),
466 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
467 s['episode'],
468 gmTools.coalesce(s['hospital'], u'')
469 ] for s in stays
470 ]
471 lctrl.set_string_items(items = items)
472 lctrl.set_data(data = stays)
473
474 gmListWidgets.get_choices_from_list (
475 parent = parent,
476 msg = _('\nSelect the hospital stay you want to edit !\n'),
477 caption = _('Editing hospital stays ...'),
478 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
479 single_selection = True,
480 edit_callback = edit,
481 new_callback = edit,
482 delete_callback = delete,
483 refresh_callback = refresh
484 )
485
486
498
500 """Phrasewheel to allow selection of a hospital stay.
501 """
503
504 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
505
506 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
507
508 mp = gmMatchProvider.cMatchProvider_SQL2 (
509 queries = [
510 u"""
511 select
512 pk_hospital_stay,
513 descr
514 from (
515 select distinct on (pk_hospital_stay)
516 pk_hospital_stay,
517 descr
518 from
519 (select
520 pk_hospital_stay,
521 (
522 to_char(admission, 'YYYY-Mon-DD')
523 || coalesce((' (' || hospital || '):'), ': ')
524 || episode
525 || coalesce((' (' || health_issue || ')'), '')
526 ) as descr
527 from
528 clin.v_pat_hospital_stays
529 where
530 %(ctxt_pat)s
531
532 hospital %(fragment_condition)s
533 or
534 episode %(fragment_condition)s
535 or
536 health_issue %(fragment_condition)s
537 ) as the_stays
538 ) as distinct_stays
539 order by descr
540 limit 25
541 """ ],
542 context = ctxt
543 )
544 mp.setThresholds(3, 4, 6)
545 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
546
547 self.matcher = mp
548 self.selection_only = True
549
550 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
551
552 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
553
557
558
559
561
562 valid = True
563
564 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
565 valid = False
566 wxps.Publisher().sendMessage (
567 topic = 'statustext',
568 data = {'msg': _('Missing admission data. Cannot save hospital stay.'), 'beep': True}
569 )
570
571 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
572 if self._PRW_discharge.date is not None:
573 if not self._PRW_discharge.date > self._PRW_admission.date:
574 valid = False
575 self._PRW_discharge.display_as_valid(False)
576 wxps.Publisher().sendMessage (
577 topic = 'statustext',
578 data = {'msg': _('Discharge date must be empty or later than admission. Cannot save hospital stay.'), 'beep': True}
579 )
580
581 if self._PRW_episode.GetValue().strip() == u'':
582 valid = False
583 self._PRW_episode.display_as_valid(False)
584 wxps.Publisher().sendMessage (
585 topic = 'statustext',
586 data = {'msg': _('Must select an episode or enter a name for a new one. Cannot save hospital stay.'), 'beep': True}
587 )
588
589 return (valid is True)
590
603
613
619
629
631 print "this was not expected to be used in this edit area"
632
633
634
643
644 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
645
647 if parent is None:
648 parent = wx.GetApp().GetTopWindow()
649
650
651 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter)
652 if dlg.ShowModal() == wx.ID_OK:
653 dlg.Destroy()
654 return True
655 dlg.Destroy()
656 return False
657
658 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
659
660 if patient is None:
661 patient = gmPerson.gmCurrentPatient()
662
663 if not patient.connected:
664 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
665 return False
666
667 if parent is None:
668 parent = wx.GetApp().GetTopWindow()
669
670 emr = patient.get_emr()
671
672
673 def refresh(lctrl):
674 if encounters is None:
675 encs = emr.get_encounters()
676 else:
677 encs = encounters
678
679 items = [
680 [
681 e['started'].strftime('%x %H:%M'),
682 e['last_affirmed'].strftime('%H:%M'),
683 e['l10n_type'],
684 gmTools.coalesce(e['reason_for_encounter'], u''),
685 gmTools.coalesce(e['assessment_of_encounter'], u''),
686 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
687 e['pk_encounter']
688 ] for e in encs
689 ]
690 lctrl.set_string_items(items = items)
691 lctrl.set_data(data = encs)
692 active_pk = emr.active_encounter['pk_encounter']
693 for idx in range(len(encs)):
694 e = encs[idx]
695 if e['pk_encounter'] == active_pk:
696 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
697
698 def new():
699 cfg_db = gmCfg.cCfgSQL()
700
701 enc_type = cfg_db.get2 (
702 option = u'encounter.default_type',
703 workplace = gmSurgery.gmCurrentPractice().active_workplace,
704 bias = u'user',
705 default = u'in surgery'
706 )
707 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
708 return edit_encounter(parent = parent, encounter = enc)
709
710 def edit(enc=None):
711 return edit_encounter(parent = parent, encounter = enc)
712
713 def edit_active(enc=None):
714 return edit_encounter(parent = parent, encounter = emr.active_encounter)
715
716 def start_new(enc=None):
717 start_new_encounter(emr = emr)
718 return True
719
720 return gmListWidgets.get_choices_from_list (
721 parent = parent,
722 msg = _('\nBelow find the relevant encounters of the patient.\n'),
723 caption = _('Encounters ...'),
724 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
725 can_return_empty = False,
726 single_selection = single_selection,
727 refresh_callback = refresh,
728 edit_callback = edit,
729 new_callback = new,
730 ignore_OK_button = ignore_OK_button,
731 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
732 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
733 )
734
736 """This is used as the callback when the EMR detects that the
737 patient was here rather recently and wants to ask the
738 provider whether to continue the recent encounter.
739 """
740 if parent is None:
741 parent = wx.GetApp().GetTopWindow()
742
743 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
744 parent = None,
745 id = -1,
746 caption = caption,
747 question = msg,
748 button_defs = [
749 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
750 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
751 ],
752 show_checkbox = False
753 )
754
755 result = dlg.ShowModal()
756 dlg.Destroy()
757
758 if result == wx.ID_YES:
759 return True
760
761 return False
762
764
765 if parent is None:
766 parent = wx.GetApp().GetTopWindow()
767
768
769 def edit(enc_type=None):
770 return edit_encounter_type(parent = parent, encounter_type = enc_type)
771
772 def delete(enc_type=None):
773 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
774 return True
775 gmDispatcher.send (
776 signal = u'statustext',
777 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
778 beep = True
779 )
780 return False
781
782 def refresh(lctrl):
783 enc_types = gmEMRStructItems.get_encounter_types()
784 lctrl.set_string_items(items = enc_types)
785
786 gmListWidgets.get_choices_from_list (
787 parent = parent,
788 msg = _('\nSelect the encounter type you want to edit !\n'),
789 caption = _('Managing encounter types ...'),
790 columns = [_('Local name'), _('Encounter type')],
791 single_selection = True,
792 edit_callback = edit,
793 new_callback = edit,
794 delete_callback = delete,
795 refresh_callback = refresh
796 )
797
807
809 """Phrasewheel to allow selection of encounter type.
810
811 - user input interpreted as encounter type in English or local language
812 - data returned is pk of corresponding encounter type or None
813 """
815
816 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
817
818 mp = gmMatchProvider.cMatchProvider_SQL2 (
819 queries = [
820 u"""
821 SELECT
822 data,
823 field_label,
824 list_label
825 FROM (
826 SELECT DISTINCT ON (data) *
827 FROM (
828 SELECT
829 pk AS data,
830 _(description) AS field_label,
831 case
832 when _(description) = description then _(description)
833 else _(description) || ' (' || description || ')'
834 end AS list_label
835 FROM
836 clin.encounter_type
837 WHERE
838 _(description) %(fragment_condition)s
839 OR
840 description %(fragment_condition)s
841 ) AS q_distinct_pk
842 ) AS q_ordered
843 ORDER BY
844 list_label
845 """ ]
846 )
847 mp.setThresholds(2, 4, 6)
848
849 self.matcher = mp
850 self.selection_only = True
851 self.picklist_delay = 50
852
854
859
860
861
862
863
893
906
916
918 self._TCTRL_l10n_name.SetValue(u'')
919 self._TCTRL_name.SetValue(u'')
920 self._TCTRL_name.Enable(True)
921
923 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
924 self._TCTRL_name.SetValue(self.data['description'])
925
926 self._TCTRL_name.Enable(False)
927
929 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
930 self._TCTRL_name.SetValue(self.data['description'])
931 self._TCTRL_name.Enable(True)
932
933
934
935
936
937
938 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
939
941
943 try:
944 self.__encounter = kwargs['encounter']
945 del kwargs['encounter']
946 except KeyError:
947 self.__encounter = None
948
949 try:
950 msg = kwargs['msg']
951 del kwargs['msg']
952 except KeyError:
953 msg = None
954
955 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
956
957 self.refresh(msg = msg)
958
959
960
961 - def refresh(self, encounter=None, msg=None):
962
963 if msg is not None:
964 self._LBL_instructions.SetLabel(msg)
965
966 if encounter is not None:
967 self.__encounter = encounter
968
969 if self.__encounter is None:
970 return True
971
972
973
974 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
975 self._LBL_patient.SetLabel(pat.get_description_gender())
976
977 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
978
979 fts = gmDateTime.cFuzzyTimestamp (
980 timestamp = self.__encounter['started'],
981 accuracy = gmDateTime.acc_minutes
982 )
983 self._PRW_start.SetText(fts.format_accurately(), data=fts)
984
985 fts = gmDateTime.cFuzzyTimestamp (
986 timestamp = self.__encounter['last_affirmed'],
987 accuracy = gmDateTime.acc_minutes
988 )
989 self._PRW_end.SetText(fts.format_accurately(), data=fts)
990
991
992 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
993 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
994 self._PRW_rfe_codes.SetText(val, data)
995
996
997 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
998 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
999 self._PRW_aoe_codes.SetText(val, data)
1000
1001
1002 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1003 self._PRW_end.SetFocus()
1004 else:
1005 self._TCTRL_aoe.SetFocus()
1006
1007 return True
1008
1010
1011 if self._PRW_encounter_type.GetData() is None:
1012 self._PRW_encounter_type.SetBackgroundColour('pink')
1013 self._PRW_encounter_type.Refresh()
1014 self._PRW_encounter_type.SetFocus()
1015 return False
1016 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1017 self._PRW_encounter_type.Refresh()
1018
1019
1020 if self._PRW_start.GetValue().strip() == u'':
1021 self._PRW_start.SetBackgroundColour('pink')
1022 self._PRW_start.Refresh()
1023 self._PRW_start.SetFocus()
1024 return False
1025 if not self._PRW_start.is_valid_timestamp():
1026 self._PRW_start.SetBackgroundColour('pink')
1027 self._PRW_start.Refresh()
1028 self._PRW_start.SetFocus()
1029 return False
1030 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1031 self._PRW_start.Refresh()
1032
1033
1034 if self._PRW_end.GetValue().strip() == u'':
1035 self._PRW_end.SetBackgroundColour('pink')
1036 self._PRW_end.Refresh()
1037 self._PRW_end.SetFocus()
1038 return False
1039 if not self._PRW_end.is_valid_timestamp():
1040 self._PRW_end.SetBackgroundColour('pink')
1041 self._PRW_end.Refresh()
1042 self._PRW_end.SetFocus()
1043 return False
1044 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1045 self._PRW_end.Refresh()
1046
1047 return True
1048
1050 if not self.__is_valid_for_save():
1051 return False
1052
1053 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1054 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1055 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1056 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1057 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1058 self.__encounter.save_payload()
1059
1060 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1061 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1062
1063 return True
1064
1065
1067
1069 encounter = kwargs['encounter']
1070 del kwargs['encounter']
1071
1072 try:
1073 button_defs = kwargs['button_defs']
1074 del kwargs['button_defs']
1075 except KeyError:
1076 button_defs = None
1077
1078 try:
1079 msg = kwargs['msg']
1080 del kwargs['msg']
1081 except KeyError:
1082 msg = None
1083
1084 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1085 self.SetSize((450, 280))
1086 self.SetMinSize((450, 280))
1087
1088 if button_defs is not None:
1089 self._BTN_save.SetLabel(button_defs[0][0])
1090 self._BTN_save.SetToolTipString(button_defs[0][1])
1091 self._BTN_close.SetLabel(button_defs[1][0])
1092 self._BTN_close.SetToolTipString(button_defs[1][1])
1093 self.Refresh()
1094
1095 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1096
1097 self.Fit()
1098
1105
1106 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1107
1109
1114
1116 self._TCTRL_encounter.SetValue(u'')
1117 self._TCTRL_encounter.SetToolTipString(u'')
1118 self._BTN_new.Enable(False)
1119 self._BTN_list.Enable(False)
1120
1122 pat = gmPerson.gmCurrentPatient()
1123 if not pat.connected:
1124 self.clear()
1125 return
1126
1127 enc = pat.get_emr().active_encounter
1128 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1129 self._TCTRL_encounter.SetToolTipString (
1130 _('The active encounter of the current patient:\n\n%s') %
1131 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1132 )
1133 self._BTN_new.Enable(True)
1134 self._BTN_list.Enable(True)
1135
1137 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1138
1139 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1140
1141
1142 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1143 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1144 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1145
1146
1147
1149 wx.CallAfter(self.clear)
1150
1152 wx.CallAfter(self.refresh)
1153 return True
1154
1158
1162
1165
1166
1167
1177
1247
1249 """Prepare changing health issue for an episode.
1250
1251 Checks for two-open-episodes conflict. When this
1252 function succeeds, the pk_health_issue has been set
1253 on the episode instance and the episode should - for
1254 all practical purposes - be ready for save_payload().
1255 """
1256
1257 if not episode['episode_open']:
1258 episode['pk_health_issue'] = target_issue['pk_health_issue']
1259 if save_to_backend:
1260 episode.save_payload()
1261 return True
1262
1263
1264 if target_issue is None:
1265 episode['pk_health_issue'] = None
1266 if save_to_backend:
1267 episode.save_payload()
1268 return True
1269
1270
1271 db_cfg = gmCfg.cCfgSQL()
1272 epi_ttl = int(db_cfg.get2 (
1273 option = u'episode.ttl',
1274 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1275 bias = 'user',
1276 default = 60
1277 ))
1278 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1279 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1280 existing_epi = target_issue.get_open_episode()
1281
1282
1283 if existing_epi is None:
1284 episode['pk_health_issue'] = target_issue['pk_health_issue']
1285 if save_to_backend:
1286 episode.save_payload()
1287 return True
1288
1289
1290 if existing_epi['pk_episode'] == episode['pk_episode']:
1291 episode['pk_health_issue'] = target_issue['pk_health_issue']
1292 if save_to_backend:
1293 episode.save_payload()
1294 return True
1295
1296
1297 move_range = episode.get_access_range()
1298 exist_range = existing_epi.get_access_range()
1299 question = _(
1300 'You want to associate the running episode:\n\n'
1301 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1302 'with the health issue:\n\n'
1303 ' "%(issue_name)s"\n\n'
1304 'There already is another episode running\n'
1305 'for this health issue:\n\n'
1306 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1307 'However, there can only be one running\n'
1308 'episode per health issue.\n\n'
1309 'Which episode do you want to close ?'
1310 ) % {
1311 'new_epi_name': episode['description'],
1312 'new_epi_start': move_range[0].strftime('%m/%y'),
1313 'new_epi_end': move_range[1].strftime('%m/%y'),
1314 'issue_name': target_issue['description'],
1315 'old_epi_name': existing_epi['description'],
1316 'old_epi_start': exist_range[0].strftime('%m/%y'),
1317 'old_epi_end': exist_range[1].strftime('%m/%y')
1318 }
1319 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1320 parent = None,
1321 id = -1,
1322 caption = _('Resolving two-running-episodes conflict'),
1323 question = question,
1324 button_defs = [
1325 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1326 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1327 ]
1328 )
1329 decision = dlg.ShowModal()
1330
1331 if decision == wx.ID_CANCEL:
1332
1333 return False
1334
1335 elif decision == wx.ID_YES:
1336
1337 existing_epi['episode_open'] = False
1338 existing_epi.save_payload()
1339
1340 elif decision == wx.ID_NO:
1341
1342 episode['episode_open'] = False
1343
1344 else:
1345 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1346
1347 episode['pk_health_issue'] = target_issue['pk_health_issue']
1348 if save_to_backend:
1349 episode.save_payload()
1350 return True
1351
1375
1377 """Let user select an episode *description*.
1378
1379 The user can select an episode description from the previously
1380 used descriptions across all episodes across all patients.
1381
1382 Selection is done with a phrasewheel so the user can
1383 type the episode name and matches will be shown. Typing
1384 "*" will show the entire list of episodes.
1385
1386 If the user types a description not existing yet a
1387 new episode description will be returned.
1388 """
1390
1391 mp = gmMatchProvider.cMatchProvider_SQL2 (
1392 queries = [
1393 u"""
1394 SELECT DISTINCT ON (description)
1395 description
1396 AS data,
1397 description
1398 AS field_label,
1399 description || ' ('
1400 || CASE
1401 WHEN is_open IS TRUE THEN _('ongoing')
1402 ELSE _('closed')
1403 END
1404 || ')'
1405 AS list_label
1406 FROM
1407 clin.episode
1408 WHERE
1409 description %(fragment_condition)s
1410 ORDER BY description
1411 LIMIT 30
1412 """
1413 ]
1414 )
1415 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1416 self.matcher = mp
1417
1419 """Let user select an episode.
1420
1421 The user can select an episode from the existing episodes of a
1422 patient. Selection is done with a phrasewheel so the user
1423 can type the episode name and matches will be shown. Typing
1424 "*" will show the entire list of episodes. Closed episodes
1425 will be marked as such. If the user types an episode name not
1426 in the list of existing episodes a new episode can be created
1427 from it if the programmer activated that feature.
1428
1429 If keyword <patient_id> is set to None or left out the control
1430 will listen to patient change signals and therefore act on
1431 gmPerson.gmCurrentPatient() changes.
1432 """
1434
1435 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1436
1437 mp = gmMatchProvider.cMatchProvider_SQL2 (
1438 queries = [
1439 u"""(
1440
1441 select
1442 pk_episode
1443 as data,
1444 description
1445 as field_label,
1446 coalesce (
1447 description || ' - ' || health_issue,
1448 description
1449 ) as list_label,
1450 1 as rank
1451 from
1452 clin.v_pat_episodes
1453 where
1454 episode_open is true and
1455 description %(fragment_condition)s
1456 %(ctxt_pat)s
1457
1458 ) union all (
1459
1460 select
1461 pk_episode
1462 as data,
1463 description
1464 as field_label,
1465 coalesce (
1466 description || _(' (closed)') || ' - ' || health_issue,
1467 description || _(' (closed)')
1468 ) as list_label,
1469 2 as rank
1470 from
1471 clin.v_pat_episodes
1472 where
1473 description %(fragment_condition)s and
1474 episode_open is false
1475 %(ctxt_pat)s
1476
1477 )
1478
1479 order by rank, list_label
1480 limit 30"""
1481 ],
1482 context = ctxt
1483 )
1484
1485 try:
1486 kwargs['patient_id']
1487 except KeyError:
1488 kwargs['patient_id'] = None
1489
1490 if kwargs['patient_id'] is None:
1491 self.use_current_patient = True
1492 self.__register_patient_change_signals()
1493 pat = gmPerson.gmCurrentPatient()
1494 if pat.connected:
1495 mp.set_context('pat', pat.ID)
1496 else:
1497 self.use_current_patient = False
1498 self.__patient_id = int(kwargs['patient_id'])
1499 mp.set_context('pat', self.__patient_id)
1500
1501 del kwargs['patient_id']
1502
1503 gmPhraseWheel.cPhraseWheel.__init__ (
1504 self,
1505 *args,
1506 **kwargs
1507 )
1508 self.matcher = mp
1509
1510
1511
1513 if self.use_current_patient:
1514 return False
1515 self.__patient_id = int(patient_id)
1516 self.set_context('pat', self.__patient_id)
1517 return True
1518
1519 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1522
1524
1525 epi_name = self.GetValue().strip()
1526 if epi_name == u'':
1527 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1528 _log.debug('cannot create episode without name')
1529 return
1530
1531 if self.use_current_patient:
1532 pat = gmPerson.gmCurrentPatient()
1533 else:
1534 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1535
1536 emr = pat.get_emr()
1537 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1538 if epi is None:
1539 self.data = {}
1540 else:
1541 self.SetText (
1542 value = epi_name,
1543 data = epi['pk_episode']
1544 )
1545
1548
1549
1550
1554
1557
1559 if self.use_current_patient:
1560 patient = gmPerson.gmCurrentPatient()
1561 self.set_context('pat', patient.ID)
1562 return True
1563
1564 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1565
1566 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1567
1580
1581
1582
1584
1585 errors = False
1586
1587 if len(self._PRW_description.GetValue().strip()) == 0:
1588 errors = True
1589 self._PRW_description.display_as_valid(False)
1590 self._PRW_description.SetFocus()
1591 else:
1592 self._PRW_description.display_as_valid(True)
1593 self._PRW_description.Refresh()
1594
1595 return not errors
1596
1598
1599 pat = gmPerson.gmCurrentPatient()
1600 emr = pat.get_emr()
1601
1602 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1603 epi['summary'] = self._TCTRL_status.GetValue().strip()
1604 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1605 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1606
1607 issue_name = self._PRW_issue.GetValue().strip()
1608 if len(issue_name) != 0:
1609 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1610 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1611
1612 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1613 gmDispatcher.send (
1614 signal = 'statustext',
1615 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1616 epi['description'],
1617 issue['description']
1618 )
1619 )
1620 gmEMRStructItems.delete_episode(episode = epi)
1621 return False
1622
1623 epi.save()
1624
1625 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1626
1627 self.data = epi
1628 return True
1629
1631
1632 self.data['description'] = self._PRW_description.GetValue().strip()
1633 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1634 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1635 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1636
1637 issue_name = self._PRW_issue.GetValue().strip()
1638 if len(issue_name) == 0:
1639 self.data['pk_health_issue'] = None
1640 else:
1641 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1642 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1643
1644 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1645 gmDispatcher.send (
1646 signal = 'statustext',
1647 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1648 self.data['description'],
1649 issue['description']
1650 )
1651 )
1652 return False
1653
1654 self.data.save()
1655 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1656
1657 return True
1658
1671
1690
1692 self._refresh_as_new()
1693
1694
1695
1705
1707
1708
1709
1711
1712 issues = kwargs['issues']
1713 del kwargs['issues']
1714
1715 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1716
1717 self.SetTitle(_('Select the health issues you are interested in ...'))
1718 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1719
1720 for issue in issues:
1721 if issue['is_confidential']:
1722 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1723 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1724 else:
1725 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1726
1727 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1728 if issue['clinically_relevant']:
1729 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1730 if issue['is_active']:
1731 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1732 if issue['is_cause_of_death']:
1733 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1734
1735 self._LCTRL_items.set_column_widths()
1736 self._LCTRL_items.set_data(data = issues)
1737
1739 """Let the user select a health issue.
1740
1741 The user can select a health issue from the existing issues
1742 of a patient. Selection is done with a phrasewheel so the user
1743 can type the issue name and matches will be shown. Typing
1744 "*" will show the entire list of issues. Inactive issues
1745 will be marked as such. If the user types an issue name not
1746 in the list of existing issues a new issue can be created
1747 from it if the programmer activated that feature.
1748
1749 If keyword <patient_id> is set to None or left out the control
1750 will listen to patient change signals and therefore act on
1751 gmPerson.gmCurrentPatient() changes.
1752 """
1754
1755 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1756
1757 mp = gmMatchProvider.cMatchProvider_SQL2 (
1758
1759 queries = [
1760 u"""
1761 SELECT
1762 data,
1763 field_label,
1764 list_label
1765 FROM ((
1766 SELECT
1767 pk_health_issue AS data,
1768 description AS field_label,
1769 description AS list_label
1770 FROM clin.v_health_issues
1771 WHERE
1772 is_active IS true
1773 AND
1774 description %(fragment_condition)s
1775 AND
1776 %(ctxt_pat)s
1777
1778 ) UNION (
1779
1780 SELECT
1781 pk_health_issue AS data,
1782 description AS field_label,
1783 description || _(' (inactive)') AS list_label
1784 FROM clin.v_health_issues
1785 WHERE
1786 is_active IS false
1787 AND
1788 description %(fragment_condition)s
1789 AND
1790 %(ctxt_pat)s
1791 )) AS union_query
1792 ORDER BY
1793 list_label"""],
1794 context = ctxt
1795 )
1796
1797 try: kwargs['patient_id']
1798 except KeyError: kwargs['patient_id'] = None
1799
1800 if kwargs['patient_id'] is None:
1801 self.use_current_patient = True
1802 self.__register_patient_change_signals()
1803 pat = gmPerson.gmCurrentPatient()
1804 if pat.connected:
1805 mp.set_context('pat', pat.ID)
1806 else:
1807 self.use_current_patient = False
1808 self.__patient_id = int(kwargs['patient_id'])
1809 mp.set_context('pat', self.__patient_id)
1810
1811 del kwargs['patient_id']
1812
1813 gmPhraseWheel.cPhraseWheel.__init__ (
1814 self,
1815 *args,
1816 **kwargs
1817 )
1818 self.matcher = mp
1819
1820
1821
1823 if self.use_current_patient:
1824 return False
1825 self.__patient_id = int(patient_id)
1826 self.set_context('pat', self.__patient_id)
1827 return True
1828
1829 - def GetData(self, can_create=False, is_open=False):
1847
1848
1849
1853
1856
1858 if self.use_current_patient:
1859 patient = gmPerson.gmCurrentPatient()
1860 self.set_context('pat', patient.ID)
1861 return True
1862
1864
1866 try:
1867 msg = kwargs['message']
1868 except KeyError:
1869 msg = None
1870 del kwargs['message']
1871 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
1872 if msg is not None:
1873 self._lbl_message.SetLabel(label=msg)
1874
1885
1886 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
1887
1888 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1889 """Panel encapsulating health issue edit area functionality."""
1890
1892
1893 try:
1894 issue = kwargs['issue']
1895 except KeyError:
1896 issue = None
1897
1898 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
1899
1900 gmEditArea.cGenericEditAreaMixin.__init__(self)
1901
1902
1903 mp = gmMatchProvider.cMatchProvider_SQL2 (
1904 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
1905 )
1906 mp.setThresholds(1, 3, 5)
1907 self._PRW_condition.matcher = mp
1908
1909 mp = gmMatchProvider.cMatchProvider_SQL2 (
1910 queries = [u"""
1911 select distinct on (grouping) grouping, grouping from (
1912
1913 select rank, grouping from ((
1914
1915 select
1916 grouping,
1917 1 as rank
1918 from
1919 clin.health_issue
1920 where
1921 grouping %%(fragment_condition)s
1922 and
1923 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
1924
1925 ) union (
1926
1927 select
1928 grouping,
1929 2 as rank
1930 from
1931 clin.health_issue
1932 where
1933 grouping %%(fragment_condition)s
1934
1935 )) as union_result
1936
1937 order by rank
1938
1939 ) as order_result
1940
1941 limit 50""" % gmPerson.gmCurrentPatient().ID
1942 ]
1943 )
1944 mp.setThresholds(1, 3, 5)
1945 self._PRW_grouping.matcher = mp
1946
1947 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1948 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1949
1950 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
1951 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
1952
1953 self._PRW_year_noted.Enable(True)
1954
1955 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
1956
1957 self.data = issue
1958
1959
1960
1980
1982 pat = gmPerson.gmCurrentPatient()
1983 emr = pat.get_emr()
1984
1985 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1986
1987 side = u''
1988 if self._ChBOX_left.GetValue():
1989 side += u's'
1990 if self._ChBOX_right.GetValue():
1991 side += u'd'
1992 issue['laterality'] = side
1993
1994 issue['summary'] = self._TCTRL_status.GetValue().strip()
1995 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1996 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1997 issue['is_active'] = self._ChBOX_active.GetValue()
1998 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1999 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2000 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2001
2002 age_noted = self._PRW_age_noted.GetData()
2003 if age_noted is not None:
2004 issue['age_noted'] = age_noted
2005
2006 issue.save()
2007
2008 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2009
2010 self.data = issue
2011 return True
2012
2014
2015 self.data['description'] = self._PRW_condition.GetValue().strip()
2016
2017 side = u''
2018 if self._ChBOX_left.GetValue():
2019 side += u's'
2020 if self._ChBOX_right.GetValue():
2021 side += u'd'
2022 self.data['laterality'] = side
2023
2024 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2025 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2026 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2027 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2028 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2029 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2030 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2031
2032 age_noted = self._PRW_age_noted.GetData()
2033 if age_noted is not None:
2034 self.data['age_noted'] = age_noted
2035
2036 self.data.save()
2037 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2038
2039 return True
2040
2042 self._PRW_condition.SetText()
2043 self._ChBOX_left.SetValue(0)
2044 self._ChBOX_right.SetValue(0)
2045 self._PRW_codes.SetText()
2046 self._on_leave_codes()
2047 self._PRW_certainty.SetText()
2048 self._PRW_grouping.SetText()
2049 self._TCTRL_status.SetValue(u'')
2050 self._PRW_age_noted.SetText()
2051 self._PRW_year_noted.SetText()
2052 self._ChBOX_active.SetValue(0)
2053 self._ChBOX_relevant.SetValue(1)
2054 self._ChBOX_confidential.SetValue(0)
2055 self._ChBOX_caused_death.SetValue(0)
2056
2057 return True
2058
2099
2101 return self._refresh_as_new()
2102
2103
2104
2106 if not self._PRW_codes.IsModified():
2107 return True
2108
2109 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2110
2112
2113 if not self._PRW_age_noted.IsModified():
2114 return True
2115
2116 str_age = self._PRW_age_noted.GetValue().strip()
2117
2118 if str_age == u'':
2119 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2120 return True
2121
2122 age = gmDateTime.str2interval(str_interval = str_age)
2123
2124 if age is None:
2125 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2126 self._PRW_age_noted.SetBackgroundColour('pink')
2127 self._PRW_age_noted.Refresh()
2128 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2129 return True
2130
2131 pat = gmPerson.gmCurrentPatient()
2132 if pat['dob'] is not None:
2133 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2134
2135 if age >= max_age:
2136 gmDispatcher.send (
2137 signal = 'statustext',
2138 msg = _(
2139 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2140 ) % (age, pat.get_medical_age())
2141 )
2142 self._PRW_age_noted.SetBackgroundColour('pink')
2143 self._PRW_age_noted.Refresh()
2144 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2145 return True
2146
2147 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2148 self._PRW_age_noted.Refresh()
2149 self._PRW_age_noted.SetData(data=age)
2150
2151 if pat['dob'] is not None:
2152 fts = gmDateTime.cFuzzyTimestamp (
2153 timestamp = pat['dob'] + age,
2154 accuracy = gmDateTime.acc_months
2155 )
2156 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2157
2158
2159
2160
2161
2162 return True
2163
2165
2166 if not self._PRW_year_noted.IsModified():
2167 return True
2168
2169 year_noted = self._PRW_year_noted.GetData()
2170
2171 if year_noted is None:
2172 if self._PRW_year_noted.GetValue().strip() == u'':
2173 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2174 return True
2175 self._PRW_year_noted.SetBackgroundColour('pink')
2176 self._PRW_year_noted.Refresh()
2177 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2178 return True
2179
2180 year_noted = year_noted.get_pydt()
2181
2182 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2183 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2184 self._PRW_year_noted.SetBackgroundColour('pink')
2185 self._PRW_year_noted.Refresh()
2186 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2187 return True
2188
2189 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2190 self._PRW_year_noted.Refresh()
2191
2192 pat = gmPerson.gmCurrentPatient()
2193 if pat['dob'] is not None:
2194 issue_age = year_noted - pat['dob']
2195 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2196 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2197
2198 return True
2199
2201 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2202 return True
2203
2205 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2206 return True
2207
2208
2209
2211
2213
2214 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2215
2216 self.selection_only = False
2217
2218 mp = gmMatchProvider.cMatchProvider_FixedList (
2219 aSeq = [
2220 {'data': u'A', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2221 {'data': u'B', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2222 {'data': u'C', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2223 {'data': u'D', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2224 ]
2225 )
2226 mp.setThresholds(1, 2, 4)
2227 self.matcher = mp
2228
2229 self.SetToolTipString(_(
2230 "The diagnostic classification or grading of this assessment.\n"
2231 "\n"
2232 "This documents how certain one is about this being a true diagnosis."
2233 ))
2234
2235
2236
2237 if __name__ == '__main__':
2238
2239
2241 """
2242 Test application for testing EMR struct widgets
2243 """
2244
2246 """
2247 Create test application UI
2248 """
2249 frame = wx.Frame (
2250 None,
2251 -4,
2252 'Testing EMR struct widgets',
2253 size=wx.Size(600, 400),
2254 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2255 )
2256 filemenu= wx.Menu()
2257 filemenu.AppendSeparator()
2258 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2259
2260
2261 menuBar = wx.MenuBar()
2262 menuBar.Append(filemenu,"&File")
2263
2264 frame.SetMenuBar(menuBar)
2265
2266 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2267 wx.DefaultPosition, wx.DefaultSize, 0 )
2268
2269
2270 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2271
2272
2273 self.__pat = gmPerson.gmCurrentPatient()
2274
2275 frame.Show(1)
2276 return 1
2277
2279 """
2280 Close test aplication
2281 """
2282 self.ExitMainLoop ()
2283
2285 app = wx.PyWidgetTester(size = (200, 300))
2286 emr = pat.get_emr()
2287 enc = emr.active_encounter
2288
2289 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
2290 app.frame.Show(True)
2291 app.MainLoop()
2292 return
2293
2295 app = wx.PyWidgetTester(size = (200, 300))
2296 emr = pat.get_emr()
2297 enc = emr.active_encounter
2298
2299
2300 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
2301 dlg.ShowModal()
2302
2303
2304
2305
2306
2308 app = wx.PyWidgetTester(size = (200, 300))
2309 emr = pat.get_emr()
2310 epi = emr.get_episodes()[0]
2311 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
2312 app.frame.Show(True)
2313 app.MainLoop()
2314
2320
2322 app = wx.PyWidgetTester(size = (400, 40))
2323 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2324 app.MainLoop()
2325
2327 app = wx.PyWidgetTester(size = (400, 40))
2328 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2329
2330 app.MainLoop()
2331
2333 app = wx.PyWidgetTester(size = (200, 300))
2334 edit_health_issue(parent=app.frame, issue=None)
2335
2337 app = wx.PyWidgetTester(size = (200, 300))
2338 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2339 app.MainLoop()
2340
2342 app = wx.PyWidgetTester(size = (200, 300))
2343 edit_procedure(parent=app.frame)
2344
2345
2346 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2347
2348 gmI18N.activate_locale()
2349 gmI18N.install_domain()
2350 gmDateTime.init()
2351
2352
2353 pat = gmPersonSearch.ask_for_patient()
2354 if pat is None:
2355 print "No patient. Exiting gracefully..."
2356 sys.exit(0)
2357 gmPatSearchWidgets.set_active_patient(patient=pat)
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375 test_edit_procedure()
2376
2377
2378