| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed patient EMR tree browser."""
2 #================================================================
3 __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net"
4 __license__ = "GPL v2 or later"
5
6 # std lib
7 import sys
8 import os.path
9 import io
10 import logging
11
12
13 # 3rd party
14 import wx
15 import wx.lib.mixins.treemixin as treemixin
16
17
18 # GNUmed libs
19 from Gnumed.pycommon import gmI18N
20 from Gnumed.pycommon import gmDispatcher
21 from Gnumed.pycommon import gmExceptions
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmDateTime
24 from Gnumed.pycommon import gmLog2
25
26 from Gnumed.exporters import gmPatientExporter
27
28 from Gnumed.business import gmGenericEMRItem
29 from Gnumed.business import gmEMRStructItems
30 from Gnumed.business import gmPerson
31 from Gnumed.business import gmSOAPimporter
32 from Gnumed.business import gmPersonSearch
33 from Gnumed.business import gmSoapDefs
34 from Gnumed.business import gmClinicalRecord
35
36 from Gnumed.wxpython import gmGuiHelpers
37 from Gnumed.wxpython import gmEMRStructWidgets
38 from Gnumed.wxpython import gmEncounterWidgets
39 from Gnumed.wxpython import gmSOAPWidgets
40 from Gnumed.wxpython import gmAllergyWidgets
41 from Gnumed.wxpython import gmDemographicsWidgets
42 from Gnumed.wxpython import gmNarrativeWidgets
43 from Gnumed.wxpython import gmNarrativeWorkflows
44 from Gnumed.wxpython import gmPatSearchWidgets
45 from Gnumed.wxpython import gmVaccWidgets
46 from Gnumed.wxpython import gmFamilyHistoryWidgets
47 from Gnumed.wxpython import gmFormWidgets
48 from Gnumed.wxpython import gmTimer
49 from Gnumed.wxpython import gmHospitalStayWidgets
50 from Gnumed.wxpython import gmProcedureWidgets
51 from Gnumed.wxpython import gmGenericEMRItemWorkflows
52
53
54 _log = logging.getLogger('gm.ui')
55
56 #============================================================
58 """
59 Dump the patient's EMR from GUI client
60 @param parent - The parent widget
61 @type parent - A wx.Window instance
62 """
63 # sanity checks
64 if parent is None:
65 raise TypeError('expected wx.Window instance as parent, got <None>')
66
67 pat = gmPerson.gmCurrentPatient()
68 if not pat.connected:
69 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
70 return False
71
72 # get file name
73 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
74 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', pat.subdir_name)))
75 gmTools.mkdir(defdir)
76 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
77 dlg = wx.FileDialog (
78 parent = parent,
79 message = _("Save patient's EMR as..."),
80 defaultDir = defdir,
81 defaultFile = fname,
82 wildcard = wc,
83 style = wx.FD_SAVE
84 )
85 choice = dlg.ShowModal()
86 fname = dlg.GetPath()
87 dlg.DestroyLater()
88 if choice != wx.ID_OK:
89 return None
90
91 _log.debug('exporting EMR to [%s]', fname)
92
93 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
94 exporter = gmPatientExporter.cEmrExport(patient = pat)
95 exporter.set_output_file(output_file)
96 exporter.dump_constraints()
97 exporter.dump_demographic_record(True)
98 exporter.dump_clinical_record()
99 exporter.dump_med_docs()
100 output_file.close()
101
102 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
103 return fname
104
105 #============================================================
107 """This wx.TreeCtrl derivative displays a tree view of a medical record."""
108
109 #--------------------------------------------------------
111 """Set up our specialised tree.
112 """
113 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
114 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
115
116 self.__soap_display = None
117 self.__soap_display_mode = 'details' # "details" or "journal" or "revisions"
118 self.__img_display = None
119 self.__cb__enable_display_mode_selection = lambda x:x
120 self.__cb__select_edit_mode = lambda x:x
121 self.__cb__add_soap_editor = lambda x:x
122 self.__pat = None
123 self.__curr_node = None
124 self.__expanded_nodes = None
125
126 self.__make_popup_menus()
127 self.__register_events()
128
129 #--------------------------------------------------------
130 # external API
131 #--------------------------------------------------------
134
136 self.__soap_display = soap_display
137 self.__soap_display_prop_font = soap_display.GetFont()
138 self.__soap_display_mono_font = wx.Font(self.__soap_display_prop_font.GetNativeFontInfo())
139 self.__soap_display_mono_font.SetFamily(wx.FONTFAMILY_TELETYPE)
140 self.__soap_display_mono_font.SetPointSize(self.__soap_display_prop_font.GetPointSize() - 2)
141
142 soap_display = property(_get_soap_display, _set_soap_display)
143
144 #--------------------------------------------------------
147
149 self.__img_display = image_display
150
151 image_display = property(_get_image_display, _set_image_display)
152
153 #--------------------------------------------------------
155 if not callable(callback):
156 raise ValueError('callback [%s] not callable' % callback)
157 self.__cb__enable_display_mode_selection = callback
158
159 #--------------------------------------------------------
161 if callback is None:
162 callback = lambda x:x
163 if not callable(callback):
164 raise ValueError('edit mode selector [%s] not callable' % callback)
165 self.__cb__select_edit_mode = callback
166
167 edit_mode_selector = property(lambda x:x, _set_edit_mode_selector)
168
169 #--------------------------------------------------------
171 if callback is None:
172 callback = lambda x:x
173 if not callable(callback):
174 raise ValueError('soap editor adder [%s] not callable' % callback)
175 self.__cb__add_soap_editor = callback
176
177 soap_editor_adder = property(lambda x:x, _set_soap_editor_adder)
178
179 #--------------------------------------------------------
180 # ExpansionState mixin API
181 #--------------------------------------------------------
183 if item is None:
184 return 'invalid item'
185
186 if not item.IsOk():
187 return 'invalid item'
188
189 try:
190 node_data = self.GetItemData(item)
191 except wx.wxAssertionError:
192 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
193 _log.error('real node: %s', item)
194 _log.error('node.IsOk(): %s', item.IsOk()) # already survived this further up
195 _log.error('is root node: %s', item == self.GetRootItem())
196 _log.error('node attributes: %s', dir(item))
197 gmLog2.log_stack_trace()
198 return 'invalid item'
199
200 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
201 return 'issue::%s' % node_data['pk_health_issue']
202 if isinstance(node_data, gmEMRStructItems.cEpisode):
203 return 'episode::%s' % node_data['pk_episode']
204 if isinstance(node_data, gmEMRStructItems.cEncounter):
205 return 'encounter::%s' % node_data['pk_encounter']
206 # unassociated episodes
207 if isinstance(node_data, dict):
208 return 'dummy node::%s' % self.__pat.ID
209 # root node == EMR level
210 return 'root node::%s' % self.__pat.ID
211
212 #--------------------------------------------------------
213 # internal helpers
214 #--------------------------------------------------------
216 """Configures enabled event signals."""
217 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_item_selected)
218 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_tree_item_activated)
219 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
220 self.Bind(wx.EVT_TREE_ITEM_MENU, self._on_tree_item_context_menu)
221
222 # handle tooltips
223 # self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
224 self.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tree_item_gettooltip)
225
226 # FIXME: xxxxx signal
227 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
228 gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_mod_db)
229 gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_issue_mod_db)
230 gmDispatcher.connect(signal = 'clin.family_history_mod_db', receiver = self._on_issue_mod_db)
231
232 #--------------------------------------------------------
236
237 #--------------------------------------------------------
239 """Updates EMR browser data."""
240 # FIXME: auto select the previously self.__curr_node if not None
241 # FIXME: error handling
242
243 _log.debug('populating EMR tree')
244
245 wx.BeginBusyCursor()
246
247 if self.__pat is None:
248 self.clear_tree()
249 self.__expanded_nodes = None
250 wx.EndBusyCursor()
251 return True
252
253 # init new tree
254 root_item = self.__populate_root_node()
255 self.__curr_node = root_item
256 if self.__expanded_nodes is not None:
257 self.ExpansionState = self.__expanded_nodes
258 self.SelectItem(root_item)
259 self.Expand(root_item)
260 self.__update_text_for_selected_node() # this is fairly slow, too
261
262 wx.EndBusyCursor()
263 return True
264
265 #--------------------------------------------------------
267
268 self.DeleteAllItems()
269
270 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
271 self.SetItemData(root_item, None)
272 self.SetItemHasChildren(root_item, True)
273
274 self.__root_tooltip = self.__pat['description_gender'] + '\n'
275 if self.__pat['deceased'] is None:
276 self.__root_tooltip += ' %s (%s)\n\n' % (
277 self.__pat.get_formatted_dob(format = '%d %b %Y'),
278 self.__pat['medical_age']
279 )
280 else:
281 template = ' %s - %s (%s)\n\n'
282 self.__root_tooltip += template % (
283 self.__pat.get_formatted_dob(format = '%d.%b %Y'),
284 gmDateTime.pydt_strftime(self.__pat['deceased'], '%Y %b %d'),
285 self.__pat['medical_age']
286 )
287 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], '', '%s\n\n')
288 doc = self.__pat.primary_provider
289 if doc is not None:
290 self.__root_tooltip += '%s:\n' % _('Primary provider in this praxis')
291 self.__root_tooltip += ' %s %s %s (%s)%s\n\n' % (
292 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
293 doc['firstnames'],
294 doc['lastnames'],
295 doc['short_alias'],
296 gmTools.bool2subst(doc['is_active'], '', ' [%s]' % _('inactive'))
297 )
298 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
299 self.__root_tooltip += _('In case of emergency contact:') + '\n'
300 if self.__pat['emergency_contact'] is not None:
301 self.__root_tooltip += gmTools.wrap (
302 text = '%s\n' % self.__pat['emergency_contact'],
303 width = 60,
304 initial_indent = ' ',
305 subsequent_indent = ' '
306 )
307 if self.__pat['pk_emergency_contact'] is not None:
308 contact = self.__pat.emergency_contact_in_database
309 self.__root_tooltip += ' %s\n' % contact['description_gender']
310 self.__root_tooltip = self.__root_tooltip.strip('\n')
311 if self.__root_tooltip == '':
312 self.__root_tooltip = ' '
313
314 return root_item
315
316 #--------------------------------------------------------
318 """Displays information for the selected tree node."""
319
320 if self.__soap_display is None:
321 return
322
323 self.__soap_display.Clear()
324 self.__img_display.clear()
325
326 if self.__curr_node is None:
327 return
328
329 if not self.__curr_node.IsOk():
330 return
331
332 try:
333 node_data = self.GetItemData(self.__curr_node)
334 except wx.wxAssertionError:
335 node_data = None # fake a root node
336 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
337 _log.error('real node: %s', self.__curr_node)
338 _log.error('node.IsOk(): %s', self.__curr_node.IsOk()) # already survived this further up
339 _log.error('is root node: %s', self.__curr_node == self.GetRootItem())
340 _log.error('node attributes: %s', dir(self.__curr_node))
341 gmLog2.log_stack_trace()
342
343 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
344 self.__update_text_for_issue_node(node_data)
345 return
346
347 # unassociated episodes # FIXME: turn into real dummy issue
348 if isinstance(node_data, dict):
349 self.__update_text_for_pseudo_issue_node(node_data)
350 return
351
352 if isinstance(node_data, gmEMRStructItems.cEpisode):
353 self.__update_text_for_episode_node(node_data)
354 return
355
356 if isinstance(node_data, gmEMRStructItems.cEncounter):
357 self.__update_text_for_encounter_node(node_data)
358 return
359
360 if isinstance(node_data, gmGenericEMRItem.cGenericEMRItem):
361 self.__update_text_for_generic_node(node_data)
362 return
363
364 # root node == EMR level
365 self.__update_text_for_root_node()
366
367 #--------------------------------------------------------
454
455 #--------------------------------------------------------
480 # ignore pseudo node "free-standing episodes"
481 # if isinstance(self.__curr_node_data, dict):
482 # pass
483
484 #--------------------------------------------------------
485 # episode level
486 #--------------------------------------------------------
488 episode = self.GetItemData(self.__curr_node)
489
490 gmNarrativeWorkflows.move_progress_notes_to_another_encounter (
491 parent = self,
492 episodes = [episode['pk_episode']],
493 move_all = True
494 )
495
496 #--------------------------------------------------------
498 self.__curr_node_data['episode_open'] = not self.__curr_node_data['episode_open']
499 self.__curr_node_data.save()
500
501 #--------------------------------------------------------
504
505 #--------------------------------------------------------
507 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = self.__pat.emr)
508
509 #--------------------------------------------------------
511 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
512 parent = self,
513 id = -1,
514 caption = _('Deleting episode'),
515 button_defs = [
516 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
517 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
518 ],
519 question = _(
520 'Are you sure you want to delete this episode ?\n'
521 '\n'
522 ' "%s"\n'
523 ) % self.__curr_node_data['description']
524 )
525 result = dlg.ShowModal()
526 if result != wx.ID_YES:
527 return
528
529 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
530 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
531
532 #--------------------------------------------------------
534 self.DeleteChildren(episode_node)
535
536 emr = self.__pat.emr
537 epi = self.GetItemData(episode_node)
538 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
539 if len(encounters) == 0:
540 self.SetItemHasChildren(episode_node, False)
541 return
542
543 self.SetItemHasChildren(episode_node, True)
544
545 for enc in encounters:
546 label = '%s: %s' % (
547 enc['started'].strftime('%Y-%m-%d'),
548 gmTools.unwrap (
549 gmTools.coalesce (
550 gmTools.coalesce (
551 gmTools.coalesce (
552 enc.get_latest_soap ( # soAp
553 soap_cat = 'a',
554 episode = epi['pk_episode']
555 ),
556 enc['assessment_of_encounter'] # or AOE
557 ),
558 enc['reason_for_encounter'] # or RFE
559 ),
560 enc['l10n_type'] # or type
561 ),
562 max_length = 40
563 )
564 )
565 encounter_node = self.AppendItem(episode_node, label)
566 self.SetItemData(encounter_node, enc)
567 self.SetItemHasChildren(encounter_node, True)
568
569 self.SortChildren(episode_node)
570
571 #--------------------------------------------------------
573 self.__cb__enable_display_mode_selection(True)
574 if self.__soap_display_mode == 'details':
575 txt = episode.format(left_margin = 1, patient = self.__pat)
576 font = self.__soap_display_prop_font
577 elif self.__soap_display_mode == 'journal':
578 txt = episode.format_as_journal(left_margin = 1)
579 font = self.__soap_display_prop_font
580 elif self.__soap_display_mode == 'revisions':
581 txt = episode.formatted_revision_history
582 font = self.__soap_display_mono_font
583 else:
584 txt = 'unknown SOAP display mode [%s]' % self.__soap_display_mode
585 font = self.__soap_display_prop_font
586 doc_folder = self.__pat.get_document_folder()
587 self.__img_display.refresh (
588 document_folder = doc_folder,
589 episodes = [ episode['pk_episode'] ]
590 )
591 self.__soap_display.SetFont(font)
592 self.__soap_display.WriteText(txt)
593 self.__soap_display.ShowPosition(0)
594
595 #--------------------------------------------------------
597 tt = ''
598 tt += gmTools.bool2subst (
599 (episode['diagnostic_certainty_classification'] is not None),
600 episode.diagnostic_certainty_description + '\n\n',
601 ''
602 )
603 tt += gmTools.bool2subst (
604 episode['episode_open'],
605 _('ongoing episode'),
606 _('closed episode'),
607 'error: episode state is None'
608 ) + '\n'
609 tt += gmTools.coalesce(episode['summary'], '', '\n%s')
610 if len(episode['pk_generic_codes']) > 0:
611 tt += '\n'
612 for code in episode.generic_codes:
613 tt += '%s: %s%s%s\n (%s %s)\n' % (
614 code['code'],
615 gmTools.u_left_double_angle_quote,
616 code['term'],
617 gmTools.u_right_double_angle_quote,
618 code['name_short'],
619 code['version']
620 )
621 return tt
622
623 #--------------------------------------------------------
624 # encounter level
625 #--------------------------------------------------------
627 encounter = self.GetItemData(self.__curr_node)
628 node_parent = self.GetItemParent(self.__curr_node)
629 episode = self.GetItemData(node_parent)
630
631 gmNarrativeWorkflows.move_progress_notes_to_another_encounter (
632 parent = self,
633 encounters = [encounter['pk_encounter']],
634 episodes = [episode['pk_episode']]
635 )
636
637 #--------------------------------------------------------
639 encounter = self.GetItemData(self.__curr_node)
640 node_parent = self.GetItemParent(self.__curr_node)
641 episode = self.GetItemData(node_parent)
642
643 gmNarrativeWorkflows.manage_progress_notes (
644 parent = self,
645 encounters = [encounter['pk_encounter']],
646 episodes = [episode['pk_episode']]
647 )
648
649 #--------------------------------------------------------
651 node_data = self.GetItemData(self.__curr_node)
652 gmEncounterWidgets.edit_encounter(parent = self, encounter = node_data)
653 self.__populate_tree()
654
655 #--------------------------------------------------------
657
658 node_parent = self.GetItemParent(self.__curr_node)
659 owning_episode = self.GetItemData(node_parent)
660
661 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
662 self,
663 -1,
664 episode = owning_episode,
665 encounter = self.__curr_node_data
666 )
667
668 result = episode_selector.ShowModal()
669 episode_selector.DestroyLater()
670
671 if result == wx.ID_YES:
672 self.__populate_tree()
673
674 #--------------------------------------------------------
676 self.DeleteChildren(encounter_node)
677 encounter = self.GetItemData(encounter_node)
678 encounter_items = self.__pat.emr.get_generic_emr_items (
679 pk_encounters = [encounter['pk_encounter']],
680 pk_episodes = [self.GetItemData(self.GetItemParent(encounter_node))['pk_episode']]
681 )
682 if len(encounter_items) == 0:
683 self.SetItemHasChildren(encounter_node, False)
684 return
685
686 for enc_item in encounter_items:
687 item_node = self.AppendItem(encounter_node, '%s [%s] %s' % (
688 enc_item['clin_when'].strftime('%H:%M'),
689 enc_item.i18n_soap_cat,
690 enc_item.item_type_str
691 ))
692 self.SetItemData(item_node, enc_item)
693 self.SetItemHasChildren(item_node, False)
694
695 # missing:
696 #self.SortChildren(encounter_node)
697
698 #--------------------------------------------------------
700 self.__cb__enable_display_mode_selection(True)
701 epi = self.GetItemData(self.GetItemParent(self.__curr_node))
702 if self.__soap_display_mode == 'revisions':
703 txt = encounter.formatted_revision_history
704 font = self.__soap_display_mono_font
705 else:
706 txt = encounter.format (
707 episodes = [epi['pk_episode']],
708 with_soap = True,
709 left_margin = 1,
710 patient = self.__pat,
711 with_co_encountlet_hints = True
712 )
713 font = self.__soap_display_prop_font
714 self.__soap_display.SetFont(font)
715 self.__soap_display.WriteText(txt)
716 self.__soap_display.ShowPosition(0)
717 self.__img_display.refresh (
718 document_folder = self.__pat.get_document_folder(),
719 episodes = [ epi['pk_episode'] ],
720 encounter = encounter['pk_encounter']
721 )
722
723 #--------------------------------------------------------
725 tt = '%s %s %s - %s\n' % (
726 gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
727 encounter['l10n_type'],
728 encounter['started'].strftime('%H:%M'),
729 encounter['last_affirmed'].strftime('%H:%M')
730 )
731 if encounter['reason_for_encounter'] is not None:
732 tt += '\n'
733 tt += _('RFE: %s') % encounter['reason_for_encounter']
734 if len(encounter['pk_generic_codes_rfe']) > 0:
735 for code in encounter.generic_codes_rfe:
736 tt += '\n %s: %s%s%s\n (%s %s)' % (
737 code['code'],
738 gmTools.u_left_double_angle_quote,
739 code['term'],
740 gmTools.u_right_double_angle_quote,
741 code['name_short'],
742 code['version']
743 )
744 if encounter['assessment_of_encounter'] is not None:
745 tt += '\n'
746 tt += _('AOE: %s') % encounter['assessment_of_encounter']
747 if len(encounter['pk_generic_codes_aoe']) > 0:
748 for code in encounter.generic_codes_aoe:
749 tt += '\n %s: %s%s%s\n (%s %s)' % (
750 code['code'],
751 gmTools.u_left_double_angle_quote,
752 code['term'],
753 gmTools.u_right_double_angle_quote,
754 code['name_short'],
755 code['version']
756 )
757 return tt
758
759 #--------------------------------------------------------
760 # generic EMR item level
761 #--------------------------------------------------------
763 self.__cb__enable_display_mode_selection(False)
764 txt = gmTools.list2text (
765 generic_item.format(),
766 strip_leading_empty_lines = False,
767 strip_trailing_empty_lines = False,
768 strip_trailing_whitespace = True,
769 max_line_width = 85
770 )
771 self.__soap_display.SetFont(self.__soap_display_prop_font)
772 self.__soap_display.WriteText(txt)
773 self.__soap_display.ShowPosition(0)
774
775 #--------------------------------------------------------
778
779 #--------------------------------------------------------
781 instance = self.__curr_node_data.specialized_item
782 if instance is None:
783 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit "%s".') % self.__curr_node_data.item_type_str, beep = True)
784 return False
785 gmGenericEMRItemWorkflows.edit_item_in_dlg(parent = self, item = instance)
786 return True
787
788 #--------------------------------------------------------
789 # issue level
790 #--------------------------------------------------------
793
794 #--------------------------------------------------------
796 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
797 parent = self,
798 id = -1,
799 caption = _('Deleting health issue'),
800 button_defs = [
801 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
802 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
803 ],
804 question = _(
805 'Are you sure you want to delete this health issue ?\n'
806 '\n'
807 ' "%s"\n'
808 ) % self.__curr_node_data['description']
809 )
810 result = dlg.ShowModal()
811 if result != wx.ID_YES:
812 dlg.DestroyLater()
813 return
814
815 dlg.DestroyLater()
816
817 if not gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data):
818 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
819
820 #--------------------------------------------------------
822
823 if not self.__curr_node.IsOk():
824 return
825
826 self.Expand(self.__curr_node)
827
828 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
829 while epi.IsOk():
830 self.Expand(epi)
831 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
832
833 #--------------------------------------------------------
835 self.DeleteChildren(issue_node)
836
837 issue = self.GetItemData(issue_node)
838 episodes = self.__pat.emr.get_episodes(issues = [issue['pk_health_issue']])
839 if len(episodes) == 0:
840 self.SetItemHasChildren(issue_node, False)
841 return
842
843 self.SetItemHasChildren(issue_node, True)
844
845 for episode in episodes:
846 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
847 episode_node = self.AppendItem(issue_node, '%s (%s)' % (
848 episode['description'],
849 range_str
850 ))
851 self.SetItemData(episode_node, episode)
852 # assume children so we can try to expand it
853 self.SetItemHasChildren(episode_node, True)
854
855 self.SortChildren(issue_node)
856
857 #--------------------------------------------------------
859 self.__cb__enable_display_mode_selection(True)
860 if self.__soap_display_mode == 'details':
861 txt = issue.format(left_margin = 1, patient = self.__pat)
862 font = self.__soap_display_prop_font
863 elif self.__soap_display_mode == 'journal':
864 txt = issue.format_as_journal(left_margin = 1)
865 font = self.__soap_display_prop_font
866 elif self.__soap_display_mode == 'revisions':
867 txt = issue.formatted_revision_history
868 font = self.__soap_display_mono_font
869 else:
870 txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
871 font = self.__soap_display_prop_font
872 epis = issue.episodes
873 if len(epis) > 0:
874 doc_folder = self.__pat.get_document_folder()
875 self.__img_display.refresh (
876 document_folder = doc_folder,
877 episodes = [ epi['pk_episode'] for epi in epis ],
878 do_async = True
879 )
880 self.__soap_display.SetFont(font)
881 self.__soap_display.WriteText(txt)
882 self.__soap_display.ShowPosition(0)
883
884 #--------------------------------------------------------
886 tt = ''
887 tt += gmTools.bool2subst(issue['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), '')
888 tt += gmTools.bool2subst (
889 (issue['diagnostic_certainty_classification'] is not None),
890 issue.diagnostic_certainty_description + '\n',
891 ''
892 )
893 tt += gmTools.bool2subst (
894 (issue['laterality'] not in [None, 'na']),
895 issue.laterality_description + '\n',
896 ''
897 )
898 # noted_at_age is too costly
899 tt += gmTools.bool2subst(issue['is_active'], _('active') + '\n', '')
900 tt += gmTools.bool2subst(issue['clinically_relevant'], _('clinically relevant') + '\n', '')
901 tt += gmTools.bool2subst(issue['is_cause_of_death'], _('contributed to death') + '\n', '')
902 tt += gmTools.coalesce(issue['grouping'], '\n', _('Grouping: %s') + '\n')
903 tt += gmTools.coalesce(issue['summary'], '', '\n%s')
904 if len(issue['pk_generic_codes']) > 0:
905 tt += '\n'
906 for code in issue.generic_codes:
907 tt += '%s: %s%s%s\n (%s %s)\n' % (
908 code['code'],
909 gmTools.u_left_double_angle_quote,
910 code['term'],
911 gmTools.u_right_double_angle_quote,
912 code['name_short'],
913 code['version']
914 )
915 return tt
916
917 #--------------------------------------------------------
919 self.DeleteChildren(fake_issue_node)
920
921 episodes = self.__pat.emr.unlinked_episodes
922 if len(episodes) == 0:
923 self.SetItemHasChildren(fake_issue_node, False)
924 return
925
926 self.SetItemHasChildren(fake_issue_node, True)
927
928 for episode in episodes:
929 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
930 episode_node = self.AppendItem(fake_issue_node, '%s (%s)' % (
931 episode['description'],
932 range_str
933 ))
934 self.SetItemData(episode_node, episode)
935 if episode['episode_open']:
936 self.SetItemBold(fake_issue_node, True)
937 # assume children so we can try to expand it
938 self.SetItemHasChildren(episode_node, True)
939
940 self.SortChildren(fake_issue_node)
941
942 #--------------------------------------------------------
944 self.__cb__enable_display_mode_selection(True)
945 if self.__soap_display_mode == 'details':
946 txt = _('Pool of unassociated episodes "%s":\n') % pseudo_issue['description']
947 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
948 if len(epis) > 0:
949 txt += '\n'
950 for epi in epis:
951 txt += epi.format (
952 left_margin = 1,
953 patient = self.__pat,
954 with_summary = True,
955 with_codes = False,
956 with_encounters = False,
957 with_documents = False,
958 with_hospital_stays = False,
959 with_procedures = False,
960 with_family_history = False,
961 with_tests = False,
962 with_vaccinations = False,
963 with_health_issue = False
964 )
965 txt += '\n'
966 else:
967 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
968 txt = ''
969 if len(epis) > 0:
970 txt += _(' Listing of unassociated episodes\n')
971 for epi in epis:
972 txt += ' %s\n' % (gmTools.u_box_horiz_4dashes * 60)
973 txt += epi.format (
974 left_margin = 1,
975 patient = self.__pat,
976 with_summary = False,
977 with_codes = False,
978 with_encounters = False,
979 with_documents = False,
980 with_hospital_stays = False,
981 with_procedures = False,
982 with_family_history = False,
983 with_tests = False,
984 with_vaccinations = False,
985 with_health_issue = False
986 )
987 txt += '\n'
988 txt += epi.format_as_journal(left_margin = 2)
989 self.__soap_display.SetFont(self.__soap_display_prop_font)
990 self.__soap_display.WriteText(txt)
991 self.__soap_display.ShowPosition(0)
992
993 #--------------------------------------------------------
994 # EMR level
995 #--------------------------------------------------------
998
999 #--------------------------------------------------------
1002
1003 #--------------------------------------------------------
1006
1007 #--------------------------------------------------------
1009 self.__cb__select_edit_mode(True)
1010 self.__cb__add_soap_editor(problem = self.__curr_node_data, allow_same_problem = False)
1011
1012 #--------------------------------------------------------
1014 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
1015 # FIXME: use signal and use node level update
1016 if dlg.ShowModal() == wx.ID_OK:
1017 self.__expanded_nodes = self.ExpansionState
1018 self.__populate_tree()
1019 dlg.DestroyLater()
1020 return
1021
1022 #--------------------------------------------------------
1025
1026 #--------------------------------------------------------
1029
1030 #--------------------------------------------------------
1033
1034 #--------------------------------------------------------
1037
1038 #--------------------------------------------------------
1041
1042 #--------------------------------------------------------
1044
1045 root_item = self.GetRootItem()
1046
1047 if not root_item.IsOk():
1048 return
1049
1050 self.Expand(root_item)
1051
1052 # collapse episodes and issues
1053 issue, issue_cookie = self.GetFirstChild(root_item)
1054 while issue.IsOk():
1055 self.Collapse(issue)
1056 epi, epi_cookie = self.GetFirstChild(issue)
1057 while epi.IsOk():
1058 self.Collapse(epi)
1059 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1060 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1061
1062 #--------------------------------------------------------
1064
1065 root_item = self.GetRootItem()
1066
1067 if not root_item.IsOk():
1068 return
1069
1070 self.Expand(root_item)
1071
1072 # collapse episodes, expand issues
1073 issue, issue_cookie = self.GetFirstChild(root_item)
1074 while issue.IsOk():
1075 self.Expand(issue)
1076 epi, epi_cookie = self.GetFirstChild(issue)
1077 while epi.IsOk():
1078 self.Collapse(epi)
1079 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1080 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1081
1082 #--------------------------------------------------------
1084
1085 root_item = self.GetRootItem()
1086
1087 if not root_item.IsOk():
1088 return
1089
1090 self.Expand(root_item)
1091
1092 # collapse episodes, expand issues
1093 issue, issue_cookie = self.GetFirstChild(root_item)
1094 while issue.IsOk():
1095 self.Expand(issue)
1096 epi, epi_cookie = self.GetFirstChild(issue)
1097 while epi.IsOk():
1098 self.Expand(epi)
1099 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1100 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1101
1102 #--------------------------------------------------------
1104 gmNarrativeWorkflows.export_narrative_for_medistar_import (
1105 parent = self,
1106 soap_cats = 'soapu',
1107 encounter = self.__curr_node_data
1108 )
1109
1110 #--------------------------------------------------------
1112 self.__curr_node_data = self.GetItemData(self.__curr_node)
1113 if isinstance(self.__curr_node_data, gmGenericEMRItem.cGenericEMRItem):
1114 self.__edit_generic_emr_item()
1115 return
1116
1117 #--------------------------------------------------------
1118 # root node level
1119 #--------------------------------------------------------
1121 root_node = self.GetRootItem()
1122 self.DeleteChildren(root_node)
1123
1124 issues = [{
1125 'description': _('Unattributed episodes'),
1126 'laterality': None,
1127 'diagnostic_certainty_classification': None,
1128 'has_open_episode': False,
1129 'pk_health_issue': None
1130 }]
1131 issues.extend(self.__pat.emr.health_issues)
1132 for issue in issues:
1133 issue_node = self.AppendItem(root_node, '%s%s%s' % (
1134 issue['description'],
1135 gmTools.coalesce(issue['laterality'], '', ' [%s]', none_equivalents = [None, 'na']),
1136 gmTools.coalesce(issue['diagnostic_certainty_classification'], '', ' [%s]')
1137 ))
1138 self.SetItemBold(issue_node, issue['has_open_episode'])
1139 self.SetItemData(issue_node, issue)
1140 # fake it so we can expand it
1141 self.SetItemHasChildren(issue_node, True)
1142
1143 self.SetItemHasChildren(root_node, (len(issues) != 0))
1144 self.SortChildren(root_node)
1145
1146 #--------------------------------------------------------
1148 self.__cb__enable_display_mode_selection(True)
1149 if self.__soap_display_mode == 'details':
1150 emr = self.__pat.emr
1151 txt = emr.format_summary()
1152 else:
1153 txt = self.__pat.emr.format_as_journal(left_margin = 1, patient = self.__pat)
1154 self.__soap_display.SetFont(self.__soap_display_prop_font)
1155 self.__soap_display.WriteText(txt)
1156 self.__soap_display.ShowPosition(0)
1157
1158 #--------------------------------------------------------
1159 # event handlers
1160 #--------------------------------------------------------
1163
1164 #--------------------------------------------------------
1168
1169 #--------------------------------------------------------
1173
1174 #--------------------------------------------------------
1176 event.Skip()
1177
1178 node = event.GetItem()
1179 if node == self.GetRootItem():
1180 self.__expand_root_node()
1181 return
1182
1183 node_data = self.GetItemData(node)
1184
1185 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
1186 self.__expand_issue_node(issue_node = node)
1187 return
1188
1189 if isinstance(node_data, gmEMRStructItems.cEpisode):
1190 self.__expand_episode_node(episode_node = node)
1191 return
1192
1193 # pseudo node "free-standing episodes"
1194 if type(node_data) == type({}):
1195 self.__expand_pseudo_issue_node(fake_issue_node = node)
1196 return
1197
1198 if isinstance(node_data, gmEMRStructItems.cEncounter):
1199 self.__expand_encounter_node(encounter_node = node)
1200 return
1201
1202 #--------------------------------------------------------
1207
1208 #--------------------------------------------------------
1210 sel_item = event.GetItem()
1211 self.__curr_node = sel_item
1212 self.__update_text_for_selected_node()
1213 return True
1214
1215 # #--------------------------------------------------------
1216 # def _on_mouse_motion(self, event):
1217 #
1218 # cursor_pos = (event.GetX(), event.GetY())
1219 #
1220 # self.SetToolTip(u'')
1221 #
1222 # if cursor_pos != self._old_cursor_pos:
1223 # self._old_cursor_pos = cursor_pos
1224 # (item, flags) = self.HitTest(cursor_pos)
1225 # #if flags != wx.TREE_HITTEST_NOWHERE:
1226 # if flags == wx.TREE_HITTEST_ONITEMLABEL:
1227 # data = self.GetItemData(item)
1228 #
1229 # if not isinstance(data, gmEMRStructItems.cEncounter):
1230 # return
1231 #
1232 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % (
1233 # gmDateTime.pydt_strftime(data['started'], '%Y %b %d'),
1234 # data['l10n_type'],
1235 # data['started'].strftime('%H:%m'),
1236 # data['last_affirmed'].strftime('%H:%m'),
1237 # gmTools.coalesce(data['reason_for_encounter'], u''),
1238 # gmTools.coalesce(data['assessment_of_encounter'], u'')
1239 # ))
1240 #--------------------------------------------------------
1242
1243 item = event.GetItem()
1244
1245 if not item.IsOk():
1246 event.SetToolTip(' ')
1247 return
1248
1249 data = self.GetItemData(item)
1250 if isinstance(data, gmEMRStructItems.cEncounter):
1251 tt = self.__calc_encounter_tooltip(data)
1252 elif isinstance(data, gmEMRStructItems.cEpisode):
1253 tt = self.__calc_episode_tooltip(data)
1254 elif isinstance(data, gmEMRStructItems.cHealthIssue):
1255 tt = self.__calc_issue_tooltip(data)
1256 else:
1257 tt = self.__root_tooltip
1258 tt = tt.strip('\n')
1259 if tt == '':
1260 tt = ' '
1261 event.SetToolTip(tt)
1262
1263 # doing this prevents the tooltip from showing at all
1264 #event.Skip()
1265
1266 #widgetXY.GetToolTip().Enable(False)
1267 #
1268 #seems to work, supposing the tooltip is actually set for the widget,
1269 #otherwise a test would be needed
1270 #if widgetXY.GetToolTip():
1271 # widgetXY.GetToolTip().Enable(False)
1272
1273 #--------------------------------------------------------
1279
1280 #--------------------------------------------------------
1282 """Used in sorting items.
1283
1284 -1: 1 < 2
1285 0: 1 = 2
1286 1: 1 > 2
1287 """
1288 # FIXME: implement sort modes, chron, reverse cron, by regex, etc
1289
1290 if not node1:
1291 _log.debug('invalid node 1')
1292 return 0
1293 if not node2:
1294 _log.debug('invalid node 2')
1295 return 0
1296
1297 if not node1.IsOk():
1298 _log.debug('invalid node 1')
1299 return 0
1300 if not node2.IsOk():
1301 _log.debug('invalid node 2')
1302 return 0
1303
1304 item1 = self.GetItemData(node1)
1305 item2 = self.GetItemData(node2)
1306
1307 # dummy health issue always on top
1308 if isinstance(item1, type({})):
1309 return -1
1310 if isinstance(item2, type({})):
1311 return 1
1312
1313 # encounters: reverse chronologically
1314 if isinstance(item1, gmEMRStructItems.cEncounter):
1315 if item1['started'] == item2['started']:
1316 return 0
1317 if item1['started'] > item2['started']:
1318 return -1
1319 return 1
1320
1321 # episodes: open, then reverse chronologically
1322 if isinstance(item1, gmEMRStructItems.cEpisode):
1323 # open episodes first
1324 if item1['episode_open']:
1325 return -1
1326 if item2['episode_open']:
1327 return 1
1328 start1 = item1.best_guess_clinical_start_date
1329 start2 = item2.best_guess_clinical_start_date
1330 if start1 == start2:
1331 return 0
1332 if start1 < start2:
1333 return 1
1334 return -1
1335
1336 # issues: alpha by grouping, no grouping at the bottom
1337 if isinstance(item1, gmEMRStructItems.cHealthIssue):
1338
1339 # no grouping below grouping
1340 if item1['grouping'] is None:
1341 if item2['grouping'] is not None:
1342 return 1
1343
1344 # grouping above no grouping
1345 if item1['grouping'] is not None:
1346 if item2['grouping'] is None:
1347 return -1
1348
1349 # both no grouping: alpha on description
1350 if (item1['grouping'] is None) and (item2['grouping'] is None):
1351 if item1['description'].lower() < item2['description'].lower():
1352 return -1
1353 if item1['description'].lower() > item2['description'].lower():
1354 return 1
1355 return 0
1356
1357 # both with grouping: alpha on grouping, then alpha on description
1358 if item1['grouping'] < item2['grouping']:
1359 return -1
1360
1361 if item1['grouping'] > item2['grouping']:
1362 return 1
1363
1364 if item1['description'].lower() < item2['description'].lower():
1365 return -1
1366
1367 if item1['description'].lower() > item2['description'].lower():
1368 return 1
1369
1370 return 0
1371
1372 _log.error('unknown item type during sorting EMR tree:')
1373 _log.error('item1: %s', type(item1))
1374 _log.error('item2: %s', type(item2))
1375
1376 return 0
1377
1378 #--------------------------------------------------------
1379 # properties
1380 #--------------------------------------------------------
1383
1385 if self.__pat == patient:
1386 return
1387 self.__pat = patient
1388 if patient is None:
1389 self.clear_tree()
1390 return
1391 return self.__populate_tree()
1392
1393 patient = property(_get_patient, _set_patient)
1394
1395 #--------------------------------------------------------
1398
1400 if mode not in ['details', 'journal', 'revisions']:
1401 raise ValueError('details display mode must be one of "details", "journal", "revisions"')
1402 if self.__soap_display_mode == mode:
1403 return
1404 self.__soap_display_mode = mode
1405 self.__update_text_for_selected_node()
1406
1407 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1408
1409 #================================================================
1410 # FIXME: still needed ?
1411 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
1412
1414 """A scrollable panel holding an EMR tree.
1415
1416 Lacks a widget to display details for selected items. The
1417 tree data will be refetched - if necessary - whenever
1418 repopulate_ui() is called, e.g., when the patient is changed.
1419 """
1422
1423 #============================================================
1424 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
1425
1427 """A splitter window holding an EMR tree.
1428
1429 The left hand side displays a scrollable EMR tree while
1430 on the right details for selected items are displayed.
1431
1432 Expects to be put into a Notebook.
1433 """
1435 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds)
1436 self._pnl_emr_tree._emr_tree.soap_display = self._TCTRL_item_details
1437 self._pnl_emr_tree._emr_tree.image_display = self._PNL_visual_soap
1438 self._pnl_emr_tree._emr_tree.set_enable_display_mode_selection_callback(self.enable_display_mode_selection)
1439 self._pnl_emr_tree._emr_tree.soap_editor_adder = self._add_soap_editor
1440 self._pnl_emr_tree._emr_tree.edit_mode_selector = self._select_edit_mode
1441 self.__register_events()
1442
1443 self.editing = False
1444 #--------------------------------------------------------
1446 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1447 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1448 return True
1449
1450 #--------------------------------------------------------
1453
1455 self.__editing = editing
1456 self.enable_display_mode_selection(enable = not self.__editing)
1457 if self.__editing:
1458 self._BTN_switch_browse_edit.SetLabel(_('&Browse %s') % gmTools.u_ellipsis)
1459 self._PNL_browse.Hide()
1460 self._PNL_visual_soap.Hide()
1461 self._PNL_edit.Show()
1462 else:
1463 self._BTN_switch_browse_edit.SetLabel(_('&New notes %s') % gmTools.u_ellipsis)
1464 self._PNL_edit.Hide()
1465 self._PNL_visual_soap.Show()
1466 self._PNL_browse.Show()
1467 self._PNL_right_side.GetSizer().Layout()
1468
1469 editing = property(_get_editing, _set_editing)
1470
1471 #--------------------------------------------------------
1472 # event handler
1473 #--------------------------------------------------------
1475 self._pnl_emr_tree._emr_tree.patient = None
1476 self._PNL_edit.patient = None
1477 return True
1478
1479 #--------------------------------------------------------
1481 if self.GetParent().GetCurrentPage() != self:
1482 return True
1483 self.repopulate_ui()
1484 return True
1485
1486 #--------------------------------------------------------
1488 self._pnl_emr_tree._emr_tree.details_display_mode = 'details'
1489
1490 #--------------------------------------------------------
1492 self._pnl_emr_tree._emr_tree.details_display_mode = 'journal'
1493
1494 #--------------------------------------------------------
1496 self._pnl_emr_tree._emr_tree.details_display_mode = 'revisions'
1497
1498 #--------------------------------------------------------
1501
1502 #--------------------------------------------------------
1503 # external API
1504 #--------------------------------------------------------
1506 """Fills UI with data."""
1507 pat = gmPerson.gmCurrentPatient()
1508 self._pnl_emr_tree._emr_tree.patient = pat
1509 self._PNL_edit.patient = pat
1510 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSize()[0] // 3, True)
1511
1512 return True
1513
1514 #--------------------------------------------------------
1516 if self.editing:
1517 enable = False
1518 if enable:
1519 self._RBTN_details.Enable(True)
1520 self._RBTN_journal.Enable(True)
1521 self._RBTN_revisions.Enable(True)
1522 return
1523 self._RBTN_details.Enable(False)
1524 self._RBTN_journal.Enable(False)
1525 self._RBTN_revisions.Enable(False)
1526
1527 #--------------------------------------------------------
1529 self._PNL_edit._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1530
1531 #--------------------------------------------------------
1534
1535 #================================================================
1536 from Gnumed.wxGladeWidgets import wxgEMRJournalPluginPnl
1537
1539
1541
1542 wxgEMRJournalPluginPnl.wxgEMRJournalPluginPnl.__init__(self, *args, **kwds)
1543 self._TCTRL_journal.disable_keyword_expansions()
1544 self._TCTRL_journal.SetValue('')
1545
1546 #--------------------------------------------------------
1547 # external API
1548 #--------------------------------------------------------
1550 self._TCTRL_journal.SetValue('')
1551 exporter = gmPatientExporter.cEMRJournalExporter()
1552 if self._RBTN_by_encounter.GetValue():
1553 fname = exporter.save_to_file_by_encounter(patient = gmPerson.gmCurrentPatient())
1554 else:
1555 fname = exporter.save_to_file_by_mod_time(patient = gmPerson.gmCurrentPatient())
1556
1557 f = io.open(fname, mode = 'rt', encoding = 'utf8', errors = 'replace')
1558 for line in f:
1559 self._TCTRL_journal.AppendText(line)
1560 f.close()
1561
1562 self._TCTRL_journal.ShowPosition(self._TCTRL_journal.GetLastPosition())
1563 return True
1564 #--------------------------------------------------------
1565 # internal helpers
1566 #--------------------------------------------------------
1568 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1569 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1570 return True
1571
1572 #--------------------------------------------------------
1573 # event handlers
1574 #--------------------------------------------------------
1578
1579 #--------------------------------------------------------
1581 if self.GetParent().GetCurrentPage() != self:
1582 return True
1583 self.repopulate_ui()
1584 return True
1585
1586 #--------------------------------------------------------
1588 self.repopulate_ui()
1589
1590 #--------------------------------------------------------
1592 self.repopulate_ui()
1593
1594 #--------------------------------------------------------
1597
1598 #================================================================
1599 from Gnumed.wxGladeWidgets import wxgEMRListJournalPluginPnl
1600
1602
1604
1605 wxgEMRListJournalPluginPnl.wxgEMRListJournalPluginPnl.__init__(self, *args, **kwds)
1606
1607 self._LCTRL_journal.select_callback = self._on_row_selected
1608 self._LCTRL_journal.activate_callback = self._on_row_activated
1609 self._TCTRL_details.SetValue('')
1610
1611 self.__load_timer = gmTimer.cTimer(callback = self._on_load_details, delay = 1000, cookie = 'EMRListJournalPluginDBLoadTimer')
1612
1613 self.__data = {}
1614
1615 #--------------------------------------------------------
1616 # external API
1617 #--------------------------------------------------------
1619 self._LCTRL_journal.remove_items_safely()
1620 self._TCTRL_details.SetValue('')
1621
1622 # <pk_episode NULLS FIRST> ensures that health issues get sorted before their episodes
1623 if self._RBTN_by_encounter.Value:
1624 order_by = 'encounter_started, pk_health_issue, pk_episode NULLS FIRST, scr, src_table, modified_when'
1625 date_col_header = _('Encounter')
1626 date_fields = ['encounter_started', 'modified_when']
1627 elif self._RBTN_by_last_modified.Value:
1628 order_by = 'modified_when, pk_health_issue, pk_episode NULLS FIRST, src_table, scr'
1629 date_col_header = _('Modified')
1630 date_fields = ['modified_when']
1631 elif self._RBTN_by_item_time.Value:
1632 order_by = 'clin_when, pk_health_issue, pk_episode NULLS FIRST, scr, src_table, modified_when'
1633 date_col_header = _('Happened')
1634 date_fields = ['clin_when', 'modified_when']
1635 else:
1636 raise ValueError('invalid EMR journal list sort state')
1637
1638 self._LCTRL_journal.set_columns([date_col_header, '', _('Entry'), _('Who / When')])
1639 self._LCTRL_journal.set_resize_column(3)
1640
1641 # journal = gmPerson.gmCurrentPatient().emr.get_as_journal(order_by = order_by)
1642 journal = gmPerson.gmCurrentPatient().emr.get_generic_emr_items (
1643 pk_encounters = None,
1644 pk_episodes = None,
1645 pk_health_issues = None,
1646 use_active_encounter = True,
1647 order_by = order_by
1648 )
1649
1650 # self.__data = {}
1651 items = []
1652 data = []
1653 prev_date = None
1654 for entry in journal:
1655 if entry['narrative'].strip() == '':
1656 continue
1657 # self.__register_journal_entry(entry)
1658 soap_cat = gmSoapDefs.soap_cat2l10n[entry['soap_cat']]
1659 who = '%s (%s)' % (entry['modified_by'], entry['date_modified'])
1660 try:
1661 entry_date = gmDateTime.pydt_strftime(entry[date_fields[0]], '%Y-%m-%d')
1662 except KeyError:
1663 entry_date = gmDateTime.pydt_strftime(entry[date_fields[1]], '%Y-%m-%d')
1664 if entry_date == prev_date:
1665 date2show = ''
1666 else:
1667 date2show = entry_date
1668 prev_date = entry_date
1669 lines_of_journal_entry = entry['narrative'].strip().split('\n')
1670 first_line = lines_of_journal_entry[0]
1671 items.append([date2show, soap_cat, first_line.rstrip(), who])
1672 # data.append ({
1673 # 'table': entry['src_table'],
1674 # 'pk': entry['src_pk']
1675 # })
1676 data.append(entry)
1677 for line in lines_of_journal_entry[1:]: # skip first line
1678 if line.strip() == '':
1679 continue
1680 # only first line carries metadata
1681 items.append(['', '', line.rstrip(), ''])
1682 # data.append ({
1683 # 'table': entry['src_table'],
1684 # 'pk': entry['src_pk']
1685 # })
1686 data.append(entry)
1687
1688 self._LCTRL_journal.set_string_items(items)
1689 # maybe add coloring per-entry ?
1690 #for item_idx in range(self._LCTRL_journal.ItemCount):
1691 # self._LCTRL_journal.SetItemBackgroundColour(item_idx, 'green')
1692 self._LCTRL_journal.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER])
1693 self._LCTRL_journal.set_data(data)
1694
1695 self._LCTRL_journal.SetFocus()
1696 return True
1697
1698 #--------------------------------------------------------
1699 # internal helpers
1700 #--------------------------------------------------------
1702 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1703 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1704 return True
1705
1706 #--------------------------------------------------------
1708 if entry['src_table'] in self.__data:
1709 if entry['src_pk'] in self.__data[entry['src_table']]:
1710 return
1711
1712 else:
1713 self.__data[entry['src_table']] = {}
1714
1715 self.__data[entry['src_table']][entry['src_pk']] = {}
1716 self.__data[entry['src_table']][entry['src_pk']]['entry'] = entry
1717 self.__data[entry['src_table']][entry['src_pk']]['formatted_instance'] = None
1718 if entry['encounter_started'] is None:
1719 enc_duration = gmTools.u_diameter
1720 else:
1721 enc_duration = '%s - %s' % (
1722 gmDateTime.pydt_strftime(entry['encounter_started'], '%Y %b %d %H:%M'),
1723 gmDateTime.pydt_strftime(entry['encounter_last_affirmed'], '%H:%M')
1724 )
1725 self.__data[entry['src_table']][entry['src_pk']]['formatted_header'] = _(
1726 'Chart entry: %s [#%s in %s]\n'
1727 ' Modified: %s by %s (%s rev %s)\n'
1728 '\n'
1729 'Health issue: %s%s\n'
1730 'Episode: %s%s\n'
1731 'Encounter: %s%s'
1732 ) % (
1733 gmGenericEMRItem.generic_item_type_str(entry['src_table']),
1734 entry['src_pk'],
1735 entry['src_table'],
1736 entry['date_modified'],
1737 entry['modified_by'],
1738 gmTools.u_arrow2right,
1739 entry['row_version'],
1740 gmTools.coalesce(entry['health_issue'], gmTools.u_diameter, '%s'),
1741 gmTools.bool2subst(entry['issue_active'], ' (' + _('active') + ')', ' (' + _('inactive') + ')', ''),
1742 gmTools.coalesce(entry['episode'], gmTools.u_diameter, '%s'),
1743 gmTools.bool2subst(entry['episode_open'], ' (' + _('open') + ')', ' (' + _('closed') + ')', ''),
1744 enc_duration,
1745 gmTools.coalesce(entry['encounter_l10n_type'], '', ' (%s)'),
1746 )
1747 self.__data[entry['src_table']][entry['src_pk']]['formatted_root_item'] = _(
1748 '%s\n'
1749 '\n'
1750 ' rev %s (%s) by %s in <%s>'
1751 ) % (
1752 entry['narrative'].strip(),
1753 entry['row_version'],
1754 entry['date_modified'],
1755 entry['modified_by'],
1756 entry['src_table']
1757 )
1758
1759 #--------------------------------------------------------
1760 # event handlers
1761 #--------------------------------------------------------
1763 self._LCTRL_journal.remove_items_safely()
1764 self._TCTRL_details.SetValue('')
1765 self.__data = {}
1766 return True
1767
1768 #--------------------------------------------------------
1770 if self.GetParent().GetCurrentPage() != self:
1771 return True
1772 self.repopulate_ui()
1773 return True
1774
1775 #--------------------------------------------------------
1777 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1778 instance = data.specialized_item
1779 if instance is None:
1780 return
1781 if gmGenericEMRItemWorkflows.edit_item_in_dlg(parent = self, item = instance):
1782 self.repopulate_ui()
1783
1784 #--------------------------------------------------------
1786 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1787 self._TCTRL_details.SetValue(data.format(eol = '\n'))
1788 # FIXME: fire off get-details
1789 return
1790
1791 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1792 if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
1793 txt = _(
1794 '%s\n'
1795 '%s\n'
1796 '%s'
1797 ) % (
1798 self.__data[data['table']][data['pk']]['formatted_header'],
1799 gmTools.u_box_horiz_4dashes * 40,
1800 self.__data[data['table']][data['pk']]['formatted_root_item']
1801 )
1802 self._TCTRL_details.SetValue(txt)
1803 self.__load_timer.Stop()
1804 self.__load_timer.Start(oneShot = True)
1805 return
1806
1807 txt = _(
1808 '%s\n'
1809 '%s\n'
1810 '%s'
1811 ) % (
1812 self.__data[data['table']][data['pk']]['formatted_header'],
1813 gmTools.u_box_horiz_4dashes * 40,
1814 self.__data[data['table']][data['pk']]['formatted_instance']
1815 )
1816 self._TCTRL_details.SetValue(txt)
1817
1818 #--------------------------------------------------------
1820 data = self._LCTRL_journal.get_selected_item_data(only_one = True)
1821 if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
1822 self.__data[data['table']][data['pk']]['formatted_instance'] = gmClinicalRecord.format_clin_root_item(data['table'], data['pk'], patient = gmPerson.gmCurrentPatient())
1823 txt = _(
1824 '%s\n'
1825 '%s\n'
1826 '%s'
1827 ) % (
1828 self.__data[data['table']][data['pk']]['formatted_header'],
1829 gmTools.u_box_horiz_4dashes * 40,
1830 self.__data[data['table']][data['pk']]['formatted_instance']
1831 )
1832 wx.CallAfter(self._TCTRL_details.SetValue, txt)
1833
1834 #--------------------------------------------------------
1836 self.repopulate_ui()
1837
1838 #--------------------------------------------------------
1840 self.repopulate_ui()
1841
1842 #--------------------------------------------------------
1844 self.repopulate_ui()
1845
1846 #--------------------------------------------------------
1850
1851 #--------------------------------------------------------
1854
1855 #--------------------------------------------------------
1858
1859 #--------------------------------------------------------
1860 # def _on_button_find_pressed(self, event):
1861 # self._TCTRL_details.show_find_dialog(title = _('Find text in EMR Journal'))
1862
1863 #================================================================
1864 # MAIN
1865 #----------------------------------------------------------------
1866 if __name__ == '__main__':
1867
1868 _log.info("starting emr browser...")
1869
1870 # obtain patient
1871 patient = gmPersonSearch.ask_for_patient()
1872 if patient is None:
1873 print("No patient. Exiting gracefully...")
1874 sys.exit(0)
1875 gmPatSearchWidgets.set_active_patient(patient = patient)
1876
1877 # display standalone browser
1878 application = wx.PyWidgetTester(size=(800,600))
1879 emr_browser = cEMRBrowserPanel(application.frame, -1)
1880 emr_browser.refresh_tree()
1881
1882 application.frame.Show(True)
1883 application.MainLoop()
1884
1885 # clean up
1886 if patient is not None:
1887 try:
1888 patient.cleanup()
1889 except Exception:
1890 print("error cleaning up patient")
1891
1892 _log.info("closing emr browser...")
1893
1894 #================================================================
1895
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |