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

Source Code for Module Gnumed.wxpython.gmMedicationWidgets

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