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 io
10 import logging
11 import datetime as pydt
12
13
14
15 import wx
16 import wx.lib.mixins.treemixin as treemixin
17
18
19
20 from Gnumed.pycommon import gmI18N
21 from Gnumed.pycommon import gmDispatcher
22 from Gnumed.pycommon import gmExceptions
23 from Gnumed.pycommon import gmTools
24 from Gnumed.pycommon import gmDateTime
25 from Gnumed.pycommon import gmLog2
26
27 from Gnumed.exporters import gmPatientExporter
28
29 from Gnumed.business import gmGenericEMRItem
30 from Gnumed.business import gmEMRStructItems
31 from Gnumed.business import gmPerson
32 from Gnumed.business import gmSOAPimporter
33 from Gnumed.business import gmPersonSearch
34 from Gnumed.business import gmSoapDefs
35 from Gnumed.business import gmClinicalRecord
36
37 from Gnumed.wxpython import gmGuiHelpers
38 from Gnumed.wxpython import gmEMRStructWidgets
39 from Gnumed.wxpython import gmEncounterWidgets
40 from Gnumed.wxpython import gmSOAPWidgets
41 from Gnumed.wxpython import gmAllergyWidgets
42 from Gnumed.wxpython import gmDemographicsWidgets
43 from Gnumed.wxpython import gmNarrativeWidgets
44 from Gnumed.wxpython import gmNarrativeWorkflows
45 from Gnumed.wxpython import gmPatSearchWidgets
46 from Gnumed.wxpython import gmVaccWidgets
47 from Gnumed.wxpython import gmFamilyHistoryWidgets
48 from Gnumed.wxpython import gmFormWidgets
49 from Gnumed.wxpython import gmTimer
50 from Gnumed.wxpython import gmHospitalStayWidgets
51 from Gnumed.wxpython import gmProcedureWidgets
52 from Gnumed.wxpython import gmGenericEMRItemWorkflows
53
54
55 _log = logging.getLogger('gm.ui')
56
57
59 """
60 Dump the patient's EMR from GUI client
61 @param parent - The parent widget
62 @type parent - A wx.Window instance
63 """
64
65 if parent is None:
66 raise TypeError('expected wx.Window instance as parent, got <None>')
67
68 pat = gmPerson.gmCurrentPatient()
69 if not pat.connected:
70 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
71 return False
72
73
74 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
75 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', pat.subdir_name)))
76 gmTools.mkdir(defdir)
77 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
78 dlg = wx.FileDialog (
79 parent = parent,
80 message = _("Save patient's EMR as..."),
81 defaultDir = defdir,
82 defaultFile = fname,
83 wildcard = wc,
84 style = wx.FD_SAVE
85 )
86 choice = dlg.ShowModal()
87 fname = dlg.GetPath()
88 dlg.DestroyLater()
89 if choice != wx.ID_OK:
90 return None
91
92 _log.debug('exporting EMR to [%s]', fname)
93
94 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
95 exporter = gmPatientExporter.cEmrExport(patient = pat)
96 exporter.set_output_file(output_file)
97 exporter.dump_constraints()
98 exporter.dump_demographic_record(True)
99 exporter.dump_clinical_record()
100 exporter.dump_med_docs()
101 output_file.close()
102
103 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
104 return fname
105
106
107 -class cEMRTree(wx.TreeCtrl, treemixin.ExpansionState):
108 """This wx.TreeCtrl derivative displays a tree view of a medical record."""
109
110
111 - def __init__(self, parent, id, *args, **kwds):
112 """Set up our specialised tree.
113 """
114 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
115 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
116
117 self.__soap_display = None
118 self.__soap_display_mode = 'details'
119 self.__img_display = None
120 self.__cb__enable_display_mode_selection = lambda x:x
121 self.__cb__select_edit_mode = lambda x:x
122 self.__cb__add_soap_editor = lambda x:x
123 self.__pat = None
124 self.__curr_node = None
125 self.__expanded_nodes = None
126
127 self.__make_popup_menus()
128 self.__register_events()
129
130
131
132
134 return self.__soap_display
135
137 self.__soap_display = soap_display
138 self.__soap_display_prop_font = soap_display.GetFont()
139 self.__soap_display_mono_font = wx.Font(self.__soap_display_prop_font.GetNativeFontInfo())
140 self.__soap_display_mono_font.SetFamily(wx.FONTFAMILY_TELETYPE)
141 self.__soap_display_mono_font.SetPointSize(self.__soap_display_prop_font.GetPointSize() - 2)
142
143 soap_display = property(_get_soap_display, _set_soap_display)
144
145
147 return self.__img_display
148
151
152 image_display = property(_get_image_display, _set_image_display)
153
154
156 if not callable(callback):
157 raise ValueError('callback [%s] not callable' % callback)
158 self.__cb__enable_display_mode_selection = callback
159
160
162 if callback is None:
163 callback = lambda x:x
164 if not callable(callback):
165 raise ValueError('edit mode selector [%s] not callable' % callback)
166 self.__cb__select_edit_mode = callback
167
168 edit_mode_selector = property(lambda x:x, _set_edit_mode_selector)
169
170
172 if callback is None:
173 callback = lambda x:x
174 if not callable(callback):
175 raise ValueError('soap editor adder [%s] not callable' % callback)
176 self.__cb__add_soap_editor = callback
177
178 soap_editor_adder = property(lambda x:x, _set_soap_editor_adder)
179
180
181
182
184 if item is None:
185 return 'invalid item'
186
187 if not item.IsOk():
188 return 'invalid item'
189
190 try:
191 node_data = self.GetItemData(item)
192 except wx.wxAssertionError:
193 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
194 _log.error('real node: %s', item)
195 _log.error('node.IsOk(): %s', item.IsOk())
196 _log.error('is root node: %s', item == self.GetRootItem())
197 _log.error('node attributes: %s', dir(item))
198 gmLog2.log_stack_trace()
199 return 'invalid item'
200
201 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
202 return 'issue::%s' % node_data['pk_health_issue']
203 if isinstance(node_data, gmEMRStructItems.cEpisode):
204 return 'episode::%s' % node_data['pk_episode']
205 if isinstance(node_data, gmEMRStructItems.cEncounter):
206 return 'encounter::%s' % node_data['pk_encounter']
207
208 if isinstance(node_data, dict):
209 return 'dummy node::%s' % self.__pat.ID
210
211 return 'root node::%s' % self.__pat.ID
212
213
214
215
217 """Configures enabled event signals."""
218 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_item_selected)
219 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_tree_item_activated)
220 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
221 self.Bind(wx.EVT_TREE_ITEM_MENU, self._on_tree_item_context_menu)
222
223
224
225 self.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tree_item_gettooltip)
226
227
228 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
229 gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_mod_db)
230 gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_issue_mod_db)
231 gmDispatcher.connect(signal = 'clin.family_history_mod_db', receiver = self._on_issue_mod_db)
232
233
235 self.DeleteAllItems()
236 self.__expanded_nodes = None
237
238
240 """Updates EMR browser data."""
241
242
243
244 _log.debug('populating EMR tree')
245
246 wx.BeginBusyCursor()
247
248 if self.__pat is None:
249 self.clear_tree()
250 self.__expanded_nodes = None
251 wx.EndBusyCursor()
252 return True
253
254
255 root_item = self.__populate_root_node()
256 self.__curr_node = root_item
257 if self.__expanded_nodes is not None:
258 self.ExpansionState = self.__expanded_nodes
259 self.SelectItem(root_item)
260 self.Expand(root_item)
261 self.__update_text_for_selected_node()
262
263 wx.EndBusyCursor()
264 return True
265
266
268
269 self.DeleteAllItems()
270
271 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
272 self.SetItemData(root_item, None)
273 self.SetItemHasChildren(root_item, True)
274
275 self.__root_tooltip = self.__pat['description_gender'] + '\n'
276 if self.__pat['deceased'] is None:
277 self.__root_tooltip += ' %s (%s)\n\n' % (
278 self.__pat.get_formatted_dob(format = '%d %b %Y'),
279 self.__pat['medical_age']
280 )
281 else:
282 template = ' %s - %s (%s)\n\n'
283 self.__root_tooltip += template % (
284 self.__pat.get_formatted_dob(format = '%d.%b %Y'),
285 gmDateTime.pydt_strftime(self.__pat['deceased'], '%Y %b %d'),
286 self.__pat['medical_age']
287 )
288 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], '', '%s\n\n')
289 doc = self.__pat.primary_provider
290 if doc is not None:
291 self.__root_tooltip += '%s:\n' % _('Primary provider in this praxis')
292 self.__root_tooltip += ' %s %s %s (%s)%s\n\n' % (
293 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
294 doc['firstnames'],
295 doc['lastnames'],
296 doc['short_alias'],
297 gmTools.bool2subst(doc['is_active'], '', ' [%s]' % _('inactive'))
298 )
299 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
300 self.__root_tooltip += _('In case of emergency contact:') + '\n'
301 if self.__pat['emergency_contact'] is not None:
302 self.__root_tooltip += gmTools.wrap (
303 text = '%s\n' % self.__pat['emergency_contact'],
304 width = 60,
305 initial_indent = ' ',
306 subsequent_indent = ' '
307 )
308 if self.__pat['pk_emergency_contact'] is not None:
309 contact = self.__pat.emergency_contact_in_database
310 self.__root_tooltip += ' %s\n' % contact['description_gender']
311 self.__root_tooltip = self.__root_tooltip.strip('\n')
312 if self.__root_tooltip == '':
313 self.__root_tooltip = ' '
314
315 return root_item
316
317
319 """Displays information for the selected tree node."""
320
321 if self.__soap_display is None:
322 return
323
324 self.__soap_display.Clear()
325 self.__img_display.clear()
326
327 if self.__curr_node is None:
328 return
329
330 if not self.__curr_node.IsOk():
331 return
332
333 try:
334 node_data = self.GetItemData(self.__curr_node)
335 except wx.wxAssertionError:
336 node_data = None
337 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
338 _log.error('real node: %s', self.__curr_node)
339 _log.error('node.IsOk(): %s', self.__curr_node.IsOk())
340 _log.error('is root node: %s', self.__curr_node == self.GetRootItem())
341 _log.error('node attributes: %s', dir(self.__curr_node))
342 gmLog2.log_stack_trace()
343
344 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
345 self.__update_text_for_issue_node(node_data)
346 return
347
348
349 if isinstance(node_data, dict):
350 self.__update_text_for_pseudo_issue_node(node_data)
351 return
352
353 if isinstance(node_data, gmEMRStructItems.cEpisode):
354 self.__update_text_for_episode_node(node_data)
355 return
356
357 if isinstance(node_data, gmEMRStructItems.cEncounter):
358 self.__update_text_for_encounter_node(node_data)
359 return
360
361 if isinstance(node_data, gmGenericEMRItem.cGenericEMRItem):
362 self.__update_text_for_generic_node(node_data)
363 return
364
365
366 self.__update_text_for_root_node()
367
368
370
371
372 self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
373 item = self.__root_context_popup.Append(-1, _('Print EMR'))
374 self.Bind(wx.EVT_MENU, self.__print_emr, item)
375 item = self.__root_context_popup.Append(-1, _('Create health issue'))
376 self.Bind(wx.EVT_MENU, self.__create_issue, item)
377 item = self.__root_context_popup.Append(-1, _('Create episode'))
378 self.Bind(wx.EVT_MENU, self.__create_episode, item)
379 item = self.__root_context_popup.Append(-1, _('Create progress note'))
380 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
381 item = self.__root_context_popup.Append(-1, _('Manage allergies'))
382 self.Bind(wx.EVT_MENU, self.__document_allergy, item)
383 item = self.__root_context_popup.Append(-1, _('Manage family history'))
384 self.Bind(wx.EVT_MENU, self.__manage_family_history, item)
385 item = self.__root_context_popup.Append(-1, _('Manage hospitalizations'))
386 self.Bind(wx.EVT_MENU, self.__manage_hospital_stays, item)
387 item = self.__root_context_popup.Append(-1, _('Manage occupation'))
388 self.Bind(wx.EVT_MENU, self.__manage_occupation, item)
389 item = self.__root_context_popup.Append(-1, _('Manage procedures'))
390 self.Bind(wx.EVT_MENU, self.__manage_procedures, item)
391 item = self.__root_context_popup.Append(-1, _('Manage vaccinations'))
392 self.Bind(wx.EVT_MENU, self.__manage_vaccinations, item)
393
394 self.__root_context_popup.AppendSeparator()
395
396
397 expand_menu = wx.Menu()
398 self.__root_context_popup.Append(wx.NewId(), _('Open EMR to ...'), expand_menu)
399 item = expand_menu.Append(-1, _('... issue level'))
400 self.Bind(wx.EVT_MENU, self.__expand_to_issue_level, item)
401 item = expand_menu.Append(-1, _('... episode level'))
402 self.Bind(wx.EVT_MENU, self.__expand_to_episode_level, item)
403 item = expand_menu.Append(-1, _('... encounter level'))
404 self.Bind(wx.EVT_MENU, self.__expand_to_encounter_level, item)
405
406
407 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
408 item = self.__issue_context_popup.Append(-1, _('Edit details'))
409 self.Bind(wx.EVT_MENU, self.__edit_issue, item)
410 item = self.__issue_context_popup.Append(-1, _('Delete'))
411 self.Bind(wx.EVT_MENU, self.__delete_issue, item)
412 self.__issue_context_popup.AppendSeparator()
413 item = self.__issue_context_popup.Append(-1, _('Open to encounter level'))
414 self.Bind(wx.EVT_MENU, self.__expand_issue_to_encounter_level, item)
415
416
417 item = self.__issue_context_popup.Append(-1, _('Create progress note'))
418 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
419
420
421 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
422 item = self.__epi_context_popup.Append(-1, _('Toggle ongoing/closed'))
423 self.Bind(wx.EVT_MENU, self.__toggle_episode_open_close, item)
424 item = self.__epi_context_popup.Append(-1, _('Edit details'))
425 self.Bind(wx.EVT_MENU, self.__edit_episode, item)
426 item = self.__epi_context_popup.Append(-1, _('Delete'))
427 self.Bind(wx.EVT_MENU, self.__delete_episode, item)
428 item = self.__epi_context_popup.Append(-1, _('Promote'))
429 self.Bind(wx.EVT_MENU, self.__promote_episode_to_issue, item)
430 item = self.__epi_context_popup.Append(-1, _('Create progress note'))
431 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
432 item = self.__epi_context_popup.Append(-1, _('Move encounters'))
433 self.Bind(wx.EVT_MENU, self.__move_encounters, item)
434
435
436 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
437 item = self.__enc_context_popup.Append(-1, _('Move data to another episode'))
438 self.Bind(wx.EVT_MENU, self.__relink_encounter_data2episode, item)
439 item = self.__enc_context_popup.Append(-1, _('Edit details'))
440 self.Bind(wx.EVT_MENU, self.__edit_encounter_details, item)
441
442
443
444
445
446 item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
447 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
448 item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
449 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
450
451
452 self.__generic_emr_item_context_popup = wx.Menu(title = _('Item Actions:'))
453 item = self.__generic_emr_item_context_popup.Append(-1, _('Edit item'))
454 self.Bind(wx.EVT_MENU, self.__on_edit_generic_emr_item, item)
455
456
458 self.__curr_node_data = self.GetItemData(self.__curr_node)
459
460 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
461 self.PopupMenu(self.__issue_context_popup, pos)
462 return True
463
464 if isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
465 self.PopupMenu(self.__epi_context_popup, pos)
466 return True
467
468 if isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
469 self.PopupMenu(self.__enc_context_popup, pos)
470 return True
471
472 if isinstance(self.__curr_node_data, gmGenericEMRItem.cGenericEMRItem):
473 self.PopupMenu(self.__generic_emr_item_context_popup, pos)
474 return True
475
476 if self.__curr_node == self.GetRootItem():
477 self.PopupMenu(self.__root_context_popup, pos)
478 return True
479
480 return False
481
482
483
484
485
486
487
496
497
499 self.__curr_node_data['episode_open'] = not self.__curr_node_data['episode_open']
500 self.__curr_node_data.save()
501
502
505
506
509
510
512 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
513 parent = self,
514 id = -1,
515 caption = _('Deleting episode'),
516 button_defs = [
517 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
518 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
519 ],
520 question = _(
521 'Are you sure you want to delete this episode ?\n'
522 '\n'
523 ' "%s"\n'
524 ) % self.__curr_node_data['description']
525 )
526 result = dlg.ShowModal()
527 if result != wx.ID_YES:
528 return
529
530 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
531 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
532
533
535 self.DeleteChildren(episode_node)
536
537 emr = self.__pat.emr
538 epi = self.GetItemData(episode_node)
539 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
540 if len(encounters) == 0:
541 self.SetItemHasChildren(episode_node, False)
542 return
543
544 self.SetItemHasChildren(episode_node, True)
545 for enc in encounters:
546 label = '%s: %s' % (
547 enc['started'].strftime('%Y %b %d'),
548 gmTools.unwrap (
549 gmTools.coalesce (
550 gmTools.coalesce (
551 gmTools.coalesce (
552 enc.get_latest_soap (
553 soap_cat = 'a',
554 episode = epi['pk_episode']
555 ),
556 enc['assessment_of_encounter']
557 ),
558 enc['reason_for_encounter']
559 ),
560 enc['l10n_type']
561 ),
562 max_length = 40
563 )
564 )
565 encounter_node = self.AppendItem(episode_node, label)
566 self.SetItemData(encounter_node, enc)
567 self.SetItemHasChildren(encounter_node, True)
568
569 self.SortChildren(episode_node)
570
571
573 self.__cb__enable_display_mode_selection(True)
574 if self.__soap_display_mode == 'details':
575 txt = episode.format(left_margin = 1, patient = self.__pat)
576 font = self.__soap_display_prop_font
577 elif self.__soap_display_mode == 'journal':
578 txt = episode.format_as_journal(left_margin = 1)
579 font = self.__soap_display_prop_font
580 elif self.__soap_display_mode == 'revisions':
581 txt = episode.formatted_revision_history
582 font = self.__soap_display_mono_font
583 else:
584 txt = 'unknown SOAP display mode [%s]' % self.__soap_display_mode
585 font = self.__soap_display_prop_font
586 doc_folder = self.__pat.get_document_folder()
587 self.__img_display.refresh (
588 document_folder = doc_folder,
589 episodes = [ episode['pk_episode'] ]
590 )
591 self.__soap_display.SetFont(font)
592 self.__soap_display.WriteText(txt)
593 self.__soap_display.ShowPosition(0)
594
595
622
623
624
625
636
637
639 encounter = self.GetItemData(self.__curr_node)
640 node_parent = self.GetItemParent(self.__curr_node)
641 episode = self.GetItemData(node_parent)
642
643 gmNarrativeWorkflows.manage_progress_notes (
644 parent = self,
645 encounters = [encounter['pk_encounter']],
646 episodes = [episode['pk_episode']]
647 )
648
649
654
655
657
658 node_parent = self.GetItemParent(self.__curr_node)
659 owning_episode = self.GetItemData(node_parent)
660
661 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
662 self,
663 -1,
664 episode = owning_episode,
665 encounter = self.__curr_node_data
666 )
667
668 result = episode_selector.ShowModal()
669 episode_selector.DestroyLater()
670
671 if result == wx.ID_YES:
672 self.__populate_tree()
673
674
676 self.DeleteChildren(encounter_node)
677 encounter = self.GetItemData(encounter_node)
678 encounter_items = self.__pat.emr.get_generic_emr_items (
679 pk_encounters = [encounter['pk_encounter']],
680 pk_episodes = [self.GetItemData(self.GetItemParent(encounter_node))['pk_episode']]
681 )
682 if len(encounter_items) == 0:
683 self.SetItemHasChildren(encounter_node, False)
684 return
685
686 for enc_item in encounter_items:
687 if encounter['started'].year != enc_item['clin_when'].year:
688 when = enc_item['clin_when'].strftime('%Y')
689 elif encounter['started'].month != enc_item['clin_when'].month:
690 when = enc_item['clin_when'].strftime('%b')
691 elif encounter['started'].day != enc_item['clin_when'].day:
692 when = enc_item['clin_when'].strftime('%b %d')
693 else:
694 when = enc_item['clin_when'].strftime('%H:%M')
695 item_node = self.AppendItem(encounter_node, '[%s] %s: %s' % (
696 enc_item.i18n_soap_cat,
697 when,
698 enc_item.item_type_str
699 ))
700 self.SetItemData(item_node, enc_item)
701 self.SetItemHasChildren(item_node, False)
702
703
704
705
706
708 self.__cb__enable_display_mode_selection(True)
709 epi = self.GetItemData(self.GetItemParent(self.__curr_node))
710 if self.__soap_display_mode == 'revisions':
711 txt = encounter.formatted_revision_history
712 font = self.__soap_display_mono_font
713 else:
714 txt = encounter.format (
715 episodes = [epi['pk_episode']],
716 with_soap = True,
717 left_margin = 1,
718 patient = self.__pat,
719 with_co_encountlet_hints = True
720 )
721 font = self.__soap_display_prop_font
722 self.__soap_display.SetFont(font)
723 self.__soap_display.WriteText(txt)
724 self.__soap_display.ShowPosition(0)
725 self.__img_display.refresh (
726 document_folder = self.__pat.get_document_folder(),
727 episodes = [ epi['pk_episode'] ],
728 encounter = encounter['pk_encounter']
729 )
730
731
766
767
768
769
771 self.__cb__enable_display_mode_selection(False)
772 txt = gmTools.list2text (
773 generic_item.format(),
774 strip_leading_empty_lines = False,
775 strip_trailing_empty_lines = False,
776 strip_trailing_whitespace = True,
777 max_line_width = 85
778 )
779 self.__soap_display.SetFont(self.__soap_display_prop_font)
780 self.__soap_display.WriteText(txt)
781 self.__soap_display.ShowPosition(0)
782
783
785 self.__edit_generic_emr_item()
786
787
795
796
797
798
801
802
804 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
805 parent = self,
806 id = -1,
807 caption = _('Deleting health issue'),
808 button_defs = [
809 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
810 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
811 ],
812 question = _(
813 'Are you sure you want to delete this health issue ?\n'
814 '\n'
815 ' "%s"\n'
816 ) % self.__curr_node_data['description']
817 )
818 result = dlg.ShowModal()
819 if result != wx.ID_YES:
820 dlg.DestroyLater()
821 return
822
823 dlg.DestroyLater()
824
825 if not gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data):
826 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
827
828
830
831 if not self.__curr_node.IsOk():
832 return
833
834 self.Expand(self.__curr_node)
835
836 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
837 while epi.IsOk():
838 self.Expand(epi)
839 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
840
841
843 self.DeleteChildren(issue_node)
844
845 issue = self.GetItemData(issue_node)
846 episodes = self.__pat.emr.get_episodes(issues = [issue['pk_health_issue']])
847 if len(episodes) == 0:
848 self.SetItemHasChildren(issue_node, False)
849 return
850
851 self.SetItemHasChildren(issue_node, True)
852
853 for episode in episodes:
854 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
855 episode_node = self.AppendItem(issue_node, '%s (%s)' % (
856 episode['description'],
857 range_str
858 ))
859 self.SetItemData(episode_node, episode)
860
861 self.SetItemHasChildren(episode_node, True)
862
863 self.SortChildren(issue_node)
864
865
867 self.__cb__enable_display_mode_selection(True)
868 if self.__soap_display_mode == 'details':
869 txt = issue.format(left_margin = 1, patient = self.__pat)
870 font = self.__soap_display_prop_font
871 elif self.__soap_display_mode == 'journal':
872 txt = issue.format_as_journal(left_margin = 1)
873 font = self.__soap_display_prop_font
874 elif self.__soap_display_mode == 'revisions':
875 txt = issue.formatted_revision_history
876 font = self.__soap_display_mono_font
877 else:
878 txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
879 font = self.__soap_display_prop_font
880 epis = issue.episodes
881 if len(epis) > 0:
882 doc_folder = self.__pat.get_document_folder()
883 self.__img_display.refresh (
884 document_folder = doc_folder,
885 episodes = [ epi['pk_episode'] for epi in epis ],
886 do_async = True
887 )
888 self.__soap_display.SetFont(font)
889 self.__soap_display.WriteText(txt)
890 self.__soap_display.ShowPosition(0)
891
892
924
925
927 self.DeleteChildren(fake_issue_node)
928
929 episodes = self.__pat.emr.unlinked_episodes
930 if len(episodes) == 0:
931 self.SetItemHasChildren(fake_issue_node, False)
932 return
933
934 self.SetItemHasChildren(fake_issue_node, True)
935
936 for episode in episodes:
937 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
938 episode_node = self.AppendItem(fake_issue_node, '%s (%s)' % (
939 episode['description'],
940 range_str
941 ))
942 self.SetItemData(episode_node, episode)
943 if episode['episode_open']:
944 self.SetItemBold(fake_issue_node, True)
945
946 self.SetItemHasChildren(episode_node, True)
947
948 self.SortChildren(fake_issue_node)
949
950
952 self.__cb__enable_display_mode_selection(True)
953 if self.__soap_display_mode == 'details':
954 txt = _('Pool of unassociated episodes "%s":\n') % pseudo_issue['description']
955 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
956 if len(epis) > 0:
957 txt += '\n'
958 for epi in epis:
959 txt += epi.format (
960 left_margin = 1,
961 patient = self.__pat,
962 with_summary = True,
963 with_codes = False,
964 with_encounters = False,
965 with_documents = False,
966 with_hospital_stays = False,
967 with_procedures = False,
968 with_family_history = False,
969 with_tests = False,
970 with_vaccinations = False,
971 with_health_issue = False
972 )
973 txt += '\n'
974 else:
975 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
976 txt = ''
977 if len(epis) > 0:
978 txt += _(' Listing of unassociated episodes\n')
979 for epi in epis:
980 txt += ' %s\n' % (gmTools.u_box_horiz_4dashes * 60)
981 txt += epi.format (
982 left_margin = 1,
983 patient = self.__pat,
984 with_summary = False,
985 with_codes = False,
986 with_encounters = False,
987 with_documents = False,
988 with_hospital_stays = False,
989 with_procedures = False,
990 with_family_history = False,
991 with_tests = False,
992 with_vaccinations = False,
993 with_health_issue = False
994 )
995 txt += '\n'
996 txt += epi.format_as_journal(left_margin = 2)
997 self.__soap_display.SetFont(self.__soap_display_prop_font)
998 self.__soap_display.WriteText(txt)
999 self.__soap_display.ShowPosition(0)
1000
1001
1002
1003
1006
1007
1010
1011
1014
1015
1017 self.__cb__select_edit_mode(True)
1018 self.__cb__add_soap_editor(problem = self.__curr_node_data, allow_same_problem = False)
1019
1020
1022 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
1023
1024 if dlg.ShowModal() == wx.ID_OK:
1025 self.__expanded_nodes = self.ExpansionState
1026 self.__populate_tree()
1027 dlg.DestroyLater()
1028 return
1029
1030
1033
1034
1035 - def __manage_family_history(self, event):
1037
1038
1041
1042
1045
1046
1049
1050
1052
1053 root_item = self.GetRootItem()
1054
1055 if not root_item.IsOk():
1056 return
1057
1058 self.Expand(root_item)
1059
1060
1061 issue, issue_cookie = self.GetFirstChild(root_item)
1062 while issue.IsOk():
1063 self.Collapse(issue)
1064 epi, epi_cookie = self.GetFirstChild(issue)
1065 while epi.IsOk():
1066 self.Collapse(epi)
1067 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1068 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1069
1070
1072
1073 root_item = self.GetRootItem()
1074
1075 if not root_item.IsOk():
1076 return
1077
1078 self.Expand(root_item)
1079
1080
1081 issue, issue_cookie = self.GetFirstChild(root_item)
1082 while issue.IsOk():
1083 self.Expand(issue)
1084 epi, epi_cookie = self.GetFirstChild(issue)
1085 while epi.IsOk():
1086 self.Collapse(epi)
1087 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1088 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1089
1090
1092
1093 root_item = self.GetRootItem()
1094
1095 if not root_item.IsOk():
1096 return
1097
1098 self.Expand(root_item)
1099
1100
1101 issue, issue_cookie = self.GetFirstChild(root_item)
1102 while issue.IsOk():
1103 self.Expand(issue)
1104 epi, epi_cookie = self.GetFirstChild(issue)
1105 while epi.IsOk():
1106 self.Expand(epi)
1107 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1108 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1109
1110
1117
1118
1120 self.__curr_node_data = self.GetItemData(self.__curr_node)
1121 if isinstance(self.__curr_node_data, gmGenericEMRItem.cGenericEMRItem):
1122 self.__edit_generic_emr_item()
1123 return
1124
1125
1126
1127
1129 root_node = self.GetRootItem()
1130 self.DeleteChildren(root_node)
1131
1132 issues = [{
1133 'description': _('Unattributed episodes'),
1134 'laterality': None,
1135 'diagnostic_certainty_classification': None,
1136 'has_open_episode': False,
1137 'pk_health_issue': None
1138 }]
1139 issues.extend(self.__pat.emr.health_issues)
1140 for issue in issues:
1141 issue_node = self.AppendItem(root_node, '%s%s%s' % (
1142 issue['description'],
1143 gmTools.coalesce(issue['laterality'], '', ' [%s]', none_equivalents = [None, 'na']),
1144 gmTools.coalesce(issue['diagnostic_certainty_classification'], '', ' [%s]')
1145 ))
1146 self.SetItemBold(issue_node, issue['has_open_episode'])
1147 self.SetItemData(issue_node, issue)
1148
1149 self.SetItemHasChildren(issue_node, True)
1150
1151 self.SetItemHasChildren(root_node, (len(issues) != 0))
1152 self.SortChildren(root_node)
1153
1154
1156 self.__cb__enable_display_mode_selection(True)
1157 if self.__soap_display_mode == 'details':
1158 emr = self.__pat.emr
1159 txt = emr.format_summary()
1160 else:
1161 txt = self.__pat.emr.format_as_journal(left_margin = 1, patient = self.__pat)
1162 self.__soap_display.SetFont(self.__soap_display_prop_font)
1163 self.__soap_display.WriteText(txt)
1164 self.__soap_display.ShowPosition(0)
1165
1166
1167
1168
1170 self.__update_text_for_selected_node()
1171
1172
1174 self.__expanded_nodes = self.ExpansionState
1175 self.__populate_tree()
1176
1177
1179 self.__expanded_nodes = self.ExpansionState
1180 self.__populate_tree()
1181
1182
1184 event.Skip()
1185
1186 node = event.GetItem()
1187 if node == self.GetRootItem():
1188 self.__expand_root_node()
1189 return
1190
1191 node_data = self.GetItemData(node)
1192
1193 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
1194 self.__expand_issue_node(issue_node = node)
1195 return
1196
1197 if isinstance(node_data, gmEMRStructItems.cEpisode):
1198 self.__expand_episode_node(episode_node = node)
1199 return
1200
1201
1202 if type(node_data) == type({}):
1203 self.__expand_pseudo_issue_node(fake_issue_node = node)
1204 return
1205
1206 if isinstance(node_data, gmEMRStructItems.cEncounter):
1207 self.__expand_encounter_node(encounter_node = node)
1208 return
1209
1210
1212 event.Skip()
1213 self.__curr_node = event.Item
1214 self.__edit_tree_item()
1215
1216
1218 sel_item = event.GetItem()
1219 self.__curr_node = sel_item
1220 self.__update_text_for_selected_node()
1221 return True
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1283 """Right button clicked: display the popup for the tree"""
1284 self.__curr_node = event.Item
1285 self.__show_context_menu(pos = event.Point)
1286 event.Skip()
1287
1288
1290 """Used in sorting items.
1291
1292 -1: 1 < 2
1293 0: 1 = 2
1294 1: 1 > 2
1295 """
1296
1297
1298 if not node1:
1299 _log.debug('invalid node 1')
1300 return 0
1301 if not node2:
1302 _log.debug('invalid node 2')
1303 return 0
1304
1305 if not node1.IsOk():
1306 _log.debug('invalid node 1')
1307 return 0
1308 if not node2.IsOk():
1309 _log.debug('invalid node 2')
1310 return 0
1311
1312 item1 = self.GetItemData(node1)
1313 item2 = self.GetItemData(node2)
1314
1315
1316 if isinstance(item1, type({})):
1317 return -1
1318 if isinstance(item2, type({})):
1319 return 1
1320
1321
1322 if isinstance(item1, gmEMRStructItems.cEncounter):
1323 if item1['started'] == item2['started']:
1324 return 0
1325 if item1['started'] > item2['started']:
1326 return -1
1327 return 1
1328
1329
1330 if isinstance(item1, gmEMRStructItems.cEpisode):
1331
1332 if item1['episode_open']:
1333 return -1
1334 if item2['episode_open']:
1335 return 1
1336 start1 = item1.best_guess_clinical_start_date
1337 start2 = item2.best_guess_clinical_start_date
1338 if start1 == start2:
1339 return 0
1340 if start1 < start2:
1341 return 1
1342 return -1
1343
1344
1345 if isinstance(item1, gmEMRStructItems.cHealthIssue):
1346
1347
1348 if item1['grouping'] is None:
1349 if item2['grouping'] is not None:
1350 return 1
1351
1352
1353 if item1['grouping'] is not None:
1354 if item2['grouping'] is None:
1355 return -1
1356
1357
1358 if (item1['grouping'] is None) and (item2['grouping'] is None):
1359 if item1['description'].lower() < item2['description'].lower():
1360 return -1
1361 if item1['description'].lower() > item2['description'].lower():
1362 return 1
1363 return 0
1364
1365
1366 if item1['grouping'] < item2['grouping']:
1367 return -1
1368
1369 if item1['grouping'] > item2['grouping']:
1370 return 1
1371
1372 if item1['description'].lower() < item2['description'].lower():
1373 return -1
1374
1375 if item1['description'].lower() > item2['description'].lower():
1376 return 1
1377
1378 return 0
1379
1380 _log.error('unknown item type during sorting EMR tree:')
1381 _log.error('item1: %s', type(item1))
1382 _log.error('item2: %s', type(item2))
1383
1384 return 0
1385
1386
1387
1388
1391
1393 if self.__pat == patient:
1394 return
1395 self.__pat = patient
1396 if patient is None:
1397 self.clear_tree()
1398 return
1399 return self.__populate_tree()
1400
1401 patient = property(_get_patient, _set_patient)
1402
1403
1405 return self.__soap_display_mode
1406
1408 if mode not in ['details', 'journal', 'revisions']:
1409 raise ValueError('details display mode must be one of "details", "journal", "revisions"')
1410 if self.__soap_display_mode == mode:
1411 return
1412 self.__soap_display_mode = mode
1413 self.__update_text_for_selected_node()
1414
1415 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1416
1417
1418
1419 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
1420
1430
1431
1432 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
1433
1435 """A splitter window holding an EMR tree.
1436
1437 The left hand side displays a scrollable EMR tree while
1438 on the right details for selected items are displayed.
1439
1440 Expects to be put into a Notebook.
1441 """
1452
1454 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1455 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1456 return True
1457
1458
1460 return self.__editing
1461
1463 self.__editing = editing
1464 self.enable_display_mode_selection(enable = not self.__editing)
1465 if self.__editing:
1466 self._BTN_switch_browse_edit.SetLabel(_('&Browse %s') % gmTools.u_ellipsis)
1467 self._PNL_browse.Hide()
1468 self._PNL_visual_soap.Hide()
1469 self._PNL_edit.Show()
1470 else:
1471 self._BTN_switch_browse_edit.SetLabel(_('&New notes %s') % gmTools.u_ellipsis)
1472 self._PNL_edit.Hide()
1473 self._PNL_visual_soap.Show()
1474 self._PNL_browse.Show()
1475 self._PNL_right_side.GetSizer().Layout()
1476
1477 editing = property(_get_editing, _set_editing)
1478
1479
1480
1481
1483 self._pnl_emr_tree._emr_tree.patient = None
1484 self._PNL_edit.patient = None
1485 return True
1486
1487
1489 if self.GetParent().GetCurrentPage() != self:
1490 return True
1491 self.repopulate_ui()
1492 return True
1493
1494
1497
1498
1501
1502
1505
1506
1509
1510
1511
1512
1514 """Fills UI with data."""
1515 pat = gmPerson.gmCurrentPatient()
1516 self._pnl_emr_tree._emr_tree.patient = pat
1517 self._PNL_edit.patient = pat
1518 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSize()[0] // 3, True)
1519
1520 return True
1521
1522
1524 if self.editing:
1525 enable = False
1526 if enable:
1527 self._RBTN_details.Enable(True)
1528 self._RBTN_journal.Enable(True)
1529 self._RBTN_revisions.Enable(True)
1530 return
1531 self._RBTN_details.Enable(False)
1532 self._RBTN_journal.Enable(False)
1533 self._RBTN_revisions.Enable(False)
1534
1535
1537 self._PNL_edit._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1538
1539
1542
1543
1544 from Gnumed.wxGladeWidgets import wxgEMRJournalPluginPnl
1545
1547
1553
1554
1555
1556
1572
1573
1574
1576 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1577 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1578 return True
1579
1580
1581
1582
1584 self._TCTRL_journal.SetValue('')
1585 return True
1586
1587
1589 if self.GetParent().GetCurrentPage() != self:
1590 return True
1591 self.repopulate_ui()
1592 return True
1593
1594
1597
1598
1601
1602
1605
1606
1607 from Gnumed.wxGladeWidgets import wxgEMRListJournalPluginPnl
1608
1610
1622
1623
1624
1625
1627 self._LCTRL_journal.remove_items_safely()
1628 self._TCTRL_details.SetValue('')
1629
1630
1631 if self._RBTN_by_encounter.Value:
1632 order_by = 'encounter_started, pk_health_issue, pk_episode NULLS FIRST, scr, src_table, modified_when'
1633 date_col_header = _('Encounter')
1634 date_fields = ['encounter_started', 'modified_when']
1635 elif self._RBTN_by_last_modified.Value:
1636 order_by = 'modified_when, pk_health_issue, pk_episode NULLS FIRST, src_table, scr'
1637 date_col_header = _('Modified')
1638 date_fields = ['modified_when']
1639 elif self._RBTN_by_item_time.Value:
1640 order_by = 'clin_when, pk_health_issue, pk_episode NULLS FIRST, scr, src_table, modified_when'
1641 date_col_header = _('Happened')
1642 date_fields = ['clin_when', 'modified_when']
1643 else:
1644 raise ValueError('invalid EMR journal list sort state')
1645
1646 self._LCTRL_journal.set_columns([date_col_header, '', _('Entry'), _('Who / When')])
1647 self._LCTRL_journal.set_resize_column(3)
1648
1649
1650 journal = gmPerson.gmCurrentPatient().emr.get_generic_emr_items (
1651 pk_encounters = None,
1652 pk_episodes = None,
1653 pk_health_issues = None,
1654 use_active_encounter = True,
1655 order_by = order_by
1656 )
1657
1658
1659 items = []
1660 data = []
1661 prev_date = None
1662 for entry in journal:
1663 if entry['narrative'].strip() == '':
1664 continue
1665
1666 soap_cat = gmSoapDefs.soap_cat2l10n[entry['soap_cat']]
1667 who = '%s (%s)' % (entry['modified_by'], entry['date_modified'])
1668 try:
1669 entry_date = gmDateTime.pydt_strftime(entry[date_fields[0]], '%Y-%m-%d')
1670 except KeyError:
1671 entry_date = gmDateTime.pydt_strftime(entry[date_fields[1]], '%Y-%m-%d')
1672 if entry_date == prev_date:
1673 date2show = ''
1674 else:
1675 date2show = entry_date
1676 prev_date = entry_date
1677 lines_of_journal_entry = entry['narrative'].strip().split('\n')
1678 first_line = lines_of_journal_entry[0]
1679 items.append([date2show, soap_cat, first_line.rstrip(), who])
1680
1681
1682
1683
1684 data.append(entry)
1685 for line in lines_of_journal_entry[1:]:
1686 if line.strip() == '':
1687 continue
1688
1689 items.append(['', '', line.rstrip(), ''])
1690
1691
1692
1693
1694 data.append(entry)
1695
1696 self._LCTRL_journal.set_string_items(items)
1697
1698
1699
1700 self._LCTRL_journal.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER])
1701 self._LCTRL_journal.set_data(data)
1702
1703 self._LCTRL_journal.SetFocus()
1704 return True
1705
1706
1707
1708
1710 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1711 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1712 return True
1713
1714
1716 if entry['src_table'] in self.__data:
1717 if entry['src_pk'] in self.__data[entry['src_table']]:
1718 return
1719
1720 else:
1721 self.__data[entry['src_table']] = {}
1722
1723 self.__data[entry['src_table']][entry['src_pk']] = {}
1724 self.__data[entry['src_table']][entry['src_pk']]['entry'] = entry
1725 self.__data[entry['src_table']][entry['src_pk']]['formatted_instance'] = None
1726 if entry['encounter_started'] is None:
1727 enc_duration = gmTools.u_diameter
1728 else:
1729 enc_duration = '%s - %s' % (
1730 gmDateTime.pydt_strftime(entry['encounter_started'], '%Y %b %d %H:%M'),
1731 gmDateTime.pydt_strftime(entry['encounter_last_affirmed'], '%H:%M')
1732 )
1733 self.__data[entry['src_table']][entry['src_pk']]['formatted_header'] = _(
1734 'Chart entry: %s [#%s in %s]\n'
1735 ' Modified: %s by %s (%s rev %s)\n'
1736 '\n'
1737 'Health issue: %s%s\n'
1738 'Episode: %s%s\n'
1739 'Encounter: %s%s'
1740 ) % (
1741 gmGenericEMRItem.generic_item_type_str(entry['src_table']),
1742 entry['src_pk'],
1743 entry['src_table'],
1744 entry['date_modified'],
1745 entry['modified_by'],
1746 gmTools.u_arrow2right,
1747 entry['row_version'],
1748 gmTools.coalesce(entry['health_issue'], gmTools.u_diameter, '%s'),
1749 gmTools.bool2subst(entry['issue_active'], ' (' + _('active') + ')', ' (' + _('inactive') + ')', ''),
1750 gmTools.coalesce(entry['episode'], gmTools.u_diameter, '%s'),
1751 gmTools.bool2subst(entry['episode_open'], ' (' + _('open') + ')', ' (' + _('closed') + ')', ''),
1752 enc_duration,
1753 gmTools.coalesce(entry['encounter_l10n_type'], '', ' (%s)'),
1754 )
1755 self.__data[entry['src_table']][entry['src_pk']]['formatted_root_item'] = _(
1756 '%s\n'
1757 '\n'
1758 ' rev %s (%s) by %s in <%s>'
1759 ) % (
1760 entry['narrative'].strip(),
1761 entry['row_version'],
1762 entry['date_modified'],
1763 entry['modified_by'],
1764 entry['src_table']
1765 )
1766
1767
1768
1769
1775
1776
1778 if self.GetParent().GetCurrentPage() != self:
1779 return True
1780 self.repopulate_ui()
1781 return True
1782
1783
1791
1792
1794 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1795 self._TCTRL_details.SetValue(data.format(eol = '\n'))
1796
1797 return
1798
1799 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1800 if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
1801 txt = _(
1802 '%s\n'
1803 '%s\n'
1804 '%s'
1805 ) % (
1806 self.__data[data['table']][data['pk']]['formatted_header'],
1807 gmTools.u_box_horiz_4dashes * 40,
1808 self.__data[data['table']][data['pk']]['formatted_root_item']
1809 )
1810 self._TCTRL_details.SetValue(txt)
1811 self.__load_timer.Stop()
1812 self.__load_timer.Start(oneShot = True)
1813 return
1814
1815 txt = _(
1816 '%s\n'
1817 '%s\n'
1818 '%s'
1819 ) % (
1820 self.__data[data['table']][data['pk']]['formatted_header'],
1821 gmTools.u_box_horiz_4dashes * 40,
1822 self.__data[data['table']][data['pk']]['formatted_instance']
1823 )
1824 self._TCTRL_details.SetValue(txt)
1825
1826
1841
1842
1845
1846
1849
1850
1853
1854
1858
1859
1862
1863
1866
1867
1868
1869
1870
1871
1872
1873
1874 if __name__ == '__main__':
1875
1876 _log.info("starting emr browser...")
1877
1878
1879 patient = gmPersonSearch.ask_for_patient()
1880 if patient is None:
1881 print("No patient. Exiting gracefully...")
1882 sys.exit(0)
1883 gmPatSearchWidgets.set_active_patient(patient = patient)
1884
1885
1886 application = wx.PyWidgetTester(size=(800,600))
1887 emr_browser = cEMRBrowserPanel(application.frame, -1)
1888 emr_browser.refresh_tree()
1889
1890 application.frame.Show(True)
1891 application.MainLoop()
1892
1893
1894 if patient is not None:
1895 try:
1896 patient.cleanup()
1897 except Exception:
1898 print("error cleaning up patient")
1899
1900 _log.info("closing emr browser...")
1901
1902
1903