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