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  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRBrowser.py,v $ 
   5  # $Id: gmEMRBrowser.py,v 1.111 2010/01/11 19:44:39 ncq Exp $ 
   6  __version__ = "$Revision: 1.111 $" 
   7  __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net" 
   8  __license__ = "GPL" 
   9   
  10  # std lib 
  11  import sys, types, os.path, StringIO, codecs, logging 
  12   
  13  # 3rd party 
  14  import wx 
  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 
  20  from Gnumed.wxpython import gmGuiHelpers, gmEMRStructWidgets, gmSOAPWidgets, gmAllergyWidgets, gmNarrativeWidgets, gmPatSearchWidgets 
  21  from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl, wxgSplittedEMRTreeBrowserPnl 
  22   
  23  _log = logging.getLogger('gm.ui') 
  24  _log.info(__version__) 
  25   
  26  #============================================================ 
27 -def export_emr_to_ascii(parent=None):
28 """ 29 Dump the patient's EMR from GUI client 30 @param parent - The parent widget 31 @type parent - A wx.Window instance 32 """ 33 # sanity checks 34 if parent is None: 35 raise TypeError('expected wx.Window instance as parent, got <None>') 36 37 pat = gmPerson.gmCurrentPatient() 38 if not pat.connected: 39 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.')) 40 return False 41 42 # get file name 43 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 44 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))) 45 gmTools.mkdir(defdir) 46 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames']) 47 dlg = wx.FileDialog ( 48 parent = parent, 49 message = _("Save patient's EMR as..."), 50 defaultDir = defdir, 51 defaultFile = fname, 52 wildcard = wc, 53 style = wx.SAVE 54 ) 55 choice = dlg.ShowModal() 56 fname = dlg.GetPath() 57 dlg.Destroy() 58 if choice != wx.ID_OK: 59 return None 60 61 _log.debug('exporting EMR to [%s]', fname) 62 63 # output_file = open(fname, 'wb') 64 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace') 65 exporter = gmPatientExporter.cEmrExport(patient = pat) 66 exporter.set_output_file(output_file) 67 exporter.dump_constraints() 68 exporter.dump_demographic_record(True) 69 exporter.dump_clinical_record() 70 exporter.dump_med_docs() 71 output_file.close() 72 73 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False) 74 return fname
75 #============================================================
76 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
77 """This wx.TreeCtrl derivative displays a tree view of the medical record.""" 78 79 #--------------------------------------------------------
80 - def __init__(self, parent, id, *args, **kwds):
81 """Set up our specialised tree. 82 """ 83 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER 84 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 85 86 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self) 87 88 try: 89 self.__narr_display = kwds['narr_display'] 90 del kwds['narr_display'] 91 except KeyError: 92 self.__narr_display = None 93 self.__pat = gmPerson.gmCurrentPatient() 94 self.__curr_node = None 95 self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat) 96 97 self._old_cursor_pos = None 98 99 self.__make_popup_menus() 100 self.__register_events()
101 #-------------------------------------------------------- 102 # external API 103 #--------------------------------------------------------
104 - def refresh(self):
105 if not self.__pat.connected: 106 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),) 107 return False 108 109 if not self.__populate_tree(): 110 return False 111 112 return True
113 #--------------------------------------------------------
114 - def set_narrative_display(self, narrative_display=None):
115 self.__narr_display = narrative_display
116 #-------------------------------------------------------- 117 # internal helpers 118 #--------------------------------------------------------
119 - def __register_events(self):
120 """Configures enabled event signals.""" 121 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected) 122 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked) 123 124 # handle tooltips 125 # wx.EVT_MOTION(self, self._on_mouse_motion) 126 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip) 127 128 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db) 129 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db) 130 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
131 #--------------------------------------------------------
132 - def __populate_tree(self):
133 """Updates EMR browser data.""" 134 # FIXME: auto select the previously self.__curr_node if not None 135 # FIXME: error handling 136 137 wx.BeginBusyCursor() 138 139 self.snapshot_expansion() 140 141 # init new tree 142 self.DeleteAllItems() 143 root_item = self.AddRoot(_('EMR of %s') % self.__pat['description']) 144 self.SetPyData(root_item, None) 145 self.SetItemHasChildren(root_item, True) 146 147 # have the tree filled by the exporter 148 self.__exporter.get_historical_tree(self) 149 self.__curr_node = root_item 150 151 self.SelectItem(root_item) 152 self.Expand(root_item) 153 self.__update_text_for_selected_node() 154 155 self.restore_expansion() 156 157 wx.EndBusyCursor() 158 return True
159 #--------------------------------------------------------
161 """Displays information for the selected tree node.""" 162 163 if self.__narr_display is None: 164 return 165 166 if self.__curr_node is None: 167 return 168 169 node_data = self.GetPyData(self.__curr_node) 170 171 # update displayed text 172 if isinstance(node_data, (gmEMRStructItems.cHealthIssue, types.DictType)): 173 # FIXME: turn into real dummy issue 174 if node_data['pk_health_issue'] is None: 175 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description'] 176 else: 177 txt = node_data.format(left_margin=1, patient = self.__pat) 178 179 elif isinstance(node_data, gmEMRStructItems.cEpisode): 180 txt = node_data.format(left_margin = 1, patient = self.__pat) 181 182 elif isinstance(node_data, gmEMRStructItems.cEncounter): 183 epi = self.GetPyData(self.GetItemParent(self.__curr_node)) 184 txt = node_data.format(episodes = [epi['pk_episode']], with_soap = True, left_margin = 1, patient = self.__pat) 185 186 else: 187 emr = self.__pat.get_emr() 188 txt = emr.format_summary() 189 190 self.__narr_display.Clear() 191 self.__narr_display.WriteText(txt)
192 #--------------------------------------------------------
193 - def __make_popup_menus(self):
194 195 # - episodes 196 self.__epi_context_popup = wx.Menu(title = _('Episode Menu')) 197 198 menu_id = wx.NewId() 199 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details'))) 200 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode) 201 202 menu_id = wx.NewId() 203 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete'))) 204 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode) 205 206 menu_id = wx.NewId() 207 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote'))) 208 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue) 209 210 menu_id = wx.NewId() 211 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters'))) 212 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters) 213 214 # - encounters 215 self.__enc_context_popup = wx.Menu(title = _('Encounter Menu')) 216 # - move data 217 menu_id = wx.NewId() 218 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode'))) 219 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode) 220 # - edit encounter details 221 menu_id = wx.NewId() 222 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details'))) 223 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details) 224 225 item = self.__enc_context_popup.Append(-1, _('Edit progress notes')) 226 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item) 227 228 item = self.__enc_context_popup.Append(-1, _('Move progress notes')) 229 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item) 230 231 item = self.__enc_context_popup.Append(-1, _('Export for Medistar')) 232 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item) 233 234 # - health issues 235 self.__issue_context_popup = wx.Menu(title = _('Health Issue Menu')) 236 237 menu_id = wx.NewId() 238 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details'))) 239 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue) 240 241 menu_id = wx.NewId() 242 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete'))) 243 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue) 244 245 self.__issue_context_popup.AppendSeparator() 246 247 menu_id = wx.NewId() 248 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level'))) 249 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level) 250 # print " attach issue to another patient" 251 # print " move all episodes to another issue" 252 253 # - root node 254 self.__root_context_popup = wx.Menu(title = _('EMR Menu')) 255 # add health issue 256 menu_id = wx.NewId() 257 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('create health issue'))) 258 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue) 259 # add allergy 260 menu_id = wx.NewId() 261 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('manage allergies'))) 262 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy) 263 264 self.__root_context_popup.AppendSeparator() 265 266 # expand tree 267 expand_menu = wx.Menu() 268 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu) 269 270 menu_id = wx.NewId() 271 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level'))) 272 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level) 273 274 menu_id = wx.NewId() 275 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level'))) 276 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level) 277 278 menu_id = wx.NewId() 279 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level'))) 280 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
281 #--------------------------------------------------------
282 - def __handle_root_context(self, pos=wx.DefaultPosition):
283 self.PopupMenu(self.__root_context_popup, pos)
284 #--------------------------------------------------------
285 - def __handle_issue_context(self, pos=wx.DefaultPosition):
286 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description']) 287 self.PopupMenu(self.__issue_context_popup, pos)
288 #--------------------------------------------------------
289 - def __handle_episode_context(self, pos=wx.DefaultPosition):
290 self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description']) 291 self.PopupMenu(self.__epi_context_popup, pos)
292 #--------------------------------------------------------
293 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
294 self.PopupMenu(self.__enc_context_popup, pos)
295 #-------------------------------------------------------- 296 # episode level 297 #--------------------------------------------------------
298 - def __move_encounters(self, event):
299 episode = self.GetPyData(self.__curr_node) 300 301 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 302 parent = self, 303 episodes = [episode['pk_episode']], 304 move_all = True 305 )
306 #--------------------------------------------------------
307 - def __edit_episode(self, event):
308 gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
309 #--------------------------------------------------------
310 - def __promote_episode_to_issue(self, evt):
311 pat = gmPerson.gmCurrentPatient() 312 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
313 #--------------------------------------------------------
314 - def __delete_episode(self, event):
315 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 316 parent = self, 317 id = -1, 318 caption = _('Deleting episode'), 319 button_defs = [ 320 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')}, 321 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')} 322 ], 323 question = _( 324 'Are you sure you want to delete this episode ?\n' 325 '\n' 326 ' "%s"\n' 327 ) % self.__curr_node_data['description'] 328 ) 329 result = dlg.ShowModal() 330 if result != wx.ID_YES: 331 return 332 333 try: 334 gmEMRStructItems.delete_episode(episode = self.__curr_node_data) 335 except gmExceptions.DatabaseObjectInUseError: 336 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.')) 337 return
338 #-------------------------------------------------------- 339 # encounter level 340 #--------------------------------------------------------
341 - def __move_progress_notes(self, evt):
342 encounter = self.GetPyData(self.__curr_node) 343 node_parent = self.GetItemParent(self.__curr_node) 344 episode = self.GetPyData(node_parent) 345 346 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 347 parent = self, 348 encounters = [encounter['pk_encounter']], 349 episodes = [episode['pk_episode']] 350 )
351 #--------------------------------------------------------
352 - def __edit_progress_notes(self, event):
353 encounter = self.GetPyData(self.__curr_node) 354 node_parent = self.GetItemParent(self.__curr_node) 355 episode = self.GetPyData(node_parent) 356 357 gmNarrativeWidgets.manage_progress_notes ( 358 parent = self, 359 encounters = [encounter['pk_encounter']], 360 episodes = [episode['pk_episode']] 361 )
362 #--------------------------------------------------------
363 - def __edit_encounter_details(self, event):
364 node_data = self.GetPyData(self.__curr_node) 365 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=node_data) 366 dlg.ShowModal() 367 dlg.Destroy() 368 self.__populate_tree()
369 #-------------------------------------------------------- 387 #--------------------------------------------------------
388 - def __edit_issue(self, event):
389 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
390 #--------------------------------------------------------
391 - def __delete_issue(self, event):
392 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 393 parent = self, 394 id = -1, 395 caption = _('Deleting health issue'), 396 button_defs = [ 397 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 398 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 399 ], 400 question = _( 401 'Are you sure you want to delete this health issue ?\n' 402 '\n' 403 ' "%s"\n' 404 ) % self.__curr_node_data['description'] 405 ) 406 result = dlg.ShowModal() 407 if result != wx.ID_YES: 408 dlg.Destroy() 409 return 410 411 dlg.Destroy() 412 413 try: 414 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 415 except gmExceptions.DatabaseObjectInUseError: 416 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
417 #--------------------------------------------------------
418 - def __expand_issue_to_encounter_level(self, evt):
419 420 if not self.__curr_node.IsOk(): 421 return 422 423 self.Expand(self.__curr_node) 424 425 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 426 while epi.IsOk(): 427 self.Expand(epi) 428 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
429 #--------------------------------------------------------
430 - def __create_issue(self, event):
431 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
432 #--------------------------------------------------------
433 - def __document_allergy(self, event):
434 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 435 # FIXME: use signal and use node level update 436 if dlg.ShowModal() == wx.ID_OK: 437 self.__populate_tree() 438 dlg.Destroy() 439 return
440 #--------------------------------------------------------
441 - def __expand_to_issue_level(self, evt):
442 443 root_item = self.GetRootItem() 444 445 if not root_item.IsOk(): 446 return 447 448 self.Expand(root_item) 449 450 # collapse episodes and issues 451 issue, issue_cookie = self.GetFirstChild(root_item) 452 while issue.IsOk(): 453 self.Collapse(issue) 454 epi, epi_cookie = self.GetFirstChild(issue) 455 while epi.IsOk(): 456 self.Collapse(epi) 457 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 458 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
459 #--------------------------------------------------------
460 - def __expand_to_episode_level(self, evt):
461 462 root_item = self.GetRootItem() 463 464 if not root_item.IsOk(): 465 return 466 467 self.Expand(root_item) 468 469 # collapse episodes, expand issues 470 issue, issue_cookie = self.GetFirstChild(root_item) 471 while issue.IsOk(): 472 self.Expand(issue) 473 epi, epi_cookie = self.GetFirstChild(issue) 474 while epi.IsOk(): 475 self.Collapse(epi) 476 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 477 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
478 #--------------------------------------------------------
479 - def __expand_to_encounter_level(self, evt):
480 481 root_item = self.GetRootItem() 482 483 if not root_item.IsOk(): 484 return 485 486 self.Expand(root_item) 487 488 # collapse episodes, expand issues 489 issue, issue_cookie = self.GetFirstChild(root_item) 490 while issue.IsOk(): 491 self.Expand(issue) 492 epi, epi_cookie = self.GetFirstChild(issue) 493 while epi.IsOk(): 494 self.Expand(epi) 495 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 496 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
497 #--------------------------------------------------------
498 - def __export_encounter_for_medistar(self, evt):
499 gmNarrativeWidgets.export_narrative_for_medistar_import ( 500 parent = self, 501 soap_cats = u'soap', 502 encounter = self.__curr_node_data 503 )
504 #-------------------------------------------------------- 505 # event handlers 506 #--------------------------------------------------------
507 - def _on_narrative_mod_db(self, *args, **kwargs):
508 wx.CallAfter(self.__update_text_for_selected_node)
509 #--------------------------------------------------------
510 - def _on_episode_mod_db(self, *args, **kwargs):
511 wx.CallAfter(self.__populate_tree)
512 #--------------------------------------------------------
513 - def _on_issue_mod_db(self, *args, **kwargs):
514 wx.CallAfter(self.__populate_tree)
515 #--------------------------------------------------------
516 - def _on_tree_item_selected(self, event):
517 sel_item = event.GetItem() 518 self.__curr_node = sel_item 519 self.__update_text_for_selected_node() 520 return True
521 # #-------------------------------------------------------- 522 # def _on_mouse_motion(self, event): 523 # 524 # cursor_pos = (event.GetX(), event.GetY()) 525 # 526 # self.SetToolTipString(u'') 527 # 528 # if cursor_pos != self._old_cursor_pos: 529 # self._old_cursor_pos = cursor_pos 530 # (item, flags) = self.HitTest(cursor_pos) 531 # #if flags != wx.TREE_HITTEST_NOWHERE: 532 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 533 # data = self.GetPyData(item) 534 # 535 # if not isinstance(data, gmEMRStructItems.cEncounter): 536 # return 537 # 538 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 539 # data['started'].strftime('%x'), 540 # data['l10n_type'], 541 # data['started'].strftime('%H:%m'), 542 # data['last_affirmed'].strftime('%H:%m'), 543 # gmTools.coalesce(data['reason_for_encounter'], u''), 544 # gmTools.coalesce(data['assessment_of_encounter'], u'') 545 # )) 546 #--------------------------------------------------------
547 - def _on_tree_item_gettooltip(self, event):
548 549 item = event.GetItem() 550 551 if not item.IsOk(): 552 event.SetToolTip(u' ') 553 return 554 555 data = self.GetPyData(item) 556 557 if isinstance(data, gmEMRStructItems.cEncounter): 558 event.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 559 data['started'].strftime('%x'), 560 data['l10n_type'], 561 data['started'].strftime('%H:%M'), 562 data['last_affirmed'].strftime('%H:%M'), 563 gmTools.coalesce(data['reason_for_encounter'], u''), 564 gmTools.coalesce(data['assessment_of_encounter'], u'') 565 )) 566 567 elif isinstance(data, gmEMRStructItems.cEpisode): 568 tt = u'' 569 tt += gmTools.bool2subst ( 570 (data['diagnostic_certainty_classification'] is not None), 571 data.diagnostic_certainty_description + u'\n\n', 572 u'' 573 ) 574 tt += gmTools.bool2subst ( 575 data['episode_open'], 576 _('ongoing episode'), 577 _('closed episode'), 578 'error: episode state is None' 579 ) 580 event.SetToolTip(tt) 581 582 elif isinstance(data, gmEMRStructItems.cHealthIssue): 583 tt = u'' 584 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 585 tt += gmTools.bool2subst ( 586 (data['diagnostic_certainty_classification'] is not None), 587 data.diagnostic_certainty_description + u'\n', 588 u'' 589 ) 590 tt += gmTools.bool2subst ( 591 (data['laterality'] not in [None, u'na']), 592 data.laterality_description + u'\n', 593 u'' 594 ) 595 # noted_at_age is too costly 596 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 597 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 598 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 599 tt += gmTools.coalesce(data['grouping'], u'', _('Grouping: %s')) 600 event.SetToolTip(tt) 601 602 else: 603 event.SetToolTip(u' ')
604 #self.SetToolTipString(u'') 605 606 # doing this prevents the tooltip from showing at all 607 #event.Skip() 608 609 #widgetXY.GetToolTip().Enable(False) 610 # 611 #seems to work, supposing the tooltip is actually set for the widget, 612 #otherwise a test would be needed 613 #if widgetXY.GetToolTip(): 614 # widgetXY.GetToolTip().Enable(False) 615 #--------------------------------------------------------
616 - def _on_tree_item_right_clicked(self, event):
617 """Right button clicked: display the popup for the tree""" 618 619 node = event.GetItem() 620 self.SelectItem(node) 621 self.__curr_node_data = self.GetPyData(node) 622 self.__curr_node = node 623 624 pos = wx.DefaultPosition 625 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 626 self.__handle_issue_context(pos=pos) 627 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 628 self.__handle_episode_context(pos=pos) 629 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 630 self.__handle_encounter_context(pos=pos) 631 elif node == self.GetRootItem(): 632 self.__handle_root_context() 633 elif type(self.__curr_node_data) == type({}): 634 # ignore pseudo node "free-standing episodes" 635 pass 636 else: 637 print "error: unknown node type, no popup menu" 638 event.Skip()
639 #--------------------------------------------------------
640 - def OnCompareItems (self, node1=None, node2=None):
641 """Used in sorting items. 642 643 -1: 1 < 2 644 0: 1 = 2 645 1: 1 > 2 646 """ 647 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 648 649 item1 = self.GetPyData(node1) 650 item2 = self.GetPyData(node2) 651 652 # encounters: reverse chron 653 if isinstance(item1, gmEMRStructItems.cEncounter): 654 if item1['started'] == item2['started']: 655 return 0 656 if item1['started'] > item2['started']: 657 return -1 658 return 1 659 660 # episodes: chron 661 if isinstance(item1, gmEMRStructItems.cEpisode): 662 start1 = item1.get_access_range()[0] 663 start2 = item2.get_access_range()[0] 664 if start1 == start2: 665 return 0 666 if start1 < start2: 667 return -1 668 return 1 669 670 # issues: alpha by grouping, no grouping at the bottom 671 if isinstance(item1, gmEMRStructItems.cHealthIssue): 672 673 # no grouping below grouping 674 if item1['grouping'] is None: 675 if item2['grouping'] is not None: 676 return 1 677 678 # grouping above no grouping 679 if item1['grouping'] is not None: 680 if item2['grouping'] is None: 681 return -1 682 683 # both no grouping: alpha on description 684 if (item1['grouping'] is None) and (item2['grouping'] is None): 685 if item1['description'].lower() < item2['description'].lower(): 686 return -1 687 if item1['description'].lower() > item2['description'].lower(): 688 return 1 689 return 0 690 691 # both with grouping: alpha on grouping, then alpha on description 692 if item1['grouping'] < item2['grouping']: 693 return -1 694 695 if item1['grouping'] > item2['grouping']: 696 return 1 697 698 if item1['description'].lower() < item2['description'].lower(): 699 return -1 700 701 if item1['description'].lower() > item2['description'].lower(): 702 return 1 703 704 return 0 705 706 # dummy health issue always on top 707 if isinstance(item1, type({})): 708 return -1 709 710 return 0
711 #================================================================
712 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
713 """A scrollable panel holding an EMR tree. 714 715 Lacks a widget to display details for selected items. The 716 tree data will be refetched - if necessary - whenever 717 repopulate_ui() is called, e.g., when then patient is changed. 718 """
719 - def __init__(self, *args, **kwds):
720 wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl.__init__(self, *args, **kwds)
721 # self.__register_interests() 722 #-------------------------------------------------------- 723 # def __register_interests(self): 724 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui) 725 #--------------------------------------------------------
726 - def repopulate_ui(self):
727 self._emr_tree.refresh() 728 return True
729 #============================================================
730 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
731 """A splitter window holding an EMR tree. 732 733 The left hand side displays a scrollable EMR tree while 734 on the right details for selected items are displayed. 735 736 Expects to be put into a Notebook. 737 """
738 - def __init__(self, *args, **kwds):
739 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 740 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 741 self.__register_events()
742 #--------------------------------------------------------
743 - def __register_events(self):
744 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 745 return True
746 #--------------------------------------------------------
747 - def _on_post_patient_selection(self):
748 if self.GetParent().GetCurrentPage() == self: 749 self.repopulate_ui() 750 return True
751 #--------------------------------------------------------
752 - def repopulate_ui(self):
753 """Fills UI with data.""" 754 self._pnl_emr_tree.repopulate_ui() 755 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 756 return True
757 #================================================================
758 -class cEMRJournalPanel(wx.Panel):
759 - def __init__(self, *args, **kwargs):
760 wx.Panel.__init__(self, *args, **kwargs) 761 762 self.__do_layout() 763 self.__register_events()
764 #--------------------------------------------------------
765 - def __do_layout(self):
766 self.__journal = wx.TextCtrl ( 767 self, 768 -1, 769 _('No EMR data loaded.'), 770 style = wx.TE_MULTILINE | wx.TE_READONLY 771 ) 772 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 773 # arrange widgets 774 szr_outer = wx.BoxSizer(wx.VERTICAL) 775 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 776 # and do layout 777 self.SetAutoLayout(1) 778 self.SetSizer(szr_outer) 779 szr_outer.Fit(self) 780 szr_outer.SetSizeHints(self) 781 self.Layout()
782 #--------------------------------------------------------
783 - def __register_events(self):
784 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
785 #--------------------------------------------------------
786 - def _on_post_patient_selection(self):
787 """Expects to be in a Notebook.""" 788 if self.GetParent().GetCurrentPage() == self: 789 self.repopulate_ui() 790 return True
791 #-------------------------------------------------------- 792 # notebook plugin API 793 #--------------------------------------------------------
794 - def repopulate_ui(self):
795 txt = StringIO.StringIO() 796 exporter = gmPatientExporter.cEMRJournalExporter() 797 # FIXME: if journal is large this will error out, use generator/yield etc 798 # FIXME: turn into proper list 799 try: 800 exporter.export(txt) 801 self.__journal.SetValue(txt.getvalue()) 802 except ValueError: 803 _log.exception('cannot get EMR journal') 804 self.__journal.SetValue (_( 805 'An error occurred while retrieving the EMR\n' 806 'in journal form for the active patient.\n\n' 807 'Please check the log file for details.' 808 )) 809 txt.close() 810 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 811 return True
812 #================================================================ 813 # MAIN 814 #---------------------------------------------------------------- 815 if __name__ == '__main__': 816 817 _log.info("starting emr browser...") 818 819 try: 820 # obtain patient 821 patient = gmPerson.ask_for_patient() 822 if patient is None: 823 print "No patient. Exiting gracefully..." 824 sys.exit(0) 825 gmPatSearchWidgets.set_active_patient(patient = patient) 826 827 # display standalone browser 828 application = wx.PyWidgetTester(size=(800,600)) 829 emr_browser = cEMRBrowserPanel(application.frame, -1) 830 emr_browser.refresh_tree() 831 832 application.frame.Show(True) 833 application.MainLoop() 834 835 # clean up 836 if patient is not None: 837 try: 838 patient.cleanup() 839 except: 840 print "error cleaning up patient" 841 except StandardError: 842 _log.exception("unhandled exception caught !") 843 # but re-raise them 844 raise 845 846 _log.info("closing emr browser...") 847 848 #================================================================ 849 # $Log: gmEMRBrowser.py,v $ 850 # Revision 1.111 2010/01/11 19:44:39 ncq 851 # - cleanup 852 # 853 # Revision 1.110 2009/11/15 01:05:02 ncq 854 # - enable moving SOAP of a list of encounters of an episode 855 # 856 # Revision 1.109 2009/11/06 15:17:07 ncq 857 # - properly promote episode to issue 858 # - better tooltips 859 # 860 # Revision 1.108 2009/10/29 17:21:14 ncq 861 # - improved tooltips 862 # 863 # Revision 1.107 2009/10/20 10:26:21 ncq 864 # - tooltips on issue/episode in EMR tree 865 # 866 # Revision 1.106 2009/09/23 14:34:21 ncq 867 # - add promoting episode to issue 868 # 869 # Revision 1.105 2009/09/01 22:26:56 ncq 870 # - use new edit_episode/edit_health_issue 871 # 872 # Revision 1.104 2009/06/04 16:30:30 ncq 873 # - use set active patient from pat search widgets 874 # 875 # Revision 1.103 2009/05/13 12:19:13 ncq 876 # - use improved encounter management 877 # - add moving narrative between encounters 878 # 879 # Revision 1.102 2009/04/16 12:48:31 ncq 880 # - edit_progress_notes -> manage_* 881 # - use proper formatter when displaying encounter tooltip 882 # 883 # Revision 1.101 2009/04/05 18:04:46 ncq 884 # - support and use grouping 885 # 886 # Revision 1.100 2009/01/21 18:03:53 ncq 887 # - comment on tooltip handling 888 # 889 # Revision 1.99 2008/12/18 21:27:56 ncq 890 # - add editing progress notes from encounter context menu 891 # 892 # Revision 1.98 2008/12/01 12:37:37 ncq 893 # - generate encounter node tooltips 894 # 895 # Revision 1.97 2008/11/20 19:50:19 ncq 896 # - use improved data formatting 897 # 898 # Revision 1.96 2008/10/12 16:13:23 ncq 899 # - rename EMR tree root node, per Jim 900 # 901 # Revision 1.95 2008/09/02 19:01:11 ncq 902 # - adjust to clin health_issue fk_patient drop and related changes 903 # 904 # Revision 1.94 2008/08/31 18:39:47 ncq 905 # - if narrative is added, say a test result with comment, before 906 # the tree was ever displayed (hence not populated) for that patient 907 # there is not yet a currently selected node, so don't update 908 # the narrative display either 909 # 910 # Revision 1.93 2008/08/15 15:56:38 ncq 911 # - properly handle context click on pseudo-issue 912 # 913 # Revision 1.92 2008/07/28 15:44:39 ncq 914 # - context menu based Medistar export for any encounter 915 # 916 # Revision 1.91 2008/07/14 13:46:11 ncq 917 # - better naming of dummy health issue 918 # 919 # Revision 1.90 2008/07/12 15:31:23 ncq 920 # - improved formatting of issue info 921 # 922 # Revision 1.89 2008/07/07 13:44:33 ncq 923 # - current patient .connected 924 # - properly sort tree, encounters: most recent on top as per user request 925 # 926 # Revision 1.88 2008/06/28 18:25:58 ncq 927 # - add expand to ... level popup menu items in EMR tree 928 # 929 # Revision 1.87 2008/05/19 16:23:33 ncq 930 # - let EMR format its summary itself 931 # 932 # Revision 1.86 2008/04/11 12:27:45 ncq 933 # - listen to issue/episode/narrative change signals thereby 934 # reducing direct repopulate calls 935 # - factor out __update_text_for_selected_node() and 936 # call format() on nodes that have it 937 # - rearrange code layout 938 # 939 # Revision 1.85 2008/03/05 22:30:14 ncq 940 # - new style logging 941 # 942 # Revision 1.84 2008/01/30 14:07:24 ncq 943 # - do not use old cfg file support anymore 944 # 945 # Revision 1.83 2008/01/22 12:20:53 ncq 946 # - dummy health issue always on top 947 # - auto-scroll to bottom of journal 948 # 949 # Revision 1.82 2007/12/11 12:49:25 ncq 950 # - explicit signal handling 951 # 952 # Revision 1.81 2007/09/07 10:56:57 ncq 953 # - cleanup 954 # 955 # Revision 1.80 2007/08/29 22:09:10 ncq 956 # - narrative widgets factored out 957 # 958 # Revision 1.79 2007/08/15 14:57:52 ncq 959 # - pretty up tree popup menus 960 # - add deletion of health issues 961 # 962 # Revision 1.78 2007/08/12 00:09:07 ncq 963 # - no more gmSignals.py 964 # 965 # Revision 1.77 2007/06/18 20:31:10 ncq 966 # - case insensitively sort health issues 967 # 968 # Revision 1.76 2007/06/10 09:56:54 ncq 969 # - actually sort tree items, add sorting for health issues 970 # 971 # Revision 1.75 2007/05/21 14:48:20 ncq 972 # - cleanup 973 # - use pat['dirname'], use export/EMR/ 974 # - unicode output files 975 # 976 # Revision 1.74 2007/05/21 13:05:25 ncq 977 # - catch-all wildcard on UNIX must be *, not *.* 978 # 979 # Revision 1.73 2007/05/18 13:29:25 ncq 980 # - some cleanup 981 # - properly support moving narrative between episodes 982 # 983 # Revision 1.72 2007/05/14 13:11:24 ncq 984 # - use statustext() signal 985 # 986 # Revision 1.71 2007/05/14 10:33:33 ncq 987 # - allow deleting episode 988 # 989 # Revision 1.70 2007/03/18 14:04:00 ncq 990 # - add allergy handling to menu and root node of tree 991 # 992 # Revision 1.69 2007/03/02 15:31:45 ncq 993 # - properly repopulation EMR tree and problem list :-) 994 # 995 # Revision 1.68 2007/02/22 17:41:13 ncq 996 # - adjust to gmPerson changes 997 # 998 # Revision 1.67 2007/02/16 12:51:46 ncq 999 # - fix add issue popup on root node as requested by user :-) 1000 # 1001 # Revision 1.66 2007/01/16 18:00:59 ncq 1002 # - cleanup 1003 # - explicitely sort episodes and encounters by when they were started 1004 # 1005 # Revision 1.65 2007/01/15 13:08:55 ncq 1006 # - remove explicit __relink_episode2issue as episode details editor now does it 1007 # 1008 # Revision 1.64 2007/01/13 22:26:55 ncq 1009 # - remove cruft 1010 # - mix expansion history into emr tree browser 1011 # 1012 # Revision 1.63 2007/01/06 23:41:40 ncq 1013 # - missing : 1014 # 1015 # Revision 1.62 2007/01/04 23:41:36 ncq 1016 # - use new episode edit area 1017 # 1018 # Revision 1.61 2006/12/25 22:50:50 ncq 1019 # - add editing of consultation details from EMR tree right-click popup menu 1020 # 1021 # Revision 1.60 2006/12/13 23:32:41 ncq 1022 # - emr journal on a diet 1023 # 1024 # Revision 1.59 2006/11/24 14:20:44 ncq 1025 # - used shiny new health issue edit area in issue context menu 1026 # - refresh tree after editing health issue 1027 # 1028 # Revision 1.58 2006/11/24 10:01:31 ncq 1029 # - gm_beep_statustext() -> gm_statustext() 1030 # 1031 # Revision 1.57 2006/11/05 16:02:00 ncq 1032 # - cleanup 1033 # 1034 # Revision 1.56 2006/10/09 12:22:27 ncq 1035 # - some cleanup 1036 # - adjust to changed signature of encounter.transfer_clinical_data() 1037 # 1038 # Revision 1.55 2006/06/26 13:03:22 ncq 1039 # - improve menu strings 1040 # - implement moving episodes among issues 1041 # 1042 # Revision 1.54 2006/05/28 20:53:28 ncq 1043 # - cleanup, fix some variables and typos 1044 # 1045 # Revision 1.53 2006/05/28 16:23:10 ncq 1046 # - cleanup 1047 # - dedicated cEMRTree akin to cDocTree 1048 # - wxGladify tree widgets 1049 # 1050 # Revision 1.52 2006/05/15 13:35:59 ncq 1051 # - signal cleanup: 1052 # - activating_patient -> pre_patient_selection 1053 # - patient_selected -> post_patient_selection 1054 # 1055 # Revision 1.51 2006/05/04 09:49:20 ncq 1056 # - get_clinical_record() -> get_emr() 1057 # - adjust to changes in set_active_patient() 1058 # - need explicit set_active_patient() after ask_for_patient() if wanted 1059 # 1060 # Revision 1.50 2005/12/27 02:52:40 sjtan 1061 # 1062 # allow choice of closing old episode, or relinking to old episode, whenever opening a new episode in the present of an already open episode of an issue. 1063 # Small logic error fixed where the id of the health_issue was passed in as the id of an episode. 1064 # 1065 # Revision 1.49 2005/10/18 13:34:00 sjtan 1066 # after running; small diffs 1067 # 1068 # Revision 1.48 2005/10/09 06:42:02 sjtan 1069 # timely cache update means a complete tree reconstruct can be done quite fast ( currently sized records anyway), 1070 # so don't use refresh_historical_tree() - need to debug this anyway. 1071 # 1072 # Revision 1.47 2005/10/08 12:33:10 sjtan 1073 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch. 1074 # 1075 # Revision 1.46 2005/10/04 19:24:53 sjtan 1076 # browser now remembers expansion state and select state between change of patients, between health issue rename, episode rename or encounter relinking. This helps when reviewing the record more than once in a day. 1077 # 1078 # Revision 1.45 2005/10/04 13:09:49 sjtan 1079 # correct syntax errors; get soap entry working again. 1080 # 1081 # Revision 1.44 2005/09/28 21:27:30 ncq 1082 # - a lot of wx2.6-ification 1083 # 1084 # Revision 1.43 2005/09/28 15:57:48 ncq 1085 # - a whole bunch of wx.Foo -> wx.Foo 1086 # 1087 # Revision 1.42 2005/09/27 20:44:58 ncq 1088 # - wx.wx* -> wx.* 1089 # 1090 # Revision 1.41 2005/09/26 18:01:50 ncq 1091 # - use proper way to import wx26 vs wx2.4 1092 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 1093 # - time for fixup 1094 # 1095 # Revision 1.40 2005/09/24 09:17:28 ncq 1096 # - some wx2.6 compatibility fixes 1097 # 1098 # Revision 1.39 2005/09/11 17:30:02 ncq 1099 # - cleanup 1100 # 1101 # Revision 1.38 2005/09/08 16:57:48 ncq 1102 # - smaller font in journal display 1103 # 1104 # Revision 1.37 2005/07/21 21:00:46 ncq 1105 # - cleanup, better strings 1106 # 1107 # Revision 1.36 2005/07/02 18:20:52 ncq 1108 # - quite some cleanup 1109 # 1110 # Revision 1.35 2005/06/29 18:35:17 cfmoro 1111 # create encounter from EMR tree. Added FIXME for refactor duplicated code 1112 # 1113 # Revision 1.34 2005/06/29 12:53:50 cfmoro 1114 # Added create issue menu item to root node 1115 # 1116 # Revision 1.33 2005/06/23 14:59:43 ncq 1117 # - cleanup __relink_encounter_data2episode() 1118 # 1119 # Revision 1.32 2005/06/20 13:03:38 cfmoro 1120 # Relink encounter to another episode 1121 # 1122 # Revision 1.31 2005/06/15 22:27:20 ncq 1123 # - allow issue renaming 1124 # 1125 # Revision 1.30 2005/06/14 20:26:04 cfmoro 1126 # refresh tree on unit test startup 1127 # 1128 # Revision 1.29 2005/06/14 20:14:16 cfmoro 1129 # unit testing fix 1130 # 1131 # Revision 1.28 2005/06/14 18:57:50 ncq 1132 # - support renaming an episode 1133 # 1134 # Revision 1.27 2005/04/24 14:44:05 ncq 1135 # - callbacks must be _* not __* or else namespace invisibility will ensue 1136 # 1137 # Revision 1.26 2005/04/12 16:19:49 ncq 1138 # - add cEMRJournalPanel for plugin 1139 # 1140 # Revision 1.25 2005/04/05 16:21:54 ncq 1141 # - a fix by Syan 1142 # - cleanup 1143 # 1144 # Revision 1.24 2005/04/03 20:10:51 ncq 1145 # - add export_emr_to_ascii() 1146 # 1147 # Revision 1.23 2005/04/03 09:15:39 ncq 1148 # - roll back EMR export button 1149 # - my suggestion to place it there wasn't logically sound 1150 # and it screwed up changing the right hand window, too 1151 # 1152 # Revision 1.22 2005/04/02 21:37:27 cfmoro 1153 # Unlinked episodes displayes in EMR tree and dump 1154 # 1155 # Revision 1.21 2005/04/02 20:45:14 cfmoro 1156 # Implementated exporting emr from gui client 1157 # 1158 # Revision 1.20 2005/03/30 22:10:07 ncq 1159 # - just cleanup 1160 # 1161 # Revision 1.19 2005/03/30 18:59:03 cfmoro 1162 # Added file selector dialog to emr dump callback function 1163 # 1164 # Revision 1.18 2005/03/30 18:14:56 cfmoro 1165 # Added emr export button 1166 # 1167 # Revision 1.17 2005/03/29 07:27:14 ncq 1168 # - add missing argument 1169 # 1170 # Revision 1.16 2005/03/11 22:52:54 ncq 1171 # - simplify popup menu use 1172 # 1173 # Revision 1.15 2005/03/10 19:51:29 cfmoro 1174 # Obtained problem from cClinicalRecord on progress notes edition 1175 # 1176 # Revision 1.14 2005/03/09 20:00:13 cfmoro 1177 # Added fixme comment in problem retrieval 1178 # 1179 # Revision 1.13 2005/03/09 19:43:21 cfmoro 1180 # EMR browser edit problem-episodes notes responsible for providing the narrative definitions to cSoapResizingPanel 1181 # 1182 # Revision 1.12 2005/03/09 18:31:57 cfmoro 1183 # As proof of concept: episode editor and notes editor are displayed in the right panel. Just an initial draft, needs feeback a lot of coding yet ;) 1184 # 1185 # Revision 1.11 2005/03/09 16:58:09 cfmoro 1186 # Thanks to Syan code, added contextual menu to emr tree. Linked episode edition action with the responsible dialog 1187 # 1188 # Revision 1.10 2005/02/03 20:19:16 ncq 1189 # - get_demographic_record() -> get_identity() 1190 # 1191 # Revision 1.9 2005/02/01 10:16:07 ihaywood 1192 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1193 # 1194 # gmTopPanel moves to gmHorstSpace 1195 # gmRichardSpace added -- example code at present, haven't even run it myself 1196 # (waiting on some icon .pngs from Richard) 1197 # 1198 # Revision 1.8 2005/01/31 13:02:18 ncq 1199 # - use ask_for_patient() in gmPerson.py 1200 # 1201 # Revision 1.7 2005/01/31 10:37:26 ncq 1202 # - gmPatient.py -> gmPerson.py 1203 # 1204 # Revision 1.6 2004/10/31 00:37:13 cfmoro 1205 # Fixed some method names. Refresh function made public for easy reload, eg. standalone. Refresh browser at startup in standalone mode 1206 # 1207 # Revision 1.5 2004/09/06 18:57:27 ncq 1208 # - Carlos pluginized the lot ! :-) 1209 # - plus some fixes/tabified it 1210 # 1211 # Revision 1.4 2004/09/01 22:01:45 ncq 1212 # - actually use Carlos' issue/episode summary code 1213 # 1214 # Revision 1.3 2004/08/11 09:46:24 ncq 1215 # - now that EMR exporter supports SOAP notes - display them 1216 # 1217 # Revision 1.2 2004/07/26 00:09:27 ncq 1218 # - Carlos brings us data display for the encounters - can REALLY browse EMR now ! 1219 # 1220 # Revision 1.1 2004/07/21 12:30:25 ncq 1221 # - initial checkin 1222 # 1223