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

Source Code for Module Gnumed.wxpython.gmMedicationWidgets

   1  """GNUmed medication handling widgets.""" 
   2   
   3  #================================================================ 
   4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   5  __license__ = "GPL v2 or later" 
   6   
   7  import logging 
   8  import sys 
   9  import datetime as pydt 
  10   
  11   
  12  import wx 
  13  import wx.grid 
  14   
  15   
  16  if __name__ == '__main__': 
  17          sys.path.insert(0, '../../') 
  18          from Gnumed.pycommon import gmI18N 
  19          gmI18N.activate_locale() 
  20          gmI18N.install_domain(domain = 'gnumed') 
  21   
  22  from Gnumed.pycommon import gmDispatcher 
  23  from Gnumed.pycommon import gmCfg 
  24  from Gnumed.pycommon import gmTools 
  25  from Gnumed.pycommon import gmDateTime 
  26  from Gnumed.pycommon import gmMatchProvider 
  27  from Gnumed.pycommon import gmI18N 
  28  from Gnumed.pycommon import gmPrinting 
  29  from Gnumed.pycommon import gmCfg2 
  30  from Gnumed.pycommon import gmNetworkTools 
  31   
  32  from Gnumed.business import gmPerson 
  33  from Gnumed.business import gmPraxis 
  34  from Gnumed.business import gmMedication 
  35  from Gnumed.business import gmForms 
  36  from Gnumed.business import gmStaff 
  37  from Gnumed.business import gmDocuments 
  38  from Gnumed.business import gmLOINC 
  39  from Gnumed.business import gmClinicalRecord 
  40  from Gnumed.business import gmClinicalCalculator 
  41  from Gnumed.business import gmPathLab 
  42   
  43  from Gnumed.wxpython import gmGuiHelpers 
  44  from Gnumed.wxpython import gmRegetMixin 
  45  from Gnumed.wxpython import gmAuthWidgets 
  46  from Gnumed.wxpython import gmEditArea 
  47  from Gnumed.wxpython import gmMacro 
  48  from Gnumed.wxpython import gmCfgWidgets 
  49  from Gnumed.wxpython import gmListWidgets 
  50  from Gnumed.wxpython import gmPhraseWheel 
  51  from Gnumed.wxpython import gmFormWidgets 
  52  from Gnumed.wxpython import gmAllergyWidgets 
  53  from Gnumed.wxpython import gmDocumentWidgets 
  54  from Gnumed.wxpython import gmSubstanceMgmtWidgets 
  55   
  56   
  57  _log = logging.getLogger('gm.ui') 
  58   
  59  #============================================================ 
  60  # perhaps leave this here: 
  61  #============================================================ 
62 -class cSubstancePreparationPhraseWheel(gmPhraseWheel.cPhraseWheel):
63
64 - def __init__(self, *args, **kwargs):
65 66 query = """ 67 SELECT DISTINCT ON (list_label) 68 preparation AS data, 69 preparation AS list_label, 70 preparation AS field_label 71 FROM ref.drug_product 72 WHERE preparation %(fragment_condition)s 73 ORDER BY list_label 74 LIMIT 30""" 75 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 76 mp.setThresholds(1, 2, 4) 77 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 78 self.SetToolTip(_('The preparation (form) of the substance or product.')) 79 self.matcher = mp 80 self.selection_only = False
81 82 #============================================================ 83 # current substance intake widgets 84 #------------------------------------------------------------
85 -class cSubstanceIntakeObjectPhraseWheel(gmPhraseWheel.cPhraseWheel):
86
87 - def __init__(self, *args, **kwargs):
88 mp = gmMedication.cSubstanceIntakeObjectMatchProvider() 89 mp.setThresholds(1, 2, 4) 90 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 91 self.SetToolTip(_('A drug product.')) 92 self.matcher = mp 93 self.selection_only = True 94 self.phrase_separators = None
95 96 #--------------------------------------------------------
97 - def _data2instance(self):
98 pk = self.GetData(as_instance = False, can_create = False) 99 if pk is None: 100 return None 101 return gmMedication.cDrugProduct(aPK_obj = pk)
102 103 #------------------------------------------------------------
104 -class cProductOrSubstancePhraseWheel(gmPhraseWheel.cPhraseWheel):
105
106 - def __init__(self, *args, **kwargs):
107 108 mp = gmMedication.cProductOrSubstanceMatchProvider() 109 mp.setThresholds(1, 2, 4) 110 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 111 self.SetToolTip(_('A substance with optional strength or a drug product.')) 112 self.matcher = mp 113 self.selection_only = False 114 self.phrase_separators = None 115 self.IS_PRODUCT = 1 116 self.IS_SUBSTANCE = 2 117 self.IS_COMPONENT = 3
118 119 #--------------------------------------------------------
120 - def _data2instance(self):
121 entry_type, pk = self.GetData(as_instance = False, can_create = False) 122 if entry_type == 1: 123 return gmMedication.cDrugProduct(aPK_obj = pk) 124 if entry_type == 2: 125 return gmMedication.cConsumableSubstance(aPK_obj = pk) 126 if entry_type == 3: 127 return gmMedication.cDrugComponent(aPK_obj = pk) 128 raise ValueError('entry type must be 1=drug product or 2=substance or 3=component')
129 130 #============================================================
131 -class cSubstanceSchedulePhraseWheel(gmPhraseWheel.cPhraseWheel):
132
133 - def __init__(self, *args, **kwargs):
134 135 query = """ 136 SELECT DISTINCT ON (sched) 137 schedule as sched, 138 schedule 139 FROM clin.substance_intake 140 WHERE schedule %(fragment_condition)s 141 ORDER BY sched 142 LIMIT 50""" 143 144 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 145 mp.setThresholds(1, 2, 4) 146 mp.word_separators = '[ \t=+&:@]+' 147 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 148 self.SetToolTip(_('The schedule for taking this substance.')) 149 self.matcher = mp 150 self.selection_only = False
151 152 #============================================================
153 -class cSubstanceAimPhraseWheel(gmPhraseWheel.cPhraseWheel):
154
155 - def __init__(self, *args, **kwargs):
156 157 query = """ 158 ( 159 SELECT DISTINCT ON (field_label) 160 aim 161 AS data, 162 aim || ' (' || substance || ' ' || amount || ' ' || unit || ')' 163 AS list_label, 164 aim 165 AS field_label 166 FROM clin.v_substance_intakes 167 WHERE 168 aim %(fragment_condition)s 169 %(ctxt_substance)s 170 ) UNION ( 171 SELECT DISTINCT ON (field_label) 172 aim 173 AS data, 174 aim || ' (' || substance || ' ' || amount || ' ' || unit || ')' 175 AS list_label, 176 aim 177 AS field_label 178 FROM clin.v_substance_intakes 179 WHERE 180 aim %(fragment_condition)s 181 ) 182 ORDER BY list_label 183 LIMIT 30""" 184 185 context = {'ctxt_substance': { 186 'where_part': 'AND substance = %(substance)s', 187 'placeholder': 'substance' 188 }} 189 190 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = context) 191 mp.setThresholds(1, 2, 4) 192 #mp.word_separators = '[ \t=+&:@]+' 193 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 194 self.SetToolTip(_('The medical aim for consuming this substance.')) 195 self.matcher = mp 196 self.selection_only = False
197 198 #============================================================
199 -def turn_substance_intake_into_allergy(parent=None, intake=None, emr=None):
200 201 if intake['is_currently_active']: 202 intake['discontinued'] = gmDateTime.pydt_now_here() 203 if intake['discontinue_reason'] is None: 204 intake['discontinue_reason'] = '%s %s' % (_('not tolerated:'), _('discontinued due to allergy or intolerance')) 205 else: 206 if not intake['discontinue_reason'].startswith(_('not tolerated:')): 207 intake['discontinue_reason'] = '%s %s' % (_('not tolerated:'), intake['discontinue_reason']) 208 if not intake.save(): 209 return False 210 211 allg = intake.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter']) 212 213 drug = intake.containing_drug 214 comps = [ c['substance'] for c in drug.components ] 215 if len(comps) > 1: 216 gmGuiHelpers.gm_show_info ( 217 aTitle = _('Documented an allergy'), 218 aMessage = _( 219 'An allergy was documented against the substance:\n' 220 '\n' 221 ' [%s]\n' 222 '\n' 223 'This substance was taken with the multi-component drug product:\n' 224 '\n' 225 ' [%s (%s)]\n' 226 '\n' 227 'Note that ALL components of this product were discontinued.' 228 ) % ( 229 intake['substance'], 230 intake['product'], 231 ' & '.join(comps) 232 ) 233 ) 234 235 if parent is None: 236 parent = wx.GetApp().GetTopWindow() 237 238 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent, -1) 239 dlg.ShowModal() 240 241 return True
242 243 #============================================================
244 -def manage_substance_intakes(parent=None, emr=None):
245 246 if parent is None: 247 parent = wx.GetApp().GetTopWindow() 248 249 if emr is None: 250 emr = gmPerson.gmCurrentPatient().emr 251 # #------------------------------------------------------------ 252 # def add_from_db(substance): 253 # drug_db = gmSubstanceMgmtWidgets.get_drug_database(parent = parent, patient = gmPerson.gmCurrentPatient()) 254 # if drug_db is None: 255 # return False 256 # drug_db.import_drugs() 257 # return True 258 # #------------------------------------------------------------ 259 # def edit(substance=None): 260 # return gmSubstanceMgmtWidgets.edit_substance(parent = parent, substance = substance, single_entry = (substance is not None)) 261 # #------------------------------------------------------------ 262 # def delete(substance): 263 # if substance.is_in_use_by_patients: 264 # gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True) 265 # return False 266 # 267 # xxxxx -> substance_dose 268 # return gmMedication.delete_xsubstance(substance = substance['pk']) 269 #------------------------------------------------------------ 270 def get_tooltip(intake=None): 271 return intake.format(single_line = False, show_all_product_components = True)
272 #------------------------------------------------------------ 273 def refresh(lctrl): 274 intakes = emr.get_current_medications ( 275 include_inactive = False, 276 include_unapproved = True, 277 order_by = 'substance, product, started' 278 ) 279 items = [] 280 for i in intakes: 281 started = i.medically_formatted_start 282 items.append ([ 283 '%s%s %s %s %s%s' % ( 284 i['substance'], 285 gmTools.coalesce(i['product'], '', ' (%s)'), 286 i['amount'], 287 i['unit'], 288 i['l10n_preparation'], 289 gmTools.coalesce(i['external_code_product'], '', ' [%s::%s]' % (i['external_code_type_product'], i['external_code_product'])) 290 ), 291 '%s%s%s' % ( 292 started, 293 gmTools.coalesce(i['schedule'], '', ' %%s %s' % gmTools.u_arrow2right), 294 gmTools.coalesce(i['duration'], '', ' %s') 295 ), 296 '%s' % ( 297 gmTools.bool2subst ( 298 i['intake_is_approved_of'], 299 '', 300 _('disapproved') 301 ) 302 ) 303 ]) 304 lctrl.set_string_items(items) 305 lctrl.set_data(intakes) 306 307 #------------------------------------------------------------ 308 return gmListWidgets.get_choices_from_list ( 309 parent = parent, 310 caption = _('Substances consumed by the patient'), 311 columns = [ _('Intake'), _('Application'), _('Status') ], 312 single_selection = False, 313 # new_callback = edit, 314 # edit_callback = edit, 315 # delete_callback = delete, 316 refresh_callback = refresh, 317 list_tooltip_callback = get_tooltip 318 # ,left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db) 319 ) 320 321 #============================================================ 322 from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl 323
324 -class cSubstanceIntakeEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
325
326 - def __init__(self, *args, **kwargs):
327 328 try: 329 data = kwargs['substance'] 330 del kwargs['substance'] 331 except KeyError: 332 data = None 333 334 self.calc = gmClinicalCalculator.cClinicalCalculator() 335 336 wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl.__init__(self, *args, **kwargs) 337 gmEditArea.cGenericEditAreaMixin.__init__(self) 338 339 self.mode = 'new' 340 self.data = data 341 if data is not None: 342 self.mode = 'edit' 343 344 self.__init_ui()
345 346 #----------------------------------------------------------------
347 - def __init_ui(self):
348 349 self._PRW_drug.add_callback_on_lose_focus(callback = self._on_leave_drug) 350 self._PRW_drug.selection_only = True 351 352 self._PRW_duration.display_accuracy = gmDateTime.acc_days 353 354 # this we want to adjust later 355 self._PRW_aim.add_callback_on_set_focus(callback = self._on_enter_aim) 356 357 self._DP_discontinued.add_callback_on_selection(callback = self._on_discontinued_date_changed)
358 359 #----------------------------------------------------------------
360 - def __refresh_precautions(self):
361 362 curr_pat = gmPerson.gmCurrentPatient() 363 emr = curr_pat.emr 364 365 # allergies 366 state = emr.allergy_state 367 if state['last_confirmed'] is None: 368 confirmed = _('never') 369 else: 370 confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d') 371 msg = _('%s, last confirmed %s\n') % (state.state_string, confirmed) 372 msg += gmTools.coalesce(state['comment'], '', _('Comment (%s): %%s\n') % state['modified_by']) 373 tooltip = '' 374 allgs = emr.get_allergies() 375 if len(allgs) > 0: 376 msg += '\n' 377 for allergy in allgs: 378 msg += '%s: %s (%s)\n' % ( 379 allergy['descriptor'], 380 allergy['l10n_type'], 381 gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), '?') 382 ) 383 tooltip += '%s: %s\n' % ( 384 allergy['descriptor'], 385 gmTools.coalesce(allergy['reaction'], _('reaction not recorded')) 386 ) 387 if len(allgs) > 0: 388 msg += '\n' 389 tooltip += '\n' 390 del allgs 391 392 # history of substance abuse 393 abuses = emr.abused_substances 394 for abuse in abuses: 395 tooltip += abuse.format(single_line = False, include_metadata = False) 396 tooltip += '\n' 397 if abuse['harmful_use_type'] in [None, 0]: 398 continue 399 msg += abuse.format(single_line = True) 400 msg += '\n' 401 if len(abuses) > 0: 402 msg += '\n' 403 tooltip += '\n' 404 del abuses 405 406 # kidney function 407 gfr = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1) 408 if gfr is None: 409 self.calc.patient = curr_pat 410 gfr = self.calc.eGFR 411 if gfr.numeric_value is None: 412 msg += _('GFR: unknown') 413 else: 414 msg += gfr.message 415 egfrs = self.calc.eGFRs 416 tts = [] 417 for egfr in egfrs: 418 if egfr.numeric_value is None: 419 continue 420 tts.append(egfr.format ( 421 left_margin = 0, 422 width = 50, 423 eol = '\n', 424 with_formula = False, 425 with_warnings = True, 426 with_variables = False, 427 with_sub_results = False, 428 return_list = False 429 )) 430 tooltip += '\n'.join(tts) 431 else: 432 msg += '%s: %s %s (%s)\n' % ( 433 gfr['unified_abbrev'], 434 gfr['unified_val'], 435 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'), 436 gmDateTime.pydt_strftime ( 437 gfr['clin_when'], 438 format = '%Y %b %d' 439 ) 440 ) 441 tooltip += _('GFR reported by path lab') 442 443 # pregnancy 444 edc = emr.EDC 445 if edc is not None: 446 msg += '\n\n' 447 if emr.EDC_is_fishy: 448 msg += _('EDC (!?!): %s') % gmDateTime.pydt_strftime(edc, format = '%Y %b %d') 449 tooltip += _( 450 'The Expected Date of Confinement is rather questionable.\n' 451 '\n' 452 'Please check patient age, patient gender, time until/since EDC.' 453 ) 454 else: 455 msg += _('EDC: %s') % gmDateTime.pydt_strftime(edc, format = '%Y %b %d') 456 457 self._LBL_allergies.SetLabel(msg) 458 self._LBL_allergies.SetToolTip(tooltip)
459 460 #----------------------------------------------------------------
461 - def __refresh_drug_details(self):
462 463 drug = self._PRW_drug.GetData(as_instance = True) 464 if drug is None: 465 self._LBL_drug_details.SetLabel('') 466 self._LBL_drug_details.SetToolTip('') 467 self.Layout() 468 return 469 470 if len(drug['components']) == 0: 471 comps = _('<no components>') 472 else: 473 comps = '\n'.join ([ 474 ' %s %s%s%s' % ( 475 c['substance'], 476 c['amount'], 477 c['unit'], 478 gmTools.coalesce(c['dose_unit'], '', '/%s') 479 ) 480 for c in drug['components'] 481 ]) 482 self._LBL_drug_details.SetLabel('%s\n%s' % (drug['product'], comps)) 483 self._LBL_drug_details.SetToolTip(drug.format()) 484 self.Layout() 485 return
486 487 #---------------------------------------------------------------- 488 # generic Edit Area mixin API 489 #----------------------------------------------------------------
490 - def _check_drug_is_valid(self):
491 492 self._PRW_drug.display_as_valid(True) 493 494 # if we are editing the drug SHOULD exist so don't error 495 if self.mode == 'edit': 496 return True 497 498 selected_drug = self._PRW_drug.GetData(as_instance = True) 499 500 # no drug selected 501 if selected_drug is None: 502 val = self._PRW_drug.GetValue().strip() 503 if val == '': 504 self._PRW_drug.display_as_valid(False) 505 self._PRW_drug.SetFocus() 506 return False 507 # create as a generic, single-substance drug if that does not exist 508 drug = gmSubstanceMgmtWidgets.edit_single_component_generic_drug ( 509 parent = self, 510 drug = None, 511 single_entry = True, 512 fields = {'substance': {'value': val, 'data': None}}, 513 return_drug = True 514 ) 515 if drug is None: 516 self._PRW_drug.display_as_valid(False) 517 self._PRW_drug.SetFocus() 518 return False 519 comp = drug.components[0] 520 self._PRW_drug.SetText ( 521 _('%s w/ %s%s%s of %s') % ( 522 comp['product'], 523 comp['amount'], 524 comp['unit'], 525 gmTools.coalesce(comp['dose_unit'], '', '/%s'), 526 comp['substance'] 527 ), 528 drug['pk_drug_product'] 529 ) 530 selected_drug = drug 531 self.__refresh_drug_details() 532 self._PRW_drug.display_as_valid(True) 533 self._PRW_drug.SetFocus() 534 # return False despite there's now a drug such 535 # that the user has another chance to inspect 536 # the edit area data after creating a new drug 537 return False 538 539 # drug already exists as intake 540 if selected_drug.exists_as_intake(pk_patient = gmPerson.gmCurrentPatient().ID): 541 title = _('Adding substance intake entry') 542 msg = _( 543 'The patient is already taking\n' 544 '\n' 545 ' %s\n' 546 '\n' 547 'You will want to adjust the schedule\n' 548 'rather than document the intake twice.' 549 ) % self._PRW_drug.GetValue().strip() 550 gmGuiHelpers.gm_show_warning(aTitle = title, aMessage = msg) 551 self._PRW_drug.display_as_valid(False) 552 self._PRW_drug.SetFocus() 553 return False 554 555 self._PRW_drug.display_as_valid(True) 556 return True
557 558 #----------------------------------------------------------------
559 - def _valid_for_save(self):
560 561 validity = self._check_drug_is_valid() 562 563 # episode must be set if intake is to be approved of 564 if self._CHBOX_approved.IsChecked(): 565 if self._PRW_episode.GetValue().strip() == '': 566 self._PRW_episode.display_as_valid(False) 567 validity = False 568 else: 569 self._PRW_episode.display_as_valid(True) 570 571 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]: 572 self._PRW_duration.display_as_valid(True) 573 else: 574 if self._PRW_duration.GetData() is None: 575 # no data ... 576 if gmDateTime.str2interval(self._PRW_duration.GetValue()) is None: 577 self._PRW_duration.display_as_valid(False) 578 validity = False 579 # ... but valid string 580 else: 581 self._PRW_duration.display_as_valid(True) 582 # has data 583 else: 584 self._PRW_duration.display_as_valid(True) 585 586 # started must exist or be unknown 587 started = None 588 if self._CHBOX_start_unknown.IsChecked() is False: 589 started = self._DP_started.GetData() 590 if started is None: 591 self._DP_started.display_as_valid(False) 592 self._DP_started.SetFocus() 593 validity = False 594 else: 595 self._DP_started.display_as_valid(True) 596 597 if validity is False: 598 gmDispatcher.send(signal = 'statustext', msg = _('Input incomplete/invalid for saving as substance intake.')) 599 600 # discontinued must be "< now()" AND "> started" if at all 601 discontinued = self._DP_discontinued.GetData() 602 if discontinued is not None: 603 now = gmDateTime.pydt_now_here().replace ( 604 hour = 23, 605 minute = 59, 606 second = 59, 607 microsecond = 111111 608 ) 609 # not in the future 610 if discontinued > now: 611 self._DP_discontinued.display_as_valid(False) 612 validity = False 613 gmDispatcher.send(signal = 'statustext', msg = _('Discontinued (%s) in the future (now: %s)!') % (discontinued, now)) 614 else: 615 if started is not None: 616 started = started.replace ( 617 hour = 0, 618 minute = 0, 619 second = 0, 620 microsecond = 1 621 ) 622 # and not before it was started 623 if started > discontinued: 624 self._DP_started.display_as_valid(False) 625 self._DP_discontinued.display_as_valid(False) 626 validity = False 627 gmDispatcher.send(signal = 'statustext', msg = _('Discontinued (%s) before started (%s) !') % (discontinued, started)) 628 else: 629 self._DP_started.display_as_valid(True) 630 self._DP_discontinued.display_as_valid(True) 631 632 return validity
633 634 #----------------------------------------------------------------
635 - def _save_as_new(self):
636 637 epi = self._PRW_episode.GetData() 638 if epi is None: 639 # create new episode, Jim wants it to auto-open 640 epi = self._PRW_episode.GetData(can_create = True, is_open = True) 641 642 selected_drug = self._PRW_drug.GetData(as_instance = True) 643 intake = selected_drug.turn_into_intake ( 644 encounter = gmPerson.gmCurrentPatient().emr.current_encounter['pk_encounter'], 645 episode = epi 646 ) 647 648 if intake is None: 649 gmDispatcher.send('statustext', msg = _('Cannot add duplicate of (maybe inactive) substance intake.'), beep = True) 650 return False 651 652 intake['started'] = self._DP_started.GetData() 653 if self._CHBOX_start_unknown.IsChecked(): 654 intake['comment_on_start'] = '?' 655 else: 656 intake['comment_on_start'] = self._PRW_start_certainty.GetValue().strip() 657 intake['discontinued'] = self._DP_discontinued.GetData() 658 if intake['discontinued'] is None: 659 intake['discontinue_reason'] = None 660 else: 661 intake['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip() 662 intake['schedule'] = self._PRW_schedule.GetValue().strip() 663 intake['aim'] = self._PRW_aim.GetValue().strip() 664 intake['notes'] = self._PRW_notes.GetValue().strip() 665 intake['is_long_term'] = self._CHBOX_long_term.IsChecked() 666 intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked() 667 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]: 668 intake['duration'] = None 669 else: 670 if self._PRW_duration.GetData() is None: 671 intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 672 else: 673 intake['duration'] = self._PRW_duration.GetData() 674 intake.save() 675 676 self.data = intake 677 678 return True
679 680 #----------------------------------------------------------------
681 - def _save_as_update(self):
682 683 # auto-applies to all components of a multi-component drug if any: 684 self.data['started'] = self._DP_started.GetData() 685 if self._CHBOX_start_unknown.IsChecked(): 686 self.data['comment_on_start'] = '?' 687 else: 688 self.data['comment_on_start'] = self._PRW_start_certainty.GetValue().strip() 689 self.data['discontinued'] = self._DP_discontinued.GetData() 690 if self.data['discontinued'] is None: 691 self.data['discontinue_reason'] = None 692 else: 693 self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip() 694 self.data['schedule'] = self._PRW_schedule.GetValue() 695 self.data['is_long_term'] = self._CHBOX_long_term.IsChecked() 696 self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked() 697 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]: 698 self.data['duration'] = None 699 else: 700 if self._PRW_duration.GetData() is None: 701 self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 702 else: 703 self.data['duration'] = self._PRW_duration.GetData() 704 705 # per-component 706 self.data['aim'] = self._PRW_aim.GetValue() 707 self.data['notes'] = self._PRW_notes.GetValue() 708 epi = self._PRW_episode.GetData() 709 if epi is None: 710 # create new episode, Jim wants it to auto-open 711 epi = self._PRW_episode.GetData(can_create = True, is_open = True) 712 self.data['pk_episode'] = epi 713 714 self.data.save() 715 716 return True
717 718 #----------------------------------------------------------------
719 - def _refresh_as_new(self):
720 self._PRW_drug.SetText('', None) 721 722 self._PRW_schedule.SetText('', None) 723 self._PRW_duration.SetText('', None) 724 self._PRW_aim.SetText('', None) 725 self._PRW_notes.SetText('', None) 726 self._PRW_episode.SetText('', None) 727 728 self._CHBOX_long_term.SetValue(False) 729 self._CHBOX_approved.SetValue(True) 730 731 self._CHBOX_start_unknown.SetValue(False) 732 self._DP_started.SetData(gmDateTime.pydt_now_here()) 733 self._DP_started.Enable(True) 734 self._PRW_start_certainty.SetText('', None) 735 self._PRW_start_certainty.Enable(True) 736 self._DP_discontinued.SetData(None) 737 self._PRW_discontinue_reason.SetValue('') 738 self._PRW_discontinue_reason.Enable(False) 739 740 self.__refresh_drug_details() 741 self.__refresh_precautions() 742 743 self._PRW_drug.SetFocus()
744 745 #----------------------------------------------------------------
746 - def _refresh_from_existing(self):
747 748 self._PRW_drug.SetText ( 749 _('%s w/ %s%s%s of %s') % ( 750 self.data['product'], 751 self.data['amount'], 752 self.data['unit'], 753 gmTools.coalesce(self.data['dose_unit'], '', '/%s'), 754 self.data['substance'] 755 ), 756 self.data['pk_drug_product'] 757 ) 758 759 self._PRW_drug.Disable() 760 761 if self.data['is_long_term']: 762 self._CHBOX_long_term.SetValue(True) 763 self._PRW_duration.Enable(False) 764 self._PRW_duration.SetText(gmTools.u_infinity, None) 765 self._BTN_discontinued_as_planned.Enable(False) 766 else: 767 self._CHBOX_long_term.SetValue(False) 768 self._PRW_duration.Enable(True) 769 self._BTN_discontinued_as_planned.Enable(True) 770 self._PRW_duration.SetData(self.data['duration']) 771 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], ''), self.data['aim']) 772 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], ''), self.data['notes']) 773 self._PRW_episode.SetData(self.data['pk_episode']) 774 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], ''), self.data['schedule']) 775 776 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of']) 777 778 self._DP_started.SetData(self.data['started']) 779 self._PRW_start_certainty.SetText(self.data['comment_on_start'], None) 780 if self.data['start_is_unknown']: 781 self._CHBOX_start_unknown.SetValue(True) 782 self._DP_started.Enable(False) 783 self._PRW_start_certainty.Enable(False) 784 else: 785 self._CHBOX_start_unknown.SetValue(False) 786 self._DP_started.Enable(True) 787 self._PRW_start_certainty.Enable(True) 788 789 self._DP_discontinued.SetData(self.data['discontinued']) 790 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], '')) 791 if self.data['discontinued'] is not None: 792 self._PRW_discontinue_reason.Enable() 793 794 self.__refresh_drug_details() 795 self.__refresh_precautions() 796 797 self._PRW_schedule.SetFocus()
798 799 #----------------------------------------------------------------
801 self._refresh_as_new() 802 803 self._PRW_episode.SetData(self.data['pk_episode']) 804 self._DP_started.SetData(self.data['started']) 805 806 self._PRW_drug.SetFocus()
807 808 #---------------------------------------------------------------- 809 # event handlers 810 #----------------------------------------------------------------
811 - def _on_leave_drug(self):
812 self.__refresh_drug_details()
813 814 #----------------------------------------------------------------
815 - def _on_enter_aim(self):
816 drug = self._PRW_drug.GetData(as_instance = True) 817 if drug is None: 818 self._PRW_aim.unset_context(context = 'substance') 819 return
820 # do not set to self._PRW_drug.GetValue() as that will contain all 821 # sorts of additional info, rather set to the canonical drug['substance'] 822 # self._PRW_aim.set_context(context = u'substance', val = drug['substance']) 823 824 #----------------------------------------------------------------
825 - def _on_discontinued_date_changed(self, event):
826 if self._DP_discontinued.GetData() is None: 827 self._PRW_discontinue_reason.Enable(False) 828 else: 829 self._PRW_discontinue_reason.Enable(True)
830 831 #----------------------------------------------------------------
832 - def _on_manage_components_button_pressed(self, event):
834 835 #----------------------------------------------------------------
836 - def _on_manage_substances_button_pressed(self, event):
838 839 #---------------------------------------------------------------- 842 843 #----------------------------------------------------------------
844 - def _on_manage_doses_button_pressed(self, event):
846 847 #----------------------------------------------------------------
848 - def _on_heart_button_pressed(self, event):
850 851 #----------------------------------------------------------------
852 - def _on_kidneys_button_pressed(self, event):
853 if self._PRW_drug.GetData() is None: 854 search_term = self._PRW_drug.GetValue().strip() 855 else: 856 search_term = self._PRW_drug.GetData(as_instance = True) 857 858 gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
859 860 #----------------------------------------------------------------
862 863 now = gmDateTime.pydt_now_here() 864 865 self.__refresh_precautions() 866 867 if self.data is None: 868 return 869 870 # do we have a (full) plan ? 871 if None not in [self.data['started'], self.data['duration']]: 872 planned_end = self.data['started'] + self.data['duration'] 873 # the plan hasn't ended so [Per plan] can't apply ;-) 874 if planned_end > now: 875 return 876 self._DP_discontinued.SetData(planned_end) 877 self._PRW_discontinue_reason.Enable(True) 878 self._PRW_discontinue_reason.SetValue('') 879 return 880 881 # we know started but not duration: apparently the plan is to stop today 882 if self.data['started'] is not None: 883 # but we haven't started yet so we can't stop 884 if self.data['started'] > now: 885 return 886 887 self._DP_discontinued.SetData(now) 888 self._PRW_discontinue_reason.Enable(True) 889 self._PRW_discontinue_reason.SetValue('')
890 891 #----------------------------------------------------------------
892 - def _on_chbox_long_term_checked(self, event):
893 if self._CHBOX_long_term.IsChecked() is True: 894 self._PRW_duration.Enable(False) 895 self._BTN_discontinued_as_planned.Enable(False) 896 self._PRW_discontinue_reason.Enable(False) 897 else: 898 self._PRW_duration.Enable(True) 899 self._BTN_discontinued_as_planned.Enable(True) 900 self._PRW_discontinue_reason.Enable(True) 901 902 self.__refresh_precautions()
903 904 #----------------------------------------------------------------
905 - def _on_start_unknown_checked(self, event):
906 event.Skip() 907 if self._CHBOX_start_unknown.IsChecked() is True: 908 self._DP_started.Enable(False) 909 self._PRW_start_certainty.Enable(False) 910 else: 911 self._DP_started.Enable(True) 912 self._PRW_start_certainty.Enable(True) 913 914 self.__refresh_precautions()
915 916 #----------------------------------------------------------------
917 - def turn_into_allergy(self, data=None):
918 if not self.save(): 919 return False 920 921 return turn_substance_intake_into_allergy ( 922 parent = self, 923 intake = self.data, 924 emr = gmPerson.gmCurrentPatient().emr 925 )
926 927 #============================================================
928 -def delete_substance_intake(parent=None, intake=None):
929 930 comps = intake.containing_drug.components 931 if len(comps) > 1: 932 msg = _( 933 'This intake is part of a multi-component drug product:\n' 934 '\n' 935 ' %s\n' 936 '\n' 937 'Really delete all intakes related to this drug product ?' 938 ) % '\n '.join ( 939 [ '%s %s%s' % (c['substance'], c['amount'], c.formatted_units) for c in comps ] 940 ) 941 delete_all = gmGuiHelpers.gm_show_question ( 942 title = _('Deleting medication / substance intake'), 943 question = msg 944 ) 945 if not delete_all: 946 return 947 948 msg = _( 949 '\n' 950 '[%s]\n' 951 '\n' 952 'It may be prudent to edit (before deletion) the details\n' 953 'of this substance intake entry so as to leave behind\n' 954 'some indication of why it was deleted.\n' 955 ) % intake.format() 956 957 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 958 parent, 959 -1, 960 caption = _('Deleting medication / substance intake'), 961 question = msg, 962 button_defs = [ 963 {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True}, 964 {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')}, 965 {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')} 966 ] 967 ) 968 969 edit_first = dlg.ShowModal() 970 dlg.Destroy() 971 972 if edit_first == wx.ID_CANCEL: 973 return 974 975 if edit_first == wx.ID_YES: 976 edit_intake_of_substance(parent = parent, substance = intake) 977 delete_it = gmGuiHelpers.gm_show_question ( 978 aMessage = _('Now delete substance intake entry ?'), 979 aTitle = _('Deleting medication / substance intake') 980 ) 981 else: 982 delete_it = True 983 984 if not delete_it: 985 return 986 987 gmMedication.delete_substance_intake(pk_intake = intake['pk_substance_intake'], delete_siblings = True)
988 989 #------------------------------------------------------------
990 -def edit_intake_of_substance(parent = None, substance=None):
991 ea = cSubstanceIntakeEAPnl(parent, -1, substance = substance) 992 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = (substance is not None)) 993 dlg.SetTitle(gmTools.coalesce(substance, _('Adding medication/non-medication substance intake'), _('Editing medication/non-medication substance intake'))) 994 dlg.left_extra_button = ( 995 _('Allergy'), 996 _('Document an allergy against this substance.'), 997 ea.turn_into_allergy 998 ) 999 dlg.SetSize((650,500)) 1000 if dlg.ShowModal() == wx.ID_OK: 1001 dlg.Destroy() 1002 return True 1003 dlg.Destroy() 1004 return False
1005 1006 #============================================================ 1007 # current substances grid 1008 #------------------------------------------------------------
1009 -def configure_medication_list_template(parent=None):
1010 1011 if parent is None: 1012 parent = wx.GetApp().GetTopWindow() 1013 1014 template = gmFormWidgets.manage_form_templates ( 1015 parent = parent, 1016 template_types = ['current medication list'] 1017 ) 1018 option = 'form_templates.medication_list' 1019 1020 if template is None: 1021 gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True) 1022 return None 1023 1024 if template['engine'] not in ['L', 'X', 'T']: 1025 gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True) 1026 return None 1027 1028 dbcfg = gmCfg.cCfgSQL() 1029 dbcfg.set ( 1030 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1031 option = option, 1032 value = '%s - %s' % (template['name_long'], template['external_version']) 1033 ) 1034 1035 return template
1036 1037 #------------------------------------------------------------ 1095 1096 #------------------------------------------------------------
1097 -def configure_prescription_template(parent=None):
1098 1099 if parent is None: 1100 parent = wx.GetApp().GetTopWindow() 1101 1102 template = gmFormWidgets.manage_form_templates ( 1103 parent = parent, 1104 msg = _('Select the default prescription template:'), 1105 template_types = ['prescription', 'current medication list'] 1106 ) 1107 1108 if template is None: 1109 gmDispatcher.send(signal = 'statustext', msg = _('No prescription template configured.'), beep = True) 1110 return None 1111 1112 if template['engine'] not in ['L', 'X', 'T']: 1113 gmDispatcher.send(signal = 'statustext', msg = _('No prescription template configured.'), beep = True) 1114 return None 1115 1116 option = 'form_templates.prescription' 1117 dbcfg = gmCfg.cCfgSQL() 1118 dbcfg.set ( 1119 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1120 option = option, 1121 value = '%s - %s' % (template['name_long'], template['external_version']) 1122 ) 1123 1124 return template
1125 1126 #------------------------------------------------------------
1127 -def get_prescription_template(parent=None):
1128 1129 if parent is None: 1130 parent = wx.GetApp().GetTopWindow() 1131 1132 dbcfg = gmCfg.cCfgSQL() 1133 option = 'form_templates.prescription' 1134 template_name = dbcfg.get2 ( 1135 option = option, 1136 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1137 bias = 'user' 1138 ) 1139 1140 if template_name is None: 1141 template = configure_prescription_template(parent = parent) 1142 if template is None: 1143 gmGuiHelpers.gm_show_error ( 1144 aMessage = _('There is no prescription template configured.'), 1145 aTitle = _('Printing prescription') 1146 ) 1147 return None 1148 return template 1149 1150 try: 1151 name, ver = template_name.split(' - ') 1152 except: 1153 _log.exception('problem splitting prescription template name [%s]', template_name) 1154 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading prescription template.'), beep = True) 1155 return False 1156 template = gmForms.get_form_template(name_long = name, external_version = ver) 1157 if template is None: 1158 gmGuiHelpers.gm_show_error ( 1159 aMessage = _('Cannot load prescription template [%s - %s]') % (name, ver), 1160 aTitle = _('Printing prescription') 1161 ) 1162 return None 1163 return template
1164 1165 #------------------------------------------------------------ 1192 1193 #------------------------------------------------------------
1194 -def prescribe_drugs(parent=None, emr=None):
1195 1196 dbcfg = gmCfg.cCfgSQL() 1197 rx_mode = dbcfg.get2 ( 1198 option = 'horst_space.default_prescription_mode', 1199 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1200 bias = 'user', 1201 default = 'form' # set to 'database' to access database 1202 ) 1203 1204 if parent is None: 1205 parent = wx.GetApp().GetTopWindow() 1206 1207 if rx_mode == 'form': 1208 return print_prescription(parent = parent, emr = emr) 1209 1210 if rx_mode == 'database': 1211 drug_db = gmSubstanceMgmtWidgets.get_drug_database() #gmPerson.gmCurrentPatient() xxxxxxx ? 1212 if drug_db is None: 1213 return 1214 drug_db.reviewer = gmStaff.gmCurrentProvider() 1215 prescribed_drugs = drug_db.prescribe() 1216 update_substance_intake_list_from_prescription ( 1217 parent = parent, 1218 prescribed_drugs = prescribed_drugs, 1219 emr = emr 1220 )
1221 1222 #------------------------------------------------------------
1223 -def update_substance_intake_list_from_prescription(parent=None, prescribed_drugs=None, emr=None):
1224 1225 if len(prescribed_drugs) == 0: 1226 return 1227 1228 curr_meds = [ i['pk_drug_product'] for i in emr.get_current_medications() if i['pk_drug_product'] is not None ] 1229 new_drugs = [] 1230 for drug in prescribed_drugs: 1231 if drug['pk_drug_product'] not in curr_meds: 1232 new_drugs.append(drug) 1233 1234 if len(new_drugs) == 0: 1235 return 1236 1237 if parent is None: 1238 parent = wx.GetApp().GetTopWindow() 1239 1240 picker = gmListWidgets.cItemPickerDlg ( 1241 parent, 1242 -1, 1243 msg = _( 1244 'These products have been prescribed but are not listed\n' 1245 'in the current medication list of this patient.\n' 1246 '\n' 1247 'Please select those you want added to the medication list.' 1248 ) 1249 ) 1250 picker.set_columns ( 1251 columns = [_('Newly prescribed drugs')], 1252 columns_right = [_('Add to medication list')] 1253 ) 1254 choices = [ ('%s %s (%s)' % (d['product'], d['l10n_preparation'], '; '.join(d['components']))) for d in new_drugs ] 1255 picker.set_choices ( 1256 choices = choices, 1257 data = new_drugs 1258 ) 1259 picker.ShowModal() 1260 drugs2add = picker.get_picks() 1261 picker.Destroy() 1262 1263 if drugs2add is None: 1264 return 1265 1266 if len(drugs2add) == 0: 1267 return 1268 1269 for drug in drugs2add: 1270 # only add first component since all other components get added by a trigger ... 1271 intake = emr.add_substance_intake(pk_component = drug['components'][0]['pk_component']) 1272 if intake is None: 1273 continue 1274 intake['intake_is_approved_of'] = True 1275 intake.save() 1276 1277 return
1278 1279 #------------------------------------------------------------
1280 -class cCurrentSubstancesGrid(wx.grid.Grid):
1281 """A grid class for displaying current substance intake. 1282 1283 - does NOT listen to the currently active patient 1284 - thereby it can display any patient at any time 1285 """
1286 - def __init__(self, *args, **kwargs):
1287 1288 wx.grid.Grid.__init__(self, *args, **kwargs) 1289 1290 self.__patient = None 1291 self.__row_data = {} 1292 self.__prev_row = None 1293 self.__prev_tooltip_row = None 1294 self.__prev_cell_0 = None 1295 self.__grouping_mode = 'issue' 1296 self.__filter_show_unapproved = True 1297 self.__filter_show_inactive = True 1298 1299 self.__grouping2col_labels = { 1300 'issue': [ 1301 _('Health issue'), 1302 _('Substance'), 1303 _('Strength'), 1304 _('Schedule'), 1305 _('Timeframe'), 1306 _('Product'), 1307 _('Advice') 1308 ], 1309 'product': [ 1310 _('Product'), 1311 _('Schedule'), 1312 _('Substance'), 1313 _('Strength'), 1314 _('Timeframe'), 1315 _('Health issue'), 1316 _('Advice') 1317 ], 1318 'episode': [ 1319 _('Episode'), 1320 _('Substance'), 1321 _('Strength'), 1322 _('Schedule'), 1323 _('Timeframe'), 1324 _('Product'), 1325 _('Advice') 1326 ], 1327 'start': [ 1328 _('Episode'), 1329 _('Substance'), 1330 _('Strength'), 1331 _('Schedule'), 1332 _('Timeframe'), 1333 _('Product'), 1334 _('Advice') 1335 ], 1336 } 1337 1338 self.__grouping2order_by_clauses = { 1339 'issue': 'pk_health_issue NULLS FIRST, substance, started', 1340 'episode': 'pk_health_issue NULLS FIRST, episode, substance, started', 1341 'product': 'product NULLS LAST, substance, started', 1342 'start': 'started DESC, substance, episode' 1343 } 1344 1345 self.__init_ui() 1346 self.__register_events()
1347 1348 #------------------------------------------------------------ 1349 # external API 1350 #------------------------------------------------------------
1351 - def get_selected_cells(self):
1352 1353 sel_block_top_left = self.GetSelectionBlockTopLeft() 1354 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 1355 sel_cols = self.GetSelectedCols() 1356 sel_rows = self.GetSelectedRows() 1357 1358 selected_cells = [] 1359 1360 # individually selected cells (ctrl-click) 1361 selected_cells += self.GetSelectedCells() 1362 1363 # selected rows 1364 selected_cells += list ( 1365 (row, col) 1366 for row in sel_rows 1367 for col in range(self.GetNumberCols()) 1368 ) 1369 1370 # selected columns 1371 selected_cells += list ( 1372 (row, col) 1373 for row in range(self.GetNumberRows()) 1374 for col in sel_cols 1375 ) 1376 1377 # selection blocks 1378 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 1379 selected_cells += [ 1380 (row, col) 1381 for row in range(top_left[0], bottom_right[0] + 1) 1382 for col in range(top_left[1], bottom_right[1] + 1) 1383 ] 1384 1385 return set(selected_cells)
1386 1387 #------------------------------------------------------------
1388 - def get_selected_rows(self):
1389 rows = {} 1390 1391 for row, col in self.get_selected_cells(): 1392 rows[row] = True 1393 1394 return rows.keys()
1395 1396 #------------------------------------------------------------
1397 - def get_selected_data(self):
1398 return [ self.__row_data[row] for row in self.get_selected_rows() ]
1399 1400 #------------------------------------------------------------
1401 - def get_row_data(self):
1402 return self.__row_data.values()
1403 1404 #------------------------------------------------------------
1405 - def repopulate_grid(self):
1406 1407 self.empty_grid() 1408 1409 if self.__patient is None: 1410 return 1411 1412 emr = self.__patient.emr 1413 meds = emr.get_current_medications ( 1414 order_by = self.__grouping2order_by_clauses[self.__grouping_mode], 1415 include_unapproved = self.__filter_show_unapproved, 1416 include_inactive = self.__filter_show_inactive 1417 ) 1418 if not meds: 1419 return 1420 1421 self.BeginBatch() 1422 1423 # columns 1424 labels = self.__grouping2col_labels[self.__grouping_mode] 1425 if self.__filter_show_unapproved: 1426 self.AppendCols(numCols = len(labels) + 1) 1427 else: 1428 self.AppendCols(numCols = len(labels)) 1429 for col_idx in range(len(labels)): 1430 self.SetColLabelValue(col_idx, labels[col_idx]) 1431 if self.__filter_show_unapproved: 1432 #self.SetColLabelValue(len(labels), u'OK?') 1433 self.SetColLabelValue(len(labels), '') 1434 self.SetColSize(len(labels), 40) 1435 1436 self.AppendRows(numRows = len(meds)) 1437 1438 # loop over data 1439 for row_idx in range(len(meds)): 1440 med = meds[row_idx] 1441 self.__row_data[row_idx] = med 1442 1443 if med['is_currently_active'] is True: 1444 atcs = [] 1445 if med['atc_substance'] is not None: 1446 atcs.append(med['atc_substance']) 1447 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],)) 1448 if allg not in [None, False]: 1449 attr = self.GetOrCreateCellAttr(row_idx, 0) 1450 if allg['type'] == 'allergy': 1451 attr.SetTextColour('red') 1452 else: 1453 #attr.SetTextColour('yellow') # too light 1454 #attr.SetTextColour('pink') # too light 1455 #attr.SetTextColour('dark orange') # slightly better 1456 attr.SetTextColour('magenta') 1457 self.SetRowAttr(row_idx, attr) 1458 else: 1459 attr = self.GetOrCreateCellAttr(row_idx, 0) 1460 attr.SetTextColour('grey') 1461 self.SetRowAttr(row_idx, attr) 1462 1463 if self.__grouping_mode in ['episode', 'start']: 1464 if med['pk_episode'] is None: 1465 self.__prev_cell_0 = None 1466 epi = gmTools.u_diameter 1467 else: 1468 if self.__prev_cell_0 == med['episode']: 1469 epi = '' 1470 else: 1471 self.__prev_cell_0 = med['episode'] 1472 epi = gmTools.coalesce(med['episode'], '') 1473 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40)) 1474 1475 self.SetCellValue(row_idx, 1, med['substance']) 1476 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit'])) 1477 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], '')) 1478 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end) 1479 1480 if med['pk_drug_product'] is None: 1481 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation']) 1482 else: 1483 if med['is_fake_product']: 1484 product = '%s (%s)' % ( 1485 gmTools.coalesce(med['product'], '', _('%s <fake>')), 1486 med['l10n_preparation'] 1487 ) 1488 else: 1489 product = '%s (%s)' % ( 1490 gmTools.coalesce(med['product'], ''), 1491 med['l10n_preparation'] 1492 ) 1493 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35)) 1494 1495 elif self.__grouping_mode == 'issue': 1496 if med['pk_health_issue'] is None: 1497 self.__prev_cell_0 = None 1498 issue = '%s%s' % ( 1499 gmTools.u_diameter, 1500 gmTools.coalesce(med['episode'], '', ' (%s)') 1501 ) 1502 else: 1503 if self.__prev_cell_0 == med['health_issue']: 1504 issue = '' 1505 else: 1506 self.__prev_cell_0 = med['health_issue'] 1507 issue = med['health_issue'] 1508 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40)) 1509 1510 self.SetCellValue(row_idx, 1, med['substance']) 1511 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit'])) 1512 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], '')) 1513 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end) 1514 1515 if med['pk_drug_product'] is None: 1516 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation']) 1517 else: 1518 if med['is_fake_product']: 1519 product = '%s (%s)' % ( 1520 gmTools.coalesce(med['product'], '', _('%s <fake>')), 1521 med['l10n_preparation'] 1522 ) 1523 else: 1524 product = '%s (%s)' % ( 1525 gmTools.coalesce(med['product'], ''), 1526 med['l10n_preparation'] 1527 ) 1528 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35)) 1529 1530 elif self.__grouping_mode == 'product': 1531 1532 if med['pk_drug_product'] is None: 1533 self.__prev_cell_0 = None 1534 product = '%s (%s)' % ( 1535 gmTools.u_diameter, 1536 med['l10n_preparation'] 1537 ) 1538 else: 1539 if self.__prev_cell_0 == med['product']: 1540 product = '' 1541 else: 1542 self.__prev_cell_0 = med['product'] 1543 if med['is_fake_product']: 1544 product = '%s (%s)' % ( 1545 gmTools.coalesce(med['product'], '', _('%s <fake>')), 1546 med['l10n_preparation'] 1547 ) 1548 else: 1549 product = '%s (%s)' % ( 1550 gmTools.coalesce(med['product'], ''), 1551 med['l10n_preparation'] 1552 ) 1553 self.SetCellValue(row_idx, 0, gmTools.wrap(text = product, width = 35)) 1554 1555 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], '')) 1556 self.SetCellValue(row_idx, 2, med['substance']) 1557 self.SetCellValue(row_idx, 3, '%s %s' % (med['amount'], med['unit'])) 1558 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end) 1559 1560 if med['pk_health_issue'] is None: 1561 issue = '%s%s' % ( 1562 gmTools.u_diameter, 1563 gmTools.coalesce(med['episode'], '', ' (%s)') 1564 ) 1565 else: 1566 issue = gmTools.coalesce(med['health_issue'], '') 1567 self.SetCellValue(row_idx, 5, gmTools.wrap(text = issue, width = 40)) 1568 1569 else: 1570 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode) 1571 1572 if med['notes'] is not None: 1573 self.SetCellValue(row_idx, 6, gmTools.wrap(text = med['notes'], width = 50)) 1574 1575 if self.__filter_show_unapproved: 1576 self.SetCellValue ( 1577 row_idx, 1578 len(labels), 1579 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, gmTools.u_frowning_face, '?') 1580 ) 1581 font = self.GetCellFont(row_idx, len(labels)) 1582 font.SetPointSize(font.GetPointSize() + 2) 1583 self.SetCellFont(row_idx, len(labels), font) 1584 1585 #self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 1586 1587 self.AutoSize() 1588 self.EndBatch()
1589 #------------------------------------------------------------
1590 - def empty_grid(self):
1591 self.BeginBatch() 1592 self.ClearGrid() 1593 # Windows cannot do "nothing", it rather decides to assert() 1594 # on thinking it is supposed to do nothing 1595 if self.GetNumberRows() > 0: 1596 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 1597 if self.GetNumberCols() > 0: 1598 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 1599 self.EndBatch() 1600 self.__row_data = {} 1601 self.__prev_cell_0 = None
1602 1603 #------------------------------------------------------------
1604 - def show_info_on_entry(self):
1605 1606 if len(self.__row_data) == 0: 1607 return 1608 1609 sel_rows = self.get_selected_rows() 1610 if len(sel_rows) != 1: 1611 return 1612 1613 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient) 1614 if drug_db is None: 1615 return 1616 1617 intake = self.get_selected_data()[0] # just in case 1618 if intake['product'] is None: 1619 drug_db.show_info_on_substance(substance_intake = intake) 1620 else: 1621 drug_db.show_info_on_drug(substance_intake = intake)
1622 1623 #------------------------------------------------------------
1625 search_term = None 1626 if len(self.__row_data) > 0: 1627 sel_rows = self.get_selected_rows() 1628 if len(sel_rows) == 1: 1629 search_term = self.get_selected_data()[0] 1630 gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
1631 1632 #------------------------------------------------------------
1633 - def show_cardiac_info(self):
1635 1636 #------------------------------------------------------------
1637 - def report_ADR(self):
1638 dbcfg = gmCfg.cCfgSQL() 1639 url = dbcfg.get2 ( 1640 option = 'external.urls.report_ADR', 1641 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1642 bias = 'user', 1643 default = 'https://dcgma.org/uaw/meldung.php' # http://www.akdae.de/Arzneimittelsicherheit/UAW-Meldung/UAW-Meldung-online.html 1644 ) 1645 gmNetworkTools.open_url_in_browser(url = url)
1646 1647 #------------------------------------------------------------
1648 - def prescribe(self):
1649 prescribe_drugs ( 1650 parent = self, 1651 emr = self.__patient.emr 1652 )
1653 #------------------------------------------------------------
1654 - def check_interactions(self):
1655 1656 if len(self.__row_data) == 0: 1657 return 1658 1659 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient) 1660 if drug_db is None: 1661 return 1662 1663 if len(self.get_selected_rows()) > 1: 1664 drug_db.check_interactions(substance_intakes = self.get_selected_data()) 1665 else: 1666 drug_db.check_interactions(substance_intakes = self.__row_data.values())
1667 #------------------------------------------------------------
1668 - def add_substance(self):
1670 #------------------------------------------------------------
1671 - def edit_substance(self):
1672 1673 rows = self.get_selected_rows() 1674 1675 if len(rows) == 0: 1676 return 1677 1678 if len(rows) > 1: 1679 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit more than one substance at once.'), beep = True) 1680 return 1681 1682 subst = self.get_selected_data()[0] 1683 edit_intake_of_substance(parent = self, substance = subst)
1684 1685 #------------------------------------------------------------
1686 - def delete_intake(self):
1687 1688 rows = self.get_selected_rows() 1689 1690 if len(rows) == 0: 1691 return 1692 1693 if len(rows) > 1: 1694 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete more than one substance at once.'), beep = True) 1695 return 1696 1697 intake = self.get_selected_data()[0] 1698 delete_substance_intake(parent = self, intake = intake)
1699 1700 #------------------------------------------------------------
1702 rows = self.get_selected_rows() 1703 1704 if len(rows) == 0: 1705 return 1706 1707 if len(rows) > 1: 1708 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create allergy from more than one substance at once.'), beep = True) 1709 return 1710 1711 return turn_substance_intake_into_allergy ( 1712 parent = self, 1713 intake = self.get_selected_data()[0], 1714 emr = self.__patient.emr 1715 )
1716 #------------------------------------------------------------
1717 - def print_medication_list(self):
1718 # there could be some filtering/user interaction going on here 1719 print_medication_list(parent = self)
1720 #------------------------------------------------------------
1721 - def get_row_tooltip(self, row=None):
1722 1723 try: 1724 entry = self.__row_data[row] 1725 except KeyError: 1726 return ' ' 1727 1728 emr = self.__patient.emr 1729 atcs = [] 1730 if entry['atc_substance'] is not None: 1731 atcs.append(entry['atc_substance']) 1732 # if entry['atc_drug'] is not None: 1733 # atcs.append(entry['atc_drug']) 1734 # allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],), product_name = entry['product']) 1735 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],)) 1736 1737 tt = _('Substance intake entry (%s, %s) [#%s] \n') % ( 1738 gmTools.bool2subst ( 1739 boolean = entry['is_currently_active'], 1740 true_return = gmTools.bool2subst ( 1741 boolean = entry['seems_inactive'], 1742 true_return = _('active, needs check'), 1743 false_return = _('active'), 1744 none_return = _('assumed active') 1745 ), 1746 false_return = _('inactive') 1747 ), 1748 gmTools.bool2subst ( 1749 boolean = entry['intake_is_approved_of'], 1750 true_return = _('approved'), 1751 false_return = _('unapproved') 1752 ), 1753 entry['pk_substance_intake'] 1754 ) 1755 1756 if allg not in [None, False]: 1757 certainty = gmTools.bool2subst(allg['definite'], _('definite'), _('suspected')) 1758 tt += '\n' 1759 tt += ' !! ---- Cave ---- !!\n' 1760 tt += ' %s (%s): %s (%s)\n' % ( 1761 allg['l10n_type'], 1762 certainty, 1763 allg['descriptor'], 1764 gmTools.coalesce(allg['reaction'], '')[:40] 1765 ) 1766 tt += '\n' 1767 1768 tt += ' ' + _('Substance: %s [#%s]\n') % (entry['substance'], entry['pk_substance']) 1769 tt += ' ' + _('Preparation: %s\n') % entry['l10n_preparation'] 1770 tt += ' ' + _('Amount per dose: %s %s') % (entry['amount'], entry['unit']) 1771 tt += '\n' 1772 tt += gmTools.coalesce(entry['atc_substance'], '', _(' ATC (substance): %s\n')) 1773 1774 tt += '\n' 1775 1776 tt += gmTools.coalesce ( 1777 entry['product'], 1778 '', 1779 _(' Product name: %%s [#%s]\n') % entry['pk_drug_product'] 1780 ) 1781 tt += gmTools.coalesce(entry['atc_drug'], '', _(' ATC (drug): %s\n')) 1782 1783 tt += '\n' 1784 1785 tt += gmTools.coalesce(entry['schedule'], '', _(' Regimen: %s\n')) 1786 1787 if entry['is_long_term']: 1788 duration = ' %s %s' % (gmTools.u_arrow2right, gmTools.u_infinity) 1789 else: 1790 if entry['duration'] is None: 1791 duration = '' 1792 else: 1793 duration = ' %s %s' % (gmTools.u_arrow2right, gmDateTime.format_interval(entry['duration'], gmDateTime.acc_days)) 1794 1795 tt += _(' Started %s%s%s\n') % ( 1796 entry.medically_formatted_start, 1797 duration, 1798 gmTools.bool2subst(entry['is_long_term'], _(' (long-term)'), _(' (short-term)'), '') 1799 ) 1800 1801 if entry['discontinued'] is not None: 1802 tt += _(' Discontinued %s\n') % gmDateTime.pydt_strftime(entry['discontinued'], '%Y %b %d') 1803 tt += gmTools.coalesce(entry['discontinue_reason'], '', _(' Reason: %s\n')) 1804 1805 tt += '\n' 1806 1807 tt += gmTools.coalesce(entry['aim'], '', _(' Aim: %s\n')) 1808 tt += gmTools.coalesce(entry['episode'], '', _(' Episode: %s\n')) 1809 tt += gmTools.coalesce(entry['health_issue'], '', _(' Health issue: %s\n')) 1810 tt += gmTools.coalesce(entry['notes'], '', _(' Advice: %s\n')) 1811 1812 tt += '\n' 1813 1814 tt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % ({ 1815 'row_ver': entry['row_version'], 1816 'mod_when': gmDateTime.pydt_strftime(entry['modified_when'], '%Y %b %d %H:%M:%S'), 1817 'mod_by': entry['modified_by'] 1818 }) 1819 1820 return tt
1821 1822 #------------------------------------------------------------ 1823 # internal helpers 1824 #------------------------------------------------------------
1825 - def __init_ui(self):
1826 self.CreateGrid(0, 1) 1827 self.EnableEditing(0) 1828 self.EnableDragGridSize(1) 1829 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) 1830 1831 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER) 1832 1833 self.SetRowLabelSize(0) 1834 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1835 1836 #------------------------------------------------------------ 1837 # properties 1838 #------------------------------------------------------------
1839 - def _get_patient(self):
1840 return self.__patient
1841
1842 - def _set_patient(self, patient):
1843 self.__patient = patient 1844 self.repopulate_grid()
1845 1846 patient = property(_get_patient, _set_patient) 1847 #------------------------------------------------------------
1848 - def _get_grouping_mode(self):
1849 return self.__grouping_mode
1850
1851 - def _set_grouping_mode(self, mode):
1852 self.__grouping_mode = mode 1853 self.repopulate_grid()
1854 1855 grouping_mode = property(_get_grouping_mode, _set_grouping_mode) 1856 #------------------------------------------------------------
1858 return self.__filter_show_unapproved
1859
1860 - def _set_filter_show_unapproved(self, val):
1861 self.__filter_show_unapproved = val 1862 self.repopulate_grid()
1863 1864 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved) 1865 #------------------------------------------------------------
1866 - def _get_filter_show_inactive(self):
1867 return self.__filter_show_inactive
1868
1869 - def _set_filter_show_inactive(self, val):
1870 self.__filter_show_inactive = val 1871 self.repopulate_grid()
1872 1873 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive) 1874 #------------------------------------------------------------ 1875 # event handling 1876 #------------------------------------------------------------
1877 - def __register_events(self):
1878 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 1879 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 1880 #self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 1881 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 1882 1883 # editing cells 1884 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1885 #------------------------------------------------------------
1886 - def __on_mouse_over_cells(self, evt):
1887 """Calculate where the mouse is and set the tooltip dynamically.""" 1888 1889 # Use CalcUnscrolledPosition() to get the mouse position within the 1890 # entire grid including what's offscreen 1891 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 1892 1893 # use this logic to prevent tooltips outside the actual cells 1894 # apply to GetRowSize, too 1895 # tot = 0 1896 # for col in range(self.NumberCols): 1897 # tot += self.GetColSize(col) 1898 # if xpos <= tot: 1899 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 1900 # self.GetColLabelValue(col)) 1901 # break 1902 # else: # mouse is in label area beyond the right-most column 1903 # self.tool_tip.Tip = '' 1904 1905 row, col = self.XYToCell(x, y) 1906 1907 if row == self.__prev_tooltip_row: 1908 return 1909 1910 self.__prev_tooltip_row = row 1911 1912 try: 1913 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row)) 1914 except KeyError: 1915 pass
1916 #------------------------------------------------------------
1917 - def __on_cell_left_dclicked(self, evt):
1918 row = evt.GetRow() 1919 data = self.__row_data[row] 1920 edit_intake_of_substance(parent = self, substance = data)
1921 1922 #============================================================
1923 -def configure_default_medications_lab_panel(parent=None):
1924 1925 panels = gmPathLab.get_test_panels(order_by = 'description') 1926 gmCfgWidgets.configure_string_from_list_option ( 1927 parent = parent, 1928 message = _( 1929 '\n' 1930 'Select the measurements panel to show in the medications plugin.' 1931 '\n' 1932 ), 1933 option = 'horstspace.medications_plugin.lab_panel', 1934 bias = 'user', 1935 default_value = None, 1936 choices = [ '%s%s' % (p['description'], gmTools.coalesce(p['comment'], '', ' (%s)')) for p in panels ], 1937 columns = [_('Measurements panel')], 1938 data = [ p['pk_test_panel'] for p in panels ], 1939 caption = _('Configuring medications plugin measurements panel') 1940 )
1941 1942 #============================================================ 1943 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl 1944
1945 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1946 1947 """Panel holding a grid with current substances. Used as notebook page.""" 1948
1949 - def __init__(self, *args, **kwargs):
1950 1951 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs) 1952 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1953 1954 self.__grouping_choice_labels = [ 1955 {'label': _('Health issue'), 'data': 'issue'} , 1956 {'label': _('Drug product'), 'data': 'product'}, 1957 {'label': _('Episode'), 'data': 'episode'}, 1958 {'label': _('Started'), 'data': 'start'} 1959 ] 1960 self.__lab_panel = None 1961 1962 self.__init_ui() 1963 self.__register_interests()
1964 1965 #-----------------------------------------------------
1966 - def __init_ui(self):
1967 self._CHCE_grouping.Clear() 1968 for option in self.__grouping_choice_labels: 1969 self._CHCE_grouping.Append(option['label'], option['data']) 1970 self._CHCE_grouping.SetSelection(0) 1971 1972 tt = self._BTN_heart.GetToolTipText() 1973 try: 1974 self._BTN_heart.SetToolTip(tt % gmMedication.URL_long_qt) 1975 except TypeError: 1976 _log.exception('translation error: %s', tt) 1977 1978 tt = self._BTN_kidneys.GetToolTipText() 1979 try: 1980 self._BTN_kidneys.SetToolTip(tt % gmMedication.URL_renal_insufficiency) 1981 except TypeError: 1982 _log.exception('translation error: %s', tt)
1983 1984 #----------------------------------------------------- 1985 # reget-on-paint mixin API 1986 #-----------------------------------------------------
1987 - def _populate_with_data(self):
1988 """Populate cells with data from model.""" 1989 pat = gmPerson.gmCurrentPatient() 1990 if pat.connected: 1991 self._grid_substances.patient = pat 1992 self.__refresh_gfr(pat) 1993 self.__refresh_lab(patient = pat) 1994 else: 1995 self._grid_substances.patient = None 1996 self.__clear_gfr() 1997 self.__refresh_lab(patient = None) 1998 return True
1999 2000 #--------------------------------------------------------
2001 - def __refresh_lab(self, patient):
2002 2003 self._GSZR_lab.Clear(True) # also delete child windows 2004 self._HLINE_lab.Hide() 2005 2006 if patient is None: 2007 self.Layout() 2008 return 2009 2010 emr = patient.emr 2011 most_recent_results = {} 2012 2013 # get most recent results for "LOINCs to monitor" 2014 loincs2monitor = set() 2015 loincs2monitor_data = {} 2016 loinc_max_age = {} 2017 loinc_max_age_str = {} 2018 for intake in self._grid_substances.get_row_data(): 2019 for l in intake['loincs']: 2020 loincs2monitor.add(l['loinc']) 2021 loincs2monitor_data[l['loinc']] = { 2022 'substance': intake['substance'], 2023 'comment': l['comment'] 2024 } 2025 if l['max_age_in_secs'] is not None: 2026 try: 2027 if loinc_max_age[l['loinc']] > l['max_age_in_secs']: 2028 loinc_max_age[l['loinc']] = l['max_age_in_secs'] 2029 loinc_max_age_str[l['loinc']] = l['max_age_str'] 2030 except KeyError: 2031 loinc_max_age[l['loinc']] = l['max_age_in_secs'] 2032 loinc_max_age_str[l['loinc']] = l['max_age_str'] 2033 loincs2monitor_missing = loincs2monitor.copy() 2034 for loinc in loincs2monitor: 2035 result = emr.get_most_recent_results_in_loinc_group ( 2036 loincs = [loinc], 2037 no_of_results = 1, 2038 consider_meta_type = True 2039 ) 2040 if result is None: 2041 continue 2042 loincs2monitor_missing.remove(loinc) 2043 # make unique 2044 most_recent_results[result['pk_test_result']] = result 2045 2046 # get most recent results for "general medication monitoring lab panel" 2047 if self.__lab_panel is not None: 2048 for result in self.__lab_panel.get_most_recent_results ( 2049 pk_patient = patient.ID, 2050 order_by = 'unified_abbrev', 2051 group_by_meta_type = True 2052 ): 2053 try: loincs2monitor_missing.remove(result['loinc_tt']) 2054 except KeyError: pass 2055 try: loincs2monitor_missing.remove(result['loinc_meta']) 2056 except KeyError: pass 2057 # make unique 2058 most_recent_results[result['pk_test_result']] = result 2059 2060 # those need special treatment 2061 gfr = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1) 2062 crea = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1) 2063 edc = emr.EDC 2064 2065 # display EDC 2066 if edc is not None: 2067 if emr.EDC_is_fishy: 2068 lbl = wx.StaticText(self, -1, _('EDC (!?!):')) 2069 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d')) 2070 else: 2071 lbl = wx.StaticText(self, -1, _('EDC:')) 2072 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d')) 2073 lbl.SetForegroundColour('blue') 2074 szr = wx.BoxSizer(wx.HORIZONTAL) 2075 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2076 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2077 self._GSZR_lab.Add(szr) 2078 2079 # decide which among Crea or GFR to show 2080 if crea is None: 2081 gfr_3_months_older_than_crea = False 2082 if gfr is not None: 2083 most_recent_results = [gfr] + most_recent_results 2084 elif gfr is None: 2085 gfr_3_months_older_than_crea = True 2086 else: 2087 three_months = pydt.timedelta(weeks = 14) 2088 gfr_3_months_older_than_crea = (crea['clin_when'] - gfr['clin_when']) > three_months 2089 if not gfr_3_months_older_than_crea: 2090 most_recent_results = [gfr] + most_recent_results 2091 2092 # if GFR not found in most_recent_results or old, then calculate 2093 now = gmDateTime.pydt_now_here() 2094 if gfr_3_months_older_than_crea: 2095 calc = gmClinicalCalculator.cClinicalCalculator() 2096 calc.patient = patient 2097 gfr = calc.eGFR 2098 if gfr.numeric_value is None: 2099 gfr_msg = '?' 2100 else: 2101 gfr_msg = _('%.1f (%s ago)') % ( 2102 gfr.numeric_value, 2103 gmDateTime.format_interval_medically(now - gfr.date_valid) 2104 ) 2105 lbl = wx.StaticText(self, -1, _('eGFR:')) 2106 lbl.SetForegroundColour('blue') 2107 val = wx.StaticText(self, -1, gfr_msg) 2108 tts = [] 2109 for egfr in calc.eGFRs: 2110 if egfr.numeric_value is None: 2111 continue 2112 tts.append(egfr.format ( 2113 left_margin = 0, 2114 width = 50, 2115 eol = '\n', 2116 with_formula = False, 2117 with_warnings = True, 2118 with_variables = False, 2119 with_sub_results = False, 2120 return_list = False 2121 )) 2122 val.SetToolTip('\n'.join(tts)) 2123 szr = wx.BoxSizer(wx.HORIZONTAL) 2124 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2125 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2126 self._GSZR_lab.Add(szr) 2127 2128 # eventually add most-recent results from monitoring panel and substances monitoring 2129 for pk_result in most_recent_results: 2130 result = most_recent_results[pk_result] 2131 # test type 2132 lbl = wx.StaticText(self, -1, '%s:' % result['unified_abbrev']) 2133 lbl.SetForegroundColour('blue') 2134 # calculate test result 2135 indicate_attention = False 2136 if result.is_considered_abnormal: 2137 indicate_attention = True 2138 # calculate tooltip data 2139 max_age = None 2140 try: 2141 max_age = loinc_max_age[result['loinc_tt']] 2142 max_age_str = loinc_max_age_str[result['loinc_tt']] 2143 except KeyError: 2144 try: 2145 max_age = loinc_max_age[result['loinc_meta']] 2146 max_age_str = loinc_max_age_str[result['loinc_meta']] 2147 except KeyError: 2148 pass 2149 subst2monitor = None 2150 try: 2151 subst2monitor = loincs2monitor_data[result['loinc_tt']]['substance'] 2152 except KeyError: 2153 try: 2154 subst2monitor = loincs2monitor_data[result['loinc_meta']]['substance'] 2155 except KeyError: 2156 pass 2157 monitor_comment = None 2158 try: 2159 monitor_comment = loincs2monitor_data[result['loinc_tt']]['comment'] 2160 except KeyError: 2161 try: 2162 monitor_comment = loincs2monitor_data[result['loinc_meta']]['comment'] 2163 except KeyError: 2164 pass 2165 result_age = now - result['clin_when'] 2166 unhappy_reasons = [] 2167 if result.is_considered_abnormal: 2168 indicator = result.formatted_abnormality_indicator 2169 if indicator == '': 2170 unhappy_reasons.append(_(' - abnormal')) 2171 else: 2172 unhappy_reasons.append(_(' - abnormal: %s') % indicator) 2173 if max_age is not None: 2174 if result_age.total_seconds() > max_age: 2175 unhappy_reasons.append(_(' - too old: %s ago (max: %s)') % ( 2176 gmDateTime.format_interval_medically(result_age), 2177 max_age_str 2178 )) 2179 # generate tooltip 2180 tt = [_('Most recent: %s ago') % gmDateTime.format_interval_medically(result_age)] 2181 if subst2monitor is not None: 2182 tt.append(_('Why monitor: %s') % subst2monitor) 2183 if monitor_comment is not None: 2184 tt.append(' %s' % monitor_comment) 2185 if len(unhappy_reasons) > 0: 2186 indicate_attention = True 2187 tt.append(_('Problems:')) 2188 tt.extend(unhappy_reasons) 2189 tt = '%s\n\n%s' % ( 2190 '\n'.join(tt), 2191 result.format() 2192 ) 2193 # set test result and tooltip 2194 val = wx.StaticText(self, -1, '%s%s%s' % ( 2195 result['unified_val'], 2196 gmTools.coalesce(result['val_unit'], '', ' %s'), 2197 gmTools.bool2subst(indicate_attention, gmTools.u_frowning_face, '', '') 2198 )) 2199 val.SetToolTip(tt) 2200 if result.is_considered_abnormal: 2201 val.SetForegroundColour('red') 2202 szr = wx.BoxSizer(wx.HORIZONTAL) 2203 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2204 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2205 self._GSZR_lab.Add(szr) 2206 2207 # hint at missing, but required results (set to be 2208 # monitored under intakes based on LOINCs): 2209 for loinc in loincs2monitor_missing: 2210 #szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2211 loinc_data = gmLOINC.loinc2data(loinc) 2212 if loinc_data is None: 2213 loinc_str = loinc 2214 else: 2215 loinc_str = loinc_data['term'] 2216 val = wx.StaticText(self, -1, '%s!' % loinc_str) 2217 tt = [ 2218 _('No test result for: %s (%s)') % (loinc_str, loinc), 2219 '', 2220 _('Why monitor: %s' % loincs2monitor_data[loinc]['substance']) 2221 ] 2222 try: 2223 tt.append(' %s' % loincs2monitor_data[loinc]['comment']) 2224 except KeyError: 2225 pass 2226 val.SetToolTip('\n'.join(tt)) 2227 val.SetForegroundColour('orange') 2228 szr = wx.BoxSizer(wx.HORIZONTAL) 2229 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2230 self._GSZR_lab.Add(szr) 2231 2232 self._HLINE_lab.Show() 2233 self.Layout()
2234 2235 #--------------------------------------------------------
2236 - def __refresh_gfr(self, patient):
2237 gfr = patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1) 2238 if gfr is None: 2239 calc = gmClinicalCalculator.cClinicalCalculator() 2240 calc.patient = patient 2241 gfr = calc.eGFR 2242 if gfr.numeric_value is None: 2243 msg = _('GFR: ?') 2244 tt = gfr.message 2245 else: 2246 msg = _('eGFR: %.1f (%s)') % ( 2247 gfr.numeric_value, 2248 gmDateTime.pydt_strftime ( 2249 gfr.date_valid, 2250 format = '%b %Y' 2251 ) 2252 ) 2253 egfrs = calc.eGFRs 2254 tts = [] 2255 for egfr in egfrs: 2256 if egfr.numeric_value is None: 2257 continue 2258 tts.append(egfr.format ( 2259 left_margin = 0, 2260 width = 50, 2261 eol = '\n', 2262 with_formula = False, 2263 with_warnings = True, 2264 with_variables = False, 2265 with_sub_results = False, 2266 return_list = False 2267 )) 2268 tt = '\n'.join(tts) 2269 else: 2270 msg = '%s: %s %s (%s)\n' % ( 2271 gfr['unified_abbrev'], 2272 gfr['unified_val'], 2273 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'), 2274 gmDateTime.pydt_strftime ( 2275 gfr['clin_when'], 2276 format = '%b %Y' 2277 ) 2278 ) 2279 tt = _('GFR reported by path lab') 2280 2281 self._LBL_gfr.SetLabel(msg) 2282 self._LBL_gfr.SetToolTip(tt) 2283 self._LBL_gfr.Refresh() 2284 self.Layout()
2285 2286 #--------------------------------------------------------
2287 - def __clear_gfr(self):
2288 self._LBL_gfr.SetLabel(_('GFR: ?')) 2289 self._LBL_gfr.Refresh() 2290 self.Layout()
2291 2292 #-------------------------------------------------------- 2293 # event handling 2294 #--------------------------------------------------------
2295 - def __register_interests(self):
2296 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 2297 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 2298 gmDispatcher.connect(signal = 'clin.substance_intake_mod_db', receiver = self._schedule_data_reget) 2299 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._on_test_result_mod)
2300 2301 #--------------------------------------------------------
2302 - def _on_test_result_mod(self):
2303 self.__refresh_lab(patient = self._grid_substances.patient)
2304 2305 #--------------------------------------------------------
2307 dbcfg = gmCfg.cCfgSQL() 2308 pk_panel = dbcfg.get2 ( 2309 option = 'horstspace.medications_plugin.lab_panel', 2310 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2311 bias = 'user' 2312 ) 2313 if pk_panel is None: 2314 self.__lab_panel = None 2315 else: 2316 self.__lab_panel = gmPathLab.cTestPanel(aPK_obj = pk_panel) 2317 self._grid_substances.patient = None 2318 self.__refresh_lab(patient = None)
2319 #--------------------------------------------------------
2320 - def _on_post_patient_selection(self):
2321 self._schedule_data_reget()
2322 #--------------------------------------------------------
2323 - def _on_add_button_pressed(self, event):
2324 self._grid_substances.add_substance()
2325 #--------------------------------------------------------
2326 - def _on_edit_button_pressed(self, event):
2327 self._grid_substances.edit_substance()
2328 #--------------------------------------------------------
2329 - def _on_delete_button_pressed(self, event):
2330 self._grid_substances.delete_intake()
2331 #--------------------------------------------------------
2332 - def _on_info_button_pressed(self, event):
2333 self._grid_substances.show_info_on_entry()
2334 #--------------------------------------------------------
2335 - def _on_interactions_button_pressed(self, event):
2336 self._grid_substances.check_interactions()
2337 #--------------------------------------------------------
2338 - def _on_grouping_selected(self, event):
2339 event.Skip() 2340 selected_item_idx = self._CHCE_grouping.GetSelection() 2341 if selected_item_idx is wx.NOT_FOUND: 2342 return 2343 self._grid_substances.grouping_mode = self._CHCE_grouping.GetClientData(selected_item_idx)
2344 #--------------------------------------------------------
2345 - def _on_show_unapproved_checked(self, event):
2346 self._grid_substances.filter_show_unapproved = self._CHBOX_show_unapproved.GetValue()
2347 #--------------------------------------------------------
2348 - def _on_show_inactive_checked(self, event):
2349 self._grid_substances.filter_show_inactive = self._CHBOX_show_inactive.GetValue()
2350 #--------------------------------------------------------
2351 - def _on_print_button_pressed(self, event):
2352 self._grid_substances.print_medication_list()
2353 #--------------------------------------------------------
2354 - def _on_allergy_button_pressed(self, event):
2355 self._grid_substances.create_allergy_from_substance()
2356 #--------------------------------------------------------
2357 - def _on_button_kidneys_pressed(self, event):
2358 self._grid_substances.show_renal_insufficiency_info()
2359 #--------------------------------------------------------
2360 - def _on_button_heart_pressed(self, event):
2361 self._grid_substances.show_cardiac_info()
2362 #--------------------------------------------------------
2363 - def _on_adr_button_pressed(self, event):
2364 self._grid_substances.report_ADR()
2365 #--------------------------------------------------------
2366 - def _on_rx_button_pressed(self, event):
2367 self._grid_substances.prescribe()
2368 2369 #============================================================ 2370 # main 2371 #------------------------------------------------------------ 2372 if __name__ == '__main__': 2373 2374 if len(sys.argv) < 2: 2375 sys.exit() 2376 2377 if sys.argv[1] != 'test': 2378 sys.exit() 2379 2380 from Gnumed.business import gmPersonSearch 2381 2382 pat = gmPersonSearch.ask_for_patient() 2383 if pat is None: 2384 sys.exit() 2385 gmPerson.set_active_patient(patient = pat) 2386 2387 #---------------------------------------- 2388 app = wx.PyWidgetTester(size = (600, 300)) 2389 app.SetWidget(cSubstanceIntakeObjectPhraseWheel, -1) 2390 app.MainLoop() 2391 #manage_substance_intakes() 2392