| Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed patient EMR tree browser.
2 """
3 #================================================================
4 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRBrowser.py,v $
5 # $Id: gmEMRBrowser.py,v 1.111 2010/01/11 19:44:39 ncq Exp $
6 __version__ = "$Revision: 1.111 $"
7 __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net"
8 __license__ = "GPL"
9
10 # std lib
11 import sys, types, os.path, StringIO, codecs, logging
12
13 # 3rd party
14 import wx
15
16 # GNUmed libs
17 from Gnumed.pycommon import gmI18N, gmDispatcher, gmExceptions, gmTools
18 from Gnumed.exporters import gmPatientExporter
19 from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter
20 from Gnumed.wxpython import gmGuiHelpers, gmEMRStructWidgets, gmSOAPWidgets, gmAllergyWidgets, gmNarrativeWidgets, gmPatSearchWidgets
21 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl, wxgSplittedEMRTreeBrowserPnl
22
23 _log = logging.getLogger('gm.ui')
24 _log.info(__version__)
25
26 #============================================================
28 """
29 Dump the patient's EMR from GUI client
30 @param parent - The parent widget
31 @type parent - A wx.Window instance
32 """
33 # sanity checks
34 if parent is None:
35 raise TypeError('expected wx.Window instance as parent, got <None>')
36
37 pat = gmPerson.gmCurrentPatient()
38 if not pat.connected:
39 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
40 return False
41
42 # get file name
43 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
44 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname'])))
45 gmTools.mkdir(defdir)
46 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
47 dlg = wx.FileDialog (
48 parent = parent,
49 message = _("Save patient's EMR as..."),
50 defaultDir = defdir,
51 defaultFile = fname,
52 wildcard = wc,
53 style = wx.SAVE
54 )
55 choice = dlg.ShowModal()
56 fname = dlg.GetPath()
57 dlg.Destroy()
58 if choice != wx.ID_OK:
59 return None
60
61 _log.debug('exporting EMR to [%s]', fname)
62
63 # output_file = open(fname, 'wb')
64 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace')
65 exporter = gmPatientExporter.cEmrExport(patient = pat)
66 exporter.set_output_file(output_file)
67 exporter.dump_constraints()
68 exporter.dump_demographic_record(True)
69 exporter.dump_clinical_record()
70 exporter.dump_med_docs()
71 output_file.close()
72
73 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
74 return fname
75 #============================================================
77 """This wx.TreeCtrl derivative displays a tree view of the medical record."""
78
79 #--------------------------------------------------------
81 """Set up our specialised tree.
82 """
83 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER
84 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
85
86 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self)
87
88 try:
89 self.__narr_display = kwds['narr_display']
90 del kwds['narr_display']
91 except KeyError:
92 self.__narr_display = None
93 self.__pat = gmPerson.gmCurrentPatient()
94 self.__curr_node = None
95 self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat)
96
97 self._old_cursor_pos = None
98
99 self.__make_popup_menus()
100 self.__register_events()
101 #--------------------------------------------------------
102 # external API
103 #--------------------------------------------------------
105 if not self.__pat.connected:
106 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),)
107 return False
108
109 if not self.__populate_tree():
110 return False
111
112 return True
113 #--------------------------------------------------------
116 #--------------------------------------------------------
117 # internal helpers
118 #--------------------------------------------------------
120 """Configures enabled event signals."""
121 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected)
122 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked)
123
124 # handle tooltips
125 # wx.EVT_MOTION(self, self._on_mouse_motion)
126 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip)
127
128 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
129 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db)
130 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
131 #--------------------------------------------------------
133 """Updates EMR browser data."""
134 # FIXME: auto select the previously self.__curr_node if not None
135 # FIXME: error handling
136
137 wx.BeginBusyCursor()
138
139 self.snapshot_expansion()
140
141 # init new tree
142 self.DeleteAllItems()
143 root_item = self.AddRoot(_('EMR of %s') % self.__pat['description'])
144 self.SetPyData(root_item, None)
145 self.SetItemHasChildren(root_item, True)
146
147 # have the tree filled by the exporter
148 self.__exporter.get_historical_tree(self)
149 self.__curr_node = root_item
150
151 self.SelectItem(root_item)
152 self.Expand(root_item)
153 self.__update_text_for_selected_node()
154
155 self.restore_expansion()
156
157 wx.EndBusyCursor()
158 return True
159 #--------------------------------------------------------
161 """Displays information for the selected tree node."""
162
163 if self.__narr_display is None:
164 return
165
166 if self.__curr_node is None:
167 return
168
169 node_data = self.GetPyData(self.__curr_node)
170
171 # update displayed text
172 if isinstance(node_data, (gmEMRStructItems.cHealthIssue, types.DictType)):
173 # FIXME: turn into real dummy issue
174 if node_data['pk_health_issue'] is None:
175 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description']
176 else:
177 txt = node_data.format(left_margin=1, patient = self.__pat)
178
179 elif isinstance(node_data, gmEMRStructItems.cEpisode):
180 txt = node_data.format(left_margin = 1, patient = self.__pat)
181
182 elif isinstance(node_data, gmEMRStructItems.cEncounter):
183 epi = self.GetPyData(self.GetItemParent(self.__curr_node))
184 txt = node_data.format(episodes = [epi['pk_episode']], with_soap = True, left_margin = 1, patient = self.__pat)
185
186 else:
187 emr = self.__pat.get_emr()
188 txt = emr.format_summary()
189
190 self.__narr_display.Clear()
191 self.__narr_display.WriteText(txt)
192 #--------------------------------------------------------
281 #--------------------------------------------------------
284 #--------------------------------------------------------
286 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description'])
287 self.PopupMenu(self.__issue_context_popup, pos)
288 #--------------------------------------------------------
290 self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description'])
291 self.PopupMenu(self.__epi_context_popup, pos)
292 #--------------------------------------------------------
295 #--------------------------------------------------------
296 # episode level
297 #--------------------------------------------------------
299 episode = self.GetPyData(self.__curr_node)
300
301 gmNarrativeWidgets.move_progress_notes_to_another_encounter (
302 parent = self,
303 episodes = [episode['pk_episode']],
304 move_all = True
305 )
306 #--------------------------------------------------------
309 #--------------------------------------------------------
311 pat = gmPerson.gmCurrentPatient()
312 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
313 #--------------------------------------------------------
315 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
316 parent = self,
317 id = -1,
318 caption = _('Deleting episode'),
319 button_defs = [
320 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
321 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
322 ],
323 question = _(
324 'Are you sure you want to delete this episode ?\n'
325 '\n'
326 ' "%s"\n'
327 ) % self.__curr_node_data['description']
328 )
329 result = dlg.ShowModal()
330 if result != wx.ID_YES:
331 return
332
333 try:
334 gmEMRStructItems.delete_episode(episode = self.__curr_node_data)
335 except gmExceptions.DatabaseObjectInUseError:
336 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
337 return
338 #--------------------------------------------------------
339 # encounter level
340 #--------------------------------------------------------
342 encounter = self.GetPyData(self.__curr_node)
343 node_parent = self.GetItemParent(self.__curr_node)
344 episode = self.GetPyData(node_parent)
345
346 gmNarrativeWidgets.move_progress_notes_to_another_encounter (
347 parent = self,
348 encounters = [encounter['pk_encounter']],
349 episodes = [episode['pk_episode']]
350 )
351 #--------------------------------------------------------
353 encounter = self.GetPyData(self.__curr_node)
354 node_parent = self.GetItemParent(self.__curr_node)
355 episode = self.GetPyData(node_parent)
356
357 gmNarrativeWidgets.manage_progress_notes (
358 parent = self,
359 encounters = [encounter['pk_encounter']],
360 episodes = [episode['pk_episode']]
361 )
362 #--------------------------------------------------------
364 node_data = self.GetPyData(self.__curr_node)
365 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=node_data)
366 dlg.ShowModal()
367 dlg.Destroy()
368 self.__populate_tree()
369 #--------------------------------------------------------
371
372 node_parent = self.GetItemParent(self.__curr_node)
373 owning_episode = self.GetPyData(node_parent)
374
375 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
376 self,
377 -1,
378 episode = owning_episode,
379 encounter = self.__curr_node_data
380 )
381
382 result = episode_selector.ShowModal()
383 episode_selector.Destroy()
384
385 if result == wx.ID_YES:
386 self.__populate_tree()
387 #--------------------------------------------------------
390 #--------------------------------------------------------
392 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
393 parent = self,
394 id = -1,
395 caption = _('Deleting health issue'),
396 button_defs = [
397 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
398 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
399 ],
400 question = _(
401 'Are you sure you want to delete this health issue ?\n'
402 '\n'
403 ' "%s"\n'
404 ) % self.__curr_node_data['description']
405 )
406 result = dlg.ShowModal()
407 if result != wx.ID_YES:
408 dlg.Destroy()
409 return
410
411 dlg.Destroy()
412
413 try:
414 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data)
415 except gmExceptions.DatabaseObjectInUseError:
416 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
417 #--------------------------------------------------------
419
420 if not self.__curr_node.IsOk():
421 return
422
423 self.Expand(self.__curr_node)
424
425 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
426 while epi.IsOk():
427 self.Expand(epi)
428 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
429 #--------------------------------------------------------
432 #--------------------------------------------------------
434 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
435 # FIXME: use signal and use node level update
436 if dlg.ShowModal() == wx.ID_OK:
437 self.__populate_tree()
438 dlg.Destroy()
439 return
440 #--------------------------------------------------------
442
443 root_item = self.GetRootItem()
444
445 if not root_item.IsOk():
446 return
447
448 self.Expand(root_item)
449
450 # collapse episodes and issues
451 issue, issue_cookie = self.GetFirstChild(root_item)
452 while issue.IsOk():
453 self.Collapse(issue)
454 epi, epi_cookie = self.GetFirstChild(issue)
455 while epi.IsOk():
456 self.Collapse(epi)
457 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
458 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
459 #--------------------------------------------------------
461
462 root_item = self.GetRootItem()
463
464 if not root_item.IsOk():
465 return
466
467 self.Expand(root_item)
468
469 # collapse episodes, expand issues
470 issue, issue_cookie = self.GetFirstChild(root_item)
471 while issue.IsOk():
472 self.Expand(issue)
473 epi, epi_cookie = self.GetFirstChild(issue)
474 while epi.IsOk():
475 self.Collapse(epi)
476 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
477 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
478 #--------------------------------------------------------
480
481 root_item = self.GetRootItem()
482
483 if not root_item.IsOk():
484 return
485
486 self.Expand(root_item)
487
488 # collapse episodes, expand issues
489 issue, issue_cookie = self.GetFirstChild(root_item)
490 while issue.IsOk():
491 self.Expand(issue)
492 epi, epi_cookie = self.GetFirstChild(issue)
493 while epi.IsOk():
494 self.Expand(epi)
495 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
496 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
497 #--------------------------------------------------------
499 gmNarrativeWidgets.export_narrative_for_medistar_import (
500 parent = self,
501 soap_cats = u'soap',
502 encounter = self.__curr_node_data
503 )
504 #--------------------------------------------------------
505 # event handlers
506 #--------------------------------------------------------
509 #--------------------------------------------------------
512 #--------------------------------------------------------
515 #--------------------------------------------------------
517 sel_item = event.GetItem()
518 self.__curr_node = sel_item
519 self.__update_text_for_selected_node()
520 return True
521 # #--------------------------------------------------------
522 # def _on_mouse_motion(self, event):
523 #
524 # cursor_pos = (event.GetX(), event.GetY())
525 #
526 # self.SetToolTipString(u'')
527 #
528 # if cursor_pos != self._old_cursor_pos:
529 # self._old_cursor_pos = cursor_pos
530 # (item, flags) = self.HitTest(cursor_pos)
531 # #if flags != wx.TREE_HITTEST_NOWHERE:
532 # if flags == wx.TREE_HITTEST_ONITEMLABEL:
533 # data = self.GetPyData(item)
534 #
535 # if not isinstance(data, gmEMRStructItems.cEncounter):
536 # return
537 #
538 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % (
539 # data['started'].strftime('%x'),
540 # data['l10n_type'],
541 # data['started'].strftime('%H:%m'),
542 # data['last_affirmed'].strftime('%H:%m'),
543 # gmTools.coalesce(data['reason_for_encounter'], u''),
544 # gmTools.coalesce(data['assessment_of_encounter'], u'')
545 # ))
546 #--------------------------------------------------------
548
549 item = event.GetItem()
550
551 if not item.IsOk():
552 event.SetToolTip(u' ')
553 return
554
555 data = self.GetPyData(item)
556
557 if isinstance(data, gmEMRStructItems.cEncounter):
558 event.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % (
559 data['started'].strftime('%x'),
560 data['l10n_type'],
561 data['started'].strftime('%H:%M'),
562 data['last_affirmed'].strftime('%H:%M'),
563 gmTools.coalesce(data['reason_for_encounter'], u''),
564 gmTools.coalesce(data['assessment_of_encounter'], u'')
565 ))
566
567 elif isinstance(data, gmEMRStructItems.cEpisode):
568 tt = u''
569 tt += gmTools.bool2subst (
570 (data['diagnostic_certainty_classification'] is not None),
571 data.diagnostic_certainty_description + u'\n\n',
572 u''
573 )
574 tt += gmTools.bool2subst (
575 data['episode_open'],
576 _('ongoing episode'),
577 _('closed episode'),
578 'error: episode state is None'
579 )
580 event.SetToolTip(tt)
581
582 elif isinstance(data, gmEMRStructItems.cHealthIssue):
583 tt = u''
584 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'')
585 tt += gmTools.bool2subst (
586 (data['diagnostic_certainty_classification'] is not None),
587 data.diagnostic_certainty_description + u'\n',
588 u''
589 )
590 tt += gmTools.bool2subst (
591 (data['laterality'] not in [None, u'na']),
592 data.laterality_description + u'\n',
593 u''
594 )
595 # noted_at_age is too costly
596 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'')
597 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'')
598 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'')
599 tt += gmTools.coalesce(data['grouping'], u'', _('Grouping: %s'))
600 event.SetToolTip(tt)
601
602 else:
603 event.SetToolTip(u' ')
604 #self.SetToolTipString(u'')
605
606 # doing this prevents the tooltip from showing at all
607 #event.Skip()
608
609 #widgetXY.GetToolTip().Enable(False)
610 #
611 #seems to work, supposing the tooltip is actually set for the widget,
612 #otherwise a test would be needed
613 #if widgetXY.GetToolTip():
614 # widgetXY.GetToolTip().Enable(False)
615 #--------------------------------------------------------
617 """Right button clicked: display the popup for the tree"""
618
619 node = event.GetItem()
620 self.SelectItem(node)
621 self.__curr_node_data = self.GetPyData(node)
622 self.__curr_node = node
623
624 pos = wx.DefaultPosition
625 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
626 self.__handle_issue_context(pos=pos)
627 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
628 self.__handle_episode_context(pos=pos)
629 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
630 self.__handle_encounter_context(pos=pos)
631 elif node == self.GetRootItem():
632 self.__handle_root_context()
633 elif type(self.__curr_node_data) == type({}):
634 # ignore pseudo node "free-standing episodes"
635 pass
636 else:
637 print "error: unknown node type, no popup menu"
638 event.Skip()
639 #--------------------------------------------------------
641 """Used in sorting items.
642
643 -1: 1 < 2
644 0: 1 = 2
645 1: 1 > 2
646 """
647 # FIXME: implement sort modes, chron, reverse cron, by regex, etc
648
649 item1 = self.GetPyData(node1)
650 item2 = self.GetPyData(node2)
651
652 # encounters: reverse chron
653 if isinstance(item1, gmEMRStructItems.cEncounter):
654 if item1['started'] == item2['started']:
655 return 0
656 if item1['started'] > item2['started']:
657 return -1
658 return 1
659
660 # episodes: chron
661 if isinstance(item1, gmEMRStructItems.cEpisode):
662 start1 = item1.get_access_range()[0]
663 start2 = item2.get_access_range()[0]
664 if start1 == start2:
665 return 0
666 if start1 < start2:
667 return -1
668 return 1
669
670 # issues: alpha by grouping, no grouping at the bottom
671 if isinstance(item1, gmEMRStructItems.cHealthIssue):
672
673 # no grouping below grouping
674 if item1['grouping'] is None:
675 if item2['grouping'] is not None:
676 return 1
677
678 # grouping above no grouping
679 if item1['grouping'] is not None:
680 if item2['grouping'] is None:
681 return -1
682
683 # both no grouping: alpha on description
684 if (item1['grouping'] is None) and (item2['grouping'] is None):
685 if item1['description'].lower() < item2['description'].lower():
686 return -1
687 if item1['description'].lower() > item2['description'].lower():
688 return 1
689 return 0
690
691 # both with grouping: alpha on grouping, then alpha on description
692 if item1['grouping'] < item2['grouping']:
693 return -1
694
695 if item1['grouping'] > item2['grouping']:
696 return 1
697
698 if item1['description'].lower() < item2['description'].lower():
699 return -1
700
701 if item1['description'].lower() > item2['description'].lower():
702 return 1
703
704 return 0
705
706 # dummy health issue always on top
707 if isinstance(item1, type({})):
708 return -1
709
710 return 0
711 #================================================================
713 """A scrollable panel holding an EMR tree.
714
715 Lacks a widget to display details for selected items. The
716 tree data will be refetched - if necessary - whenever
717 repopulate_ui() is called, e.g., when then patient is changed.
718 """
720 wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl.__init__(self, *args, **kwds)
721 # self.__register_interests()
722 #--------------------------------------------------------
723 # def __register_interests(self):
724 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui)
725 #--------------------------------------------------------
729 #============================================================
731 """A splitter window holding an EMR tree.
732
733 The left hand side displays a scrollable EMR tree while
734 on the right details for selected items are displayed.
735
736 Expects to be put into a Notebook.
737 """
739 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds)
740 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details)
741 self.__register_events()
742 #--------------------------------------------------------
744 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
745 return True
746 #--------------------------------------------------------
751 #--------------------------------------------------------
753 """Fills UI with data."""
754 self._pnl_emr_tree.repopulate_ui()
755 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True)
756 return True
757 #================================================================
760 wx.Panel.__init__(self, *args, **kwargs)
761
762 self.__do_layout()
763 self.__register_events()
764 #--------------------------------------------------------
766 self.__journal = wx.TextCtrl (
767 self,
768 -1,
769 _('No EMR data loaded.'),
770 style = wx.TE_MULTILINE | wx.TE_READONLY
771 )
772 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
773 # arrange widgets
774 szr_outer = wx.BoxSizer(wx.VERTICAL)
775 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0)
776 # and do layout
777 self.SetAutoLayout(1)
778 self.SetSizer(szr_outer)
779 szr_outer.Fit(self)
780 szr_outer.SetSizeHints(self)
781 self.Layout()
782 #--------------------------------------------------------
784 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
785 #--------------------------------------------------------
787 """Expects to be in a Notebook."""
788 if self.GetParent().GetCurrentPage() == self:
789 self.repopulate_ui()
790 return True
791 #--------------------------------------------------------
792 # notebook plugin API
793 #--------------------------------------------------------
795 txt = StringIO.StringIO()
796 exporter = gmPatientExporter.cEMRJournalExporter()
797 # FIXME: if journal is large this will error out, use generator/yield etc
798 # FIXME: turn into proper list
799 try:
800 exporter.export(txt)
801 self.__journal.SetValue(txt.getvalue())
802 except ValueError:
803 _log.exception('cannot get EMR journal')
804 self.__journal.SetValue (_(
805 'An error occurred while retrieving the EMR\n'
806 'in journal form for the active patient.\n\n'
807 'Please check the log file for details.'
808 ))
809 txt.close()
810 self.__journal.ShowPosition(self.__journal.GetLastPosition())
811 return True
812 #================================================================
813 # MAIN
814 #----------------------------------------------------------------
815 if __name__ == '__main__':
816
817 _log.info("starting emr browser...")
818
819 try:
820 # obtain patient
821 patient = gmPerson.ask_for_patient()
822 if patient is None:
823 print "No patient. Exiting gracefully..."
824 sys.exit(0)
825 gmPatSearchWidgets.set_active_patient(patient = patient)
826
827 # display standalone browser
828 application = wx.PyWidgetTester(size=(800,600))
829 emr_browser = cEMRBrowserPanel(application.frame, -1)
830 emr_browser.refresh_tree()
831
832 application.frame.Show(True)
833 application.MainLoop()
834
835 # clean up
836 if patient is not None:
837 try:
838 patient.cleanup()
839 except:
840 print "error cleaning up patient"
841 except StandardError:
842 _log.exception("unhandled exception caught !")
843 # but re-raise them
844 raise
845
846 _log.info("closing emr browser...")
847
848 #================================================================
849 # $Log: gmEMRBrowser.py,v $
850 # Revision 1.111 2010/01/11 19:44:39 ncq
851 # - cleanup
852 #
853 # Revision 1.110 2009/11/15 01:05:02 ncq
854 # - enable moving SOAP of a list of encounters of an episode
855 #
856 # Revision 1.109 2009/11/06 15:17:07 ncq
857 # - properly promote episode to issue
858 # - better tooltips
859 #
860 # Revision 1.108 2009/10/29 17:21:14 ncq
861 # - improved tooltips
862 #
863 # Revision 1.107 2009/10/20 10:26:21 ncq
864 # - tooltips on issue/episode in EMR tree
865 #
866 # Revision 1.106 2009/09/23 14:34:21 ncq
867 # - add promoting episode to issue
868 #
869 # Revision 1.105 2009/09/01 22:26:56 ncq
870 # - use new edit_episode/edit_health_issue
871 #
872 # Revision 1.104 2009/06/04 16:30:30 ncq
873 # - use set active patient from pat search widgets
874 #
875 # Revision 1.103 2009/05/13 12:19:13 ncq
876 # - use improved encounter management
877 # - add moving narrative between encounters
878 #
879 # Revision 1.102 2009/04/16 12:48:31 ncq
880 # - edit_progress_notes -> manage_*
881 # - use proper formatter when displaying encounter tooltip
882 #
883 # Revision 1.101 2009/04/05 18:04:46 ncq
884 # - support and use grouping
885 #
886 # Revision 1.100 2009/01/21 18:03:53 ncq
887 # - comment on tooltip handling
888 #
889 # Revision 1.99 2008/12/18 21:27:56 ncq
890 # - add editing progress notes from encounter context menu
891 #
892 # Revision 1.98 2008/12/01 12:37:37 ncq
893 # - generate encounter node tooltips
894 #
895 # Revision 1.97 2008/11/20 19:50:19 ncq
896 # - use improved data formatting
897 #
898 # Revision 1.96 2008/10/12 16:13:23 ncq
899 # - rename EMR tree root node, per Jim
900 #
901 # Revision 1.95 2008/09/02 19:01:11 ncq
902 # - adjust to clin health_issue fk_patient drop and related changes
903 #
904 # Revision 1.94 2008/08/31 18:39:47 ncq
905 # - if narrative is added, say a test result with comment, before
906 # the tree was ever displayed (hence not populated) for that patient
907 # there is not yet a currently selected node, so don't update
908 # the narrative display either
909 #
910 # Revision 1.93 2008/08/15 15:56:38 ncq
911 # - properly handle context click on pseudo-issue
912 #
913 # Revision 1.92 2008/07/28 15:44:39 ncq
914 # - context menu based Medistar export for any encounter
915 #
916 # Revision 1.91 2008/07/14 13:46:11 ncq
917 # - better naming of dummy health issue
918 #
919 # Revision 1.90 2008/07/12 15:31:23 ncq
920 # - improved formatting of issue info
921 #
922 # Revision 1.89 2008/07/07 13:44:33 ncq
923 # - current patient .connected
924 # - properly sort tree, encounters: most recent on top as per user request
925 #
926 # Revision 1.88 2008/06/28 18:25:58 ncq
927 # - add expand to ... level popup menu items in EMR tree
928 #
929 # Revision 1.87 2008/05/19 16:23:33 ncq
930 # - let EMR format its summary itself
931 #
932 # Revision 1.86 2008/04/11 12:27:45 ncq
933 # - listen to issue/episode/narrative change signals thereby
934 # reducing direct repopulate calls
935 # - factor out __update_text_for_selected_node() and
936 # call format() on nodes that have it
937 # - rearrange code layout
938 #
939 # Revision 1.85 2008/03/05 22:30:14 ncq
940 # - new style logging
941 #
942 # Revision 1.84 2008/01/30 14:07:24 ncq
943 # - do not use old cfg file support anymore
944 #
945 # Revision 1.83 2008/01/22 12:20:53 ncq
946 # - dummy health issue always on top
947 # - auto-scroll to bottom of journal
948 #
949 # Revision 1.82 2007/12/11 12:49:25 ncq
950 # - explicit signal handling
951 #
952 # Revision 1.81 2007/09/07 10:56:57 ncq
953 # - cleanup
954 #
955 # Revision 1.80 2007/08/29 22:09:10 ncq
956 # - narrative widgets factored out
957 #
958 # Revision 1.79 2007/08/15 14:57:52 ncq
959 # - pretty up tree popup menus
960 # - add deletion of health issues
961 #
962 # Revision 1.78 2007/08/12 00:09:07 ncq
963 # - no more gmSignals.py
964 #
965 # Revision 1.77 2007/06/18 20:31:10 ncq
966 # - case insensitively sort health issues
967 #
968 # Revision 1.76 2007/06/10 09:56:54 ncq
969 # - actually sort tree items, add sorting for health issues
970 #
971 # Revision 1.75 2007/05/21 14:48:20 ncq
972 # - cleanup
973 # - use pat['dirname'], use export/EMR/
974 # - unicode output files
975 #
976 # Revision 1.74 2007/05/21 13:05:25 ncq
977 # - catch-all wildcard on UNIX must be *, not *.*
978 #
979 # Revision 1.73 2007/05/18 13:29:25 ncq
980 # - some cleanup
981 # - properly support moving narrative between episodes
982 #
983 # Revision 1.72 2007/05/14 13:11:24 ncq
984 # - use statustext() signal
985 #
986 # Revision 1.71 2007/05/14 10:33:33 ncq
987 # - allow deleting episode
988 #
989 # Revision 1.70 2007/03/18 14:04:00 ncq
990 # - add allergy handling to menu and root node of tree
991 #
992 # Revision 1.69 2007/03/02 15:31:45 ncq
993 # - properly repopulation EMR tree and problem list :-)
994 #
995 # Revision 1.68 2007/02/22 17:41:13 ncq
996 # - adjust to gmPerson changes
997 #
998 # Revision 1.67 2007/02/16 12:51:46 ncq
999 # - fix add issue popup on root node as requested by user :-)
1000 #
1001 # Revision 1.66 2007/01/16 18:00:59 ncq
1002 # - cleanup
1003 # - explicitely sort episodes and encounters by when they were started
1004 #
1005 # Revision 1.65 2007/01/15 13:08:55 ncq
1006 # - remove explicit __relink_episode2issue as episode details editor now does it
1007 #
1008 # Revision 1.64 2007/01/13 22:26:55 ncq
1009 # - remove cruft
1010 # - mix expansion history into emr tree browser
1011 #
1012 # Revision 1.63 2007/01/06 23:41:40 ncq
1013 # - missing :
1014 #
1015 # Revision 1.62 2007/01/04 23:41:36 ncq
1016 # - use new episode edit area
1017 #
1018 # Revision 1.61 2006/12/25 22:50:50 ncq
1019 # - add editing of consultation details from EMR tree right-click popup menu
1020 #
1021 # Revision 1.60 2006/12/13 23:32:41 ncq
1022 # - emr journal on a diet
1023 #
1024 # Revision 1.59 2006/11/24 14:20:44 ncq
1025 # - used shiny new health issue edit area in issue context menu
1026 # - refresh tree after editing health issue
1027 #
1028 # Revision 1.58 2006/11/24 10:01:31 ncq
1029 # - gm_beep_statustext() -> gm_statustext()
1030 #
1031 # Revision 1.57 2006/11/05 16:02:00 ncq
1032 # - cleanup
1033 #
1034 # Revision 1.56 2006/10/09 12:22:27 ncq
1035 # - some cleanup
1036 # - adjust to changed signature of encounter.transfer_clinical_data()
1037 #
1038 # Revision 1.55 2006/06/26 13:03:22 ncq
1039 # - improve menu strings
1040 # - implement moving episodes among issues
1041 #
1042 # Revision 1.54 2006/05/28 20:53:28 ncq
1043 # - cleanup, fix some variables and typos
1044 #
1045 # Revision 1.53 2006/05/28 16:23:10 ncq
1046 # - cleanup
1047 # - dedicated cEMRTree akin to cDocTree
1048 # - wxGladify tree widgets
1049 #
1050 # Revision 1.52 2006/05/15 13:35:59 ncq
1051 # - signal cleanup:
1052 # - activating_patient -> pre_patient_selection
1053 # - patient_selected -> post_patient_selection
1054 #
1055 # Revision 1.51 2006/05/04 09:49:20 ncq
1056 # - get_clinical_record() -> get_emr()
1057 # - adjust to changes in set_active_patient()
1058 # - need explicit set_active_patient() after ask_for_patient() if wanted
1059 #
1060 # Revision 1.50 2005/12/27 02:52:40 sjtan
1061 #
1062 # allow choice of closing old episode, or relinking to old episode, whenever opening a new episode in the present of an already open episode of an issue.
1063 # Small logic error fixed where the id of the health_issue was passed in as the id of an episode.
1064 #
1065 # Revision 1.49 2005/10/18 13:34:00 sjtan
1066 # after running; small diffs
1067 #
1068 # Revision 1.48 2005/10/09 06:42:02 sjtan
1069 # timely cache update means a complete tree reconstruct can be done quite fast ( currently sized records anyway),
1070 # so don't use refresh_historical_tree() - need to debug this anyway.
1071 #
1072 # Revision 1.47 2005/10/08 12:33:10 sjtan
1073 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch.
1074 #
1075 # Revision 1.46 2005/10/04 19:24:53 sjtan
1076 # browser now remembers expansion state and select state between change of patients, between health issue rename, episode rename or encounter relinking. This helps when reviewing the record more than once in a day.
1077 #
1078 # Revision 1.45 2005/10/04 13:09:49 sjtan
1079 # correct syntax errors; get soap entry working again.
1080 #
1081 # Revision 1.44 2005/09/28 21:27:30 ncq
1082 # - a lot of wx2.6-ification
1083 #
1084 # Revision 1.43 2005/09/28 15:57:48 ncq
1085 # - a whole bunch of wx.Foo -> wx.Foo
1086 #
1087 # Revision 1.42 2005/09/27 20:44:58 ncq
1088 # - wx.wx* -> wx.*
1089 #
1090 # Revision 1.41 2005/09/26 18:01:50 ncq
1091 # - use proper way to import wx26 vs wx2.4
1092 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES
1093 # - time for fixup
1094 #
1095 # Revision 1.40 2005/09/24 09:17:28 ncq
1096 # - some wx2.6 compatibility fixes
1097 #
1098 # Revision 1.39 2005/09/11 17:30:02 ncq
1099 # - cleanup
1100 #
1101 # Revision 1.38 2005/09/08 16:57:48 ncq
1102 # - smaller font in journal display
1103 #
1104 # Revision 1.37 2005/07/21 21:00:46 ncq
1105 # - cleanup, better strings
1106 #
1107 # Revision 1.36 2005/07/02 18:20:52 ncq
1108 # - quite some cleanup
1109 #
1110 # Revision 1.35 2005/06/29 18:35:17 cfmoro
1111 # create encounter from EMR tree. Added FIXME for refactor duplicated code
1112 #
1113 # Revision 1.34 2005/06/29 12:53:50 cfmoro
1114 # Added create issue menu item to root node
1115 #
1116 # Revision 1.33 2005/06/23 14:59:43 ncq
1117 # - cleanup __relink_encounter_data2episode()
1118 #
1119 # Revision 1.32 2005/06/20 13:03:38 cfmoro
1120 # Relink encounter to another episode
1121 #
1122 # Revision 1.31 2005/06/15 22:27:20 ncq
1123 # - allow issue renaming
1124 #
1125 # Revision 1.30 2005/06/14 20:26:04 cfmoro
1126 # refresh tree on unit test startup
1127 #
1128 # Revision 1.29 2005/06/14 20:14:16 cfmoro
1129 # unit testing fix
1130 #
1131 # Revision 1.28 2005/06/14 18:57:50 ncq
1132 # - support renaming an episode
1133 #
1134 # Revision 1.27 2005/04/24 14:44:05 ncq
1135 # - callbacks must be _* not __* or else namespace invisibility will ensue
1136 #
1137 # Revision 1.26 2005/04/12 16:19:49 ncq
1138 # - add cEMRJournalPanel for plugin
1139 #
1140 # Revision 1.25 2005/04/05 16:21:54 ncq
1141 # - a fix by Syan
1142 # - cleanup
1143 #
1144 # Revision 1.24 2005/04/03 20:10:51 ncq
1145 # - add export_emr_to_ascii()
1146 #
1147 # Revision 1.23 2005/04/03 09:15:39 ncq
1148 # - roll back EMR export button
1149 # - my suggestion to place it there wasn't logically sound
1150 # and it screwed up changing the right hand window, too
1151 #
1152 # Revision 1.22 2005/04/02 21:37:27 cfmoro
1153 # Unlinked episodes displayes in EMR tree and dump
1154 #
1155 # Revision 1.21 2005/04/02 20:45:14 cfmoro
1156 # Implementated exporting emr from gui client
1157 #
1158 # Revision 1.20 2005/03/30 22:10:07 ncq
1159 # - just cleanup
1160 #
1161 # Revision 1.19 2005/03/30 18:59:03 cfmoro
1162 # Added file selector dialog to emr dump callback function
1163 #
1164 # Revision 1.18 2005/03/30 18:14:56 cfmoro
1165 # Added emr export button
1166 #
1167 # Revision 1.17 2005/03/29 07:27:14 ncq
1168 # - add missing argument
1169 #
1170 # Revision 1.16 2005/03/11 22:52:54 ncq
1171 # - simplify popup menu use
1172 #
1173 # Revision 1.15 2005/03/10 19:51:29 cfmoro
1174 # Obtained problem from cClinicalRecord on progress notes edition
1175 #
1176 # Revision 1.14 2005/03/09 20:00:13 cfmoro
1177 # Added fixme comment in problem retrieval
1178 #
1179 # Revision 1.13 2005/03/09 19:43:21 cfmoro
1180 # EMR browser edit problem-episodes notes responsible for providing the narrative definitions to cSoapResizingPanel
1181 #
1182 # Revision 1.12 2005/03/09 18:31:57 cfmoro
1183 # As proof of concept: episode editor and notes editor are displayed in the right panel. Just an initial draft, needs feeback a lot of coding yet ;)
1184 #
1185 # Revision 1.11 2005/03/09 16:58:09 cfmoro
1186 # Thanks to Syan code, added contextual menu to emr tree. Linked episode edition action with the responsible dialog
1187 #
1188 # Revision 1.10 2005/02/03 20:19:16 ncq
1189 # - get_demographic_record() -> get_identity()
1190 #
1191 # Revision 1.9 2005/02/01 10:16:07 ihaywood
1192 # refactoring of gmDemographicRecord and follow-on changes as discussed.
1193 #
1194 # gmTopPanel moves to gmHorstSpace
1195 # gmRichardSpace added -- example code at present, haven't even run it myself
1196 # (waiting on some icon .pngs from Richard)
1197 #
1198 # Revision 1.8 2005/01/31 13:02:18 ncq
1199 # - use ask_for_patient() in gmPerson.py
1200 #
1201 # Revision 1.7 2005/01/31 10:37:26 ncq
1202 # - gmPatient.py -> gmPerson.py
1203 #
1204 # Revision 1.6 2004/10/31 00:37:13 cfmoro
1205 # Fixed some method names. Refresh function made public for easy reload, eg. standalone. Refresh browser at startup in standalone mode
1206 #
1207 # Revision 1.5 2004/09/06 18:57:27 ncq
1208 # - Carlos pluginized the lot ! :-)
1209 # - plus some fixes/tabified it
1210 #
1211 # Revision 1.4 2004/09/01 22:01:45 ncq
1212 # - actually use Carlos' issue/episode summary code
1213 #
1214 # Revision 1.3 2004/08/11 09:46:24 ncq
1215 # - now that EMR exporter supports SOAP notes - display them
1216 #
1217 # Revision 1.2 2004/07/26 00:09:27 ncq
1218 # - Carlos brings us data display for the encounters - can REALLY browse EMR now !
1219 #
1220 # Revision 1.1 2004/07/21 12:30:25 ncq
1221 # - initial checkin
1222 #
1223
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:01:58 2010 | http://epydoc.sourceforge.net |