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.__soap_display = None
103 self.__soap_display_mode = u'details'
104 self.__img_display = None
105 self.__cb__enable_display_mode_selection = lambda x:x
106 self.__cb__select_edit_mode = lambda x:x
107 self.__cb__add_soap_editor = lambda x:x
108 self.__pat = gmPerson.gmCurrentPatient()
109 self.__curr_node = None
110
111 self._old_cursor_pos = None
112
113 self.__make_popup_menus()
114 self.__register_events()
115
116
117
119 if not self.__pat.connected:
120 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),)
121 return False
122
123 if not self.__populate_tree():
124 return False
125
126 return True
127
129 return self.__soap_display
130
133
134 soap_display = property(_get_soap_display, _set_soap_display)
135
137 return self.__img_display
138
141
142 image_display = property(_get_image_display, _set_image_display)
143
145 if not callable(callback):
146 raise ValueError('callback [%s] not callable' % callback)
147 self.__cb__enable_display_mode_selection = callback
148
150 if callback is None:
151 callback = lambda x:x
152 if not callable(callback):
153 raise ValueError('edit mode selector [%s] not callable' % callback)
154 self.__cb__select_edit_mode = callback
155
156 edit_mode_selector = property(lambda x:x, _set_edit_mode_selector)
157
159 if callback is None:
160 callback = lambda x:x
161 if not callable(callback):
162 raise ValueError('soap editor adder [%s] not callable' % callback)
163 self.__cb__add_soap_editor = callback
164
165 soap_editor_adder = property(lambda x:x, _set_soap_editor_adder)
166
167
168
169
171 """Configures enabled event signals."""
172 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected)
173 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked)
174 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
175
176
177
178 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip)
179
180 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
181 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db)
182 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
183 gmDispatcher.connect(signal = 'family_history_mod_db', receiver = self._on_issue_mod_db)
184
186 self.DeleteAllItems()
187
189 """Updates EMR browser data."""
190
191
192
193 if not self.__pat.connected:
194 return
195
196 wx.BeginBusyCursor()
197
198
199 root_item = self.__populate_root_node()
200
201 self.__curr_node = root_item
202 self.SelectItem(root_item)
203 self.Expand(root_item)
204 self.__update_text_for_selected_node()
205
206 wx.EndBusyCursor()
207 return True
208
210
211 self.DeleteAllItems()
212
213 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
214 self.SetItemPyData(root_item, None)
215 self.SetItemHasChildren(root_item, True)
216
217 self.__root_tooltip = self.__pat['description_gender'] + u'\n'
218 if self.__pat['deceased'] is None:
219 self.__root_tooltip += u' %s (%s)\n\n' % (
220 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()),
221 self.__pat['medical_age']
222 )
223 else:
224 template = u' %s - %s (%s)\n\n'
225 self.__root_tooltip += template % (
226 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()),
227 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()),
228 self.__pat['medical_age']
229 )
230 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n')
231 doc = self.__pat.primary_provider
232 if doc is not None:
233 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis')
234 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % (
235 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
236 doc['firstnames'],
237 doc['lastnames'],
238 doc['short_alias'],
239 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive'))
240 )
241 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
242 self.__root_tooltip += _('In case of emergency contact:') + u'\n'
243 if self.__pat['emergency_contact'] is not None:
244 self.__root_tooltip += gmTools.wrap (
245 text = u'%s\n' % self.__pat['emergency_contact'],
246 width = 60,
247 initial_indent = u' ',
248 subsequent_indent = u' '
249 )
250 if self.__pat['pk_emergency_contact'] is not None:
251 contact = self.__pat.emergency_contact_in_database
252 self.__root_tooltip += u' %s\n' % contact['description_gender']
253 self.__root_tooltip = self.__root_tooltip.strip('\n')
254 if self.__root_tooltip == u'':
255 self.__root_tooltip = u' '
256
257 return root_item
258
260 """Displays information for the selected tree node."""
261
262 if self.__soap_display is None:
263 self.__img_display.clear()
264 return
265
266 if self.__curr_node is None:
267 self.__img_display.clear()
268 return
269
270 node_data = self.GetPyData(self.__curr_node)
271 doc_folder = self.__pat.get_document_folder()
272
273 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
274 self.__cb__enable_display_mode_selection(True)
275 if self.__soap_display_mode == u'details':
276 txt = node_data.format(left_margin=1, patient = self.__pat)
277 else:
278 txt = node_data.format_as_journal(left_margin = 1)
279
280 self.__img_display.refresh (
281 document_folder = doc_folder,
282 episodes = [ epi['pk_episode'] for epi in node_data.episodes ]
283 )
284
285 elif isinstance(node_data, type({})):
286 self.__cb__enable_display_mode_selection(False)
287
288 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description']
289 self.__img_display.clear()
290
291 elif isinstance(node_data, gmEMRStructItems.cEpisode):
292 self.__cb__enable_display_mode_selection(True)
293 if self.__soap_display_mode == u'details':
294 txt = node_data.format(left_margin = 1, patient = self.__pat)
295 else:
296 txt = node_data.format_as_journal(left_margin = 1)
297 self.__img_display.refresh (
298 document_folder = doc_folder,
299 episodes = [node_data['pk_episode']]
300 )
301
302 elif isinstance(node_data, gmEMRStructItems.cEncounter):
303 self.__cb__enable_display_mode_selection(False)
304 epi = self.GetPyData(self.GetItemParent(self.__curr_node))
305 txt = node_data.format (
306 episodes = [epi['pk_episode']],
307 with_soap = True,
308 left_margin = 1,
309 patient = self.__pat,
310 with_co_encountlet_hints = True
311 )
312 self.__img_display.refresh (
313 document_folder = doc_folder,
314 episodes = [epi['pk_episode']],
315 encounter = node_data['pk_encounter']
316 )
317
318
319 else:
320 self.__cb__enable_display_mode_selection(True)
321 if self.__soap_display_mode == u'details':
322 emr = self.__pat.get_emr()
323 txt = emr.format_summary(dob = self.__pat['dob'])
324 else:
325 txt = self.__pat.emr.format_as_journal(left_margin = 1, patient = self.__pat)
326 self.__img_display.clear()
327
328 self.__soap_display.Clear()
329 self.__soap_display.WriteText(txt)
330 self.__soap_display.ShowPosition(0)
331
333
334
335 self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
336
337 menu_id = wx.NewId()
338 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue')))
339 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue)
340
341 item = self.__root_context_popup.Append(-1, _('Create episode'))
342 self.Bind(wx.EVT_MENU, self.__create_episode, item)
343
344 item = self.__root_context_popup.Append(-1, _('Create progress note'))
345 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
346
347 menu_id = wx.NewId()
348 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies')))
349 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy)
350
351 menu_id = wx.NewId()
352 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history')))
353 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history)
354
355 menu_id = wx.NewId()
356 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations')))
357 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays)
358
359 menu_id = wx.NewId()
360 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation')))
361 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation)
362
363 menu_id = wx.NewId()
364 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures')))
365 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures)
366
367 menu_id = wx.NewId()
368 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations')))
369 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations)
370
371 self.__root_context_popup.AppendSeparator()
372
373
374 expand_menu = wx.Menu()
375 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu)
376
377 menu_id = wx.NewId()
378 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level')))
379 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level)
380
381 menu_id = wx.NewId()
382 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level')))
383 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level)
384
385 menu_id = wx.NewId()
386 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level')))
387 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
388
389
390 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
391
392 menu_id = wx.NewId()
393 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details')))
394 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue)
395
396 menu_id = wx.NewId()
397 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete')))
398 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue)
399
400 self.__issue_context_popup.AppendSeparator()
401
402 menu_id = wx.NewId()
403 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level')))
404 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level)
405
406
407
408 item = self.__issue_context_popup.Append(-1, _('Create progress note'))
409 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
410
411
412 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
413
414 menu_id = wx.NewId()
415 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details')))
416 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode)
417
418 menu_id = wx.NewId()
419 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete')))
420 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode)
421
422 menu_id = wx.NewId()
423 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote')))
424 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue)
425
426 item = self.__epi_context_popup.Append(-1, _('Create progress note'))
427 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
428
429 menu_id = wx.NewId()
430 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters')))
431 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters)
432
433
434 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
435
436 menu_id = wx.NewId()
437 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode')))
438 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode)
439
440 menu_id = wx.NewId()
441 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details')))
442 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details)
443
444
445
446
447
448 item = self.__enc_context_popup.Append(-1, _('Edit progress notes'))
449 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item)
450
451 item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
452 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
453
454 item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
455 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
456
457 - def __handle_root_context(self, pos=wx.DefaultPosition):
458 self.PopupMenu(self.__root_context_popup, pos)
459
460 - def __handle_issue_context(self, pos=wx.DefaultPosition):
461
462 self.PopupMenu(self.__issue_context_popup, pos)
463
464 - def __handle_episode_context(self, pos=wx.DefaultPosition):
465
466 self.PopupMenu(self.__epi_context_popup, pos)
467
468 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
469 self.PopupMenu(self.__enc_context_popup, pos)
470
471
472
481
484
488
490 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
491 parent = self,
492 id = -1,
493 caption = _('Deleting episode'),
494 button_defs = [
495 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
496 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
497 ],
498 question = _(
499 'Are you sure you want to delete this episode ?\n'
500 '\n'
501 ' "%s"\n'
502 ) % self.__curr_node_data['description']
503 )
504 result = dlg.ShowModal()
505 if result != wx.ID_YES:
506 return
507
508 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
509 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
510
512 self.DeleteChildren(episode_node)
513
514 emr = self.__pat.emr
515 epi = self.GetPyData(episode_node)
516 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
517 if len(encounters) == 0:
518 self.SetItemHasChildren(episode_node, False)
519 return
520
521 self.SetItemHasChildren(episode_node, True)
522
523 for enc in encounters:
524 label = u'%s: %s' % (
525 enc['started'].strftime('%Y-%m-%d'),
526 gmTools.unwrap (
527 gmTools.coalesce (
528 gmTools.coalesce (
529 gmTools.coalesce (
530 enc.get_latest_soap (
531 soap_cat = 'a',
532 episode = epi['pk_episode']
533 ),
534 enc['assessment_of_encounter']
535 ),
536 enc['reason_for_encounter']
537 ),
538 enc['l10n_type']
539 ),
540 max_length = 40
541 )
542 )
543 encounter_node = self.AppendItem(episode_node, label)
544 self.SetItemPyData(encounter_node, enc)
545
546 self.SetItemHasChildren(encounter_node, False)
547
548 self.SortChildren(episode_node)
549
550
551
562
564 encounter = self.GetPyData(self.__curr_node)
565 node_parent = self.GetItemParent(self.__curr_node)
566 episode = self.GetPyData(node_parent)
567
568 gmNarrativeWidgets.manage_progress_notes (
569 parent = self,
570 encounters = [encounter['pk_encounter']],
571 episodes = [episode['pk_episode']]
572 )
573
578
580
581 node_parent = self.GetItemParent(self.__curr_node)
582 owning_episode = self.GetPyData(node_parent)
583
584 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
585 self,
586 -1,
587 episode = owning_episode,
588 encounter = self.__curr_node_data
589 )
590
591 result = episode_selector.ShowModal()
592 episode_selector.Destroy()
593
594 if result == wx.ID_YES:
595 self.__populate_tree()
596
597
598
601
603 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
604 parent = self,
605 id = -1,
606 caption = _('Deleting health issue'),
607 button_defs = [
608 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
609 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
610 ],
611 question = _(
612 'Are you sure you want to delete this health issue ?\n'
613 '\n'
614 ' "%s"\n'
615 ) % self.__curr_node_data['description']
616 )
617 result = dlg.ShowModal()
618 if result != wx.ID_YES:
619 dlg.Destroy()
620 return
621
622 dlg.Destroy()
623
624 try:
625 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data)
626 except gmExceptions.DatabaseObjectInUseError:
627 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
628
630
631 if not self.__curr_node.IsOk():
632 return
633
634 self.Expand(self.__curr_node)
635
636 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
637 while epi.IsOk():
638 self.Expand(epi)
639 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
640
642 self.DeleteChildren(issue_node)
643
644 emr = self.__pat.emr
645 issue = self.GetPyData(issue_node)
646 episodes = emr.get_episodes(issues = [issue['pk_health_issue']])
647 if len(episodes) == 0:
648 self.SetItemHasChildren(issue_node, False)
649 return
650
651 self.SetItemHasChildren(issue_node, True)
652
653 for episode in episodes:
654 episode_node = self.AppendItem(issue_node, episode['description'])
655 self.SetItemPyData(episode_node, episode)
656
657 self.SetItemHasChildren(episode_node, True)
658
659 self.SortChildren(issue_node)
660
662 self.DeleteChildren(fake_issue_node)
663
664 emr = self.__pat.emr
665 episodes = emr.unlinked_episodes
666 if len(episodes) == 0:
667 self.SetItemHasChildren(fake_issue_node, False)
668 return
669
670 self.SetItemHasChildren(fake_issue_node, True)
671
672 for episode in episodes:
673 episode_node = self.AppendItem(fake_issue_node, episode['description'])
674 self.SetItemPyData(episode_node, episode)
675 if episode['episode_open']:
676 self.SetItemBold(fake_issue_node, True)
677
678 self.SetItemHasChildren(episode_node, True)
679
680 self.SortChildren(fake_issue_node)
681
682
683
686
689
691 self.__cb__select_edit_mode(True)
692 self.__cb__add_soap_editor(problem = self.__curr_node_data, allow_same_problem = False)
693
701
704
707
710
713
716
718
719 root_item = self.GetRootItem()
720
721 if not root_item.IsOk():
722 return
723
724 self.Expand(root_item)
725
726
727 issue, issue_cookie = self.GetFirstChild(root_item)
728 while issue.IsOk():
729 self.Collapse(issue)
730 epi, epi_cookie = self.GetFirstChild(issue)
731 while epi.IsOk():
732 self.Collapse(epi)
733 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
734 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
735
737
738 root_item = self.GetRootItem()
739
740 if not root_item.IsOk():
741 return
742
743 self.Expand(root_item)
744
745
746 issue, issue_cookie = self.GetFirstChild(root_item)
747 while issue.IsOk():
748 self.Expand(issue)
749 epi, epi_cookie = self.GetFirstChild(issue)
750 while epi.IsOk():
751 self.Collapse(epi)
752 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
753 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
754
756
757 root_item = self.GetRootItem()
758
759 if not root_item.IsOk():
760 return
761
762 self.Expand(root_item)
763
764
765 issue, issue_cookie = self.GetFirstChild(root_item)
766 while issue.IsOk():
767 self.Expand(issue)
768 epi, epi_cookie = self.GetFirstChild(issue)
769 while epi.IsOk():
770 self.Expand(epi)
771 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
772 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
773
780
782 root_node = self.GetRootItem()
783 self.DeleteChildren(root_node)
784
785 issues = [{
786 'description': _('Unattributed episodes'),
787 'has_open_episode': False,
788 'pk_health_issue': None
789 }]
790
791 emr = self.__pat.emr
792 issues.extend(emr.health_issues)
793
794 for issue in issues:
795 issue_node = self.AppendItem(root_node, issue['description'])
796 self.SetItemBold(issue_node, issue['has_open_episode'])
797 self.SetItemPyData(issue_node, issue)
798
799 self.SetItemHasChildren(issue_node, True)
800
801 self.SetItemHasChildren(root_node, (len(issues) != 0))
802 self.SortChildren(root_node)
803
804
805
807 wx.CallAfter(self.__update_text_for_selected_node)
808
810 wx.CallAfter(self.__populate_tree)
811
813 wx.CallAfter(self.__populate_tree)
814
816 if not self.__pat.connected:
817 return
818
819 event.Skip()
820
821 node = event.GetItem()
822 if node == self.GetRootItem():
823 self.__expand_root_node()
824 return
825
826 node_data = self.GetPyData(node)
827
828 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
829 self.__expand_issue_node(issue_node = node)
830 return
831
832 if isinstance(node_data, gmEMRStructItems.cEpisode):
833 self.__expand_episode_node(episode_node = node)
834 return
835
836
837 if type(node_data) == type({}):
838 self.__expand_pseudo_issue_node(fake_issue_node = node)
839 return
840
841
842
843
845 sel_item = event.GetItem()
846 self.__curr_node = sel_item
847 self.__update_text_for_selected_node()
848 return True
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
988
989
990
991
992
993
994
995
996
997
998
1000 """Right button clicked: display the popup for the tree"""
1001
1002 node = event.GetItem()
1003 self.SelectItem(node)
1004 self.__curr_node_data = self.GetPyData(node)
1005 self.__curr_node = node
1006
1007 pos = wx.DefaultPosition
1008 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
1009 self.__handle_issue_context(pos=pos)
1010 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
1011 self.__handle_episode_context(pos=pos)
1012 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
1013 self.__handle_encounter_context(pos=pos)
1014 elif node == self.GetRootItem():
1015 self.__handle_root_context()
1016 elif type(self.__curr_node_data) == type({}):
1017
1018 pass
1019 else:
1020 print "error: unknown node type, no popup menu"
1021 event.Skip()
1022
1024 """Used in sorting items.
1025
1026 -1: 1 < 2
1027 0: 1 = 2
1028 1: 1 > 2
1029 """
1030
1031
1032 if not node1:
1033 _log.debug('invalid node 1')
1034 return 0
1035 if not node2:
1036 _log.debug('invalid node 2')
1037 return 0
1038
1039 if not node1.IsOk():
1040 _log.debug('invalid node 1')
1041 return 0
1042 if not node2.IsOk():
1043 _log.debug('invalid node 2')
1044 return 0
1045
1046 item1 = self.GetPyData(node1)
1047 item2 = self.GetPyData(node2)
1048
1049
1050 if isinstance(item1, type({})):
1051 return -1
1052 if isinstance(item2, type({})):
1053 return 1
1054
1055
1056 if isinstance(item1, gmEMRStructItems.cEncounter):
1057 if item1['started'] == item2['started']:
1058 return 0
1059 if item1['started'] > item2['started']:
1060 return -1
1061 return 1
1062
1063
1064 if isinstance(item1, gmEMRStructItems.cEpisode):
1065 start1 = item1.best_guess_start_date
1066 start2 = item2.best_guess_start_date
1067 if start1 == start2:
1068 return 0
1069 if start1 < start2:
1070 return -1
1071 return 1
1072
1073
1074 if isinstance(item1, gmEMRStructItems.cHealthIssue):
1075
1076
1077 if item1['grouping'] is None:
1078 if item2['grouping'] is not None:
1079 return 1
1080
1081
1082 if item1['grouping'] is not None:
1083 if item2['grouping'] is None:
1084 return -1
1085
1086
1087 if (item1['grouping'] is None) and (item2['grouping'] is None):
1088 if item1['description'].lower() < item2['description'].lower():
1089 return -1
1090 if item1['description'].lower() > item2['description'].lower():
1091 return 1
1092 return 0
1093
1094
1095 if item1['grouping'] < item2['grouping']:
1096 return -1
1097
1098 if item1['grouping'] > item2['grouping']:
1099 return 1
1100
1101 if item1['description'].lower() < item2['description'].lower():
1102 return -1
1103
1104 if item1['description'].lower() > item2['description'].lower():
1105 return 1
1106
1107 return 0
1108
1109 _log.error('unknown item type during sorting EMR tree:')
1110 _log.error('item1: %s', type(item1))
1111 _log.error('item2: %s', type(item2))
1112
1113 return 0
1114
1115
1116
1118 return self.__soap_display_mode
1119
1121 if mode not in [u'details', u'journal']:
1122 raise ValueError('details display mode must be one of "details", "journal"')
1123 if self.__soap_display_mode == mode:
1124 return
1125 self.__soap_display_mode = mode
1126 self.__update_text_for_selected_node()
1127
1128 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1129
1130 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
1131
1145
1146 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
1147
1149 """A splitter window holding an EMR tree.
1150
1151 The left hand side displays a scrollable EMR tree while
1152 on the right details for selected items are displayed.
1153
1154 Expects to be put into a Notebook.
1155 """
1166
1168 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1169 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1170 return True
1171
1173 return self.__editing
1174
1176 self.__editing = editing
1177 self.enable_display_mode_selection(enable = not self.__editing)
1178 if self.__editing:
1179 self._BTN_switch_browse_edit.SetLabel(_('&Browse'))
1180 self._PNL_browse.Hide()
1181 self._PNL_visual_soap.Hide()
1182 self._PNL_edit.Show()
1183 else:
1184 self._BTN_switch_browse_edit.SetLabel(_('&Edit'))
1185 self._PNL_edit.Hide()
1186 self._PNL_visual_soap.Show()
1187 self._PNL_browse.Show()
1188 self._PNL_right_side.GetSizer().Layout()
1189
1190 editing = property(_get_editing, _set_editing)
1191
1192
1193
1195 self._pnl_emr_tree._emr_tree.clear_tree()
1196 self._PNL_edit.patient = None
1197 return True
1198
1200 wx.CallAfter(self.__on_post_patient_selection)
1201 return True
1202
1204 if self.GetParent().GetCurrentPage() != self:
1205 return True
1206 self.repopulate_ui()
1207
1210
1213
1216
1217
1218
1220 """Fills UI with data."""
1221 self._pnl_emr_tree.repopulate_ui()
1222 self._PNL_edit.patient = gmPerson.gmCurrentPatient()
1223 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True)
1224 return True
1225
1227 if self.editing:
1228 enable = False
1229 if enable:
1230 self._RBTN_details.Enable(True)
1231 self._RBTN_journal.Enable(True)
1232 return
1233 self._RBTN_details.Enable(False)
1234 self._RBTN_journal.Enable(False)
1235
1237 self._PNL_edit._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1238
1241
1242
1243 from Gnumed.wxGladeWidgets import wxgEMRJournalPluginPnl
1244
1246
1251
1252
1253
1255 self._TCTRL_journal.SetValue(u'')
1256 exporter = gmPatientExporter.cEMRJournalExporter()
1257 if self._RBTN_by_encounter.GetValue():
1258 txt = StringIO.StringIO()
1259
1260
1261 try:
1262 exporter.export(txt)
1263 self._TCTRL_journal.SetValue(txt.getvalue())
1264 except ValueError:
1265 _log.exception('cannot get EMR journal')
1266 self._TCTRL_journal.SetValue (_(
1267 'An error occurred while retrieving the EMR\n'
1268 'in journal form for the active patient.\n\n'
1269 'Please check the log file for details.'
1270 ))
1271 txt.close()
1272 else:
1273 fname = exporter.export_to_file_by_mod_time()
1274 f = codecs.open(filename = fname, mode = 'rU', encoding = 'utf8', errors = 'replace')
1275 for line in f:
1276 self._TCTRL_journal.AppendText(line)
1277 f.close()
1278
1279 self._TCTRL_journal.ShowPosition(self._TCTRL_journal.GetLastPosition())
1280 return True
1281
1282
1283
1285 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1286 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1287 return True
1288
1289
1290
1292 self._TCTRL_journal.SetValue(u'')
1293 return True
1294
1296 wx.CallAfter(self.__on_post_patient_selection)
1297 return True
1298
1300 if self.GetParent().GetCurrentPage() != self:
1301 return True
1302 self.repopulate_ui()
1303
1306
1309
1310
1313 wx.Panel.__init__(self, *args, **kwargs)
1314
1315 self.__do_layout()
1316 self.__register_events()
1317
1319 self.__journal = wx.TextCtrl (
1320 self,
1321 -1,
1322 _('No EMR data loaded.'),
1323 style = wx.TE_MULTILINE | wx.TE_READONLY
1324 )
1325 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
1326
1327 szr_outer = wx.BoxSizer(wx.VERTICAL)
1328 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0)
1329
1330 self.SetAutoLayout(1)
1331 self.SetSizer(szr_outer)
1332 szr_outer.Fit(self)
1333 szr_outer.SetSizeHints(self)
1334 self.Layout()
1335
1337 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1338
1340 """Expects to be in a Notebook."""
1341 if self.GetParent().GetCurrentPage() == self:
1342 self.repopulate_ui()
1343 return True
1344
1345
1346
1348
1349 exporter = gmPatientExporter.cEMRJournalExporter()
1350 fname = exporter.export_to_file_by_mod_time()
1351 f = codecs.open(filename = fname, mode = 'rU', encoding = 'utf8', errors = 'replace')
1352 for line in f:
1353 self.__journal.AppendText(line)
1354 f.close()
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370 self.__journal.ShowPosition(self.__journal.GetLastPosition())
1371 return True
1372
1373
1374
1375 if __name__ == '__main__':
1376
1377 _log.info("starting emr browser...")
1378
1379 try:
1380
1381 patient = gmPersonSearch.ask_for_patient()
1382 if patient is None:
1383 print "No patient. Exiting gracefully..."
1384 sys.exit(0)
1385 gmPatSearchWidgets.set_active_patient(patient = patient)
1386
1387
1388 application = wx.PyWidgetTester(size=(800,600))
1389 emr_browser = cEMRBrowserPanel(application.frame, -1)
1390 emr_browser.refresh_tree()
1391
1392 application.frame.Show(True)
1393 application.MainLoop()
1394
1395
1396 if patient is not None:
1397 try:
1398 patient.cleanup()
1399 except:
1400 print "error cleaning up patient"
1401 except StandardError:
1402 _log.exception("unhandled exception caught !")
1403
1404 raise
1405
1406 _log.info("closing emr browser...")
1407
1408
1409