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