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

Source Code for Module Gnumed.wxpython.gmNarrativeWidgets

   1  """GNUmed narrative handling widgets.""" 
   2  #================================================================ 
   3  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   4  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
   5   
   6  import sys 
   7  import logging 
   8  import os 
   9  import os.path 
  10  import time 
  11  import re as regex 
  12  import shutil 
  13   
  14   
  15  import wx 
  16  import wx.lib.expando as wx_expando 
  17  import wx.lib.agw.supertooltip as agw_stt 
  18  import wx.lib.statbmp as wx_genstatbmp 
  19   
  20   
  21  if __name__ == '__main__': 
  22          sys.path.insert(0, '../../') 
  23   
  24  from Gnumed.pycommon import gmI18N 
  25   
  26  if __name__ == '__main__': 
  27          gmI18N.activate_locale() 
  28          gmI18N.install_domain() 
  29   
  30  from Gnumed.pycommon import gmDispatcher 
  31  from Gnumed.pycommon import gmTools 
  32  from Gnumed.pycommon import gmDateTime 
  33  from Gnumed.pycommon import gmShellAPI 
  34  from Gnumed.pycommon import gmPG2 
  35  from Gnumed.pycommon import gmCfg 
  36  from Gnumed.pycommon import gmMatchProvider 
  37   
  38  from Gnumed.business import gmPerson 
  39  from Gnumed.business import gmStaff 
  40  from Gnumed.business import gmEMRStructItems 
  41  from Gnumed.business import gmClinNarrative 
  42  from Gnumed.business import gmPraxis 
  43  from Gnumed.business import gmForms 
  44  from Gnumed.business import gmDocuments 
  45  from Gnumed.business import gmPersonSearch 
  46  from Gnumed.business import gmKeywordExpansion 
  47   
  48  from Gnumed.wxpython import gmListWidgets 
  49  from Gnumed.wxpython import gmEMRStructWidgets 
  50  from Gnumed.wxpython import gmRegetMixin 
  51  from Gnumed.wxpython import gmPhraseWheel 
  52  from Gnumed.wxpython import gmGuiHelpers 
  53  from Gnumed.wxpython import gmCfgWidgets 
  54  from Gnumed.wxpython import gmDocumentWidgets 
  55  from Gnumed.wxpython import gmKeywordExpansionWidgets 
  56  from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient 
  57   
  58  from Gnumed.exporters import gmPatientExporter 
  59   
  60   
  61  _log = logging.getLogger('gm.ui') 
  62  #============================================================ 
  63  # narrative related widgets/functions 
  64  #------------------------------------------------------------ 
65 -def move_progress_notes_to_another_encounter(parent=None, encounters=None, episodes=None, patient=None, move_all=False):
66 67 # sanity checks 68 if patient is None: 69 patient = gmPerson.gmCurrentPatient() 70 71 if not patient.connected: 72 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.')) 73 return False 74 75 if parent is None: 76 parent = wx.GetApp().GetTopWindow() 77 78 emr = patient.get_emr() 79 80 if encounters is None: 81 encs = emr.get_encounters(episodes = episodes) 82 encounters = gmEMRStructWidgets.select_encounters ( 83 parent = parent, 84 patient = patient, 85 single_selection = False, 86 encounters = encs 87 ) 88 # cancelled 89 if encounters is None: 90 return True 91 # none selected 92 if len(encounters) == 0: 93 return True 94 95 notes = emr.get_clin_narrative ( 96 encounters = encounters, 97 episodes = episodes 98 ) 99 100 # which narrative 101 if move_all: 102 selected_narr = notes 103 else: 104 selected_narr = gmListWidgets.get_choices_from_list ( 105 parent = parent, 106 caption = _('Moving progress notes between encounters ...'), 107 single_selection = False, 108 can_return_empty = True, 109 data = notes, 110 msg = _('\n Select the progress notes to move from the list !\n\n'), 111 columns = [_('when'), _('who'), _('type'), _('entry')], 112 choices = [ 113 [ narr['date'].strftime('%x %H:%M'), 114 narr['provider'], 115 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 116 narr['narrative'].replace('\n', '/').replace('\r', '/') 117 ] for narr in notes 118 ] 119 ) 120 121 if not selected_narr: 122 return True 123 124 # which encounter to move to 125 enc2move2 = gmEMRStructWidgets.select_encounters ( 126 parent = parent, 127 patient = patient, 128 single_selection = True 129 ) 130 131 if not enc2move2: 132 return True 133 134 for narr in selected_narr: 135 narr['pk_encounter'] = enc2move2['pk_encounter'] 136 narr.save() 137 138 return True
139 #------------------------------------------------------------
140 -def manage_progress_notes(parent=None, encounters=None, episodes=None, patient=None):
141 142 # sanity checks 143 if patient is None: 144 patient = gmPerson.gmCurrentPatient() 145 146 if not patient.connected: 147 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.')) 148 return False 149 150 if parent is None: 151 parent = wx.GetApp().GetTopWindow() 152 153 emr = patient.get_emr() 154 #-------------------------- 155 def delete(item): 156 if item is None: 157 return False 158 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 159 parent, 160 -1, 161 caption = _('Deleting progress note'), 162 question = _( 163 'Are you positively sure you want to delete this\n' 164 'progress note from the medical record ?\n' 165 '\n' 166 'Note that even if you chose to delete the entry it will\n' 167 'still be (invisibly) kept in the audit trail to protect\n' 168 'you from litigation because physical deletion is known\n' 169 'to be unlawful in some jurisdictions.\n' 170 ), 171 button_defs = ( 172 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False}, 173 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True} 174 ) 175 ) 176 decision = dlg.ShowModal() 177 178 if decision != wx.ID_YES: 179 return False 180 181 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative']) 182 return True
183 #-------------------------- 184 def edit(item): 185 if item is None: 186 return False 187 188 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 189 parent, 190 -1, 191 title = _('Editing progress note'), 192 msg = _('This is the original progress note:'), 193 data = item.format(left_margin = u' ', fancy = True), 194 text = item['narrative'] 195 ) 196 decision = dlg.ShowModal() 197 198 if decision != wx.ID_SAVE: 199 return False 200 201 val = dlg.value 202 dlg.Destroy() 203 if val.strip() == u'': 204 return False 205 206 item['narrative'] = val 207 item.save_payload() 208 209 return True 210 #-------------------------- 211 def refresh(lctrl): 212 notes = emr.get_clin_narrative ( 213 encounters = encounters, 214 episodes = episodes, 215 providers = [ gmStaff.gmCurrentProvider()['short_alias'] ] 216 ) 217 lctrl.set_string_items(items = [ 218 [ narr['date'].strftime('%x %H:%M'), 219 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 220 narr['narrative'].replace('\n', '/').replace('\r', '/') 221 ] for narr in notes 222 ]) 223 lctrl.set_data(data = notes) 224 #-------------------------- 225 226 gmListWidgets.get_choices_from_list ( 227 parent = parent, 228 caption = _('Managing progress notes'), 229 msg = _( 230 '\n' 231 ' This list shows the progress notes by %s.\n' 232 '\n' 233 ) % gmStaff.gmCurrentProvider()['short_alias'], 234 columns = [_('when'), _('type'), _('entry')], 235 single_selection = True, 236 can_return_empty = False, 237 edit_callback = edit, 238 delete_callback = delete, 239 refresh_callback = refresh 240 ) 241 #------------------------------------------------------------
242 -def search_narrative_across_emrs(parent=None):
243 244 if parent is None: 245 parent = wx.GetApp().GetTopWindow() 246 247 search_term_dlg = wx.TextEntryDialog ( 248 parent = parent, 249 message = _('Enter (regex) term to search for across all EMRs:'), 250 caption = _('Text search across all EMRs'), 251 style = wx.OK | wx.CANCEL | wx.CENTRE 252 ) 253 result = search_term_dlg.ShowModal() 254 255 if result != wx.ID_OK: 256 return 257 258 wx.BeginBusyCursor() 259 search_term = search_term_dlg.GetValue() 260 search_term_dlg.Destroy() 261 results = gmClinNarrative.search_text_across_emrs(search_term = search_term) 262 wx.EndBusyCursor() 263 264 if len(results) == 0: 265 gmGuiHelpers.gm_show_info ( 266 _( 267 'Nothing found for search term:\n' 268 ' "%s"' 269 ) % search_term, 270 _('Search results') 271 ) 272 return 273 274 items = [ [ 275 gmPerson.cIdentity(aPK_obj = r['pk_patient'])['description_gender'], 276 r['narrative'], 277 r['src_table'] 278 ] for r in results ] 279 280 selected_patient = gmListWidgets.get_choices_from_list ( 281 parent = parent, 282 caption = _('Search results for [%s]') % search_term, 283 choices = items, 284 columns = [_('Patient'), _('Match'), _('Match location')], 285 data = [ r['pk_patient'] for r in results ], 286 single_selection = True, 287 can_return_empty = False 288 ) 289 290 if selected_patient is None: 291 return 292 293 wx.CallAfter(set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
294 #------------------------------------------------------------
295 -def search_narrative_in_emr(parent=None, patient=None):
296 297 # sanity checks 298 if patient is None: 299 patient = gmPerson.gmCurrentPatient() 300 301 if not patient.connected: 302 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.')) 303 return False 304 305 if parent is None: 306 parent = wx.GetApp().GetTopWindow() 307 308 search_term_dlg = wx.TextEntryDialog ( 309 parent = parent, 310 message = _('Enter search term:'), 311 caption = _('Text search of entire EMR of active patient'), 312 style = wx.OK | wx.CANCEL | wx.CENTRE 313 ) 314 result = search_term_dlg.ShowModal() 315 316 if result != wx.ID_OK: 317 search_term_dlg.Destroy() 318 return False 319 320 wx.BeginBusyCursor() 321 val = search_term_dlg.GetValue() 322 search_term_dlg.Destroy() 323 emr = patient.get_emr() 324 rows = emr.search_narrative_simple(val) 325 wx.EndBusyCursor() 326 327 if len(rows) == 0: 328 gmGuiHelpers.gm_show_info ( 329 _( 330 'Nothing found for search term:\n' 331 ' "%s"' 332 ) % val, 333 _('Search results') 334 ) 335 return True 336 337 txt = u'' 338 for row in rows: 339 txt += u'%s: %s\n' % ( 340 row['soap_cat'], 341 row['narrative'] 342 ) 343 344 txt += u' %s: %s - %s %s\n' % ( 345 _('Encounter'), 346 row['encounter_started'].strftime('%x %H:%M'), 347 row['encounter_ended'].strftime('%H:%M'), 348 row['encounter_type'] 349 ) 350 txt += u' %s: %s\n' % ( 351 _('Episode'), 352 row['episode'] 353 ) 354 txt += u' %s: %s\n\n' % ( 355 _('Health issue'), 356 row['health_issue'] 357 ) 358 359 msg = _( 360 'Search term was: "%s"\n' 361 '\n' 362 'Search results:\n\n' 363 '%s\n' 364 ) % (val, txt) 365 366 dlg = wx.MessageDialog ( 367 parent = parent, 368 message = msg, 369 caption = _('Search results for [%s]') % val, 370 style = wx.OK | wx.STAY_ON_TOP 371 ) 372 dlg.ShowModal() 373 dlg.Destroy() 374 375 return True
376 #------------------------------------------------------------
377 -def export_narrative_for_medistar_import(parent=None, soap_cats=u'soapu', encounter=None):
378 379 # sanity checks 380 pat = gmPerson.gmCurrentPatient() 381 if not pat.connected: 382 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.')) 383 return False 384 385 if encounter is None: 386 encounter = pat.get_emr().active_encounter 387 388 if parent is None: 389 parent = wx.GetApp().GetTopWindow() 390 391 # get file name 392 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 393 # FIXME: make configurable 394 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed'))) 395 # FIXME: make configurable 396 fname = '%s-%s-%s-%s-%s.txt' % ( 397 'Medistar-MD', 398 time.strftime('%Y-%m-%d',time.localtime()), 399 pat['lastnames'].replace(' ', '-'), 400 pat['firstnames'].replace(' ', '_'), 401 pat.get_formatted_dob(format = '%Y-%m-%d') 402 ) 403 dlg = wx.FileDialog ( 404 parent = parent, 405 message = _("Save EMR extract for MEDISTAR import as..."), 406 defaultDir = aDefDir, 407 defaultFile = fname, 408 wildcard = aWildcard, 409 style = wx.SAVE 410 ) 411 choice = dlg.ShowModal() 412 fname = dlg.GetPath() 413 dlg.Destroy() 414 if choice != wx.ID_OK: 415 return False 416 417 wx.BeginBusyCursor() 418 _log.debug('exporting encounter for medistar import to [%s]', fname) 419 exporter = gmPatientExporter.cMedistarSOAPExporter() 420 successful, fname = exporter.export_to_file ( 421 filename = fname, 422 encounter = encounter, 423 soap_cats = u'soapu', 424 export_to_import_file = True 425 ) 426 if not successful: 427 gmGuiHelpers.gm_show_error ( 428 _('Error exporting progress notes for MEDISTAR import.'), 429 _('MEDISTAR progress notes export') 430 ) 431 wx.EndBusyCursor() 432 return False 433 434 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False) 435 436 wx.EndBusyCursor() 437 return True
438 #------------------------------------------------------------
439 -def select_narrative_by_issue(parent=None, soap_cats=None):
440 441 pat = gmPerson.gmCurrentPatient() 442 emr = pat.get_emr() 443 444 # not useful if you think about it: 445 # issues = [ i for i in emr.health_issues ] 446 # if len(issues) == 0: 447 # gmDispatcher.send(signal = 'statustext', msg = _('No progress notes found.')) 448 # return [] 449 450 if parent is None: 451 parent = wx.GetApp().GetTopWindow() 452 453 if soap_cats is None: 454 soap_cats = u'soapu' 455 soap_cats = list(soap_cats) 456 i18n_soap_cats = [ gmClinNarrative.soap_cat2l10n[cat].upper() for cat in soap_cats ] 457 458 selected_soap = {} 459 #selected_narrative_pks = [] 460 461 #----------------------------------------------- 462 def get_soap_tooltip(soap): 463 return soap.format(fancy = True, width = 60)
464 #----------------------------------------------- 465 def pick_soap_from_issue(issue): 466 467 if issue is None: 468 return False 469 470 narr_for_issue = emr.get_clin_narrative(issues = [issue['pk_health_issue']], soap_cats = soap_cats) 471 472 if len(narr_for_issue) == 0: 473 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for this health issue.')) 474 return True 475 476 selected_narr = gmListWidgets.get_choices_from_list ( 477 parent = parent, 478 msg = _('Pick the [%s] narrative you want to include in the report.') % u'/'.join(i18n_soap_cats), 479 caption = _('Picking [%s] from %s%s%s') % ( 480 u'/'.join(i18n_soap_cats), 481 gmTools.u_left_double_angle_quote, 482 issue['description'], 483 gmTools.u_right_double_angle_quote 484 ), 485 columns = [_('When'), _('Who'), _('Type'), _('Entry')], 486 choices = [ [ 487 gmDateTime.pydt_strftime(narr['date'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes), 488 narr['provider'], 489 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 490 narr['narrative'].replace('\n', '//').replace('\r', '//') 491 ] for narr in narr_for_issue ], 492 data = narr_for_issue, 493 #selections=None, 494 #edit_callback=None, 495 single_selection = False, 496 can_return_empty = False, 497 list_tooltip_callback = get_soap_tooltip 498 ) 499 500 if selected_narr is None: 501 return True 502 503 for narr in selected_narr: 504 selected_soap[narr['pk_narrative']] = narr 505 506 return True 507 #----------------------------------------------- 508 def edit_issue(issue): 509 return gmEMRStructWidgets.edit_health_issue(parent = parent, issue = issue) 510 #----------------------------------------------- 511 def refresh_issues(lctrl): 512 #issues = [ i for i in emr.health_issues ] 513 issues = emr.health_issues 514 lctrl.set_string_items ([ [ 515 gmTools.bool2subst(i['is_confidential'], _('!! CONFIDENTIAL !!'), u''), 516 i['description'], 517 gmTools.bool2subst(i['is_active'], _('active'), _('inactive')) 518 ] for i in issues 519 ]) 520 lctrl.set_data(issues) 521 #----------------------------------------------- 522 def get_issue_tooltip(issue): 523 return issue.format ( 524 patient = pat, 525 with_encounters = False, 526 with_medications = False, 527 with_hospital_stays = False, 528 with_procedures = False, 529 with_family_history = False, 530 with_documents = False, 531 with_tests = False, 532 with_vaccinations = False 533 ) 534 #----------------------------------------------- 535 #selected_episode_pks = [] 536 537 issues_picked_from = gmListWidgets.get_choices_from_list ( 538 parent = parent, 539 msg = _('\n Select the issue you want to report on.'), 540 caption = _('Picking [%s] from health issues') % u'/'.join(i18n_soap_cats), 541 columns = [_('Privacy'), _('Issue'), _('Status')], 542 edit_callback = edit_issue, 543 refresh_callback = refresh_issues, 544 single_selection = True, 545 can_return_empty = True, 546 ignore_OK_button = False, 547 left_extra_button = ( 548 _('&Pick notes'), 549 _('Pick [%s] entries from selected health issue') % u'/'.join(i18n_soap_cats), 550 pick_soap_from_issue 551 ), 552 list_tooltip_callback = get_issue_tooltip 553 ) 554 555 if issues_picked_from is None: 556 return [] 557 558 return selected_soap.values() 559 560 # selection_idxs = [] 561 # for idx in range(len(all_epis)): 562 # if all_epis[idx]['pk_episode'] in selected_episode_pks: 563 # selection_idxs.append(idx) 564 # if len(selection_idxs) != 0: 565 # dlg.set_selections(selections = selection_idxs) 566 #------------------------------------------------------------
567 -def select_narrative_by_episode(parent=None, soap_cats=None):
568 569 pat = gmPerson.gmCurrentPatient() 570 emr = pat.get_emr() 571 572 all_epis = [ epi for epi in emr.get_episodes(order_by = u'description') if epi.has_narrative ] 573 if len(all_epis) == 0: 574 gmDispatcher.send(signal = 'statustext', msg = _('No episodes with progress notes found.')) 575 return [] 576 577 if parent is None: 578 parent = wx.GetApp().GetTopWindow() 579 580 if soap_cats is None: 581 soap_cats = u'soapu' 582 soap_cats = list(soap_cats) 583 i18n_soap_cats = [ gmClinNarrative.soap_cat2l10n[cat].upper() for cat in soap_cats ] 584 585 selected_soap = {} 586 #selected_narrative_pks = [] 587 588 #----------------------------------------------- 589 def get_soap_tooltip(soap): 590 return soap.format(fancy = True, width = 60)
591 #----------------------------------------------- 592 def pick_soap_from_episode(episode): 593 594 if episode is None: 595 return False 596 597 narr_for_epi = emr.get_clin_narrative(episodes = [episode['pk_episode']], soap_cats = soap_cats) 598 599 if len(narr_for_epi) == 0: 600 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episode.')) 601 return True 602 603 selected_narr = gmListWidgets.get_choices_from_list ( 604 parent = parent, 605 msg = _('Pick the [%s] narrative you want to include in the report.') % u'/'.join(i18n_soap_cats), 606 caption = _('Picking [%s] from %s%s%s') % ( 607 u'/'.join(i18n_soap_cats), 608 gmTools.u_left_double_angle_quote, 609 episode['description'], 610 gmTools.u_right_double_angle_quote 611 ), 612 columns = [_('When'), _('Who'), _('Type'), _('Entry')], 613 choices = [ [ 614 gmDateTime.pydt_strftime(narr['date'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes), 615 narr['provider'], 616 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 617 narr['narrative'].replace('\n', '//').replace('\r', '//') 618 ] for narr in narr_for_epi ], 619 data = narr_for_epi, 620 #selections=None, 621 #edit_callback=None, 622 single_selection = False, 623 can_return_empty = False, 624 list_tooltip_callback = get_soap_tooltip 625 ) 626 627 if selected_narr is None: 628 return True 629 630 for narr in selected_narr: 631 selected_soap[narr['pk_narrative']] = narr 632 633 return True 634 635 # selection_idxs = [] 636 # for idx in range(len(narr_for_epi)): 637 # if narr_for_epi[idx]['pk_narrative'] in selected_narrative_pks: 638 # selection_idxs.append(idx) 639 # if len(selection_idxs) != 0: 640 # dlg.set_selections(selections = selection_idxs) 641 642 # selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ] 643 # for narr in selected_narr: 644 # selected_soap[narr['pk_narrative']] = narr 645 # 646 # print "before returning from picking soap" 647 # 648 # return True 649 # #----------------------------------------------- 650 def edit_episode(episode): 651 return gmEMRStructWidgets.edit_episode(parent = parent, episode = episode) 652 #----------------------------------------------- 653 def refresh_episodes(lctrl): 654 all_epis = [ epi for epi in emr.get_episodes(order_by = u'description') if epi.has_narrative ] 655 lctrl.set_string_items ([ [ 656 u'%s%s' % (e['description'], gmTools.coalesce(e['health_issue'], u'', u' (%s)')), 657 gmTools.bool2subst(e['episode_open'], _('open'), _('closed')) 658 ] for e in all_epis 659 ]) 660 lctrl.set_data(all_epis) 661 #----------------------------------------------- 662 def get_episode_tooltip(episode): 663 return episode.format ( 664 patient = pat, 665 with_encounters = False, 666 with_documents = False, 667 with_hospital_stays = False, 668 with_procedures = False, 669 with_family_history = False, 670 with_tests = False, 671 with_vaccinations = False 672 ) 673 #----------------------------------------------- 674 #selected_episode_pks = [] 675 676 epis_picked_from = gmListWidgets.get_choices_from_list ( 677 parent = parent, 678 msg = _('\n Select the episode you want to report on.'), 679 caption = _('Picking [%s] from episodes') % u'/'.join(i18n_soap_cats), 680 columns = [_('Episode'), _('Status')], 681 edit_callback = edit_episode, 682 refresh_callback = refresh_episodes, 683 single_selection = True, 684 can_return_empty = True, 685 ignore_OK_button = False, 686 left_extra_button = ( 687 _('&Pick notes'), 688 _('Pick [%s] entries from selected episode') % u'/'.join(i18n_soap_cats), 689 pick_soap_from_episode 690 ), 691 list_tooltip_callback = get_episode_tooltip 692 ) 693 694 if epis_picked_from is None: 695 return [] 696 697 return selected_soap.values() 698 699 # selection_idxs = [] 700 # for idx in range(len(all_epis)): 701 # if all_epis[idx]['pk_episode'] in selected_episode_pks: 702 # selection_idxs.append(idx) 703 # if len(selection_idxs) != 0: 704 # dlg.set_selections(selections = selection_idxs) 705 #------------------------------------------------------------
706 -def select_narrative_from_episodes(parent=None, soap_cats=None):
707 """soap_cats needs to be a list""" 708 709 pat = gmPerson.gmCurrentPatient() 710 emr = pat.get_emr() 711 712 if parent is None: 713 parent = wx.GetApp().GetTopWindow() 714 715 selected_soap = {} 716 selected_issue_pks = [] 717 selected_episode_pks = [] 718 selected_narrative_pks = [] 719 720 while 1: 721 # 1) select health issues to select episodes from 722 all_issues = emr.get_health_issues() 723 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue()) 724 dlg = gmEMRStructWidgets.cIssueListSelectorDlg ( 725 parent = parent, 726 id = -1, 727 issues = all_issues, 728 msg = _('\n In the list below mark the health issues you want to report on.\n') 729 ) 730 selection_idxs = [] 731 for idx in range(len(all_issues)): 732 if all_issues[idx]['pk_health_issue'] in selected_issue_pks: 733 selection_idxs.append(idx) 734 if len(selection_idxs) != 0: 735 dlg.set_selections(selections = selection_idxs) 736 btn_pressed = dlg.ShowModal() 737 selected_issues = dlg.get_selected_item_data() 738 dlg.Destroy() 739 740 if btn_pressed == wx.ID_CANCEL: 741 return selected_soap.values() 742 743 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ] 744 745 while 1: 746 # 2) select episodes to select items from 747 all_epis = emr.get_episodes(issues = selected_issue_pks) 748 749 if len(all_epis) == 0: 750 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.')) 751 break 752 753 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg ( 754 parent = parent, 755 id = -1, 756 episodes = all_epis, 757 msg = _( 758 '\n These are the episodes known for the health issues just selected.\n\n' 759 ' Now, mark the the episodes you want to report on.\n' 760 ) 761 ) 762 selection_idxs = [] 763 for idx in range(len(all_epis)): 764 if all_epis[idx]['pk_episode'] in selected_episode_pks: 765 selection_idxs.append(idx) 766 if len(selection_idxs) != 0: 767 dlg.set_selections(selections = selection_idxs) 768 btn_pressed = dlg.ShowModal() 769 selected_epis = dlg.get_selected_item_data() 770 dlg.Destroy() 771 772 if btn_pressed == wx.ID_CANCEL: 773 break 774 775 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ] 776 777 # 3) select narrative corresponding to the above constraints 778 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats) 779 780 if len(all_narr) == 0: 781 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.')) 782 continue 783 784 dlg = cNarrativeListSelectorDlg ( 785 parent = parent, 786 id = -1, 787 narrative = all_narr, 788 msg = _( 789 '\n This is the narrative (type %s) for the chosen episodes.\n\n' 790 ' Now, mark the entries you want to include in your report.\n' 791 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soapu')) ]) 792 ) 793 selection_idxs = [] 794 for idx in range(len(all_narr)): 795 if all_narr[idx]['pk_narrative'] in selected_narrative_pks: 796 selection_idxs.append(idx) 797 if len(selection_idxs) != 0: 798 dlg.set_selections(selections = selection_idxs) 799 btn_pressed = dlg.ShowModal() 800 selected_narr = dlg.get_selected_item_data() 801 dlg.Destroy() 802 803 if btn_pressed == wx.ID_CANCEL: 804 continue 805 806 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ] 807 for narr in selected_narr: 808 selected_soap[narr['pk_narrative']] = narr
809 #------------------------------------------------------------
810 -class cNarrativeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
811
812 - def __init__(self, *args, **kwargs):
813 814 narrative = kwargs['narrative'] 815 del kwargs['narrative'] 816 817 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 818 819 self.SetTitle(_('Select the narrative you are interested in ...')) 820 # FIXME: add epi/issue 821 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')]) #, _('Episode'), u'', _('Health Issue')]) 822 # FIXME: date used should be date of encounter, not date_modified 823 self._LCTRL_items.set_string_items ( 824 items = [ [narr['date'].strftime('%x %H:%M'), narr['provider'], gmClinNarrative.soap_cat2l10n[narr['soap_cat']], narr['narrative'].replace('\n', '/').replace('\r', '/')] for narr in narrative ] 825 ) 826 self._LCTRL_items.set_column_widths() 827 self._LCTRL_items.set_data(data = narrative)
828 #------------------------------------------------------------ 829 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg 830
831 -class cMoveNarrativeDlg(wxgMoveNarrativeDlg.wxgMoveNarrativeDlg):
832
833 - def __init__(self, *args, **kwargs):
834 835 self.encounter = kwargs['encounter'] 836 self.source_episode = kwargs['episode'] 837 del kwargs['encounter'] 838 del kwargs['episode'] 839 840 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs) 841 842 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)'))) 843 self.LBL_encounter.SetLabel('%s: %s %s - %s' % ( 844 gmDateTime.pydt_strftime(self.encounter['started'], '%Y %b %d'), 845 self.encounter['l10n_type'], 846 gmDateTime.pydt_strftime(self.encounter['started'], '%H:%M'), 847 gmDateTime.pydt_strftime(self.encounter['last_affirmed'], '%H:%M') 848 )) 849 pat = gmPerson.gmCurrentPatient() 850 emr = pat.get_emr() 851 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']]) 852 if len(narr) == 0: 853 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}] 854 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
855 856 #------------------------------------------------------------
857 - def _on_move_button_pressed(self, event):
858 859 target_episode = self._PRW_episode_selector.GetData(can_create = False) 860 861 if target_episode is None: 862 gmDispatcher.send(signal='statustext', msg=_('Must select episode to move narrative to first.')) 863 # FIXME: set to pink 864 self._PRW_episode_selector.SetFocus() 865 return False 866 867 target_episode = gmEMRStructItems.cEpisode(aPK_obj=target_episode) 868 869 self.encounter.transfer_clinical_data ( 870 source_episode = self.source_episode, 871 target_episode = target_episode 872 ) 873 874 if self.IsModal(): 875 self.EndModal(wx.ID_OK) 876 else: 877 self.Close()
878 #============================================================ 879 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl 880
881 -class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
882 """A panel for in-context editing of progress notes. 883 884 Expects to be used as a notebook page. 885 886 Left hand side: 887 - problem list (health issues and active episodes) 888 - previous notes 889 890 Right hand side: 891 - panel handling 892 - encounter details fields 893 - notebook with progress note editors 894 - visual progress notes 895 896 Listens to patient change signals, thus acts on the current patient. 897 """
898 - def __init__(self, *args, **kwargs):
899 900 wxgSoapPluginPnl.wxgSoapPluginPnl.__init__(self, *args, **kwargs) 901 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 902 903 self.__pat = gmPerson.gmCurrentPatient() 904 self.__init_ui() 905 self.__reset_ui_content() 906 self.__register_interests()
907 #-------------------------------------------------------- 908 # internal helpers 909 #--------------------------------------------------------
910 - def __init_ui(self):
911 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('In health issue')]) 912 self._LCTRL_active_problems.set_string_items() 913 914 self._splitter_main.SetSashGravity(0.5) 915 self._splitter_left.SetSashGravity(0.5) 916 917 splitter_size = self._splitter_main.GetSizeTuple()[0] 918 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True) 919 920 splitter_size = self._splitter_left.GetSizeTuple()[1] 921 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
922 #--------------------------------------------------------
923 - def __reset_ui_content(self):
924 """Clear all information from input panel.""" 925 926 self._LCTRL_active_problems.set_string_items() 927 928 self._TCTRL_recent_notes.SetValue(u'') 929 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on selected problem')) 930 931 self._PNL_editors.patient = None
932 #--------------------------------------------------------
933 - def __refresh_problem_list(self):
934 """Update health problems list.""" 935 936 self._LCTRL_active_problems.set_string_items() 937 938 emr = self.__pat.get_emr() 939 problems = emr.get_problems ( 940 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(), 941 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked() 942 ) 943 944 list_items = [] 945 active_problems = [] 946 for problem in problems: 947 if not problem['problem_active']: 948 if not problem['is_potential_problem']: 949 continue 950 951 active_problems.append(problem) 952 953 if problem['type'] == 'issue': 954 issue = emr.problem2issue(problem) 955 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue']) 956 if last_encounter is None: 957 last = issue['modified_when'].strftime('%m/%Y') 958 else: 959 last = last_encounter['last_affirmed'].strftime('%m/%Y') 960 961 list_items.append([last, problem['problem'], gmTools.u_left_arrow_with_tail]) 962 963 elif problem['type'] == 'episode': 964 epi = emr.problem2episode(problem) 965 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode']) 966 if last_encounter is None: 967 last = epi['episode_modified_when'].strftime('%m/%Y') 968 else: 969 last = last_encounter['last_affirmed'].strftime('%m/%Y') 970 971 list_items.append ([ 972 last, 973 problem['problem'], 974 gmTools.coalesce(initial = epi['health_issue'], instead = u'?') #gmTools.u_diameter 975 ]) 976 977 self._LCTRL_active_problems.set_string_items(items = list_items) 978 self._LCTRL_active_problems.set_column_widths() 979 self._LCTRL_active_problems.set_data(data = active_problems) 980 981 showing_potential_problems = ( 982 self._CHBOX_show_closed_episodes.IsChecked() 983 or 984 self._CHBOX_irrelevant_issues.IsChecked() 985 ) 986 if showing_potential_problems: 987 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items)) 988 else: 989 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items)) 990 991 return True
992 #--------------------------------------------------------
993 - def __get_info_for_issue_problem(self, problem=None, fancy=False):
994 soap = u'' 995 emr = self.__pat.get_emr() 996 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue']) 997 if prev_enc is not None: 998 soap += prev_enc.format ( 999 issues = [ problem['pk_health_issue'] ], 1000 with_soap = True, 1001 with_docs = fancy, 1002 with_tests = fancy, 1003 patient = self.__pat, 1004 fancy_header = False, 1005 with_rfe_aoe = True 1006 ) 1007 1008 tmp = emr.active_encounter.format_soap ( 1009 soap_cats = 'soapu', 1010 emr = emr, 1011 issues = [ problem['pk_health_issue'] ], 1012 ) 1013 if len(tmp) > 0: 1014 soap += _('Current encounter:') + u'\n' 1015 soap += u'\n'.join(tmp) + u'\n' 1016 1017 if problem['summary'] is not None: 1018 soap += u'\n-- %s ----------\n%s' % ( 1019 _('Cumulative summary'), 1020 gmTools.wrap ( 1021 text = problem['summary'], 1022 width = 45, 1023 initial_indent = u' ', 1024 subsequent_indent = u' ' 1025 ).strip('\n') 1026 ) 1027 1028 return soap
1029 #--------------------------------------------------------
1030 - def __get_info_for_episode_problem(self, problem=None, fancy=False):
1031 soap = u'' 1032 emr = self.__pat.get_emr() 1033 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode']) 1034 if prev_enc is not None: 1035 soap += prev_enc.format ( 1036 episodes = [ problem['pk_episode'] ], 1037 with_soap = True, 1038 with_docs = fancy, 1039 with_tests = fancy, 1040 patient = self.__pat, 1041 fancy_header = False, 1042 with_rfe_aoe = True 1043 ) 1044 else: 1045 if problem['pk_health_issue'] is not None: 1046 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue']) 1047 if prev_enc is not None: 1048 soap += prev_enc.format ( 1049 with_soap = True, 1050 with_docs = fancy, 1051 with_tests = fancy, 1052 patient = self.__pat, 1053 issues = [ problem['pk_health_issue'] ], 1054 fancy_header = False, 1055 with_rfe_aoe = True 1056 ) 1057 1058 tmp = emr.active_encounter.format_soap ( 1059 soap_cats = 'soapu', 1060 emr = emr, 1061 issues = [ problem['pk_health_issue'] ], 1062 ) 1063 if len(tmp) > 0: 1064 soap += _('Current encounter:') + u'\n' 1065 soap += u'\n'.join(tmp) + u'\n' 1066 1067 if problem['summary'] is not None: 1068 soap += u'\n-- %s ----------\n%s' % ( 1069 _('Cumulative summary'), 1070 gmTools.wrap ( 1071 text = problem['summary'], 1072 width = 45, 1073 initial_indent = u' ', 1074 subsequent_indent = u' ' 1075 ).strip('\n') 1076 ) 1077 1078 return soap
1079 #--------------------------------------------------------
1080 - def __refresh_recent_notes(self, problem=None):
1081 """This refreshes the recent-notes part.""" 1082 1083 if problem is None: 1084 caption = u'<?>' 1085 txt = u'' 1086 elif problem['type'] == u'issue': 1087 caption = problem['problem'][:35] 1088 txt = self.__get_info_for_issue_problem(problem = problem, fancy = not self._RBTN_notes_only.GetValue()) 1089 elif problem['type'] == u'episode': 1090 caption = problem['problem'][:35] 1091 txt = self.__get_info_for_episode_problem(problem = problem, fancy = not self._RBTN_notes_only.GetValue()) 1092 1093 self._TCTRL_recent_notes.SetValue(txt) 1094 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition()) 1095 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent info on %s%s%s') % ( 1096 gmTools.u_left_double_angle_quote, 1097 caption, 1098 gmTools.u_right_double_angle_quote 1099 )) 1100 1101 self._TCTRL_recent_notes.Refresh() 1102 1103 return True
1104 #-------------------------------------------------------- 1105 # event handling 1106 #--------------------------------------------------------
1107 - def __register_interests(self):
1108 """Configure enabled event signals.""" 1109 # client internal signals 1110 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1111 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1112 gmDispatcher.connect(signal = u'clin.episode_mod_db', receiver = self._on_episode_issue_mod_db) 1113 gmDispatcher.connect(signal = u'clin.health_issue_mod_db', receiver = self._on_episode_issue_mod_db) 1114 gmDispatcher.connect(signal = u'clin.episode_code_mod_db', receiver = self._on_episode_issue_mod_db)
1115 #--------------------------------------------------------
1116 - def _on_pre_patient_selection(self):
1117 wx.CallAfter(self.__on_pre_patient_selection)
1118
1119 - def __on_pre_patient_selection(self):
1120 self.__reset_ui_content()
1121 #--------------------------------------------------------
1122 - def _on_post_patient_selection(self):
1123 wx.CallAfter(self.__on_post_patient_selection)
1124
1125 - def __on_post_patient_selection(self):
1126 self._schedule_data_reget() 1127 self._PNL_editors.patient = self.__pat
1128 #--------------------------------------------------------
1129 - def _on_episode_issue_mod_db(self):
1130 wx.CallAfter(self._schedule_data_reget)
1131 #-------------------------------------------------------- 1132 # problem list specific events 1133 #--------------------------------------------------------
1134 - def _on_problem_focused(self, event):
1135 """Show related note at the bottom.""" 1136 pass
1137 #--------------------------------------------------------
1138 - def _on_problem_rclick(self, event):
1139 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1140 if problem['type'] == u'issue': 1141 gmEMRStructWidgets.edit_health_issue(parent = self, issue = problem.get_as_health_issue()) 1142 return 1143 1144 if problem['type'] == u'episode': 1145 gmEMRStructWidgets.edit_episode(parent = self, episode = problem.get_as_episode()) 1146 return 1147 1148 event.Skip()
1149 #--------------------------------------------------------
1150 - def _on_problem_selected(self, event):
1151 """Show related note at the bottom.""" 1152 self.__refresh_recent_notes ( 1153 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1154 )
1155 #--------------------------------------------------------
1156 - def _on_problem_activated(self, event):
1157 """Open progress note editor for this problem. 1158 """ 1159 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1160 if problem is None: 1161 return True 1162 1163 dbcfg = gmCfg.cCfgSQL() 1164 allow_duplicate_editors = bool(dbcfg.get2 ( 1165 option = u'horstspace.soap_editor.allow_same_episode_multiple_times', 1166 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1167 bias = u'user', 1168 default = False 1169 )) 1170 if self._PNL_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors): 1171 return True 1172 1173 gmGuiHelpers.gm_show_error ( 1174 aMessage = _( 1175 'Cannot open progress note editor for\n\n' 1176 '[%s].\n\n' 1177 ) % problem['problem'], 1178 aTitle = _('opening progress note editor') 1179 ) 1180 return False
1181 #--------------------------------------------------------
1182 - def _on_show_closed_episodes_checked(self, event):
1183 self.__refresh_problem_list()
1184 #--------------------------------------------------------
1185 - def _on_irrelevant_issues_checked(self, event):
1186 self.__refresh_problem_list()
1187 #-------------------------------------------------------- 1188 # recent-notes specific events 1189 #--------------------------------------------------------
1190 - def _on_notes_only_selected(self, event):
1191 self.__refresh_recent_notes ( 1192 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1193 )
1194 #--------------------------------------------------------
1195 - def _on_full_encounter_selected(self, event):
1196 self.__refresh_recent_notes ( 1197 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1198 )
1199 #-------------------------------------------------------- 1200 # reget mixin API 1201 #-------------------------------------------------------- 1202 # only needed for debugging: 1203 #def _schedule_data_reget(self): 1204 # gmRegetMixin.cRegetOnPaintMixin._schedule_data_reget(self) 1205 #--------------------------------------------------------
1206 - def _populate_with_data(self):
1207 self.__refresh_problem_list() 1208 return True
1209 1210 #============================================================ 1211 from Gnumed.wxGladeWidgets import wxgFancySoapEditorPnl 1212
1213 -class cFancySoapEditorPnl(wxgFancySoapEditorPnl.wxgFancySoapEditorPnl):
1214 """A panel holding everything needed to edit 1215 1216 - encounter metadata 1217 - textual progress notes 1218 - visual progress notes 1219 1220 in context. Does NOT act on the current patient. 1221 """
1222 - def __init__(self, *args, **kwargs):
1223 1224 wxgFancySoapEditorPnl.wxgFancySoapEditorPnl.__init__(self, *args, **kwargs) 1225 1226 self.__init_ui() 1227 self.patient = None 1228 self.__register_interests()
1229 #-------------------------------------------------------- 1230 # public API 1231 #--------------------------------------------------------
1232 - def add_editor(self, problem=None, allow_same_problem=False):
1233 return self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1234 #--------------------------------------------------------
1235 - def _get_patient(self):
1236 return self.__pat
1237
1238 - def _set_patient(self, patient):
1239 #if 1240 # self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback) 1241 self.__pat = patient 1242 self.__refresh_encounter() 1243 self.__refresh_soap_notebook()
1244 1245 patient = property(_get_patient, _set_patient) 1246 #--------------------------------------------------------
1247 - def save_encounter(self):
1248 1249 if self.__pat is None: 1250 return True 1251 1252 if not self.__encounter_valid_for_save(): 1253 return False 1254 1255 enc = self.__pat.emr.active_encounter 1256 1257 rfe = self._TCTRL_rfe.GetValue().strip() 1258 if len(rfe) == 0: 1259 enc['reason_for_encounter'] = None 1260 else: 1261 enc['reason_for_encounter'] = rfe 1262 aoe = self._TCTRL_aoe.GetValue().strip() 1263 if len(aoe) == 0: 1264 enc['assessment_of_encounter'] = None 1265 else: 1266 enc['assessment_of_encounter'] = aoe 1267 1268 enc.save_payload() 1269 1270 enc.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ] 1271 enc.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ] 1272 1273 return True
1274 #-------------------------------------------------------- 1275 # internal helpers 1276 #--------------------------------------------------------
1277 - def __init_ui(self):
1278 self._NB_soap_editors.MoveAfterInTabOrder(self._PRW_aoe_codes)
1279 #--------------------------------------------------------
1280 - def __reset_soap_notebook(self):
1281 self._NB_soap_editors.DeleteAllPages() 1282 self._NB_soap_editors.add_editor()
1283 #--------------------------------------------------------
1284 - def __refresh_soap_notebook(self):
1285 self.__reset_soap_notebook() 1286 1287 if self.__pat is None: 1288 return 1289 1290 dbcfg = gmCfg.cCfgSQL() 1291 auto_open_recent_problems = bool(dbcfg.get2 ( 1292 option = u'horstspace.soap_editor.auto_open_latest_episodes', 1293 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1294 bias = u'user', 1295 default = True 1296 )) 1297 1298 emr = self.__pat.emr 1299 recent_epis = emr.active_encounter.get_episodes() 1300 prev_enc = emr.get_last_but_one_encounter() 1301 if prev_enc is not None: 1302 recent_epis.extend(prev_enc.get_episodes()) 1303 1304 for epi in recent_epis: 1305 if not epi['episode_open']: 1306 continue 1307 self._NB_soap_editors.add_editor(problem = epi)
1308 #--------------------------------------------------------
1309 - def __reset_encounter_fields(self):
1310 self._TCTRL_rfe.SetValue(u'') 1311 self._PRW_rfe_codes.SetText(suppress_smarts = True) 1312 self._TCTRL_aoe.SetValue(u'') 1313 self._PRW_aoe_codes.SetText(suppress_smarts = True)
1314 #--------------------------------------------------------
1315 - def __refresh_encounter(self):
1316 """Update encounter fields.""" 1317 1318 self.__reset_encounter_fields() 1319 1320 if self.__pat is None: 1321 return 1322 1323 enc = self.__pat.emr.active_encounter 1324 1325 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u'')) 1326 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(enc.generic_codes_rfe) 1327 self._PRW_rfe_codes.SetText(val, data) 1328 1329 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u'')) 1330 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(enc.generic_codes_aoe) 1331 self._PRW_aoe_codes.SetText(val, data) 1332 1333 self._TCTRL_rfe.Refresh() 1334 self._PRW_rfe_codes.Refresh() 1335 self._TCTRL_aoe.Refresh() 1336 self._PRW_aoe_codes.Refresh()
1337 #--------------------------------------------------------
1338 - def __refresh_current_editor(self):
1339 self._NB_soap_editors.refresh_current_editor()
1340 # #-------------------------------------------------------- 1341 # def __encounter_modified(self): 1342 # """Assumes that the field data is valid.""" 1343 # 1344 # emr = self.__pat.get_emr() 1345 # enc = emr.active_encounter 1346 # 1347 # data = { 1348 # 'pk_type': enc['pk_type'], 1349 # 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''), 1350 # 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1351 # 'pk_location': enc['pk_org_unit'], 1352 # 'pk_patient': enc['pk_patient'], 1353 # 'pk_generic_codes_rfe': self._PRW_rfe_codes.GetData(), 1354 # 'pk_generic_codes_aoe': self._PRW_aoe_codes.GetData(), 1355 # 'started': enc['started'], 1356 # 'last_affirmed': enc['last_affirmed'] 1357 # } 1358 # 1359 # return not enc.same_payload(another_object = data) 1360 #--------------------------------------------------------
1361 - def __encounter_valid_for_save(self):
1362 return True
1363 #-------------------------------------------------------- 1364 # event handling 1365 #--------------------------------------------------------
1366 - def __register_interests(self):
1367 """Configure enabled event signals.""" 1368 # synchronous signals 1369 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback) 1370 1371 # client internal signals 1372 gmDispatcher.connect(signal = u'blobs.doc_med_mod_db', receiver = self._on_doc_mod_db) # visual progress notes 1373 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified) 1374 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched) 1375 gmDispatcher.connect(signal = u'clin.rfe_code_mod_db', receiver = self._on_encounter_code_modified) 1376 gmDispatcher.connect(signal = u'clin.aoe_code_mod_db', receiver = self._on_encounter_code_modified)
1377 #--------------------------------------------------------
1378 - def _pre_selection_callback(self):
1379 """Another patient is about to be activated. 1380 1381 Patient change will not proceed before this returns True. 1382 """ 1383 # don't worry about the encounter here - it will be offered 1384 # for editing higher up if anything was saved to the EMR 1385 if self.__pat is None: 1386 return True 1387 return self._NB_soap_editors.warn_on_unsaved_soap()
1388 #--------------------------------------------------------
1389 - def _pre_exit_callback(self):
1390 """The client is about to (be) shut down. 1391 1392 Shutdown will not proceed before this returns. 1393 """ 1394 if self.__pat is None: 1395 return True 1396 1397 # if self.__encounter_modified(): 1398 # do_save_enc = gmGuiHelpers.gm_show_question ( 1399 # aMessage = _( 1400 # 'You have modified the details\n' 1401 # 'of the current encounter.\n' 1402 # '\n' 1403 # 'Do you want to save those changes ?' 1404 # ), 1405 # aTitle = _('Starting new encounter') 1406 # ) 1407 # if do_save_enc: 1408 # if not self.save_encounter(): 1409 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 1410 1411 saved = self._NB_soap_editors.save_all_editors ( 1412 emr = self.__pat.emr, 1413 episode_name_candidates = [ 1414 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1415 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1416 ] 1417 ) 1418 if not saved: 1419 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True) 1420 return True
1421 #--------------------------------------------------------
1422 - def _on_doc_mod_db(self):
1423 wx.CallAfter(self.__refresh_current_editor)
1424 #--------------------------------------------------------
1426 wx.CallAfter(self.__on_encounter_code_modified)
1427 #--------------------------------------------------------
1429 self.__pat.emr.active_encounter.refetch_payload() 1430 self.__refresh_encounter()
1431 #--------------------------------------------------------
1433 wx.CallAfter(self.__refresh_encounter)
1434 #--------------------------------------------------------
1436 wx.CallAfter(self.__refresh_encounter)
1437 #-------------------------------------------------------- 1438 # SOAP editor specific buttons 1439 #--------------------------------------------------------
1440 - def _on_discard_editor_button_pressed(self, event):
1441 self._NB_soap_editors.close_current_editor() 1442 event.Skip()
1443 #--------------------------------------------------------
1444 - def _on_new_editor_button_pressed(self, event):
1445 self._NB_soap_editors.add_editor(allow_same_problem = True) 1446 event.Skip()
1447 #--------------------------------------------------------
1448 - def _on_clear_editor_button_pressed(self, event):
1449 self._NB_soap_editors.clear_current_editor() 1450 event.Skip()
1451 #--------------------------------------------------------
1452 - def _on_save_note_button_pressed(self, event):
1453 self._NB_soap_editors.save_current_editor ( 1454 emr = self.__pat.emr, 1455 episode_name_candidates = [ 1456 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1457 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1458 ] 1459 ) 1460 event.Skip()
1461 #--------------------------------------------------------
1462 - def _on_save_note_under_button_pressed(self, event):
1463 encounter = gmEMRStructWidgets.select_encounters ( 1464 parent = self, 1465 patient = self.__pat, 1466 single_selection = True 1467 ) 1468 # cancelled or None selected: 1469 if encounter is None: 1470 return 1471 1472 self._NB_soap_editors.save_current_editor ( 1473 emr = self.__pat.emr, 1474 encounter = encounter['pk_encounter'], 1475 episode_name_candidates = [ 1476 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1477 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1478 ] 1479 ) 1480 event.Skip()
1481 #--------------------------------------------------------
1482 - def _on_image_button_pressed(self, event):
1483 self._NB_soap_editors.add_visual_progress_note_to_current_problem() 1484 event.Skip()
1485 #-------------------------------------------------------- 1486 # encounter specific buttons 1487 #--------------------------------------------------------
1488 - def _on_save_encounter_button_pressed(self, event):
1489 self.save_encounter() 1490 event.Skip()
1491 #-------------------------------------------------------- 1492 # other buttons 1493 #--------------------------------------------------------
1494 - def _on_save_all_button_pressed(self, event):
1495 self.save_encounter() 1496 time.sleep(0.3) 1497 event.Skip() 1498 wx.SafeYield() 1499 1500 wx.CallAfter(self._save_all_button_pressed_bottom_half) 1501 wx.SafeYield()
1502 #--------------------------------------------------------
1504 emr = self.__pat.get_emr() 1505 saved = self._NB_soap_editors.save_all_editors ( 1506 emr = emr, 1507 episode_name_candidates = [ 1508 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1509 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1510 ] 1511 ) 1512 if not saved: 1513 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1514 1515 #============================================================
1516 -class cSoapNoteInputNotebook(wx.Notebook):
1517 """A notebook holding panels with progress note editors. 1518 1519 There can be one or several progress note editor panel 1520 for each episode being worked on. The editor class in 1521 each panel is configurable. 1522 1523 There will always be one open editor. 1524 """
1525 - def __init__(self, *args, **kwargs):
1526 1527 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER 1528 1529 wx.Notebook.__init__(self, *args, **kwargs)
1530 #-------------------------------------------------------- 1531 # public API 1532 #--------------------------------------------------------
1533 - def add_editor(self, problem=None, allow_same_problem=False):
1534 """Add a progress note editor page. 1535 1536 The way <allow_same_problem> is currently used in callers 1537 it only applies to unassociated episodes. 1538 """ 1539 problem_to_add = problem 1540 1541 # determine label 1542 if problem_to_add is None: 1543 label = _('new problem') 1544 else: 1545 # normalize problem type 1546 if isinstance(problem_to_add, gmEMRStructItems.cEpisode): 1547 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add, allow_closed = True) 1548 1549 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue): 1550 problem_to_add = gmEMRStructItems.health_issue2problem(health_issue = problem_to_add, allow_irrelevant = True) 1551 1552 if not isinstance(problem_to_add, gmEMRStructItems.cProblem): 1553 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add) 1554 1555 label = problem_to_add['problem'] 1556 # FIXME: configure maximum length 1557 if len(label) > 23: 1558 label = label[:21] + gmTools.u_ellipsis 1559 1560 # new unassociated problem or dupes allowed 1561 if allow_same_problem: 1562 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1563 result = self.AddPage ( 1564 page = new_page, 1565 text = label, 1566 select = True 1567 ) 1568 return result 1569 1570 # real problem, no dupes allowed 1571 # - raise existing editor 1572 for page_idx in range(self.GetPageCount()): 1573 page = self.GetPage(page_idx) 1574 1575 if problem_to_add is None: 1576 if page.problem is None: 1577 self.SetSelection(page_idx) 1578 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1579 return True 1580 continue 1581 1582 # editor is for unassociated new problem 1583 if page.problem is None: 1584 continue 1585 1586 # editor is for episode 1587 if page.problem['type'] == 'episode': 1588 if page.problem['pk_episode'] == problem_to_add['pk_episode']: 1589 self.SetSelection(page_idx) 1590 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1591 return True 1592 continue 1593 1594 # editor is for health issue 1595 if page.problem['type'] == 'issue': 1596 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']: 1597 self.SetSelection(page_idx) 1598 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1599 return True 1600 continue 1601 1602 # - or add new editor 1603 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1604 result = self.AddPage ( 1605 page = new_page, 1606 text = label, 1607 select = True 1608 ) 1609 1610 return result
1611 #--------------------------------------------------------
1612 - def close_current_editor(self):
1613 1614 page_idx = self.GetSelection() 1615 page = self.GetPage(page_idx) 1616 1617 if not page.empty: 1618 really_discard = gmGuiHelpers.gm_show_question ( 1619 _('Are you sure you really want to\n' 1620 'discard this progress note ?\n' 1621 ), 1622 _('Discarding progress note') 1623 ) 1624 if really_discard is False: 1625 return 1626 1627 self.DeletePage(page_idx) 1628 1629 # always keep one unassociated editor open 1630 if self.GetPageCount() == 0: 1631 self.add_editor()
1632 #--------------------------------------------------------
1633 - def save_current_editor(self, emr=None, episode_name_candidates=None, encounter=None):
1634 1635 page_idx = self.GetSelection() 1636 page = self.GetPage(page_idx) 1637 1638 if not page.save(emr = emr, episode_name_candidates = episode_name_candidates, encounter = encounter): 1639 return 1640 1641 self.DeletePage(page_idx) 1642 1643 # always keep one unassociated editor open 1644 if self.GetPageCount() == 0: 1645 self.add_editor()
1646 #--------------------------------------------------------
1647 - def warn_on_unsaved_soap(self):
1648 for page_idx in range(self.GetPageCount()): 1649 page = self.GetPage(page_idx) 1650 if page.empty: 1651 continue 1652 1653 gmGuiHelpers.gm_show_warning ( 1654 _('There are unsaved progress notes !\n'), 1655 _('Unsaved progress notes') 1656 ) 1657 return False 1658 1659 return True
1660 #--------------------------------------------------------
1661 - def save_all_editors(self, emr=None, episode_name_candidates=None):
1662 1663 _log.debug('saving editors: %s', self.GetPageCount()) 1664 1665 all_closed = True 1666 for page_idx in range((self.GetPageCount() - 1), -1, -1): 1667 _log.debug('#%s of %s', page_idx, self.GetPageCount()) 1668 try: 1669 self.ChangeSelection(page_idx) 1670 _log.debug('editor raised') 1671 except: 1672 _log.exception('cannot raise editor') 1673 page = self.GetPage(page_idx) 1674 if page.save(emr = emr, episode_name_candidates = episode_name_candidates): 1675 _log.debug('saved, deleting now') 1676 self.DeletePage(page_idx) 1677 else: 1678 _log.debug('not saved, not deleting') 1679 all_closed = False 1680 1681 # always keep one unassociated editor open 1682 if self.GetPageCount() == 0: 1683 self.add_editor() 1684 1685 return (all_closed is True)
1686 #--------------------------------------------------------
1687 - def clear_current_editor(self):
1688 page_idx = self.GetSelection() 1689 page = self.GetPage(page_idx) 1690 page.clear()
1691 #--------------------------------------------------------
1692 - def get_current_problem(self):
1693 page_idx = self.GetSelection() 1694 page = self.GetPage(page_idx) 1695 return page.problem
1696 #--------------------------------------------------------
1697 - def refresh_current_editor(self):
1698 page_idx = self.GetSelection() 1699 page = self.GetPage(page_idx) 1700 page.refresh()
1701 #--------------------------------------------------------
1703 page_idx = self.GetSelection() 1704 page = self.GetPage(page_idx) 1705 page.add_visual_progress_note()
1706 1707 #============================================================ 1708 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl 1709
1710 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1711 """An Edit Area like panel for entering progress notes. 1712 1713 Subjective: Codes: 1714 expando text ctrl 1715 Objective: Codes: 1716 expando text ctrl 1717 Assessment: Codes: 1718 expando text ctrl 1719 Plan: Codes: 1720 expando text ctrl 1721 visual progress notes 1722 panel with images 1723 Episode synopsis: Codes: 1724 text ctrl 1725 1726 - knows the problem this edit area is about 1727 - can deal with issue or episode type problems 1728 """ 1729
1730 - def __init__(self, *args, **kwargs):
1731 1732 try: 1733 self.problem = kwargs['problem'] 1734 del kwargs['problem'] 1735 except KeyError: 1736 self.problem = None 1737 1738 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs) 1739 1740 self.soap_fields = [ 1741 self._TCTRL_Soap, 1742 self._TCTRL_sOap, 1743 self._TCTRL_soAp, 1744 self._TCTRL_soaP 1745 ] 1746 1747 self.__init_ui() 1748 self.__register_interests()
1749 #--------------------------------------------------------
1750 - def __init_ui(self):
1751 self.refresh_summary() 1752 if self.problem is not None: 1753 if self.problem['summary'] is None: 1754 self._TCTRL_episode_summary.SetValue(u'') 1755 self.refresh_visual_soap()
1756 #--------------------------------------------------------
1757 - def refresh(self):
1758 self.refresh_summary() 1759 self.refresh_visual_soap()
1760 #--------------------------------------------------------
1761 - def refresh_summary(self):
1762 self._TCTRL_episode_summary.SetValue(u'') 1763 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([])) 1764 self._LBL_summary.SetLabel(_('Episode synopsis')) 1765 1766 # new problem ? 1767 if self.problem is None: 1768 return 1769 1770 # issue-level problem ? 1771 if self.problem['type'] == u'issue': 1772 return 1773 1774 # episode-level problem 1775 caption = _(u'Synopsis (%s)') % ( 1776 gmDateTime.pydt_strftime ( 1777 self.problem['modified_when'], 1778 format = '%B %Y', 1779 accuracy = gmDateTime.acc_days 1780 ) 1781 ) 1782 self._LBL_summary.SetLabel(caption) 1783 1784 if self.problem['summary'] is not None: 1785 self._TCTRL_episode_summary.SetValue(self.problem['summary'].strip()) 1786 1787 val, data = self._PRW_episode_codes.generic_linked_codes2item_dict(self.problem.generic_codes) 1788 self._PRW_episode_codes.SetText(val, data)
1789 #--------------------------------------------------------
1790 - def refresh_visual_soap(self):
1791 if self.problem is None: 1792 self._PNL_visual_soap.refresh(document_folder = None) 1793 return 1794 1795 if self.problem['type'] == u'issue': 1796 self._PNL_visual_soap.refresh(document_folder = None) 1797 return 1798 1799 if self.problem['type'] == u'episode': 1800 pat = gmPerson.gmCurrentPatient() 1801 doc_folder = pat.get_document_folder() 1802 emr = pat.get_emr() 1803 self._PNL_visual_soap.refresh ( 1804 document_folder = doc_folder, 1805 episodes = [self.problem['pk_episode']], 1806 encounter = emr.active_encounter['pk_encounter'] 1807 ) 1808 return
1809 #--------------------------------------------------------
1810 - def clear(self):
1811 for field in self.soap_fields: 1812 field.SetValue(u'') 1813 self._TCTRL_episode_summary.SetValue(u'') 1814 self._LBL_summary.SetLabel(_('Episode synopsis')) 1815 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([])) 1816 self._PNL_visual_soap.clear()
1817 #--------------------------------------------------------
1818 - def add_visual_progress_note(self):
1819 fname, discard_unmodified = select_visual_progress_note_template(parent = self) 1820 if fname is None: 1821 return False 1822 1823 if self.problem is None: 1824 issue = None 1825 episode = None 1826 elif self.problem['type'] == 'issue': 1827 issue = self.problem['pk_health_issue'] 1828 episode = None 1829 else: 1830 issue = self.problem['pk_health_issue'] 1831 episode = gmEMRStructItems.problem2episode(self.problem) 1832 1833 wx.CallAfter ( 1834 edit_visual_progress_note, 1835 filename = fname, 1836 episode = episode, 1837 discard_unmodified = discard_unmodified, 1838 health_issue = issue 1839 )
1840 #--------------------------------------------------------
1841 - def save(self, emr=None, episode_name_candidates=None, encounter=None):
1842 1843 if self.empty: 1844 return True 1845 1846 # new episode (standalone=unassociated or new-in-issue) 1847 if (self.problem is None) or (self.problem['type'] == 'issue'): 1848 episode = self.__create_new_episode(emr = emr, episode_name_candidates = episode_name_candidates) 1849 # user cancelled 1850 if episode is None: 1851 return False 1852 # existing episode 1853 else: 1854 episode = emr.problem2episode(self.problem) 1855 1856 if encounter is None: 1857 encounter = emr.current_encounter['pk_encounter'] 1858 1859 soap_notes = [] 1860 for note in self.soap: 1861 saved, data = gmClinNarrative.create_clin_narrative ( 1862 soap_cat = note[0], 1863 narrative = note[1], 1864 episode_id = episode['pk_episode'], 1865 encounter_id = encounter 1866 ) 1867 if saved: 1868 soap_notes.append(data) 1869 1870 # codes per narrative ! 1871 # for note in soap_notes: 1872 # if note['soap_cat'] == u's': 1873 # codes = self._PRW_Soap_codes 1874 # elif note['soap_cat'] == u'o': 1875 # elif note['soap_cat'] == u'a': 1876 # elif note['soap_cat'] == u'p': 1877 1878 # set summary but only if not already set above for a 1879 # newly created episode (either standalone or within 1880 # a health issue) 1881 if self.problem is not None: 1882 if self.problem['type'] == 'episode': 1883 episode['summary'] = self._TCTRL_episode_summary.GetValue().strip() 1884 episode.save() 1885 1886 # codes for episode 1887 episode.generic_codes = [ d['data'] for d in self._PRW_episode_codes.GetData() ] 1888 1889 return True
1890 #-------------------------------------------------------- 1891 # internal helpers 1892 #--------------------------------------------------------
1893 - def __create_new_episode(self, emr=None, episode_name_candidates=None):
1894 1895 episode_name_candidates.append(self._TCTRL_episode_summary.GetValue().strip()) 1896 for candidate in episode_name_candidates: 1897 if candidate is None: 1898 continue 1899 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//') 1900 break 1901 1902 dlg = wx.TextEntryDialog ( 1903 parent = self, 1904 message = _('Enter a short working name for this new problem:'), 1905 caption = _('Creating a problem (episode) to save the notelet under ...'), 1906 defaultValue = epi_name, 1907 style = wx.OK | wx.CANCEL | wx.CENTRE 1908 ) 1909 decision = dlg.ShowModal() 1910 if decision != wx.ID_OK: 1911 return None 1912 1913 epi_name = dlg.GetValue().strip() 1914 if epi_name == u'': 1915 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note')) 1916 return None 1917 1918 # create episode 1919 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True) 1920 new_episode['summary'] = self._TCTRL_episode_summary.GetValue().strip() 1921 new_episode.save() 1922 1923 if self.problem is not None: 1924 issue = emr.problem2issue(self.problem) 1925 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True): 1926 gmGuiHelpers.gm_show_warning ( 1927 _( 1928 'The new episode:\n' 1929 '\n' 1930 ' "%s"\n' 1931 '\n' 1932 'will remain unassociated despite the editor\n' 1933 'having been invoked from the health issue:\n' 1934 '\n' 1935 ' "%s"' 1936 ) % ( 1937 new_episode['description'], 1938 issue['description'] 1939 ), 1940 _('saving progress note') 1941 ) 1942 1943 return new_episode
1944 #-------------------------------------------------------- 1945 # event handling 1946 #--------------------------------------------------------
1947 - def __register_interests(self):
1948 for field in self.soap_fields: 1949 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout) 1950 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_episode_summary, self._TCTRL_episode_summary.GetId(), self._on_expando_needs_layout) 1951 gmDispatcher.connect(signal = u'blobs.doc_obj_mod_db', receiver = self._refresh_visual_soap)
1952 #--------------------------------------------------------
1953 - def _refresh_visual_soap(self):
1954 wx.CallAfter(self.refresh_visual_soap)
1955 #--------------------------------------------------------
1956 - def _on_expando_needs_layout(self, evt):
1957 # need to tell ourselves to re-Layout to refresh scroll bars 1958 1959 # provoke adding scrollbar if needed 1960 #self.Fit() # works on Linux but not on Windows 1961 self.FitInside() # needed on Windows rather than self.Fit() 1962 1963 if self.HasScrollbar(wx.VERTICAL): 1964 # scroll panel to show cursor 1965 expando = self.FindWindowById(evt.GetId()) 1966 y_expando = expando.GetPositionTuple()[1] 1967 h_expando = expando.GetSizeTuple()[1] 1968 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1 1969 if expando.NumberOfLines == 0: 1970 no_of_lines = 1 1971 else: 1972 no_of_lines = expando.NumberOfLines 1973 y_cursor = int(round((float(line_cursor) / no_of_lines) * h_expando)) 1974 y_desired_visible = y_expando + y_cursor 1975 1976 y_view = self.ViewStart[1] 1977 h_view = self.GetClientSizeTuple()[1] 1978 1979 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines 1980 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint() 1981 # print "wanted :", y_desired_visible 1982 # print "view-y :", y_view 1983 # print "scroll2:", h_view 1984 1985 # expando starts before view 1986 if y_desired_visible < y_view: 1987 # print "need to scroll up" 1988 self.Scroll(0, y_desired_visible) 1989 1990 if y_desired_visible > h_view: 1991 # print "need to scroll down" 1992 self.Scroll(0, y_desired_visible)
1993 #-------------------------------------------------------- 1994 # properties 1995 #--------------------------------------------------------
1996 - def _get_soap(self):
1997 soap_notes = [] 1998 1999 tmp = self._TCTRL_Soap.GetValue().strip() 2000 if tmp != u'': 2001 soap_notes.append(['s', tmp]) 2002 2003 tmp = self._TCTRL_sOap.GetValue().strip() 2004 if tmp != u'': 2005 soap_notes.append(['o', tmp]) 2006 2007 tmp = self._TCTRL_soAp.GetValue().strip() 2008 if tmp != u'': 2009 soap_notes.append(['a', tmp]) 2010 2011 tmp = self._TCTRL_soaP.GetValue().strip() 2012 if tmp != u'': 2013 soap_notes.append(['p', tmp]) 2014 2015 return soap_notes
2016 2017 soap = property(_get_soap, lambda x:x) 2018 #--------------------------------------------------------
2019 - def _get_empty(self):
2020 2021 # soap fields 2022 for field in self.soap_fields: 2023 if field.GetValue().strip() != u'': 2024 return False 2025 2026 # summary 2027 summary = self._TCTRL_episode_summary.GetValue().strip() 2028 if self.problem is None: 2029 if summary != u'': 2030 return False 2031 elif self.problem['type'] == u'issue': 2032 if summary != u'': 2033 return False 2034 else: 2035 if self.problem['summary'] is None: 2036 if summary != u'': 2037 return False 2038 else: 2039 if summary != self.problem['summary'].strip(): 2040 return False 2041 2042 # codes 2043 new_codes = self._PRW_episode_codes.GetData() 2044 if self.problem is None: 2045 if len(new_codes) > 0: 2046 return False 2047 elif self.problem['type'] == u'issue': 2048 if len(new_codes) > 0: 2049 return False 2050 else: 2051 old_code_pks = self.problem.generic_codes 2052 if len(old_code_pks) != len(new_codes): 2053 return False 2054 for code in new_codes: 2055 if code['data'] not in old_code_pks: 2056 return False 2057 2058 return True
2059 2060 empty = property(_get_empty, lambda x:x)
2061 #============================================================
2062 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl, gmKeywordExpansionWidgets.cKeywordExpansion_TextCtrlMixin):
2063
2064 - def __init__(self, *args, **kwargs):
2065 2066 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs) 2067 gmKeywordExpansionWidgets.cKeywordExpansion_TextCtrlMixin.__init__(self) 2068 self.enable_keyword_expansions() 2069 2070 self.__register_interests()
2071 #------------------------------------------------ 2072 # monkeypatch platform expando.py 2073 #------------------------------------------------
2074 - def _wrapLine(self, line, dc, width):
2075 2076 if (wx.MAJOR_VERSION >= 2) and (wx.MINOR_VERSION > 8): 2077 return wx_expando.ExpandoTextCtrl._wrapLine(line, dc, width) 2078 2079 # THIS FIX LIFTED FROM TRUNK IN SVN: 2080 # Estimate where the control will wrap the lines and 2081 # return the count of extra lines needed. 2082 pte = dc.GetPartialTextExtents(line) 2083 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) 2084 idx = 0 2085 start = 0 2086 count = 0 2087 spc = -1 2088 while idx < len(pte): 2089 if line[idx] == ' ': 2090 spc = idx 2091 if pte[idx] - start > width: 2092 # we've reached the max width, add a new line 2093 count += 1 2094 # did we see a space? if so restart the count at that pos 2095 if spc != -1: 2096 idx = spc + 1 2097 spc = -1 2098 if idx < len(pte): 2099 start = pte[idx] 2100 else: 2101 idx += 1 2102 return count
2103 #------------------------------------------------ 2104 # event handling 2105 #------------------------------------------------
2106 - def __register_interests(self):
2107 #wx.EVT_KEY_DOWN (self, self.__on_key_down) 2108 #wx.EVT_KEY_UP (self, self.__OnKeyUp) 2109 wx.EVT_SET_FOCUS(self, self.__on_focus)
2110 #--------------------------------------------------------
2111 - def __on_focus(self, evt):
2112 evt.Skip() 2113 wx.CallAfter(self._after_on_focus)
2114 #--------------------------------------------------------
2115 - def _after_on_focus(self):
2116 # robustify against PyDeadObjectError - since we are called 2117 # from wx.CallAfter this SoapCtrl may be gone by the time 2118 # we get to handling this layout request, say, on patient 2119 # change or some such 2120 if not self: 2121 return 2122 #wx.CallAfter(self._adjustCtrl) 2123 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId()) 2124 evt.SetEventObject(self) 2125 #evt.height = None 2126 #evt.numLines = None 2127 #evt.height = self.GetSize().height 2128 #evt.numLines = self.GetNumberOfLines() 2129 self.GetEventHandler().ProcessEvent(evt)
2130 2131 #============================================================ 2132 # visual progress notes 2133 #============================================================
2134 -def configure_visual_progress_note_editor():
2135 2136 def is_valid(value): 2137 2138 if value is None: 2139 gmDispatcher.send ( 2140 signal = 'statustext', 2141 msg = _('You need to actually set an editor.'), 2142 beep = True 2143 ) 2144 return False, value 2145 2146 if value.strip() == u'': 2147 gmDispatcher.send ( 2148 signal = 'statustext', 2149 msg = _('You need to actually set an editor.'), 2150 beep = True 2151 ) 2152 return False, value 2153 2154 found, binary = gmShellAPI.detect_external_binary(value) 2155 if not found: 2156 gmDispatcher.send ( 2157 signal = 'statustext', 2158 msg = _('The command [%s] is not found.') % value, 2159 beep = True 2160 ) 2161 return True, value 2162 2163 return True, binary
2164 #------------------------------------------ 2165 cmd = gmCfgWidgets.configure_string_option ( 2166 message = _( 2167 'Enter the shell command with which to start\n' 2168 'the image editor for visual progress notes.\n' 2169 '\n' 2170 'Any "%(img)s" included with the arguments\n' 2171 'will be replaced by the file name of the\n' 2172 'note template.' 2173 ), 2174 option = u'external.tools.visual_soap_editor_cmd', 2175 bias = 'user', 2176 default_value = None, 2177 validator = is_valid 2178 ) 2179 2180 return cmd 2181 #============================================================
2182 -def select_file_as_visual_progress_note_template(parent=None):
2183 if parent is None: 2184 parent = wx.GetApp().GetTopWindow() 2185 2186 dlg = wx.FileDialog ( 2187 parent = parent, 2188 message = _('Choose file to use as template for new visual progress note'), 2189 defaultDir = os.path.expanduser('~'), 2190 defaultFile = '', 2191 #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 2192 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 2193 ) 2194 result = dlg.ShowModal() 2195 2196 if result == wx.ID_CANCEL: 2197 dlg.Destroy() 2198 return None 2199 2200 full_filename = dlg.GetPath() 2201 dlg.Hide() 2202 dlg.Destroy() 2203 return full_filename
2204 #------------------------------------------------------------
2205 -def select_visual_progress_note_template(parent=None):
2206 2207 if parent is None: 2208 parent = wx.GetApp().GetTopWindow() 2209 2210 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 2211 parent, 2212 -1, 2213 caption = _('Visual progress note source'), 2214 question = _('From which source do you want to pick the image template ?'), 2215 button_defs = [ 2216 {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True}, 2217 {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False}, 2218 {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False} 2219 ] 2220 ) 2221 result = dlg.ShowModal() 2222 dlg.Destroy() 2223 2224 # 1) select from template 2225 if result == wx.ID_YES: 2226 _log.debug('visual progress note template from: database template') 2227 from Gnumed.wxpython import gmFormWidgets 2228 template = gmFormWidgets.manage_form_templates ( 2229 parent = parent, 2230 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE], 2231 active_only = True 2232 ) 2233 if template is None: 2234 return (None, None) 2235 filename = template.export_to_file() 2236 if filename is None: 2237 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long']) 2238 return (None, None) 2239 return (filename, True) 2240 2241 # 2) select from disk file 2242 if result == wx.ID_NO: 2243 _log.debug('visual progress note template from: disk file') 2244 fname = select_file_as_visual_progress_note_template(parent = parent) 2245 if fname is None: 2246 return (None, None) 2247 # create a copy of the picked file -- don't modify the original 2248 ext = os.path.splitext(fname)[1] 2249 tmp_name = gmTools.get_unique_filename(suffix = ext) 2250 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name) 2251 shutil.copy2(fname, tmp_name) 2252 return (tmp_name, False) 2253 2254 # 3) acquire from capture device 2255 if result == wx.ID_CANCEL: 2256 _log.debug('visual progress note template from: image capture device') 2257 fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent) 2258 if fnames is None: 2259 return (None, None) 2260 if len(fnames) == 0: 2261 return (None, None) 2262 return (fnames[0], False) 2263 2264 _log.debug('no visual progress note template source selected') 2265 return (None, None)
2266 #------------------------------------------------------------
2267 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None, health_issue=None):
2268 """This assumes <filename> contains an image which can be handled by the configured image editor.""" 2269 2270 if doc_part is not None: 2271 filename = doc_part.export_to_file() 2272 if filename is None: 2273 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 2274 return None 2275 2276 dbcfg = gmCfg.cCfgSQL() 2277 cmd = dbcfg.get2 ( 2278 option = u'external.tools.visual_soap_editor_cmd', 2279 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2280 bias = 'user' 2281 ) 2282 2283 if cmd is None: 2284 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False) 2285 cmd = configure_visual_progress_note_editor() 2286 if cmd is None: 2287 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True) 2288 return None 2289 2290 if u'%(img)s' in cmd: 2291 cmd = cmd % {u'img': filename} 2292 else: 2293 cmd = u'%s %s' % (cmd, filename) 2294 2295 if discard_unmodified: 2296 original_stat = os.stat(filename) 2297 original_md5 = gmTools.file2md5(filename) 2298 2299 success = gmShellAPI.run_command_in_shell(cmd, blocking = True) 2300 if not success: 2301 gmGuiHelpers.gm_show_error ( 2302 _( 2303 'There was a problem with running the editor\n' 2304 'for visual progress notes.\n' 2305 '\n' 2306 ' [%s]\n' 2307 '\n' 2308 ) % cmd, 2309 _('Editing visual progress note') 2310 ) 2311 return None 2312 2313 try: 2314 open(filename, 'r').close() 2315 except StandardError: 2316 _log.exception('problem accessing visual progress note file [%s]', filename) 2317 gmGuiHelpers.gm_show_error ( 2318 _( 2319 'There was a problem reading the visual\n' 2320 'progress note from the file:\n' 2321 '\n' 2322 ' [%s]\n' 2323 '\n' 2324 ) % filename, 2325 _('Saving visual progress note') 2326 ) 2327 return None 2328 2329 if discard_unmodified: 2330 modified_stat = os.stat(filename) 2331 # same size ? 2332 if original_stat.st_size == modified_stat.st_size: 2333 modified_md5 = gmTools.file2md5(filename) 2334 # same hash ? 2335 if original_md5 == modified_md5: 2336 _log.debug('visual progress note (template) not modified') 2337 # ask user to decide 2338 msg = _( 2339 u'You either created a visual progress note from a template\n' 2340 u'in the database (rather than from a file on disk) or you\n' 2341 u'edited an existing visual progress note.\n' 2342 u'\n' 2343 u'The template/original was not modified at all, however.\n' 2344 u'\n' 2345 u'Do you still want to save the unmodified image as a\n' 2346 u'visual progress note into the EMR of the patient ?\n' 2347 ) 2348 save_unmodified = gmGuiHelpers.gm_show_question ( 2349 msg, 2350 _('Saving visual progress note') 2351 ) 2352 if not save_unmodified: 2353 _log.debug('user discarded unmodified note') 2354 return 2355 2356 if doc_part is not None: 2357 _log.debug('updating visual progress note') 2358 doc_part.update_data_from_file(fname = filename) 2359 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True) 2360 return None 2361 2362 if not isinstance(episode, gmEMRStructItems.cEpisode): 2363 if episode is None: 2364 episode = _('visual progress notes') 2365 pat = gmPerson.gmCurrentPatient() 2366 emr = pat.get_emr() 2367 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False) 2368 2369 doc = gmDocumentWidgets.save_file_as_new_document ( 2370 filename = filename, 2371 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE, 2372 episode = episode, 2373 unlock_patient = False 2374 ) 2375 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True) 2376 2377 return doc
2378 #============================================================
2379 -class cVisualSoapTemplatePhraseWheel(gmPhraseWheel.cPhraseWheel):
2380 """Phrasewheel to allow selection of visual SOAP template.""" 2381
2382 - def __init__(self, *args, **kwargs):
2383 2384 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 2385 2386 query = u""" 2387 SELECT 2388 pk AS data, 2389 name_short AS list_label, 2390 name_sort AS field_label 2391 FROM 2392 ref.paperwork_templates 2393 WHERE 2394 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND ( 2395 name_long %%(fragment_condition)s 2396 OR 2397 name_short %%(fragment_condition)s 2398 ) 2399 ORDER BY list_label 2400 LIMIT 15 2401 """ % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE 2402 2403 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query]) 2404 mp.setThresholds(2, 3, 5) 2405 2406 self.matcher = mp 2407 self.selection_only = True
2408 #--------------------------------------------------------
2409 - def _data2instance(self):
2410 if self.GetData() is None: 2411 return None 2412 2413 return gmForms.cFormTemplate(aPK_obj = self.GetData())
2414 #============================================================ 2415 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl 2416
2417 -class cVisualSoapPresenterPnl(wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl):
2418
2419 - def __init__(self, *args, **kwargs):
2420 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs) 2421 self._SZR_soap = self.GetSizer() 2422 self.__bitmaps = []
2423 #-------------------------------------------------------- 2424 # external API 2425 #--------------------------------------------------------
2426 - def refresh(self, document_folder=None, episodes=None, encounter=None):
2427 2428 self.clear() 2429 if document_folder is not None: 2430 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter) 2431 if len(soap_docs) > 0: 2432 for soap_doc in soap_docs: 2433 parts = soap_doc.parts 2434 if len(parts) == 0: 2435 continue 2436 part = parts[0] 2437 fname = part.export_to_file() 2438 if fname is None: 2439 continue 2440 2441 # create bitmap 2442 img = gmGuiHelpers.file2scaled_image ( 2443 filename = fname, 2444 height = 30 2445 ) 2446 #bmp = wx.StaticBitmap(self, -1, img, style = wx.NO_BORDER) 2447 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER) 2448 2449 # create tooltip 2450 img = gmGuiHelpers.file2scaled_image ( 2451 filename = fname, 2452 height = 150 2453 ) 2454 tip = agw_stt.SuperToolTip ( 2455 u'', 2456 bodyImage = img, 2457 header = _('Created: %s') % gmDateTime.pydt_strftime(part['date_generated'], '%Y %b %d'), 2458 footer = gmTools.coalesce(part['doc_comment'], u'').strip() 2459 ) 2460 tip.SetTopGradientColor('white') 2461 tip.SetMiddleGradientColor('white') 2462 tip.SetBottomGradientColor('white') 2463 tip.SetTarget(bmp) 2464 2465 bmp.doc_part = part 2466 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked) 2467 # FIXME: add context menu for Delete/Clone/Add/Configure 2468 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3) 2469 self.__bitmaps.append(bmp) 2470 2471 self.GetParent().Layout()
2472 #--------------------------------------------------------
2473 - def clear(self):
2474 while len(self._SZR_soap.GetChildren()) > 0: 2475 self._SZR_soap.Detach(0) 2476 # for child_idx in range(len(self._SZR_soap.GetChildren())): 2477 # self._SZR_soap.Detach(child_idx) 2478 for bmp in self.__bitmaps: 2479 bmp.Destroy() 2480 self.__bitmaps = []
2481 #--------------------------------------------------------
2482 - def _on_bitmap_leftclicked(self, evt):
2483 wx.CallAfter ( 2484 edit_visual_progress_note, 2485 doc_part = evt.GetEventObject().doc_part, 2486 discard_unmodified = True 2487 )
2488 2489 #============================================================ 2490 #============================================================ 2491 from Gnumed.wxGladeWidgets import wxgSimpleSoapPluginPnl 2492
2493 -class cSimpleSoapPluginPnl(wxgSimpleSoapPluginPnl.wxgSimpleSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
2494 - def __init__(self, *args, **kwargs):
2495 2496 wxgSimpleSoapPluginPnl.wxgSimpleSoapPluginPnl.__init__(self, *args, **kwargs) 2497 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2498 2499 self.__curr_pat = gmPerson.gmCurrentPatient() 2500 self.__problem = None 2501 self.__init_ui() 2502 self.__register_interests()
2503 #----------------------------------------------------- 2504 # internal API 2505 #-----------------------------------------------------
2506 - def __init_ui(self):
2507 self._LCTRL_problems.set_columns(columns = [_('Problem list')]) 2508 self._LCTRL_problems.activate_callback = self._on_problem_activated 2509 self._LCTRL_problems.item_tooltip_callback = self._on_get_problem_tooltip 2510 2511 self._splitter_main.SetSashGravity(0.5) 2512 splitter_width = self._splitter_main.GetSizeTuple()[0] 2513 self._splitter_main.SetSashPosition(splitter_width / 2, True) 2514 2515 self._TCTRL_soap.Disable() 2516 self._BTN_save_soap.Disable() 2517 self._BTN_clear_soap.Disable()
2518 #-----------------------------------------------------
2519 - def __reset_ui(self):
2520 self._LCTRL_problems.set_string_items() 2521 self._TCTRL_soap_problem.SetValue(_('<above, double-click problem to start entering SOAP note>')) 2522 self._TCTRL_soap.SetValue(u'') 2523 self._CHBOX_filter_by_problem.SetLabel(_('&Filter by problem')) 2524 self._TCTRL_journal.SetValue(u'') 2525 2526 self._TCTRL_soap.Disable() 2527 self._BTN_save_soap.Disable() 2528 self._BTN_clear_soap.Disable()
2529 #-----------------------------------------------------
2530 - def __save_soap(self):
2531 if not self.__curr_pat.connected: 2532 return None 2533 2534 if self.__problem is None: 2535 return None 2536 2537 saved = self.__curr_pat.emr.add_clin_narrative ( 2538 note = self._TCTRL_soap.GetValue().strip(), 2539 soap_cat = u'u', 2540 episode = self.__problem 2541 ) 2542 2543 if saved is None: 2544 return False 2545 2546 self._TCTRL_soap.SetValue(u'') 2547 self.__refresh_journal() 2548 return True
2549 #-----------------------------------------------------
2550 - def __perhaps_save_soap(self):
2551 if self._TCTRL_soap.GetValue().strip() == u'': 2552 return True 2553 if self.__problem is None: 2554 # FIXME: this could potentially lose input 2555 self._TCTRL_soap.SetValue(u'') 2556 return None 2557 save_it = gmGuiHelpers.gm_show_question ( 2558 title = _('Saving SOAP note'), 2559 question = _('Do you want to save the SOAP note ?') 2560 ) 2561 if save_it: 2562 return self.__save_soap() 2563 return False
2564 #-----------------------------------------------------
2565 - def __refresh_problem_list(self):
2566 self._LCTRL_problems.set_string_items() 2567 emr = self.__curr_pat.get_emr() 2568 epis = emr.get_episodes(open_status = True) 2569 if len(epis) > 0: 2570 self._LCTRL_problems.set_string_items(items = [ u'%s%s' % ( 2571 e['description'], 2572 gmTools.coalesce(e['health_issue'], u'', u' (%s)') 2573 ) for e in epis ]) 2574 self._LCTRL_problems.set_data(epis)
2575 #-----------------------------------------------------
2576 - def __refresh_journal(self):
2577 self._TCTRL_journal.SetValue(u'') 2578 epi = self._LCTRL_problems.get_selected_item_data(only_one = True) 2579 2580 if epi is not None: 2581 self._CHBOX_filter_by_problem.SetLabel(_('&Filter by problem %s%s%s') % ( 2582 gmTools.u_left_double_angle_quote, 2583 epi['description'], 2584 gmTools.u_right_double_angle_quote 2585 )) 2586 self._CHBOX_filter_by_problem.Refresh() 2587 2588 if not self._CHBOX_filter_by_problem.IsChecked(): 2589 self._TCTRL_journal.SetValue(self.__curr_pat.emr.format_summary(dob = self.__curr_pat['dob'])) 2590 return 2591 2592 if epi is None: 2593 return 2594 2595 self._TCTRL_journal.SetValue(epi.format_as_journal())
2596 #----------------------------------------------------- 2597 # event handling 2598 #-----------------------------------------------------
2599 - def __register_interests(self):
2600 """Configure enabled event signals.""" 2601 # client internal signals 2602 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 2603 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 2604 gmDispatcher.connect(signal = u'clin.episode_mod_db', receiver = self._on_episode_issue_mod_db) 2605 gmDispatcher.connect(signal = u'clin.health_issue_mod_db', receiver = self._on_episode_issue_mod_db) 2606 2607 # synchronous signals 2608 self.__curr_pat.register_pre_selection_callback(callback = self._pre_selection_callback) 2609 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
2610 #-----------------------------------------------------
2611 - def _pre_selection_callback(self):
2612 """Another patient is about to be activated. 2613 2614 Patient change will not proceed before this returns True. 2615 """ 2616 if not self.__curr_pat.connected: 2617 return True 2618 self.__perhaps_save_soap() 2619 self.__problem = None 2620 return True
2621 #-----------------------------------------------------
2622 - def _pre_exit_callback(self):
2623 """The client is about to be shut down. 2624 2625 Shutdown will not proceed before this returns. 2626 """ 2627 if not self.__curr_pat.connected: 2628 return 2629 if not self.__save_soap(): 2630 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save SimpleNotes SOAP note.'), beep = True) 2631 return
2632 #-----------------------------------------------------
2633 - def _on_pre_patient_selection(self):
2634 wx.CallAfter(self.__reset_ui)
2635 #-----------------------------------------------------
2636 - def _on_post_patient_selection(self):
2637 wx.CallAfter(self._schedule_data_reget)
2638 #-----------------------------------------------------
2639 - def _on_episode_issue_mod_db(self):
2640 wx.CallAfter(self._schedule_data_reget)
2641 #-----------------------------------------------------
2642 - def _on_problem_activated(self, event):
2643 self.__perhaps_save_soap() 2644 epi = self._LCTRL_problems.get_selected_item_data(only_one = True) 2645 self._TCTRL_soap_problem.SetValue(_('Progress note: %s%s') % ( 2646 epi['description'], 2647 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2648 )) 2649 self.__problem = epi 2650 self._TCTRL_soap.SetValue(u'') 2651 2652 self._TCTRL_soap.Enable() 2653 self._BTN_save_soap.Enable() 2654 self._BTN_clear_soap.Enable()
2655 #-----------------------------------------------------
2656 - def _on_get_problem_tooltip(self, episode):
2657 return episode.format ( 2658 patient = self.__curr_pat, 2659 with_summary = False, 2660 with_codes = True, 2661 with_encounters = False, 2662 with_documents = False, 2663 with_hospital_stays = False, 2664 with_procedures = False, 2665 with_family_history = False, 2666 with_tests = False, 2667 with_vaccinations = False, 2668 with_health_issue = True 2669 )
2670 #-----------------------------------------------------
2671 - def _on_list_item_selected(self, event):
2672 event.Skip() 2673 self.__refresh_journal()
2674 #-----------------------------------------------------
2675 - def _on_filter_by_problem_checked(self, event):
2676 event.Skip() 2677 self.__refresh_journal()
2678 #-----------------------------------------------------
2679 - def _on_add_problem_button_pressed(self, event):
2680 event.Skip() 2681 epi_name = wx.GetTextFromUser ( 2682 _('Please enter a name for the new problem:'), 2683 caption = _('Adding a problem'), 2684 parent = self 2685 ).strip() 2686 if epi_name == u'': 2687 return 2688 self.__curr_pat.emr.add_episode ( 2689 episode_name = epi_name, 2690 pk_health_issue = None, 2691 is_open = True 2692 )
2693 #-----------------------------------------------------
2694 - def _on_edit_problem_button_pressed(self, event):
2695 event.Skip() 2696 epi = self._LCTRL_problems.get_selected_item_data(only_one = True) 2697 if epi is None: 2698 return 2699 gmEMRStructWidgets.edit_episode(parent = self, episode = epi)
2700 #-----------------------------------------------------
2701 - def _on_delete_problem_button_pressed(self, event):
2702 event.Skip() 2703 epi = self._LCTRL_problems.get_selected_item_data(only_one = True) 2704 if epi is None: 2705 return 2706 if not gmEMRStructItems.delete_episode(episode = epi): 2707 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete problem. There is still clinical data recorded for it.'))
2708 #-----------------------------------------------------
2709 - def _on_save_soap_button_pressed(self, event):
2710 event.Skip() 2711 self.__save_soap()
2712 #-----------------------------------------------------
2713 - def _on_clear_soap_button_pressed(self, event):
2714 event.Skip() 2715 self._TCTRL_soap.SetValue(u'')
2716 #----------------------------------------------------- 2717 # reget-on-paint mixin API 2718 #-----------------------------------------------------
2719 - def _populate_with_data(self):
2720 self.__refresh_problem_list() 2721 self.__refresh_journal() 2722 self._TCTRL_soap.SetValue(u'') 2723 return True
2724 2725 #============================================================ 2726 # main 2727 #------------------------------------------------------------ 2728 if __name__ == '__main__': 2729 2730 if len(sys.argv) < 2: 2731 sys.exit() 2732 2733 if sys.argv[1] != 'test': 2734 sys.exit() 2735 2736 gmI18N.activate_locale() 2737 gmI18N.install_domain(domain = 'gnumed') 2738 2739 #----------------------------------------
2740 - def test_select_narrative_from_episodes():
2741 pat = gmPersonSearch.ask_for_patient() 2742 set_active_patient(patient = pat) 2743 app = wx.PyWidgetTester(size = (200, 200)) 2744 sels = select_narrative_from_episodes_new() 2745 print "selected:" 2746 for sel in sels: 2747 print sel
2748 #----------------------------------------
2749 - def test_cSoapNoteExpandoEditAreaPnl():
2750 pat = gmPersonSearch.ask_for_patient() 2751 application = wx.PyWidgetTester(size=(800,500)) 2752 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1) 2753 application.frame.Show(True) 2754 application.MainLoop()
2755 #----------------------------------------
2756 - def test_cSoapPluginPnl():
2757 patient = gmPersonSearch.ask_for_patient() 2758 if patient is None: 2759 print "No patient. Exiting gracefully..." 2760 return 2761 set_active_patient(patient=patient) 2762 2763 application = wx.PyWidgetTester(size=(800,500)) 2764 soap_input = cSoapPluginPnl(application.frame, -1) 2765 application.frame.Show(True) 2766 soap_input._schedule_data_reget() 2767 application.MainLoop()
2768 #---------------------------------------- 2769 test_select_narrative_from_episodes() 2770 #test_cSoapNoteExpandoEditAreaPnl() 2771 #test_cSoapPluginPnl() 2772 2773 #============================================================ 2774