Package Gnumed :: Package wxpython :: Module gmEMRBrowser
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmEMRBrowser

   1  """GNUmed patient EMR tree browser. 
   2  """ 
   3  #================================================================ 
   4  __version__ = "$Revision: 1.111 $" 
   5  __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net" 
   6  __license__ = "GPL" 
   7   
   8  # std lib 
   9  import sys, os.path, StringIO, codecs, logging 
  10   
  11   
  12  # 3rd party 
  13  import wx 
  14   
  15   
  16  # GNUmed libs 
  17  from Gnumed.pycommon import gmI18N, gmDispatcher, gmExceptions, gmTools 
  18  from Gnumed.exporters import gmPatientExporter 
  19  from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmPersonSearch 
  20  from Gnumed.wxpython import gmGuiHelpers 
  21  from Gnumed.wxpython import gmEMRStructWidgets 
  22  from Gnumed.wxpython import gmSOAPWidgets 
  23  from Gnumed.wxpython import gmAllergyWidgets 
  24  from Gnumed.wxpython import gmDemographicsWidgets 
  25  from Gnumed.wxpython import gmNarrativeWidgets 
  26  from Gnumed.wxpython import gmPatSearchWidgets 
  27  from Gnumed.wxpython import gmVaccWidgets 
  28  from Gnumed.wxpython import gmFamilyHistoryWidgets 
  29   
  30   
  31  _log = logging.getLogger('gm.ui') 
  32  _log.info(__version__) 
  33   
  34  #============================================================ 
35 -def export_emr_to_ascii(parent=None):
36 """ 37 Dump the patient's EMR from GUI client 38 @param parent - The parent widget 39 @type parent - A wx.Window instance 40 """ 41 # sanity checks 42 if parent is None: 43 raise TypeError('expected wx.Window instance as parent, got <None>') 44 45 pat = gmPerson.gmCurrentPatient() 46 if not pat.connected: 47 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.')) 48 return False 49 50 # get file name 51 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 52 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))) 53 gmTools.mkdir(defdir) 54 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames']) 55 dlg = wx.FileDialog ( 56 parent = parent, 57 message = _("Save patient's EMR as..."), 58 defaultDir = defdir, 59 defaultFile = fname, 60 wildcard = wc, 61 style = wx.SAVE 62 ) 63 choice = dlg.ShowModal() 64 fname = dlg.GetPath() 65 dlg.Destroy() 66 if choice != wx.ID_OK: 67 return None 68 69 _log.debug('exporting EMR to [%s]', fname) 70 71 # output_file = open(fname, 'wb') 72 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace') 73 exporter = gmPatientExporter.cEmrExport(patient = pat) 74 exporter.set_output_file(output_file) 75 exporter.dump_constraints() 76 exporter.dump_demographic_record(True) 77 exporter.dump_clinical_record() 78 exporter.dump_med_docs() 79 output_file.close() 80 81 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False) 82 return fname
83 #============================================================
84 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
85 """This wx.TreeCtrl derivative displays a tree view of the medical record.""" 86 87 #--------------------------------------------------------
88 - def __init__(self, parent, id, *args, **kwds):
89 """Set up our specialised tree. 90 """ 91 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE 92 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 93 94 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self) 95 96 self.__details_display = None 97 self.__details_display_mode = u'details' # "details" or "journal" 98 self.__enable_display_mode_selection = None 99 self.__pat = gmPerson.gmCurrentPatient() 100 self.__curr_node = None 101 self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat) 102 103 self._old_cursor_pos = None 104 105 self.__make_popup_menus() 106 self.__register_events()
107 #-------------------------------------------------------- 108 # external API 109 #--------------------------------------------------------
110 - def refresh(self):
111 if not self.__pat.connected: 112 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),) 113 return False 114 115 if not self.__populate_tree(): 116 return False 117 118 return True
119 #--------------------------------------------------------
120 - def set_narrative_display(self, narrative_display=None):
121 self.__details_display = narrative_display
122 #--------------------------------------------------------
123 - def set_image_display(self, image_display=None):
124 self.__img_display = image_display
125 #--------------------------------------------------------
127 if not callable(callback): 128 raise ValueError('callback [%s] not callable' % callback) 129 130 self.__enable_display_mode_selection = callback
131 #-------------------------------------------------------- 132 # internal helpers 133 #--------------------------------------------------------
134 - def __register_events(self):
135 """Configures enabled event signals.""" 136 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected) 137 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked) 138 139 # handle tooltips 140 # wx.EVT_MOTION(self, self._on_mouse_motion) 141 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip) 142 143 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db) 144 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db) 145 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db) 146 gmDispatcher.connect(signal = 'family_history_mod_db', receiver = self._on_issue_mod_db)
147 #--------------------------------------------------------
148 - def __populate_tree(self):
149 """Updates EMR browser data.""" 150 # FIXME: auto select the previously self.__curr_node if not None 151 # FIXME: error handling 152 153 wx.BeginBusyCursor() 154 155 # self.snapshot_expansion() 156 157 # init new tree 158 self.DeleteAllItems() 159 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name()) 160 self.SetItemPyData(root_item, None) 161 self.SetItemHasChildren(root_item, True) 162 self.__root_tooltip = self.__pat['description_gender'] + u'\n' 163 if self.__pat['deceased'] is None: 164 self.__root_tooltip += u' %s %s (%s)\n\n' % ( 165 gmPerson.map_gender2symbol[self.__pat['gender']], 166 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()), 167 self.__pat['medical_age'] 168 ) 169 else: 170 template = u' %s %s - %s (%s)\n\n' 171 self.__root_tooltip += template % ( 172 gmPerson.map_gender2symbol[self.__pat['gender']], 173 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()), 174 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()), 175 self.__pat['medical_age'] 176 ) 177 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n') 178 doc = self.__pat.primary_provider 179 if doc is not None: 180 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis') 181 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % ( 182 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])), 183 doc['firstnames'], 184 doc['lastnames'], 185 doc['short_alias'], 186 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive')) 187 ) 188 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)): 189 self.__root_tooltip += _('In case of emergency contact:') + u'\n' 190 if self.__pat['emergency_contact'] is not None: 191 self.__root_tooltip += gmTools.wrap ( 192 text = u'%s\n' % self.__pat['emergency_contact'], 193 width = 60, 194 initial_indent = u' ', 195 subsequent_indent = u' ' 196 ) 197 if self.__pat['pk_emergency_contact'] is not None: 198 contact = self.__pat.emergency_contact_in_database 199 self.__root_tooltip += u' %s\n' % contact['description_gender'] 200 self.__root_tooltip = self.__root_tooltip.strip('\n') 201 if self.__root_tooltip == u'': 202 self.__root_tooltip = u' ' 203 204 # have the tree filled by the exporter 205 self.__exporter.get_historical_tree(self) 206 self.__curr_node = root_item 207 208 self.SelectItem(root_item) 209 self.Expand(root_item) 210 self.__update_text_for_selected_node() 211 212 # self.restore_expansion() 213 214 wx.EndBusyCursor() 215 return True
216 #--------------------------------------------------------
218 """Displays information for the selected tree node.""" 219 220 if self.__details_display is None: 221 self.__img_display.clear() 222 return 223 224 if self.__curr_node is None: 225 self.__img_display.clear() 226 return 227 228 node_data = self.GetPyData(self.__curr_node) 229 doc_folder = self.__pat.get_document_folder() 230 231 if isinstance(node_data, gmEMRStructItems.cHealthIssue): 232 self.__enable_display_mode_selection(True) 233 if self.__details_display_mode == u'details': 234 txt = node_data.format(left_margin=1, patient = self.__pat) 235 else: 236 txt = node_data.format_as_journal(left_margin = 1) 237 238 self.__img_display.refresh ( 239 document_folder = doc_folder, 240 episodes = [ epi['pk_episode'] for epi in node_data.episodes ] 241 ) 242 243 elif isinstance(node_data, type({})): 244 self.__enable_display_mode_selection(False) 245 # FIXME: turn into real dummy issue 246 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description'] 247 self.__img_display.clear() 248 249 elif isinstance(node_data, gmEMRStructItems.cEpisode): 250 self.__enable_display_mode_selection(True) 251 if self.__details_display_mode == u'details': 252 txt = node_data.format(left_margin = 1, patient = self.__pat) 253 else: 254 txt = node_data.format_as_journal(left_margin = 1) 255 self.__img_display.refresh ( 256 document_folder = doc_folder, 257 episodes = [node_data['pk_episode']] 258 ) 259 260 elif isinstance(node_data, gmEMRStructItems.cEncounter): 261 self.__enable_display_mode_selection(False) 262 epi = self.GetPyData(self.GetItemParent(self.__curr_node)) 263 txt = node_data.format ( 264 episodes = [epi['pk_episode']], 265 with_soap = True, 266 left_margin = 1, 267 patient = self.__pat, 268 with_co_encountlet_hints = True 269 ) 270 self.__img_display.refresh ( 271 document_folder = doc_folder, 272 episodes = [epi['pk_episode']], 273 encounter = node_data['pk_encounter'] 274 ) 275 276 # root node == EMR level 277 else: 278 self.__enable_display_mode_selection(False) 279 emr = self.__pat.get_emr() 280 txt = emr.format_summary(dob = self.__pat['dob']) 281 self.__img_display.clear() 282 283 self.__details_display.Clear() 284 self.__details_display.WriteText(txt) 285 self.__details_display.ShowPosition(0)
286 #--------------------------------------------------------
287 - def __make_popup_menus(self):
288 289 # - episodes 290 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:')) 291 292 menu_id = wx.NewId() 293 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details'))) 294 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode) 295 296 menu_id = wx.NewId() 297 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete'))) 298 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode) 299 300 menu_id = wx.NewId() 301 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote'))) 302 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue) 303 304 menu_id = wx.NewId() 305 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters'))) 306 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters) 307 308 # - encounters 309 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:')) 310 # - move data 311 menu_id = wx.NewId() 312 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode'))) 313 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode) 314 # - edit encounter details 315 menu_id = wx.NewId() 316 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details'))) 317 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details) 318 319 item = self.__enc_context_popup.Append(-1, _('Edit progress notes')) 320 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item) 321 322 item = self.__enc_context_popup.Append(-1, _('Move progress notes')) 323 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item) 324 325 item = self.__enc_context_popup.Append(-1, _('Export for Medistar')) 326 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item) 327 328 # - health issues 329 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:')) 330 331 menu_id = wx.NewId() 332 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details'))) 333 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue) 334 335 menu_id = wx.NewId() 336 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete'))) 337 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue) 338 339 self.__issue_context_popup.AppendSeparator() 340 341 menu_id = wx.NewId() 342 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level'))) 343 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level) 344 # print " attach issue to another patient" 345 # print " move all episodes to another issue" 346 347 # - root node 348 self.__root_context_popup = wx.Menu(title = _('EMR Actions:')) 349 350 menu_id = wx.NewId() 351 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue'))) 352 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue) 353 354 item = self.__root_context_popup.Append(-1, _('Create episode')) 355 self.Bind(wx.EVT_MENU, self.__create_episode, item) 356 357 menu_id = wx.NewId() 358 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies'))) 359 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy) 360 361 menu_id = wx.NewId() 362 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history'))) 363 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history) 364 365 menu_id = wx.NewId() 366 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations'))) 367 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays) 368 369 menu_id = wx.NewId() 370 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation'))) 371 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation) 372 373 menu_id = wx.NewId() 374 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures'))) 375 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures) 376 377 menu_id = wx.NewId() 378 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations'))) 379 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations) 380 381 self.__root_context_popup.AppendSeparator() 382 383 # expand tree 384 expand_menu = wx.Menu() 385 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu) 386 387 menu_id = wx.NewId() 388 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level'))) 389 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level) 390 391 menu_id = wx.NewId() 392 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level'))) 393 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level) 394 395 menu_id = wx.NewId() 396 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level'))) 397 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
398 #--------------------------------------------------------
399 - def __handle_root_context(self, pos=wx.DefaultPosition):
400 self.PopupMenu(self.__root_context_popup, pos)
401 #--------------------------------------------------------
402 - def __handle_issue_context(self, pos=wx.DefaultPosition):
403 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description']) 404 self.PopupMenu(self.__issue_context_popup, pos)
405 #--------------------------------------------------------
406 - def __handle_episode_context(self, pos=wx.DefaultPosition):
407 # self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description']) 408 self.PopupMenu(self.__epi_context_popup, pos)
409 #--------------------------------------------------------
410 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
411 self.PopupMenu(self.__enc_context_popup, pos)
412 #-------------------------------------------------------- 413 # episode level 414 #--------------------------------------------------------
415 - def __move_encounters(self, event):
416 episode = self.GetPyData(self.__curr_node) 417 418 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 419 parent = self, 420 episodes = [episode['pk_episode']], 421 move_all = True 422 )
423 #--------------------------------------------------------
424 - def __edit_episode(self, event):
425 gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
426 #--------------------------------------------------------
427 - def __promote_episode_to_issue(self, evt):
428 pat = gmPerson.gmCurrentPatient() 429 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
430 #--------------------------------------------------------
431 - def __delete_episode(self, event):
432 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 433 parent = self, 434 id = -1, 435 caption = _('Deleting episode'), 436 button_defs = [ 437 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')}, 438 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')} 439 ], 440 question = _( 441 'Are you sure you want to delete this episode ?\n' 442 '\n' 443 ' "%s"\n' 444 ) % self.__curr_node_data['description'] 445 ) 446 result = dlg.ShowModal() 447 if result != wx.ID_YES: 448 return 449 450 try: 451 gmEMRStructItems.delete_episode(episode = self.__curr_node_data) 452 except gmExceptions.DatabaseObjectInUseError: 453 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.')) 454 return
455 #-------------------------------------------------------- 456 # encounter level 457 #--------------------------------------------------------
458 - def __move_progress_notes(self, evt):
459 encounter = self.GetPyData(self.__curr_node) 460 node_parent = self.GetItemParent(self.__curr_node) 461 episode = self.GetPyData(node_parent) 462 463 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 464 parent = self, 465 encounters = [encounter['pk_encounter']], 466 episodes = [episode['pk_episode']] 467 )
468 #--------------------------------------------------------
469 - def __edit_progress_notes(self, event):
470 encounter = self.GetPyData(self.__curr_node) 471 node_parent = self.GetItemParent(self.__curr_node) 472 episode = self.GetPyData(node_parent) 473 474 gmNarrativeWidgets.manage_progress_notes ( 475 parent = self, 476 encounters = [encounter['pk_encounter']], 477 episodes = [episode['pk_episode']] 478 )
479 #--------------------------------------------------------
480 - def __edit_encounter_details(self, event):
481 node_data = self.GetPyData(self.__curr_node) 482 gmEMRStructWidgets.edit_encounter(parent = self, encounter = node_data) 483 self.__populate_tree()
484 #-------------------------------------------------------- 502 #-------------------------------------------------------- 503 # issue level 504 #--------------------------------------------------------
505 - def __edit_issue(self, event):
506 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
507 #--------------------------------------------------------
508 - def __delete_issue(self, event):
509 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 510 parent = self, 511 id = -1, 512 caption = _('Deleting health issue'), 513 button_defs = [ 514 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 515 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 516 ], 517 question = _( 518 'Are you sure you want to delete this health issue ?\n' 519 '\n' 520 ' "%s"\n' 521 ) % self.__curr_node_data['description'] 522 ) 523 result = dlg.ShowModal() 524 if result != wx.ID_YES: 525 dlg.Destroy() 526 return 527 528 dlg.Destroy() 529 530 try: 531 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 532 except gmExceptions.DatabaseObjectInUseError: 533 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
534 #--------------------------------------------------------
535 - def __expand_issue_to_encounter_level(self, evt):
536 537 if not self.__curr_node.IsOk(): 538 return 539 540 self.Expand(self.__curr_node) 541 542 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 543 while epi.IsOk(): 544 self.Expand(epi) 545 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
546 #-------------------------------------------------------- 547 # EMR level 548 #--------------------------------------------------------
549 - def __create_issue(self, event):
550 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
551 #--------------------------------------------------------
552 - def __create_episode(self, event):
553 gmEMRStructWidgets.edit_episode(parent = self, episode = None)
554 #--------------------------------------------------------
555 - def __document_allergy(self, event):
556 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 557 # FIXME: use signal and use node level update 558 if dlg.ShowModal() == wx.ID_OK: 559 self.__populate_tree() 560 dlg.Destroy() 561 return
562 #--------------------------------------------------------
563 - def __manage_procedures(self, event):
565 #--------------------------------------------------------
566 - def __manage_family_history(self, event):
568 #--------------------------------------------------------
569 - def __manage_hospital_stays(self, event):
571 #--------------------------------------------------------
572 - def __manage_occupation(self, event):
574 #--------------------------------------------------------
575 - def __manage_vaccinations(self, event):
576 gmVaccWidgets.manage_vaccinations(parent = self)
577 #--------------------------------------------------------
578 - def __expand_to_issue_level(self, evt):
579 580 root_item = self.GetRootItem() 581 582 if not root_item.IsOk(): 583 return 584 585 self.Expand(root_item) 586 587 # collapse episodes and issues 588 issue, issue_cookie = self.GetFirstChild(root_item) 589 while issue.IsOk(): 590 self.Collapse(issue) 591 epi, epi_cookie = self.GetFirstChild(issue) 592 while epi.IsOk(): 593 self.Collapse(epi) 594 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 595 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
596 #--------------------------------------------------------
597 - def __expand_to_episode_level(self, evt):
598 599 root_item = self.GetRootItem() 600 601 if not root_item.IsOk(): 602 return 603 604 self.Expand(root_item) 605 606 # collapse episodes, expand issues 607 issue, issue_cookie = self.GetFirstChild(root_item) 608 while issue.IsOk(): 609 self.Expand(issue) 610 epi, epi_cookie = self.GetFirstChild(issue) 611 while epi.IsOk(): 612 self.Collapse(epi) 613 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 614 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
615 #--------------------------------------------------------
616 - def __expand_to_encounter_level(self, evt):
617 618 root_item = self.GetRootItem() 619 620 if not root_item.IsOk(): 621 return 622 623 self.Expand(root_item) 624 625 # collapse episodes, expand issues 626 issue, issue_cookie = self.GetFirstChild(root_item) 627 while issue.IsOk(): 628 self.Expand(issue) 629 epi, epi_cookie = self.GetFirstChild(issue) 630 while epi.IsOk(): 631 self.Expand(epi) 632 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 633 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
634 #--------------------------------------------------------
635 - def __export_encounter_for_medistar(self, evt):
636 gmNarrativeWidgets.export_narrative_for_medistar_import ( 637 parent = self, 638 soap_cats = u'soap', 639 encounter = self.__curr_node_data 640 )
641 #-------------------------------------------------------- 642 # event handlers 643 #--------------------------------------------------------
644 - def _on_narrative_mod_db(self, *args, **kwargs):
645 wx.CallAfter(self.__update_text_for_selected_node)
646 #--------------------------------------------------------
647 - def _on_episode_mod_db(self, *args, **kwargs):
648 wx.CallAfter(self.__populate_tree)
649 #--------------------------------------------------------
650 - def _on_issue_mod_db(self, *args, **kwargs):
651 wx.CallAfter(self.__populate_tree)
652 #--------------------------------------------------------
653 - def _on_tree_item_selected(self, event):
654 sel_item = event.GetItem() 655 self.__curr_node = sel_item 656 self.__update_text_for_selected_node() 657 return True
658 # #-------------------------------------------------------- 659 # def _on_mouse_motion(self, event): 660 # 661 # cursor_pos = (event.GetX(), event.GetY()) 662 # 663 # self.SetToolTipString(u'') 664 # 665 # if cursor_pos != self._old_cursor_pos: 666 # self._old_cursor_pos = cursor_pos 667 # (item, flags) = self.HitTest(cursor_pos) 668 # #if flags != wx.TREE_HITTEST_NOWHERE: 669 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 670 # data = self.GetPyData(item) 671 # 672 # if not isinstance(data, gmEMRStructItems.cEncounter): 673 # return 674 # 675 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 676 # data['started'].strftime('%x'), 677 # data['l10n_type'], 678 # data['started'].strftime('%H:%m'), 679 # data['last_affirmed'].strftime('%H:%m'), 680 # gmTools.coalesce(data['reason_for_encounter'], u''), 681 # gmTools.coalesce(data['assessment_of_encounter'], u'') 682 # )) 683 #--------------------------------------------------------
684 - def _on_tree_item_gettooltip(self, event):
685 686 item = event.GetItem() 687 688 if not item.IsOk(): 689 event.SetToolTip(u' ') 690 return 691 692 data = self.GetPyData(item) 693 694 if isinstance(data, gmEMRStructItems.cEncounter): 695 tt = u'%s %s %s - %s\n' % ( 696 data['started'].strftime('%x'), 697 data['l10n_type'], 698 data['started'].strftime('%H:%M'), 699 data['last_affirmed'].strftime('%H:%M') 700 ) 701 if data['reason_for_encounter'] is not None: 702 tt += u'\n' 703 tt += _('RFE: %s') % data['reason_for_encounter'] 704 if len(data['pk_generic_codes_rfe']) > 0: 705 for code in data.generic_codes_rfe: 706 tt += u'\n %s: %s%s%s\n (%s %s)' % ( 707 code['code'], 708 gmTools.u_left_double_angle_quote, 709 code['term'], 710 gmTools.u_right_double_angle_quote, 711 code['name_short'], 712 code['version'] 713 ) 714 if data['assessment_of_encounter'] is not None: 715 tt += u'\n' 716 tt += _('AOE: %s') % data['assessment_of_encounter'] 717 if len(data['pk_generic_codes_aoe']) > 0: 718 for code in data.generic_codes_aoe: 719 tt += u'\n %s: %s%s%s\n (%s %s)' % ( 720 code['code'], 721 gmTools.u_left_double_angle_quote, 722 code['term'], 723 gmTools.u_right_double_angle_quote, 724 code['name_short'], 725 code['version'] 726 ) 727 728 elif isinstance(data, gmEMRStructItems.cEpisode): 729 tt = u'' 730 tt += gmTools.bool2subst ( 731 (data['diagnostic_certainty_classification'] is not None), 732 data.diagnostic_certainty_description + u'\n\n', 733 u'' 734 ) 735 tt += gmTools.bool2subst ( 736 data['episode_open'], 737 _('ongoing episode'), 738 _('closed episode'), 739 'error: episode state is None' 740 ) + u'\n' 741 tt += gmTools.coalesce(data['summary'], u'', u'\n%s') 742 if len(data['pk_generic_codes']) > 0: 743 tt += u'\n' 744 for code in data.generic_codes: 745 tt += u'%s: %s%s%s\n (%s %s)\n' % ( 746 code['code'], 747 gmTools.u_left_double_angle_quote, 748 code['term'], 749 gmTools.u_right_double_angle_quote, 750 code['name_short'], 751 code['version'] 752 ) 753 754 tt = tt.strip(u'\n') 755 if tt == u'': 756 tt = u' ' 757 758 elif isinstance(data, gmEMRStructItems.cHealthIssue): 759 tt = u'' 760 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 761 tt += gmTools.bool2subst ( 762 (data['diagnostic_certainty_classification'] is not None), 763 data.diagnostic_certainty_description + u'\n', 764 u'' 765 ) 766 tt += gmTools.bool2subst ( 767 (data['laterality'] not in [None, u'na']), 768 data.laterality_description + u'\n', 769 u'' 770 ) 771 # noted_at_age is too costly 772 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 773 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 774 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 775 tt += gmTools.coalesce(data['grouping'], u'\n', _('Grouping: %s') + u'\n') 776 tt += gmTools.coalesce(data['summary'], u'', u'\n%s') 777 if len(data['pk_generic_codes']) > 0: 778 tt += u'\n' 779 for code in data.generic_codes: 780 tt += u'%s: %s%s%s\n (%s %s)\n' % ( 781 code['code'], 782 gmTools.u_left_double_angle_quote, 783 code['term'], 784 gmTools.u_right_double_angle_quote, 785 code['name_short'], 786 code['version'] 787 ) 788 789 tt = tt.strip(u'\n') 790 if tt == u'': 791 tt = u' ' 792 793 else: 794 tt = self.__root_tooltip 795 796 event.SetToolTip(tt)
797 798 # doing this prevents the tooltip from showing at all 799 #event.Skip() 800 801 #widgetXY.GetToolTip().Enable(False) 802 # 803 #seems to work, supposing the tooltip is actually set for the widget, 804 #otherwise a test would be needed 805 #if widgetXY.GetToolTip(): 806 # widgetXY.GetToolTip().Enable(False) 807 #--------------------------------------------------------
808 - def _on_tree_item_right_clicked(self, event):
809 """Right button clicked: display the popup for the tree""" 810 811 node = event.GetItem() 812 self.SelectItem(node) 813 self.__curr_node_data = self.GetPyData(node) 814 self.__curr_node = node 815 816 pos = wx.DefaultPosition 817 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 818 self.__handle_issue_context(pos=pos) 819 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 820 self.__handle_episode_context(pos=pos) 821 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 822 self.__handle_encounter_context(pos=pos) 823 elif node == self.GetRootItem(): 824 self.__handle_root_context() 825 elif type(self.__curr_node_data) == type({}): 826 # ignore pseudo node "free-standing episodes" 827 pass 828 else: 829 print "error: unknown node type, no popup menu" 830 event.Skip()
831 #--------------------------------------------------------
832 - def OnCompareItems (self, node1=None, node2=None):
833 """Used in sorting items. 834 835 -1: 1 < 2 836 0: 1 = 2 837 1: 1 > 2 838 """ 839 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 840 841 if not node1: 842 _log.debug('invalid node 1') 843 return 0 844 if not node2: 845 _log.debug('invalid node 2') 846 return 0 847 848 if not node1.IsOk(): 849 _log.debug('invalid node 1') 850 return 0 851 if not node2.IsOk(): 852 _log.debug('invalid node 2') 853 return 0 854 855 item1 = self.GetPyData(node1) 856 item2 = self.GetPyData(node2) 857 858 # dummy health issue always on top 859 if isinstance(item1, type({})): 860 return -1 861 if isinstance(item2, type({})): 862 return 1 863 864 # encounters: reverse chronologically 865 if isinstance(item1, gmEMRStructItems.cEncounter): 866 if item1['started'] == item2['started']: 867 return 0 868 if item1['started'] > item2['started']: 869 return -1 870 return 1 871 872 # episodes: chronologically 873 if isinstance(item1, gmEMRStructItems.cEpisode): 874 start1 = item1.get_access_range()[0] 875 start2 = item2.get_access_range()[0] 876 if start1 == start2: 877 return 0 878 if start1 < start2: 879 return -1 880 return 1 881 882 # issues: alpha by grouping, no grouping at the bottom 883 if isinstance(item1, gmEMRStructItems.cHealthIssue): 884 885 # no grouping below grouping 886 if item1['grouping'] is None: 887 if item2['grouping'] is not None: 888 return 1 889 890 # grouping above no grouping 891 if item1['grouping'] is not None: 892 if item2['grouping'] is None: 893 return -1 894 895 # both no grouping: alpha on description 896 if (item1['grouping'] is None) and (item2['grouping'] is None): 897 if item1['description'].lower() < item2['description'].lower(): 898 return -1 899 if item1['description'].lower() > item2['description'].lower(): 900 return 1 901 return 0 902 903 # both with grouping: alpha on grouping, then alpha on description 904 if item1['grouping'] < item2['grouping']: 905 return -1 906 907 if item1['grouping'] > item2['grouping']: 908 return 1 909 910 if item1['description'].lower() < item2['description'].lower(): 911 return -1 912 913 if item1['description'].lower() > item2['description'].lower(): 914 return 1 915 916 return 0 917 918 _log.error('unknown item type during sorting EMR tree:') 919 _log.error('item1: %s', type(item1)) 920 _log.error('item2: %s', type(item2)) 921 922 return 0
923 #-------------------------------------------------------- 924 # properties 925 #--------------------------------------------------------
926 - def _get_details_display_mode(self):
927 return self.__details_display_mode
928
929 - def _set_details_display_mode(self, mode):
930 if mode not in [u'details', u'journal']: 931 raise ValueError('details display mode must be one of "details", "journal"') 932 if self.__details_display_mode == mode: 933 return 934 self.__details_display_mode = mode 935 self.__update_text_for_selected_node()
936 937 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
938 #================================================================ 939 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl 940
941 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
942 """A scrollable panel holding an EMR tree. 943 944 Lacks a widget to display details for selected items. The 945 tree data will be refetched - if necessary - whenever 946 repopulate_ui() is called, e.g., when then patient is changed. 947 """
948 - def __init__(self, *args, **kwds):
950 #--------------------------------------------------------
951 - def repopulate_ui(self):
952 self._emr_tree.refresh() 953 return True
954 #============================================================ 955 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl 956
957 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
958 """A splitter window holding an EMR tree. 959 960 The left hand side displays a scrollable EMR tree while 961 on the right details for selected items are displayed. 962 963 Expects to be put into a Notebook. 964 """
965 - def __init__(self, *args, **kwds):
966 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 967 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 968 self._pnl_emr_tree._emr_tree.set_image_display(image_display = self._PNL_visual_soap) 969 self._pnl_emr_tree._emr_tree.set_enable_display_mode_selection_callback(self.enable_display_mode_selection) 970 self.__register_events()
971 #--------------------------------------------------------
972 - def __register_events(self):
973 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 974 return True
975 #-------------------------------------------------------- 976 # event handler 977 #--------------------------------------------------------
978 - def _on_post_patient_selection(self):
979 if self.GetParent().GetCurrentPage() == self: 980 self.repopulate_ui() 981 return True
982 #--------------------------------------------------------
983 - def _on_show_details_selected(self, event):
984 #event.Skip() 985 self._pnl_emr_tree._emr_tree.details_display_mode = u'details'
986 #--------------------------------------------------------
987 - def _on_show_journal_selected(self, event):
988 #event.Skip() 989 self._pnl_emr_tree._emr_tree.details_display_mode = u'journal'
990 #-------------------------------------------------------- 991 # external API 992 #--------------------------------------------------------
993 - def repopulate_ui(self):
994 """Fills UI with data.""" 995 self._pnl_emr_tree.repopulate_ui() 996 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 997 return True
998 #--------------------------------------------------------
999 - def enable_display_mode_selection(self, enable):
1000 if enable: 1001 self._RBTN_details.Enable(True) 1002 self._RBTN_journal.Enable(True) 1003 return 1004 self._RBTN_details.Enable(False) 1005 self._RBTN_journal.Enable(False)
1006 #================================================================
1007 -class cEMRJournalPanel(wx.Panel):
1008 - def __init__(self, *args, **kwargs):
1009 wx.Panel.__init__(self, *args, **kwargs) 1010 1011 self.__do_layout() 1012 self.__register_events()
1013 #--------------------------------------------------------
1014 - def __do_layout(self):
1015 self.__journal = wx.TextCtrl ( 1016 self, 1017 -1, 1018 _('No EMR data loaded.'), 1019 style = wx.TE_MULTILINE | wx.TE_READONLY 1020 ) 1021 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 1022 # arrange widgets 1023 szr_outer = wx.BoxSizer(wx.VERTICAL) 1024 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 1025 # and do layout 1026 self.SetAutoLayout(1) 1027 self.SetSizer(szr_outer) 1028 szr_outer.Fit(self) 1029 szr_outer.SetSizeHints(self) 1030 self.Layout()
1031 #--------------------------------------------------------
1032 - def __register_events(self):
1033 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1034 #--------------------------------------------------------
1035 - def _on_post_patient_selection(self):
1036 """Expects to be in a Notebook.""" 1037 if self.GetParent().GetCurrentPage() == self: 1038 self.repopulate_ui() 1039 return True
1040 #-------------------------------------------------------- 1041 # notebook plugin API 1042 #--------------------------------------------------------
1043 - def repopulate_ui(self):
1044 txt = StringIO.StringIO() 1045 exporter = gmPatientExporter.cEMRJournalExporter() 1046 # FIXME: if journal is large this will error out, use generator/yield etc 1047 # FIXME: turn into proper list 1048 try: 1049 exporter.export(txt) 1050 self.__journal.SetValue(txt.getvalue()) 1051 except ValueError: 1052 _log.exception('cannot get EMR journal') 1053 self.__journal.SetValue (_( 1054 'An error occurred while retrieving the EMR\n' 1055 'in journal form for the active patient.\n\n' 1056 'Please check the log file for details.' 1057 )) 1058 txt.close() 1059 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 1060 return True
1061 #================================================================ 1062 # MAIN 1063 #---------------------------------------------------------------- 1064 if __name__ == '__main__': 1065 1066 _log.info("starting emr browser...") 1067 1068 try: 1069 # obtain patient 1070 patient = gmPersonSearch.ask_for_patient() 1071 if patient is None: 1072 print "No patient. Exiting gracefully..." 1073 sys.exit(0) 1074 gmPatSearchWidgets.set_active_patient(patient = patient) 1075 1076 # display standalone browser 1077 application = wx.PyWidgetTester(size=(800,600)) 1078 emr_browser = cEMRBrowserPanel(application.frame, -1) 1079 emr_browser.refresh_tree() 1080 1081 application.frame.Show(True) 1082 application.MainLoop() 1083 1084 # clean up 1085 if patient is not None: 1086 try: 1087 patient.cleanup() 1088 except: 1089 print "error cleaning up patient" 1090 except StandardError: 1091 _log.exception("unhandled exception caught !") 1092 # but re-raise them 1093 raise 1094 1095 _log.info("closing emr browser...") 1096 1097 #================================================================ 1098