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 Exception: 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 for row, col in self.get_selected_cells(): 1392 rows[row] = True 1393 return list(rows)
1394 1395 #------------------------------------------------------------
1396 - def get_selected_data(self):
1397 return [ self.__row_data[row] for row in self.get_selected_rows() ]
1398 1399 #------------------------------------------------------------
1400 - def get_row_data(self):
1401 return self.__row_data.values()
1402 1403 #------------------------------------------------------------
1404 - def repopulate_grid(self):
1405 1406 self.empty_grid() 1407 1408 if self.__patient is None: 1409 return 1410 1411 emr = self.__patient.emr 1412 meds = emr.get_current_medications ( 1413 order_by = self.__grouping2order_by_clauses[self.__grouping_mode], 1414 include_unapproved = self.__filter_show_unapproved, 1415 include_inactive = self.__filter_show_inactive 1416 ) 1417 if not meds: 1418 return 1419 1420 self.BeginBatch() 1421 1422 # columns 1423 labels = self.__grouping2col_labels[self.__grouping_mode] 1424 if self.__filter_show_unapproved: 1425 self.AppendCols(numCols = len(labels) + 1) 1426 else: 1427 self.AppendCols(numCols = len(labels)) 1428 for col_idx in range(len(labels)): 1429 self.SetColLabelValue(col_idx, labels[col_idx]) 1430 if self.__filter_show_unapproved: 1431 #self.SetColLabelValue(len(labels), u'OK?') 1432 self.SetColLabelValue(len(labels), '') 1433 self.SetColSize(len(labels), 40) 1434 1435 self.AppendRows(numRows = len(meds)) 1436 1437 # loop over data 1438 for row_idx in range(len(meds)): 1439 med = meds[row_idx] 1440 self.__row_data[row_idx] = med 1441 1442 if med['is_currently_active'] is True: 1443 atcs = [] 1444 if med['atc_substance'] is not None: 1445 atcs.append(med['atc_substance']) 1446 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],)) 1447 if allg not in [None, False]: 1448 attr = self.GetOrCreateCellAttr(row_idx, 0) 1449 if allg['type'] == 'allergy': 1450 attr.SetTextColour('red') 1451 else: 1452 #attr.SetTextColour('yellow') # too light 1453 #attr.SetTextColour('pink') # too light 1454 #attr.SetTextColour('dark orange') # slightly better 1455 attr.SetTextColour('magenta') 1456 self.SetRowAttr(row_idx, attr) 1457 else: 1458 attr = self.GetOrCreateCellAttr(row_idx, 0) 1459 attr.SetTextColour('grey') 1460 self.SetRowAttr(row_idx, attr) 1461 1462 if self.__grouping_mode in ['episode', 'start']: 1463 if med['pk_episode'] is None: 1464 self.__prev_cell_0 = None 1465 epi = gmTools.u_diameter 1466 else: 1467 if self.__prev_cell_0 == med['episode']: 1468 epi = '' 1469 else: 1470 self.__prev_cell_0 = med['episode'] 1471 epi = gmTools.coalesce(med['episode'], '') 1472 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40)) 1473 1474 self.SetCellValue(row_idx, 1, med['substance']) 1475 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit'])) 1476 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], '')) 1477 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end) 1478 1479 if med['pk_drug_product'] is None: 1480 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation']) 1481 else: 1482 if med['is_fake_product']: 1483 product = '%s (%s)' % ( 1484 gmTools.coalesce(med['product'], '', _('%s <fake>')), 1485 med['l10n_preparation'] 1486 ) 1487 else: 1488 product = '%s (%s)' % ( 1489 gmTools.coalesce(med['product'], ''), 1490 med['l10n_preparation'] 1491 ) 1492 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35)) 1493 1494 elif self.__grouping_mode == 'issue': 1495 if med['pk_health_issue'] is None: 1496 self.__prev_cell_0 = None 1497 issue = '%s%s' % ( 1498 gmTools.u_diameter, 1499 gmTools.coalesce(med['episode'], '', ' (%s)') 1500 ) 1501 else: 1502 if self.__prev_cell_0 == med['health_issue']: 1503 issue = '' 1504 else: 1505 self.__prev_cell_0 = med['health_issue'] 1506 issue = med['health_issue'] 1507 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40)) 1508 1509 self.SetCellValue(row_idx, 1, med['substance']) 1510 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit'])) 1511 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], '')) 1512 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end) 1513 1514 if med['pk_drug_product'] is None: 1515 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation']) 1516 else: 1517 if med['is_fake_product']: 1518 product = '%s (%s)' % ( 1519 gmTools.coalesce(med['product'], '', _('%s <fake>')), 1520 med['l10n_preparation'] 1521 ) 1522 else: 1523 product = '%s (%s)' % ( 1524 gmTools.coalesce(med['product'], ''), 1525 med['l10n_preparation'] 1526 ) 1527 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35)) 1528 1529 elif self.__grouping_mode == 'product': 1530 1531 if med['pk_drug_product'] is None: 1532 self.__prev_cell_0 = None 1533 product = '%s (%s)' % ( 1534 gmTools.u_diameter, 1535 med['l10n_preparation'] 1536 ) 1537 else: 1538 if self.__prev_cell_0 == med['product']: 1539 product = '' 1540 else: 1541 self.__prev_cell_0 = med['product'] 1542 if med['is_fake_product']: 1543 product = '%s (%s)' % ( 1544 gmTools.coalesce(med['product'], '', _('%s <fake>')), 1545 med['l10n_preparation'] 1546 ) 1547 else: 1548 product = '%s (%s)' % ( 1549 gmTools.coalesce(med['product'], ''), 1550 med['l10n_preparation'] 1551 ) 1552 self.SetCellValue(row_idx, 0, gmTools.wrap(text = product, width = 35)) 1553 1554 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], '')) 1555 self.SetCellValue(row_idx, 2, med['substance']) 1556 self.SetCellValue(row_idx, 3, '%s %s' % (med['amount'], med['unit'])) 1557 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end) 1558 1559 if med['pk_health_issue'] is None: 1560 issue = '%s%s' % ( 1561 gmTools.u_diameter, 1562 gmTools.coalesce(med['episode'], '', ' (%s)') 1563 ) 1564 else: 1565 issue = gmTools.coalesce(med['health_issue'], '') 1566 self.SetCellValue(row_idx, 5, gmTools.wrap(text = issue, width = 40)) 1567 1568 else: 1569 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode) 1570 1571 if med['notes'] is not None: 1572 self.SetCellValue(row_idx, 6, gmTools.wrap(text = med['notes'], width = 50)) 1573 1574 if self.__filter_show_unapproved: 1575 self.SetCellValue ( 1576 row_idx, 1577 len(labels), 1578 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, gmTools.u_frowning_face, '?') 1579 ) 1580 font = self.GetCellFont(row_idx, len(labels)) 1581 font.SetPointSize(font.GetPointSize() + 2) 1582 self.SetCellFont(row_idx, len(labels), font) 1583 1584 #self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 1585 1586 self.AutoSize() 1587 self.EndBatch()
1588 #------------------------------------------------------------
1589 - def empty_grid(self):
1590 self.BeginBatch() 1591 self.ClearGrid() 1592 # Windows cannot do "nothing", it rather decides to assert() 1593 # on thinking it is supposed to do nothing 1594 if self.GetNumberRows() > 0: 1595 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 1596 if self.GetNumberCols() > 0: 1597 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 1598 self.EndBatch() 1599 self.__row_data = {} 1600 self.__prev_cell_0 = None
1601 1602 #------------------------------------------------------------
1603 - def show_info_on_entry(self):
1604 1605 if len(self.__row_data) == 0: 1606 return 1607 1608 sel_rows = self.get_selected_rows() 1609 if len(sel_rows) != 1: 1610 return 1611 1612 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient) 1613 if drug_db is None: 1614 return 1615 1616 intake = self.get_selected_data()[0] # just in case 1617 if intake['product'] is None: 1618 drug_db.show_info_on_substance(substance_intake = intake) 1619 else: 1620 drug_db.show_info_on_drug(substance_intake = intake)
1621 1622 #------------------------------------------------------------
1624 search_term = None 1625 if len(self.__row_data) > 0: 1626 sel_rows = self.get_selected_rows() 1627 if len(sel_rows) == 1: 1628 search_term = self.get_selected_data()[0] 1629 gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
1630 1631 #------------------------------------------------------------
1632 - def show_cardiac_info(self):
1634 1635 #------------------------------------------------------------
1636 - def report_ADR(self):
1637 dbcfg = gmCfg.cCfgSQL() 1638 url = dbcfg.get2 ( 1639 option = 'external.urls.report_ADR', 1640 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1641 bias = 'user', 1642 default = gmMedication.URL_drug_adr_german_default 1643 ) 1644 gmNetworkTools.open_url_in_browser(url = url)
1645 1646 #------------------------------------------------------------
1647 - def prescribe(self):
1648 prescribe_drugs ( 1649 parent = self, 1650 emr = self.__patient.emr 1651 )
1652 #------------------------------------------------------------
1653 - def check_interactions(self):
1654 1655 if len(self.__row_data) == 0: 1656 return 1657 1658 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient) 1659 if drug_db is None: 1660 return 1661 1662 if len(self.get_selected_rows()) > 1: 1663 drug_db.check_interactions(substance_intakes = self.get_selected_data()) 1664 else: 1665 drug_db.check_interactions(substance_intakes = self.__row_data.values())
1666 #------------------------------------------------------------
1667 - def add_substance(self):
1669 #------------------------------------------------------------
1670 - def edit_substance(self):
1671 1672 rows = self.get_selected_rows() 1673 1674 if len(rows) == 0: 1675 return 1676 1677 if len(rows) > 1: 1678 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit more than one substance at once.'), beep = True) 1679 return 1680 1681 subst = self.get_selected_data()[0] 1682 edit_intake_of_substance(parent = self, substance = subst)
1683 1684 #------------------------------------------------------------
1685 - def delete_intake(self):
1686 1687 rows = self.get_selected_rows() 1688 1689 if len(rows) == 0: 1690 return 1691 1692 if len(rows) > 1: 1693 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete more than one substance at once.'), beep = True) 1694 return 1695 1696 intake = self.get_selected_data()[0] 1697 delete_substance_intake(parent = self, intake = intake)
1698 1699 #------------------------------------------------------------
1701 rows = self.get_selected_rows() 1702 1703 if len(rows) == 0: 1704 return 1705 1706 if len(rows) > 1: 1707 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create allergy from more than one substance at once.'), beep = True) 1708 return 1709 1710 return turn_substance_intake_into_allergy ( 1711 parent = self, 1712 intake = self.get_selected_data()[0], 1713 emr = self.__patient.emr 1714 )
1715 #------------------------------------------------------------
1716 - def print_medication_list(self):
1717 # there could be some filtering/user interaction going on here 1718 print_medication_list(parent = self)
1719 #------------------------------------------------------------
1720 - def get_row_tooltip(self, row=None):
1721 1722 try: 1723 entry = self.__row_data[row] 1724 except KeyError: 1725 return ' ' 1726 1727 emr = self.__patient.emr 1728 atcs = [] 1729 if entry['atc_substance'] is not None: 1730 atcs.append(entry['atc_substance']) 1731 # if entry['atc_drug'] is not None: 1732 # atcs.append(entry['atc_drug']) 1733 # allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],), product_name = entry['product']) 1734 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],)) 1735 1736 tt = _('Substance intake entry (%s, %s) [#%s] \n') % ( 1737 gmTools.bool2subst ( 1738 boolean = entry['is_currently_active'], 1739 true_return = gmTools.bool2subst ( 1740 boolean = entry['seems_inactive'], 1741 true_return = _('active, needs check'), 1742 false_return = _('active'), 1743 none_return = _('assumed active') 1744 ), 1745 false_return = _('inactive') 1746 ), 1747 gmTools.bool2subst ( 1748 boolean = entry['intake_is_approved_of'], 1749 true_return = _('approved'), 1750 false_return = _('unapproved') 1751 ), 1752 entry['pk_substance_intake'] 1753 ) 1754 1755 if allg not in [None, False]: 1756 certainty = gmTools.bool2subst(allg['definite'], _('definite'), _('suspected')) 1757 tt += '\n' 1758 tt += ' !! ---- Cave ---- !!\n' 1759 tt += ' %s (%s): %s (%s)\n' % ( 1760 allg['l10n_type'], 1761 certainty, 1762 allg['descriptor'], 1763 gmTools.coalesce(allg['reaction'], '')[:40] 1764 ) 1765 tt += '\n' 1766 1767 tt += ' ' + _('Substance: %s [#%s]\n') % (entry['substance'], entry['pk_substance']) 1768 tt += ' ' + _('Preparation: %s\n') % entry['l10n_preparation'] 1769 tt += ' ' + _('Amount per dose: %s %s') % (entry['amount'], entry['unit']) 1770 tt += '\n' 1771 tt += gmTools.coalesce(entry['atc_substance'], '', _(' ATC (substance): %s\n')) 1772 1773 tt += '\n' 1774 1775 tt += gmTools.coalesce ( 1776 entry['product'], 1777 '', 1778 _(' Product name: %%s [#%s]\n') % entry['pk_drug_product'] 1779 ) 1780 tt += gmTools.coalesce(entry['atc_drug'], '', _(' ATC (drug): %s\n')) 1781 1782 tt += '\n' 1783 1784 tt += gmTools.coalesce(entry['schedule'], '', _(' Regimen: %s\n')) 1785 1786 if entry['is_long_term']: 1787 duration = ' %s %s' % (gmTools.u_arrow2right, gmTools.u_infinity) 1788 else: 1789 if entry['duration'] is None: 1790 duration = '' 1791 else: 1792 duration = ' %s %s' % (gmTools.u_arrow2right, gmDateTime.format_interval(entry['duration'], gmDateTime.acc_days)) 1793 1794 tt += _(' Started %s%s%s\n') % ( 1795 entry.medically_formatted_start, 1796 duration, 1797 gmTools.bool2subst(entry['is_long_term'], _(' (long-term)'), _(' (short-term)'), '') 1798 ) 1799 1800 if entry['discontinued'] is not None: 1801 tt += _(' Discontinued %s\n') % gmDateTime.pydt_strftime(entry['discontinued'], '%Y %b %d') 1802 tt += gmTools.coalesce(entry['discontinue_reason'], '', _(' Reason: %s\n')) 1803 1804 tt += '\n' 1805 1806 tt += gmTools.coalesce(entry['aim'], '', _(' Aim: %s\n')) 1807 tt += gmTools.coalesce(entry['episode'], '', _(' Episode: %s\n')) 1808 tt += gmTools.coalesce(entry['health_issue'], '', _(' Health issue: %s\n')) 1809 tt += gmTools.coalesce(entry['notes'], '', _(' Advice: %s\n')) 1810 1811 tt += '\n' 1812 1813 tt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % ({ 1814 'row_ver': entry['row_version'], 1815 'mod_when': gmDateTime.pydt_strftime(entry['modified_when'], '%Y %b %d %H:%M:%S'), 1816 'mod_by': entry['modified_by'] 1817 }) 1818 1819 return tt
1820 1821 #------------------------------------------------------------ 1822 # internal helpers 1823 #------------------------------------------------------------
1824 - def __init_ui(self):
1825 self.CreateGrid(0, 1) 1826 self.EnableEditing(0) 1827 self.EnableDragGridSize(1) 1828 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) 1829 1830 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER) 1831 1832 self.SetRowLabelSize(0) 1833 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1834 1835 #------------------------------------------------------------ 1836 # properties 1837 #------------------------------------------------------------
1838 - def _get_patient(self):
1839 return self.__patient
1840
1841 - def _set_patient(self, patient):
1842 self.__patient = patient 1843 self.repopulate_grid()
1844 1845 patient = property(_get_patient, _set_patient) 1846 #------------------------------------------------------------
1847 - def _get_grouping_mode(self):
1848 return self.__grouping_mode
1849
1850 - def _set_grouping_mode(self, mode):
1851 self.__grouping_mode = mode 1852 self.repopulate_grid()
1853 1854 grouping_mode = property(_get_grouping_mode, _set_grouping_mode) 1855 #------------------------------------------------------------
1857 return self.__filter_show_unapproved
1858
1859 - def _set_filter_show_unapproved(self, val):
1860 self.__filter_show_unapproved = val 1861 self.repopulate_grid()
1862 1863 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved) 1864 #------------------------------------------------------------
1865 - def _get_filter_show_inactive(self):
1866 return self.__filter_show_inactive
1867
1868 - def _set_filter_show_inactive(self, val):
1869 self.__filter_show_inactive = val 1870 self.repopulate_grid()
1871 1872 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive) 1873 #------------------------------------------------------------ 1874 # event handling 1875 #------------------------------------------------------------
1876 - def __register_events(self):
1877 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 1878 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 1879 #self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 1880 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 1881 1882 # editing cells 1883 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1884 #------------------------------------------------------------
1885 - def __on_mouse_over_cells(self, evt):
1886 """Calculate where the mouse is and set the tooltip dynamically.""" 1887 1888 # Use CalcUnscrolledPosition() to get the mouse position within the 1889 # entire grid including what's offscreen 1890 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 1891 1892 # use this logic to prevent tooltips outside the actual cells 1893 # apply to GetRowSize, too 1894 # tot = 0 1895 # for col in range(self.NumberCols): 1896 # tot += self.GetColSize(col) 1897 # if xpos <= tot: 1898 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 1899 # self.GetColLabelValue(col)) 1900 # break 1901 # else: # mouse is in label area beyond the right-most column 1902 # self.tool_tip.Tip = '' 1903 1904 row, col = self.XYToCell(x, y) 1905 1906 if row == self.__prev_tooltip_row: 1907 return 1908 1909 self.__prev_tooltip_row = row 1910 1911 try: 1912 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row)) 1913 except KeyError: 1914 pass
1915 #------------------------------------------------------------
1916 - def __on_cell_left_dclicked(self, evt):
1917 row = evt.GetRow() 1918 data = self.__row_data[row] 1919 edit_intake_of_substance(parent = self, substance = data)
1920 1921 #============================================================
1922 -def configure_default_medications_lab_panel(parent=None):
1923 1924 panels = gmPathLab.get_test_panels(order_by = 'description') 1925 gmCfgWidgets.configure_string_from_list_option ( 1926 parent = parent, 1927 message = _( 1928 '\n' 1929 'Select the measurements panel to show in the medications plugin.' 1930 '\n' 1931 ), 1932 option = 'horstspace.medications_plugin.lab_panel', 1933 bias = 'user', 1934 default_value = None, 1935 choices = [ '%s%s' % (p['description'], gmTools.coalesce(p['comment'], '', ' (%s)')) for p in panels ], 1936 columns = [_('Measurements panel')], 1937 data = [ p['pk_test_panel'] for p in panels ], 1938 caption = _('Configuring medications plugin measurements panel') 1939 )
1940 1941 #============================================================
1942 -def configure_adr_url():
1943 1944 def is_valid(value): 1945 value = value.strip() 1946 if value == '': 1947 return True, gmMedication.URL_drug_adr_german_default 1948 try: 1949 urllib.request.urlopen(value) 1950 return True, value 1951 except Exception: 1952 return True, value
1953 1954 gmCfgWidgets.configure_string_option ( 1955 message = _( 1956 'GNUmed will use this URL to access a website which lets\n' 1957 'you report an adverse drug reaction (ADR).\n' 1958 '\n' 1959 'If you leave this empty it will fall back\n' 1960 'to an URL for reporting ADRs in Germany.' 1961 ), 1962 option = 'external.urls.report_ADR', 1963 bias = 'user', 1964 default_value = gmMedication.URL_drug_adr_german_default, 1965 validator = is_valid 1966 ) 1967 1968 #============================================================ 1969 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl 1970
1971 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1972 1973 """Panel holding a grid with current substances. Used as notebook page.""" 1974
1975 - def __init__(self, *args, **kwargs):
1976 1977 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs) 1978 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1979 1980 self.__grouping_choice_labels = [ 1981 {'label': _('Health issue'), 'data': 'issue'} , 1982 {'label': _('Drug product'), 'data': 'product'}, 1983 {'label': _('Episode'), 'data': 'episode'}, 1984 {'label': _('Started'), 'data': 'start'} 1985 ] 1986 self.__lab_panel = None 1987 1988 self.__init_ui() 1989 self.__register_interests()
1990 1991 #-----------------------------------------------------
1992 - def __init_ui(self):
1993 self._CHCE_grouping.Clear() 1994 for option in self.__grouping_choice_labels: 1995 self._CHCE_grouping.Append(option['label'], option['data']) 1996 self._CHCE_grouping.SetSelection(0) 1997 1998 tt = self._BTN_heart.GetToolTipText() 1999 try: 2000 self._BTN_heart.SetToolTip(tt % gmMedication.URL_long_qt) 2001 except TypeError: 2002 _log.exception('translation error: %s', tt) 2003 2004 tt = self._BTN_kidneys.GetToolTipText() 2005 try: 2006 self._BTN_kidneys.SetToolTip(tt % gmMedication.URL_renal_insufficiency) 2007 except TypeError: 2008 _log.exception('translation error: %s', tt)
2009 2010 #----------------------------------------------------- 2011 # reget-on-paint mixin API 2012 #-----------------------------------------------------
2013 - def _populate_with_data(self):
2014 """Populate cells with data from model.""" 2015 pat = gmPerson.gmCurrentPatient() 2016 if pat.connected: 2017 self._grid_substances.patient = pat 2018 self.__refresh_gfr(pat) 2019 self.__refresh_lab(patient = pat) 2020 else: 2021 self._grid_substances.patient = None 2022 self.__clear_gfr() 2023 self.__refresh_lab(patient = None) 2024 return True
2025 2026 #--------------------------------------------------------
2027 - def __refresh_lab(self, patient):
2028 2029 self._GSZR_lab.Clear(True) # also delete child windows 2030 self._HLINE_lab.Hide() 2031 2032 if patient is None: 2033 self.Layout() 2034 return 2035 2036 emr = patient.emr 2037 most_recent_results = {} 2038 2039 # get most recent results for "LOINCs to monitor" 2040 loincs2monitor = set() 2041 loincs2monitor_data = {} 2042 loinc_max_age = {} 2043 loinc_max_age_str = {} 2044 for intake in self._grid_substances.get_row_data(): 2045 for l in intake['loincs']: 2046 loincs2monitor.add(l['loinc']) 2047 loincs2monitor_data[l['loinc']] = { 2048 'substance': intake['substance'], 2049 'comment': l['comment'] 2050 } 2051 if l['max_age_in_secs'] is not None: 2052 try: 2053 if loinc_max_age[l['loinc']] > l['max_age_in_secs']: 2054 loinc_max_age[l['loinc']] = l['max_age_in_secs'] 2055 loinc_max_age_str[l['loinc']] = l['max_age_str'] 2056 except KeyError: 2057 loinc_max_age[l['loinc']] = l['max_age_in_secs'] 2058 loinc_max_age_str[l['loinc']] = l['max_age_str'] 2059 loincs2monitor_missing = loincs2monitor.copy() 2060 for loinc in loincs2monitor: 2061 results = emr.get_most_recent_results_in_loinc_group(loincs = [loinc], max_no_of_results = 1) 2062 if len(results) == 0: 2063 continue 2064 loincs2monitor_missing.remove(loinc) 2065 # make unique 2066 result = results[0] 2067 most_recent_results[result['pk_test_result']] = result 2068 2069 # get most recent results for "general medication monitoring lab panel" 2070 if self.__lab_panel is not None: 2071 for result in self.__lab_panel.get_most_recent_results ( 2072 pk_patient = patient.ID, 2073 order_by = 'unified_abbrev', 2074 group_by_meta_type = True 2075 ): 2076 try: loincs2monitor_missing.remove(result['loinc_tt']) 2077 except KeyError: pass 2078 try: loincs2monitor_missing.remove(result['loinc_meta']) 2079 except KeyError: pass 2080 # make unique 2081 most_recent_results[result['pk_test_result']] = result 2082 2083 # those need special treatment 2084 gfrs = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1) 2085 gfr = gfrs[0] if len(gfrs) > 0 else None 2086 creas = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1) 2087 crea = creas[0] if len(creas) > 0 else None 2088 edc = emr.EDC 2089 2090 # display EDC 2091 if edc is not None: 2092 if emr.EDC_is_fishy: 2093 lbl = wx.StaticText(self, -1, _('EDC (!?!):')) 2094 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d')) 2095 else: 2096 lbl = wx.StaticText(self, -1, _('EDC:')) 2097 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d')) 2098 lbl.SetForegroundColour('blue') 2099 szr = wx.BoxSizer(wx.HORIZONTAL) 2100 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2101 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2102 self._GSZR_lab.Add(szr) 2103 2104 # decide which among Crea or GFR to show 2105 if crea is None: 2106 gfr_3_months_older_than_crea = False 2107 if gfr is not None: 2108 most_recent_results = [gfr] + most_recent_results 2109 elif gfr is None: 2110 gfr_3_months_older_than_crea = True 2111 else: 2112 three_months = pydt.timedelta(weeks = 14) 2113 gfr_3_months_older_than_crea = (crea['clin_when'] - gfr['clin_when']) > three_months 2114 if not gfr_3_months_older_than_crea: 2115 most_recent_results = [gfr] + most_recent_results 2116 2117 # if GFR not found in most_recent_results or old, then calculate 2118 now = gmDateTime.pydt_now_here() 2119 if gfr_3_months_older_than_crea: 2120 calc = gmClinicalCalculator.cClinicalCalculator() 2121 calc.patient = patient 2122 gfr = calc.eGFR 2123 if gfr.numeric_value is None: 2124 gfr_msg = '?' 2125 else: 2126 gfr_msg = _('%.1f (%s ago)') % ( 2127 gfr.numeric_value, 2128 gmDateTime.format_interval_medically(now - gfr.date_valid) 2129 ) 2130 lbl = wx.StaticText(self, -1, _('eGFR:')) 2131 lbl.SetForegroundColour('blue') 2132 val = wx.StaticText(self, -1, gfr_msg) 2133 tts = [] 2134 for egfr in calc.eGFRs: 2135 if egfr.numeric_value is None: 2136 continue 2137 tts.append(egfr.format ( 2138 left_margin = 0, 2139 width = 50, 2140 eol = '\n', 2141 with_formula = False, 2142 with_warnings = True, 2143 with_variables = False, 2144 with_sub_results = False, 2145 return_list = False 2146 )) 2147 val.SetToolTip('\n'.join(tts)) 2148 szr = wx.BoxSizer(wx.HORIZONTAL) 2149 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2150 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2151 self._GSZR_lab.Add(szr) 2152 2153 # eventually add most-recent results from monitoring panel and substances monitoring 2154 for pk_result in most_recent_results: 2155 result = most_recent_results[pk_result] 2156 # test type 2157 lbl = wx.StaticText(self, -1, '%s:' % result['unified_abbrev']) 2158 lbl.SetForegroundColour('blue') 2159 # calculate test result 2160 indicate_attention = False 2161 if result.is_considered_abnormal: 2162 indicate_attention = True 2163 # calculate tooltip data 2164 max_age = None 2165 try: 2166 max_age = loinc_max_age[result['loinc_tt']] 2167 max_age_str = loinc_max_age_str[result['loinc_tt']] 2168 except KeyError: 2169 try: 2170 max_age = loinc_max_age[result['loinc_meta']] 2171 max_age_str = loinc_max_age_str[result['loinc_meta']] 2172 except KeyError: 2173 pass 2174 subst2monitor = None 2175 try: 2176 subst2monitor = loincs2monitor_data[result['loinc_tt']]['substance'] 2177 except KeyError: 2178 try: 2179 subst2monitor = loincs2monitor_data[result['loinc_meta']]['substance'] 2180 except KeyError: 2181 pass 2182 monitor_comment = None 2183 try: 2184 monitor_comment = loincs2monitor_data[result['loinc_tt']]['comment'] 2185 except KeyError: 2186 try: 2187 monitor_comment = loincs2monitor_data[result['loinc_meta']]['comment'] 2188 except KeyError: 2189 pass 2190 result_age = now - result['clin_when'] 2191 unhappy_reasons = [] 2192 if result.is_considered_abnormal: 2193 indicator = result.formatted_abnormality_indicator 2194 if indicator == '': 2195 unhappy_reasons.append(_(' - abnormal')) 2196 else: 2197 unhappy_reasons.append(_(' - abnormal: %s') % indicator) 2198 if max_age is not None: 2199 if result_age.total_seconds() > max_age: 2200 unhappy_reasons.append(_(' - too old: %s ago (max: %s)') % ( 2201 gmDateTime.format_interval_medically(result_age), 2202 max_age_str 2203 )) 2204 # generate tooltip 2205 tt = [_('Most recent: %s ago') % gmDateTime.format_interval_medically(result_age)] 2206 if subst2monitor is not None: 2207 tt.append(_('Why monitor: %s') % subst2monitor) 2208 if monitor_comment is not None: 2209 tt.append(' %s' % monitor_comment) 2210 if len(unhappy_reasons) > 0: 2211 indicate_attention = True 2212 tt.append(_('Problems:')) 2213 tt.extend(unhappy_reasons) 2214 tt = '%s\n\n%s' % ( 2215 '\n'.join(tt), 2216 result.format() 2217 ) 2218 # set test result and tooltip 2219 val = wx.StaticText(self, -1, '%s%s%s' % ( 2220 result['unified_val'], 2221 gmTools.coalesce(result['val_unit'], '', ' %s'), 2222 gmTools.bool2subst(indicate_attention, gmTools.u_frowning_face, '', '') 2223 )) 2224 val.SetToolTip(tt) 2225 if result.is_considered_abnormal: 2226 val.SetForegroundColour('red') 2227 szr = wx.BoxSizer(wx.HORIZONTAL) 2228 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2229 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2230 self._GSZR_lab.Add(szr) 2231 2232 # hint at missing, but required results (set to be 2233 # monitored under intakes based on LOINCs): 2234 for loinc in loincs2monitor_missing: 2235 #szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) 2236 loinc_data = gmLOINC.loinc2data(loinc) 2237 if loinc_data is None: 2238 loinc_str = loinc 2239 else: 2240 loinc_str = loinc_data['term'] 2241 val = wx.StaticText(self, -1, '%s!' % loinc_str) 2242 tt = [ 2243 _('No test result for: %s (%s)') % (loinc_str, loinc), 2244 '', 2245 _('Why monitor: %s' % loincs2monitor_data[loinc]['substance']) 2246 ] 2247 try: 2248 tt.append(' %s' % loincs2monitor_data[loinc]['comment']) 2249 except KeyError: 2250 pass 2251 val.SetToolTip('\n'.join(tt)) 2252 val.SetForegroundColour('orange') 2253 szr = wx.BoxSizer(wx.HORIZONTAL) 2254 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL) 2255 self._GSZR_lab.Add(szr) 2256 2257 self._HLINE_lab.Show() 2258 self.Layout()
2259 2260 #--------------------------------------------------------
2261 - def __refresh_gfr(self, patient):
2262 gfrs = patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1) 2263 if len(gfrs) == 0: 2264 calc = gmClinicalCalculator.cClinicalCalculator() 2265 calc.patient = patient 2266 gfr = calc.eGFR 2267 if gfr.numeric_value is None: 2268 msg = _('GFR: ?') 2269 tt = gfr.message 2270 else: 2271 msg = _('eGFR: %.1f (%s)') % ( 2272 gfr.numeric_value, 2273 gmDateTime.pydt_strftime ( 2274 gfr.date_valid, 2275 format = '%b %Y' 2276 ) 2277 ) 2278 egfrs = calc.eGFRs 2279 tts = [] 2280 for egfr in egfrs: 2281 if egfr.numeric_value is None: 2282 continue 2283 tts.append(egfr.format ( 2284 left_margin = 0, 2285 width = 50, 2286 eol = '\n', 2287 with_formula = False, 2288 with_warnings = True, 2289 with_variables = False, 2290 with_sub_results = False, 2291 return_list = False 2292 )) 2293 tt = '\n'.join(tts) 2294 else: 2295 gfr = gfrs[0] 2296 msg = '%s: %s %s (%s)\n' % ( 2297 gfr['unified_abbrev'], 2298 gfr['unified_val'], 2299 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'), 2300 gmDateTime.pydt_strftime ( 2301 gfr['clin_when'], 2302 format = '%b %Y' 2303 ) 2304 ) 2305 tt = _('GFR reported by path lab') 2306 2307 self._LBL_gfr.SetLabel(msg) 2308 self._LBL_gfr.SetToolTip(tt) 2309 self._LBL_gfr.Refresh() 2310 self.Layout()
2311 2312 #--------------------------------------------------------
2313 - def __clear_gfr(self):
2314 self._LBL_gfr.SetLabel(_('GFR: ?')) 2315 self._LBL_gfr.Refresh() 2316 self.Layout()
2317 2318 #-------------------------------------------------------- 2319 # event handling 2320 #--------------------------------------------------------
2321 - def __register_interests(self):
2322 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 2323 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 2324 gmDispatcher.connect(signal = 'clin.substance_intake_mod_db', receiver = self._schedule_data_reget) 2325 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._on_test_result_mod)
2326 2327 #--------------------------------------------------------
2328 - def _on_test_result_mod(self):
2329 self.__refresh_lab(patient = self._grid_substances.patient)
2330 2331 #--------------------------------------------------------
2333 dbcfg = gmCfg.cCfgSQL() 2334 pk_panel = dbcfg.get2 ( 2335 option = 'horstspace.medications_plugin.lab_panel', 2336 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2337 bias = 'user' 2338 ) 2339 if pk_panel is None: 2340 self.__lab_panel = None 2341 else: 2342 self.__lab_panel = gmPathLab.cTestPanel(aPK_obj = pk_panel) 2343 self._grid_substances.patient = None 2344 self.__refresh_lab(patient = None)
2345 #--------------------------------------------------------
2346 - def _on_post_patient_selection(self):
2347 self._schedule_data_reget()
2348 #--------------------------------------------------------
2349 - def _on_add_button_pressed(self, event):
2350 self._grid_substances.add_substance()
2351 #--------------------------------------------------------
2352 - def _on_edit_button_pressed(self, event):
2353 self._grid_substances.edit_substance()
2354 #--------------------------------------------------------
2355 - def _on_delete_button_pressed(self, event):
2356 self._grid_substances.delete_intake()
2357 #--------------------------------------------------------
2358 - def _on_info_button_pressed(self, event):
2359 self._grid_substances.show_info_on_entry()
2360 #--------------------------------------------------------
2361 - def _on_interactions_button_pressed(self, event):
2362 self._grid_substances.check_interactions()
2363 #--------------------------------------------------------
2364 - def _on_grouping_selected(self, event):
2365 event.Skip() 2366 selected_item_idx = self._CHCE_grouping.GetSelection() 2367 if selected_item_idx is wx.NOT_FOUND: 2368 return 2369 self._grid_substances.grouping_mode = self._CHCE_grouping.GetClientData(selected_item_idx)
2370 #--------------------------------------------------------
2371 - def _on_show_unapproved_checked(self, event):
2372 self._grid_substances.filter_show_unapproved = self._CHBOX_show_unapproved.GetValue()
2373 #--------------------------------------------------------
2374 - def _on_show_inactive_checked(self, event):
2375 self._grid_substances.filter_show_inactive = self._CHBOX_show_inactive.GetValue()
2376 #--------------------------------------------------------
2377 - def _on_print_button_pressed(self, event):
2378 self._grid_substances.print_medication_list()
2379 #--------------------------------------------------------
2380 - def _on_allergy_button_pressed(self, event):
2381 self._grid_substances.create_allergy_from_substance()
2382 #--------------------------------------------------------
2383 - def _on_button_kidneys_pressed(self, event):
2384 self._grid_substances.show_renal_insufficiency_info()
2385 #--------------------------------------------------------
2386 - def _on_button_heart_pressed(self, event):
2387 self._grid_substances.show_cardiac_info()
2388 #--------------------------------------------------------
2389 - def _on_adr_button_pressed(self, event):
2390 self._grid_substances.report_ADR()
2391 #--------------------------------------------------------
2392 - def _on_rx_button_pressed(self, event):
2393 self._grid_substances.prescribe()
2394 2395 #============================================================ 2396 # main 2397 #------------------------------------------------------------ 2398 if __name__ == '__main__': 2399 2400 if len(sys.argv) < 2: 2401 sys.exit() 2402 2403 if sys.argv[1] != 'test': 2404 sys.exit() 2405 2406 from Gnumed.business import gmPersonSearch 2407 2408 pat = gmPersonSearch.ask_for_patient() 2409 if pat is None: 2410 sys.exit() 2411 gmPerson.set_active_patient(patient = pat) 2412 2413 #---------------------------------------- 2414 app = wx.PyWidgetTester(size = (600, 300)) 2415 app.SetWidget(cSubstanceIntakeObjectPhraseWheel, -1) 2416 app.MainLoop() 2417 #manage_substance_intakes() 2418