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