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

Source Code for Module Gnumed.business.gmMedication

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