| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed patient EMR tree browser."""
2 #================================================================
3 __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net"
4 __license__ = "GPL v2 or later"
5
6 # std lib
7 import sys
8 import os.path
9 import io
10 import logging
11
12
13 # 3rd party
14 import wx
15 import wx.lib.mixins.treemixin as treemixin
16
17
18 # GNUmed libs
19 from Gnumed.pycommon import gmI18N
20 from Gnumed.pycommon import gmDispatcher
21 from Gnumed.pycommon import gmExceptions
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmDateTime
24 from Gnumed.pycommon import gmLog2
25
26 from Gnumed.exporters import gmPatientExporter
27
28 from Gnumed.business import gmEMRStructItems
29 from Gnumed.business import gmPerson
30 from Gnumed.business import gmSOAPimporter
31 from Gnumed.business import gmPersonSearch
32 from Gnumed.business import gmSoapDefs
33 from Gnumed.business import gmClinicalRecord
34
35 from Gnumed.wxpython import gmGuiHelpers
36 from Gnumed.wxpython import gmEMRStructWidgets
37 from Gnumed.wxpython import gmEncounterWidgets
38 from Gnumed.wxpython import gmSOAPWidgets
39 from Gnumed.wxpython import gmAllergyWidgets
40 from Gnumed.wxpython import gmDemographicsWidgets
41 from Gnumed.wxpython import gmNarrativeWidgets
42 from Gnumed.wxpython import gmNarrativeWorkflows
43 from Gnumed.wxpython import gmPatSearchWidgets
44 from Gnumed.wxpython import gmVaccWidgets
45 from Gnumed.wxpython import gmFamilyHistoryWidgets
46 from Gnumed.wxpython import gmFormWidgets
47 from Gnumed.wxpython import gmTimer
48 from Gnumed.wxpython import gmHospitalStayWidgets
49 from Gnumed.wxpython import gmProcedureWidgets
50
51
52 _log = logging.getLogger('gm.ui')
53
54 #============================================================
56 """
57 Dump the patient's EMR from GUI client
58 @param parent - The parent widget
59 @type parent - A wx.Window instance
60 """
61 # sanity checks
62 if parent is None:
63 raise TypeError('expected wx.Window instance as parent, got <None>')
64
65 pat = gmPerson.gmCurrentPatient()
66 if not pat.connected:
67 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
68 return False
69
70 # get file name
71 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
72 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', pat.subdir_name)))
73 gmTools.mkdir(defdir)
74 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
75 dlg = wx.FileDialog (
76 parent = parent,
77 message = _("Save patient's EMR as..."),
78 defaultDir = defdir,
79 defaultFile = fname,
80 wildcard = wc,
81 style = wx.FD_SAVE
82 )
83 choice = dlg.ShowModal()
84 fname = dlg.GetPath()
85 dlg.Destroy()
86 if choice != wx.ID_OK:
87 return None
88
89 _log.debug('exporting EMR to [%s]', fname)
90
91 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
92 exporter = gmPatientExporter.cEmrExport(patient = pat)
93 exporter.set_output_file(output_file)
94 exporter.dump_constraints()
95 exporter.dump_demographic_record(True)
96 exporter.dump_clinical_record()
97 exporter.dump_med_docs()
98 output_file.close()
99
100 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
101 return fname
102
103 #============================================================
105 """This wx.TreeCtrl derivative displays a tree view of a medical record."""
106
107 #--------------------------------------------------------
109 """Set up our specialised tree.
110 """
111 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
112 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
113
114 self.__soap_display = None
115 self.__soap_display_mode = 'details' # "details" or "journal" or "revisions"
116 self.__img_display = None
117 self.__cb__enable_display_mode_selection = lambda x:x
118 self.__cb__select_edit_mode = lambda x:x
119 self.__cb__add_soap_editor = lambda x:x
120 self.__pat = None
121 self.__curr_node = None
122 self.__expanded_nodes = None
123
124 self.__make_popup_menus()
125 self.__register_events()
126
127 #--------------------------------------------------------
128 # external API
129 #--------------------------------------------------------
132
134 self.__soap_display = soap_display
135 self.__soap_display_prop_font = soap_display.GetFont()
136 self.__soap_display_mono_font = wx.Font(self.__soap_display_prop_font.GetNativeFontInfo())
137 self.__soap_display_mono_font.SetFamily(wx.FONTFAMILY_TELETYPE)
138 self.__soap_display_mono_font.SetPointSize(self.__soap_display_prop_font.GetPointSize() - 2)
139
140 soap_display = property(_get_soap_display, _set_soap_display)
141
142 #--------------------------------------------------------
145
147 self.__img_display = image_display
148
149 image_display = property(_get_image_display, _set_image_display)
150
151 #--------------------------------------------------------
153 if not callable(callback):
154 raise ValueError('callback [%s] not callable' % callback)
155 self.__cb__enable_display_mode_selection = callback
156
157 #--------------------------------------------------------
159 if callback is None:
160 callback = lambda x:x
161 if not callable(callback):
162 raise ValueError('edit mode selector [%s] not callable' % callback)
163 self.__cb__select_edit_mode = callback
164
165 edit_mode_selector = property(lambda x:x, _set_edit_mode_selector)
166
167 #--------------------------------------------------------
169 if callback is None:
170 callback = lambda x:x
171 if not callable(callback):
172 raise ValueError('soap editor adder [%s] not callable' % callback)
173 self.__cb__add_soap_editor = callback
174
175 soap_editor_adder = property(lambda x:x, _set_soap_editor_adder)
176
177 #--------------------------------------------------------
178 # ExpansionState mixin API
179 #--------------------------------------------------------
181 if item is None:
182 return 'invalid item'
183
184 if not item.IsOk():
185 return 'invalid item'
186
187 try:
188 node_data = self.GetItemData(item)
189 except wx.wxAssertionError:
190 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
191 _log.error('real node: %s', item)
192 _log.error('node.IsOk(): %s', item.IsOk()) # already survived this further up
193 _log.error('is root node: %s', item == self.GetRootItem())
194 _log.error('node attributes: %s', dir(item))
195 gmLog2.log_stack_trace()
196 return 'invalid item'
197
198 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
199 return 'issue::%s' % node_data['pk_health_issue']
200 if isinstance(node_data, gmEMRStructItems.cEpisode):
201 return 'episode::%s' % node_data['pk_episode']
202 if isinstance(node_data, gmEMRStructItems.cEncounter):
203 return 'encounter::%s' % node_data['pk_encounter']
204 # unassociated episodes
205 if isinstance(node_data, type({})):
206 return 'dummy node::%s' % self.__pat.ID
207 # root node == EMR level
208 return 'root node::%s' % self.__pat.ID
209
210 #--------------------------------------------------------
211 # internal helpers
212 #--------------------------------------------------------
214 """Configures enabled event signals."""
215 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_item_selected)
216 self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self._on_tree_item_right_clicked)
217 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
218
219 # handle tooltips
220 # self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
221 self.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tree_item_gettooltip)
222
223 # FIXME: xxxxx signal
224 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
225 gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_mod_db)
226 gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_issue_mod_db)
227 gmDispatcher.connect(signal = 'clin.family_history_mod_db', receiver = self._on_issue_mod_db)
228
229 #--------------------------------------------------------
233
234 #--------------------------------------------------------
236 """Updates EMR browser data."""
237 # FIXME: auto select the previously self.__curr_node if not None
238 # FIXME: error handling
239
240 _log.debug('populating EMR tree')
241
242 wx.BeginBusyCursor()
243
244 if self.__pat is None:
245 self.clear_tree()
246 self.__expanded_nodes = None
247 wx.EndBusyCursor()
248 return True
249
250 # init new tree
251 root_item = self.__populate_root_node()
252 self.__curr_node = root_item
253 if self.__expanded_nodes is not None:
254 self.ExpansionState = self.__expanded_nodes
255 self.SelectItem(root_item)
256 self.Expand(root_item)
257 self.__update_text_for_selected_node() # this is fairly slow, too
258
259 wx.EndBusyCursor()
260 return True
261
262 #--------------------------------------------------------
264
265 self.DeleteAllItems()
266
267 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
268 self.SetItemData(root_item, None)
269 self.SetItemHasChildren(root_item, True)
270
271 self.__root_tooltip = self.__pat['description_gender'] + '\n'
272 if self.__pat['deceased'] is None:
273 self.__root_tooltip += ' %s (%s)\n\n' % (
274 self.__pat.get_formatted_dob(format = '%d %b %Y'),
275 self.__pat['medical_age']
276 )
277 else:
278 template = ' %s - %s (%s)\n\n'
279 self.__root_tooltip += template % (
280 self.__pat.get_formatted_dob(format = '%d.%b %Y'),
281 gmDateTime.pydt_strftime(self.__pat['deceased'], '%Y %b %d'),
282 self.__pat['medical_age']
283 )
284 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], '', '%s\n\n')
285 doc = self.__pat.primary_provider
286 if doc is not None:
287 self.__root_tooltip += '%s:\n' % _('Primary provider in this praxis')
288 self.__root_tooltip += ' %s %s %s (%s)%s\n\n' % (
289 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
290 doc['firstnames'],
291 doc['lastnames'],
292 doc['short_alias'],
293 gmTools.bool2subst(doc['is_active'], '', ' [%s]' % _('inactive'))
294 )
295 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
296 self.__root_tooltip += _('In case of emergency contact:') + '\n'
297 if self.__pat['emergency_contact'] is not None:
298 self.__root_tooltip += gmTools.wrap (
299 text = '%s\n' % self.__pat['emergency_contact'],
300 width = 60,
301 initial_indent = ' ',
302 subsequent_indent = ' '
303 )
304 if self.__pat['pk_emergency_contact'] is not None:
305 contact = self.__pat.emergency_contact_in_database
306 self.__root_tooltip += ' %s\n' % contact['description_gender']
307 self.__root_tooltip = self.__root_tooltip.strip('\n')
308 if self.__root_tooltip == '':
309 self.__root_tooltip = ' '
310
311 return root_item
312
313 #--------------------------------------------------------
315 """Displays information for the selected tree node."""
316
317 if self.__soap_display is None:
318 return
319
320 self.__soap_display.Clear()
321 self.__img_display.clear()
322
323 if self.__curr_node is None:
324 return
325
326 if not self.__curr_node.IsOk():
327 return
328
329 try:
330 node_data = self.GetItemData(self.__curr_node)
331 except wx.wxAssertionError:
332 node_data = None # fake a root node
333 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
334 _log.error('real node: %s', self.__curr_node)
335 _log.error('node.IsOk(): %s', self.__curr_node.IsOk()) # already survived this further up
336 _log.error('is root node: %s', self.__curr_node == self.GetRootItem())
337 _log.error('node attributes: %s', dir(self.__curr_node))
338 gmLog2.log_stack_trace()
339
340 doc_folder = self.__pat.get_document_folder()
341
342 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
343 self.__cb__enable_display_mode_selection(True)
344 txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
345 if self.__soap_display_mode == 'details':
346 txt = node_data.format(left_margin = 1, patient = self.__pat)
347 font = self.__soap_display_prop_font
348 if self.__soap_display_mode == 'journal':
349 txt = node_data.format_as_journal(left_margin = 1)
350 font = self.__soap_display_prop_font
351 if self.__soap_display_mode == 'revisions':
352 txt = node_data.formatted_revision_history
353 font = self.__soap_display_mono_font
354 epis = node_data.episodes
355 if len(epis) > 0:
356 self.__img_display.refresh (
357 document_folder = doc_folder,
358 episodes = [ epi['pk_episode'] for epi in epis ],
359 do_async = True
360 )
361 self.__soap_display.SetFont(font)
362 self.__soap_display.WriteText(txt)
363 self.__soap_display.ShowPosition(0)
364 return
365
366 # unassociated episodes # FIXME: turn into real dummy issue
367 if isinstance(node_data, type({})):
368 self.__cb__enable_display_mode_selection(True)
369 if self.__soap_display_mode == 'details':
370 txt = _('Pool of unassociated episodes "%s":\n') % node_data['description']
371 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
372 if len(epis) > 0:
373 txt += '\n'
374 for epi in epis:
375 txt += epi.format (
376 left_margin = 1,
377 patient = self.__pat,
378 with_summary = True,
379 with_codes = False,
380 with_encounters = False,
381 with_documents = False,
382 with_hospital_stays = False,
383 with_procedures = False,
384 with_family_history = False,
385 with_tests = False,
386 with_vaccinations = False,
387 with_health_issue = False
388 )
389 txt += '\n'
390 else:
391 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
392 txt = ''
393 if len(epis) > 0:
394 txt += _(' Listing of unassociated episodes\n')
395 for epi in epis:
396 txt += ' %s\n' % (gmTools.u_box_horiz_4dashes * 60)
397 txt += epi.format (
398 left_margin = 1,
399 patient = self.__pat,
400 with_summary = False,
401 with_codes = False,
402 with_encounters = False,
403 with_documents = False,
404 with_hospital_stays = False,
405 with_procedures = False,
406 with_family_history = False,
407 with_tests = False,
408 with_vaccinations = False,
409 with_health_issue = False
410 )
411 txt += '\n'
412 txt += epi.format_as_journal(left_margin = 2)
413 self.__soap_display.SetFont(self.__soap_display_prop_font)
414 self.__soap_display.WriteText(txt)
415 self.__soap_display.ShowPosition(0)
416 return
417
418 if isinstance(node_data, gmEMRStructItems.cEpisode):
419 self.__cb__enable_display_mode_selection(True)
420 txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
421 if self.__soap_display_mode == 'details':
422 txt = node_data.format(left_margin = 1, patient = self.__pat)
423 font = self.__soap_display_prop_font
424 if self.__soap_display_mode == 'journal':
425 txt = node_data.format_as_journal(left_margin = 1)
426 font = self.__soap_display_prop_font
427 if self.__soap_display_mode == 'revisions':
428 txt = node_data.formatted_revision_history
429 font = self.__soap_display_mono_font
430 self.__img_display.refresh (
431 document_folder = doc_folder,
432 episodes = [ node_data['pk_episode'] ]
433 )
434 self.__soap_display.SetFont(font)
435 self.__soap_display.WriteText(txt)
436 self.__soap_display.ShowPosition(0)
437 return
438
439 if isinstance(node_data, gmEMRStructItems.cEncounter):
440 self.__cb__enable_display_mode_selection(True)
441 epi = self.GetItemData(self.GetItemParent(self.__curr_node))
442 if self.__soap_display_mode == 'revisions':
443 txt = node_data.formatted_revision_history
444 font = self.__soap_display_mono_font
445 else:
446 txt = node_data.format (
447 episodes = [epi['pk_episode']],
448 with_soap = True,
449 left_margin = 1,
450 patient = self.__pat,
451 with_co_encountlet_hints = True
452 )
453 font = self.__soap_display_prop_font
454 self.__img_display.refresh (
455 document_folder = doc_folder,
456 episodes = [ epi['pk_episode'] ],
457 encounter = node_data['pk_encounter']
458 )
459 self.__soap_display.SetFont(font)
460 self.__soap_display.WriteText(txt)
461 self.__soap_display.ShowPosition(0)
462 return
463
464 # root node == EMR level
465 self.__cb__enable_display_mode_selection(True)
466 if self.__soap_display_mode == 'details':
467 emr = self.__pat.emr
468 txt = emr.format_summary()
469 else:
470 txt = self.__pat.emr.format_as_journal(left_margin = 1, patient = self.__pat)
471 self.__soap_display.SetFont(self.__soap_display_prop_font)
472 self.__soap_display.WriteText(txt)
473 self.__soap_display.ShowPosition(0)
474
475 #--------------------------------------------------------
557
558 #--------------------------------------------------------
560 self.PopupMenu(self.__root_context_popup, pos)
561
562 #--------------------------------------------------------
564 self.PopupMenu(self.__issue_context_popup, pos)
565
566 #--------------------------------------------------------
568 self.PopupMenu(self.__epi_context_popup, pos)
569
570 #--------------------------------------------------------
572 self.PopupMenu(self.__enc_context_popup, pos)
573
574 #--------------------------------------------------------
575 # episode level
576 #--------------------------------------------------------
578 episode = self.GetItemData(self.__curr_node)
579
580 gmNarrativeWorkflows.move_progress_notes_to_another_encounter (
581 parent = self,
582 episodes = [episode['pk_episode']],
583 move_all = True
584 )
585
586 #--------------------------------------------------------
588 self.__curr_node_data['episode_open'] = not self.__curr_node_data['episode_open']
589 self.__curr_node_data.save()
590
591 #--------------------------------------------------------
594
595 #--------------------------------------------------------
597 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = self.__pat.emr)
598
599 #--------------------------------------------------------
601 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
602 parent = self,
603 id = -1,
604 caption = _('Deleting episode'),
605 button_defs = [
606 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
607 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
608 ],
609 question = _(
610 'Are you sure you want to delete this episode ?\n'
611 '\n'
612 ' "%s"\n'
613 ) % self.__curr_node_data['description']
614 )
615 result = dlg.ShowModal()
616 if result != wx.ID_YES:
617 return
618
619 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
620 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
621
622 #--------------------------------------------------------
624 self.DeleteChildren(episode_node)
625
626 emr = self.__pat.emr
627 epi = self.GetItemData(episode_node)
628 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
629 if len(encounters) == 0:
630 self.SetItemHasChildren(episode_node, False)
631 return
632
633 self.SetItemHasChildren(episode_node, True)
634
635 for enc in encounters:
636 label = '%s: %s' % (
637 enc['started'].strftime('%Y-%m-%d'),
638 gmTools.unwrap (
639 gmTools.coalesce (
640 gmTools.coalesce (
641 gmTools.coalesce (
642 enc.get_latest_soap ( # soAp
643 soap_cat = 'a',
644 episode = epi['pk_episode']
645 ),
646 enc['assessment_of_encounter'] # or AOE
647 ),
648 enc['reason_for_encounter'] # or RFE
649 ),
650 enc['l10n_type'] # or type
651 ),
652 max_length = 40
653 )
654 )
655 encounter_node = self.AppendItem(episode_node, label)
656 self.SetItemData(encounter_node, enc)
657 # we don't expand encounter nodes (what for ?)
658 self.SetItemHasChildren(encounter_node, False)
659
660 self.SortChildren(episode_node)
661
662 #--------------------------------------------------------
663 # encounter level
664 #--------------------------------------------------------
666 encounter = self.GetItemData(self.__curr_node)
667 node_parent = self.GetItemParent(self.__curr_node)
668 episode = self.GetItemData(node_parent)
669
670 gmNarrativeWorkflows.move_progress_notes_to_another_encounter (
671 parent = self,
672 encounters = [encounter['pk_encounter']],
673 episodes = [episode['pk_episode']]
674 )
675
676 #--------------------------------------------------------
678 encounter = self.GetItemData(self.__curr_node)
679 node_parent = self.GetItemParent(self.__curr_node)
680 episode = self.GetItemData(node_parent)
681
682 gmNarrativeWorkflows.manage_progress_notes (
683 parent = self,
684 encounters = [encounter['pk_encounter']],
685 episodes = [episode['pk_episode']]
686 )
687
688 #--------------------------------------------------------
690 node_data = self.GetItemData(self.__curr_node)
691 gmEncounterWidgets.edit_encounter(parent = self, encounter = node_data)
692 self.__populate_tree()
693
694 #--------------------------------------------------------
696
697 node_parent = self.GetItemParent(self.__curr_node)
698 owning_episode = self.GetItemData(node_parent)
699
700 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
701 self,
702 -1,
703 episode = owning_episode,
704 encounter = self.__curr_node_data
705 )
706
707 result = episode_selector.ShowModal()
708 episode_selector.Destroy()
709
710 if result == wx.ID_YES:
711 self.__populate_tree()
712
713 #--------------------------------------------------------
714 # issue level
715 #--------------------------------------------------------
718
719 #--------------------------------------------------------
721 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
722 parent = self,
723 id = -1,
724 caption = _('Deleting health issue'),
725 button_defs = [
726 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
727 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
728 ],
729 question = _(
730 'Are you sure you want to delete this health issue ?\n'
731 '\n'
732 ' "%s"\n'
733 ) % self.__curr_node_data['description']
734 )
735 result = dlg.ShowModal()
736 if result != wx.ID_YES:
737 dlg.Destroy()
738 return
739
740 dlg.Destroy()
741
742 if not gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data):
743 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
744
745 #--------------------------------------------------------
747
748 if not self.__curr_node.IsOk():
749 return
750
751 self.Expand(self.__curr_node)
752
753 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
754 while epi.IsOk():
755 self.Expand(epi)
756 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
757
758 #--------------------------------------------------------
760 self.DeleteChildren(issue_node)
761
762 issue = self.GetItemData(issue_node)
763 episodes = self.__pat.emr.get_episodes(issues = [issue['pk_health_issue']])
764 if len(episodes) == 0:
765 self.SetItemHasChildren(issue_node, False)
766 return
767
768 self.SetItemHasChildren(issue_node, True)
769
770 for episode in episodes:
771 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
772 episode_node = self.AppendItem(issue_node, '%s (%s)' % (
773 episode['description'],
774 range_str
775 ))
776 self.SetItemData(episode_node, episode)
777 # assume children so we can try to expand it
778 self.SetItemHasChildren(episode_node, True)
779
780 self.SortChildren(issue_node)
781
782 #--------------------------------------------------------
784 self.DeleteChildren(fake_issue_node)
785
786 episodes = self.__pat.emr.unlinked_episodes
787 if len(episodes) == 0:
788 self.SetItemHasChildren(fake_issue_node, False)
789 return
790
791 self.SetItemHasChildren(fake_issue_node, True)
792
793 for episode in episodes:
794 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
795 episode_node = self.AppendItem(fake_issue_node, '%s (%s)' % (
796 episode['description'],
797 range_str
798 ))
799 self.SetItemData(episode_node, episode)
800 if episode['episode_open']:
801 self.SetItemBold(fake_issue_node, True)
802 # assume children so we can try to expand it
803 self.SetItemHasChildren(episode_node, True)
804
805 self.SortChildren(fake_issue_node)
806
807 #--------------------------------------------------------
808 # EMR level
809 #--------------------------------------------------------
812
813 #--------------------------------------------------------
816
817 #--------------------------------------------------------
820
821 #--------------------------------------------------------
823 self.__cb__select_edit_mode(True)
824 self.__cb__add_soap_editor(problem = self.__curr_node_data, allow_same_problem = False)
825
826 #--------------------------------------------------------
828 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
829 # FIXME: use signal and use node level update
830 if dlg.ShowModal() == wx.ID_OK:
831 self.__expanded_nodes = self.ExpansionState
832 self.__populate_tree()
833 dlg.Destroy()
834 return
835
836 #--------------------------------------------------------
839
840 #--------------------------------------------------------
843
844 #--------------------------------------------------------
847
848 #--------------------------------------------------------
851
852 #--------------------------------------------------------
855
856 #--------------------------------------------------------
858
859 root_item = self.GetRootItem()
860
861 if not root_item.IsOk():
862 return
863
864 self.Expand(root_item)
865
866 # collapse episodes and issues
867 issue, issue_cookie = self.GetFirstChild(root_item)
868 while issue.IsOk():
869 self.Collapse(issue)
870 epi, epi_cookie = self.GetFirstChild(issue)
871 while epi.IsOk():
872 self.Collapse(epi)
873 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
874 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
875
876 #--------------------------------------------------------
878
879 root_item = self.GetRootItem()
880
881 if not root_item.IsOk():
882 return
883
884 self.Expand(root_item)
885
886 # collapse episodes, expand issues
887 issue, issue_cookie = self.GetFirstChild(root_item)
888 while issue.IsOk():
889 self.Expand(issue)
890 epi, epi_cookie = self.GetFirstChild(issue)
891 while epi.IsOk():
892 self.Collapse(epi)
893 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
894 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
895
896 #--------------------------------------------------------
898
899 root_item = self.GetRootItem()
900
901 if not root_item.IsOk():
902 return
903
904 self.Expand(root_item)
905
906 # collapse episodes, expand issues
907 issue, issue_cookie = self.GetFirstChild(root_item)
908 while issue.IsOk():
909 self.Expand(issue)
910 epi, epi_cookie = self.GetFirstChild(issue)
911 while epi.IsOk():
912 self.Expand(epi)
913 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
914 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
915
916 #--------------------------------------------------------
918 gmNarrativeWorkflows.export_narrative_for_medistar_import (
919 parent = self,
920 soap_cats = 'soapu',
921 encounter = self.__curr_node_data
922 )
923 #--------------------------------------------------------
925 root_node = self.GetRootItem()
926 self.DeleteChildren(root_node)
927
928 issues = [{
929 'description': _('Unattributed episodes'),
930 'laterality': None,
931 'diagnostic_certainty_classification': None,
932 'has_open_episode': False,
933 'pk_health_issue': None
934 }]
935 issues.extend(self.__pat.emr.health_issues)
936 for issue in issues:
937 issue_node = self.AppendItem(root_node, '%s%s%s' % (
938 issue['description'],
939 gmTools.coalesce(issue['laterality'], '', ' [%s]', none_equivalents = [None, 'na']),
940 gmTools.coalesce(issue['diagnostic_certainty_classification'], '', ' [%s]')
941 ))
942 self.SetItemBold(issue_node, issue['has_open_episode'])
943 self.SetItemData(issue_node, issue)
944 # fake it so we can expand it
945 self.SetItemHasChildren(issue_node, True)
946
947 self.SetItemHasChildren(root_node, (len(issues) != 0))
948 self.SortChildren(root_node)
949
950 #--------------------------------------------------------
951 # event handlers
952 #--------------------------------------------------------
955
956 #--------------------------------------------------------
960
961 #--------------------------------------------------------
965
966 #--------------------------------------------------------
968 event.Skip()
969
970 node = event.GetItem()
971 if node == self.GetRootItem():
972 self.__expand_root_node()
973 return
974
975 node_data = self.GetItemData(node)
976
977 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
978 self.__expand_issue_node(issue_node = node)
979 return
980
981 if isinstance(node_data, gmEMRStructItems.cEpisode):
982 self.__expand_episode_node(episode_node = node)
983 return
984
985 # pseudo node "free-standing episodes"
986 if type(node_data) == type({}):
987 self.__expand_pseudo_issue_node(fake_issue_node = node)
988 return
989
990 # encounter nodes do not need expanding
991 #if isinstance(node_data, gmEMRStructItems.cEncounter):
992
993 #--------------------------------------------------------
995 sel_item = event.GetItem()
996 self.__curr_node = sel_item
997 self.__update_text_for_selected_node()
998 return True
999
1000 # #--------------------------------------------------------
1001 # def _on_mouse_motion(self, event):
1002 #
1003 # cursor_pos = (event.GetX(), event.GetY())
1004 #
1005 # self.SetToolTip(u'')
1006 #
1007 # if cursor_pos != self._old_cursor_pos:
1008 # self._old_cursor_pos = cursor_pos
1009 # (item, flags) = self.HitTest(cursor_pos)
1010 # #if flags != wx.TREE_HITTEST_NOWHERE:
1011 # if flags == wx.TREE_HITTEST_ONITEMLABEL:
1012 # data = self.GetItemData(item)
1013 #
1014 # if not isinstance(data, gmEMRStructItems.cEncounter):
1015 # return
1016 #
1017 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % (
1018 # gmDateTime.pydt_strftime(data['started'], '%Y %b %d'),
1019 # data['l10n_type'],
1020 # data['started'].strftime('%H:%m'),
1021 # data['last_affirmed'].strftime('%H:%m'),
1022 # gmTools.coalesce(data['reason_for_encounter'], u''),
1023 # gmTools.coalesce(data['assessment_of_encounter'], u'')
1024 # ))
1025 #--------------------------------------------------------
1027
1028 item = event.GetItem()
1029
1030 if not item.IsOk():
1031 event.SetToolTip(' ')
1032 return
1033
1034 data = self.GetItemData(item)
1035
1036 if isinstance(data, gmEMRStructItems.cEncounter):
1037 tt = '%s %s %s - %s\n' % (
1038 gmDateTime.pydt_strftime(data['started'], '%Y %b %d'),
1039 data['l10n_type'],
1040 data['started'].strftime('%H:%M'),
1041 data['last_affirmed'].strftime('%H:%M')
1042 )
1043 if data['reason_for_encounter'] is not None:
1044 tt += '\n'
1045 tt += _('RFE: %s') % data['reason_for_encounter']
1046 if len(data['pk_generic_codes_rfe']) > 0:
1047 for code in data.generic_codes_rfe:
1048 tt += '\n %s: %s%s%s\n (%s %s)' % (
1049 code['code'],
1050 gmTools.u_left_double_angle_quote,
1051 code['term'],
1052 gmTools.u_right_double_angle_quote,
1053 code['name_short'],
1054 code['version']
1055 )
1056 if data['assessment_of_encounter'] is not None:
1057 tt += '\n'
1058 tt += _('AOE: %s') % data['assessment_of_encounter']
1059 if len(data['pk_generic_codes_aoe']) > 0:
1060 for code in data.generic_codes_aoe:
1061 tt += '\n %s: %s%s%s\n (%s %s)' % (
1062 code['code'],
1063 gmTools.u_left_double_angle_quote,
1064 code['term'],
1065 gmTools.u_right_double_angle_quote,
1066 code['name_short'],
1067 code['version']
1068 )
1069
1070 elif isinstance(data, gmEMRStructItems.cEpisode):
1071 tt = ''
1072 tt += gmTools.bool2subst (
1073 (data['diagnostic_certainty_classification'] is not None),
1074 data.diagnostic_certainty_description + '\n\n',
1075 ''
1076 )
1077 tt += gmTools.bool2subst (
1078 data['episode_open'],
1079 _('ongoing episode'),
1080 _('closed episode'),
1081 'error: episode state is None'
1082 ) + '\n'
1083 tt += gmTools.coalesce(data['summary'], '', '\n%s')
1084 if len(data['pk_generic_codes']) > 0:
1085 tt += '\n'
1086 for code in data.generic_codes:
1087 tt += '%s: %s%s%s\n (%s %s)\n' % (
1088 code['code'],
1089 gmTools.u_left_double_angle_quote,
1090 code['term'],
1091 gmTools.u_right_double_angle_quote,
1092 code['name_short'],
1093 code['version']
1094 )
1095
1096 tt = tt.strip('\n')
1097 if tt == '':
1098 tt = ' '
1099
1100 elif isinstance(data, gmEMRStructItems.cHealthIssue):
1101 tt = ''
1102 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), '')
1103 tt += gmTools.bool2subst (
1104 (data['diagnostic_certainty_classification'] is not None),
1105 data.diagnostic_certainty_description + '\n',
1106 ''
1107 )
1108 tt += gmTools.bool2subst (
1109 (data['laterality'] not in [None, 'na']),
1110 data.laterality_description + '\n',
1111 ''
1112 )
1113 # noted_at_age is too costly
1114 tt += gmTools.bool2subst(data['is_active'], _('active') + '\n', '')
1115 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + '\n', '')
1116 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + '\n', '')
1117 tt += gmTools.coalesce(data['grouping'], '\n', _('Grouping: %s') + '\n')
1118 tt += gmTools.coalesce(data['summary'], '', '\n%s')
1119 if len(data['pk_generic_codes']) > 0:
1120 tt += '\n'
1121 for code in data.generic_codes:
1122 tt += '%s: %s%s%s\n (%s %s)\n' % (
1123 code['code'],
1124 gmTools.u_left_double_angle_quote,
1125 code['term'],
1126 gmTools.u_right_double_angle_quote,
1127 code['name_short'],
1128 code['version']
1129 )
1130
1131 tt = tt.strip('\n')
1132 if tt == '':
1133 tt = ' '
1134
1135 else:
1136 tt = self.__root_tooltip
1137
1138 event.SetToolTip(tt)
1139
1140 # doing this prevents the tooltip from showing at all
1141 #event.Skip()
1142
1143 #widgetXY.GetToolTip().Enable(False)
1144 #
1145 #seems to work, supposing the tooltip is actually set for the widget,
1146 #otherwise a test would be needed
1147 #if widgetXY.GetToolTip():
1148 # widgetXY.GetToolTip().Enable(False)
1149
1150 #--------------------------------------------------------
1152 """Right button clicked: display the popup for the tree"""
1153
1154 node = event.GetItem()
1155 self.SelectItem(node)
1156 self.__curr_node_data = self.GetItemData(node)
1157 self.__curr_node = node
1158
1159 pos = wx.DefaultPosition
1160 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
1161 self.__handle_issue_context(pos=pos)
1162 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
1163 self.__handle_episode_context(pos=pos)
1164 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
1165 self.__handle_encounter_context(pos=pos)
1166 elif node == self.GetRootItem():
1167 self.__handle_root_context()
1168 elif type(self.__curr_node_data) == type({}):
1169 # ignore pseudo node "free-standing episodes"
1170 pass
1171 else:
1172 print("error: unknown node type, no popup menu")
1173 event.Skip()
1174
1175 #--------------------------------------------------------
1177 """Used in sorting items.
1178
1179 -1: 1 < 2
1180 0: 1 = 2
1181 1: 1 > 2
1182 """
1183 # FIXME: implement sort modes, chron, reverse cron, by regex, etc
1184
1185 if not node1:
1186 _log.debug('invalid node 1')
1187 return 0
1188 if not node2:
1189 _log.debug('invalid node 2')
1190 return 0
1191
1192 if not node1.IsOk():
1193 _log.debug('invalid node 1')
1194 return 0
1195 if not node2.IsOk():
1196 _log.debug('invalid node 2')
1197 return 0
1198
1199 item1 = self.GetItemData(node1)
1200 item2 = self.GetItemData(node2)
1201
1202 # dummy health issue always on top
1203 if isinstance(item1, type({})):
1204 return -1
1205 if isinstance(item2, type({})):
1206 return 1
1207
1208 # encounters: reverse chronologically
1209 if isinstance(item1, gmEMRStructItems.cEncounter):
1210 if item1['started'] == item2['started']:
1211 return 0
1212 if item1['started'] > item2['started']:
1213 return -1
1214 return 1
1215
1216 # episodes: open, then reverse chronologically
1217 if isinstance(item1, gmEMRStructItems.cEpisode):
1218 # open episodes first
1219 if item1['episode_open']:
1220 return -1
1221 if item2['episode_open']:
1222 return 1
1223 start1 = item1.best_guess_clinical_start_date
1224 start2 = item2.best_guess_clinical_start_date
1225 if start1 == start2:
1226 return 0
1227 if start1 < start2:
1228 return 1
1229 return -1
1230
1231 # issues: alpha by grouping, no grouping at the bottom
1232 if isinstance(item1, gmEMRStructItems.cHealthIssue):
1233
1234 # no grouping below grouping
1235 if item1['grouping'] is None:
1236 if item2['grouping'] is not None:
1237 return 1
1238
1239 # grouping above no grouping
1240 if item1['grouping'] is not None:
1241 if item2['grouping'] is None:
1242 return -1
1243
1244 # both no grouping: alpha on description
1245 if (item1['grouping'] is None) and (item2['grouping'] is None):
1246 if item1['description'].lower() < item2['description'].lower():
1247 return -1
1248 if item1['description'].lower() > item2['description'].lower():
1249 return 1
1250 return 0
1251
1252 # both with grouping: alpha on grouping, then alpha on description
1253 if item1['grouping'] < item2['grouping']:
1254 return -1
1255
1256 if item1['grouping'] > item2['grouping']:
1257 return 1
1258
1259 if item1['description'].lower() < item2['description'].lower():
1260 return -1
1261
1262 if item1['description'].lower() > item2['description'].lower():
1263 return 1
1264
1265 return 0
1266
1267 _log.error('unknown item type during sorting EMR tree:')
1268 _log.error('item1: %s', type(item1))
1269 _log.error('item2: %s', type(item2))
1270
1271 return 0
1272
1273 #--------------------------------------------------------
1274 # properties
1275 #--------------------------------------------------------
1278
1280 if self.__pat == patient:
1281 return
1282 self.__pat = patient
1283 if patient is None:
1284 self.clear_tree()
1285 return
1286 return self.__populate_tree()
1287
1288 patient = property(_get_patient, _set_patient)
1289 #--------------------------------------------------------
1292
1294 if mode not in ['details', 'journal', 'revisions']:
1295 raise ValueError('details display mode must be one of "details", "journal", "revisions"')
1296 if self.__soap_display_mode == mode:
1297 return
1298 self.__soap_display_mode = mode
1299 self.__update_text_for_selected_node()
1300
1301 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1302
1303 #================================================================
1304 # FIXME: still needed ?
1305 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
1306
1308 """A scrollable panel holding an EMR tree.
1309
1310 Lacks a widget to display details for selected items. The
1311 tree data will be refetched - if necessary - whenever
1312 repopulate_ui() is called, e.g., when the patient is changed.
1313 """
1316
1317 #============================================================
1318 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
1319
1321 """A splitter window holding an EMR tree.
1322
1323 The left hand side displays a scrollable EMR tree while
1324 on the right details for selected items are displayed.
1325
1326 Expects to be put into a Notebook.
1327 """
1329 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds)
1330 self._pnl_emr_tree._emr_tree.soap_display = self._TCTRL_item_details
1331 self._pnl_emr_tree._emr_tree.image_display = self._PNL_visual_soap
1332 self._pnl_emr_tree._emr_tree.set_enable_display_mode_selection_callback(self.enable_display_mode_selection)
1333 self._pnl_emr_tree._emr_tree.soap_editor_adder = self._add_soap_editor
1334 self._pnl_emr_tree._emr_tree.edit_mode_selector = self._select_edit_mode
1335 self.__register_events()
1336
1337 self.editing = False
1338 #--------------------------------------------------------
1340 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1341 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1342 return True
1343
1344 #--------------------------------------------------------
1347
1349 self.__editing = editing
1350 self.enable_display_mode_selection(enable = not self.__editing)
1351 if self.__editing:
1352 self._BTN_switch_browse_edit.SetLabel(_('&Browse %s') % gmTools.u_ellipsis)
1353 self._PNL_browse.Hide()
1354 self._PNL_visual_soap.Hide()
1355 self._PNL_edit.Show()
1356 else:
1357 self._BTN_switch_browse_edit.SetLabel(_('&New notes %s') % gmTools.u_ellipsis)
1358 self._PNL_edit.Hide()
1359 self._PNL_visual_soap.Show()
1360 self._PNL_browse.Show()
1361 self._PNL_right_side.GetSizer().Layout()
1362
1363 editing = property(_get_editing, _set_editing)
1364
1365 #--------------------------------------------------------
1366 # event handler
1367 #--------------------------------------------------------
1369 self._pnl_emr_tree._emr_tree.patient = None
1370 self._PNL_edit.patient = None
1371 return True
1372
1373 #--------------------------------------------------------
1375 if self.GetParent().GetCurrentPage() != self:
1376 return True
1377 self.repopulate_ui()
1378 return True
1379
1380 #--------------------------------------------------------
1382 self._pnl_emr_tree._emr_tree.details_display_mode = 'details'
1383
1384 #--------------------------------------------------------
1386 self._pnl_emr_tree._emr_tree.details_display_mode = 'journal'
1387
1388 #--------------------------------------------------------
1390 self._pnl_emr_tree._emr_tree.details_display_mode = 'revisions'
1391
1392 #--------------------------------------------------------
1395
1396 #--------------------------------------------------------
1397 # external API
1398 #--------------------------------------------------------
1400 """Fills UI with data."""
1401 pat = gmPerson.gmCurrentPatient()
1402 self._pnl_emr_tree._emr_tree.patient = pat
1403 self._PNL_edit.patient = pat
1404 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSize()[0] // 3, True)
1405
1406 return True
1407
1408 #--------------------------------------------------------
1410 if self.editing:
1411 enable = False
1412 if enable:
1413 self._RBTN_details.Enable(True)
1414 self._RBTN_journal.Enable(True)
1415 self._RBTN_revisions.Enable(True)
1416 return
1417 self._RBTN_details.Enable(False)
1418 self._RBTN_journal.Enable(False)
1419 self._RBTN_revisions.Enable(False)
1420
1421 #--------------------------------------------------------
1423 self._PNL_edit._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1424
1425 #--------------------------------------------------------
1428
1429 #================================================================
1430 from Gnumed.wxGladeWidgets import wxgEMRJournalPluginPnl
1431
1433
1435
1436 wxgEMRJournalPluginPnl.wxgEMRJournalPluginPnl.__init__(self, *args, **kwds)
1437 self._TCTRL_journal.disable_keyword_expansions()
1438 self._TCTRL_journal.SetValue('')
1439 #--------------------------------------------------------
1440 # external API
1441 #--------------------------------------------------------
1443 self._TCTRL_journal.SetValue('')
1444 exporter = gmPatientExporter.cEMRJournalExporter()
1445 if self._RBTN_by_encounter.GetValue():
1446 fname = exporter.save_to_file_by_encounter(patient = gmPerson.gmCurrentPatient())
1447 else:
1448 fname = exporter.save_to_file_by_mod_time(patient = gmPerson.gmCurrentPatient())
1449
1450 f = io.open(fname, mode = 'rt', encoding = 'utf8', errors = 'replace')
1451 for line in f:
1452 self._TCTRL_journal.AppendText(line)
1453 f.close()
1454
1455 self._TCTRL_journal.ShowPosition(self._TCTRL_journal.GetLastPosition())
1456 return True
1457 #--------------------------------------------------------
1458 # internal helpers
1459 #--------------------------------------------------------
1461 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1462 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1463 return True
1464
1465 #--------------------------------------------------------
1466 # event handlers
1467 #--------------------------------------------------------
1471
1472 #--------------------------------------------------------
1474 if self.GetParent().GetCurrentPage() != self:
1475 return True
1476 self.repopulate_ui()
1477 return True
1478
1479 #--------------------------------------------------------
1481 self.repopulate_ui()
1482
1483 #--------------------------------------------------------
1485 self.repopulate_ui()
1486
1487 #--------------------------------------------------------
1490
1491 #================================================================
1492 from Gnumed.wxGladeWidgets import wxgEMRListJournalPluginPnl
1493
1495
1497
1498 wxgEMRListJournalPluginPnl.wxgEMRListJournalPluginPnl.__init__(self, *args, **kwds)
1499
1500 self._LCTRL_journal.select_callback = self._on_row_selected
1501 self._TCTRL_details.SetValue('')
1502
1503 self.__load_timer = gmTimer.cTimer(callback = self._on_load_details, delay = 1000, cookie = 'EMRListJournalPluginDBLoadTimer')
1504
1505 self.__data = {}
1506
1507 #--------------------------------------------------------
1508 # external API
1509 #--------------------------------------------------------
1511 self._LCTRL_journal.remove_items_safely()
1512 self._TCTRL_details.SetValue('')
1513
1514 if self._RBTN_by_encounter.Value: # (... is True:)
1515 order_by = 'encounter_started, pk_episode, src_table, scr, modified_when'
1516 #, clin_when (should not make a relevant difference)
1517 date_col_header = _('Encounter')
1518 date_fields = ['encounter_started', 'modified_when']
1519 elif self._RBTN_by_last_modified.Value: # (... is True:)
1520 order_by = 'modified_when, pk_episode, src_table, scr'
1521 #, clin_when (should not make a relevant difference)
1522 date_col_header = _('Modified')
1523 date_fields = ['modified_when']
1524 elif self._RBTN_by_item_time.Value: # (... is True:)
1525 order_by = 'clin_when, pk_episode, src_table, scr, modified_when'
1526 date_col_header = _('Clinical time')
1527 date_fields = ['clin_when', 'modified_when']
1528 else:
1529 raise ValueError('invalid EMR journal list sort state')
1530
1531 self._LCTRL_journal.set_columns([date_col_header, '', _('Entry'), _('Who / When')])
1532 self._LCTRL_journal.set_resize_column(3)
1533
1534 journal = gmPerson.gmCurrentPatient().emr.get_as_journal(order_by = order_by)
1535
1536 items = []
1537 data = []
1538 self.__data = {}
1539 prev_date = None
1540 for entry in journal:
1541 if entry['narrative'].strip() == '':
1542 continue
1543 soap_cat = gmSoapDefs.soap_cat2l10n[entry['soap_cat']]
1544 who = '%s (%s)' % (entry['modified_by'], entry['date_modified'])
1545 try:
1546 entry_date = gmDateTime.pydt_strftime(entry[date_fields[0]], '%Y-%m-%d')
1547 except KeyError:
1548 entry_date = gmDateTime.pydt_strftime(entry[date_fields[1]], '%Y-%m-%d')
1549 if entry_date == prev_date:
1550 date2show = ''
1551 else:
1552 date2show = entry_date
1553 prev_date = entry_date
1554 lines = entry['narrative'].strip().split('\n')
1555 line_0 = lines[0].rstrip() # assumes there's at least one line ...
1556 if len(lines) == 1:
1557 delim = gmTools.u_box_horiz_light_3dashes * 10 + gmTools.u_box_T_left
1558 else:
1559 delim = (gmTools.u_box_horiz_light_3dashes * 10) + gmTools.u_box_top_right_arc
1560 entry_line = '%s %s' % (line_0, delim)
1561 items.append([date2show, soap_cat, entry_line, who])
1562 try:
1563 self.__data[entry['src_table']]
1564 except KeyError:
1565 self.__data[entry['src_table']] = {}
1566 try:
1567 self.__data[entry['src_table']][entry['src_pk']]
1568 except KeyError:
1569 self.__data[entry['src_table']][entry['src_pk']] = {}
1570 self.__data[entry['src_table']][entry['src_pk']]['entry'] = entry
1571 self.__data[entry['src_table']][entry['src_pk']]['formatted_instance'] = None
1572 if entry['encounter_started'] is None:
1573 enc_duration = gmTools.u_diameter
1574 else:
1575 enc_duration = '%s - %s' % (
1576 gmDateTime.pydt_strftime(entry['encounter_started'], '%Y %b %d %H:%M'),
1577 gmDateTime.pydt_strftime(entry['encounter_last_affirmed'], '%H:%M')
1578 )
1579 self.__data[entry['src_table']][entry['src_pk']]['formatted_header'] = _(
1580 'Chart entry: %s [#%s in %s]\n'
1581 ' Modified: %s by %s (%s rev %s)\n'
1582 '\n'
1583 'Health issue: %s%s\n'
1584 'Episode: %s%s\n'
1585 'Encounter: %s%s'
1586 ) % (
1587 gmClinicalRecord.format_clin_root_item_type(entry['src_table']),
1588 entry['src_pk'],
1589 entry['src_table'],
1590 entry['date_modified'],
1591 entry['modified_by'],
1592 gmTools.u_arrow2right,
1593 entry['row_version'],
1594 gmTools.coalesce(entry['health_issue'], gmTools.u_diameter, '%s'),
1595 gmTools.bool2subst(entry['issue_active'], ' (' + _('active') + ')', ' (' + _('inactive') + ')', ''),
1596 gmTools.coalesce(entry['episode'], gmTools.u_diameter, '%s'),
1597 gmTools.bool2subst(entry['episode_open'], ' (' + _('open') + ')', ' (' + _('closed') + ')', ''),
1598 enc_duration,
1599 gmTools.coalesce(entry['encounter_l10n_type'], '', ' (%s)'),
1600 )
1601 self.__data[entry['src_table']][entry['src_pk']]['formatted_root_item'] = _(
1602 '%s\n'
1603 '\n'
1604 ' rev %s (%s) by %s in <%s>'
1605 ) % (
1606 entry['narrative'].strip(),
1607 entry['row_version'],
1608 entry['date_modified'],
1609 entry['modified_by'],
1610 entry['src_table']
1611 )
1612 data.append ({
1613 'table': entry['src_table'],
1614 'pk': entry['src_pk']
1615 })
1616 if len(lines) > 1:
1617 lines = lines[1:]
1618 idx = 0
1619 last_line = len(lines)
1620 for entry_line in lines:
1621 idx += 1
1622 if entry_line.strip() == '':
1623 continue
1624 if idx == last_line:
1625 bar = gmTools.u_box_bottom_left_arc
1626 else:
1627 bar = gmTools.u_box_vert_light_4dashes
1628 items.append(['', bar, entry_line.rstrip(), who])
1629 data.append ({
1630 'table': entry['src_table'],
1631 'pk': entry['src_pk']
1632 })
1633
1634 self._LCTRL_journal.set_string_items(items)
1635 self._LCTRL_journal.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER])
1636 self._LCTRL_journal.set_data(data)
1637
1638 self._LCTRL_journal.SetFocus()
1639 return True
1640
1641 #--------------------------------------------------------
1642 # internal helpers
1643 #--------------------------------------------------------
1645 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1646 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1647 return True
1648
1649 #--------------------------------------------------------
1650 # event handlers
1651 #--------------------------------------------------------
1653 self._LCTRL_journal.remove_items_safely()
1654 self._TCTRL_details.SetValue('')
1655 self.__data = {}
1656 return True
1657
1658 #--------------------------------------------------------
1660 if self.GetParent().GetCurrentPage() != self:
1661 return True
1662 self.repopulate_ui()
1663 return True
1664
1665 #--------------------------------------------------------
1667 # FIXME: work on all selected
1668 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1669 if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
1670 txt = _(
1671 '%s\n'
1672 '%s\n'
1673 '%s'
1674 ) % (
1675 self.__data[data['table']][data['pk']]['formatted_header'],
1676 gmTools.u_box_horiz_4dashes * 40,
1677 self.__data[data['table']][data['pk']]['formatted_root_item']
1678 )
1679
1680 self._TCTRL_details.SetValue(txt)
1681 self.__load_timer.Stop()
1682 self.__load_timer.Start(oneShot = True)
1683 return
1684
1685 txt = _(
1686 '%s\n'
1687 '%s\n'
1688 '%s'
1689 ) % (
1690 self.__data[data['table']][data['pk']]['formatted_header'],
1691 gmTools.u_box_horiz_4dashes * 40,
1692 self.__data[data['table']][data['pk']]['formatted_instance']
1693 )
1694 self._TCTRL_details.SetValue(txt)
1695
1696 #--------------------------------------------------------
1698 data = self._LCTRL_journal.get_selected_item_data(only_one = True)
1699 if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
1700 self.__data[data['table']][data['pk']]['formatted_instance'] = gmClinicalRecord.format_clin_root_item(data['table'], data['pk'], patient = gmPerson.gmCurrentPatient())
1701 txt = _(
1702 '%s\n'
1703 '%s\n'
1704 '%s'
1705 ) % (
1706 self.__data[data['table']][data['pk']]['formatted_header'],
1707 gmTools.u_box_horiz_4dashes * 40,
1708 self.__data[data['table']][data['pk']]['formatted_instance']
1709 )
1710 wx.CallAfter(self._TCTRL_details.SetValue, txt)
1711
1712 #--------------------------------------------------------
1714 self.repopulate_ui()
1715
1716 #--------------------------------------------------------
1718 self.repopulate_ui()
1719
1720 #--------------------------------------------------------
1722 self.repopulate_ui()
1723
1724 #--------------------------------------------------------
1728
1729 #--------------------------------------------------------
1732
1733 #--------------------------------------------------------
1736
1737 #--------------------------------------------------------
1738 # def _on_button_find_pressed(self, event):
1739 # self._TCTRL_details.show_find_dialog(title = _('Find text in EMR Journal'))
1740
1741 #================================================================
1742 # MAIN
1743 #----------------------------------------------------------------
1744 if __name__ == '__main__':
1745
1746 _log.info("starting emr browser...")
1747
1748 try:
1749 # obtain patient
1750 patient = gmPersonSearch.ask_for_patient()
1751 if patient is None:
1752 print("No patient. Exiting gracefully...")
1753 sys.exit(0)
1754 gmPatSearchWidgets.set_active_patient(patient = patient)
1755
1756 # display standalone browser
1757 application = wx.PyWidgetTester(size=(800,600))
1758 emr_browser = cEMRBrowserPanel(application.frame, -1)
1759 emr_browser.refresh_tree()
1760
1761 application.frame.Show(True)
1762 application.MainLoop()
1763
1764 # clean up
1765 if patient is not None:
1766 try:
1767 patient.cleanup()
1768 except:
1769 print("error cleaning up patient")
1770 except Exception:
1771 _log.exception("unhandled exception caught !")
1772 # but re-raise them
1773 raise
1774
1775 _log.info("closing emr browser...")
1776
1777 #================================================================
1778
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jan 25 02:55:27 2019 | http://epydoc.sourceforge.net |