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

Source Code for Module Gnumed.wxpython.gmEMRStructWidgets

   1  """GNUmed EMR structure editors 
   2   
   3          This module contains widgets to create and edit EMR structural 
   4          elements (issues, enconters, episodes). 
   5   
   6          This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au> 
   7          and Karsten <Karsten.Hilbert@gmx.net>. 
   8  """ 
   9  #================================================================ 
  10  __version__ = "$Revision: 1.114 $" 
  11  __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net" 
  12  __license__ = "GPL" 
  13   
  14  # stdlib 
  15  import sys, re, datetime as pydt, logging, time 
  16   
  17   
  18  # 3rd party 
  19  import wx 
  20   
  21   
  22  # GNUmed 
  23  if __name__ == '__main__': 
  24          sys.path.insert(0, '../../') 
  25  from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions 
  26  from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery, gmPersonSearch 
  27  from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets 
  28  from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg 
  29  from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl 
  30   
  31   
  32  _log = logging.getLogger('gm.ui') 
  33  _log.info(__version__) 
  34  #================================================================ 
  35  # performed procedure related widgets/functions 
  36  #---------------------------------------------------------------- 
37 -def manage_performed_procedures(parent=None):
38 39 pat = gmPerson.gmCurrentPatient() 40 emr = pat.get_emr() 41 42 if parent is None: 43 parent = wx.GetApp().GetTopWindow() 44 #----------------------------------------- 45 def edit(procedure=None): 46 return edit_procedure(parent = parent, procedure = procedure)
47 #----------------------------------------- 48 def delete(procedure=None): 49 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']): 50 return True 51 52 gmDispatcher.send ( 53 signal = u'statustext', 54 msg = _('Cannot delete performed procedure.'), 55 beep = True 56 ) 57 return False 58 #----------------------------------------- 59 def refresh(lctrl): 60 procs = emr.get_performed_procedures() 61 62 items = [ 63 [ 64 u'%s%s' % ( 65 p['clin_when'].strftime('%Y-%m-%d'), 66 gmTools.bool2subst ( 67 p['is_ongoing'], 68 _(' (ongoing)'), 69 gmTools.coalesce ( 70 initial = p['clin_end'], 71 instead = u'', 72 template_initial = u' - %s', 73 function_initial = ('strftime', u'%Y-%m-%d') 74 ) 75 ) 76 ), 77 p['clin_where'], 78 p['episode'], 79 p['performed_procedure'] 80 ] for p in procs 81 ] 82 lctrl.set_string_items(items = items) 83 lctrl.set_data(data = procs) 84 #----------------------------------------- 85 gmListWidgets.get_choices_from_list ( 86 parent = parent, 87 msg = _('\nSelect the procedure you want to edit !\n'), 88 caption = _('Editing performed procedures ...'), 89 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')], 90 single_selection = True, 91 edit_callback = edit, 92 new_callback = edit, 93 delete_callback = delete, 94 refresh_callback = refresh 95 ) 96 #----------------------------------------------------------------
97 -def edit_procedure(parent=None, procedure=None):
98 ea = cProcedureEAPnl(parent = parent, id = -1) 99 ea.data = procedure 100 ea.mode = gmTools.coalesce(procedure, 'new', 'edit') 101 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 102 dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure'))) 103 if dlg.ShowModal() == wx.ID_OK: 104 dlg.Destroy() 105 return True 106 dlg.Destroy() 107 return False
108 #---------------------------------------------------------------- 109 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl 110
111 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
112
113 - def __init__(self, *args, **kwargs):
114 wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs) 115 gmEditArea.cGenericEditAreaMixin.__init__(self) 116 117 self.mode = 'new' 118 self.data = None 119 120 self.__init_ui()
121 #----------------------------------------------------------------
122 - def __init_ui(self):
123 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus) 124 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID) 125 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus) 126 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus) 127 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus) 128 129 # location 130 mp = gmMatchProvider.cMatchProvider_SQL2 ( 131 queries = [ 132 u""" 133 SELECT DISTINCT ON (data) data, location 134 FROM ( 135 SELECT 136 clin_where as data, 137 clin_where as location 138 FROM 139 clin.procedure 140 WHERE 141 clin_where %(fragment_condition)s 142 143 UNION ALL 144 145 SELECT 146 narrative as data, 147 narrative as location 148 FROM 149 clin.hospital_stay 150 WHERE 151 narrative %(fragment_condition)s 152 ) as union_result 153 ORDER BY data 154 LIMIT 25""" 155 ] 156 ) 157 mp.setThresholds(2, 4, 6) 158 self._PRW_location.matcher = mp 159 160 # procedure 161 mp = gmMatchProvider.cMatchProvider_SQL2 ( 162 queries = [ 163 u""" 164 select distinct on (narrative) narrative, narrative 165 from clin.procedure 166 where narrative %(fragment_condition)s 167 order by narrative 168 limit 25 169 """ ] 170 ) 171 mp.setThresholds(2, 4, 6) 172 self._PRW_procedure.matcher = mp
173 #----------------------------------------------------------------
175 stay = self._PRW_hospital_stay.GetData() 176 if stay is None: 177 self._PRW_hospital_stay.SetText() 178 self._PRW_location.Enable(True) 179 self._PRW_episode.Enable(True) 180 self._LBL_hospital_details.SetLabel(u'') 181 else: 182 self._PRW_location.SetText() 183 self._PRW_location.Enable(False) 184 self._PRW_episode.SetText() 185 self._PRW_episode.Enable(False) 186 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
187 #----------------------------------------------------------------
188 - def _on_location_lost_focus(self):
189 if self._PRW_location.GetValue().strip() == u'': 190 self._PRW_hospital_stay.Enable(True) 191 # self._PRW_episode.Enable(False) 192 else: 193 self._PRW_hospital_stay.SetText() 194 self._PRW_hospital_stay.Enable(False) 195 self._PRW_hospital_stay.display_as_valid(True)
196 # self._PRW_episode.Enable(True) 197 #----------------------------------------------------------------
198 - def _on_start_lost_focus(self):
199 if not self._DPRW_date.is_valid_timestamp(): 200 return 201 end = self._DPRW_end.GetData() 202 if end is None: 203 return 204 end = end.get_pydt() 205 start = self._DPRW_date.GetData().get_pydt() 206 if start < end: 207 return 208 self._DPRW_date.display_as_valid(False)
209 #----------------------------------------------------------------
210 - def _on_end_lost_focus(self):
211 end = self._DPRW_end.GetData() 212 if end is None: 213 self._CHBOX_ongoing.Enable(True) 214 self._DPRW_end.display_as_valid(True) 215 else: 216 self._CHBOX_ongoing.Enable(False) 217 end = end.get_pydt() 218 now = gmDateTime.pydt_now_here() 219 if end > now: 220 self._CHBOX_ongoing.SetValue(True) 221 else: 222 self._CHBOX_ongoing.SetValue(False) 223 start = self._DPRW_date.GetData() 224 if start is None: 225 self._DPRW_end.display_as_valid(True) 226 else: 227 start = start.get_pydt() 228 if end > start: 229 self._DPRW_end.display_as_valid(True) 230 else: 231 self._DPRW_end.display_as_valid(False)
232 #---------------------------------------------------------------- 233 # generic Edit Area mixin API 234 #----------------------------------------------------------------
235 - def _valid_for_save(self):
236 237 has_errors = False 238 239 if not self._DPRW_date.is_valid_timestamp(): 240 self._DPRW_date.display_as_valid(False) 241 has_errors = True 242 else: 243 self._DPRW_date.display_as_valid(True) 244 245 end = self._DPRW_end.GetData() 246 self._DPRW_end.display_as_valid(True) 247 if end is not None: 248 end = end.get_pydt() 249 start = self._DPRW_end.GetData() 250 if start is not None: 251 start = start.get_pydt() 252 if end < start: 253 has_errors = True 254 self._DPRW_end.display_as_valid(False) 255 if self._CHBOX_ongoing.IsChecked(): 256 now = gmDateTime.pydt_now_here() 257 if end < now: 258 has_errors = True 259 self._DPRW_end.display_as_valid(False) 260 261 if self._PRW_hospital_stay.GetData() is None: 262 if self._PRW_episode.GetData() is None: 263 self._PRW_episode.display_as_valid(False) 264 has_errors = True 265 else: 266 self._PRW_episode.display_as_valid(True) 267 else: 268 self._PRW_episode.display_as_valid(True) 269 270 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''): 271 self._PRW_procedure.display_as_valid(False) 272 has_errors = True 273 else: 274 self._PRW_procedure.display_as_valid(True) 275 276 invalid_location = ( 277 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'') 278 or 279 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'') 280 ) 281 if invalid_location: 282 self._PRW_hospital_stay.display_as_valid(False) 283 self._PRW_location.display_as_valid(False) 284 has_errors = True 285 else: 286 self._PRW_hospital_stay.display_as_valid(True) 287 self._PRW_location.display_as_valid(True) 288 289 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save procedure.'), beep = True) 290 291 return (has_errors is False)
292 #----------------------------------------------------------------
293 - def _save_as_new(self):
294 295 pat = gmPerson.gmCurrentPatient() 296 emr = pat.get_emr() 297 298 if self._PRW_hospital_stay.GetData() is None: 299 stay = None 300 epi = self._PRW_episode.GetData() 301 loc = self._PRW_location.GetValue().strip() 302 else: 303 stay = self._PRW_hospital_stay.GetData() 304 epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode'] 305 loc = None 306 307 proc = emr.add_performed_procedure ( 308 episode = epi, 309 location = loc, 310 hospital_stay = stay, 311 procedure = self._PRW_procedure.GetValue().strip() 312 ) 313 314 proc['clin_when'] = self._DPRW_date.GetData().get_pydt() 315 if self._DPRW_end.GetData() is None: 316 proc['clin_end'] = None 317 else: 318 proc['clin_end'] = self._DPRW_end.GetData().get_pydt() 319 proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked() 320 proc.save() 321 322 proc.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 323 324 self.data = proc 325 326 return True
327 #----------------------------------------------------------------
328 - def _save_as_update(self):
329 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt() 330 331 if self._DPRW_end.GetData() is None: 332 self.data['clin_end'] = None 333 else: 334 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt() 335 336 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked() 337 338 if self._PRW_hospital_stay.GetData() is None: 339 self.data['pk_hospital_stay'] = None 340 self.data['clin_where'] = self._PRW_location.GetValue().strip() 341 self.data['pk_episode'] = self._PRW_episode.GetData() 342 else: 343 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData() 344 self.data['clin_where'] = None 345 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData()) 346 self.data['pk_episode'] = stay['pk_episode'] 347 348 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip() 349 350 self.data.save() 351 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 352 353 return True
354 #----------------------------------------------------------------
355 - def _refresh_as_new(self):
356 self._DPRW_date.SetText() 357 self._DPRW_end.SetText() 358 self._CHBOX_ongoing.SetValue(False) 359 self._CHBOX_ongoing.Enable(True) 360 self._PRW_hospital_stay.SetText() 361 self._PRW_location.SetText() 362 self._PRW_episode.SetText() 363 self._PRW_procedure.SetText() 364 self._PRW_codes.SetText() 365 366 self._PRW_procedure.SetFocus()
367 #----------------------------------------------------------------
368 - def _refresh_from_existing(self):
369 self._DPRW_date.SetData(data = self.data['clin_when']) 370 if self.data['clin_end'] is None: 371 self._DPRW_end.SetText() 372 self._CHBOX_ongoing.Enable(True) 373 self._CHBOX_ongoing.SetValue(self.data['is_ongoing']) 374 else: 375 self._DPRW_end.SetData(data = self.data['clin_end']) 376 self._CHBOX_ongoing.Enable(False) 377 now = gmDateTime.pydt_now_here() 378 if self.data['clin_end'] > now: 379 self._CHBOX_ongoing.SetValue(True) 380 else: 381 self._CHBOX_ongoing.SetValue(False) 382 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) 383 self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure']) 384 385 if self.data['pk_hospital_stay'] is None: 386 self._PRW_hospital_stay.SetText() 387 self._LBL_hospital_details.SetLabel(u'') 388 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where']) 389 else: 390 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay']) 391 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format()) 392 self._PRW_location.SetText() 393 394 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes) 395 self._PRW_codes.SetText(val, data) 396 397 self._PRW_procedure.SetFocus()
398 #----------------------------------------------------------------
400 self._refresh_as_new() 401 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) 402 if self.data['pk_hospital_stay'] is None: 403 self._PRW_hospital_stay.SetText() 404 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where']) 405 else: 406 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay']) 407 self._PRW_location.SetText() 408 409 self._PRW_procedure.SetFocus()
410 #---------------------------------------------------------------- 411 # event handlers 412 #----------------------------------------------------------------
414 # FIXME: this would benefit from setting the created stay 415 edit_hospital_stay(parent = self.GetParent()) 416 evt.Skip()
417 #----------------------------------------------------------------
418 - def _on_ongoing_checkbox_checked(self, event):
419 if self._CHBOX_ongoing.IsChecked(): 420 end = self._DPRW_end.GetData() 421 if end is None: 422 self._DPRW_end.display_as_valid(True) 423 else: 424 end = end.get_pydt() 425 now = gmDateTime.pydt_now_here() 426 if end > now: 427 self._DPRW_end.display_as_valid(True) 428 else: 429 self._DPRW_end.display_as_valid(False) 430 else: 431 self._DPRW_end.is_valid_timestamp() 432 event.Skip()
433 #================================================================ 434 # hospitalizations related widgets/functions 435 #----------------------------------------------------------------
436 -def manage_hospital_stays(parent=None):
437 438 pat = gmPerson.gmCurrentPatient() 439 emr = pat.get_emr() 440 441 if parent is None: 442 parent = wx.GetApp().GetTopWindow() 443 #----------------------------------------- 444 def edit(stay=None): 445 return edit_hospital_stay(parent = parent, hospital_stay = stay)
446 #----------------------------------------- 447 def delete(stay=None): 448 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']): 449 return True 450 gmDispatcher.send ( 451 signal = u'statustext', 452 msg = _('Cannot delete hospitalization.'), 453 beep = True 454 ) 455 return False 456 #----------------------------------------- 457 def refresh(lctrl): 458 stays = emr.get_hospital_stays() 459 items = [ 460 [ 461 s['admission'].strftime('%Y-%m-%d'), 462 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')), 463 s['episode'], 464 gmTools.coalesce(s['hospital'], u'') 465 ] for s in stays 466 ] 467 lctrl.set_string_items(items = items) 468 lctrl.set_data(data = stays) 469 #----------------------------------------- 470 gmListWidgets.get_choices_from_list ( 471 parent = parent, 472 msg = _("The patient's hospitalizations:\n"), 473 caption = _('Editing hospitalizations ...'), 474 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')], 475 single_selection = True, 476 edit_callback = edit, 477 new_callback = edit, 478 delete_callback = delete, 479 refresh_callback = refresh 480 ) 481 482 #----------------------------------------------------------------
483 -def edit_hospital_stay(parent=None, hospital_stay=None):
484 ea = cHospitalStayEditAreaPnl(parent = parent, id = -1) 485 ea.data = hospital_stay 486 ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit') 487 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 488 dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospitalization'), _('Editing a hospitalization'))) 489 if dlg.ShowModal() == wx.ID_OK: 490 dlg.Destroy() 491 return True 492 dlg.Destroy() 493 return False
494 #----------------------------------------------------------------
495 -class cHospitalStayPhraseWheel(gmPhraseWheel.cPhraseWheel):
496 """Phrasewheel to allow selection of a hospitalization."""
497 - def __init__(self, *args, **kwargs):
498 499 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 500 501 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}} 502 503 mp = gmMatchProvider.cMatchProvider_SQL2 ( 504 queries = [ 505 u""" 506 select 507 pk_hospital_stay, 508 descr 509 from ( 510 select distinct on (pk_hospital_stay) 511 pk_hospital_stay, 512 descr 513 from 514 (select 515 pk_hospital_stay, 516 ( 517 to_char(admission, 'YYYY-Mon-DD') 518 || coalesce((' (' || hospital || '):'), ': ') 519 || episode 520 || coalesce((' (' || health_issue || ')'), '') 521 ) as descr 522 from 523 clin.v_pat_hospital_stays 524 where 525 %(ctxt_pat)s 526 527 hospital %(fragment_condition)s 528 or 529 episode %(fragment_condition)s 530 or 531 health_issue %(fragment_condition)s 532 ) as the_stays 533 ) as distinct_stays 534 order by descr 535 limit 25 536 """ ], 537 context = ctxt 538 ) 539 mp.setThresholds(3, 4, 6) 540 mp.set_context('pat', gmPerson.gmCurrentPatient().ID) 541 542 self.matcher = mp 543 self.selection_only = True
544 #---------------------------------------------------------------- 545 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl 546
547 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
548
549 - def __init__(self, *args, **kwargs):
552 #---------------------------------------------------------------- 553 # generic Edit Area mixin API 554 #----------------------------------------------------------------
555 - def _valid_for_save(self):
556 557 valid = True 558 559 if not self._PRW_admission.is_valid_timestamp(allow_empty = False): 560 valid = False 561 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True) 562 563 if self._PRW_discharge.is_valid_timestamp(allow_empty = True): 564 if self._PRW_discharge.date is not None: 565 if not self._PRW_discharge.date > self._PRW_admission.date: 566 valid = False 567 self._PRW_discharge.display_as_valid(False) 568 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True) 569 570 if self._PRW_episode.GetValue().strip() == u'': 571 valid = False 572 self._PRW_episode.display_as_valid(False) 573 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True) 574 575 return (valid is True)
576 #----------------------------------------------------------------
577 - def _save_as_new(self):
578 579 pat = gmPerson.gmCurrentPatient() 580 emr = pat.get_emr() 581 stay = emr.add_hospital_stay(episode = self._PRW_episode.GetData(can_create = True)) 582 stay['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'') 583 stay['admission'] = self._PRW_admission.GetData() 584 stay['discharge'] = self._PRW_discharge.GetData() 585 stay.save_payload() 586 587 self.data = stay 588 return True
589 #----------------------------------------------------------------
590 - def _save_as_update(self):
591 592 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True) 593 self.data['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'') 594 self.data['admission'] = self._PRW_admission.GetData() 595 self.data['discharge'] = self._PRW_discharge.GetData() 596 self.data.save_payload() 597 598 return True
599 #----------------------------------------------------------------
600 - def _refresh_as_new(self):
601 self._PRW_hospital.SetText(value = u'') 602 self._PRW_episode.SetText(value = u'') 603 self._PRW_admission.SetText(data = gmDateTime.pydt_now_here()) 604 self._PRW_discharge.SetText()
605 #----------------------------------------------------------------
606 - def _refresh_from_existing(self):
607 if self.data['hospital'] is not None: 608 self._PRW_hospital.SetText(value = self.data['hospital']) 609 610 if self.data['pk_episode'] is not None: 611 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) 612 613 self._PRW_admission.SetText(data = self.data['admission']) 614 self._PRW_discharge.SetText(data = self.data['discharge'])
615 #----------------------------------------------------------------
617 print "this was not expected to be used in this edit area"
618 #================================================================ 619 # encounter related widgets/functions 620 #----------------------------------------------------------------
621 -def start_new_encounter(emr=None):
622 emr.start_new_encounter() 623 gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True) 624 time.sleep(0.5) 625 gmGuiHelpers.gm_show_info ( 626 _('\nA new encounter was started for the active patient.\n'), 627 _('Start of new encounter') 628 )
629 #---------------------------------------------------------------- 630 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg 631
632 -def edit_encounter(parent=None, encounter=None):
633 if parent is None: 634 parent = wx.GetApp().GetTopWindow() 635 636 # FIXME: use generic dialog 2 637 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter) 638 if dlg.ShowModal() == wx.ID_OK: 639 dlg.Destroy() 640 return True 641 dlg.Destroy() 642 return False
643 #----------------------------------------------------------------
644 -def manage_encounters(**kwargs):
645 return select_encounters(**kwargs)
646
647 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
648 649 if patient is None: 650 patient = gmPerson.gmCurrentPatient() 651 652 if not patient.connected: 653 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.')) 654 return False 655 656 if parent is None: 657 parent = wx.GetApp().GetTopWindow() 658 659 emr = patient.get_emr() 660 661 #-------------------- 662 def refresh(lctrl): 663 if encounters is None: 664 encs = emr.get_encounters() 665 else: 666 encs = encounters 667 668 items = [ 669 [ 670 e['started'].strftime('%x %H:%M'), 671 e['last_affirmed'].strftime('%H:%M'), 672 e['l10n_type'], 673 gmTools.coalesce(e['reason_for_encounter'], u''), 674 gmTools.coalesce(e['assessment_of_encounter'], u''), 675 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin), 676 e['pk_encounter'] 677 ] for e in encs 678 ] 679 lctrl.set_string_items(items = items) 680 lctrl.set_data(data = encs) 681 active_pk = emr.active_encounter['pk_encounter'] 682 for idx in range(len(encs)): 683 e = encs[idx] 684 if e['pk_encounter'] == active_pk: 685 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
686 #-------------------- 687 def new(): 688 cfg_db = gmCfg.cCfgSQL() 689 # FIXME: look for MRU/MCU encounter type config here 690 enc_type = cfg_db.get2 ( 691 option = u'encounter.default_type', 692 workplace = gmSurgery.gmCurrentPractice().active_workplace, 693 bias = u'user', 694 default = u'in surgery' 695 ) 696 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type) 697 return edit_encounter(parent = parent, encounter = enc) 698 #-------------------- 699 def edit(enc=None): 700 return edit_encounter(parent = parent, encounter = enc) 701 #-------------------- 702 def edit_active(enc=None): 703 return edit_encounter(parent = parent, encounter = emr.active_encounter) 704 #-------------------- 705 def start_new(enc=None): 706 start_new_encounter(emr = emr) 707 return True 708 #-------------------- 709 return gmListWidgets.get_choices_from_list ( 710 parent = parent, 711 msg = _("The patient's encounters.\n"), 712 caption = _('Encounters ...'), 713 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'], 714 can_return_empty = False, 715 single_selection = single_selection, 716 refresh_callback = refresh, 717 edit_callback = edit, 718 new_callback = new, 719 ignore_OK_button = ignore_OK_button, 720 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active), 721 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new) 722 ) 723 #----------------------------------------------------------------
724 -def ask_for_encounter_continuation(msg=None, caption=None, encounter=None, parent=None):
725 """This is used as the callback when the EMR detects that the 726 patient was here rather recently and wants to ask the 727 provider whether to continue the recent encounter. 728 """ 729 if parent is None: 730 parent = wx.GetApp().GetTopWindow() 731 732 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 733 parent = None, 734 id = -1, 735 caption = caption, 736 question = msg, 737 button_defs = [ 738 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False}, 739 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True} 740 ], 741 show_checkbox = False 742 ) 743 744 result = dlg.ShowModal() 745 dlg.Destroy() 746 747 if result == wx.ID_YES: 748 return True 749 750 return False
751 #----------------------------------------------------------------
752 -def manage_encounter_types(parent=None):
753 754 if parent is None: 755 parent = wx.GetApp().GetTopWindow() 756 757 #-------------------- 758 def edit(enc_type=None): 759 return edit_encounter_type(parent = parent, encounter_type = enc_type)
760 #-------------------- 761 def delete(enc_type=None): 762 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']): 763 return True 764 gmDispatcher.send ( 765 signal = u'statustext', 766 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'], 767 beep = True 768 ) 769 return False 770 #-------------------- 771 def refresh(lctrl): 772 enc_types = gmEMRStructItems.get_encounter_types() 773 lctrl.set_string_items(items = enc_types) 774 #-------------------- 775 gmListWidgets.get_choices_from_list ( 776 parent = parent, 777 msg = _('\nSelect the encounter type you want to edit !\n'), 778 caption = _('Managing encounter types ...'), 779 columns = [_('Local name'), _('Encounter type')], 780 single_selection = True, 781 edit_callback = edit, 782 new_callback = edit, 783 delete_callback = delete, 784 refresh_callback = refresh 785 ) 786 #----------------------------------------------------------------
787 -def edit_encounter_type(parent=None, encounter_type=None):
788 ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1) 789 ea.data = encounter_type 790 ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit') 791 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea) 792 dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name'))) 793 if dlg.ShowModal() == wx.ID_OK: 794 return True 795 return False
796 #----------------------------------------------------------------
797 -class cEncounterPhraseWheel(gmPhraseWheel.cPhraseWheel):
798
799 - def __init__(self, *args, **kwargs):
800 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 801 802 cmd = u""" 803 SELECT -- DISTINCT ON (data) 804 pk_encounter 805 AS data, 806 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type 807 AS list_label, 808 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type 809 AS field_label 810 FROM 811 clin.v_pat_encounters 812 WHERE 813 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s 814 OR 815 l10n_type %(fragment_condition)s 816 OR 817 type %(fragment_condition)s 818 %(ctxt_patient)s 819 ORDER BY 820 list_label 821 LIMIT 822 30 823 """ 824 context = {'ctxt_patient': { 825 'where_part': u'AND pk_patient = %(patient)s', 826 'placeholder': u'patient' 827 }} 828 829 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context) 830 self.matcher._SQL_data2match = u""" 831 SELECT 832 pk_encounter 833 AS data, 834 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type 835 AS list_label, 836 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type 837 AS field_label 838 FROM 839 clin.v_pat_encounters 840 WHERE 841 pk_encounter = %(pk)s 842 """ 843 self.matcher.setThresholds(1, 3, 5) 844 self.selection_only = True 845 # outside code MUST bind this to a patient 846 self.set_context(context = 'patient', val = None)
847 #--------------------------------------------------------
848 - def set_from_instance(self, instance):
849 val = u'%s: %s' % ( 850 gmDateTime.pydt_strftime(instance['started'], '%Y %b %d'), 851 instance['l10n_type'] 852 ) 853 self.SetText(value = val, data = instance['pk_encounter'])
854 #------------------------------------------------------------
855 - def _get_data_tooltip(self):
856 if self.GetData() is None: 857 return None 858 enc = gmEMRStructItems.cEncounter(aPK_obj = self._data.values()[0]['data']) 859 return enc.format ( 860 with_docs = False, 861 with_tests = False, 862 with_vaccinations = False, 863 with_family_history = False 864 )
865 #----------------------------------------------------------------
866 -class cEncounterTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
867 """Phrasewheel to allow selection of encounter type. 868 869 - user input interpreted as encounter type in English or local language 870 - data returned is pk of corresponding encounter type or None 871 """
872 - def __init__(self, *args, **kwargs):
873 874 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 875 876 mp = gmMatchProvider.cMatchProvider_SQL2 ( 877 queries = [ 878 u""" 879 SELECT 880 data, 881 field_label, 882 list_label 883 FROM ( 884 SELECT DISTINCT ON (data) * 885 FROM ( 886 SELECT 887 pk AS data, 888 _(description) AS field_label, 889 case 890 when _(description) = description then _(description) 891 else _(description) || ' (' || description || ')' 892 end AS list_label 893 FROM 894 clin.encounter_type 895 WHERE 896 _(description) %(fragment_condition)s 897 OR 898 description %(fragment_condition)s 899 ) AS q_distinct_pk 900 ) AS q_ordered 901 ORDER BY 902 list_label 903 """ ] 904 ) 905 mp.setThresholds(2, 4, 6) 906 907 self.matcher = mp 908 self.selection_only = True 909 self.picklist_delay = 50
910 #----------------------------------------------------------------
911 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
912
913 - def __init__(self, *args, **kwargs):
917 918 # self.__register_interests() 919 #------------------------------------------------------- 920 # generic edit area API 921 #-------------------------------------------------------
922 - def _valid_for_save(self):
923 if self.mode == 'edit': 924 if self._TCTRL_l10n_name.GetValue().strip() == u'': 925 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False) 926 return False 927 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) 928 return True 929 930 no_errors = True 931 932 if self._TCTRL_l10n_name.GetValue().strip() == u'': 933 if self._TCTRL_name.GetValue().strip() == u'': 934 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False) 935 no_errors = False 936 else: 937 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) 938 else: 939 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) 940 941 if self._TCTRL_name.GetValue().strip() == u'': 942 if self._TCTRL_l10n_name.GetValue().strip() == u'': 943 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False) 944 no_errors = False 945 else: 946 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True) 947 else: 948 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True) 949 950 return no_errors
951 #-------------------------------------------------------
952 - def _save_as_new(self):
953 enc_type = gmEMRStructItems.create_encounter_type ( 954 description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''), 955 l10n_description = gmTools.coalesce ( 956 gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''), 957 self._TCTRL_name.GetValue().strip() 958 ) 959 ) 960 if enc_type is None: 961 return False 962 self.data = enc_type 963 return True
964 #-------------------------------------------------------
965 - def _save_as_update(self):
966 enc_type = gmEMRStructItems.update_encounter_type ( 967 description = self._TCTRL_name.GetValue().strip(), 968 l10n_description = self._TCTRL_l10n_name.GetValue().strip() 969 ) 970 if enc_type is None: 971 return False 972 self.data = enc_type 973 return True
974 #-------------------------------------------------------
975 - def _refresh_as_new(self):
976 self._TCTRL_l10n_name.SetValue(u'') 977 self._TCTRL_name.SetValue(u'') 978 self._TCTRL_name.Enable(True)
979 #-------------------------------------------------------
980 - def _refresh_from_existing(self):
981 self._TCTRL_l10n_name.SetValue(self.data['l10n_description']) 982 self._TCTRL_name.SetValue(self.data['description']) 983 # disallow changing type on all encounters by editing system name 984 self._TCTRL_name.Enable(False)
985 #-------------------------------------------------------
987 self._TCTRL_l10n_name.SetValue(self.data['l10n_description']) 988 self._TCTRL_name.SetValue(self.data['description']) 989 self._TCTRL_name.Enable(True)
990 #------------------------------------------------------- 991 # internal API 992 #------------------------------------------------------- 993 # def __register_interests(self): 994 # return 995 #---------------------------------------------------------------- 996 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl 997
998 -class cEncounterEditAreaPnl(wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl):
999
1000 - def __init__(self, *args, **kwargs):
1001 try: 1002 self.__encounter = kwargs['encounter'] 1003 del kwargs['encounter'] 1004 except KeyError: 1005 self.__encounter = None 1006 1007 try: 1008 msg = kwargs['msg'] 1009 del kwargs['msg'] 1010 except KeyError: 1011 msg = None 1012 1013 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs) 1014 1015 self.refresh(msg = msg)
1016 #-------------------------------------------------------- 1017 # external API 1018 #--------------------------------------------------------
1019 - def refresh(self, encounter=None, msg=None):
1020 1021 if msg is not None: 1022 self._LBL_instructions.SetLabel(msg) 1023 1024 if encounter is not None: 1025 self.__encounter = encounter 1026 1027 if self.__encounter is None: 1028 return True 1029 1030 # getting the patient via the encounter allows us to act 1031 # on any encounter regardless of the currently active patient 1032 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient']) 1033 self._LBL_patient.SetLabel(pat.get_description_gender()) 1034 1035 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type']) 1036 1037 fts = gmDateTime.cFuzzyTimestamp ( 1038 timestamp = self.__encounter['started'], 1039 accuracy = gmDateTime.acc_minutes 1040 ) 1041 self._PRW_start.SetText(fts.format_accurately(), data=fts) 1042 1043 fts = gmDateTime.cFuzzyTimestamp ( 1044 timestamp = self.__encounter['last_affirmed'], 1045 accuracy = gmDateTime.acc_minutes 1046 ) 1047 self._PRW_end.SetText(fts.format_accurately(), data=fts) 1048 1049 # RFE 1050 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], '')) 1051 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe) 1052 self._PRW_rfe_codes.SetText(val, data) 1053 1054 # AOE 1055 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], '')) 1056 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe) 1057 self._PRW_aoe_codes.SetText(val, data) 1058 1059 # last affirmed 1060 if self.__encounter['last_affirmed'] == self.__encounter['started']: 1061 self._PRW_end.SetFocus() 1062 else: 1063 self._TCTRL_aoe.SetFocus() 1064 1065 return True
1066 #--------------------------------------------------------
1067 - def __is_valid_for_save(self):
1068 1069 if self._PRW_encounter_type.GetData() is None: 1070 self._PRW_encounter_type.SetBackgroundColour('pink') 1071 self._PRW_encounter_type.Refresh() 1072 self._PRW_encounter_type.SetFocus() 1073 return False 1074 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1075 self._PRW_encounter_type.Refresh() 1076 1077 # start 1078 if self._PRW_start.GetValue().strip() == u'': 1079 self._PRW_start.SetBackgroundColour('pink') 1080 self._PRW_start.Refresh() 1081 self._PRW_start.SetFocus() 1082 return False 1083 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False): 1084 self._PRW_start.SetBackgroundColour('pink') 1085 self._PRW_start.Refresh() 1086 self._PRW_start.SetFocus() 1087 return False 1088 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1089 self._PRW_start.Refresh() 1090 1091 # last_affirmed 1092 # if self._PRW_end.GetValue().strip() == u'': 1093 # self._PRW_end.SetBackgroundColour('pink') 1094 # self._PRW_end.Refresh() 1095 # self._PRW_end.SetFocus() 1096 # return False 1097 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False): 1098 self._PRW_end.SetBackgroundColour('pink') 1099 self._PRW_end.Refresh() 1100 self._PRW_end.SetFocus() 1101 return False 1102 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1103 self._PRW_end.Refresh() 1104 1105 return True
1106 #--------------------------------------------------------
1107 - def save(self):
1108 if not self.__is_valid_for_save(): 1109 return False 1110 1111 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData() 1112 self.__encounter['started'] = self._PRW_start.GetData().get_pydt() 1113 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt() 1114 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1115 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'') 1116 self.__encounter.save_payload() # FIXME: error checking 1117 1118 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ] 1119 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ] 1120 1121 return True
1122 #---------------------------------------------------------------- 1123 # FIXME: use generic dialog 2
1124 -class cEncounterEditAreaDlg(wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg):
1125
1126 - def __init__(self, *args, **kwargs):
1127 encounter = kwargs['encounter'] 1128 del kwargs['encounter'] 1129 1130 try: 1131 button_defs = kwargs['button_defs'] 1132 del kwargs['button_defs'] 1133 except KeyError: 1134 button_defs = None 1135 1136 try: 1137 msg = kwargs['msg'] 1138 del kwargs['msg'] 1139 except KeyError: 1140 msg = None 1141 1142 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs) 1143 self.SetSize((450, 280)) 1144 self.SetMinSize((450, 280)) 1145 1146 if button_defs is not None: 1147 self._BTN_save.SetLabel(button_defs[0][0]) 1148 self._BTN_save.SetToolTipString(button_defs[0][1]) 1149 self._BTN_close.SetLabel(button_defs[1][0]) 1150 self._BTN_close.SetToolTipString(button_defs[1][1]) 1151 self.Refresh() 1152 1153 self._PNL_edit_area.refresh(encounter = encounter, msg = msg) 1154 1155 self.Fit()
1156 #--------------------------------------------------------
1157 - def _on_save_button_pressed(self, evt):
1158 if self._PNL_edit_area.save(): 1159 if self.IsModal(): 1160 self.EndModal(wx.ID_OK) 1161 else: 1162 self.Close()
1163 #---------------------------------------------------------------- 1164 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl 1165
1166 -class cActiveEncounterPnl(wxgActiveEncounterPnl.wxgActiveEncounterPnl):
1167
1168 - def __init__(self, *args, **kwargs):
1169 wxgActiveEncounterPnl.wxgActiveEncounterPnl.__init__(self, *args, **kwargs) 1170 self.__register_events() 1171 self.refresh()
1172 #------------------------------------------------------------
1173 - def clear(self):
1174 self._TCTRL_encounter.SetValue(u'') 1175 self._TCTRL_encounter.SetToolTipString(u'') 1176 self._BTN_new.Enable(False) 1177 self._BTN_list.Enable(False)
1178 #------------------------------------------------------------
1179 - def refresh(self):
1180 pat = gmPerson.gmCurrentPatient() 1181 if not pat.connected: 1182 self.clear() 1183 return 1184 1185 enc = pat.get_emr().active_encounter 1186 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n')) 1187 self._TCTRL_encounter.SetToolTipString ( 1188 _('The active encounter of the current patient:\n\n%s') % 1189 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n') 1190 ) 1191 self._BTN_new.Enable(True) 1192 self._BTN_list.Enable(True)
1193 #------------------------------------------------------------
1194 - def __register_events(self):
1195 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick) 1196 1197 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear) 1198 # this would throw an exception due to concurrency issues: 1199 #gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_refresh) 1200 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh) 1201 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh) 1202 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1203 #------------------------------------------------------------ 1204 # event handler 1205 #------------------------------------------------------------
1206 - def _schedule_clear(self):
1207 wx.CallAfter(self.clear)
1208 #------------------------------------------------------------
1209 - def _schedule_refresh(self, *args, **kwargs):
1210 wx.CallAfter(self.refresh) 1211 return True
1212 #------------------------------------------------------------
1213 - def _on_ldclick(self, event):
1214 pat = gmPerson.gmCurrentPatient() 1215 edit_encounter(encounter = pat.get_emr().active_encounter)
1216 #------------------------------------------------------------
1217 - def _on_new_button_pressed(self, event):
1220 #------------------------------------------------------------
1221 - def _on_list_button_pressed(self, event):
1223 #================================================================ 1224 # episode related widgets/functions 1225 #----------------------------------------------------------------
1226 -def edit_episode(parent=None, episode=None):
1227 ea = cEpisodeEditAreaPnl(parent = parent, id = -1) 1228 ea.data = episode 1229 ea.mode = gmTools.coalesce(episode, 'new', 'edit') 1230 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 1231 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode'))) 1232 if dlg.ShowModal() == wx.ID_OK: 1233 return True 1234 return False
1235 #----------------------------------------------------------------
1236 -def promote_episode_to_issue(parent=None, episode=None, emr=None):
1237 1238 created_new_issue = False 1239 1240 try: 1241 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient']) 1242 except gmExceptions.NoSuchBusinessObjectError: 1243 issue = None 1244 1245 if issue is None: 1246 issue = emr.add_health_issue(issue_name = episode['description']) 1247 created_new_issue = True 1248 else: 1249 # issue exists already, so ask user 1250 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 1251 parent, 1252 -1, 1253 caption = _('Promoting episode to health issue'), 1254 question = _( 1255 'There already is a health issue\n' 1256 '\n' 1257 ' %s\n' 1258 '\n' 1259 'What do you want to do ?' 1260 ) % issue['description'], 1261 button_defs = [ 1262 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False}, 1263 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True} 1264 ] 1265 ) 1266 use_existing = dlg.ShowModal() 1267 dlg.Destroy() 1268 1269 if use_existing == wx.ID_CANCEL: 1270 return 1271 1272 # user wants to create new issue with alternate name 1273 if use_existing == wx.ID_NO: 1274 # loop until name modified but non-empty or cancelled 1275 issue_name = episode['description'] 1276 while issue_name == episode['description']: 1277 dlg = wx.TextEntryDialog ( 1278 parent = parent, 1279 message = _('Enter a short descriptive name for the new health issue:'), 1280 caption = _('Creating a new health issue ...'), 1281 defaultValue = issue_name, 1282 style = wx.OK | wx.CANCEL | wx.CENTRE 1283 ) 1284 decision = dlg.ShowModal() 1285 if decision != wx.ID_OK: 1286 dlg.Destroy() 1287 return 1288 issue_name = dlg.GetValue().strip() 1289 dlg.Destroy() 1290 if issue_name == u'': 1291 issue_name = episode['description'] 1292 1293 issue = emr.add_health_issue(issue_name = issue_name) 1294 created_new_issue = True 1295 1296 # eventually move the episode to the issue 1297 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True): 1298 # user cancelled the move so delete just-created issue 1299 if created_new_issue: 1300 # shouldn't fail as it is completely new 1301 gmEMRStructItems.delete_health_issue(health_issue = issue) 1302 return 1303 1304 return
1305 #----------------------------------------------------------------
1306 -def move_episode_to_issue(episode=None, target_issue=None, save_to_backend=False):
1307 """Prepare changing health issue for an episode. 1308 1309 Checks for two-open-episodes conflict. When this 1310 function succeeds, the pk_health_issue has been set 1311 on the episode instance and the episode should - for 1312 all practical purposes - be ready for save_payload(). 1313 """ 1314 # episode is closed: should always work 1315 if not episode['episode_open']: 1316 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1317 if save_to_backend: 1318 episode.save_payload() 1319 return True 1320 1321 # un-associate: should always work, too 1322 if target_issue is None: 1323 episode['pk_health_issue'] = None 1324 if save_to_backend: 1325 episode.save_payload() 1326 return True 1327 1328 # try closing possibly expired episode on target issue if any 1329 db_cfg = gmCfg.cCfgSQL() 1330 epi_ttl = int(db_cfg.get2 ( 1331 option = u'episode.ttl', 1332 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1333 bias = 'user', 1334 default = 60 # 2 months 1335 )) 1336 if target_issue.close_expired_episode(ttl=epi_ttl) is True: 1337 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description'])) 1338 existing_epi = target_issue.get_open_episode() 1339 1340 # no more open episode on target issue: should work now 1341 if existing_epi is None: 1342 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1343 if save_to_backend: 1344 episode.save_payload() 1345 return True 1346 1347 # don't conflict on SELF ;-) 1348 if existing_epi['pk_episode'] == episode['pk_episode']: 1349 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1350 if save_to_backend: 1351 episode.save_payload() 1352 return True 1353 1354 # we got two open episodes at once, ask user 1355 move_range = episode.get_access_range() 1356 exist_range = existing_epi.get_access_range() 1357 question = _( 1358 'You want to associate the running episode:\n\n' 1359 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n' 1360 'with the health issue:\n\n' 1361 ' "%(issue_name)s"\n\n' 1362 'There already is another episode running\n' 1363 'for this health issue:\n\n' 1364 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n' 1365 'However, there can only be one running\n' 1366 'episode per health issue.\n\n' 1367 'Which episode do you want to close ?' 1368 ) % { 1369 'new_epi_name': episode['description'], 1370 'new_epi_start': move_range[0].strftime('%m/%y'), 1371 'new_epi_end': move_range[1].strftime('%m/%y'), 1372 'issue_name': target_issue['description'], 1373 'old_epi_name': existing_epi['description'], 1374 'old_epi_start': exist_range[0].strftime('%m/%y'), 1375 'old_epi_end': exist_range[1].strftime('%m/%y') 1376 } 1377 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 1378 parent = None, 1379 id = -1, 1380 caption = _('Resolving two-running-episodes conflict'), 1381 question = question, 1382 button_defs = [ 1383 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']}, 1384 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']} 1385 ] 1386 ) 1387 decision = dlg.ShowModal() 1388 1389 if decision == wx.ID_CANCEL: 1390 # button 3: move cancelled by user 1391 return False 1392 1393 elif decision == wx.ID_YES: 1394 # button 1: close old episode 1395 existing_epi['episode_open'] = False 1396 existing_epi.save_payload() 1397 1398 elif decision == wx.ID_NO: 1399 # button 2: close new episode 1400 episode['episode_open'] = False 1401 1402 else: 1403 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision) 1404 1405 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1406 if save_to_backend: 1407 episode.save_payload() 1408 return True
1409 #----------------------------------------------------------------
1410 -class cEpisodeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
1411 1412 # FIXME: support pre-selection 1413
1414 - def __init__(self, *args, **kwargs):
1415 1416 episodes = kwargs['episodes'] 1417 del kwargs['episodes'] 1418 1419 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 1420 1421 self.SetTitle(_('Select the episodes you are interested in ...')) 1422 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')]) 1423 self._LCTRL_items.set_string_items ( 1424 items = [ 1425 [ epi['description'], 1426 gmTools.bool2str(epi['episode_open'], _('ongoing'), u''), 1427 gmTools.coalesce(epi['health_issue'], u'') 1428 ] 1429 for epi in episodes ] 1430 ) 1431 self._LCTRL_items.set_column_widths() 1432 self._LCTRL_items.set_data(data = episodes)
1433 #----------------------------------------------------------------
1434 -class cEpisodeDescriptionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1435 """Let user select an episode *description*. 1436 1437 The user can select an episode description from the previously 1438 used descriptions across all episodes across all patients. 1439 1440 Selection is done with a phrasewheel so the user can 1441 type the episode name and matches will be shown. Typing 1442 "*" will show the entire list of episodes. 1443 1444 If the user types a description not existing yet a 1445 new episode description will be returned. 1446 """
1447 - def __init__(self, *args, **kwargs):
1448 1449 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1450 queries = [ 1451 u""" 1452 SELECT DISTINCT ON (description) 1453 description 1454 AS data, 1455 description 1456 AS field_label, 1457 description || ' (' 1458 || CASE 1459 WHEN is_open IS TRUE THEN _('ongoing') 1460 ELSE _('closed') 1461 END 1462 || ')' 1463 AS list_label 1464 FROM 1465 clin.episode 1466 WHERE 1467 description %(fragment_condition)s 1468 ORDER BY description 1469 LIMIT 30 1470 """ 1471 ] 1472 ) 1473 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1474 self.matcher = mp
1475 #----------------------------------------------------------------
1476 -class cEpisodeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1477 """Let user select an episode. 1478 1479 The user can select an episode from the existing episodes of a 1480 patient. Selection is done with a phrasewheel so the user 1481 can type the episode name and matches will be shown. Typing 1482 "*" will show the entire list of episodes. Closed episodes 1483 will be marked as such. If the user types an episode name not 1484 in the list of existing episodes a new episode can be created 1485 from it if the programmer activated that feature. 1486 1487 If keyword <patient_id> is set to None or left out the control 1488 will listen to patient change signals and therefore act on 1489 gmPerson.gmCurrentPatient() changes. 1490 """
1491 - def __init__(self, *args, **kwargs):
1492 1493 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}} 1494 1495 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1496 queries = [ 1497 u"""( 1498 1499 select 1500 pk_episode 1501 as data, 1502 description 1503 as field_label, 1504 coalesce ( 1505 description || ' - ' || health_issue, 1506 description 1507 ) as list_label, 1508 1 as rank 1509 from 1510 clin.v_pat_episodes 1511 where 1512 episode_open is true and 1513 description %(fragment_condition)s 1514 %(ctxt_pat)s 1515 1516 ) union all ( 1517 1518 select 1519 pk_episode 1520 as data, 1521 description 1522 as field_label, 1523 coalesce ( 1524 description || _(' (closed)') || ' - ' || health_issue, 1525 description || _(' (closed)') 1526 ) as list_label, 1527 2 as rank 1528 from 1529 clin.v_pat_episodes 1530 where 1531 description %(fragment_condition)s and 1532 episode_open is false 1533 %(ctxt_pat)s 1534 1535 ) 1536 1537 order by rank, list_label 1538 limit 30""" 1539 ], 1540 context = ctxt 1541 ) 1542 1543 try: 1544 kwargs['patient_id'] 1545 except KeyError: 1546 kwargs['patient_id'] = None 1547 1548 if kwargs['patient_id'] is None: 1549 self.use_current_patient = True 1550 self.__register_patient_change_signals() 1551 pat = gmPerson.gmCurrentPatient() 1552 if pat.connected: 1553 mp.set_context('pat', pat.ID) 1554 else: 1555 self.use_current_patient = False 1556 self.__patient_id = int(kwargs['patient_id']) 1557 mp.set_context('pat', self.__patient_id) 1558 1559 del kwargs['patient_id'] 1560 1561 gmPhraseWheel.cPhraseWheel.__init__ ( 1562 self, 1563 *args, 1564 **kwargs 1565 ) 1566 self.matcher = mp
1567 #-------------------------------------------------------- 1568 # external API 1569 #--------------------------------------------------------
1570 - def set_patient(self, patient_id=None):
1571 if self.use_current_patient: 1572 return False 1573 self.__patient_id = int(patient_id) 1574 self.set_context('pat', self.__patient_id) 1575 return True
1576 #--------------------------------------------------------
1577 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1578 self.__is_open_for_create_data = is_open # used (only) in _create_data() 1579 return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
1580 #--------------------------------------------------------
1581 - def _create_data(self):
1582 1583 epi_name = self.GetValue().strip() 1584 if epi_name == u'': 1585 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True) 1586 _log.debug('cannot create episode without name') 1587 return 1588 1589 if self.use_current_patient: 1590 pat = gmPerson.gmCurrentPatient() 1591 else: 1592 pat = gmPerson.cPatient(aPK_obj = self.__patient_id) 1593 1594 emr = pat.get_emr() 1595 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data) 1596 if epi is None: 1597 self.data = {} 1598 else: 1599 self.SetText ( 1600 value = epi_name, 1601 data = epi['pk_episode'] 1602 )
1603 #--------------------------------------------------------
1604 - def _data2instance(self):
1605 return gmEMRStructItems.cEpisode(aPK_obj = self.GetData())
1606 #-------------------------------------------------------- 1607 # internal API 1608 #--------------------------------------------------------
1610 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection') 1611 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1612 #--------------------------------------------------------
1613 - def _pre_patient_selection(self):
1614 return True
1615 #--------------------------------------------------------
1616 - def _post_patient_selection(self):
1617 if self.use_current_patient: 1618 patient = gmPerson.gmCurrentPatient() 1619 self.set_context('pat', patient.ID) 1620 return True
1621 #---------------------------------------------------------------- 1622 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl 1623
1624 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1625
1626 - def __init__(self, *args, **kwargs):
1627 1628 try: 1629 episode = kwargs['episode'] 1630 del kwargs['episode'] 1631 except KeyError: 1632 episode = None 1633 1634 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs) 1635 gmEditArea.cGenericEditAreaMixin.__init__(self) 1636 1637 self.data = episode
1638 #---------------------------------------------------------------- 1639 # generic Edit Area mixin API 1640 #----------------------------------------------------------------
1641 - def _valid_for_save(self):
1642 1643 errors = False 1644 1645 if len(self._PRW_description.GetValue().strip()) == 0: 1646 errors = True 1647 self._PRW_description.display_as_valid(False) 1648 self._PRW_description.SetFocus() 1649 else: 1650 self._PRW_description.display_as_valid(True) 1651 self._PRW_description.Refresh() 1652 1653 return not errors
1654 #----------------------------------------------------------------
1655 - def _save_as_new(self):
1656 1657 pat = gmPerson.gmCurrentPatient() 1658 emr = pat.get_emr() 1659 1660 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip()) 1661 epi['summary'] = self._TCTRL_status.GetValue().strip() 1662 epi['episode_open'] = not self._CHBOX_closed.IsChecked() 1663 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData() 1664 1665 issue_name = self._PRW_issue.GetValue().strip() 1666 if len(issue_name) != 0: 1667 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True) 1668 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue']) 1669 1670 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False): 1671 gmDispatcher.send ( 1672 signal = 'statustext', 1673 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % ( 1674 epi['description'], 1675 issue['description'] 1676 ) 1677 ) 1678 gmEMRStructItems.delete_episode(episode = epi) 1679 return False 1680 1681 epi.save() 1682 1683 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 1684 1685 self.data = epi 1686 return True
1687 #----------------------------------------------------------------
1688 - def _save_as_update(self):
1689 1690 self.data['description'] = self._PRW_description.GetValue().strip() 1691 self.data['summary'] = self._TCTRL_status.GetValue().strip() 1692 self.data['episode_open'] = not self._CHBOX_closed.IsChecked() 1693 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData() 1694 1695 issue_name = self._PRW_issue.GetValue().strip() 1696 if len(issue_name) == 0: 1697 self.data['pk_health_issue'] = None 1698 else: 1699 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True) 1700 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue']) 1701 1702 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False): 1703 gmDispatcher.send ( 1704 signal = 'statustext', 1705 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % ( 1706 self.data['description'], 1707 issue['description'] 1708 ) 1709 ) 1710 return False 1711 1712 self.data.save() 1713 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 1714 1715 return True
1716 #----------------------------------------------------------------
1717 - def _refresh_as_new(self):
1718 if self.data is None: 1719 ident = gmPerson.gmCurrentPatient() 1720 else: 1721 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient']) 1722 self._TCTRL_patient.SetValue(ident.get_description_gender()) 1723 self._PRW_issue.SetText() 1724 self._PRW_description.SetText() 1725 self._TCTRL_status.SetValue(u'') 1726 self._PRW_certainty.SetText() 1727 self._CHBOX_closed.SetValue(False) 1728 self._PRW_codes.SetText()
1729 #----------------------------------------------------------------
1730 - def _refresh_from_existing(self):
1731 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient']) 1732 self._TCTRL_patient.SetValue(ident.get_description_gender()) 1733 1734 if self.data['pk_health_issue'] is not None: 1735 self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue']) 1736 1737 self._PRW_description.SetText(self.data['description'], data=self.data['description']) 1738 1739 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u'')) 1740 1741 if self.data['diagnostic_certainty_classification'] is not None: 1742 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification']) 1743 1744 self._CHBOX_closed.SetValue(not self.data['episode_open']) 1745 1746 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes) 1747 self._PRW_codes.SetText(val, data)
1748 #----------------------------------------------------------------
1750 self._refresh_as_new()
1751 #================================================================ 1752 # health issue related widgets/functions 1753 #----------------------------------------------------------------
1754 -def edit_health_issue(parent=None, issue=None):
1755 ea = cHealthIssueEditAreaPnl(parent = parent, id = -1) 1756 ea.data = issue 1757 ea.mode = gmTools.coalesce(issue, 'new', 'edit') 1758 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (issue is not None)) 1759 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue'))) 1760 if dlg.ShowModal() == wx.ID_OK: 1761 return True 1762 return False
1763 #----------------------------------------------------------------
1764 -class cIssueListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
1765 1766 # FIXME: support pre-selection 1767
1768 - def __init__(self, *args, **kwargs):
1769 1770 issues = kwargs['issues'] 1771 del kwargs['issues'] 1772 1773 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 1774 1775 self.SetTitle(_('Select the health issues you are interested in ...')) 1776 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u'']) 1777 1778 for issue in issues: 1779 if issue['is_confidential']: 1780 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential')) 1781 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED')) 1782 else: 1783 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'') 1784 1785 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description']) 1786 if issue['clinically_relevant']: 1787 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant')) 1788 if issue['is_active']: 1789 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active')) 1790 if issue['is_cause_of_death']: 1791 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal')) 1792 1793 self._LCTRL_items.set_column_widths() 1794 self._LCTRL_items.set_data(data = issues)
1795 #----------------------------------------------------------------
1796 -class cIssueSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1797 """Let the user select a health issue. 1798 1799 The user can select a health issue from the existing issues 1800 of a patient. Selection is done with a phrasewheel so the user 1801 can type the issue name and matches will be shown. Typing 1802 "*" will show the entire list of issues. Inactive issues 1803 will be marked as such. If the user types an issue name not 1804 in the list of existing issues a new issue can be created 1805 from it if the programmer activated that feature. 1806 1807 If keyword <patient_id> is set to None or left out the control 1808 will listen to patient change signals and therefore act on 1809 gmPerson.gmCurrentPatient() changes. 1810 """
1811 - def __init__(self, *args, **kwargs):
1812 1813 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}} 1814 1815 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1816 # FIXME: consider clin.health_issue.clinically_relevant 1817 queries = [ 1818 u""" 1819 SELECT 1820 data, 1821 field_label, 1822 list_label 1823 FROM (( 1824 SELECT 1825 pk_health_issue AS data, 1826 description AS field_label, 1827 description AS list_label 1828 FROM clin.v_health_issues 1829 WHERE 1830 is_active IS true 1831 AND 1832 description %(fragment_condition)s 1833 AND 1834 %(ctxt_pat)s 1835 1836 ) UNION ( 1837 1838 SELECT 1839 pk_health_issue AS data, 1840 description AS field_label, 1841 description || _(' (inactive)') AS list_label 1842 FROM clin.v_health_issues 1843 WHERE 1844 is_active IS false 1845 AND 1846 description %(fragment_condition)s 1847 AND 1848 %(ctxt_pat)s 1849 )) AS union_query 1850 ORDER BY 1851 list_label"""], 1852 context = ctxt 1853 ) 1854 1855 try: kwargs['patient_id'] 1856 except KeyError: kwargs['patient_id'] = None 1857 1858 if kwargs['patient_id'] is None: 1859 self.use_current_patient = True 1860 self.__register_patient_change_signals() 1861 pat = gmPerson.gmCurrentPatient() 1862 if pat.connected: 1863 mp.set_context('pat', pat.ID) 1864 else: 1865 self.use_current_patient = False 1866 self.__patient_id = int(kwargs['patient_id']) 1867 mp.set_context('pat', self.__patient_id) 1868 1869 del kwargs['patient_id'] 1870 1871 gmPhraseWheel.cPhraseWheel.__init__ ( 1872 self, 1873 *args, 1874 **kwargs 1875 ) 1876 self.matcher = mp
1877 #-------------------------------------------------------- 1878 # external API 1879 #--------------------------------------------------------
1880 - def set_patient(self, patient_id=None):
1881 if self.use_current_patient: 1882 return False 1883 self.__patient_id = int(patient_id) 1884 self.set_context('pat', self.__patient_id) 1885 return True
1886 #--------------------------------------------------------
1887 - def _create_data(self):
1888 issue_name = self.GetValue().strip() 1889 if issue_name == u'': 1890 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True) 1891 _log.debug('cannot create health issue without name') 1892 return 1893 1894 if self.use_current_patient: 1895 pat = gmPerson.gmCurrentPatient() 1896 else: 1897 pat = gmPerson.cPatient(aPK_obj = self.__patient_id) 1898 1899 emr = pat.get_emr() 1900 issue = emr.add_health_issue(issue_name = issue_name) 1901 1902 if issue is None: 1903 self.data = {} 1904 else: 1905 self.SetText ( 1906 value = issue_name, 1907 data = issue['pk_health_issue'] 1908 )
1909 #--------------------------------------------------------
1910 - def _data2instance(self):
1911 return gmEMRStructItems.cHealthIssue(aPK_obj = self.GetData())
1912 #-------------------------------------------------------- 1913 # internal API 1914 #--------------------------------------------------------
1916 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection') 1917 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1918 #--------------------------------------------------------
1919 - def _pre_patient_selection(self):
1920 return True
1921 #--------------------------------------------------------
1922 - def _post_patient_selection(self):
1923 if self.use_current_patient: 1924 patient = gmPerson.gmCurrentPatient() 1925 self.set_context('pat', patient.ID) 1926 return True
1927 #------------------------------------------------------------
1928 -class cIssueSelectionDlg(wxgIssueSelectionDlg.wxgIssueSelectionDlg):
1929
1930 - def __init__(self, *args, **kwargs):
1931 try: 1932 msg = kwargs['message'] 1933 except KeyError: 1934 msg = None 1935 del kwargs['message'] 1936 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs) 1937 if msg is not None: 1938 self._lbl_message.SetLabel(label=msg)
1939 #--------------------------------------------------------
1940 - def _on_OK_button_pressed(self, event):
1941 event.Skip() 1942 pk_issue = self._PhWheel_issue.GetData(can_create=True) 1943 if pk_issue is None: 1944 gmGuiHelpers.gm_show_error ( 1945 _('Cannot create new health issue:\n [%(issue)s]') % {'issue': self._PhWheel_issue.GetValue().strip()}, 1946 _('Selecting health issue') 1947 ) 1948 return False 1949 return True
1950 #------------------------------------------------------------ 1951 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl 1952
1953 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1954 """Panel encapsulating health issue edit area functionality.""" 1955
1956 - def __init__(self, *args, **kwargs):
1957 1958 try: 1959 issue = kwargs['issue'] 1960 except KeyError: 1961 issue = None 1962 1963 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs) 1964 1965 gmEditArea.cGenericEditAreaMixin.__init__(self) 1966 1967 # FIXME: include more sources: coding systems/other database columns 1968 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1969 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"] 1970 ) 1971 mp.setThresholds(1, 3, 5) 1972 self._PRW_condition.matcher = mp 1973 1974 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1975 queries = [u""" 1976 select distinct on (grouping) grouping, grouping from ( 1977 1978 select rank, grouping from (( 1979 1980 select 1981 grouping, 1982 1 as rank 1983 from 1984 clin.health_issue 1985 where 1986 grouping %%(fragment_condition)s 1987 and 1988 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter) 1989 1990 ) union ( 1991 1992 select 1993 grouping, 1994 2 as rank 1995 from 1996 clin.health_issue 1997 where 1998 grouping %%(fragment_condition)s 1999 2000 )) as union_result 2001 2002 order by rank 2003 2004 ) as order_result 2005 2006 limit 50""" % gmPerson.gmCurrentPatient().ID 2007 ] 2008 ) 2009 mp.setThresholds(1, 3, 5) 2010 self._PRW_grouping.matcher = mp 2011 2012 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted) 2013 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted) 2014 2015 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted) 2016 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted) 2017 2018 self._PRW_year_noted.Enable(True) 2019 2020 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes) 2021 2022 self.data = issue
2023 #---------------------------------------------------------------- 2024 # generic Edit Area mixin API 2025 #----------------------------------------------------------------
2026 - def _valid_for_save(self):
2027 2028 if self._PRW_condition.GetValue().strip() == '': 2029 self._PRW_condition.display_as_valid(False) 2030 self._PRW_condition.SetFocus() 2031 return False 2032 self._PRW_condition.display_as_valid(True) 2033 self._PRW_condition.Refresh() 2034 2035 # FIXME: sanity check age/year diagnosed 2036 age_noted = self._PRW_age_noted.GetValue().strip() 2037 if age_noted != '': 2038 if gmDateTime.str2interval(str_interval = age_noted) is None: 2039 self._PRW_age_noted.display_as_valid(False) 2040 self._PRW_age_noted.SetFocus() 2041 return False 2042 self._PRW_age_noted.display_as_valid(True) 2043 2044 return True
2045 #----------------------------------------------------------------
2046 - def _save_as_new(self):
2047 pat = gmPerson.gmCurrentPatient() 2048 emr = pat.get_emr() 2049 2050 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip()) 2051 2052 side = u'' 2053 if self._ChBOX_left.GetValue(): 2054 side += u's' 2055 if self._ChBOX_right.GetValue(): 2056 side += u'd' 2057 issue['laterality'] = side 2058 2059 issue['summary'] = self._TCTRL_status.GetValue().strip() 2060 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData() 2061 issue['grouping'] = self._PRW_grouping.GetValue().strip() 2062 issue['is_active'] = self._ChBOX_active.GetValue() 2063 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue() 2064 issue['is_confidential'] = self._ChBOX_confidential.GetValue() 2065 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue() 2066 2067 age_noted = self._PRW_age_noted.GetData() 2068 if age_noted is not None: 2069 issue['age_noted'] = age_noted 2070 2071 issue.save() 2072 2073 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 2074 2075 self.data = issue 2076 return True
2077 #----------------------------------------------------------------
2078 - def _save_as_update(self):
2079 2080 self.data['description'] = self._PRW_condition.GetValue().strip() 2081 2082 side = u'' 2083 if self._ChBOX_left.GetValue(): 2084 side += u's' 2085 if self._ChBOX_right.GetValue(): 2086 side += u'd' 2087 self.data['laterality'] = side 2088 2089 self.data['summary'] = self._TCTRL_status.GetValue().strip() 2090 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData() 2091 self.data['grouping'] = self._PRW_grouping.GetValue().strip() 2092 self.data['is_active'] = bool(self._ChBOX_active.GetValue()) 2093 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue()) 2094 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue()) 2095 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue()) 2096 2097 age_noted = self._PRW_age_noted.GetData() 2098 if age_noted is not None: 2099 self.data['age_noted'] = age_noted 2100 2101 self.data.save() 2102 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 2103 2104 return True
2105 #----------------------------------------------------------------
2106 - def _refresh_as_new(self):
2107 self._PRW_condition.SetText() 2108 self._ChBOX_left.SetValue(0) 2109 self._ChBOX_right.SetValue(0) 2110 self._PRW_codes.SetText() 2111 self._on_leave_codes() 2112 self._PRW_certainty.SetText() 2113 self._PRW_grouping.SetText() 2114 self._TCTRL_status.SetValue(u'') 2115 self._PRW_age_noted.SetText() 2116 self._PRW_year_noted.SetText() 2117 self._ChBOX_active.SetValue(0) 2118 self._ChBOX_relevant.SetValue(1) 2119 self._ChBOX_confidential.SetValue(0) 2120 self._ChBOX_caused_death.SetValue(0) 2121 2122 return True
2123 #----------------------------------------------------------------
2124 - def _refresh_from_existing(self):
2125 self._PRW_condition.SetText(self.data['description']) 2126 2127 lat = gmTools.coalesce(self.data['laterality'], '') 2128 if lat.find('s') == -1: 2129 self._ChBOX_left.SetValue(0) 2130 else: 2131 self._ChBOX_left.SetValue(1) 2132 if lat.find('d') == -1: 2133 self._ChBOX_right.SetValue(0) 2134 else: 2135 self._ChBOX_right.SetValue(1) 2136 2137 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes) 2138 self._PRW_codes.SetText(val, data) 2139 self._on_leave_codes() 2140 2141 if self.data['diagnostic_certainty_classification'] is not None: 2142 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification']) 2143 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u'')) 2144 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u'')) 2145 2146 if self.data['age_noted'] is None: 2147 self._PRW_age_noted.SetText() 2148 else: 2149 self._PRW_age_noted.SetText ( 2150 value = '%sd' % self.data['age_noted'].days, 2151 data = self.data['age_noted'] 2152 ) 2153 2154 self._ChBOX_active.SetValue(self.data['is_active']) 2155 self._ChBOX_relevant.SetValue(self.data['clinically_relevant']) 2156 self._ChBOX_confidential.SetValue(self.data['is_confidential']) 2157 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death']) 2158 2159 # this dance should assure self._PRW_year_noted gets set -- but it doesn't ... 2160 # self._PRW_age_noted.SetFocus() 2161 # self._PRW_condition.SetFocus() 2162 2163 return True
2164 #----------------------------------------------------------------
2166 return self._refresh_as_new()
2167 #-------------------------------------------------------- 2168 # internal helpers 2169 #--------------------------------------------------------
2170 - def _on_leave_codes(self, *args, **kwargs):
2171 if not self._PRW_codes.IsModified(): 2172 return True 2173 2174 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2175 #--------------------------------------------------------
2176 - def _on_leave_age_noted(self, *args, **kwargs):
2177 2178 if not self._PRW_age_noted.IsModified(): 2179 return True 2180 2181 str_age = self._PRW_age_noted.GetValue().strip() 2182 2183 if str_age == u'': 2184 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 2185 return True 2186 2187 age = gmDateTime.str2interval(str_interval = str_age) 2188 2189 if age is None: 2190 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age) 2191 self._PRW_age_noted.SetBackgroundColour('pink') 2192 self._PRW_age_noted.Refresh() 2193 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 2194 return True 2195 2196 pat = gmPerson.gmCurrentPatient() 2197 if pat['dob'] is not None: 2198 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob'] 2199 2200 if age >= max_age: 2201 gmDispatcher.send ( 2202 signal = 'statustext', 2203 msg = _( 2204 'Health issue cannot have been noted at age %s. Patient is only %s old.' 2205 ) % (age, pat.get_medical_age()) 2206 ) 2207 self._PRW_age_noted.SetBackgroundColour('pink') 2208 self._PRW_age_noted.Refresh() 2209 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 2210 return True 2211 2212 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2213 self._PRW_age_noted.Refresh() 2214 self._PRW_age_noted.SetData(data=age) 2215 2216 if pat['dob'] is not None: 2217 fts = gmDateTime.cFuzzyTimestamp ( 2218 timestamp = pat['dob'] + age, 2219 accuracy = gmDateTime.acc_months 2220 ) 2221 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts) 2222 # if we do this we will *always* navigate there, regardless of TAB vs ALT-TAB 2223 #wx.CallAfter(self._ChBOX_active.SetFocus) 2224 # if we do the following instead it will take us to the save/update button ... 2225 #wx.CallAfter(self.Navigate) 2226 2227 return True
2228 #--------------------------------------------------------
2229 - def _on_leave_year_noted(self, *args, **kwargs):
2230 2231 if not self._PRW_year_noted.IsModified(): 2232 return True 2233 2234 year_noted = self._PRW_year_noted.GetData() 2235 2236 if year_noted is None: 2237 if self._PRW_year_noted.GetValue().strip() == u'': 2238 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 2239 return True 2240 self._PRW_year_noted.SetBackgroundColour('pink') 2241 self._PRW_year_noted.Refresh() 2242 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 2243 return True 2244 2245 year_noted = year_noted.get_pydt() 2246 2247 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo): 2248 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.')) 2249 self._PRW_year_noted.SetBackgroundColour('pink') 2250 self._PRW_year_noted.Refresh() 2251 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 2252 return True 2253 2254 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2255 self._PRW_year_noted.Refresh() 2256 2257 pat = gmPerson.gmCurrentPatient() 2258 if pat['dob'] is not None: 2259 issue_age = year_noted - pat['dob'] 2260 str_age = gmDateTime.format_interval_medically(interval = issue_age) 2261 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age) 2262 2263 return True
2264 #--------------------------------------------------------
2265 - def _on_modified_age_noted(self, *args, **kwargs):
2266 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 2267 return True
2268 #--------------------------------------------------------
2269 - def _on_modified_year_noted(self, *args, **kwargs):
2270 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 2271 return True
2272 #================================================================ 2273 # diagnostic certainty related widgets/functions 2274 #----------------------------------------------------------------
2275 -class cDiagnosticCertaintyClassificationPhraseWheel(gmPhraseWheel.cPhraseWheel):
2276
2277 - def __init__(self, *args, **kwargs):
2278 2279 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 2280 2281 self.selection_only = False # can be NULL, too 2282 2283 mp = gmMatchProvider.cMatchProvider_FixedList ( 2284 aSeq = [ 2285 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1}, 2286 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1}, 2287 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1}, 2288 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1} 2289 ] 2290 ) 2291 mp.setThresholds(1, 2, 4) 2292 self.matcher = mp 2293 2294 self.SetToolTipString(_( 2295 "The diagnostic classification or grading of this assessment.\n" 2296 "\n" 2297 "This documents how certain one is about this being a true diagnosis." 2298 ))
2299 #================================================================ 2300 # MAIN 2301 #---------------------------------------------------------------- 2302 if __name__ == '__main__': 2303 2304 #================================================================
2305 - class testapp (wx.App):
2306 """ 2307 Test application for testing EMR struct widgets 2308 """ 2309 #--------------------------------------------------------
2310 - def OnInit (self):
2311 """ 2312 Create test application UI 2313 """ 2314 frame = wx.Frame ( 2315 None, 2316 -4, 2317 'Testing EMR struct widgets', 2318 size=wx.Size(600, 400), 2319 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE 2320 ) 2321 filemenu= wx.Menu() 2322 filemenu.AppendSeparator() 2323 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application") 2324 2325 # Creating the menubar. 2326 menuBar = wx.MenuBar() 2327 menuBar.Append(filemenu,"&File") 2328 2329 frame.SetMenuBar(menuBar) 2330 2331 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"), 2332 wx.DefaultPosition, wx.DefaultSize, 0 ) 2333 2334 # event handlers 2335 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow) 2336 2337 # patient EMR 2338 self.__pat = gmPerson.gmCurrentPatient() 2339 2340 frame.Show(1) 2341 return 1
2342 #--------------------------------------------------------
2343 - def OnCloseWindow (self, e):
2344 """ 2345 Close test aplication 2346 """ 2347 self.ExitMainLoop ()
2348 #----------------------------------------------------------------
2349 - def test_encounter_edit_area_panel():
2350 app = wx.PyWidgetTester(size = (200, 300)) 2351 emr = pat.get_emr() 2352 enc = emr.active_encounter 2353 #enc = gmEMRStructItems.cEncounter(1) 2354 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc) 2355 app.frame.Show(True) 2356 app.MainLoop() 2357 return
2358 #----------------------------------------------------------------
2359 - def test_encounter_edit_area_dialog():
2360 app = wx.PyWidgetTester(size = (200, 300)) 2361 emr = pat.get_emr() 2362 enc = emr.active_encounter 2363 #enc = gmEMRStructItems.cEncounter(1) 2364 2365 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc) 2366 dlg.ShowModal()
2367 2368 # pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc) 2369 # app.frame.Show(True) 2370 # app.MainLoop() 2371 #----------------------------------------------------------------
2372 - def test_epsiode_edit_area_pnl():
2373 app = wx.PyWidgetTester(size = (200, 300)) 2374 emr = pat.get_emr() 2375 epi = emr.get_episodes()[0] 2376 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi) 2377 app.frame.Show(True) 2378 app.MainLoop()
2379 #----------------------------------------------------------------
2380 - def test_episode_edit_area_dialog():
2381 app = wx.PyWidgetTester(size = (200, 300)) 2382 emr = pat.get_emr() 2383 epi = emr.get_episodes()[0] 2384 edit_episode(parent=app.frame, episode=epi)
2385 #----------------------------------------------------------------
2386 - def test_hospital_stay_prw():
2387 app = wx.PyWidgetTester(size = (400, 40)) 2388 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 2389 app.MainLoop()
2390 #----------------------------------------------------------------
2391 - def test_episode_selection_prw():
2392 app = wx.PyWidgetTester(size = (400, 40)) 2393 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 2394 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID) 2395 app.MainLoop()
2396 #----------------------------------------------------------------
2397 - def test_health_issue_edit_area_dlg():
2398 app = wx.PyWidgetTester(size = (200, 300)) 2399 edit_health_issue(parent=app.frame, issue=None)
2400 #----------------------------------------------------------------
2401 - def test_health_issue_edit_area_pnl():
2402 app = wx.PyWidgetTester(size = (200, 300)) 2403 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400)) 2404 app.MainLoop()
2405 #----------------------------------------------------------------
2406 - def test_edit_procedure():
2407 app = wx.PyWidgetTester(size = (200, 300)) 2408 edit_procedure(parent=app.frame)
2409 #================================================================ 2410 2411 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 2412 2413 gmI18N.activate_locale() 2414 gmI18N.install_domain() 2415 gmDateTime.init() 2416 2417 # obtain patient 2418 pat = gmPersonSearch.ask_for_patient() 2419 if pat is None: 2420 print "No patient. Exiting gracefully..." 2421 sys.exit(0) 2422 gmPatSearchWidgets.set_active_patient(patient=pat) 2423 2424 # try: 2425 # lauch emr dialogs test application 2426 # app = testapp(0) 2427 # app.MainLoop() 2428 # except StandardError: 2429 # _log.exception("unhandled exception caught !") 2430 # but re-raise them 2431 # raise 2432 2433 #test_encounter_edit_area_panel() 2434 #test_encounter_edit_area_dialog() 2435 #test_epsiode_edit_area_pnl() 2436 #test_episode_edit_area_dialog() 2437 #test_health_issue_edit_area_dlg() 2438 #test_episode_selection_prw() 2439 #test_hospital_stay_prw() 2440 test_edit_procedure() 2441 2442 #================================================================ 2443