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