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: /home/ncq/Projekte/cvs2git/vcs-mirror/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 if tt == u'': 581 tt = u' ' 582 event.SetToolTip(tt) 583 584 elif isinstance(data, gmEMRStructItems.cHealthIssue): 585 tt = u'' 586 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 587 tt += gmTools.bool2subst ( 588 (data['diagnostic_certainty_classification'] is not None), 589 data.diagnostic_certainty_description + u'\n', 590 u'' 591 ) 592 tt += gmTools.bool2subst ( 593 (data['laterality'] not in [None, u'na']), 594 data.laterality_description + u'\n', 595 u'' 596 ) 597 # noted_at_age is too costly 598 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 599 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 600 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 601 tt += gmTools.coalesce(data['grouping'], u'', _('Grouping: %s')) 602 if tt == u'': 603 tt = u' ' 604 event.SetToolTip(tt) 605 606 else: 607 event.SetToolTip(u' ')
608 #self.SetToolTipString(u'') 609 610 # doing this prevents the tooltip from showing at all 611 #event.Skip() 612 613 #widgetXY.GetToolTip().Enable(False) 614 # 615 #seems to work, supposing the tooltip is actually set for the widget, 616 #otherwise a test would be needed 617 #if widgetXY.GetToolTip(): 618 # widgetXY.GetToolTip().Enable(False) 619 #--------------------------------------------------------
620 - def _on_tree_item_right_clicked(self, event):
621 """Right button clicked: display the popup for the tree""" 622 623 node = event.GetItem() 624 self.SelectItem(node) 625 self.__curr_node_data = self.GetPyData(node) 626 self.__curr_node = node 627 628 pos = wx.DefaultPosition 629 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 630 self.__handle_issue_context(pos=pos) 631 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 632 self.__handle_episode_context(pos=pos) 633 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 634 self.__handle_encounter_context(pos=pos) 635 elif node == self.GetRootItem(): 636 self.__handle_root_context() 637 elif type(self.__curr_node_data) == type({}): 638 # ignore pseudo node "free-standing episodes" 639 pass 640 else: 641 print "error: unknown node type, no popup menu" 642 event.Skip()
643 #--------------------------------------------------------
644 - def OnCompareItems (self, node1=None, node2=None):
645 """Used in sorting items. 646 647 -1: 1 < 2 648 0: 1 = 2 649 1: 1 > 2 650 """ 651 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 652 653 item1 = self.GetPyData(node1) 654 item2 = self.GetPyData(node2) 655 656 # encounters: reverse chron 657 if isinstance(item1, gmEMRStructItems.cEncounter): 658 if item1['started'] == item2['started']: 659 return 0 660 if item1['started'] > item2['started']: 661 return -1 662 return 1 663 664 # episodes: chron 665 if isinstance(item1, gmEMRStructItems.cEpisode): 666 start1 = item1.get_access_range()[0] 667 start2 = item2.get_access_range()[0] 668 if start1 == start2: 669 return 0 670 if start1 < start2: 671 return -1 672 return 1 673 674 # issues: alpha by grouping, no grouping at the bottom 675 if isinstance(item1, gmEMRStructItems.cHealthIssue): 676 677 # no grouping below grouping 678 if item1['grouping'] is None: 679 if item2['grouping'] is not None: 680 return 1 681 682 # grouping above no grouping 683 if item1['grouping'] is not None: 684 if item2['grouping'] is None: 685 return -1 686 687 # both no grouping: alpha on description 688 if (item1['grouping'] is None) and (item2['grouping'] is None): 689 if item1['description'].lower() < item2['description'].lower(): 690 return -1 691 if item1['description'].lower() > item2['description'].lower(): 692 return 1 693 return 0 694 695 # both with grouping: alpha on grouping, then alpha on description 696 if item1['grouping'] < item2['grouping']: 697 return -1 698 699 if item1['grouping'] > item2['grouping']: 700 return 1 701 702 if item1['description'].lower() < item2['description'].lower(): 703 return -1 704 705 if item1['description'].lower() > item2['description'].lower(): 706 return 1 707 708 return 0 709 710 # dummy health issue always on top 711 if isinstance(item1, type({})): 712 return -1 713 714 return 0
715 #================================================================
716 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
717 """A scrollable panel holding an EMR tree. 718 719 Lacks a widget to display details for selected items. The 720 tree data will be refetched - if necessary - whenever 721 repopulate_ui() is called, e.g., when then patient is changed. 722 """
723 - def __init__(self, *args, **kwds):
725 # self.__register_interests() 726 #-------------------------------------------------------- 727 # def __register_interests(self): 728 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui) 729 #--------------------------------------------------------
730 - def repopulate_ui(self):
731 self._emr_tree.refresh() 732 return True
733 #============================================================
734 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
735 """A splitter window holding an EMR tree. 736 737 The left hand side displays a scrollable EMR tree while 738 on the right details for selected items are displayed. 739 740 Expects to be put into a Notebook. 741 """
742 - def __init__(self, *args, **kwds):
743 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 744 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 745 self.__register_events()
746 #--------------------------------------------------------
747 - def __register_events(self):
748 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 749 return True
750 #--------------------------------------------------------
751 - def _on_post_patient_selection(self):
752 if self.GetParent().GetCurrentPage() == self: 753 self.repopulate_ui() 754 return True
755 #--------------------------------------------------------
756 - def repopulate_ui(self):
757 """Fills UI with data.""" 758 self._pnl_emr_tree.repopulate_ui() 759 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 760 return True
761 #================================================================
762 -class cEMRJournalPanel(wx.Panel):
763 - def __init__(self, *args, **kwargs):
764 wx.Panel.__init__(self, *args, **kwargs) 765 766 self.__do_layout() 767 self.__register_events()
768 #--------------------------------------------------------
769 - def __do_layout(self):
770 self.__journal = wx.TextCtrl ( 771 self, 772 -1, 773 _('No EMR data loaded.'), 774 style = wx.TE_MULTILINE | wx.TE_READONLY 775 ) 776 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 777 # arrange widgets 778 szr_outer = wx.BoxSizer(wx.VERTICAL) 779 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 780 # and do layout 781 self.SetAutoLayout(1) 782 self.SetSizer(szr_outer) 783 szr_outer.Fit(self) 784 szr_outer.SetSizeHints(self) 785 self.Layout()
786 #--------------------------------------------------------
787 - def __register_events(self):
788 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
789 #--------------------------------------------------------
790 - def _on_post_patient_selection(self):
791 """Expects to be in a Notebook.""" 792 if self.GetParent().GetCurrentPage() == self: 793 self.repopulate_ui() 794 return True
795 #-------------------------------------------------------- 796 # notebook plugin API 797 #--------------------------------------------------------
798 - def repopulate_ui(self):
799 txt = StringIO.StringIO() 800 exporter = gmPatientExporter.cEMRJournalExporter() 801 # FIXME: if journal is large this will error out, use generator/yield etc 802 # FIXME: turn into proper list 803 try: 804 exporter.export(txt) 805 self.__journal.SetValue(txt.getvalue()) 806 except ValueError: 807 _log.exception('cannot get EMR journal') 808 self.__journal.SetValue (_( 809 'An error occurred while retrieving the EMR\n' 810 'in journal form for the active patient.\n\n' 811 'Please check the log file for details.' 812 )) 813 txt.close() 814 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 815 return True
816 #================================================================ 817 # MAIN 818 #---------------------------------------------------------------- 819 if __name__ == '__main__': 820 821 _log.info("starting emr browser...") 822 823 try: 824 # obtain patient 825 patient = gmPerson.ask_for_patient() 826 if patient is None: 827 print "No patient. Exiting gracefully..." 828 sys.exit(0) 829 gmPatSearchWidgets.set_active_patient(patient = patient) 830 831 # display standalone browser 832 application = wx.PyWidgetTester(size=(800,600)) 833 emr_browser = cEMRBrowserPanel(application.frame, -1) 834 emr_browser.refresh_tree() 835 836 application.frame.Show(True) 837 application.MainLoop() 838 839 # clean up 840 if patient is not None: 841 try: 842 patient.cleanup() 843 except: 844 print "error cleaning up patient" 845 except StandardError: 846 _log.exception("unhandled exception caught !") 847 # but re-raise them 848 raise 849 850 _log.info("closing emr browser...") 851 852 #================================================================ 853 # $Log: gmEMRBrowser.py,v $ 854 # Revision 1.111 2010-01-11 19:44:39 ncq 855 # - cleanup 856 # 857 # Revision 1.110 2009/11/15 01:05:02 ncq 858 # - enable moving SOAP of a list of encounters of an episode 859 # 860 # Revision 1.109 2009/11/06 15:17:07 ncq 861 # - properly promote episode to issue 862 # - better tooltips 863 # 864 # Revision 1.108 2009/10/29 17:21:14 ncq 865 # - improved tooltips 866 # 867 # Revision 1.107 2009/10/20 10:26:21 ncq 868 # - tooltips on issue/episode in EMR tree 869 # 870 # Revision 1.106 2009/09/23 14:34:21 ncq 871 # - add promoting episode to issue 872 # 873 # Revision 1.105 2009/09/01 22:26:56 ncq 874 # - use new edit_episode/edit_health_issue 875 # 876 # Revision 1.104 2009/06/04 16:30:30 ncq 877 # - use set active patient from pat search widgets 878 # 879 # Revision 1.103 2009/05/13 12:19:13 ncq 880 # - use improved encounter management 881 # - add moving narrative between encounters 882 # 883 # Revision 1.102 2009/04/16 12:48:31 ncq 884 # - edit_progress_notes -> manage_* 885 # - use proper formatter when displaying encounter tooltip 886 # 887 # Revision 1.101 2009/04/05 18:04:46 ncq 888 # - support and use grouping 889 # 890 # Revision 1.100 2009/01/21 18:03:53 ncq 891 # - comment on tooltip handling 892 # 893 # Revision 1.99 2008/12/18 21:27:56 ncq 894 # - add editing progress notes from encounter context menu 895 # 896 # Revision 1.98 2008/12/01 12:37:37 ncq 897 # - generate encounter node tooltips 898 # 899 # Revision 1.97 2008/11/20 19:50:19 ncq 900 # - use improved data formatting 901 # 902 # Revision 1.96 2008/10/12 16:13:23 ncq 903 # - rename EMR tree root node, per Jim 904 # 905 # Revision 1.95 2008/09/02 19:01:11 ncq 906 # - adjust to clin health_issue fk_patient drop and related changes 907 # 908 # Revision 1.94 2008/08/31 18:39:47 ncq 909 # - if narrative is added, say a test result with comment, before 910 # the tree was ever displayed (hence not populated) for that patient 911 # there is not yet a currently selected node, so don't update 912 # the narrative display either 913 # 914 # Revision 1.93 2008/08/15 15:56:38 ncq 915 # - properly handle context click on pseudo-issue 916 # 917 # Revision 1.92 2008/07/28 15:44:39 ncq 918 # - context menu based Medistar export for any encounter 919 # 920 # Revision 1.91 2008/07/14 13:46:11 ncq 921 # - better naming of dummy health issue 922 # 923 # Revision 1.90 2008/07/12 15:31:23 ncq 924 # - improved formatting of issue info 925 # 926 # Revision 1.89 2008/07/07 13:44:33 ncq 927 # - current patient .connected 928 # - properly sort tree, encounters: most recent on top as per user request 929 # 930 # Revision 1.88 2008/06/28 18:25:58 ncq 931 # - add expand to ... level popup menu items in EMR tree 932 # 933 # Revision 1.87 2008/05/19 16:23:33 ncq 934 # - let EMR format its summary itself 935 # 936 # Revision 1.86 2008/04/11 12:27:45 ncq 937 # - listen to issue/episode/narrative change signals thereby 938 # reducing direct repopulate calls 939 # - factor out __update_text_for_selected_node() and 940 # call format() on nodes that have it 941 # - rearrange code layout 942 # 943 # Revision 1.85 2008/03/05 22:30:14 ncq 944 # - new style logging 945 # 946 # Revision 1.84 2008/01/30 14:07:24 ncq 947 # - do not use old cfg file support anymore 948 # 949 # Revision 1.83 2008/01/22 12:20:53 ncq 950 # - dummy health issue always on top 951 # - auto-scroll to bottom of journal 952 # 953 # Revision 1.82 2007/12/11 12:49:25 ncq 954 # - explicit signal handling 955 # 956 # Revision 1.81 2007/09/07 10:56:57 ncq 957 # - cleanup 958 # 959 # Revision 1.80 2007/08/29 22:09:10 ncq 960 # - narrative widgets factored out 961 # 962 # Revision 1.79 2007/08/15 14:57:52 ncq 963 # - pretty up tree popup menus 964 # - add deletion of health issues 965 # 966 # Revision 1.78 2007/08/12 00:09:07 ncq 967 # - no more gmSignals.py 968 # 969 # Revision 1.77 2007/06/18 20:31:10 ncq 970 # - case insensitively sort health issues 971 # 972 # Revision 1.76 2007/06/10 09:56:54 ncq 973 # - actually sort tree items, add sorting for health issues 974 # 975 # Revision 1.75 2007/05/21 14:48:20 ncq 976 # - cleanup 977 # - use pat['dirname'], use export/EMR/ 978 # - unicode output files 979 # 980 # Revision 1.74 2007/05/21 13:05:25 ncq 981 # - catch-all wildcard on UNIX must be *, not *.* 982 # 983 # Revision 1.73 2007/05/18 13:29:25 ncq 984 # - some cleanup 985 # - properly support moving narrative between episodes 986 # 987 # Revision 1.72 2007/05/14 13:11:24 ncq 988 # - use statustext() signal 989 # 990 # Revision 1.71 2007/05/14 10:33:33 ncq 991 # - allow deleting episode 992 # 993 # Revision 1.70 2007/03/18 14:04:00 ncq 994 # - add allergy handling to menu and root node of tree 995 # 996 # Revision 1.69 2007/03/02 15:31:45 ncq 997 # - properly repopulation EMR tree and problem list :-) 998 # 999 # Revision 1.68 2007/02/22 17:41:13 ncq 1000 # - adjust to gmPerson changes 1001 # 1002 # Revision 1.67 2007/02/16 12:51:46 ncq 1003 # - fix add issue popup on root node as requested by user :-) 1004 # 1005 # Revision 1.66 2007/01/16 18:00:59 ncq 1006 # - cleanup 1007 # - explicitely sort episodes and encounters by when they were started 1008 # 1009 # Revision 1.65 2007/01/15 13:08:55 ncq 1010 # - remove explicit __relink_episode2issue as episode details editor now does it 1011 # 1012 # Revision 1.64 2007/01/13 22:26:55 ncq 1013 # - remove cruft 1014 # - mix expansion history into emr tree browser 1015 # 1016 # Revision 1.63 2007/01/06 23:41:40 ncq 1017 # - missing : 1018 # 1019 # Revision 1.62 2007/01/04 23:41:36 ncq 1020 # - use new episode edit area 1021 # 1022 # Revision 1.61 2006/12/25 22:50:50 ncq 1023 # - add editing of consultation details from EMR tree right-click popup menu 1024 # 1025 # Revision 1.60 2006/12/13 23:32:41 ncq 1026 # - emr journal on a diet 1027 # 1028 # Revision 1.59 2006/11/24 14:20:44 ncq 1029 # - used shiny new health issue edit area in issue context menu 1030 # - refresh tree after editing health issue 1031 # 1032 # Revision 1.58 2006/11/24 10:01:31 ncq 1033 # - gm_beep_statustext() -> gm_statustext() 1034 # 1035 # Revision 1.57 2006/11/05 16:02:00 ncq 1036 # - cleanup 1037 # 1038 # Revision 1.56 2006/10/09 12:22:27 ncq 1039 # - some cleanup 1040 # - adjust to changed signature of encounter.transfer_clinical_data() 1041 # 1042 # Revision 1.55 2006/06/26 13:03:22 ncq 1043 # - improve menu strings 1044 # - implement moving episodes among issues 1045 # 1046 # Revision 1.54 2006/05/28 20:53:28 ncq 1047 # - cleanup, fix some variables and typos 1048 # 1049 # Revision 1.53 2006/05/28 16:23:10 ncq 1050 # - cleanup 1051 # - dedicated cEMRTree akin to cDocTree 1052 # - wxGladify tree widgets 1053 # 1054 # Revision 1.52 2006/05/15 13:35:59 ncq 1055 # - signal cleanup: 1056 # - activating_patient -> pre_patient_selection 1057 # - patient_selected -> post_patient_selection 1058 # 1059 # Revision 1.51 2006/05/04 09:49:20 ncq 1060 # - get_clinical_record() -> get_emr() 1061 # - adjust to changes in set_active_patient() 1062 # - need explicit set_active_patient() after ask_for_patient() if wanted 1063 # 1064 # Revision 1.50 2005/12/27 02:52:40 sjtan 1065 # 1066 # 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. 1067 # Small logic error fixed where the id of the health_issue was passed in as the id of an episode. 1068 # 1069 # Revision 1.49 2005/10/18 13:34:00 sjtan 1070 # after running; small diffs 1071 # 1072 # Revision 1.48 2005/10/09 06:42:02 sjtan 1073 # timely cache update means a complete tree reconstruct can be done quite fast ( currently sized records anyway), 1074 # so don't use refresh_historical_tree() - need to debug this anyway. 1075 # 1076 # Revision 1.47 2005/10/08 12:33:10 sjtan 1077 # 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. 1078 # 1079 # Revision 1.46 2005/10/04 19:24:53 sjtan 1080 # 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. 1081 # 1082 # Revision 1.45 2005/10/04 13:09:49 sjtan 1083 # correct syntax errors; get soap entry working again. 1084 # 1085 # Revision 1.44 2005/09/28 21:27:30 ncq 1086 # - a lot of wx2.6-ification 1087 # 1088 # Revision 1.43 2005/09/28 15:57:48 ncq 1089 # - a whole bunch of wx.Foo -> wx.Foo 1090 # 1091 # Revision 1.42 2005/09/27 20:44:58 ncq 1092 # - wx.wx* -> wx.* 1093 # 1094 # Revision 1.41 2005/09/26 18:01:50 ncq 1095 # - use proper way to import wx26 vs wx2.4 1096 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 1097 # - time for fixup 1098 # 1099 # Revision 1.40 2005/09/24 09:17:28 ncq 1100 # - some wx2.6 compatibility fixes 1101 # 1102 # Revision 1.39 2005/09/11 17:30:02 ncq 1103 # - cleanup 1104 # 1105 # Revision 1.38 2005/09/08 16:57:48 ncq 1106 # - smaller font in journal display 1107 # 1108 # Revision 1.37 2005/07/21 21:00:46 ncq 1109 # - cleanup, better strings 1110 # 1111 # Revision 1.36 2005/07/02 18:20:52 ncq 1112 # - quite some cleanup 1113 # 1114 # Revision 1.35 2005/06/29 18:35:17 cfmoro 1115 # create encounter from EMR tree. Added FIXME for refactor duplicated code 1116 # 1117 # Revision 1.34 2005/06/29 12:53:50 cfmoro 1118 # Added create issue menu item to root node 1119 # 1120 # Revision 1.33 2005/06/23 14:59:43 ncq 1121 # - cleanup __relink_encounter_data2episode() 1122 # 1123 # Revision 1.32 2005/06/20 13:03:38 cfmoro 1124 # Relink encounter to another episode 1125 # 1126 # Revision 1.31 2005/06/15 22:27:20 ncq 1127 # - allow issue renaming 1128 # 1129 # Revision 1.30 2005/06/14 20:26:04 cfmoro 1130 # refresh tree on unit test startup 1131 # 1132 # Revision 1.29 2005/06/14 20:14:16 cfmoro 1133 # unit testing fix 1134 # 1135 # Revision 1.28 2005/06/14 18:57:50 ncq 1136 # - support renaming an episode 1137 # 1138 # Revision 1.27 2005/04/24 14:44:05 ncq 1139 # - callbacks must be _* not __* or else namespace invisibility will ensue 1140 # 1141 # Revision 1.26 2005/04/12 16:19:49 ncq 1142 # - add cEMRJournalPanel for plugin 1143 # 1144 # Revision 1.25 2005/04/05 16:21:54 ncq 1145 # - a fix by Syan 1146 # - cleanup 1147 # 1148 # Revision 1.24 2005/04/03 20:10:51 ncq 1149 # - add export_emr_to_ascii() 1150 # 1151 # Revision 1.23 2005/04/03 09:15:39 ncq 1152 # - roll back EMR export button 1153 # - my suggestion to place it there wasn't logically sound 1154 # and it screwed up changing the right hand window, too 1155 # 1156 # Revision 1.22 2005/04/02 21:37:27 cfmoro 1157 # Unlinked episodes displayes in EMR tree and dump 1158 # 1159 # Revision 1.21 2005/04/02 20:45:14 cfmoro 1160 # Implementated exporting emr from gui client 1161 # 1162 # Revision 1.20 2005/03/30 22:10:07 ncq 1163 # - just cleanup 1164 # 1165 # Revision 1.19 2005/03/30 18:59:03 cfmoro 1166 # Added file selector dialog to emr dump callback function 1167 # 1168 # Revision 1.18 2005/03/30 18:14:56 cfmoro 1169 # Added emr export button 1170 # 1171 # Revision 1.17 2005/03/29 07:27:14 ncq 1172 # - add missing argument 1173 # 1174 # Revision 1.16 2005/03/11 22:52:54 ncq 1175 # - simplify popup menu use 1176 # 1177 # Revision 1.15 2005/03/10 19:51:29 cfmoro 1178 # Obtained problem from cClinicalRecord on progress notes edition 1179 # 1180 # Revision 1.14 2005/03/09 20:00:13 cfmoro 1181 # Added fixme comment in problem retrieval 1182 # 1183 # Revision 1.13 2005/03/09 19:43:21 cfmoro 1184 # EMR browser edit problem-episodes notes responsible for providing the narrative definitions to cSoapResizingPanel 1185 # 1186 # Revision 1.12 2005/03/09 18:31:57 cfmoro 1187 # 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 ;) 1188 # 1189 # Revision 1.11 2005/03/09 16:58:09 cfmoro 1190 # Thanks to Syan code, added contextual menu to emr tree. Linked episode edition action with the responsible dialog 1191 # 1192 # Revision 1.10 2005/02/03 20:19:16 ncq 1193 # - get_demographic_record() -> get_identity() 1194 # 1195 # Revision 1.9 2005/02/01 10:16:07 ihaywood 1196 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1197 # 1198 # gmTopPanel moves to gmHorstSpace 1199 # gmRichardSpace added -- example code at present, haven't even run it myself 1200 # (waiting on some icon .pngs from Richard) 1201 # 1202 # Revision 1.8 2005/01/31 13:02:18 ncq 1203 # - use ask_for_patient() in gmPerson.py 1204 # 1205 # Revision 1.7 2005/01/31 10:37:26 ncq 1206 # - gmPatient.py -> gmPerson.py 1207 # 1208 # Revision 1.6 2004/10/31 00:37:13 cfmoro 1209 # Fixed some method names. Refresh function made public for easy reload, eg. standalone. Refresh browser at startup in standalone mode 1210 # 1211 # Revision 1.5 2004/09/06 18:57:27 ncq 1212 # - Carlos pluginized the lot ! :-) 1213 # - plus some fixes/tabified it 1214 # 1215 # Revision 1.4 2004/09/01 22:01:45 ncq 1216 # - actually use Carlos' issue/episode summary code 1217 # 1218 # Revision 1.3 2004/08/11 09:46:24 ncq 1219 # - now that EMR exporter supports SOAP notes - display them 1220 # 1221 # Revision 1.2 2004/07/26 00:09:27 ncq 1222 # - Carlos brings us data display for the encounters - can REALLY browse EMR now ! 1223 # 1224 # Revision 1.1 2004/07/21 12:30:25 ncq 1225 # - initial checkin 1226 # 1227