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