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