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 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
11 __license__ = "GPL v2 or later"
12
13
14 import sys
15 import time
16 import logging
17 import datetime as pydt
18
19
20
21 import wx
22
23
24
25 if __name__ == '__main__':
26 sys.path.insert(0, '../../')
27
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.pycommon import gmDateTime
30
31 if __name__ == '__main__':
32 gmI18N.activate_locale()
33 gmI18N.install_domain()
34 gmDateTime.init()
35
36 from Gnumed.pycommon import gmExceptions
37 from Gnumed.pycommon import gmCfg
38 from Gnumed.pycommon import gmTools
39 from Gnumed.pycommon import gmDispatcher
40 from Gnumed.pycommon import gmMatchProvider
41
42 from Gnumed.business import gmEMRStructItems
43 from Gnumed.business import gmPraxis
44 from Gnumed.business import gmPerson
45
46 from Gnumed.wxpython import gmPhraseWheel
47 from Gnumed.wxpython import gmGuiHelpers
48 from Gnumed.wxpython import gmListWidgets
49 from Gnumed.wxpython import gmEditArea
50
51
52 _log = logging.getLogger('gm.ui')
53
54
55
56
58 """Spin time in seconds but let wx go on."""
59 if time2spin == 0:
60 return
61 sleep_time = 0.1
62 total_rounds = int(time2spin / sleep_time)
63 if total_rounds < 1:
64 wx.Yield()
65 time.sleep(sleep_time)
66 return
67 rounds = 0
68 while rounds < total_rounds:
69 wx.Yield()
70 time.sleep(sleep_time)
71 rounds += 1
72
73
74
75
85
86
97
98 def delete(episode=None):
99 if gmEMRStructItems.delete_episode(episode = episode):
100 return True
101 gmDispatcher.send (
102 signal = 'statustext',
103 msg = _('Cannot delete episode.'),
104 beep = True
105 )
106 return False
107
108 def manage_issues(episode=None):
109 return select_health_issues(parent = None, emr = emr)
110
111 def get_tooltip(data):
112 if data is None:
113 return None
114 return data.format (
115 patient = pat,
116 with_summary = True,
117 with_codes = True,
118 with_encounters = False,
119 with_documents = False,
120 with_hospital_stays = False,
121 with_procedures = False,
122 with_family_history = False,
123 with_tests = False,
124 with_vaccinations = False,
125 with_health_issue = True
126 )
127
128 def refresh(lctrl):
129 epis = emr.get_episodes(order_by = 'description')
130 items = [
131 [ e['description'],
132 gmTools.bool2subst(e['episode_open'], _('ongoing'), _('closed'), '<unknown>'),
133 gmDateTime.pydt_strftime(e.best_guess_clinical_start_date, '%Y %b %d'),
134 gmTools.coalesce(e['health_issue'], '')
135 ] for e in epis
136 ]
137 lctrl.set_string_items(items = items)
138 lctrl.set_data(data = epis)
139
140 gmListWidgets.get_choices_from_list (
141 parent = parent,
142 msg = _('\nSelect the episode you want to edit !\n'),
143 caption = _('Editing episodes ...'),
144 columns = [_('Episode'), _('Status'), _('Started'), _('Health issue')],
145 single_selection = True,
146 edit_callback = edit,
147 new_callback = edit,
148 delete_callback = delete,
149 refresh_callback = refresh,
150 list_tooltip_callback = get_tooltip,
151 left_extra_button = (_('Manage issues'), _('Manage health issues'), manage_issues)
152 )
153
154
224
225
227 """Prepare changing health issue for an episode.
228
229 Checks for two-open-episodes conflict. When this
230 function succeeds, the pk_health_issue has been set
231 on the episode instance and the episode should - for
232 all practical purposes - be ready for save_payload().
233 """
234
235 if not episode['episode_open']:
236 episode['pk_health_issue'] = target_issue['pk_health_issue']
237 if save_to_backend:
238 episode.save_payload()
239 return True
240
241
242 if target_issue is None:
243 episode['pk_health_issue'] = None
244 if save_to_backend:
245 episode.save_payload()
246 return True
247
248
249 db_cfg = gmCfg.cCfgSQL()
250 epi_ttl = int(db_cfg.get2 (
251 option = 'episode.ttl',
252 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
253 bias = 'user',
254 default = 60
255 ))
256 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
257 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
258 existing_epi = target_issue.get_open_episode()
259
260
261 if existing_epi is None:
262 episode['pk_health_issue'] = target_issue['pk_health_issue']
263 if save_to_backend:
264 episode.save_payload()
265 return True
266
267
268 if existing_epi['pk_episode'] == episode['pk_episode']:
269 episode['pk_health_issue'] = target_issue['pk_health_issue']
270 if save_to_backend:
271 episode.save_payload()
272 return True
273
274
275 move_range = (episode.best_guess_clinical_start_date, episode.best_guess_clinical_end_date)
276 if move_range[1] is None:
277 move_range_end = '?'
278 else:
279 move_range_end = move_range[1].strftime('%m/%y')
280 exist_range = (existing_epi.best_guess_clinical_start_date, existing_epi.best_guess_clinical_end_date)
281 if exist_range[1] is None:
282 exist_range_end = '?'
283 else:
284 exist_range_end = exist_range[1].strftime('%m/%y')
285 question = _(
286 'You want to associate the running episode:\n\n'
287 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
288 'with the health issue:\n\n'
289 ' "%(issue_name)s"\n\n'
290 'There already is another episode running\n'
291 'for this health issue:\n\n'
292 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
293 'However, there can only be one running\n'
294 'episode per health issue.\n\n'
295 'Which episode do you want to close ?'
296 ) % {
297 'new_epi_name': episode['description'],
298 'new_epi_start': move_range[0].strftime('%m/%y'),
299 'new_epi_end': move_range_end,
300 'issue_name': target_issue['description'],
301 'old_epi_name': existing_epi['description'],
302 'old_epi_start': exist_range[0].strftime('%m/%y'),
303 'old_epi_end': exist_range_end
304 }
305 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
306 parent = None,
307 id = -1,
308 caption = _('Resolving two-running-episodes conflict'),
309 question = question,
310 button_defs = [
311 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
312 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
313 ]
314 )
315 decision = dlg.ShowModal()
316
317 if decision == wx.ID_CANCEL:
318
319 return False
320
321 elif decision == wx.ID_YES:
322
323 existing_epi['episode_open'] = False
324 existing_epi.save_payload()
325
326 elif decision == wx.ID_NO:
327
328 episode['episode_open'] = False
329
330 else:
331 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
332
333 episode['pk_health_issue'] = target_issue['pk_health_issue']
334 if save_to_backend:
335 episode.save_payload()
336 return True
337
338
362
363
365 """Let user select an episode *description*.
366
367 The user can select an episode description from the previously
368 used descriptions across all episodes across all patients.
369
370 Selection is done with a phrasewheel so the user can
371 type the episode name and matches will be shown. Typing
372 "*" will show the entire list of episodes.
373
374 If the user types a description not existing yet a
375 new episode description will be returned.
376 """
378
379 mp = gmMatchProvider.cMatchProvider_SQL2 (
380 queries = [
381 """
382 SELECT DISTINCT ON (description)
383 description
384 AS data,
385 description
386 AS field_label,
387 description || ' ('
388 || CASE
389 WHEN is_open IS TRUE THEN _('ongoing')
390 ELSE _('closed')
391 END
392 || ')'
393 AS list_label
394 FROM
395 clin.episode
396 WHERE
397 description %(fragment_condition)s
398 ORDER BY description
399 LIMIT 30
400 """
401 ]
402 )
403 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
404 self.matcher = mp
405
406
408 """Let user select an episode.
409
410 The user can select an episode from the existing episodes of a
411 patient. Selection is done with a phrasewheel so the user
412 can type the episode name and matches will be shown. Typing
413 "*" will show the entire list of episodes. Closed episodes
414 will be marked as such. If the user types an episode name not
415 in the list of existing episodes a new episode can be created
416 from it if the programmer activated that feature.
417
418 If keyword <patient_id> is set to None or left out the control
419 will listen to patient change signals and therefore act on
420 gmPerson.gmCurrentPatient() changes.
421 """
423
424 ctxt = {'ctxt_pat': {'where_part': 'and pk_patient = %(pat)s', 'placeholder': 'pat'}}
425
426 mp = gmMatchProvider.cMatchProvider_SQL2 (
427 queries = [
428 """(
429
430 SELECT
431 pk_episode
432 as data,
433 description
434 as field_label,
435 coalesce (
436 description || ' - ' || health_issue,
437 description
438 ) as list_label,
439 1 as rank
440 from
441 clin.v_pat_episodes
442 where
443 episode_open is true and
444 description %(fragment_condition)s
445 %(ctxt_pat)s
446
447 ) union all (
448
449 SELECT
450 pk_episode
451 as data,
452 description
453 as field_label,
454 coalesce (
455 description || _(' (closed)') || ' - ' || health_issue,
456 description || _(' (closed)')
457 ) as list_label,
458 2 as rank
459 from
460 clin.v_pat_episodes
461 where
462 description %(fragment_condition)s and
463 episode_open is false
464 %(ctxt_pat)s
465
466 )
467
468 order by rank, list_label
469 limit 30"""
470 ],
471 context = ctxt
472 )
473
474 try:
475 kwargs['patient_id']
476 except KeyError:
477 kwargs['patient_id'] = None
478
479 if kwargs['patient_id'] is None:
480 self.use_current_patient = True
481 self.__register_patient_change_signals()
482 pat = gmPerson.gmCurrentPatient()
483 if pat.connected:
484 mp.set_context('pat', pat.ID)
485 else:
486 self.use_current_patient = False
487 self.__patient_id = int(kwargs['patient_id'])
488 mp.set_context('pat', self.__patient_id)
489
490 del kwargs['patient_id']
491
492 gmPhraseWheel.cPhraseWheel.__init__ (
493 self,
494 *args,
495 **kwargs
496 )
497 self.matcher = mp
498
499
500
502 if self.use_current_patient:
503 return False
504 self.__patient_id = int(patient_id)
505 self.set_context('pat', self.__patient_id)
506 return True
507
508 - def GetData(self, can_create=False, as_instance=False, is_open=False):
511
513
514 epi_name = self.GetValue().strip()
515 if epi_name == '':
516 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create episode without name.'), beep = True)
517 _log.debug('cannot create episode without name')
518 return
519
520 if self.use_current_patient:
521 pat = gmPerson.gmCurrentPatient()
522 else:
523 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
524
525 emr = pat.emr
526 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
527 if epi is None:
528 self.data = {}
529 else:
530 self.SetText (
531 value = epi_name,
532 data = epi['pk_episode']
533 )
534
537
538
539
543
549
551 if self.use_current_patient:
552 patient = gmPerson.gmCurrentPatient()
553 self.set_context('pat', patient.ID)
554 return True
555
556
557 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
558
559 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
560
573
574
575
576
578
579 errors = False
580
581 if len(self._PRW_description.GetValue().strip()) == 0:
582 errors = True
583 self._PRW_description.display_as_valid(False)
584 self._PRW_description.SetFocus()
585 else:
586 self._PRW_description.display_as_valid(True)
587 self._PRW_description.Refresh()
588
589 return not errors
590
591
593
594 pat = gmPerson.gmCurrentPatient()
595 emr = pat.emr
596
597 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
598 epi['summary'] = self._TCTRL_status.GetValue().strip()
599 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
600 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
601
602 issue_name = self._PRW_issue.GetValue().strip()
603 if len(issue_name) != 0:
604 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
605 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
606
607 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
608 gmDispatcher.send (
609 signal = 'statustext',
610 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
611 epi['description'],
612 issue['description']
613 )
614 )
615 gmEMRStructItems.delete_episode(episode = epi)
616 return False
617
618 epi.save()
619
620 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
621
622 self.data = epi
623 return True
624
625
627
628 self.data['description'] = self._PRW_description.GetValue().strip()
629 self.data['summary'] = self._TCTRL_status.GetValue().strip()
630 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
631 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
632
633 issue_name = self._PRW_issue.GetValue().strip()
634 if len(issue_name) == 0:
635 self.data['pk_health_issue'] = None
636 else:
637 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
638 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
639
640 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
641 gmDispatcher.send (
642 signal = 'statustext',
643 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
644 self.data['description'],
645 issue['description']
646 )
647 )
648 return False
649
650 self.data.save()
651 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
652
653 return True
654
655
670
671
701
702
704 self._refresh_as_new()
705
706
707
708
720
728
729 def delete(issue=None):
730 if gmEMRStructItems.delete_health_issue(health_issue = issue):
731 return True
732 gmDispatcher.send (
733 signal = 'statustext',
734 msg = _('Cannot delete health issue.'),
735 beep = True
736 )
737 return False
738
739 def get_tooltip(data):
740 if data is None:
741 return None
742 patient = gmPerson.cPatient(data['pk_patient'])
743 return data.format (
744 patient = patient,
745 with_summary = True,
746 with_codes = True,
747 with_episodes = True,
748 with_encounters = True,
749 with_medications = False,
750 with_hospital_stays = False,
751 with_procedures = False,
752 with_family_history = False,
753 with_documents = False,
754 with_tests = False,
755 with_vaccinations = False
756 )
757
758 def refresh(lctrl):
759 issues = emr.get_health_issues()
760 items = [
761 [
762 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), '', ''),
763 i['description'],
764 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), '', ''),
765 gmTools.bool2subst(i['is_active'], _('active'), '', ''),
766 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), '', '')
767 ] for i in issues
768 ]
769 lctrl.set_string_items(items = items)
770 lctrl.set_data(data = issues)
771
772 return gmListWidgets.get_choices_from_list (
773 parent = parent,
774 msg = _('\nSelect the health issues !\n'),
775 caption = _('Showing health issues ...'),
776 columns = ['', _('Health issue'), '', '', ''],
777 single_selection = False,
778 edit_callback = edit,
779 new_callback = edit,
780 delete_callback = delete,
781 refresh_callback = refresh
782 )
783
785
786
787
789
790 issues = kwargs['issues']
791 del kwargs['issues']
792
793 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
794
795 self.SetTitle(_('Select the health issues you are interested in ...'))
796 self._LCTRL_items.set_columns(['', _('Health Issue'), '', '', ''])
797
798 for issue in issues:
799 if issue['is_confidential']:
800 row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = _('confidential'))
801 self._LCTRL_items.SetItemTextColour(row_num, col=wx.Colour('RED'))
802 else:
803 row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = '')
804
805 self._LCTRL_items.SetItem(index = row_num, column = 1, label = issue['description'])
806 if issue['clinically_relevant']:
807 self._LCTRL_items.SetItem(index = row_num, column = 2, label = _('relevant'))
808 if issue['is_active']:
809 self._LCTRL_items.SetItem(index = row_num, column = 3, label = _('active'))
810 if issue['is_cause_of_death']:
811 self._LCTRL_items.SetItem(index = row_num, column = 4, label = _('fatal'))
812
813 self._LCTRL_items.set_column_widths()
814 self._LCTRL_items.set_data(data = issues)
815
816
818 """Let the user select a health issue.
819
820 The user can select a health issue from the existing issues
821 of a patient. Selection is done with a phrasewheel so the user
822 can type the issue name and matches will be shown. Typing
823 "*" will show the entire list of issues. Inactive issues
824 will be marked as such. If the user types an issue name not
825 in the list of existing issues a new issue can be created
826 from it if the programmer activated that feature.
827
828 If keyword <patient_id> is set to None or left out the control
829 will listen to patient change signals and therefore act on
830 gmPerson.gmCurrentPatient() changes.
831 """
833
834 ctxt = {'ctxt_pat': {'where_part': 'pk_patient=%(pat)s', 'placeholder': 'pat'}}
835
836 mp = gmMatchProvider.cMatchProvider_SQL2 (
837
838 queries = ["""
839 SELECT
840 data,
841 field_label,
842 list_label
843 FROM ((
844 SELECT
845 pk_health_issue AS data,
846 description AS field_label,
847 description AS list_label
848 FROM clin.v_health_issues
849 WHERE
850 is_active IS true
851 AND
852 description %(fragment_condition)s
853 AND
854 %(ctxt_pat)s
855
856 ) UNION (
857
858 SELECT
859 pk_health_issue AS data,
860 description AS field_label,
861 description || _(' (inactive)') AS list_label
862 FROM clin.v_health_issues
863 WHERE
864 is_active IS false
865 AND
866 description %(fragment_condition)s
867 AND
868 %(ctxt_pat)s
869 )) AS union_query
870 ORDER BY
871 list_label"""
872 ],
873 context = ctxt
874 )
875 try: kwargs['patient_id']
876 except KeyError: kwargs['patient_id'] = None
877
878 if kwargs['patient_id'] is None:
879 self.use_current_patient = True
880 self.__register_patient_change_signals()
881 pat = gmPerson.gmCurrentPatient()
882 if pat.connected:
883 mp.set_context('pat', pat.ID)
884 else:
885 self.use_current_patient = False
886 self.__patient_id = int(kwargs['patient_id'])
887 mp.set_context('pat', self.__patient_id)
888
889 del kwargs['patient_id']
890
891 gmPhraseWheel.cPhraseWheel.__init__ (
892 self,
893 *args,
894 **kwargs
895 )
896 self.matcher = mp
897
898
899
901 if self.use_current_patient:
902 return False
903 self.__patient_id = int(patient_id)
904 self.set_context('pat', self.__patient_id)
905 return True
906
908 issue_name = self.GetValue().strip()
909 if issue_name == '':
910 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create health issue without name.'), beep = True)
911 _log.debug('cannot create health issue without name')
912 return
913
914 if self.use_current_patient:
915 pat = gmPerson.gmCurrentPatient()
916 else:
917 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
918
919 emr = pat.emr
920 issue = emr.add_health_issue(issue_name = issue_name)
921
922 if issue is None:
923 self.data = {}
924 else:
925 self.SetText (
926 value = issue_name,
927 data = issue['pk_health_issue']
928 )
929
932
954
955
956
960
963
965 if self.use_current_patient:
966 patient = gmPerson.gmCurrentPatient()
967 self.set_context('pat', patient.ID)
968 return True
969
970 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
971
994
995 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
996
997 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
998 """Panel encapsulating health issue edit area functionality."""
999
1017
1019
1020
1021 mp = gmMatchProvider.cMatchProvider_SQL2 (
1022 queries = ["SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
1023 )
1024 mp.setThresholds(1, 3, 5)
1025 self._PRW_condition.matcher = mp
1026
1027 mp = gmMatchProvider.cMatchProvider_SQL2 (
1028 queries = ["""
1029 SELECT DISTINCT ON (grouping) grouping, grouping from (
1030
1031 SELECT rank, grouping from ((
1032
1033 SELECT
1034 grouping,
1035 1 as rank
1036 from
1037 clin.health_issue
1038 where
1039 grouping %%(fragment_condition)s
1040 and
1041 (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
1042
1043 ) union (
1044
1045 SELECT
1046 grouping,
1047 2 as rank
1048 from
1049 clin.health_issue
1050 where
1051 grouping %%(fragment_condition)s
1052
1053 )) as union_result
1054
1055 order by rank
1056
1057 ) as order_result
1058
1059 limit 50""" % gmPerson.gmCurrentPatient().ID
1060 ]
1061 )
1062 mp.setThresholds(1, 3, 5)
1063 self._PRW_grouping.matcher = mp
1064
1065 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1066 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1067
1068
1069
1070
1071 self._PRW_year_noted.Enable(True)
1072
1073 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
1074
1075
1076
1077
1096
1098 pat = gmPerson.gmCurrentPatient()
1099 emr = pat.emr
1100
1101 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1102
1103 side = ''
1104 if self._ChBOX_left.GetValue():
1105 side += 's'
1106 if self._ChBOX_right.GetValue():
1107 side += 'd'
1108 issue['laterality'] = side
1109
1110 issue['summary'] = self._TCTRL_status.GetValue().strip()
1111 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1112 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1113 issue['is_active'] = self._ChBOX_active.GetValue()
1114 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1115 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1116 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1117
1118 age_noted = self._PRW_age_noted.GetData()
1119 if age_noted is not None:
1120 issue['age_noted'] = age_noted
1121
1122 issue.save()
1123
1124 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1125
1126 self.data = issue
1127 return True
1128
1130
1131 self.data['description'] = self._PRW_condition.GetValue().strip()
1132
1133 side = ''
1134 if self._ChBOX_left.GetValue():
1135 side += 's'
1136 if self._ChBOX_right.GetValue():
1137 side += 'd'
1138 self.data['laterality'] = side
1139
1140 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1141 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1142 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1143 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1144 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1145 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1146 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1147
1148 age_noted = self._PRW_age_noted.GetData()
1149 if age_noted is not None:
1150 self.data['age_noted'] = age_noted
1151
1152 self.data.save()
1153 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1154
1155 return True
1156
1158 self._PRW_condition.SetText()
1159 self._ChBOX_left.SetValue(0)
1160 self._ChBOX_right.SetValue(0)
1161 self._PRW_codes.SetText()
1162 self._on_leave_codes()
1163 self._PRW_certainty.SetText()
1164 self._PRW_grouping.SetText()
1165 self._TCTRL_status.SetValue('')
1166 self._PRW_age_noted.SetText()
1167 self._PRW_year_noted.SetText()
1168 self._ChBOX_active.SetValue(1)
1169 self._ChBOX_relevant.SetValue(1)
1170 self._ChBOX_confidential.SetValue(0)
1171 self._ChBOX_caused_death.SetValue(0)
1172
1173 self._PRW_condition.SetFocus()
1174 return True
1175
1214
1216 return self._refresh_as_new()
1217
1218
1219
1221 if not self._PRW_codes.IsModified():
1222 return True
1223
1224 self._TCTRL_code_details.SetValue('- ' + '\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
1225
1227
1228 if not self._PRW_age_noted.IsModified():
1229 return True
1230
1231 age_str = self._PRW_age_noted.GetValue().strip()
1232
1233 if age_str == '':
1234 return True
1235
1236 issue_age = gmDateTime.str2interval(str_interval = age_str)
1237
1238 if issue_age is None:
1239 self.status_message = _('Cannot parse [%s] into valid interval.') % age_str
1240 self._PRW_age_noted.display_as_valid(False)
1241 return True
1242
1243 pat = gmPerson.gmCurrentPatient()
1244 if pat['dob'] is not None:
1245 max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1246 if issue_age >= max_issue_age:
1247 self.status_message = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
1248 self._PRW_age_noted.display_as_valid(False)
1249 return True
1250
1251 self._PRW_age_noted.display_as_valid(True)
1252 self._PRW_age_noted.SetText(value = age_str, data = issue_age)
1253
1254 if pat['dob'] is not None:
1255 fts = gmDateTime.cFuzzyTimestamp (
1256 timestamp = pat['dob'] + issue_age,
1257 accuracy = gmDateTime.acc_months
1258 )
1259 self._PRW_year_noted.SetText(value = str(fts), data = fts)
1260
1261 return True
1262
1264
1265 if not self._PRW_year_noted.IsModified():
1266 return True
1267
1268 year_noted = self._PRW_year_noted.GetData()
1269
1270 if year_noted is None:
1271 if self._PRW_year_noted.GetValue().strip() == '':
1272 self._PRW_year_noted.display_as_valid(True)
1273 return True
1274 self._PRW_year_noted.display_as_valid(False)
1275 return True
1276
1277 year_noted = year_noted.get_pydt()
1278
1279 if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
1280 self.status_message = _('Condition diagnosed in the future.')
1281 self._PRW_year_noted.display_as_valid(False)
1282 return True
1283
1284 self._PRW_year_noted.display_as_valid(True)
1285
1286 pat = gmPerson.gmCurrentPatient()
1287 if pat['dob'] is not None:
1288 issue_age = year_noted - pat['dob']
1289 age_str = gmDateTime.format_interval_medically(interval = issue_age)
1290 self._PRW_age_noted.SetText(age_str, issue_age, True)
1291
1292 return True
1293
1295 wx.CallAfter(self._PRW_year_noted.SetText, '', None, True)
1296 return True
1297
1299 wx.CallAfter(self._PRW_age_noted.SetText, '', None, True)
1300 return True
1301
1302
1303
1305
1307
1308 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1309
1310 self.selection_only = False
1311
1312 mp = gmMatchProvider.cMatchProvider_FixedList (
1313 aSeq = [
1314 {'data': 'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'weight': 1},
1315 {'data': 'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'weight': 1},
1316 {'data': 'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'weight': 1},
1317 {'data': 'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'weight': 1}
1318 ]
1319 )
1320 mp.setThresholds(1, 2, 4)
1321 self.matcher = mp
1322
1323 self.SetToolTip(_(
1324 "The diagnostic classification or grading of this assessment.\n"
1325 "\n"
1326 "This documents how certain one is about this being a true diagnosis."
1327 ))
1328
1329
1330
1331
1332 if __name__ == '__main__':
1333
1334 if len(sys.argv) < 2:
1335 sys.exit()
1336
1337 if sys.argv[1] != 'test':
1338 sys.exit()
1339
1340 from Gnumed.business import gmPersonSearch
1341 from Gnumed.wxpython import gmPatSearchWidgets
1342
1343
1345 """
1346 Test application for testing EMR struct widgets
1347 """
1348
1350 """
1351 Create test application UI
1352 """
1353 frame = wx.Frame (
1354 None,
1355 -4,
1356 'Testing EMR struct widgets',
1357 size=wx.Size(600, 400),
1358 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1359 )
1360 filemenu = wx.Menu()
1361 filemenu.AppendSeparator()
1362 item = filemenu.Append(ID_EXIT, "E&xit"," Terminate test application")
1363 self.Bind(wx.EVT_MENU, self.OnCloseWindow, item)
1364
1365
1366 menuBar = wx.MenuBar()
1367 menuBar.Append(filemenu,"&File")
1368
1369 frame.SetMenuBar(menuBar)
1370
1371 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1372 wx.DefaultPosition, wx.DefaultSize, 0 )
1373
1374
1375 self.__pat = gmPerson.gmCurrentPatient()
1376
1377 frame.Show(1)
1378 return 1
1379
1381 """
1382 Close test aplication
1383 """
1384 self.ExitMainLoop ()
1385
1386
1388 app = wx.PyWidgetTester(size = (200, 300))
1389 emr = pat.emr
1390 epi = emr.get_episodes()[0]
1391 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1392 app.frame.Show(True)
1393 app.MainLoop()
1394
1400
1401
1407
1408
1412
1413
1415 app = wx.PyWidgetTester(size = (200, 300))
1416 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
1417 app.MainLoop()
1418
1419
1420
1421
1422 pat = gmPersonSearch.ask_for_patient()
1423 if pat is None:
1424 print("No patient. Exiting gracefully...")
1425 sys.exit(0)
1426 gmPatSearchWidgets.set_active_patient(patient=pat)
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440 test_episode_selection_prw()
1441