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
7 import sys
8 import os.path
9 import StringIO
10 import codecs
11 import logging
12
13
14
15 import wx
16
17
18
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.exporters import gmPatientExporter
24 from Gnumed.business import gmEMRStructItems
25 from Gnumed.business import gmPerson
26 from Gnumed.business import gmSOAPimporter
27 from Gnumed.business import gmPersonSearch
28 from Gnumed.wxpython import gmGuiHelpers
29 from Gnumed.wxpython import gmEMRStructWidgets
30 from Gnumed.wxpython import gmSOAPWidgets
31 from Gnumed.wxpython import gmAllergyWidgets
32 from Gnumed.wxpython import gmDemographicsWidgets
33 from Gnumed.wxpython import gmNarrativeWidgets
34 from Gnumed.wxpython import gmPatSearchWidgets
35 from Gnumed.wxpython import gmVaccWidgets
36 from Gnumed.wxpython import gmFamilyHistoryWidgets
37
38
39 _log = logging.getLogger('gm.ui')
40
41
43 """
44 Dump the patient's EMR from GUI client
45 @param parent - The parent widget
46 @type parent - A wx.Window instance
47 """
48
49 if parent is None:
50 raise TypeError('expected wx.Window instance as parent, got <None>')
51
52 pat = gmPerson.gmCurrentPatient()
53 if not pat.connected:
54 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
55 return False
56
57
58 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
59 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname'])))
60 gmTools.mkdir(defdir)
61 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
62 dlg = wx.FileDialog (
63 parent = parent,
64 message = _("Save patient's EMR as..."),
65 defaultDir = defdir,
66 defaultFile = fname,
67 wildcard = wc,
68 style = wx.SAVE
69 )
70 choice = dlg.ShowModal()
71 fname = dlg.GetPath()
72 dlg.Destroy()
73 if choice != wx.ID_OK:
74 return None
75
76 _log.debug('exporting EMR to [%s]', fname)
77
78 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace')
79 exporter = gmPatientExporter.cEmrExport(patient = pat)
80 exporter.set_output_file(output_file)
81 exporter.dump_constraints()
82 exporter.dump_demographic_record(True)
83 exporter.dump_clinical_record()
84 exporter.dump_med_docs()
85 output_file.close()
86
87 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
88 return fname
89
90 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
91 """This wx.TreeCtrl derivative displays a tree view of the medical record."""
92
93
94 - def __init__(self, parent, id, *args, **kwds):
95 """Set up our specialised tree.
96 """
97 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
98 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
99
100 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self)
101
102 self.__details_display = None
103 self.__details_display_mode = u'details'
104 self.__enable_display_mode_selection = None
105 self.__pat = gmPerson.gmCurrentPatient()
106 self.__curr_node = None
107
108
109 self._old_cursor_pos = None
110
111 self.__make_popup_menus()
112 self.__register_events()
113
114
115
117 if not self.__pat.connected:
118 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),)
119 return False
120
121 if not self.__populate_tree():
122 return False
123
124 return True
125
127 self.__details_display = narrative_display
128
130 self.__img_display = image_display
131
133 if not callable(callback):
134 raise ValueError('callback [%s] not callable' % callback)
135
136 self.__enable_display_mode_selection = callback
137
138
139
141 """Configures enabled event signals."""
142 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected)
143 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked)
144 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
145
146
147
148 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip)
149
150 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
151 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db)
152 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
153 gmDispatcher.connect(signal = 'family_history_mod_db', receiver = self._on_issue_mod_db)
154
156 self.DeleteAllItems()
157
159 """Updates EMR browser data."""
160
161
162
163 if not self.__pat.connected:
164 return
165
166 wx.BeginBusyCursor()
167
168
169 root_item = self.__populate_root_node()
170
171 self.__curr_node = root_item
172 self.SelectItem(root_item)
173 self.Expand(root_item)
174 self.__update_text_for_selected_node()
175
176 wx.EndBusyCursor()
177 return True
178
180
181 self.DeleteAllItems()
182
183 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
184 self.SetItemPyData(root_item, None)
185 self.SetItemHasChildren(root_item, True)
186
187 self.__root_tooltip = self.__pat['description_gender'] + u'\n'
188 if self.__pat['deceased'] is None:
189 self.__root_tooltip += u' %s (%s)\n\n' % (
190 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()),
191 self.__pat['medical_age']
192 )
193 else:
194 template = u' %s - %s (%s)\n\n'
195 self.__root_tooltip += template % (
196 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()),
197 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()),
198 self.__pat['medical_age']
199 )
200 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n')
201 doc = self.__pat.primary_provider
202 if doc is not None:
203 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis')
204 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % (
205 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
206 doc['firstnames'],
207 doc['lastnames'],
208 doc['short_alias'],
209 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive'))
210 )
211 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
212 self.__root_tooltip += _('In case of emergency contact:') + u'\n'
213 if self.__pat['emergency_contact'] is not None:
214 self.__root_tooltip += gmTools.wrap (
215 text = u'%s\n' % self.__pat['emergency_contact'],
216 width = 60,
217 initial_indent = u' ',
218 subsequent_indent = u' '
219 )
220 if self.__pat['pk_emergency_contact'] is not None:
221 contact = self.__pat.emergency_contact_in_database
222 self.__root_tooltip += u' %s\n' % contact['description_gender']
223 self.__root_tooltip = self.__root_tooltip.strip('\n')
224 if self.__root_tooltip == u'':
225 self.__root_tooltip = u' '
226
227 return root_item
228
230 """Displays information for the selected tree node."""
231
232 if self.__details_display is None:
233 self.__img_display.clear()
234 return
235
236 if self.__curr_node is None:
237 self.__img_display.clear()
238 return
239
240 node_data = self.GetPyData(self.__curr_node)
241 doc_folder = self.__pat.get_document_folder()
242
243 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
244 self.__enable_display_mode_selection(True)
245 if self.__details_display_mode == u'details':
246 txt = node_data.format(left_margin=1, patient = self.__pat)
247 else:
248 txt = node_data.format_as_journal(left_margin = 1)
249
250 self.__img_display.refresh (
251 document_folder = doc_folder,
252 episodes = [ epi['pk_episode'] for epi in node_data.episodes ]
253 )
254
255 elif isinstance(node_data, type({})):
256 self.__enable_display_mode_selection(False)
257
258 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description']
259 self.__img_display.clear()
260
261 elif isinstance(node_data, gmEMRStructItems.cEpisode):
262 self.__enable_display_mode_selection(True)
263 if self.__details_display_mode == u'details':
264 txt = node_data.format(left_margin = 1, patient = self.__pat)
265 else:
266 txt = node_data.format_as_journal(left_margin = 1)
267 self.__img_display.refresh (
268 document_folder = doc_folder,
269 episodes = [node_data['pk_episode']]
270 )
271
272 elif isinstance(node_data, gmEMRStructItems.cEncounter):
273 self.__enable_display_mode_selection(False)
274 epi = self.GetPyData(self.GetItemParent(self.__curr_node))
275 txt = node_data.format (
276 episodes = [epi['pk_episode']],
277 with_soap = True,
278 left_margin = 1,
279 patient = self.__pat,
280 with_co_encountlet_hints = True
281 )
282 self.__img_display.refresh (
283 document_folder = doc_folder,
284 episodes = [epi['pk_episode']],
285 encounter = node_data['pk_encounter']
286 )
287
288
289 else:
290 self.__enable_display_mode_selection(False)
291 emr = self.__pat.get_emr()
292 txt = emr.format_summary(dob = self.__pat['dob'])
293 self.__img_display.clear()
294
295 self.__details_display.Clear()
296 self.__details_display.WriteText(txt)
297 self.__details_display.ShowPosition(0)
298
300
301
302 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
303
304 menu_id = wx.NewId()
305 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details')))
306 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode)
307
308 menu_id = wx.NewId()
309 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete')))
310 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode)
311
312 menu_id = wx.NewId()
313 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote')))
314 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue)
315
316 menu_id = wx.NewId()
317 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters')))
318 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters)
319
320
321 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
322
323 menu_id = wx.NewId()
324 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode')))
325 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode)
326
327 menu_id = wx.NewId()
328 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details')))
329 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details)
330
331 item = self.__enc_context_popup.Append(-1, _('Edit progress notes'))
332 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item)
333
334 item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
335 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
336
337 item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
338 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
339
340
341 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
342
343 menu_id = wx.NewId()
344 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details')))
345 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue)
346
347 menu_id = wx.NewId()
348 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete')))
349 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue)
350
351 self.__issue_context_popup.AppendSeparator()
352
353 menu_id = wx.NewId()
354 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level')))
355 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level)
356
357
358
359
360 self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
361
362 menu_id = wx.NewId()
363 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue')))
364 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue)
365
366 item = self.__root_context_popup.Append(-1, _('Create episode'))
367 self.Bind(wx.EVT_MENU, self.__create_episode, item)
368
369 menu_id = wx.NewId()
370 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies')))
371 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy)
372
373 menu_id = wx.NewId()
374 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history')))
375 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history)
376
377 menu_id = wx.NewId()
378 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations')))
379 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays)
380
381 menu_id = wx.NewId()
382 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation')))
383 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation)
384
385 menu_id = wx.NewId()
386 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures')))
387 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures)
388
389 menu_id = wx.NewId()
390 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations')))
391 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations)
392
393 self.__root_context_popup.AppendSeparator()
394
395
396 expand_menu = wx.Menu()
397 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu)
398
399 menu_id = wx.NewId()
400 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level')))
401 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level)
402
403 menu_id = wx.NewId()
404 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level')))
405 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level)
406
407 menu_id = wx.NewId()
408 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level')))
409 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
410
411 - def __handle_root_context(self, pos=wx.DefaultPosition):
412 self.PopupMenu(self.__root_context_popup, pos)
413
414 - def __handle_issue_context(self, pos=wx.DefaultPosition):
415
416 self.PopupMenu(self.__issue_context_popup, pos)
417
418 - def __handle_episode_context(self, pos=wx.DefaultPosition):
419
420 self.PopupMenu(self.__epi_context_popup, pos)
421
422 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
423 self.PopupMenu(self.__enc_context_popup, pos)
424
425
426
435
438
442
444 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
445 parent = self,
446 id = -1,
447 caption = _('Deleting episode'),
448 button_defs = [
449 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
450 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
451 ],
452 question = _(
453 'Are you sure you want to delete this episode ?\n'
454 '\n'
455 ' "%s"\n'
456 ) % self.__curr_node_data['description']
457 )
458 result = dlg.ShowModal()
459 if result != wx.ID_YES:
460 return
461
462 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
463 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
464
466 self.DeleteChildren(episode_node)
467
468 emr = self.__pat.emr
469 epi = self.GetPyData(episode_node)
470 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
471 if len(encounters) == 0:
472 self.SetItemHasChildren(episode_node, False)
473 return
474
475 self.SetItemHasChildren(episode_node, True)
476
477 for enc in encounters:
478 label = u'%s: %s' % (
479 enc['started'].strftime('%Y-%m-%d'),
480 gmTools.unwrap (
481 gmTools.coalesce (
482 gmTools.coalesce (
483 gmTools.coalesce (
484 enc.get_latest_soap (
485 soap_cat = 'a',
486 episode = epi['pk_episode']
487 ),
488 enc['assessment_of_encounter']
489 ),
490 enc['reason_for_encounter']
491 ),
492 enc['l10n_type']
493 ),
494 max_length = 40
495 )
496 )
497 encounter_node = self.AppendItem(episode_node, label)
498 self.SetItemPyData(encounter_node, enc)
499
500 self.SetItemHasChildren(encounter_node, False)
501
502 self.SortChildren(episode_node)
503
504
505
516
518 encounter = self.GetPyData(self.__curr_node)
519 node_parent = self.GetItemParent(self.__curr_node)
520 episode = self.GetPyData(node_parent)
521
522 gmNarrativeWidgets.manage_progress_notes (
523 parent = self,
524 encounters = [encounter['pk_encounter']],
525 episodes = [episode['pk_episode']]
526 )
527
532
534
535 node_parent = self.GetItemParent(self.__curr_node)
536 owning_episode = self.GetPyData(node_parent)
537
538 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
539 self,
540 -1,
541 episode = owning_episode,
542 encounter = self.__curr_node_data
543 )
544
545 result = episode_selector.ShowModal()
546 episode_selector.Destroy()
547
548 if result == wx.ID_YES:
549 self.__populate_tree()
550
551
552
555
557 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
558 parent = self,
559 id = -1,
560 caption = _('Deleting health issue'),
561 button_defs = [
562 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
563 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
564 ],
565 question = _(
566 'Are you sure you want to delete this health issue ?\n'
567 '\n'
568 ' "%s"\n'
569 ) % self.__curr_node_data['description']
570 )
571 result = dlg.ShowModal()
572 if result != wx.ID_YES:
573 dlg.Destroy()
574 return
575
576 dlg.Destroy()
577
578 try:
579 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data)
580 except gmExceptions.DatabaseObjectInUseError:
581 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
582
584
585 if not self.__curr_node.IsOk():
586 return
587
588 self.Expand(self.__curr_node)
589
590 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
591 while epi.IsOk():
592 self.Expand(epi)
593 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
594
596 self.DeleteChildren(issue_node)
597
598 emr = self.__pat.emr
599 issue = self.GetPyData(issue_node)
600 episodes = emr.get_episodes(issues = [issue['pk_health_issue']])
601 if len(episodes) == 0:
602 self.SetItemHasChildren(issue_node, False)
603 return
604
605 self.SetItemHasChildren(issue_node, True)
606
607 for episode in episodes:
608 episode_node = self.AppendItem(issue_node, episode['description'])
609 self.SetItemPyData(episode_node, episode)
610
611 self.SetItemHasChildren(episode_node, True)
612
613 self.SortChildren(issue_node)
614
616 self.DeleteChildren(fake_issue_node)
617
618 emr = self.__pat.emr
619 episodes = emr.unlinked_episodes
620 if len(episodes) == 0:
621 self.SetItemHasChildren(fake_issue_node, False)
622 return
623
624 self.SetItemHasChildren(fake_issue_node, True)
625
626 for episode in episodes:
627 episode_node = self.AppendItem(fake_issue_node, episode['description'])
628 self.SetItemPyData(episode_node, episode)
629 if episode['episode_open']:
630 self.SetItemBold(fake_issue_node, True)
631
632 self.SetItemHasChildren(episode_node, True)
633
634 self.SortChildren(fake_issue_node)
635
636
637
640
643
651
654
657
660
663
666
668
669 root_item = self.GetRootItem()
670
671 if not root_item.IsOk():
672 return
673
674 self.Expand(root_item)
675
676
677 issue, issue_cookie = self.GetFirstChild(root_item)
678 while issue.IsOk():
679 self.Collapse(issue)
680 epi, epi_cookie = self.GetFirstChild(issue)
681 while epi.IsOk():
682 self.Collapse(epi)
683 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
684 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
685
687
688 root_item = self.GetRootItem()
689
690 if not root_item.IsOk():
691 return
692
693 self.Expand(root_item)
694
695
696 issue, issue_cookie = self.GetFirstChild(root_item)
697 while issue.IsOk():
698 self.Expand(issue)
699 epi, epi_cookie = self.GetFirstChild(issue)
700 while epi.IsOk():
701 self.Collapse(epi)
702 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
703 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
704
706
707 root_item = self.GetRootItem()
708
709 if not root_item.IsOk():
710 return
711
712 self.Expand(root_item)
713
714
715 issue, issue_cookie = self.GetFirstChild(root_item)
716 while issue.IsOk():
717 self.Expand(issue)
718 epi, epi_cookie = self.GetFirstChild(issue)
719 while epi.IsOk():
720 self.Expand(epi)
721 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
722 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
723
730
732 root_node = self.GetRootItem()
733 self.DeleteChildren(root_node)
734
735 issues = [{
736 'description': _('Unattributed episodes'),
737 'has_open_episode': False,
738 'pk_health_issue': None
739 }]
740
741 emr = self.__pat.emr
742 issues.extend(emr.health_issues)
743
744 for issue in issues:
745 issue_node = self.AppendItem(root_node, issue['description'])
746 self.SetItemBold(issue_node, issue['has_open_episode'])
747 self.SetItemPyData(issue_node, issue)
748
749 self.SetItemHasChildren(issue_node, True)
750
751 self.SetItemHasChildren(root_node, (len(issues) != 0))
752 self.SortChildren(root_node)
753
754
755
757 wx.CallAfter(self.__update_text_for_selected_node)
758
760 wx.CallAfter(self.__populate_tree)
761
763 wx.CallAfter(self.__populate_tree)
764
766 if not self.__pat.connected:
767 return
768
769 event.Skip()
770
771 node = event.GetItem()
772 if node == self.GetRootItem():
773 self.__expand_root_node()
774 return
775
776 node_data = self.GetPyData(node)
777
778 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
779 self.__expand_issue_node(issue_node = node)
780 return
781
782 if isinstance(node_data, gmEMRStructItems.cEpisode):
783 self.__expand_episode_node(episode_node = node)
784 return
785
786
787 if type(node_data) == type({}):
788 self.__expand_pseudo_issue_node(fake_issue_node = node)
789 return
790
791
792
793
795 sel_item = event.GetItem()
796 self.__curr_node = sel_item
797 self.__update_text_for_selected_node()
798 return True
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
938
939
940
941
942
943
944
945
946
947
948
950 """Right button clicked: display the popup for the tree"""
951
952 node = event.GetItem()
953 self.SelectItem(node)
954 self.__curr_node_data = self.GetPyData(node)
955 self.__curr_node = node
956
957 pos = wx.DefaultPosition
958 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
959 self.__handle_issue_context(pos=pos)
960 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
961 self.__handle_episode_context(pos=pos)
962 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
963 self.__handle_encounter_context(pos=pos)
964 elif node == self.GetRootItem():
965 self.__handle_root_context()
966 elif type(self.__curr_node_data) == type({}):
967
968 pass
969 else:
970 print "error: unknown node type, no popup menu"
971 event.Skip()
972
974 """Used in sorting items.
975
976 -1: 1 < 2
977 0: 1 = 2
978 1: 1 > 2
979 """
980
981
982 if not node1:
983 _log.debug('invalid node 1')
984 return 0
985 if not node2:
986 _log.debug('invalid node 2')
987 return 0
988
989 if not node1.IsOk():
990 _log.debug('invalid node 1')
991 return 0
992 if not node2.IsOk():
993 _log.debug('invalid node 2')
994 return 0
995
996 item1 = self.GetPyData(node1)
997 item2 = self.GetPyData(node2)
998
999
1000 if isinstance(item1, type({})):
1001 return -1
1002 if isinstance(item2, type({})):
1003 return 1
1004
1005
1006 if isinstance(item1, gmEMRStructItems.cEncounter):
1007 if item1['started'] == item2['started']:
1008 return 0
1009 if item1['started'] > item2['started']:
1010 return -1
1011 return 1
1012
1013
1014 if isinstance(item1, gmEMRStructItems.cEpisode):
1015 start1 = item1.best_guess_start_date
1016 start2 = item2.best_guess_start_date
1017 if start1 == start2:
1018 return 0
1019 if start1 < start2:
1020 return -1
1021 return 1
1022
1023
1024 if isinstance(item1, gmEMRStructItems.cHealthIssue):
1025
1026
1027 if item1['grouping'] is None:
1028 if item2['grouping'] is not None:
1029 return 1
1030
1031
1032 if item1['grouping'] is not None:
1033 if item2['grouping'] is None:
1034 return -1
1035
1036
1037 if (item1['grouping'] is None) and (item2['grouping'] is None):
1038 if item1['description'].lower() < item2['description'].lower():
1039 return -1
1040 if item1['description'].lower() > item2['description'].lower():
1041 return 1
1042 return 0
1043
1044
1045 if item1['grouping'] < item2['grouping']:
1046 return -1
1047
1048 if item1['grouping'] > item2['grouping']:
1049 return 1
1050
1051 if item1['description'].lower() < item2['description'].lower():
1052 return -1
1053
1054 if item1['description'].lower() > item2['description'].lower():
1055 return 1
1056
1057 return 0
1058
1059 _log.error('unknown item type during sorting EMR tree:')
1060 _log.error('item1: %s', type(item1))
1061 _log.error('item2: %s', type(item2))
1062
1063 return 0
1064
1065
1066
1068 return self.__details_display_mode
1069
1071 if mode not in [u'details', u'journal']:
1072 raise ValueError('details display mode must be one of "details", "journal"')
1073 if self.__details_display_mode == mode:
1074 return
1075 self.__details_display_mode = mode
1076 self.__update_text_for_selected_node()
1077
1078 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1079
1080 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
1081
1095
1096 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
1097
1099 """A splitter window holding an EMR tree.
1100
1101 The left hand side displays a scrollable EMR tree while
1102 on the right details for selected items are displayed.
1103
1104 Expects to be put into a Notebook.
1105 """
1112
1114 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1115 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1116 return True
1117
1118
1119
1121 self._pnl_emr_tree._emr_tree.clear_tree()
1122 return True
1123
1125 wx.CallAfter(self.__on_post_patient_selection)
1126 return True
1127
1129 if self.GetParent().GetCurrentPage() != self:
1130 return True
1131 self.repopulate_ui()
1132
1135
1138
1139
1140
1142 """Fills UI with data."""
1143 self._pnl_emr_tree.repopulate_ui()
1144 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True)
1145 return True
1146
1148 if enable:
1149 self._RBTN_details.Enable(True)
1150 self._RBTN_journal.Enable(True)
1151 return
1152 self._RBTN_details.Enable(False)
1153 self._RBTN_journal.Enable(False)
1154
1157 wx.Panel.__init__(self, *args, **kwargs)
1158
1159 self.__do_layout()
1160 self.__register_events()
1161
1163 self.__journal = wx.TextCtrl (
1164 self,
1165 -1,
1166 _('No EMR data loaded.'),
1167 style = wx.TE_MULTILINE | wx.TE_READONLY
1168 )
1169 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
1170
1171 szr_outer = wx.BoxSizer(wx.VERTICAL)
1172 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0)
1173
1174 self.SetAutoLayout(1)
1175 self.SetSizer(szr_outer)
1176 szr_outer.Fit(self)
1177 szr_outer.SetSizeHints(self)
1178 self.Layout()
1179
1181 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1182
1184 """Expects to be in a Notebook."""
1185 if self.GetParent().GetCurrentPage() == self:
1186 self.repopulate_ui()
1187 return True
1188
1189
1190
1192 txt = StringIO.StringIO()
1193 exporter = gmPatientExporter.cEMRJournalExporter()
1194
1195
1196 try:
1197 exporter.export(txt)
1198 self.__journal.SetValue(txt.getvalue())
1199 except ValueError:
1200 _log.exception('cannot get EMR journal')
1201 self.__journal.SetValue (_(
1202 'An error occurred while retrieving the EMR\n'
1203 'in journal form for the active patient.\n\n'
1204 'Please check the log file for details.'
1205 ))
1206 txt.close()
1207 self.__journal.ShowPosition(self.__journal.GetLastPosition())
1208 return True
1209
1210
1211
1212 if __name__ == '__main__':
1213
1214 _log.info("starting emr browser...")
1215
1216 try:
1217
1218 patient = gmPersonSearch.ask_for_patient()
1219 if patient is None:
1220 print "No patient. Exiting gracefully..."
1221 sys.exit(0)
1222 gmPatSearchWidgets.set_active_patient(patient = patient)
1223
1224
1225 application = wx.PyWidgetTester(size=(800,600))
1226 emr_browser = cEMRBrowserPanel(application.frame, -1)
1227 emr_browser.refresh_tree()
1228
1229 application.frame.Show(True)
1230 application.MainLoop()
1231
1232
1233 if patient is not None:
1234 try:
1235 patient.cleanup()
1236 except:
1237 print "error cleaning up patient"
1238 except StandardError:
1239 _log.exception("unhandled exception caught !")
1240
1241 raise
1242
1243 _log.info("closing emr browser...")
1244
1245
1246