Package Gnumed :: Package business :: Module gmMedication
[frames] | no frames]

Source Code for Module Gnumed.business.gmMedication

   1  # -*- coding: utf-8 -*- 
   2  """Medication handling code. 
   3   
   4  license: GPL v2 or later 
   5  """ 
   6  #============================================================ 
   7  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
   8   
   9  import sys 
  10  import logging 
  11  import io 
  12  import uuid 
  13  import re as regex 
  14  import datetime as pydt 
  15   
  16   
  17  if __name__ == '__main__': 
  18          sys.path.insert(0, '../../') 
  19          from Gnumed.pycommon import gmI18N 
  20          gmI18N.activate_locale() 
  21          gmI18N.install_domain('gnumed') 
  22  from Gnumed.pycommon import gmBusinessDBObject 
  23  from Gnumed.pycommon import gmTools 
  24  from Gnumed.pycommon import gmPG2 
  25  from Gnumed.pycommon import gmDispatcher 
  26  from Gnumed.pycommon import gmMatchProvider 
  27  from Gnumed.pycommon import gmHooks 
  28  from Gnumed.pycommon import gmDateTime 
  29   
  30  from Gnumed.business import gmATC 
  31  from Gnumed.business import gmAllergy 
  32  from Gnumed.business import gmEMRStructItems 
  33   
  34   
  35  _log = logging.getLogger('gm.meds') 
  36   
  37  #============================================================ 
  38  DEFAULT_MEDICATION_HISTORY_EPISODE = _('Medication history') 
  39   
  40  URL_renal_insufficiency = 'http://www.dosing.de' 
  41  URL_renal_insufficiency_search_template = 'http://www.google.com/search?hl=de&source=hp&q=site%%3Adosing.de+%s&btnG=Google-Suche' 
  42   
  43  URL_long_qt = 'https://www.crediblemeds.org' 
  44   
  45  #============================================================ 
46 -def _on_substance_intake_modified():
47 """Always relates to the active patient.""" 48 gmHooks.run_hook_script(hook = 'after_substance_intake_modified')
49 50 gmDispatcher.connect(_on_substance_intake_modified, 'clin.substance_intake_mod_db') 51 52 #============================================================
53 -def drug2renal_insufficiency_url(search_term=None):
54 55 if search_term is None: 56 return URL_renal_insufficiency 57 58 if isinstance(search_term, str): 59 if search_term.strip() == '': 60 return URL_renal_insufficiency 61 62 terms = [] 63 names = [] 64 65 if isinstance(search_term, cDrugProduct): 66 if search_term['atc'] is not None: 67 terms.append(search_term['atc']) 68 69 elif isinstance(search_term, cSubstanceIntakeEntry): 70 names.append(search_term['substance']) 71 if search_term['atc_drug'] is not None: 72 terms.append(search_term['atc_drug']) 73 if search_term['atc_substance'] is not None: 74 terms.append(search_term['atc_substance']) 75 76 elif isinstance(search_term, cDrugComponent): 77 names.append(search_term['substance']) 78 if search_term['atc_drug'] is not None: 79 terms.append(search_term['atc_drug']) 80 if search_term['atc_substance'] is not None: 81 terms.append(search_term['atc_substance']) 82 83 elif isinstance(search_term, cSubstance): 84 names.append(search_term['substance']) 85 if search_term['atc'] is not None: 86 terms.append(search_term['atc']) 87 88 elif isinstance(search_term, cSubstanceDose): 89 names.append(search_term['substance']) 90 if search_term['atc'] is not None: 91 terms.append(search_term['atc_substance']) 92 93 elif search_term is not None: 94 names.append('%s' % search_term) 95 terms.extend(gmATC.text2atc(text = '%s' % search_term, fuzzy = True)) 96 97 for name in names: 98 if name.endswith('e'): 99 terms.append(name[:-1]) 100 else: 101 terms.append(name) 102 103 #url_template = 'http://www.google.de/#q=site%%3Adosing.de+%s' 104 #url = url_template % '+OR+'.join(terms) 105 url = URL_renal_insufficiency_search_template % '+OR+'.join(terms) 106 107 _log.debug('renal insufficiency URL: %s', url) 108 109 return url
110 111 #============================================================ 112 #============================================================ 113 # plain substances 114 #------------------------------------------------------------ 115 _SQL_get_substance = "SELECT * FROM ref.v_substances WHERE %s" 116
117 -class cSubstance(gmBusinessDBObject.cBusinessDBObject):
118 119 _cmd_fetch_payload = _SQL_get_substance % "pk_substance = %s" 120 _cmds_store_payload = [ 121 """UPDATE ref.substance SET 122 description = %(substance)s, 123 atc = gm.nullify_empty_string(%(atc)s), 124 intake_instructions = gm.nullify_empty_string(%(intake_instructions)s) 125 WHERE 126 pk = %(pk_substance)s 127 AND 128 xmin = %(xmin_substance)s 129 RETURNING 130 xmin AS xmin_substance 131 """ 132 ] 133 _updatable_fields = [ 134 'substance', 135 'atc', 136 'intake_instructions' 137 ] 138 #--------------------------------------------------------
139 - def format(self, left_margin=0):
140 if len(self._payload[self._idx['loincs']]) == 0: 141 loincs = '' 142 else: 143 loincs = """ 144 %s %s 145 %s %s""" % ( 146 (' ' * left_margin), 147 _('LOINCs to monitor:'), 148 (' ' * left_margin), 149 ('\n' + (' ' * (left_margin + 1))).join ([ 150 '%s%s%s' % ( 151 l['loinc'], 152 gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')), 153 gmTools.coalesce(l['comment'], '', ' (%s)') 154 ) for l in self._payload[self._idx['loincs']] 155 ]) 156 ) 157 return (' ' * left_margin) + '%s: %s%s%s%s' % ( 158 _('Substance'), 159 self._payload[self._idx['substance']], 160 gmTools.coalesce(self._payload[self._idx['atc']], '', ' [%s]'), 161 gmTools.coalesce(self._payload[self._idx['intake_instructions']], '', _('\n Instructions: %s')), 162 loincs 163 )
164 165 #--------------------------------------------------------
166 - def save_payload(self, conn=None):
167 success, data = super(self.__class__, self).save_payload(conn = conn) 168 169 if not success: 170 return (success, data) 171 172 if self._payload[self._idx['atc']] is not None: 173 atc = self._payload[self._idx['atc']].strip() 174 if atc != '': 175 gmATC.propagate_atc ( 176 substance = self._payload[self._idx['substance']].strip(), 177 atc = atc 178 ) 179 180 return (success, data)
181 182 #--------------------------------------------------------
183 - def exists_as_intake(self, pk_patient=None):
184 return substance_intake_exists ( 185 pk_substance = self.pk_obj, 186 pk_identity = pk_patient 187 )
188 189 #-------------------------------------------------------- 190 # properties 191 #--------------------------------------------------------
192 - def _set_loincs(self, loincs):
193 args = {'pk_subst': self.pk_obj, 'loincs': tuple(loincs)} 194 # insert new entries 195 for loinc in loincs: 196 cmd = """INSERT INTO ref.lnk_loinc2substance (fk_substance, loinc) 197 SELECT 198 %(pk_subst)s, %(loinc)s 199 WHERE NOT EXISTS ( 200 SELECT 1 from ref.lnk_loinc2substance WHERE fk_substance = %(pk_subst)s AND loinc = %(loinc)s 201 )""" 202 args['loinc'] = loinc 203 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 204 205 # delete old entries 206 cmd = """DELETE FROM ref.lnk_loinc2substance WHERE fk_substance = %(pk_subst)s AND loinc NOT IN %(loincs)s""" 207 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
208 209 loincs = property(lambda x:x, _set_loincs) 210 211 #--------------------------------------------------------
212 - def _get_is_in_use_by_patients(self):
213 cmd = """ 214 SELECT EXISTS ( 215 SELECT 1 216 FROM clin.v_substance_intakes 217 WHERE pk_substance = %(pk)s 218 LIMIT 1 219 )""" 220 args = {'pk': self.pk_obj} 221 222 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 223 return rows[0][0]
224 225 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x) 226 227 #--------------------------------------------------------
228 - def _get_is_drug_component(self):
229 cmd = """ 230 SELECT EXISTS ( 231 SELECT 1 232 FROM ref.v_drug_components 233 WHERE pk_substance = %(pk)s 234 LIMIT 1 235 )""" 236 args = {'pk': self.pk_obj} 237 238 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 239 return rows[0][0]
240 241 is_drug_component = property(_get_is_drug_component, lambda x:x)
242 243 #------------------------------------------------------------
244 -def get_substances(order_by=None):
245 if order_by is None: 246 order_by = 'true' 247 else: 248 order_by = 'true ORDER BY %s' % order_by 249 cmd = _SQL_get_substance % order_by 250 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 251 return [ cSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance'}) for r in rows ]
252 253 #------------------------------------------------------------
254 -def create_substance(substance=None, atc=None):
255 if atc is not None: 256 atc = atc.strip() 257 258 args = { 259 'desc': substance.strip(), 260 'atc': atc 261 } 262 cmd = "SELECT pk FROM ref.substance WHERE lower(description) = lower(%(desc)s)" 263 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 264 265 if len(rows) == 0: 266 cmd = """ 267 INSERT INTO ref.substance (description, atc) VALUES ( 268 %(desc)s, 269 coalesce ( 270 gm.nullify_empty_string(%(atc)s), 271 (SELECT code FROM ref.atc WHERE term = %(desc)s LIMIT 1) 272 ) 273 ) RETURNING pk""" 274 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 275 276 if atc is not None: 277 gmATC.propagate_atc(substance = substance.strip(), atc = atc) 278 279 return cSubstance(aPK_obj = rows[0]['pk'])
280 281 #------------------------------------------------------------
282 -def create_substance_by_atc(substance=None, atc=None, link_obj=None):
283 284 if atc is None: 285 raise ValueError('<atc> must be supplied') 286 atc = atc.strip() 287 if atc == '': 288 raise ValueError('<atc> cannot be empty: [%s]', atc) 289 290 queries = [] 291 args = { 292 'desc': substance.strip(), 293 'atc': atc 294 } 295 # in case the substance already exists: add ATC 296 cmd = "UPDATE ref.substance SET atc = %(atc)s WHERE lower(description) = lower(%(desc)s) AND atc IS NULL" 297 queries.append({'cmd': cmd, 'args': args}) 298 # or else INSERT the substance 299 cmd = """ 300 INSERT INTO ref.substance (description, atc) 301 SELECT 302 %(desc)s, 303 %(atc)s 304 WHERE NOT EXISTS ( 305 SELECT 1 FROM ref.substance WHERE atc = %(atc)s 306 ) 307 RETURNING pk""" 308 queries.append({'cmd': cmd, 'args': args}) 309 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data = True, get_col_idx = False) 310 if len(rows) == 0: 311 cmd = "SELECT pk FROM ref.substance WHERE atc = %(atc)s LIMIT 1" 312 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}]) 313 314 return cSubstance(aPK_obj = rows[0]['pk'], link_obj = link_obj)
315 316 #------------------------------------------------------------
317 -def delete_substance(pk_substance=None):
318 args = {'pk': pk_substance} 319 cmd = """ 320 DELETE FROM ref.substance WHERE 321 pk = %(pk)s 322 AND 323 -- must not currently be used with a patient 324 NOT EXISTS ( 325 SELECT 1 FROM clin.v_substance_intakes 326 WHERE pk_substance = %(pk)s 327 LIMIT 1 328 ) 329 AND 330 -- must not currently have doses defined for it 331 NOT EXISTS ( 332 SELECT 1 FROM ref.dose 333 WHERE fk_substance = %(pk)s 334 LIMIT 1 335 ) 336 """ 337 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 338 return True
339 340 #============================================================ 341 # substance doses 342 #------------------------------------------------------------ 343 _SQL_get_substance_dose = "SELECT * FROM ref.v_substance_doses WHERE %s" 344
345 -class cSubstanceDose(gmBusinessDBObject.cBusinessDBObject):
346 347 _cmd_fetch_payload = _SQL_get_substance_dose % "pk_dose = %s" 348 _cmds_store_payload = [ 349 """UPDATE ref.dose SET 350 amount = %(amount)s, 351 unit = %(unit)s, 352 dose_unit = gm.nullify_empty_string(%(dose_unit)s) 353 WHERE 354 pk = %(pk_dose)s 355 AND 356 xmin = %(xmin_dose)s 357 RETURNING 358 xmin as xmin_dose, 359 pk as pk_dose 360 """ 361 ] 362 _updatable_fields = [ 363 'amount', 364 'unit', 365 'dose_unit' 366 ] 367 368 #--------------------------------------------------------
369 - def format(self, left_margin=0, include_loincs=False):
370 loincs = '' 371 if include_loincs and (len(self._payload[self._idx['loincs']]) > 0): 372 loincs = """ 373 %s %s 374 %s %s""" % ( 375 (' ' * left_margin), 376 _('LOINCs to monitor:'), 377 (' ' * left_margin), 378 ('\n' + (' ' * (left_margin + 1))).join ([ 379 '%s%s%s' % ( 380 l['loinc'], 381 gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')), 382 gmTools.coalesce(l['comment'], '', ' (%s)') 383 ) for l in self._payload[self._idx['loincs']] 384 ]) 385 ) 386 return (' ' * left_margin) + '%s: %s %s%s%s%s%s' % ( 387 _('Substance dose'), 388 self._payload[self._idx['substance']], 389 self._payload[self._idx['amount']], 390 self.formatted_units, 391 gmTools.coalesce(self._payload[self._idx['atc_substance']], '', ' [%s]'), 392 gmTools.coalesce(self._payload[self._idx['intake_instructions']], '', '\n' + (' ' * left_margin) + ' ' + _('Instructions: %s')), 393 loincs 394 )
395 396 #--------------------------------------------------------
397 - def exists_as_intake(self, pk_patient=None):
398 return substance_intake_exists ( 399 pk_dose = self.pk_obj, 400 pk_identity = pk_patient 401 )
402 403 #-------------------------------------------------------- 404 # properties 405 #--------------------------------------------------------
406 - def _get_is_in_use_by_patients(self):
407 cmd = """ 408 SELECT EXISTS ( 409 SELECT 1 410 FROM clin.v_substance_intakes 411 WHERE pk_dose = %(pk)s 412 LIMIT 1 413 )""" 414 args = {'pk': self.pk_obj} 415 416 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 417 return rows[0][0]
418 419 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x) 420 421 #--------------------------------------------------------
422 - def _get_is_drug_component(self):
423 cmd = """ 424 SELECT EXISTS ( 425 SELECT 1 426 FROM ref.v_drug_components 427 WHERE pk_dose = %(pk)s 428 LIMIT 1 429 )""" 430 args = {'pk': self.pk_obj} 431 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 432 return rows[0][0]
433 434 is_drug_component = property(_get_is_drug_component, lambda x:x) 435 436 #--------------------------------------------------------
437 - def _get_formatted_units(self, short=True):
438 return format_units ( 439 self._payload[self._idx['unit']], 440 gmTools.coalesce(self._payload[self._idx['dose_unit']], _('delivery unit')), 441 short = short 442 )
443 444 formatted_units = property(_get_formatted_units, lambda x:x)
445 446 #------------------------------------------------------------
447 -def get_substance_doses(order_by=None):
448 if order_by is None: 449 order_by = 'true' 450 else: 451 order_by = 'true ORDER BY %s' % order_by 452 cmd = _SQL_get_substance_dose % order_by 453 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 454 return [ cSubstanceDose(row = {'data': r, 'idx': idx, 'pk_field': 'pk_dose'}) for r in rows ]
455 456 #------------------------------------------------------------
457 -def create_substance_dose(link_obj=None, pk_substance=None, substance=None, atc=None, amount=None, unit=None, dose_unit=None):
458 459 if [pk_substance, substance].count(None) != 1: 460 raise ValueError('exctly one of <pk_substance> and <substance> must be None') 461 462 converted, amount = gmTools.input2decimal(amount) 463 if not converted: 464 raise ValueError('<amount> must be a number: %s (is: %s)', amount, type(amount)) 465 466 if pk_substance is None: 467 pk_substance = create_substance(link_obj = link_obj, substance = substance, atc = atc)['pk_substance'] 468 469 args = { 470 'pk_subst': pk_substance, 471 'amount': amount, 472 'unit': unit.strip(), 473 'dose_unit': dose_unit 474 } 475 cmd = """ 476 SELECT pk FROM ref.dose 477 WHERE 478 fk_substance = %(pk_subst)s 479 AND 480 amount = %(amount)s 481 AND 482 unit = %(unit)s 483 AND 484 dose_unit IS NOT DISTINCT FROM gm.nullify_empty_string(%(dose_unit)s) 485 """ 486 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}]) 487 488 if len(rows) == 0: 489 cmd = """ 490 INSERT INTO ref.dose (fk_substance, amount, unit, dose_unit) VALUES ( 491 %(pk_subst)s, 492 %(amount)s, 493 gm.nullify_empty_string(%(unit)s), 494 gm.nullify_empty_string(%(dose_unit)s) 495 ) RETURNING pk""" 496 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 497 498 return cSubstanceDose(aPK_obj = rows[0]['pk'], link_obj = link_obj)
499 500 #------------------------------------------------------------
501 -def create_substance_dose_by_atc(link_obj=None, substance=None, atc=None, amount=None, unit=None, dose_unit=None):
502 subst = create_substance_by_atc ( 503 link_obj = link_obj, 504 substance = substance, 505 atc = atc 506 ) 507 return create_substance_dose ( 508 link_obj = link_obj, 509 pk_substance = subst['pk_substance'], 510 amount = amount, 511 unit = unit, 512 dose_unit = dose_unit 513 )
514 515 #------------------------------------------------------------
516 -def delete_substance_dose(pk_dose=None):
517 args = {'pk_dose': pk_dose} 518 cmd = """ 519 DELETE FROM ref.dose WHERE 520 pk = %(pk_dose)s 521 AND 522 -- must not currently be used with a patient 523 NOT EXISTS ( 524 SELECT 1 FROM clin.v_substance_intakes 525 WHERE pk_dose = %(pk_dose)s 526 LIMIT 1 527 ) 528 AND 529 -- must not currently be linked to a drug 530 NOT EXISTS ( 531 SELECT 1 FROM ref.lnk_dose2drug 532 WHERE fk_dose = %(pk_dose)s 533 LIMIT 1 534 ) 535 """ 536 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 537 return True
538 539 #------------------------------------------------------------
540 -class cSubstanceDoseMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
541 542 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE) 543 544 # the "normal query" is run when the search fragment 545 # does NOT match the regex ._pattern (which is: "chars SPACE digits") 546 _normal_query = """ 547 SELECT 548 r_vsd.pk_dose 549 AS data, 550 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, '')) 551 AS field_label, 552 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, '')) 553 AS list_label 554 FROM 555 ref.v_substance_doses r_vsd 556 WHERE 557 r_vsd.substance %%(fragment_condition)s 558 ORDER BY 559 list_label 560 LIMIT 50""" 561 562 # the "regex query" is run when the search fragment 563 # DOES match the regex ._pattern (which is: "chars SPACE digits") 564 _regex_query = """ 565 SELECT 566 r_vsd.pk_dose 567 AS data, 568 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, '')) 569 AS field_label, 570 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, '')) 571 AS list_label 572 FROM 573 ref.v_substance_doses r_vsd 574 WHERE 575 %%(fragment_condition)s 576 ORDER BY 577 list_label 578 LIMIT 50""" 579 580 #--------------------------------------------------------
581 - def getMatchesByPhrase(self, aFragment):
582 """Return matches for aFragment at start of phrases.""" 583 584 if cSubstanceMatchProvider._pattern.match(aFragment): 585 self._queries = [cSubstanceMatchProvider._regex_query] 586 fragment_condition = """substance ILIKE %(subst)s 587 AND 588 amount::text ILIKE %(amount)s""" 589 self._args['subst'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 590 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 591 else: 592 self._queries = [cSubstanceMatchProvider._normal_query] 593 fragment_condition = "ILIKE %(fragment)s" 594 self._args['fragment'] = "%s%%" % aFragment 595 596 return self._find_matches(fragment_condition)
597 598 #--------------------------------------------------------
599 - def getMatchesByWord(self, aFragment):
600 """Return matches for aFragment at start of words inside phrases.""" 601 602 if cSubstanceMatchProvider._pattern.match(aFragment): 603 self._queries = [cSubstanceMatchProvider._regex_query] 604 605 subst = regex.sub(r'\s*\d+$', '', aFragment) 606 subst = gmPG2.sanitize_pg_regex(expression = subst, escape_all = False) 607 608 fragment_condition = """substance ~* %(subst)s 609 AND 610 amount::text ILIKE %(amount)s""" 611 612 self._args['subst'] = "( %s)|(^%s)" % (subst, subst) 613 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 614 else: 615 self._queries = [cSubstanceMatchProvider._normal_query] 616 fragment_condition = "~* %(fragment)s" 617 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False) 618 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment) 619 620 return self._find_matches(fragment_condition)
621 622 #--------------------------------------------------------
623 - def getMatchesBySubstr(self, aFragment):
624 """Return matches for aFragment as a true substring.""" 625 626 if cSubstanceMatchProvider._pattern.match(aFragment): 627 self._queries = [cSubstanceMatchProvider._regex_query] 628 fragment_condition = """substance ILIKE %(subst)s 629 AND 630 amount::text ILIKE %(amount)s""" 631 self._args['subst'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 632 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 633 else: 634 self._queries = [cSubstanceMatchProvider._normal_query] 635 fragment_condition = "ILIKE %(fragment)s" 636 self._args['fragment'] = "%%%s%%" % aFragment 637 638 return self._find_matches(fragment_condition)
639 640 #------------------------------------------------------------ 641 #------------------------------------------------------------
642 -class cProductOrSubstanceMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
643 644 # by product name 645 _query_drug_product_by_name = """ 646 SELECT 647 ARRAY[1, pk]::INTEGER[] 648 AS data, 649 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', '')) 650 AS list_label, 651 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', '')) 652 AS field_label, 653 1 AS rank 654 FROM ref.drug_product 655 WHERE description %(fragment_condition)s 656 LIMIT 50 657 """ 658 _query_drug_product_by_name_and_strength = """ 659 SELECT 660 ARRAY[1, pk_drug_product]::INTEGER[] 661 AS data, 662 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 663 AS list_label, 664 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 665 AS field_label, 666 1 AS rank 667 FROM 668 (SELECT *, product AS description FROM ref.v_drug_components) AS _components 669 WHERE %%(fragment_condition)s 670 LIMIT 50 671 """ % ( 672 _('w/'), 673 _('w/') 674 ) 675 676 # by component 677 # _query_component_by_name = u""" 678 # SELECT 679 # ARRAY[3, r_vdc1.pk_component]::INTEGER[] 680 # AS data, 681 # (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' (' 682 # || r_vdc1.product || ' [' 683 # || ( 684 # SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 685 # FROM ref.v_drug_components r_vdc2 686 # WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 687 # ) 688 # || ']' 689 # || ')' 690 # ) AS field_label, 691 # (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' (' 692 # || r_vdc1.product || ' [' 693 # || ( 694 # SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 695 # FROM ref.v_drug_components r_vdc2 696 # WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 697 # ) 698 # || ']' 699 # || ')' 700 # ) AS list_label, 701 # 1 AS rank 702 # FROM 703 # (SELECT *, product AS description FROM ref.v_drug_components) AS r_vdc1 704 # WHERE 705 # r_vdc1.substance %(fragment_condition)s 706 # LIMIT 50""" 707 708 # _query_component_by_name_and_strength = u""" 709 # SELECT 710 # ARRAY[3, r_vdc1.pk_component]::INTEGER[] 711 # AS data, 712 # (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' (' 713 # || r_vdc1.product || ' [' 714 # || ( 715 # SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 716 # FROM ref.v_drug_components r_vdc2 717 # WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 718 # ) 719 # || ']' 720 # || ')' 721 # ) AS field_label, 722 # (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' (' 723 # || r_vdc1.product || ' [' 724 # || ( 725 # SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 726 # FROM ref.v_drug_components r_vdc2 727 # WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 728 # ) 729 # || ']' 730 # || ')' 731 # ) AS list_label, 732 # 1 AS rank 733 # FROM (SELECT *, substance AS description FROM ref.v_drug_components) AS r_vdc1 734 # WHERE 735 # %(fragment_condition)s 736 # ORDER BY list_label 737 # LIMIT 50""" 738 739 # by substance name in doses 740 _query_substance_by_name = """ 741 SELECT 742 data, 743 field_label, 744 list_label, 745 rank 746 FROM (( 747 -- first: substance intakes which match, because we tend to reuse them often 748 SELECT 749 ARRAY[2, pk_substance]::INTEGER[] AS data, 750 (description || ' ' || amount || unit || coalesce('/' || dose_unit, '')) AS field_label, 751 (description || ' ' || amount || unit || coalesce('/' || dose_unit, '') || ' (%s)') AS list_label, 752 1 AS rank 753 FROM ( 754 SELECT DISTINCT ON (description, amount, unit, dose_unit) 755 pk_substance, 756 substance AS description, 757 amount, 758 unit, 759 dose_unit 760 FROM clin.v_substance_intakes 761 ) AS normalized_intakes 762 WHERE description %%(fragment_condition)s 763 764 ) UNION ALL ( 765 xxxxxxxxxxxxxxxxxxxxxxxxxxxx 766 -- second: consumable substances which match but are not intakes 767 SELECT 768 ARRAY[2, pk]::INTEGER[] AS data, 769 (description || ' ' || amount || ' ' || unit) AS field_label, 770 (description || ' ' || amount || ' ' || unit) AS list_label, 771 2 AS rank 772 FROM ref.consumable_substance 773 WHERE 774 description %%(fragment_condition)s 775 AND 776 pk NOT IN ( 777 SELECT fk_substance 778 FROM clin.substance_intake 779 WHERE fk_substance IS NOT NULL 780 ) 781 )) AS candidates 782 --ORDER BY rank, list_label 783 LIMIT 50""" % _('in use') 784 785 _query_substance_by_name_and_strength = """ 786 SELECT 787 data, 788 field_label, 789 list_label, 790 rank 791 FROM (( 792 SELECT 793 ARRAY[2, pk_substance]::INTEGER[] AS data, 794 (description || ' ' || amount || ' ' || unit) AS field_label, 795 (description || ' ' || amount || ' ' || unit || ' (%s)') AS list_label, 796 1 AS rank 797 FROM ( 798 SELECT DISTINCT ON (description, amount, unit) 799 pk_substance, 800 substance AS description, 801 amount, 802 unit 803 FROM clin.v_nonbraXXXnd_intakes 804 ) AS normalized_intakes 805 WHERE 806 %%(fragment_condition)s 807 808 ) UNION ALL ( 809 810 -- matching substances which are not in intakes 811 SELECT 812 ARRAY[2, pk]::INTEGER[] AS data, 813 (description || ' ' || amount || ' ' || unit) AS field_label, 814 (description || ' ' || amount || ' ' || unit) AS list_label, 815 2 AS rank 816 FROM ref.consumable_substance 817 WHERE 818 %%(fragment_condition)s 819 AND 820 pk NOT IN ( 821 SELECT fk_substance 822 FROM clin.substance_intake 823 WHERE fk_substance IS NOT NULL 824 ) 825 )) AS candidates 826 --ORDER BY rank, list_label 827 LIMIT 50""" % _('in use') 828 829 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE) 830 831 _master_query = """ 832 SELECT 833 data, field_label, list_label, rank 834 FROM ((%s) UNION (%s) UNION (%s)) 835 AS _union 836 ORDER BY rank, list_label 837 LIMIT 50 838 """ 839 #--------------------------------------------------------
840 - def getMatchesByPhrase(self, aFragment):
841 """Return matches for aFragment at start of phrases.""" 842 843 if cProductOrSubstanceMatchProvider._pattern.match(aFragment): 844 self._queries = [ 845 cProductOrSubstanceMatchProvider._master_query % ( 846 cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength, 847 cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength, 848 cProductOrSubstanceMatchProvider._query_component_by_name_and_strength 849 ) 850 ] 851 #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength] 852 fragment_condition = """description ILIKE %(desc)s 853 AND 854 amount::text ILIKE %(amount)s""" 855 self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 856 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 857 else: 858 self._queries = [ 859 cProductOrSubstanceMatchProvider._master_query % ( 860 cProductOrSubstanceMatchProvider._query_drug_product_by_name, 861 cProductOrSubstanceMatchProvider._query_substance_by_name, 862 cProductOrSubstanceMatchProvider._query_component_by_name 863 ) 864 ] 865 #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name] 866 fragment_condition = "ILIKE %(fragment)s" 867 self._args['fragment'] = "%s%%" % aFragment 868 869 return self._find_matches(fragment_condition)
870 871 #--------------------------------------------------------
872 - def getMatchesByWord(self, aFragment):
873 """Return matches for aFragment at start of words inside phrases.""" 874 875 if cProductOrSubstanceMatchProvider._pattern.match(aFragment): 876 self._queries = [ 877 cProductOrSubstanceMatchProvider._master_query % ( 878 cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength, 879 cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength, 880 cProductOrSubstanceMatchProvider._query_component_by_name_and_strength 881 ) 882 ] 883 #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength] 884 885 desc = regex.sub(r'\s*\d+$', '', aFragment) 886 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False) 887 888 fragment_condition = """description ~* %(desc)s 889 AND 890 amount::text ILIKE %(amount)s""" 891 892 self._args['desc'] = "( %s)|(^%s)" % (desc, desc) 893 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 894 else: 895 self._queries = [ 896 cProductOrSubstanceMatchProvider._master_query % ( 897 cProductOrSubstanceMatchProvider._query_drug_product_by_name, 898 cProductOrSubstanceMatchProvider._query_substance_by_name, 899 cProductOrSubstanceMatchProvider._query_component_by_name 900 ) 901 ] 902 #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name] 903 fragment_condition = "~* %(fragment)s" 904 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False) 905 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment) 906 907 return self._find_matches(fragment_condition)
908 909 #--------------------------------------------------------
910 - def getMatchesBySubstr(self, aFragment):
911 """Return matches for aFragment as a true substring.""" 912 913 if cProductOrSubstanceMatchProvider._pattern.match(aFragment): 914 self._queries = [ 915 cProductOrSubstanceMatchProvider._master_query % ( 916 cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength, 917 cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength, 918 cProductOrSubstanceMatchProvider._query_component_by_name_and_strength 919 ) 920 ] 921 #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength] 922 fragment_condition = """description ILIKE %(desc)s 923 AND 924 amount::text ILIKE %(amount)s""" 925 self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 926 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 927 else: 928 self._queries = [ 929 cProductOrSubstanceMatchProvider._master_query % ( 930 cProductOrSubstanceMatchProvider._query_drug_product_by_name, 931 cProductOrSubstanceMatchProvider._query_substance_by_name, 932 cProductOrSubstanceMatchProvider._query_component_by_name 933 ) 934 ] 935 #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name] 936 fragment_condition = "ILIKE %(fragment)s" 937 self._args['fragment'] = "%%%s%%" % aFragment 938 939 return self._find_matches(fragment_condition)
940 941 #------------------------------------------------------------
942 -class cSubstanceIntakeObjectMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
943 944 # (product name) -> product 945 _SQL_drug_product_by_name = """ 946 SELECT 947 pk_drug_product 948 AS data, 949 (product || ' (' || preparation || ')' || coalesce(' [' || atc || ']', '')) 950 AS list_label, 951 (product || ' (' || preparation || ')' || coalesce(' [' || atc || ']', '')) 952 AS field_label 953 FROM ref.v_drug_products 954 WHERE 955 is_vaccine IS FALSE 956 AND 957 product %(fragment_condition)s 958 LIMIT 50 959 """ 960 # (component name) -> product 961 _SQL_drug_product_by_component_name = """ 962 SELECT 963 pk_drug_product 964 AS data, 965 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 966 AS list_label, 967 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 968 AS field_label 969 FROM 970 ref.v_drug_components 971 WHERE substance %%(fragment_condition)s 972 LIMIT 50 973 """ % ( 974 _('w/'), 975 _('w/') 976 ) 977 # (product name + component strength) -> product 978 _SQL_drug_product_by_name_and_strength = """ 979 SELECT 980 pk_drug_product 981 AS data, 982 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 983 AS list_label, 984 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 985 AS field_label 986 FROM 987 (SELECT *, product AS description FROM ref.v_drug_components) AS _components 988 WHERE %%(fragment_condition)s 989 LIMIT 50 990 """ % ( 991 _('w/'), 992 _('w/') 993 ) 994 # (component name + component strength) -> product 995 _SQL_drug_product_by_component_name_and_strength = """ 996 SELECT 997 pk_drug_product 998 AS data, 999 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 1000 AS list_label, 1001 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', '')) 1002 AS field_label 1003 FROM 1004 (SELECT *, substance AS description FROM ref.v_drug_components) AS _components 1005 WHERE %%(fragment_condition)s 1006 LIMIT 50 1007 """ % ( 1008 _('w/'), 1009 _('w/') 1010 ) 1011 # non-drug substance name 1012 _SQL_substance_name = """ 1013 SELECT DISTINCT ON (field_label) 1014 data, list_label, field_label 1015 FROM ( 1016 SELECT DISTINCT ON (term) 1017 NULL::integer 1018 AS data, 1019 term || ' (ATC: ' || code || ')' 1020 AS list_label, 1021 term 1022 AS field_label 1023 FROM 1024 ref.atc 1025 WHERE 1026 lower(term) %(fragment_condition)s 1027 1028 UNION ALL 1029 1030 SELECT DISTINCT ON (description) 1031 NULL::integer 1032 AS data, 1033 description || coalesce(' (ATC: ' || atc || ')', '') 1034 AS list_label, 1035 description 1036 AS field_label 1037 FROM 1038 ref.substance 1039 WHERE 1040 lower(description) %(fragment_condition)s 1041 ) AS nondrug_substances 1042 WHERE NOT EXISTS ( 1043 SELECT 1 FROM ref.v_drug_components WHERE lower(substance) = lower(nondrug_substances.field_label) 1044 ) 1045 LIMIT 30 1046 """ 1047 1048 # this query UNIONs together individual queries 1049 _SQL_regex_master_query = """ 1050 SELECT 1051 data, field_label, list_label 1052 FROM ((%s) UNION (%s)) 1053 AS _union 1054 ORDER BY list_label 1055 LIMIT 50 1056 """ % ( 1057 _SQL_drug_product_by_name_and_strength, 1058 _SQL_drug_product_by_component_name_and_strength 1059 ) 1060 _SQL_nonregex_master_query = """ 1061 SELECT 1062 data, field_label, list_label 1063 FROM ((%s) UNION (%s) UNION (%s)) 1064 AS _union 1065 ORDER BY list_label 1066 LIMIT 50 1067 """ % ( 1068 _SQL_drug_product_by_name, 1069 _SQL_drug_product_by_component_name, 1070 _SQL_substance_name 1071 ) 1072 1073 _REGEX_name_and_strength = regex.compile(r'^\D+\s*\d+$', regex.UNICODE) 1074 1075 #--------------------------------------------------------
1076 - def getMatchesByPhrase(self, aFragment):
1077 """Return matches for aFragment at start of phrases.""" 1078 1079 if cSubstanceIntakeObjectMatchProvider._REGEX_name_and_strength.match(aFragment): 1080 self._queries = [cSubstanceIntakeObjectMatchProvider._SQL_regex_master_query] 1081 fragment_condition = """description ILIKE %(desc)s 1082 AND 1083 amount::text ILIKE %(amount)s""" 1084 self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 1085 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 1086 else: 1087 self._queries = [ cSubstanceIntakeObjectMatchProvider._SQL_nonregex_master_query ] 1088 fragment_condition = "ILIKE %(fragment)s" 1089 self._args['fragment'] = "%s%%" % aFragment 1090 1091 return self._find_matches(fragment_condition)
1092 1093 #--------------------------------------------------------
1094 - def getMatchesByWord(self, aFragment):
1095 """Return matches for aFragment at start of words inside phrases.""" 1096 1097 if cSubstanceIntakeObjectMatchProvider._REGEX_name_and_strength.match(aFragment): 1098 self._queries = [cSubstanceIntakeObjectMatchProvider._SQL_regex_master_query] 1099 1100 desc = regex.sub(r'\s*\d+$', '', aFragment) 1101 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False) 1102 1103 fragment_condition = """description ~* %(desc)s 1104 AND 1105 amount::text ILIKE %(amount)s""" 1106 1107 self._args['desc'] = "( %s)|(^%s)" % (desc, desc) 1108 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 1109 else: 1110 self._queries = [ cSubstanceIntakeObjectMatchProvider._SQL_nonregex_master_query ] 1111 fragment_condition = "~* %(fragment)s" 1112 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False) 1113 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment) 1114 1115 return self._find_matches(fragment_condition)
1116 1117 #--------------------------------------------------------
1118 - def getMatchesBySubstr(self, aFragment):
1119 """Return matches for aFragment as a true substring.""" 1120 1121 if cSubstanceIntakeObjectMatchProvider._REGEX_name_and_strength.match(aFragment): 1122 self._queries = [cSubstanceIntakeObjectMatchProvider._SQL_regex_master_query] 1123 fragment_condition = """description ILIKE %(desc)s 1124 AND 1125 amount::text ILIKE %(amount)s""" 1126 self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 1127 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 1128 else: 1129 self._queries = [ cSubstanceIntakeObjectMatchProvider._SQL_nonregex_master_query ] 1130 fragment_condition = "ILIKE %(fragment)s" 1131 self._args['fragment'] = "%%%s%%" % aFragment 1132 1133 return self._find_matches(fragment_condition)
1134 1135 #============================================================ 1136 # drug components 1137 #------------------------------------------------------------ 1138 _SQL_get_drug_components = 'SELECT * FROM ref.v_drug_components WHERE %s' 1139
1140 -class cDrugComponent(gmBusinessDBObject.cBusinessDBObject):
1141 1142 _cmd_fetch_payload = _SQL_get_drug_components % 'pk_component = %s' 1143 _cmds_store_payload = [ 1144 """UPDATE ref.lnk_dose2drug SET 1145 fk_drug_product = %(pk_drug_product)s, 1146 fk_dose = %(pk_dose)s 1147 WHERE 1148 pk = %(pk_component)s 1149 AND 1150 NOT EXISTS ( 1151 SELECT 1 1152 FROM clin.substance_intake 1153 WHERE fk_drug_component = %(pk_component)s 1154 LIMIT 1 1155 ) 1156 AND 1157 xmin = %(xmin_lnk_dose2drug)s 1158 RETURNING 1159 xmin AS xmin_lnk_dose2drug 1160 """ 1161 ] 1162 _updatable_fields = [ 1163 'pk_drug_product', 1164 'pk_dose' 1165 ] 1166 #--------------------------------------------------------
1167 - def format(self, left_margin=0, include_loincs=False):
1168 lines = [] 1169 lines.append('%s %s%s' % ( 1170 self._payload[self._idx['substance']], 1171 self._payload[self._idx['amount']], 1172 self.formatted_units 1173 )) 1174 lines.append(_('Component of %s (%s)') % ( 1175 self._payload[self._idx['product']], 1176 self._payload[self._idx['l10n_preparation']] 1177 )) 1178 if self._payload[self._idx['is_fake_product']]: 1179 lines.append(' ' + _('(not a real drug product)')) 1180 1181 if self._payload[self._idx['intake_instructions']] is not None: 1182 lines.append(_('Instructions: %s') % self._payload[self._idx['intake_instructions']]) 1183 if self._payload[self._idx['atc_substance']] is not None: 1184 lines.append(_('ATC (substance): %s') % self._payload[self._idx['atc_substance']]) 1185 if self._payload[self._idx['atc_drug']] is not None: 1186 lines.append(_('ATC (drug): %s') % self._payload[self._idx['atc_drug']]) 1187 if self._payload[self._idx['external_code']] is not None: 1188 lines.append('%s: %s' % ( 1189 self._payload[self._idx['external_code_type']], 1190 self._payload[self._idx['external_code']] 1191 )) 1192 1193 if include_loincs: 1194 if len(self._payload[self._idx['loincs']]) > 0: 1195 lines.append(_('LOINCs to monitor:')) 1196 lines.extend ([ 1197 ' %s%s%s' % ( 1198 loinc['loinc'], 1199 gmTools.coalesce(loinc['max_age_str'], '', ': ' + _('once within %s')), 1200 gmTools.coalesce(loinc['comment'], '', ' (%s)') 1201 ) for loinc in self._payload[self._idx['loincs']] 1202 ]) 1203 1204 return (' ' * left_margin) + ('\n' + (' ' * left_margin)).join(lines)
1205 1206 #--------------------------------------------------------
1207 - def exists_as_intake(self, pk_patient=None):
1208 return substance_intake_exists ( 1209 pk_component = self._payload[self._idx['pk_component']], 1210 pk_identity = pk_patient 1211 )
1212 1213 #--------------------------------------------------------
1214 - def turn_into_intake(self, emr=None, encounter=None, episode=None):
1215 return create_substance_intake ( 1216 pk_component = self._payload[self._idx['pk_component']], 1217 pk_encounter = encounter, 1218 pk_episode = episode 1219 )
1220 1221 #-------------------------------------------------------- 1222 # properties 1223 #--------------------------------------------------------
1224 - def _get_containing_drug(self):
1225 return cDrugProduct(aPK_obj = self._payload[self._idx['pk_drug_product']])
1226 1227 containing_drug = property(_get_containing_drug, lambda x:x) 1228 1229 #--------------------------------------------------------
1230 - def _get_is_in_use_by_patients(self):
1231 return self._payload[self._idx['is_in_use']]
1232 1233 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x) 1234 1235 #--------------------------------------------------------
1236 - def _get_substance_dose(self):
1237 return cSubstanceDose(aPK_obj = self._payload[self._idx['pk_dose']])
1238 1239 substance_dose = property(_get_substance_dose, lambda x:x) 1240 1241 #--------------------------------------------------------
1242 - def _get_substance(self):
1243 return cSubstance(aPK_obj = self._payload[self._idx['pk_substance']])
1244 1245 substance = property(_get_substance, lambda x:x) 1246 1247 #--------------------------------------------------------
1248 - def _get_formatted_units(self, short=True):
1249 return format_units ( 1250 self._payload[self._idx['unit']], 1251 self._payload[self._idx['dose_unit']], 1252 self._payload[self._idx['l10n_preparation']] 1253 )
1254 1255 formatted_units = property(_get_formatted_units, lambda x:x)
1256 1257 #------------------------------------------------------------
1258 -def get_drug_components():
1259 cmd = _SQL_get_drug_components % 'true ORDER BY product, substance' 1260 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 1261 return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
1262 1263 #------------------------------------------------------------
1264 -class cDrugComponentMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
1265 1266 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE) 1267 1268 _query_desc_only = """ 1269 SELECT DISTINCT ON (list_label) 1270 r_vdc1.pk_component 1271 AS data, 1272 (r_vdc1.substance || ' ' 1273 || r_vdc1.amount || r_vdc1.unit || ' ' 1274 || r_vdc1.preparation || ' (' 1275 || r_vdc1.product || ' [' 1276 || ( 1277 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 1278 FROM ref.v_drug_components r_vdc2 1279 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 1280 ) 1281 || ']' 1282 || ')' 1283 ) AS field_label, 1284 (r_vdc1.substance || ' ' 1285 || r_vdc1.amount || r_vdc1.unit || ' ' 1286 || r_vdc1.preparation || ' (' 1287 || r_vdc1.product || ' [' 1288 || ( 1289 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 1290 FROM ref.v_drug_components r_vdc2 1291 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 1292 ) 1293 || ']' 1294 || ')' 1295 ) AS list_label 1296 FROM ref.v_drug_components r_vdc1 1297 WHERE 1298 r_vdc1.substance %(fragment_condition)s 1299 OR 1300 r_vdc1.product %(fragment_condition)s 1301 ORDER BY list_label 1302 LIMIT 50""" 1303 1304 _query_desc_and_amount = """ 1305 SELECT DISTINCT ON (list_label) 1306 pk_component AS data, 1307 (r_vdc1.substance || ' ' 1308 || r_vdc1.amount || r_vdc1.unit || ' ' 1309 || r_vdc1.preparation || ' (' 1310 || r_vdc1.product || ' [' 1311 || ( 1312 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 1313 FROM ref.v_drug_components r_vdc2 1314 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 1315 ) 1316 || ']' 1317 || ')' 1318 ) AS field_label, 1319 (r_vdc1.substance || ' ' 1320 || r_vdc1.amount || r_vdc1.unit || ' ' 1321 || r_vdc1.preparation || ' (' 1322 || r_vdc1.product || ' [' 1323 || ( 1324 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ') 1325 FROM ref.v_drug_components r_vdc2 1326 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product 1327 ) 1328 || ']' 1329 || ')' 1330 ) AS list_label 1331 FROM ref.v_drug_components 1332 WHERE 1333 %(fragment_condition)s 1334 ORDER BY list_label 1335 LIMIT 50""" 1336 #--------------------------------------------------------
1337 - def getMatchesByPhrase(self, aFragment):
1338 """Return matches for aFragment at start of phrases.""" 1339 1340 if cDrugComponentMatchProvider._pattern.match(aFragment): 1341 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount] 1342 fragment_condition = """(substance ILIKE %(desc)s OR product ILIKE %(desc)s) 1343 AND 1344 amount::text ILIKE %(amount)s""" 1345 self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 1346 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 1347 else: 1348 self._queries = [cDrugComponentMatchProvider._query_desc_only] 1349 fragment_condition = "ILIKE %(fragment)s" 1350 self._args['fragment'] = "%s%%" % aFragment 1351 1352 return self._find_matches(fragment_condition)
1353 #--------------------------------------------------------
1354 - def getMatchesByWord(self, aFragment):
1355 """Return matches for aFragment at start of words inside phrases.""" 1356 1357 if cDrugComponentMatchProvider._pattern.match(aFragment): 1358 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount] 1359 1360 desc = regex.sub(r'\s*\d+$', '', aFragment) 1361 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False) 1362 1363 fragment_condition = """(substance ~* %(desc)s OR product ~* %(desc)s) 1364 AND 1365 amount::text ILIKE %(amount)s""" 1366 1367 self._args['desc'] = "( %s)|(^%s)" % (desc, desc) 1368 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 1369 else: 1370 self._queries = [cDrugComponentMatchProvider._query_desc_only] 1371 fragment_condition = "~* %(fragment)s" 1372 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False) 1373 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment) 1374 1375 return self._find_matches(fragment_condition)
1376 #--------------------------------------------------------
1377 - def getMatchesBySubstr(self, aFragment):
1378 """Return matches for aFragment as a true substring.""" 1379 1380 if cDrugComponentMatchProvider._pattern.match(aFragment): 1381 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount] 1382 fragment_condition = """(substance ILIKE %(desc)s OR product ILIKE %(desc)s) 1383 AND 1384 amount::text ILIKE %(amount)s""" 1385 self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment) 1386 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment) 1387 else: 1388 self._queries = [cDrugComponentMatchProvider._query_desc_only] 1389 fragment_condition = "ILIKE %(fragment)s" 1390 self._args['fragment'] = "%%%s%%" % aFragment 1391 1392 return self._find_matches(fragment_condition)
1393 1394 #============================================================ 1395 # drug products 1396 #------------------------------------------------------------ 1397 _SQL_get_drug_product = "SELECT * FROM ref.v_drug_products WHERE %s" 1398
1399 -class cDrugProduct(gmBusinessDBObject.cBusinessDBObject):
1400 """Represents a drug as marketed by a manufacturer or a generic drug product.""" 1401 1402 _cmd_fetch_payload = _SQL_get_drug_product % 'pk_drug_product = %s' 1403 _cmds_store_payload = [ 1404 """UPDATE ref.drug_product SET 1405 description = %(product)s, 1406 preparation = %(preparation)s, 1407 atc_code = gm.nullify_empty_string(%(atc)s), 1408 external_code = gm.nullify_empty_string(%(external_code)s), 1409 external_code_type = gm.nullify_empty_string(%(external_code_type)s), 1410 is_fake = %(is_fake_product)s, 1411 fk_data_source = %(pk_data_source)s 1412 WHERE 1413 pk = %(pk_drug_product)s 1414 AND 1415 xmin = %(xmin_drug_product)s 1416 RETURNING 1417 xmin AS xmin_drug_product 1418 """ 1419 ] 1420 _updatable_fields = [ 1421 'product', 1422 'preparation', 1423 'atc', 1424 'is_fake_product', 1425 'external_code', 1426 'external_code_type', 1427 'pk_data_source' 1428 ] 1429 #--------------------------------------------------------
1430 - def format(self, left_margin=0, include_component_details=False):
1431 lines = [] 1432 lines.append('%s (%s)' % ( 1433 self._payload[self._idx['product']], 1434 self._payload[self._idx['l10n_preparation']] 1435 ) 1436 ) 1437 if self._payload[self._idx['atc']] is not None: 1438 lines.append('ATC: %s' % self._payload[self._idx['atc']]) 1439 if self._payload[self._idx['external_code']] is not None: 1440 lines.append('%s: %s' % (self._payload[self._idx['external_code_type']], self._payload[self._idx['external_code']])) 1441 if len(self._payload[self._idx['components']]) > 0: 1442 lines.append(_('Components:')) 1443 for comp in self._payload[self._idx['components']]: 1444 lines.append(' %s %s %s' % ( 1445 comp['substance'], 1446 comp['amount'], 1447 format_units(comp['unit'], comp['dose_unit'], short = False) 1448 )) 1449 if include_component_details: 1450 if comp['intake_instructions'] is not None: 1451 lines.append(comp['intake_instructions']) 1452 lines.extend([ '%s%s%s' % ( 1453 l['loinc'], 1454 gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')), 1455 gmTools.coalesce(l['comment'], '', ' (%s)') 1456 ) for l in comp['loincs'] ]) 1457 1458 if self._payload[self._idx['is_fake_product']]: 1459 lines.append('') 1460 lines.append(_('this is a fake drug product')) 1461 if self.is_vaccine: 1462 lines.append(_('this is a vaccine')) 1463 1464 return (' ' * left_margin) + ('\n' + (' ' * left_margin)).join(lines)
1465 1466 #--------------------------------------------------------
1467 - def save_payload(self, conn=None):
1468 success, data = super(self.__class__, self).save_payload(conn = conn) 1469 1470 if not success: 1471 return (success, data) 1472 1473 if self._payload[self._idx['atc']] is not None: 1474 atc = self._payload[self._idx['atc']].strip() 1475 if atc != '': 1476 gmATC.propagate_atc ( 1477 link_obj = conn, 1478 substance = self._payload[self._idx['product']].strip(), 1479 atc = atc 1480 ) 1481 1482 return (success, data)
1483 1484 #--------------------------------------------------------
1485 - def set_substance_doses_as_components(self, substance_doses=None, link_obj=None):
1486 if self.is_in_use_by_patients: 1487 return False 1488 1489 pk_doses2keep = [ s['pk_dose'] for s in substance_doses ] 1490 _log.debug('setting components of "%s" from doses: %s', self._payload[self._idx['product']], pk_doses2keep) 1491 1492 args = {'pk_drug_product': self._payload[self._idx['pk_drug_product']]} 1493 queries = [] 1494 # INSERT those which are not there yet 1495 cmd = """ 1496 INSERT INTO ref.lnk_dose2drug ( 1497 fk_drug_product, 1498 fk_dose 1499 ) 1500 SELECT 1501 %(pk_drug_product)s, 1502 %(pk_dose)s 1503 WHERE NOT EXISTS ( 1504 SELECT 1 FROM ref.lnk_dose2drug 1505 WHERE 1506 fk_drug_product = %(pk_drug_product)s 1507 AND 1508 fk_dose = %(pk_dose)s 1509 )""" 1510 for pk_dose in pk_doses2keep: 1511 args['pk_dose'] = pk_dose 1512 queries.append({'cmd': cmd, 'args': args.copy()}) 1513 1514 # DELETE those that don't belong anymore 1515 args['doses2keep'] = tuple(pk_doses2keep) 1516 cmd = """ 1517 DELETE FROM ref.lnk_dose2drug 1518 WHERE 1519 fk_drug_product = %(pk_drug_product)s 1520 AND 1521 fk_dose NOT IN %(doses2keep)s""" 1522 queries.append({'cmd': cmd, 'args': args}) 1523 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries) 1524 self.refetch_payload(link_obj = link_obj) 1525 1526 return True
1527 1528 #--------------------------------------------------------
1529 - def add_component(self, substance=None, atc=None, amount=None, unit=None, dose_unit=None, pk_dose=None, pk_substance=None):
1530 1531 if pk_dose is None: 1532 if pk_substance is None: 1533 pk_dose = create_substance_dose(substance = substance, atc = atc, amount = amount, unit = unit, dose_unit = dose_unit)['pk_dose'] 1534 else: 1535 pk_dose = create_substance_dose(pk_substance = pk_substance, atc = atc, amount = amount, unit = unit, dose_unit = dose_unit)['pk_dose'] 1536 1537 args = { 1538 'pk_dose': pk_dose, 1539 'pk_drug_product': self.pk_obj 1540 } 1541 1542 cmd = """ 1543 INSERT INTO ref.lnk_dose2drug (fk_drug_product, fk_dose) 1544 SELECT 1545 %(pk_drug_product)s, 1546 %(pk_dose)s 1547 WHERE NOT EXISTS ( 1548 SELECT 1 FROM ref.lnk_dose2drug 1549 WHERE 1550 fk_drug_product = %(pk_drug_product)s 1551 AND 1552 fk_dose = %(pk_dose)s 1553 )""" 1554 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1555 self.refetch_payload()
1556 1557 #------------------------------------------------------------
1558 - def remove_component(self, pk_dose=None, pk_component=None):
1559 if len(self._payload[self._idx['components']]) == 1: 1560 _log.error('will not remove the only component of a drug') 1561 return False 1562 1563 args = {'pk_drug_product': self.pk_obj, 'pk_dose': pk_dose, 'pk_component': pk_component} 1564 1565 if pk_component is None: 1566 cmd = """DELETE FROM ref.lnk_dose2drug WHERE 1567 fk_drug_product = %(pk_drug_product)s 1568 AND 1569 fk_dose = %(pk_dose)s 1570 AND 1571 NOT EXISTS ( 1572 SELECT 1 FROM clin.v_substance_intakes 1573 WHERE pk_dose = %(pk_dose)s 1574 LIMIT 1 1575 )""" 1576 else: 1577 cmd = """DELETE FROM ref.lnk_dose2drug WHERE 1578 pk = %(pk_component)s 1579 AND 1580 NOT EXISTS ( 1581 SELECT 1 FROM clin.substance_intake 1582 WHERE fk_drug_component = %(pk_component)s 1583 LIMIT 1 1584 )""" 1585 1586 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1587 self.refetch_payload() 1588 return True
1589 1590 #--------------------------------------------------------
1591 - def exists_as_intake(self, pk_patient=None):
1592 return substance_intake_exists ( 1593 pk_drug_product = self._payload[self._idx['pk_drug_product']], 1594 pk_identity = pk_patient 1595 )
1596 1597 #--------------------------------------------------------
1598 - def turn_into_intake(self, emr=None, encounter=None, episode=None):
1599 return create_substance_intake ( 1600 pk_drug_product = self._payload[self._idx['pk_drug_product']], 1601 pk_encounter = encounter, 1602 pk_episode = episode 1603 )
1604 1605 #--------------------------------------------------------
1606 - def delete_associated_vaccine(self):
1607 if self._payload[self._idx['is_vaccine']] is False: 1608 return True 1609 1610 args = {'pk_product': self._payload[self._idx['pk_drug_product']]} 1611 cmd = """DELETE FROM ref.vaccine 1612 WHERE 1613 fk_drug_product = %(pk_product)s 1614 AND 1615 -- not in use: 1616 NOT EXISTS ( 1617 SELECT 1 FROM clin.vaccination WHERE fk_vaccine = ( 1618 select pk from ref.vaccine where fk_drug_product = %(pk_product)s 1619 ) 1620 ) 1621 RETURNING *""" 1622 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True) 1623 if len(rows) == 0: 1624 _log.debug('cannot delete vaccine on: %s', self) 1625 return False 1626 return True
1627 1628 #-------------------------------------------------------- 1629 # properties 1630 #--------------------------------------------------------
1631 - def _get_external_code(self):
1632 return self._payload[self._idx['external_code']]
1633 1634 external_code = property(_get_external_code, lambda x:x) 1635 1636 #--------------------------------------------------------
1637 - def _get_external_code_type(self):
1638 # FIXME: maybe evaluate fk_data_source ? 1639 return self._payload[self._idx['external_code_type']]
1640 1641 external_code_type = property(_get_external_code_type, lambda x:x) 1642 1643 #--------------------------------------------------------
1644 - def _get_components(self):
1645 cmd = _SQL_get_drug_components % 'pk_drug_product = %(product)s' 1646 args = {'product': self._payload[self._idx['pk_drug_product']]} 1647 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1648 return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
1649 1650 components = property(_get_components, lambda x:x) 1651 1652 #--------------------------------------------------------
1653 - def _get_components_as_doses(self):
1654 pk_doses = [ c['pk_dose'] for c in self._payload[self._idx['components']] ] 1655 if len(pk_doses) == 0: 1656 return [] 1657 cmd = _SQL_get_substance_dose % 'pk_dose IN %(pks)s' 1658 args = {'pks': tuple(pk_doses)} 1659 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1660 return [ cSubstanceDose(row = {'data': r, 'idx': idx, 'pk_field': 'pk_dose'}) for r in rows ]
1661 1662 components_as_doses = property(_get_components_as_doses, lambda x:x) 1663 1664 #--------------------------------------------------------
1666 pk_substances = [ c['pk_substance'] for c in self._payload[self._idx['components']] ] 1667 if len(pk_substances) == 0: 1668 return [] 1669 cmd = _SQL_get_substance % 'pk_substance IN %(pks)s' 1670 args = {'pks': tuple(pk_substances)} 1671 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1672 return [ cSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance'}) for r in rows ]
1673 1674 components_as_substances = property(_get_components_as_substances, lambda x:x) 1675 1676 #--------------------------------------------------------
1677 - def _get_is_fake_product(self):
1678 return self._payload[self._idx['is_fake_product']]
1679 1680 is_fake_product = property(_get_is_fake_product, lambda x:x) 1681 1682 #--------------------------------------------------------
1683 - def _get_is_vaccine(self):
1684 return self._payload[self._idx['is_vaccine']]
1685 1686 is_vaccine = property(_get_is_vaccine, lambda x:x) 1687 1688 #--------------------------------------------------------
1689 - def _get_is_in_use_by_patients(self):
1690 cmd = """ 1691 SELECT EXISTS ( 1692 SELECT 1 FROM clin.substance_intake WHERE 1693 fk_drug_component IN ( 1694 SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk)s 1695 ) 1696 LIMIT 1 1697 )""" 1698 args = {'pk': self.pk_obj} 1699 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1700 return rows[0][0]
1701 1702 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x) 1703 1704 #--------------------------------------------------------
1705 - def _get_is_in_use_as_vaccine(self):
1706 if self._payload[self._idx['is_vaccine']] is False: 1707 return False 1708 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.vaccination WHERE fk_vaccine = (select pk from ref.vaccine where fk_drug_product = %(pk)s))' 1709 args = {'pk': self.pk_obj} 1710 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1711 return rows[0][0]
1712 1713 is_in_use_as_vaccine = property(_get_is_in_use_as_vaccine, lambda x:x)
1714 1715 #------------------------------------------------------------
1716 -def get_drug_products():
1717 cmd = _SQL_get_drug_product % 'TRUE ORDER BY product' 1718 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 1719 return [ cDrugProduct(row = {'data': r, 'idx': idx, 'pk_field': 'pk_drug_product'}) for r in rows ]
1720 1721 #------------------------------------------------------------
1722 -def get_drug_by_name(product_name=None, preparation=None, link_obj=None):
1723 args = {'prod_name': product_name, 'prep': preparation} 1724 cmd = 'SELECT * FROM ref.v_drug_products WHERE lower(product) = lower(%(prod_name)s) AND lower(preparation) = lower(%(prep)s)' 1725 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1726 if len(rows) == 0: 1727 return None 1728 return cDrugProduct(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_drug_product'})
1729 1730 #------------------------------------------------------------
1731 -def get_drug_by_atc(atc=None, preparation=None, link_obj=None):
1732 args = {'atc': atc, 'prep': preparation} 1733 cmd = 'SELECT * FROM ref.v_drug_products WHERE lower(atc) = lower(%(atc)s) AND lower(preparation) = lower(%(prep)s)' 1734 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1735 if len(rows) == 0: 1736 return None 1737 return cDrugProduct(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_drug_product'}, link_obj = link_obj)
1738 1739 #------------------------------------------------------------
1740 -def create_drug_product(product_name=None, preparation=None, return_existing=False, link_obj=None, doses=None):
1741 1742 if preparation is None: 1743 preparation = _('units') 1744 1745 if preparation.strip() == '': 1746 preparation = _('units') 1747 1748 if return_existing: 1749 drug = get_drug_by_name(product_name = product_name, preparation = preparation, link_obj = link_obj) 1750 if drug is not None: 1751 return drug 1752 1753 cmd = 'INSERT INTO ref.drug_product (description, preparation) VALUES (%(prod_name)s, %(prep)s) RETURNING pk' 1754 args = {'prod_name': product_name, 'prep': preparation} 1755 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 1756 product = cDrugProduct(aPK_obj = rows[0]['pk'], link_obj = link_obj) 1757 if doses is not None: 1758 product.set_substance_doses_as_components(substance_doses = doses, link_obj = link_obj) 1759 1760 return product
1761 1762 #------------------------------------------------------------
1763 -def create_drug_product_by_atc(atc=None, product_name=None, preparation=None, return_existing=False, link_obj=None):
1764 1765 if atc is None: 1766 raise ValueError('cannot create drug product by ATC without ATC') 1767 1768 if preparation is None: 1769 preparation = _('units') 1770 1771 if preparation.strip() == '': 1772 preparation = _('units') 1773 1774 if return_existing: 1775 drug = get_drug_by_atc(atc = atc, preparation = preparation, link_obj = link_obj) 1776 if drug is not None: 1777 return drug 1778 1779 drug = create_drug_product ( 1780 link_obj = link_obj, 1781 product_name = product_name, 1782 preparation = preparation, 1783 return_existing = False 1784 ) 1785 drug['atc'] = atc 1786 drug.save(conn = link_obj) 1787 return drug
1788 1789 #------------------------------------------------------------
1790 -def delete_drug_product(pk_drug_product=None):
1791 args = {'pk': pk_drug_product} 1792 queries = [] 1793 # delete components 1794 cmd = """ 1795 DELETE FROM ref.lnk_dose2drug 1796 WHERE 1797 fk_drug_product = %(pk)s 1798 AND 1799 NOT EXISTS ( 1800 SELECT 1 1801 FROM clin.v_substance_intakes 1802 WHERE pk_drug_product = %(pk)s 1803 LIMIT 1 1804 )""" 1805 queries.append({'cmd': cmd, 'args': args}) 1806 # delete drug 1807 cmd = """ 1808 DELETE FROM ref.drug_product 1809 WHERE 1810 pk = %(pk)s 1811 AND 1812 NOT EXISTS ( 1813 SELECT 1 FROM clin.v_substance_intakes 1814 WHERE pk_drug_product = %(pk)s 1815 LIMIT 1 1816 )""" 1817 queries.append({'cmd': cmd, 'args': args}) 1818 gmPG2.run_rw_queries(queries = queries)
1819 1820 #============================================================ 1821 # substance intakes 1822 #------------------------------------------------------------ 1823 _SQL_get_substance_intake = "SELECT * FROM clin.v_substance_intakes WHERE %s" 1824
1825 -class cSubstanceIntakeEntry(gmBusinessDBObject.cBusinessDBObject):
1826 """Represents a substance currently taken by a patient.""" 1827 1828 _cmd_fetch_payload = _SQL_get_substance_intake % 'pk_substance_intake = %s' 1829 _cmds_store_payload = [ 1830 """UPDATE clin.substance_intake SET 1831 -- if .comment_on_start = '?' then .started will be mapped to NULL 1832 -- in the view, also, .started CANNOT be NULL any other way so far, 1833 -- so do not attempt to set .clin_when if .started is NULL 1834 clin_when = ( 1835 CASE 1836 WHEN %(started)s IS NULL THEN clin_when 1837 ELSE %(started)s 1838 END 1839 )::timestamp with time zone, 1840 comment_on_start = gm.nullify_empty_string(%(comment_on_start)s), 1841 discontinued = %(discontinued)s, 1842 discontinue_reason = gm.nullify_empty_string(%(discontinue_reason)s), 1843 schedule = gm.nullify_empty_string(%(schedule)s), 1844 aim = gm.nullify_empty_string(%(aim)s), 1845 narrative = gm.nullify_empty_string(%(notes)s), 1846 intake_is_approved_of = %(intake_is_approved_of)s, 1847 harmful_use_type = %(harmful_use_type)s, 1848 fk_episode = %(pk_episode)s, 1849 -- only used to document "last checked" such that 1850 -- .clin_when -> .started does not have to change meaning 1851 fk_encounter = %(pk_encounter)s, 1852 1853 is_long_term = ( 1854 case 1855 when ( 1856 (%(is_long_term)s is False) 1857 and 1858 (%(duration)s is NULL) 1859 ) is True then null 1860 else %(is_long_term)s 1861 end 1862 )::boolean, 1863 1864 duration = ( 1865 case 1866 when %(is_long_term)s is True then null 1867 else %(duration)s 1868 end 1869 )::interval 1870 WHERE 1871 pk = %(pk_substance_intake)s 1872 AND 1873 xmin = %(xmin_substance_intake)s 1874 RETURNING 1875 xmin as xmin_substance_intake 1876 """ 1877 ] 1878 _updatable_fields = [ 1879 'started', 1880 'comment_on_start', 1881 'discontinued', 1882 'discontinue_reason', 1883 'intake_is_approved_of', 1884 'schedule', 1885 'duration', 1886 'aim', 1887 'is_long_term', 1888 'notes', 1889 'pk_episode', 1890 'pk_encounter', 1891 'harmful_use_type' 1892 ] 1893 1894 #--------------------------------------------------------
1895 - def format_maximum_information(self, patient=None):
1896 return self.format ( 1897 single_line = False, 1898 show_all_product_components = True, 1899 include_metadata = True, 1900 date_format = '%Y %b %d', 1901 include_instructions = True, 1902 include_loincs = True 1903 ).split('\n')
1904 1905 #--------------------------------------------------------
1906 - def format(self, left_margin=0, date_format='%Y %b %d', single_line=True, allergy=None, show_all_product_components=False, include_metadata=True, include_instructions=False, include_loincs=False):
1907 1908 # medication 1909 if self._payload[self._idx['harmful_use_type']] is None: 1910 if single_line: 1911 return self.format_as_single_line(left_margin = left_margin, date_format = date_format) 1912 return self.format_as_multiple_lines ( 1913 left_margin = left_margin, 1914 date_format = date_format, 1915 allergy = allergy, 1916 show_all_product_components = show_all_product_components, 1917 include_instructions = include_instructions 1918 ) 1919 1920 # abuse 1921 if single_line: 1922 return self.format_as_single_line_abuse(left_margin = left_margin, date_format = date_format) 1923 1924 return self.format_as_multiple_lines_abuse(left_margin = left_margin, date_format = date_format, include_metadata = include_metadata)
1925 1926 #--------------------------------------------------------
1927 - def format_as_single_line_abuse(self, left_margin=0, date_format='%Y %b %d'):
1928 return '%s%s: %s (%s)' % ( 1929 ' ' * left_margin, 1930 self._payload[self._idx['substance']], 1931 self.harmful_use_type_string, 1932 gmDateTime.pydt_strftime(self._payload[self._idx['last_checked_when']], '%b %Y') 1933 )
1934 1935 #--------------------------------------------------------
1936 - def format_as_single_line(self, left_margin=0, date_format='%Y %b %d'):
1937 1938 if self._payload[self._idx['is_currently_active']]: 1939 if self._payload[self._idx['duration']] is None: 1940 duration = gmTools.bool2subst ( 1941 self._payload[self._idx['is_long_term']], 1942 _('long-term'), 1943 _('short-term'), 1944 _('?short-term') 1945 ) 1946 else: 1947 duration = gmDateTime.format_interval ( 1948 self._payload[self._idx['duration']], 1949 accuracy_wanted = gmDateTime.acc_days 1950 ) 1951 else: 1952 duration = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], date_format) 1953 1954 line = '%s%s (%s %s): %s %s%s (%s)' % ( 1955 ' ' * left_margin, 1956 self.medically_formatted_start, 1957 gmTools.u_arrow2right, 1958 duration, 1959 self._payload[self._idx['substance']], 1960 self._payload[self._idx['amount']], 1961 self.formatted_units, 1962 gmTools.bool2subst(self._payload[self._idx['is_currently_active']], _('ongoing'), _('inactive'), _('?ongoing')) 1963 ) 1964 1965 return line
1966 1967 #--------------------------------------------------------
1968 - def format_as_multiple_lines_abuse(self, left_margin=0, date_format='%Y %b %d', include_metadata=True):
1969 1970 txt = '' 1971 if include_metadata: 1972 txt = _('Substance abuse entry [#%s]\n') % self._payload[self._idx['pk_substance_intake']] 1973 txt += ' ' + _('Substance: %s [#%s]%s\n') % ( 1974 self._payload[self._idx['substance']], 1975 self._payload[self._idx['pk_substance']], 1976 gmTools.coalesce(self._payload[self._idx['atc_substance']], '', ' ATC %s') 1977 ) 1978 txt += ' ' + _('Use type: %s\n') % self.harmful_use_type_string 1979 txt += ' ' + _('Last checked: %s\n') % gmDateTime.pydt_strftime(self._payload[self._idx['last_checked_when']], '%Y %b %d') 1980 if self._payload[self._idx['discontinued']] is not None: 1981 txt += _(' Discontinued %s\n') % ( 1982 gmDateTime.pydt_strftime ( 1983 self._payload[self._idx['discontinued']], 1984 format = date_format, 1985 accuracy = gmDateTime.acc_days 1986 ) 1987 ) 1988 txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Notes: %s\n')) 1989 if include_metadata: 1990 txt += '\n' 1991 txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % { 1992 'row_ver': self._payload[self._idx['row_version']], 1993 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]), 1994 'mod_by': self._payload[self._idx['modified_by']] 1995 } 1996 1997 return txt
1998 1999 #--------------------------------------------------------
2000 - def format_as_multiple_lines(self, left_margin=0, date_format='%Y %b %d', allergy=None, show_all_product_components=False, include_instructions=False, include_loincs=False):
2001 2002 txt = _('Substance intake entry (%s, %s) [#%s] \n') % ( 2003 gmTools.bool2subst ( 2004 boolean = self._payload[self._idx['is_currently_active']], 2005 true_return = gmTools.bool2subst ( 2006 boolean = self._payload[self._idx['seems_inactive']], 2007 true_return = _('active, needs check'), 2008 false_return = _('active'), 2009 none_return = _('assumed active') 2010 ), 2011 false_return = _('inactive') 2012 ), 2013 gmTools.bool2subst ( 2014 boolean = self._payload[self._idx['intake_is_approved_of']], 2015 true_return = _('approved'), 2016 false_return = _('unapproved') 2017 ), 2018 self._payload[self._idx['pk_substance_intake']] 2019 ) 2020 2021 if allergy is not None: 2022 certainty = gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected')) 2023 txt += '\n' 2024 txt += ' !! ---- Cave ---- !!\n' 2025 txt += ' %s (%s): %s (%s)\n' % ( 2026 allergy['l10n_type'], 2027 certainty, 2028 allergy['descriptor'], 2029 gmTools.coalesce(allergy['reaction'], '')[:40] 2030 ) 2031 txt += '\n' 2032 2033 txt += ' ' + _('Substance: %s [#%s]\n') % (self._payload[self._idx['substance']], self._payload[self._idx['pk_substance']]) 2034 txt += ' ' + _('Preparation: %s\n') % self._payload[self._idx['l10n_preparation']] 2035 txt += ' ' + _('Amount per dose: %s %s') % ( 2036 self._payload[self._idx['amount']], 2037 self._get_formatted_units(short = False) 2038 ) 2039 txt += '\n' 2040 txt += gmTools.coalesce(self._payload[self._idx['atc_substance']], '', _(' ATC (substance): %s\n')) 2041 if include_loincs and (len(self._payload[self._idx['loincs']]) > 0): 2042 loincs = """ 2043 %s %s 2044 %s %s""" % ( 2045 (' ' * left_margin), 2046 _('LOINCs to monitor:'), 2047 (' ' * left_margin), 2048 ('\n' + (' ' * (left_margin + 1))).join ([ 2049 '%s%s%s' % ( 2050 l['loinc'], 2051 gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')), 2052 gmTools.coalesce(l['comment'], '', ' (%s)') 2053 ) for l in self._payload[self._idx['loincs']] 2054 ]) 2055 ) 2056 txt += '\n' 2057 2058 txt += '\n' 2059 2060 txt += _(' Product name: %s [#%s]\n') % (self._payload[self._idx['product']], self._payload[self._idx['pk_drug_product']]) 2061 txt += gmTools.coalesce(self._payload[self._idx['atc_drug']], '', _(' ATC (drug): %s\n')) 2062 if show_all_product_components: 2063 product = self.containing_drug 2064 if len(product['components']) > 1: 2065 for comp in product['components']: 2066 if comp['pk_substance'] == self._payload[self._idx['substance']]: 2067 continue 2068 txt += (' ' + _('Other component: %s %s %s\n') % ( 2069 comp['substance'], 2070 comp['amount'], 2071 format_units(comp['unit'], comp['dose_unit']) 2072 )) 2073 txt += gmTools.coalesce(comp['intake_instructions'], '', ' ' + _('Intake: %s') + '\n') 2074 if include_loincs and (len(comp['loincs']) > 0): 2075 txt += (' ' + _('LOINCs to monitor:') + '\n') 2076 txt += '\n'.join([ ' %s%s%s' % ( 2077 l['loinc'], 2078 gmTools.coalesce(l['max_age_str'], '', ': %s'), 2079 gmTools.coalesce(l['comment'], '', ' (%s)') 2080 ) for l in comp['loincs'] ]) 2081 2082 txt += '\n' 2083 2084 txt += gmTools.coalesce(self._payload[self._idx['schedule']], '', _(' Regimen: %s\n')) 2085 2086 if self._payload[self._idx['is_long_term']]: 2087 duration = ' %s %s' % (gmTools.u_arrow2right, gmTools.u_infinity) 2088 else: 2089 if self._payload[self._idx['duration']] is None: 2090 duration = '' 2091 else: 2092 duration = ' %s %s' % (gmTools.u_arrow2right, gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days)) 2093 2094 txt += _(' Started %s%s%s\n') % ( 2095 self.medically_formatted_start, 2096 duration, 2097 gmTools.bool2subst(self._payload[self._idx['is_long_term']], _(' (long-term)'), _(' (short-term)'), '') 2098 ) 2099 2100 if self._payload[self._idx['discontinued']] is not None: 2101 txt += _(' Discontinued %s\n') % ( 2102 gmDateTime.pydt_strftime ( 2103 self._payload[self._idx['discontinued']], 2104 format = date_format, 2105 accuracy = gmDateTime.acc_days 2106 ) 2107 ) 2108 txt += gmTools.coalesce(self._payload[self._idx['discontinue_reason']], '', _(' Reason: %s\n')) 2109 2110 txt += '\n' 2111 2112 txt += gmTools.coalesce(self._payload[self._idx['aim']], '', _(' Aim: %s\n')) 2113 txt += gmTools.coalesce(self._payload[self._idx['episode']], '', _(' Episode: %s\n')) 2114 txt += gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n')) 2115 txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Advice: %s\n')) 2116 if self._payload[self._idx['intake_instructions']] is not None: 2117 txt += (' '+ (_('Intake: %s') % self._payload[self._idx['intake_instructions']]) + '\n') 2118 if len(self._payload[self._idx['loincs']]) > 0: 2119 txt += (' ' + _('LOINCs to monitor:') + '\n') 2120 txt += '\n'.join([ ' %s%s%s' % ( 2121 l['loinc'], 2122 gmTools.coalesce(l['max_age_str'], '', ': %s'), 2123 gmTools.coalesce(l['comment'], '', ' (%s)') 2124 ) for l in self._payload[self._idx['loincs']] ]) 2125 2126 txt += '\n' 2127 2128 txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % { 2129 'row_ver': self._payload[self._idx['row_version']], 2130 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]), 2131 'mod_by': self._payload[self._idx['modified_by']] 2132 } 2133 2134 return txt
2135 2136 #--------------------------------------------------------
2137 - def turn_into_allergy(self, encounter_id=None, allergy_type='allergy'):
2138 allg = gmAllergy.create_allergy ( 2139 allergene = self._payload[self._idx['substance']], 2140 allg_type = allergy_type, 2141 episode_id = self._payload[self._idx['pk_episode']], 2142 encounter_id = encounter_id 2143 ) 2144 allg['substance'] = gmTools.coalesce ( 2145 self._payload[self._idx['product']], 2146 self._payload[self._idx['substance']] 2147 ) 2148 allg['reaction'] = self._payload[self._idx['discontinue_reason']] 2149 allg['atc_code'] = gmTools.coalesce(self._payload[self._idx['atc_substance']], self._payload[self._idx['atc_drug']]) 2150 if self._payload[self._idx['external_code_product']] is not None: 2151 allg['substance_code'] = '%s::::%s' % (self._payload[self._idx['external_code_type_product']], self._payload[self._idx['external_code_product']]) 2152 2153 if self._payload[self._idx['pk_drug_product']] is None: 2154 allg['generics'] = self._payload[self._idx['substance']] 2155 else: 2156 comps = [ c['substance'] for c in self.containing_drug.components ] 2157 if len(comps) == 0: 2158 allg['generics'] = self._payload[self._idx['substance']] 2159 else: 2160 allg['generics'] = '; '.join(comps) 2161 2162 allg.save() 2163 return allg
2164 2165 #--------------------------------------------------------
2166 - def delete(self):
2167 return delete_substance_intake(pk_intake = self._payload[self._idx['pk_substance_intake']])
2168 2169 #-------------------------------------------------------- 2170 # properties 2171 #--------------------------------------------------------
2173 2174 if self._payload[self._idx['harmful_use_type']] is None: 2175 return _('medication, not abuse') 2176 if self._payload[self._idx['harmful_use_type']] == 0: 2177 return _('no or non-harmful use') 2178 if self._payload[self._idx['harmful_use_type']] == 1: 2179 return _('presently harmful use') 2180 if self._payload[self._idx['harmful_use_type']] == 2: 2181 return _('presently addicted') 2182 if self._payload[self._idx['harmful_use_type']] == 3: 2183 return _('previously addicted')
2184 2185 harmful_use_type_string = property(_get_harmful_use_type_string) 2186 2187 #--------------------------------------------------------
2188 - def _get_external_code(self):
2189 drug = self.containing_drug 2190 2191 if drug is None: 2192 return None 2193 2194 return drug.external_code
2195 2196 external_code = property(_get_external_code, lambda x:x) 2197 2198 #--------------------------------------------------------
2199 - def _get_external_code_type(self):
2200 drug = self.containing_drug 2201 2202 if drug is None: 2203 return None 2204 2205 return drug.external_code_type
2206 2207 external_code_type = property(_get_external_code_type, lambda x:x) 2208 2209 #--------------------------------------------------------
2210 - def _get_containing_drug(self):
2211 if self._payload[self._idx['pk_drug_product']] is None: 2212 return None 2213 2214 return cDrugProduct(aPK_obj = self._payload[self._idx['pk_drug_product']])
2215 2216 containing_drug = property(_get_containing_drug, lambda x:x) 2217 2218 #--------------------------------------------------------
2219 - def _get_formatted_units(self, short=True):
2220 return format_units ( 2221 self._payload[self._idx['unit']], 2222 self._payload[self._idx['dose_unit']], 2223 self._payload[self._idx['l10n_preparation']], 2224 short = short 2225 )
2226 2227 formatted_units = property(_get_formatted_units, lambda x:x) 2228 2229 #--------------------------------------------------------
2231 if self._payload[self._idx['comment_on_start']] == '?': 2232 return '?' 2233 2234 start_prefix = '' 2235 if self._payload[self._idx['comment_on_start']] is not None: 2236 start_prefix = gmTools.u_almost_equal_to 2237 2238 duration_taken = gmDateTime.pydt_now_here() - self._payload[self._idx['started']] 2239 2240 three_months = pydt.timedelta(weeks = 13, days = 3) 2241 if duration_taken < three_months: 2242 return _('%s%s: %s ago%s') % ( 2243 start_prefix, 2244 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b %d', 'utf8', gmDateTime.acc_days), 2245 gmDateTime.format_interval_medically(duration_taken), 2246 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' (%s)') 2247 ) 2248 2249 five_years = pydt.timedelta(weeks = 265) 2250 if duration_taken < five_years: 2251 return _('%s%s: %s ago (%s)') % ( 2252 start_prefix, 2253 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b', 'utf8', gmDateTime.acc_months), 2254 gmDateTime.format_interval_medically(duration_taken), 2255 gmTools.coalesce ( 2256 self._payload[self._idx['comment_on_start']], 2257 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days), 2258 ) 2259 ) 2260 2261 return _('%s%s: %s ago (%s)') % ( 2262 start_prefix, 2263 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y', 'utf8', gmDateTime.acc_years), 2264 gmDateTime.format_interval_medically(duration_taken), 2265 gmTools.coalesce ( 2266 self._payload[self._idx['comment_on_start']], 2267 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days), 2268 ) 2269 )
2270 2271 medically_formatted_start = property(_get_medically_formatted_start, lambda x:x) 2272 2273 #--------------------------------------------------------
2275 2276 # format intro 2277 if gmDateTime.pydt_is_today(self._payload[self._idx['discontinued']]): 2278 intro = _('until today') 2279 else: 2280 ended_ago = now - self._payload[self._idx['discontinued']] 2281 intro = _('until %s%s ago') % ( 2282 gmTools.u_almost_equal_to, 2283 gmDateTime.format_interval_medically(ended_ago), 2284 ) 2285 2286 # format start 2287 if self._payload[self._idx['started']] is None: 2288 start = gmTools.coalesce(self._payload[self._idx['comment_on_start']], '?') 2289 else: 2290 start = '%s%s%s' % ( 2291 gmTools.bool2subst((self._payload[self._idx['comment_on_start']] is None), '', gmTools.u_almost_equal_to), 2292 gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y %b %d', accuracy = gmDateTime.acc_days), 2293 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]') 2294 ) 2295 2296 # format duration taken 2297 if self._payload[self._idx['started']] is None: 2298 duration_taken_str = '?' 2299 else: 2300 duration_taken = self._payload[self._idx['discontinued']] - self._payload[self._idx['started']] + pydt.timedelta(days = 1) 2301 duration_taken_str = gmDateTime.format_interval (duration_taken, gmDateTime.acc_days) 2302 2303 # format duration planned 2304 if self._payload[self._idx['duration']] is None: 2305 duration_planned_str = '' 2306 else: 2307 duration_planned_str = _(' [planned: %s]') % gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days) 2308 2309 # format end 2310 end = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%Y %b %d', 'utf8', gmDateTime.acc_days) 2311 2312 # assemble 2313 txt = '%s (%s %s %s%s %s %s)' % ( 2314 intro, 2315 start, 2316 gmTools.u_arrow2right_thick, 2317 duration_taken_str, 2318 duration_planned_str, 2319 gmTools.u_arrow2right_thick, 2320 end 2321 ) 2322 return txt
2323 2324 #--------------------------------------------------------
2326 2327 now = gmDateTime.pydt_now_here() 2328 2329 # medications stopped today or before today 2330 if self._payload[self._idx['discontinued']] is not None: 2331 if (self._payload[self._idx['discontinued']] < now) or (gmDateTime.pydt_is_today(self._payload[self._idx['discontinued']])): 2332 return self._get_medically_formatted_start_end_of_stopped(now) 2333 2334 # ongoing medications 2335 arrow_parts = [] 2336 2337 # format start 2338 if self._payload[self._idx['started']] is None: 2339 start_str = gmTools.coalesce(self._payload[self._idx['comment_on_start']], '?') 2340 else: 2341 start_prefix = gmTools.bool2subst((self._payload[self._idx['comment_on_start']] is None), '', gmTools.u_almost_equal_to) 2342 # starts today 2343 if gmDateTime.pydt_is_today(self._payload[self._idx['started']]): 2344 start_str = _('today (%s)') % gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y %b %d', accuracy = gmDateTime.acc_days) 2345 # started in the past 2346 elif self._payload[self._idx['started']] < now: 2347 started_ago = now - self._payload[self._idx['started']] 2348 three_months = pydt.timedelta(weeks = 13, days = 3) 2349 five_years = pydt.timedelta(weeks = 265) 2350 if started_ago < three_months: 2351 start_str = _('%s%s%s (%s%s ago, in %s)') % ( 2352 start_prefix, 2353 gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%b %d', accuracy = gmDateTime.acc_days), 2354 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'), 2355 gmTools.u_almost_equal_to, 2356 gmDateTime.format_interval_medically(started_ago), 2357 gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y', accuracy = gmDateTime.acc_days) 2358 ) 2359 elif started_ago < five_years: 2360 start_str = _('%s%s%s (%s%s ago, %s)') % ( 2361 start_prefix, 2362 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b', 'utf8', gmDateTime.acc_months), 2363 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'), 2364 gmTools.u_almost_equal_to, 2365 gmDateTime.format_interval_medically(started_ago), 2366 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days) 2367 ) 2368 else: 2369 start_str = _('%s%s%s (%s%s ago, %s)') % ( 2370 start_prefix, 2371 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y', 'utf8', gmDateTime.acc_years), 2372 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'), 2373 gmTools.u_almost_equal_to, 2374 gmDateTime.format_interval_medically(started_ago), 2375 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days), 2376 ) 2377 # starts in the future 2378 else: 2379 starts_in = self._payload[self._idx['started']] - now 2380 start_str = _('%s%s%s (in %s%s)') % ( 2381 start_prefix, 2382 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b %d', 'utf8', gmDateTime.acc_days), 2383 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'), 2384 gmTools.u_almost_equal_to, 2385 gmDateTime.format_interval_medically(starts_in) 2386 ) 2387 2388 arrow_parts.append(start_str) 2389 2390 # format durations 2391 durations = [] 2392 if self._payload[self._idx['discontinued']] is not None: 2393 if self._payload[self._idx['started']] is not None: 2394 duration_documented = self._payload[self._idx['discontinued']] - self._payload[self._idx['started']] 2395 durations.append(_('%s (documented)') % gmDateTime.format_interval(duration_documented, gmDateTime.acc_days)) 2396 if self._payload[self._idx['duration']] is not None: 2397 durations.append(_('%s (plan)') % gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days)) 2398 if len(durations) == 0: 2399 if self._payload[self._idx['is_long_term']]: 2400 duration_str = gmTools.u_infinity 2401 else: 2402 duration_str = '?' 2403 else: 2404 duration_str = ', '.join(durations) 2405 2406 arrow_parts.append(duration_str) 2407 2408 # format end 2409 if self._payload[self._idx['discontinued']] is None: 2410 if self._payload[self._idx['duration']] is None: 2411 end_str = '?' 2412 else: 2413 if self._payload[self._idx['started']] is None: 2414 end_str = '?' 2415 else: 2416 planned_end = self._payload[self._idx['started']] + self._payload[self._idx['duration']] - pydt.timedelta(days = 1) 2417 if planned_end.year == now.year: 2418 end_template = '%b %d' 2419 if planned_end < now: 2420 planned_end_from_now_str = _('%s ago, in %s') % (gmDateTime.format_interval(now - planned_end, gmDateTime.acc_days), planned_end.year) 2421 else: 2422 planned_end_from_now_str = _('in %s, %s') % (gmDateTime.format_interval(planned_end - now, gmDateTime.acc_days), planned_end.year) 2423 else: 2424 end_template = '%Y' 2425 if planned_end < now: 2426 planned_end_from_now_str = _('%s ago = %s') % ( 2427 gmDateTime.format_interval(now - planned_end, gmDateTime.acc_days), 2428 gmDateTime.pydt_strftime(planned_end, '%b %d', 'utf8', gmDateTime.acc_days) 2429 ) 2430 else: 2431 planned_end_from_now_str = _('in %s = %s') % ( 2432 gmDateTime.format_interval(planned_end - now, gmDateTime.acc_days), 2433 gmDateTime.pydt_strftime(planned_end, '%b %d', 'utf8', gmDateTime.acc_days) 2434 ) 2435 end_str = '%s (%s)' % ( 2436 gmDateTime.pydt_strftime(planned_end, end_template, 'utf8', gmDateTime.acc_days), 2437 planned_end_from_now_str 2438 ) 2439 else: 2440 if gmDateTime.is_today(self._payload[self._idx['discontinued']]): 2441 end_str = _('today') 2442 elif self._payload[self._idx['discontinued']].year == now.year: 2443 end_date_template = '%b %d' 2444 if self._payload[self._idx['discontinued']] < now: 2445 planned_end_from_now_str = _('%s ago, in %s') % ( 2446 gmDateTime.format_interval(now - self._payload[self._idx['discontinued']], gmDateTime.acc_days), 2447 self._payload[self._idx['discontinued']].year 2448 ) 2449 else: 2450 planned_end_from_now_str = _('in %s, %s') % ( 2451 gmDateTime.format_interval(self._payload[self._idx['discontinued']] - now, gmDateTime.acc_days), 2452 self._payload[self._idx['discontinued']].year 2453 ) 2454 else: 2455 end_date_template = '%Y' 2456 if self._payload[self._idx['discontinued']] < now: 2457 planned_end_from_now_str = _('%s ago = %s') % ( 2458 gmDateTime.format_interval(now - self._payload[self._idx['discontinued']], gmDateTime.acc_days), 2459 gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%b %d', 'utf8', gmDateTime.acc_days) 2460 ) 2461 else: 2462 planned_end_from_now_str = _('in %s = %s') % ( 2463 gmDateTime.format_interval(self._payload[self._idx['discontinued']] - now, gmDateTime.acc_days), 2464 gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%b %d', 'utf8', gmDateTime.acc_days) 2465 ) 2466 end_str = '%s (%s)' % ( 2467 gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], end_date_template, 'utf8', gmDateTime.acc_days), 2468 planned_end_from_now_str 2469 ) 2470 2471 arrow_parts.append(end_str) 2472 2473 # assemble 2474 return (' %s ' % gmTools.u_arrow2right_thick).join(arrow_parts)
2475 2476 medically_formatted_start_end = property(_get_medically_formatted_start_end, lambda x:x) 2477 2478 #--------------------------------------------------------
2479 - def _get_as_amts_latex(self, strict=True):
2480 return format_substance_intake_as_amts_latex(intake = self, strict=strict)
2481 2482 as_amts_latex = property(_get_as_amts_latex, lambda x:x) 2483 2484 #--------------------------------------------------------
2485 - def _get_as_amts_data(self, strict=True):
2486 return format_substance_intake_as_amts_data(intake = self, strict = strict)
2487 2488 as_amts_data = property(_get_as_amts_data, lambda x:x) 2489 2490 #--------------------------------------------------------
2491 - def _get_parsed_schedule(self):
2492 tests = [ 2493 # lead, trail 2494 ' 1-1-1-1 ', 2495 # leading dose 2496 '1-1-1-1', 2497 '22-1-1-1', 2498 '1/3-1-1-1', 2499 '/4-1-1-1' 2500 ] 2501 pattern = "^(\d\d|/\d|\d/\d|\d)[\s-]{1,5}\d{0,2}[\s-]{1,5}\d{0,2}[\s-]{1,5}\d{0,2}$" 2502 for test in tests: 2503 print(test.strip(), ":", regex.match(pattern, test.strip()))
2504 2505 #------------------------------------------------------------
2506 -def get_substance_intakes(pk_patient=None):
2507 args = {'pat': pk_patient} 2508 if pk_patient is None: 2509 cmd = _SQL_get_substance_intake % 'true' 2510 else: 2511 cmd = _SQL_get_substance_intake % 'pk_patient = %(pat)s' 2512 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 2513 return [ cSubstanceIntakeEntry(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance_intake'}) for r in rows ]
2514 2515 #------------------------------------------------------------
2516 -def substance_intake_exists(pk_component=None, pk_identity=None, pk_drug_product=None, pk_dose=None):
2517 2518 if [pk_component, pk_drug_product, pk_dose].count(None) != 2: 2519 raise ValueError('only one of pk_component, pk_dose, and pk_drug_product can be non-NULL') 2520 2521 args = { 2522 'pk_comp': pk_component, 2523 'pk_pat': pk_identity, 2524 'pk_drug_product': pk_drug_product, 2525 'pk_dose': pk_dose 2526 } 2527 where_parts = ['fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pk_pat)s)'] 2528 2529 if pk_dose is not None: 2530 where_parts.append('fk_drug_component IN (SELECT pk FROM ref.lnk_dose2drug WHERE fk_dose = %(pk_dose)s)') 2531 if pk_component is not None: 2532 where_parts.append('fk_drug_component = %(pk_comp)s') 2533 if pk_drug_product is not None: 2534 where_parts.append('fk_drug_component IN (SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk_drug_product)s)') 2535 2536 cmd = """ 2537 SELECT EXISTS ( 2538 SELECT 1 FROM clin.substance_intake 2539 WHERE 2540 %s 2541 LIMIT 1 2542 ) 2543 """ % '\nAND\n'.join(where_parts) 2544 2545 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2546 return rows[0][0]
2547 2548 #------------------------------------------------------------
2549 -def substance_intake_exists_by_atc(pk_identity=None, atc=None):
2550 2551 if (atc is None) or (pk_identity is None): 2552 raise ValueError('atc and pk_identity cannot be None') 2553 2554 args = { 2555 'pat': pk_identity, 2556 'atc': atc 2557 } 2558 where_parts = [ 2559 'pk_patient = %(pat)s', 2560 '((atc_substance = %(atc)s) OR (atc_drug = %(atc)s))' 2561 ] 2562 cmd = """ 2563 SELECT EXISTS ( 2564 SELECT 1 FROM clin.v_substance_intakes 2565 WHERE 2566 %s 2567 LIMIT 1 2568 ) 2569 """ % '\nAND\n'.join(where_parts) 2570 2571 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2572 return rows[0][0]
2573 2574 #------------------------------------------------------------
2575 -def create_substance_intake(pk_component=None, pk_encounter=None, pk_episode=None, pk_drug_product=None):
2576 2577 if [pk_component, pk_drug_product].count(None) != 1: 2578 raise ValueError('only one of pk_component and pk_drug_product can be non-NULL') 2579 2580 args = { 2581 'pk_enc': pk_encounter, 2582 'pk_epi': pk_episode, 2583 'pk_comp': pk_component, 2584 'pk_drug_product': pk_drug_product 2585 } 2586 2587 if pk_drug_product is not None: 2588 cmd = """ 2589 INSERT INTO clin.substance_intake ( 2590 fk_encounter, 2591 fk_episode, 2592 intake_is_approved_of, 2593 fk_drug_component 2594 ) VALUES ( 2595 %(pk_enc)s, 2596 %(pk_epi)s, 2597 False, 2598 -- select one of the components (the others will be added by a trigger) 2599 (SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk_drug_product)s LIMIT 1) 2600 ) 2601 RETURNING pk""" 2602 2603 if pk_component is not None: 2604 cmd = """ 2605 INSERT INTO clin.substance_intake ( 2606 fk_encounter, 2607 fk_episode, 2608 intake_is_approved_of, 2609 fk_drug_component 2610 ) VALUES ( 2611 %(pk_enc)s, 2612 %(pk_epi)s, 2613 False, 2614 %(pk_comp)s 2615 ) 2616 RETURNING pk""" 2617 2618 try: 2619 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 2620 except gmPG2.dbapi.InternalError as exc: 2621 if exc.pgerror is None: 2622 raise 2623 exc = make_pg_exception_fields_unicode(exc) 2624 if 'prevent_duplicate_component' in exc.u_pgerror: 2625 _log.exception('will not create duplicate substance intake entry') 2626 _log.error(exc.u_pgerror) 2627 return None 2628 raise 2629 2630 return cSubstanceIntakeEntry(aPK_obj = rows[0][0])
2631 2632 #------------------------------------------------------------
2633 -def delete_substance_intake(pk_intake=None, delete_siblings=False):
2634 if delete_siblings: 2635 cmd = """ 2636 DELETE FROM clin.substance_intake c_si 2637 WHERE 2638 c_si.fk_drug_component IN ( 2639 SELECT r_ld2d.pk FROM ref.lnk_dose2drug r_ld2d 2640 WHERE r_ld2d.fk_drug_product = ( 2641 SELECT c_vsi1.pk_drug_product FROM clin.v_substance_intakes c_vsi1 WHERE c_vsi1.pk_substance_intake = %(pk)s 2642 ) 2643 ) 2644 AND 2645 c_si.fk_encounter IN ( 2646 SELECT c_e.pk FROM clin.encounter c_e 2647 WHERE c_e.fk_patient = ( 2648 SELECT c_vsi2.pk_patient FROM clin.v_substance_intakes c_vsi2 WHERE c_vsi2.pk_substance_intake = %(pk)s 2649 ) 2650 )""" 2651 else: 2652 cmd = 'DELETE FROM clin.substance_intake WHERE pk = %(pk)s' 2653 2654 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk_intake}}]) 2655 return True
2656 2657 #------------------------------------------------------------ 2658 # AMTS formatting 2659 #------------------------------------------------------------
2660 -def format_substance_intake_as_amts_latex(intake=None, strict=True):
2661 2662 _esc = gmTools.tex_escape_string 2663 2664 # %(contains)s & %(product)s & %(amount)s%(unit)s & %(preparation)s & \multicolumn{4}{l|}{%(schedule)s} & Einheit & %(notes)s & %(aim)s \tabularnewline{}\hline 2665 cells = [] 2666 # components 2667 components = intake.containing_drug['components'] 2668 if len(components) > 3: 2669 cells.append(_esc('WS-Kombi.')) 2670 elif len(components) == 1: 2671 c = components[0] 2672 if strict: 2673 cells.append('\\mbox{%s}' % _esc(c['substance'][:80])) 2674 else: 2675 cells.append('\\mbox{%s}' % _esc(c['substance'])) 2676 else: 2677 if strict: 2678 cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline '.join(['\\mbox{%s}' % _esc(c['substance'][:80]) for c in components])) 2679 else: 2680 cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline '.join(['\\mbox{%s}' % _esc(c['substance']) for c in components])) 2681 # product 2682 if strict: 2683 cells.append(_esc(intake['product'][:50])) 2684 else: 2685 cells.append(_esc(intake['product'])) 2686 # Wirkstärken 2687 if len(components) > 3: 2688 cells.append('') 2689 elif len(components) == 1: 2690 c = components[0] 2691 dose = ('%s%s' % (c['amount'], format_units(c['unit'], c['dose_unit'], short = True))).replace('.', ',') 2692 if strict: 2693 dose = dose[:11] 2694 cells.append(_esc(dose)) 2695 else: # 2 2696 if strict: 2697 doses = '\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline\\ '.join ([ 2698 _esc(('%s%s' % ( 2699 ('%s' % c['amount']).replace('.', ','), 2700 format_units(c['unit'], c['dose_unit'], short = True) 2701 ))[:11]) for c in components 2702 ]) 2703 else: 2704 doses = '\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline\\ '.join ([ 2705 _esc('%s%s' % ( 2706 ('%s' % c['amount']).replace('.', ','), 2707 format_units(c['unit'], c['dose_unit'], short = True) 2708 )) for c in components 2709 ]) 2710 cells.append(doses) 2711 # preparation 2712 if strict: 2713 cells.append(_esc(intake['l10n_preparation'][:7])) 2714 else: 2715 cells.append(_esc(intake['l10n_preparation'])) 2716 # schedule - for now be simple - maybe later parse 1-1-1-1 etc 2717 if intake['schedule'] is None: 2718 cells.append('\\multicolumn{4}{p{3.2cm}|}{\\ }') 2719 else: 2720 # spec says [:20] but implementation guide says: never trim 2721 if len(intake['schedule']) > 20: 2722 cells.append('\\multicolumn{4}{>{\\RaggedRight}p{3.2cm}|}{\\fontsize{10pt}{12pt}\selectfont %s}' % _esc(intake['schedule'])) 2723 else: 2724 cells.append('\\multicolumn{4}{>{\\RaggedRight}p{3.2cm}|}{%s}' % _esc(intake['schedule'])) 2725 # Einheit to take 2726 cells.append('')#[:20] 2727 # notes 2728 if intake['notes'] is None: 2729 cells.append(' ') 2730 else: 2731 if strict: 2732 cells.append(_esc(intake['notes'][:80])) 2733 else: 2734 cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % _esc(intake['notes'])) 2735 # aim 2736 if intake['aim'] is None: 2737 #cells.append(' ') 2738 cells.append(_esc(intake['episode'][:50])) 2739 else: 2740 if strict: 2741 cells.append(_esc(intake['aim'][:50])) 2742 else: 2743 cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % _esc(intake['aim'])) 2744 2745 table_row = ' & '.join(cells) 2746 table_row += '\\tabularnewline{}\n\\hline' 2747 2748 return table_row
2749 2750 #------------------------------------------------------------
2751 -def format_substance_intake_as_amts_data(intake=None, strict=True):
2752 """ 2753 <M a="Handelsname" fd="freie Formangabe" t="freies Dosierschema" dud="freie Dosiereinheit (Stück Tab)" r="reason" i="info"> 2754 <W w="Metformin" s="500 mg"/> 2755 <W ...> 2756 </M> 2757 """ 2758 if not strict: 2759 pass 2760 # relax length checks 2761 2762 M_fields = [] 2763 M_fields.append('a="%s"' % intake['product']) 2764 M_fields.append('fd="%s"' % intake['l10n_preparation']) 2765 if intake['schedule'] is not None: 2766 M_fields.append('t="%s"' % intake['schedule']) 2767 #M_fields.append(u'dud="%s"' % intake['dose unit, like Stück']) 2768 if intake['aim'] is None: 2769 M_fields.append('r="%s"' % intake['episode']) 2770 else: 2771 M_fields.append('r="%s"' % intake['aim']) 2772 if intake['notes'] is not None: 2773 M_fields.append('i="%s"' % intake['notes']) 2774 M_line = '<M %s>' % ' '.join(M_fields) 2775 2776 W_lines = [] 2777 for comp in intake.containing_drug['components']: 2778 W_lines.append('<W w="%s" s="%s %s"/>' % ( 2779 comp['substance'], 2780 comp['amount'], 2781 format_units(comp['unit'], comp['dose_unit'], short = True) 2782 )) 2783 2784 return M_line + ''.join(W_lines) + '</M>'
2785 2786 #------------------------------------------------------------
2787 -def generate_amts_data_template_definition_file(work_dir=None, strict=True):
2788 2789 _log.debug('generating AMTS data template definition file(workdir=%s, strict=%s)', work_dir, strict) 2790 2791 if not strict: 2792 return __generate_enhanced_amts_data_template_definition_file(work_dir = work_dir) 2793 2794 amts_lines = [ l for l in ('<MP v="023" U="%s"' % uuid.uuid4().hex + """ l="de-DE"$<<if_not_empty::$<amts_page_idx::::1>$// a="%s"//::>>$$<<if_not_empty::$<amts_page_idx::::>$// z="$<amts_total_pages::::1>$"//::>>$> 2795 <P g="$<name::%(firstnames)s::45>$" f="$<name::%(lastnames)s::45>$" b="$<date_of_birth::%Y%m%d::8>$"/> 2796 <A 2797 n="$<<range_of::$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$::30>>$" 2798 $<praxis_address:: s="%(street)s"::>$ 2799 $<praxis_address:: z="%(postcode)s"::>$ 2800 $<praxis_address:: c="%(urb)s"::>$ 2801 $<praxis_comm::workphone// p="%(url)s"::20>$ 2802 $<praxis_comm::email// e="%(url)s"::80>$ 2803 t="$<today::%Y%m%d::8>$" 2804 /> 2805 <O ai="s.S.$<amts_total_pages::::1>$ unten"/> 2806 $<amts_intakes_as_data::::9999999>$ 2807 </MP>""").split('\n') ] 2808 #$<<if_not_empty::$<allergy_list::%(descriptor)s//,::>$//<O ai="%s"/>::>>$ 2809 2810 amts_fname = gmTools.get_unique_filename ( 2811 prefix = 'gm2amts_data-', 2812 suffix = '.txt', 2813 tmp_dir = work_dir 2814 ) 2815 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8') 2816 amts_template.write('[form]\n') 2817 amts_template.write('template = $template$\n') 2818 amts_template.write(''.join(amts_lines)) 2819 amts_template.write('\n') 2820 amts_template.write('$template$\n') 2821 amts_template.close() 2822 2823 return amts_fname
2824 2825 #------------------------------------------------------------
2826 -def __generate_enhanced_amts_data_template_definition_file(work_dir=None):
2827 2828 amts_lines = [ l for l in ('<MP v="023" U="%s"' % uuid.uuid4().hex + """ l="de-DE" a="1" z="1"> 2829 <P g="$<name::%(firstnames)s::>$" f="$<name::%(lastnames)s::>$" b="$<date_of_birth::%Y%m%d::8>$"/> 2830 <A 2831 n="$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$" 2832 $<praxis_address:: s="%(street)s %(number)s %(subunit)s"::>$ 2833 $<praxis_address:: z="%(postcode)s"::>$ 2834 $<praxis_address:: c="%(urb)s"::>$ 2835 $<praxis_comm::workphone// p="%(url)s"::>$ 2836 $<praxis_comm::email// e="%(url)s"::>$ 2837 t="$<today::%Y%m%d::8>$" 2838 /> 2839 <O ai="Seite 1 unten"/> 2840 $<amts_intakes_as_data_enhanced::::9999999>$ 2841 </MP>""").split('\n') ] 2842 #$<<if_not_empty::$<allergy_list::%(descriptor)s//,::>$//<O ai="%s"/>::>>$ 2843 2844 amts_fname = gmTools.get_unique_filename ( 2845 prefix = 'gm2amts_data-utf8-unabridged-', 2846 suffix = '.txt', 2847 tmp_dir = work_dir 2848 ) 2849 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8') 2850 amts_template.write('[form]\n') 2851 amts_template.write('template = $template$\n') 2852 amts_template.write(''.join(amts_lines)) 2853 amts_template.write('\n') 2854 amts_template.write('$template$\n') 2855 amts_template.close() 2856 2857 return amts_fname
2858 2859 #------------------------------------------------------------ 2860 # AMTS v2.0 -- outdated 2861 #------------------------------------------------------------
2862 -def format_substance_intake_as_amts_data_v2_0(intake=None, strict=True):
2863 2864 if not strict: 2865 pass 2866 # relax length checks 2867 2868 fields = [] 2869 2870 # components 2871 components = [ c.split('::') for c in intake.containing_drug['components'] ] 2872 if len(components) > 3: 2873 fields.append('WS-Kombi.') 2874 elif len(components) == 1: 2875 c = components[0] 2876 fields.append(c[0][:80]) 2877 else: 2878 fields.append('~'.join([c[0][:80] for c in components])) 2879 # product 2880 fields.append(intake['product'][:50]) 2881 # Wirkstärken 2882 if len(components) > 3: 2883 fields.append('') 2884 elif len(components) == 1: 2885 c = components[0] 2886 fields.append(('%s%s' % (c[1], c[2]))[:11]) 2887 else: 2888 fields.append('~'.join([('%s%s' % (c[1], c[2]))[:11] for c in components])) 2889 # preparation 2890 fields.append(intake['l10n_preparation'][:7]) 2891 # schedule - for now be simple - maybe later parse 1-1-1-1 etc 2892 fields.append(gmTools.coalesce(intake['schedule'], '')[:20]) 2893 # Einheit to take 2894 fields.append('')#[:20] 2895 # notes 2896 fields.append(gmTools.coalesce(intake['notes'], '')[:80]) 2897 # aim 2898 fields.append(gmTools.coalesce(intake['aim'], '')[:50]) 2899 2900 return '|'.join(fields)
2901 2902 #------------------------------------------------------------
2903 -def calculate_amts_data_check_symbol_v2_0(intakes=None):
2904 2905 # first char of generic substance or product name 2906 first_chars = [] 2907 for intake in intakes: 2908 first_chars.append(intake['product'][0]) 2909 2910 # add up_per page 2911 val_sum = 0 2912 for first_char in first_chars: 2913 # ziffer: ascii+7 2914 if first_char.isdigit(): 2915 val_sum += (ord(first_char) + 7) 2916 # großbuchstabe: ascii 2917 # kleinbuchstabe ascii(großbuchstabe) 2918 if first_char.isalpha(): 2919 val_sum += ord(first_char.upper()) 2920 # other: 0 2921 2922 # get remainder of sum mod 36 2923 tmp, remainder = divmod(val_sum, 36) 2924 # 0-9 -> '0' - '9' 2925 if remainder < 10: 2926 return '%s' % remainder 2927 # 10-35 -> 'A' - 'Z' 2928 return chr(remainder + 55)
2929 2930 #------------------------------------------------------------
2931 -def generate_amts_data_template_definition_file_v2_0(work_dir=None, strict=True):
2932 2933 if not strict: 2934 return __generate_enhanced_amts_data_template_definition_file(work_dir = work_dir) 2935 2936 amts_fields = [ 2937 'MP', 2938 '020', # Version 2939 'DE', # Land 2940 'DE', # Sprache 2941 '1', # Zeichensatz 1 = Ext ASCII (fest) = ISO8859-1 = Latin1 2942 '$<today::%Y%m%d::8>$', 2943 '$<amts_page_idx::::1>$', # to be set by code using the template 2944 '$<amts_total_pages::::1>$', # to be set by code using the template 2945 '0', # Zertifizierungsstatus 2946 2947 '$<name::%(firstnames)s::45>$', 2948 '$<name::%(lastnames)s::45>$', 2949 '', # Patienten-ID 2950 '$<date_of_birth::%Y%m%d::8>$', 2951 2952 '$<<range_of::$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$::30>>$', 2953 '$<praxis_address::%(street)s %(number)s %(subunit)s|%(postcode)s|%(urb)s::57>$', # 55+2 because of 2 embedded "|"s 2954 '$<praxis_comm::workphone::20>$', 2955 '$<praxis_comm::email::80>$', 2956 2957 #u'264 $<allergy_state::::21>$', # param 1, Allergien 25-4 (4 for "264 ", spec says max of 25) 2958 '264 Seite $<amts_total_pages::::1>$ unten', # param 1, Allergien 25-4 (4 for "264 ", spec says max of 25) 2959 '', # param 2, not used currently 2960 '', # param 3, not used currently 2961 2962 # Medikationseinträge 2963 '$<amts_intakes_as_data::::9999999>$', 2964 2965 '$<amts_check_symbol::::1>$', # Prüfzeichen, value to be set by code using the template, *per page* ! 2966 '#@', # Endesymbol 2967 ] 2968 2969 amts_fname = gmTools.get_unique_filename ( 2970 prefix = 'gm2amts_data-', 2971 suffix = '.txt', 2972 tmp_dir = work_dir 2973 ) 2974 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8') 2975 amts_template.write('[form]\n') 2976 amts_template.write('template = $template$\n') 2977 amts_template.write('|'.join(amts_fields)) 2978 amts_template.write('\n') 2979 amts_template.write('$template$\n') 2980 amts_template.close() 2981 2982 return amts_fname
2983 2984 #------------------------------------------------------------
2985 -def __generate_enhanced_amts_data_template_definition_file_v2_0(work_dir=None):
2986 2987 amts_fields = [ 2988 'MP', 2989 '020', # Version 2990 'DE', # Land 2991 'DE', # Sprache 2992 '1', # Zeichensatz 1 = Ext ASCII (fest) = ISO8859-1 = Latin1 2993 '$<today::%Y%m%d::8>$', 2994 '1', # idx of this page 2995 '1', # total pages 2996 '0', # Zertifizierungsstatus 2997 2998 '$<name::%(firstnames)s::>$', 2999 '$<name::%(lastnames)s::>$', 3000 '', # Patienten-ID 3001 '$<date_of_birth::%Y%m%d::8>$', 3002 3003 '$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$', 3004 '$<praxis_address::%(street)s %(number)s %(subunit)s::>$', 3005 '$<praxis_address::%(postcode)s::>$', 3006 '$<praxis_address::%(urb)s::>$', 3007 '$<praxis_comm::workphone::>$', 3008 '$<praxis_comm::email::>$', 3009 3010 #u'264 $<allergy_state::::>$', # param 1, Allergien 3011 '264 Seite 1 unten', # param 1, Allergien 3012 '', # param 2, not used currently 3013 '', # param 3, not used currently 3014 3015 # Medikationseinträge 3016 '$<amts_intakes_as_data_enhanced::::>$', 3017 3018 '$<amts_check_symbol::::1>$', # Prüfzeichen, value to be set by code using the template, *per page* ! 3019 '#@', # Endesymbol 3020 ] 3021 3022 amts_fname = gmTools.get_unique_filename ( 3023 prefix = 'gm2amts_data-utf8-unabridged-', 3024 suffix = '.txt', 3025 tmp_dir = work_dir 3026 ) 3027 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8') 3028 amts_template.write('[form]\n') 3029 amts_template.write('template = $template$\n') 3030 amts_template.write('|'.join(amts_fields)) 3031 amts_template.write('\n') 3032 amts_template.write('$template$\n') 3033 amts_template.close() 3034 3035 return amts_fname
3036 3037 #------------------------------------------------------------ 3038 # other formatting 3039 #------------------------------------------------------------
3040 -def format_substance_intake_notes(emr=None, output_format='latex', table_type=u'by-product'):
3041 3042 tex = '%s\n' % _('Additional notes for healthcare professionals') 3043 tex += '%%%% requires "\\usepackage{longtable}"\n' 3044 tex += '%%%% requires "\\usepackage{tabu}"\n' 3045 tex += '\\begin{longtabu} to \\textwidth {|X[,L]|r|X[,L]|}\n' 3046 tex += '\\hline\n' 3047 tex += '%s {\\scriptsize (%s)} & %s & %s\\\\\n' % (_('Substance'), _('Drug Product'), _('Strength'), _('Aim')) 3048 tex += '\\hline\n' 3049 tex += '%s\n' # this is where the lines end up 3050 tex += '\\end{longtabu}\n' 3051 3052 current_meds = emr.get_current_medications ( 3053 include_inactive = False, 3054 include_unapproved = False, 3055 order_by = 'product, substance' 3056 ) 3057 # create lines 3058 lines = [] 3059 for med in current_meds: 3060 if med['aim'] is None: 3061 aim = '' 3062 else: 3063 aim = '{\\scriptsize %s}' % gmTools.tex_escape_string(med['aim']) 3064 lines.append('%s {\\small (%s: {\\tiny %s})} & %s%s & %s\\\\' % ( 3065 gmTools.tex_escape_string(med['substance']), 3066 gmTools.tex_escape_string(med['l10n_preparation']), 3067 gmTools.tex_escape_string(med['product']), 3068 med['amount'], 3069 gmTools.tex_escape_string(med.formatted_units), 3070 aim 3071 )) 3072 lines.append(u'\\hline') 3073 3074 return tex % '\n'.join(lines)
3075 3076 #------------------------------------------------------------
3077 -def format_substance_intake(emr=None, output_format='latex', table_type='by-product'):
3078 3079 # FIXME: add intake_instructions 3080 3081 tex = '%s {\\tiny (%s)}\n' % ( 3082 gmTools.tex_escape_string(_('Medication list')), 3083 gmTools.tex_escape_string(_('ordered by brand')) 3084 ) 3085 tex += '%% requires "\\usepackage{longtable}"\n' 3086 tex += '%% requires "\\usepackage{tabu}"\n' 3087 tex += u'\\begin{longtabu} to \\textwidth {|X[-1,L]|X[2.5,L]|}\n' 3088 tex += u'\\hline\n' 3089 tex += u'%s & %s\\\\\n' % ( 3090 gmTools.tex_escape_string(_('Drug')), 3091 gmTools.tex_escape_string(_('Regimen / Advice')) 3092 ) 3093 tex += '\\hline\n' 3094 tex += '%s\n' 3095 tex += '\\end{longtabu}\n' 3096 3097 # aggregate medication data 3098 current_meds = emr.get_current_medications ( 3099 include_inactive = False, 3100 include_unapproved = False, 3101 order_by = 'product, substance' 3102 ) 3103 line_data = {} 3104 for med in current_meds: 3105 identifier = med['product'] 3106 3107 try: 3108 line_data[identifier] 3109 except KeyError: 3110 line_data[identifier] = {'product': '', 'l10n_preparation': '', 'schedule': '', 'notes': [], 'strengths': []} 3111 3112 line_data[identifier]['product'] = identifier 3113 line_data[identifier]['strengths'].append('%s %s%s' % (med['substance'][:20], med['amount'], med.formatted_units)) 3114 if med['l10n_preparation'] not in identifier: 3115 line_data[identifier]['l10n_preparation'] = med['l10n_preparation'] 3116 sched_parts = [] 3117 if med['duration'] is not None: 3118 sched_parts.append(gmDateTime.format_interval(med['duration'], gmDateTime.acc_days, verbose = True)) 3119 if med['schedule'] is not None: 3120 sched_parts.append(med['schedule']) 3121 line_data[identifier]['schedule'] = ': '.join(sched_parts) 3122 if med['notes'] is not None: 3123 if med['notes'] not in line_data[identifier]['notes']: 3124 line_data[identifier]['notes'].append(med['notes']) 3125 # format aggregated data 3126 already_seen = [] 3127 lines = [] 3128 #line1_template = u'\\rule{0pt}{3ex}{\\Large %s} %s & %s \\\\' 3129 line1_template = u'{\\Large %s} %s & %s\\\\' 3130 line2_template = u'{\\tiny %s} & {\\scriptsize %s}\\\\' 3131 line3_template = u' & {\\scriptsize %s}\\\\' 3132 for med in current_meds: 3133 identifier = med['product'] 3134 if identifier in already_seen: 3135 continue 3136 already_seen.append(identifier) 3137 lines.append (line1_template % ( 3138 gmTools.tex_escape_string(line_data[identifier]['product']), 3139 gmTools.tex_escape_string(line_data[identifier]['l10n_preparation']), 3140 gmTools.tex_escape_string(line_data[identifier]['schedule']) 3141 )) 3142 strengths = gmTools.tex_escape_string(' / '.join(line_data[identifier]['strengths'])) 3143 if len(line_data[identifier]['notes']) == 0: 3144 first_note = '' 3145 else: 3146 first_note = gmTools.tex_escape_string(line_data[identifier]['notes'][0]) 3147 lines.append(line2_template % (strengths, first_note)) 3148 if len(line_data[identifier]['notes']) > 1: 3149 for note in line_data[identifier]['notes'][1:]: 3150 lines.append(line3_template % gmTools.tex_escape_string(note)) 3151 lines.append('\\hline') 3152 3153 return tex % '\n'.join(lines)
3154 3155 #============================================================ 3156 # convenience functions 3157 #------------------------------------------------------------
3158 -def create_default_medication_history_episode(pk_health_issue=None, encounter=None, link_obj=None):
3159 return gmEMRStructItems.create_episode ( 3160 pk_health_issue = pk_health_issue, 3161 episode_name = DEFAULT_MEDICATION_HISTORY_EPISODE, 3162 is_open = False, 3163 allow_dupes = False, 3164 encounter = encounter, 3165 link_obj = link_obj 3166 )
3167 3168 #------------------------------------------------------------
3169 -def get_tobacco():
3170 tobacco = create_drug_product ( 3171 product_name = _('nicotine'), 3172 preparation = _('tobacco'), 3173 return_existing = True 3174 ) 3175 tobacco['is_fake_product'] = True 3176 tobacco.save() 3177 nicotine = create_substance_dose_by_atc ( 3178 substance = _('nicotine'), 3179 atc = gmATC.ATC_NICOTINE, 3180 amount = 1, 3181 unit = 'pack', 3182 dose_unit = 'year' 3183 ) 3184 tobacco.set_substance_doses_as_components(substance_doses = [nicotine]) 3185 return tobacco
3186 3187 #------------------------------------------------------------
3188 -def get_alcohol():
3189 drink = create_drug_product ( 3190 product_name = _('alcohol'), 3191 preparation = _('liquid'), 3192 return_existing = True 3193 ) 3194 drink['is_fake_product'] = True 3195 drink.save() 3196 ethanol = create_substance_dose_by_atc ( 3197 substance = _('ethanol'), 3198 atc = gmATC.ATC_ETHANOL, 3199 amount = 1, 3200 unit = 'g', 3201 dose_unit = 'ml' 3202 ) 3203 drink.set_substance_doses_as_components(substance_doses = [ethanol]) 3204 return drink
3205 3206 #------------------------------------------------------------
3207 -def get_other_drug(name=None, pk_dose=None):
3208 drug = create_drug_product ( 3209 product_name = name, 3210 preparation = _('unit'), 3211 return_existing = True 3212 ) 3213 drug['is_fake_product'] = True 3214 drug.save() 3215 if pk_dose is None: 3216 content = create_substance_dose ( 3217 substance = name, 3218 amount = 1, 3219 unit = _('unit'), 3220 dose_unit = _('unit') 3221 ) 3222 else: 3223 content = {'pk_dose': pk_dose} #cSubstanceDose(aPK_obj = pk_dose) 3224 drug.set_substance_doses_as_components(substance_doses = [content]) 3225 return drug
3226 3227 #------------------------------------------------------------
3228 -def format_units(unit, dose_unit, preparation=None, short=True):
3229 if short: 3230 return '%s%s' % (unit, gmTools.coalesce(dose_unit, '', '/%s')) 3231 3232 return '%s / %s' % ( 3233 unit, 3234 gmTools.coalesce ( 3235 dose_unit, 3236 _('delivery unit%s') % gmTools.coalesce(preparation, '', ' (%s)'), 3237 '%s' 3238 ) 3239 )
3240 3241 #============================================================ 3242 # main 3243 #------------------------------------------------------------ 3244 if __name__ == "__main__": 3245 3246 if len(sys.argv) < 2: 3247 sys.exit() 3248 3249 if sys.argv[1] != 'test': 3250 sys.exit() 3251 3252 from Gnumed.pycommon import gmLog2 3253 #from Gnumed.pycommon import gmI18N 3254 3255 gmI18N.activate_locale() 3256 # gmDateTime.init() 3257 3258 #-------------------------------------------------------- 3259 # generic 3260 #--------------------------------------------------------
3261 - def test_create_substance_intake():
3262 drug = create_substance_intake ( 3263 pk_component = 2, 3264 pk_encounter = 1, 3265 pk_episode = 1 3266 ) 3267 print(drug)
3268 3269 #--------------------------------------------------------
3270 - def test_get_substances():
3271 for s in get_substances(): 3272 ##print s 3273 print("--------------------------") 3274 print(s.format()) 3275 print('in use:', s.is_in_use_by_patients) 3276 print('is component:', s.is_drug_component)
3277 3278 # s = cSubstance(1) 3279 # print s 3280 # print s['loincs'] 3281 # print s.format() 3282 # print 'in use:', s.is_in_use_by_patients 3283 # print 'is component:', s.is_drug_component 3284 3285 #--------------------------------------------------------
3286 - def test_get_doses():
3287 for d in get_substance_doses(): 3288 #print d 3289 print("--------------------------") 3290 print(d.format(left_margin = 1, include_loincs = True)) 3291 print('in use:', d.is_in_use_by_patients) 3292 print('is component:', d.is_drug_component)
3293 3294 #--------------------------------------------------------
3295 - def test_get_components():
3296 for c in get_drug_components(): 3297 #print c 3298 print('--------------------------------------') 3299 print(c.format()) 3300 print('dose:', c.substance_dose.format()) 3301 print('substance:', c.substance.format())
3302 3303 #--------------------------------------------------------
3304 - def test_get_drugs():
3305 for d in get_drug_products(): 3306 if d['is_fake_product'] or d.is_vaccine: 3307 continue 3308 print('--------------------------------------') 3309 print(d.format()) 3310 for c in d.components: 3311 print('-------') 3312 print(c.format()) 3313 print(c.substance_dose.format()) 3314 print(c.substance.format())
3315 3316 #--------------------------------------------------------
3317 - def test_get_intakes():
3318 for i in get_substance_intakes(): 3319 #print i 3320 print('------------------------------------------------') 3321 print('\n'.join(i.format_maximum_information()))
3322 3323 #--------------------------------------------------------
3324 - def test_get_habit_drugs():
3325 print(get_tobacco().format()) 3326 print(get_alcohol().format()) 3327 print(get_other_drug(name = 'LSD').format())
3328 3329 #--------------------------------------------------------
3330 - def test_drug2renal_insufficiency_url():
3331 drug2renal_insufficiency_url(search_term = 'Metoprolol')
3332 #--------------------------------------------------------
3333 - def test_medically_formatted_start_end():
3334 cmd = "SELECT pk_substance_intake FROM clin.v_substance_intakes" 3335 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 3336 for row in rows: 3337 entry = cSubstanceIntakeEntry(row['pk_substance_intake']) 3338 print('===============================================================') 3339 print(entry.format(left_margin = 1, single_line = False, show_all_product_components = True)) 3340 print('--------------------------------') 3341 print(entry.medically_formatted_start_end) 3342 gmTools.prompted_input()
3343 3344 #--------------------------------------------------------
3345 - def test_generate_amts_data_template_definition_file(work_dir=None, strict=True):
3346 print('file:', generate_amts_data_template_definition_file(strict = True))
3347 3348 #--------------------------------------------------------
3349 - def test_format_substance_intake_as_amts_data():
3350 #print format_substance_intake_as_amts_data(cSubstanceIntakeEntry(1)) 3351 print(cSubstanceIntakeEntry(1).as_amts_data)
3352 3353 #--------------------------------------------------------
3354 - def test_delete_intake():
3355 delete_substance_intake(pk_intake = 1, delete_siblings = True)
3356 3357 #-------------------------------------------------------- 3358 # generic 3359 #test_drug2renal_insufficiency_url() 3360 #test_interaction_check() 3361 #test_medically_formatted_start_end() 3362 3363 #test_get_substances() 3364 #test_get_doses() 3365 #test_get_components() 3366 #test_get_drugs() 3367 #test_get_intakes() 3368 #test_create_substance_intake() 3369 test_delete_intake() 3370 3371 #test_get_habit_drugs() 3372 3373 # AMTS 3374 #test_generate_amts_data_template_definition_file() 3375 #test_format_substance_intake_as_amts_data() 3376