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

Source Code for Module Gnumed.business.gmMedication

   1  # -*- coding: utf8 -*- 
   2  """Medication handling code. 
   3   
   4  license: GPL v2 or later 
   5  """ 
   6  #============================================================ 
   7  __version__ = "$Revision: 1.21 $" 
   8  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
   9   
  10  import sys 
  11  import logging 
  12  import csv 
  13  import codecs 
  14  import os 
  15  import re as regex 
  16  import subprocess 
  17  import decimal 
  18  from xml.etree import ElementTree as etree 
  19   
  20   
  21  if __name__ == '__main__': 
  22          sys.path.insert(0, '../../') 
  23          _ = lambda x:x 
  24  from Gnumed.pycommon import gmBusinessDBObject 
  25  from Gnumed.pycommon import gmTools 
  26  from Gnumed.pycommon import gmShellAPI 
  27  from Gnumed.pycommon import gmPG2 
  28  from Gnumed.pycommon import gmDispatcher 
  29  from Gnumed.pycommon import gmMatchProvider 
  30  from Gnumed.pycommon import gmHooks 
  31  from Gnumed.pycommon import gmDateTime 
  32   
  33  from Gnumed.business import gmATC 
  34  from Gnumed.business import gmAllergy 
  35  from Gnumed.business.gmDocuments import DOCUMENT_TYPE_PRESCRIPTION 
  36  from Gnumed.business.gmDocuments import create_document_type 
  37   
  38   
  39  _log = logging.getLogger('gm.meds') 
  40  _log.info(__version__) 
  41   
  42   
  43  DEFAULT_MEDICATION_HISTORY_EPISODE = _('Medication history') 
  44  #============================================================ 
45 -def _on_substance_intake_modified():
46 """Always relates to the active patient.""" 47 gmHooks.run_hook_script(hook = u'after_substance_intake_modified')
48 49 gmDispatcher.connect(_on_substance_intake_modified, u'substance_intake_mod_db') 50 51 #============================================================
52 -def drug2renal_insufficiency_url(search_term=None):
53 54 if search_term is None: 55 return u'http://www.dosing.de' 56 57 terms = [] 58 names = [] 59 60 if isinstance(search_term, cBrandedDrug): 61 if search_term['atc'] is not None: 62 terms.append(search_term['atc']) 63 64 elif isinstance(search_term, cSubstanceIntakeEntry): 65 names.append(search_term['substance']) 66 if search_term['atc_brand'] is not None: 67 terms.append(search_term['atc_brand']) 68 if search_term['atc_substance'] is not None: 69 terms.append(search_term['atc_substance']) 70 71 elif search_term is not None: 72 names.append(u'%s' % search_term) 73 terms.extend(gmATC.text2atc(text = u'%s' % search_term, fuzzy = True)) 74 75 for name in names: 76 if name.endswith('e'): 77 terms.append(name[:-1]) 78 else: 79 terms.append(name) 80 81 #url_template = u'http://www.google.de/#q=site%%3Adosing.de+%s' 82 #url = url_template % u'+OR+'.join(terms) 83 84 url_template = u'http://www.google.com/search?hl=de&source=hp&q=site%%3Adosing.de+%s&btnG=Google-Suche' 85 url = url_template % u'+OR+'.join(terms) 86 87 _log.debug(u'renal insufficiency URL: %s', url) 88 89 return url
90 #============================================================ 91 # this should be in gmCoding.py
92 -def create_data_source(long_name=None, short_name=None, version=None, source=None, language=None):
93 94 args = { 95 'lname': long_name, 96 'sname': short_name, 97 'ver': version, 98 'src': source, 99 'lang': language 100 } 101 102 cmd = u"""select pk from ref.data_source where name_long = %(lname)s and name_short = %(sname)s and version = %(ver)s""" 103 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 104 if len(rows) > 0: 105 return rows[0]['pk'] 106 107 cmd = u""" 108 INSERT INTO ref.data_source (name_long, name_short, version, source, lang) 109 VALUES ( 110 %(lname)s, 111 %(sname)s, 112 %(ver)s, 113 %(src)s, 114 %(lang)s 115 ) 116 returning pk 117 """ 118 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 119 120 return rows[0]['pk']
121 #============================================================ 122 # wishlist: 123 # - --conf-file= for glwin.exe 124 # - wirkstoff: Konzentration auch in Multiprodukten 125 # - wirkstoff: ATC auch in Multiprodukten 126 # - Suche nach ATC per CLI 127
128 -class cGelbeListeCSVFile(object):
129 """Iterator over a Gelbe Liste/MMI v8.2 CSV file.""" 130 131 version = u'Gelbe Liste/MMI v8.2 CSV file interface' 132 default_transfer_file_windows = r"c:\rezept.txt" 133 #default_encoding = 'cp1252' 134 default_encoding = 'cp1250' 135 csv_fieldnames = [ 136 u'name', 137 u'packungsgroesse', # obsolete, use "packungsmenge" 138 u'darreichungsform', 139 u'packungstyp', 140 u'festbetrag', 141 u'avp', 142 u'hersteller', 143 u'rezepttext', 144 u'pzn', 145 u'status_vertrieb', 146 u'status_rezeptpflicht', 147 u'status_fachinfo', 148 u'btm', 149 u'atc', 150 u'anzahl_packungen', 151 u'zuzahlung_pro_packung', 152 u'einheit', 153 u'schedule_morgens', 154 u'schedule_mittags', 155 u'schedule_abends', 156 u'schedule_nachts', 157 u'status_dauermedikament', 158 u'status_hausliste', 159 u'status_negativliste', 160 u'ik_nummer', 161 u'status_rabattvertrag', 162 u'wirkstoffe', 163 u'wirkstoffmenge', 164 u'wirkstoffeinheit', 165 u'wirkstoffmenge_bezug', 166 u'wirkstoffmenge_bezugseinheit', 167 u'status_import', 168 u'status_lifestyle', 169 u'status_ausnahmeliste', 170 u'packungsmenge', 171 u'apothekenpflicht', 172 u'status_billigere_packung', 173 u'rezepttyp', 174 u'besonderes_arzneimittel', # Abstimmungsverfahren SGB-V 175 u't_rezept_pflicht', # Thalidomid-Rezept 176 u'erstattbares_medizinprodukt', 177 u'hilfsmittel', 178 u'hzv_rabattkennung', 179 u'hzv_preis' 180 ] 181 boolean_fields = [ 182 u'status_rezeptpflicht', 183 u'status_fachinfo', 184 u'btm', 185 u'status_dauermedikament', 186 u'status_hausliste', 187 u'status_negativliste', 188 u'status_rabattvertrag', 189 u'status_import', 190 u'status_lifestyle', 191 u'status_ausnahmeliste', 192 u'apothekenpflicht', 193 u'status_billigere_packung', 194 u'besonderes_arzneimittel', # Abstimmungsverfahren SGB-V 195 u't_rezept_pflicht', 196 u'erstattbares_medizinprodukt', 197 u'hilfsmittel' 198 ] 199 #--------------------------------------------------------
200 - def __init__(self, filename=None):
201 202 _log.info(cGelbeListeCSVFile.version) 203 204 self.filename = filename 205 if filename is None: 206 self.filename = cGelbeListeCSVFile.default_transfer_file_windows 207 208 _log.debug('reading Gelbe Liste/MMI drug data from [%s]', self.filename) 209 210 self.csv_file = codecs.open(filename = filename, mode = 'rUb', encoding = cGelbeListeCSVFile.default_encoding) 211 212 self.csv_lines = gmTools.unicode_csv_reader ( 213 self.csv_file, 214 fieldnames = cGelbeListeCSVFile.csv_fieldnames, 215 delimiter = ';', 216 quotechar = '"', 217 dict = True 218 )
219 #--------------------------------------------------------
220 - def __iter__(self):
221 return self
222 #--------------------------------------------------------
223 - def next(self):
224 line = self.csv_lines.next() 225 226 for field in cGelbeListeCSVFile.boolean_fields: 227 line[field] = (line[field].strip() == u'T') 228 229 # split field "Wirkstoff" by ";" 230 if line['wirkstoffe'].strip() == u'': 231 line['wirkstoffe'] = [] 232 else: 233 line['wirkstoffe'] = [ wirkstoff.strip() for wirkstoff in line['wirkstoffe'].split(u';') ] 234 235 return line
236 #--------------------------------------------------------
237 - def close(self, truncate=True):
238 try: self.csv_file.close() 239 except: pass 240 241 if truncate: 242 try: os.open(self.filename, 'wb').close 243 except: pass
244 #--------------------------------------------------------
245 - def _get_has_unknown_fields(self):
247 248 has_unknown_fields = property(_get_has_unknown_fields, lambda x:x)
249 #============================================================
250 -class cDrugDataSourceInterface(object):
251 252 #--------------------------------------------------------
253 - def __init__(self):
254 self.patient = None 255 self.reviewer = None 256 self.custom_path_to_binary = None
257 #--------------------------------------------------------
258 - def get_data_source_version(self):
259 raise NotImplementedError
260 #--------------------------------------------------------
261 - def create_data_source_entry(self):
262 raise NotImplementedError
263 #--------------------------------------------------------
264 - def switch_to_frontend(self, blocking=False):
265 raise NotImplementedError
266 #--------------------------------------------------------
267 - def import_drugs(self):
268 self.switch_to_frontend()
269 #--------------------------------------------------------
270 - def check_interactions(self, substance_intakes=None):
271 self.switch_to_frontend()
272 #--------------------------------------------------------
273 - def show_info_on_drug(self, substance_intake=None):
274 self.switch_to_frontend()
275 #--------------------------------------------------------
276 - def show_info_on_substance(self, substance_intake=None):
277 self.switch_to_frontend()
278 #--------------------------------------------------------
279 - def prescribe(self, substance_intakes=None):
280 self.switch_to_frontend() 281 return []
282 #============================================================
283 -class cFreeDiamsInterface(cDrugDataSourceInterface):
284 285 version = u'FreeDiams v0.5.4 interface' 286 default_encoding = 'utf8' 287 default_dob_format = '%Y/%m/%d' 288 289 map_gender2mf = { 290 'm': u'M', 291 'f': u'F', 292 'tf': u'H', 293 'tm': u'H', 294 'h': u'H' 295 } 296 #--------------------------------------------------------
297 - def __init__(self):
298 cDrugDataSourceInterface.__init__(self) 299 _log.info(cFreeDiamsInterface.version) 300 301 self.__imported_drugs = [] 302 303 self.__gm2fd_filename = gmTools.get_unique_filename(prefix = r'gm2freediams-', suffix = r'.xml') 304 _log.debug('GNUmed -> FreeDiams "exchange-in" file: %s', self.__gm2fd_filename) 305 self.__fd2gm_filename = gmTools.get_unique_filename(prefix = r'freediams2gm-', suffix = r'.xml') 306 _log.debug('GNUmed <-> FreeDiams "exchange-out"/"prescription" file: %s', self.__fd2gm_filename) 307 paths = gmTools.gmPaths() 308 self.__fd4gm_config_file = os.path.join(paths.home_dir, '.gnumed', 'freediams4gm.conf') 309 310 self.path_to_binary = None 311 self.__detect_binary()
312 #--------------------------------------------------------
313 - def get_data_source_version(self):
314 # ~/.freediams/config.ini: [License] -> AcceptedVersion=.... 315 316 if not self.__detect_binary(): 317 return False 318 319 freediams = subprocess.Popen ( 320 args = u'--version', # --version or -version or -v 321 executable = self.path_to_binary, 322 stdout = subprocess.PIPE, 323 stderr = subprocess.PIPE, 324 # close_fds = True, # Windows can't do that in conjunction with stdout/stderr = ... :-( 325 universal_newlines = True 326 ) 327 data, errors = freediams.communicate() 328 version = regex.search('FreeDiams\s\d.\d.\d', data).group().split()[1] 329 _log.debug('FreeDiams %s', version) 330 331 return version
332 #--------------------------------------------------------
333 - def create_data_source_entry(self):
334 return create_data_source ( 335 long_name = u'"FreeDiams" Drug Database Frontend', 336 short_name = u'FreeDiams', 337 version = self.get_data_source_version(), 338 source = u'http://ericmaeker.fr/FreeMedForms/di-manual/index.html', 339 language = u'fr' # actually to be multi-locale 340 )
341 #--------------------------------------------------------
342 - def switch_to_frontend(self, blocking=False, mode='interactions'):
343 """http://ericmaeker.fr/FreeMedForms/di-manual/en/html/ligne_commandes.html""" 344 345 _log.debug('calling FreeDiams in [%s] mode', mode) 346 347 self.__imported_drugs = [] 348 349 if not self.__detect_binary(): 350 return False 351 352 self.__create_gm2fd_file(mode = mode) 353 354 args = u'--exchange-in="%s"' % (self.__gm2fd_filename) 355 cmd = r'%s %s' % (self.path_to_binary, args) 356 if os.name == 'nt': 357 blocking = True 358 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking): 359 _log.error('problem switching to the FreeDiams drug database') 360 return False 361 362 if blocking == True: 363 self.import_fd2gm_file_as_drugs() 364 365 return True
366 #--------------------------------------------------------
367 - def import_drugs(self):
368 self.switch_to_frontend(blocking = True)
369 #--------------------------------------------------------
370 - def check_interactions(self, substance_intakes=None):
371 if substance_intakes is None: 372 return 373 if len(substance_intakes) < 2: 374 return 375 376 self.__create_prescription_file(substance_intakes = substance_intakes) 377 self.switch_to_frontend(mode = 'interactions', blocking = False)
378 #--------------------------------------------------------
379 - def show_info_on_drug(self, substance_intake=None):
380 if substance_intake is None: 381 return 382 383 self.__create_prescription_file(substance_intakes = [substance_intake]) 384 self.switch_to_frontend(mode = 'interactions', blocking = False)
385 #--------------------------------------------------------
386 - def show_info_on_substance(self, substance_intake=None):
387 self.show_info_on_drug(substance_intake = substance_intake)
388 #--------------------------------------------------------
389 - def prescribe(self, substance_intakes=None):
390 if substance_intakes is None: 391 if not self.__export_latest_prescription(): 392 self.__create_prescription_file() 393 else: 394 self.__create_prescription_file(substance_intakes = substance_intakes) 395 396 self.switch_to_frontend(mode = 'prescription', blocking = True) 397 self.import_fd2gm_file_as_prescription() 398 399 return self.__imported_drugs
400 #-------------------------------------------------------- 401 # internal helpers 402 #--------------------------------------------------------
403 - def __detect_binary(self):
404 405 if self.path_to_binary is not None: 406 return True 407 408 found, cmd = gmShellAPI.find_first_binary(binaries = [ 409 r'/usr/bin/freediams', 410 r'freediams', 411 r'/Applications/FreeDiams.app/Contents/MacOs/FreeDiams', 412 r'C:\Program Files (x86)\FreeDiams\freediams.exe', 413 r'C:\Program Files\FreeDiams\freediams.exe', 414 r'c:\programs\freediams\freediams.exe', 415 r'freediams.exe' 416 ]) 417 418 if found: 419 self.path_to_binary = cmd 420 return True 421 422 try: 423 self.custom_path_to_binary 424 except AttributeError: 425 _log.error('cannot find FreeDiams binary, no custom path set') 426 return False 427 428 if self.custom_path_to_binary is None: 429 _log.error('cannot find FreeDiams binary') 430 return False 431 432 found, cmd = gmShellAPI.detect_external_binary(binary = self.custom_path_to_binary) 433 if found: 434 self.path_to_binary = cmd 435 return True 436 437 _log.error('cannot find FreeDiams binary') 438 return False
439 #--------------------------------------------------------
441 442 if self.patient is None: 443 _log.debug('cannot export latest FreeDiams prescriptions w/o patient') 444 return False 445 446 docs = self.patient.get_document_folder() 447 prescription = docs.get_latest_freediams_prescription() 448 if prescription is None: 449 _log.debug('no FreeDiams prescription available') 450 return False 451 452 for part in prescription.parts: 453 if part['filename'] == u'freediams-prescription.xml': 454 if part.export_to_file(filename = self.__fd2gm_filename) is not None: 455 return True 456 457 _log.error('cannot export latest FreeDiams prescription to XML file') 458 459 return False
460 #--------------------------------------------------------
461 - def __create_prescription_file(self, substance_intakes=None):
462 """FreeDiams calls this exchange-out or prescription file. 463 464 CIS stands for Unique Speciality Identifier (eg bisoprolol 5 mg, gel). 465 CIS is AFSSAPS specific, but pharmacist can retreive drug name with the CIS. 466 AFSSAPS is the French FDA. 467 468 CIP stands for Unique Presentation Identifier (eg 30 pills plaq) 469 CIP if you want to specify the packaging of the drug (30 pills 470 thermoformed tablet...) -- actually not really usefull for french 471 doctors. 472 # .external_code_type: u'FR-CIS' 473 # .external_cod: the CIS value 474 475 OnlyForTest: 476 OnlyForTest drugs will be processed by the IA Engine but 477 not printed (regardless of FreeDiams mode). They are shown 478 in gray in the prescription view. 479 480 Select-only is a mode where FreeDiams creates a list of drugs 481 not a full prescription. In this list, users can add ForTestOnly 482 drug if they want to 483 1. print the list without some drugs 484 2. but including these drugs in the IA engine calculation 485 486 Select-Only mode does not have any relation with the ForTestOnly drugs. 487 488 IsTextual: 489 What is the use and significance of the 490 <IsTextual>true/false</IsTextual> 491 flag when both <DrugName> and <TextualDrugName> exist ? 492 493 This tag must be setted even if it sounds like a duplicated 494 data. This tag is needed inside FreeDiams code. 495 496 INN: 497 GNUmed will pass the substance in <TextualDrugName 498 and will also pass <INN>True</INN>. 499 500 Eric: Nop, this is not usefull because pure textual drugs 501 are not processed but just shown. 502 """ 503 # virginize file 504 open(self.__fd2gm_filename, 'wb').close() 505 506 # make sure we've got something to do 507 if substance_intakes is None: 508 if self.patient is None: 509 _log.warning('cannot create prescription file because there is neither a patient nor a substance intake list') 510 # do fail because __export_latest_prescription() should not have been called without patient 511 return False 512 emr = self.patient.get_emr() 513 substance_intakes = emr.get_current_substance_intake ( 514 include_inactive = False, 515 include_unapproved = True 516 ) 517 518 drug_snippets = [] 519 520 # process FD drugs 521 fd_intakes = [ i for i in substance_intakes if ( 522 (i['intake_is_approved_of'] is True) 523 and 524 (i['external_code_type_brand'] is not None) 525 and 526 (i['external_code_type_brand'].startswith(u'FreeDiams::')) 527 )] 528 529 intakes_pooled_by_brand = {} 530 for intake in fd_intakes: 531 # this will leave only one entry per brand 532 # but FreeDiams knows the components ... 533 intakes_pooled_by_brand[intake['brand']] = intake 534 del fd_intakes 535 536 drug_snippet = u"""<Prescription> 537 <IsTextual>False</IsTextual> 538 <DrugName>%s</DrugName> 539 <Drug_UID>%s</Drug_UID> 540 <Drug_UID_type>%s</Drug_UID_type> <!-- not yet supported by FreeDiams --> 541 </Prescription>""" 542 543 last_db_id = u'CA_HCDPD' 544 for intake in intakes_pooled_by_brand.values(): 545 last_db_id = gmTools.xml_escape_string(text = intake['external_code_type_brand'].replace(u'FreeDiams::', u'').split(u'::')[0]) 546 drug_snippets.append(drug_snippet % ( 547 gmTools.xml_escape_string(text = intake['brand'].strip()), 548 gmTools.xml_escape_string(text = intake['external_code_brand'].strip()), 549 last_db_id 550 )) 551 552 # process non-FD drugs 553 non_fd_intakes = [ i for i in substance_intakes if ( 554 (i['intake_is_approved_of'] is True) 555 and ( 556 (i['external_code_type_brand'] is None) 557 or 558 (not i['external_code_type_brand'].startswith(u'FreeDiams::')) 559 ) 560 )] 561 562 non_fd_brand_intakes = [ i for i in non_fd_intakes if i['brand'] is not None ] 563 non_fd_substance_intakes = [ i for i in non_fd_intakes if i['brand'] is None ] 564 del non_fd_intakes 565 566 drug_snippet = u"""<Prescription> 567 <IsTextual>True</IsTextual> 568 <TextualDrugName>%s</TextualDrugName> 569 </Prescription>""" 570 571 for intake in non_fd_substance_intakes: 572 drug_name = u'%s %s%s (%s)%s' % ( 573 intake['substance'], 574 intake['amount'], 575 intake['unit'], 576 intake['preparation'], 577 gmTools.coalesce(intake['schedule'], u'', _('\n Take: %s')) 578 ) 579 drug_snippets.append(drug_snippet % gmTools.xml_escape_string(text = drug_name.strip())) 580 581 intakes_pooled_by_brand = {} 582 for intake in non_fd_brand_intakes: 583 brand = u'%s %s' % (intake['brand'], intake['preparation']) 584 try: 585 intakes_pooled_by_brand[brand].append(intake) 586 except KeyError: 587 intakes_pooled_by_brand[brand] = [intake] 588 589 for brand, comps in intakes_pooled_by_brand.iteritems(): 590 drug_name = u'%s\n' % brand 591 for comp in comps: 592 drug_name += u' %s %s%s\n' % ( 593 comp['substance'], 594 comp['amount'], 595 comp['unit'] 596 ) 597 if comps[0]['schedule'] is not None: 598 drug_name += gmTools.coalesce(comps[0]['schedule'], u'', _('Take: %s')) 599 drug_snippets.append(drug_snippet % gmTools.xml_escape_string(text = drug_name.strip())) 600 601 # assemble XML file 602 xml = u"""<?xml version = "1.0" encoding = "UTF-8"?> 603 604 <FreeDiams> 605 <DrugsDatabaseName>%s</DrugsDatabaseName> 606 <FullPrescription version="0.5.0"> 607 608 %s 609 610 </FullPrescription> 611 </FreeDiams> 612 """ 613 614 xml_file = codecs.open(self.__fd2gm_filename, 'wb', 'utf8') 615 xml_file.write(xml % ( 616 last_db_id, 617 u'\n\t\t'.join(drug_snippets) 618 )) 619 xml_file.close() 620 621 return True
622 #--------------------------------------------------------
623 - def __create_gm2fd_file(self, mode='interactions'):
624 625 if mode == 'interactions': 626 mode = u'select-only' 627 elif mode == 'prescription': 628 mode = u'prescriber' 629 else: 630 mode = u'select-only' 631 632 xml_file = codecs.open(self.__gm2fd_filename, 'wb', 'utf8') 633 634 xml = u"""<?xml version="1.0" encoding="UTF-8"?> 635 636 <FreeDiams_In version="0.5.0"> 637 <EMR name="GNUmed" uid="unused"/> 638 <ConfigFile value="%s"/> 639 <ExchangeOut value="%s" format="xml"/> 640 <!-- <DrugsDatabase uid="can be set to a specific DB"/> --> 641 <Ui editmode="%s" blockPatientDatas="1"/> 642 %%s 643 </FreeDiams_In> 644 645 <!-- 646 # FIXME: search by LOINC code and add (as soon as supported by FreeDiams ...) 647 <Creatinine value="12" unit="mg/l or mmol/l"/> 648 <Weight value="70" unit="kg or pd" /> 649 <Height value="170" unit="cm or "/> 650 <ICD10 value="J11.0;A22;Z23"/> 651 --> 652 """ % ( 653 self.__fd4gm_config_file, 654 self.__fd2gm_filename, 655 mode 656 ) 657 658 if self.patient is None: 659 xml_file.write(xml % u'') 660 xml_file.close() 661 return 662 663 name = self.patient.get_active_name() 664 if self.patient['dob'] is None: 665 dob = u'' 666 else: 667 dob = self.patient['dob'].strftime(cFreeDiamsInterface.default_dob_format) 668 669 emr = self.patient.get_emr() 670 allgs = emr.get_allergies() 671 atc_allgs = [ 672 a['atc_code'] for a in allgs if ((a['atc_code'] is not None) and (a['type'] == u'allergy')) 673 ] 674 atc_sens = [ 675 a['atc_code'] for a in allgs if ((a['atc_code'] is not None) and (a['type'] == u'sensitivity')) 676 ] 677 inn_allgs = [ 678 a['allergene'] for a in allgs if ((a['allergene'] is not None) and (a['type'] == u'allergy')) 679 ] 680 inn_sens = [ 681 a['allergene'] for a in allgs if ((a['allergene'] is not None) and (a['type'] == u'sensitivity')) 682 ] 683 # this is rather fragile: FreeDiams won't know what type of UID this is 684 # (but it will assume it is of the type of the drug database in use) 685 # but eventually FreeDiams puts all drugs into one database :-) 686 uid_allgs = [ 687 a['substance_code'] for a in allgs if ((a['substance_code'] is not None) and (a['type'] == u'allergy')) 688 ] 689 uid_sens = [ 690 a['substance_code'] for a in allgs if ((a['substance_code'] is not None) and (a['type'] == u'sensitivity')) 691 ] 692 693 patient_xml = u"""<Patient> 694 <Identity 695 lastnames="%s" 696 firstnames="%s" 697 uid="%s" 698 dob="%s" 699 gender="%s" 700 /> 701 <ATCAllergies value="%s"/> 702 <ATCIntolerances value="%s"/> 703 704 <InnAllergies value="%s"/> 705 <InnIntolerances value="%s"/> 706 707 <DrugsUidAllergies value="%s"/> 708 <DrugsUidIntolerances value="%s"/> 709 </Patient> 710 """ % ( 711 gmTools.xml_escape_string(text = name['lastnames']), 712 gmTools.xml_escape_string(text = name['firstnames']), 713 self.patient.ID, 714 dob, 715 cFreeDiamsInterface.map_gender2mf[self.patient['gender']], 716 gmTools.xml_escape_string(text = u';'.join(atc_allgs)), 717 gmTools.xml_escape_string(text = u';'.join(atc_sens)), 718 gmTools.xml_escape_string(text = u';'.join(inn_allgs)), 719 gmTools.xml_escape_string(text = u';'.join(inn_sens)), 720 gmTools.xml_escape_string(text = u';'.join(uid_allgs)), 721 gmTools.xml_escape_string(text = u';'.join(uid_sens)) 722 ) 723 724 xml_file.write(xml % patient_xml) 725 xml_file.close()
726 #--------------------------------------------------------
727 - def import_fd2gm_file_as_prescription(self, filename=None):
728 729 if filename is None: 730 filename = self.__fd2gm_filename 731 732 fd2gm_xml = etree.ElementTree() 733 fd2gm_xml.parse(filename) 734 735 pdfs = fd2gm_xml.findall('ExtraDatas/Printed') 736 if len(pdfs) == 0: 737 return 738 739 fd_filenames = [] 740 for pdf in pdfs: 741 fd_filenames.append(pdf.attrib['file']) 742 743 docs = self.patient.get_document_folder() 744 emr = self.patient.get_emr() 745 746 prescription = docs.add_document ( 747 document_type = create_document_type ( 748 document_type = DOCUMENT_TYPE_PRESCRIPTION 749 )['pk_doc_type'], 750 encounter = emr.active_encounter['pk_encounter'], 751 episode = emr.add_episode ( 752 episode_name = DEFAULT_MEDICATION_HISTORY_EPISODE, 753 is_open = False 754 )['pk_episode'] 755 ) 756 prescription['ext_ref'] = u'FreeDiams' 757 prescription.save() 758 fd_filenames.append(filename) 759 success, msg, parts = prescription.add_parts_from_files(files = fd_filenames) 760 if not success: 761 _log.error(msg) 762 return 763 764 for part in parts: 765 part['obj_comment'] = _('copy') 766 part.save() 767 768 xml_part = parts[-1] 769 xml_part['filename'] = u'freediams-prescription.xml' 770 xml_part['obj_comment'] = _('data') 771 xml_part.save() 772 773 # are we the intended reviewer ? 774 from Gnumed.business.gmPerson import gmCurrentProvider 775 me = gmCurrentProvider() 776 # if so: auto-sign the prescription 777 if xml_part['pk_intended_reviewer'] == me['pk_staff']: 778 prescription.set_reviewed(technically_abnormal = False, clinically_relevant = False)
779 #--------------------------------------------------------
780 - def import_fd2gm_file_as_drugs(self, filename=None):
781 """ 782 If returning textual prescriptions (say, drugs which FreeDiams 783 did not know) then "IsTextual" will be True and UID will be -1. 784 """ 785 if filename is None: 786 filename = self.__fd2gm_filename 787 788 # FIXME: do not import IsTextual drugs, or rather, make that configurable 789 790 fd2gm_xml = etree.ElementTree() 791 fd2gm_xml.parse(filename) 792 793 data_src_pk = self.create_data_source_entry() 794 795 db_def = fd2gm_xml.find('DrugsDatabaseName') 796 db_id = db_def.text.strip() 797 drug_id_name = db_def.attrib['drugUidName'] 798 fd_xml_drug_entries = fd2gm_xml.findall('FullPrescription/Prescription') 799 800 self.__imported_drugs = [] 801 for fd_xml_drug in fd_xml_drug_entries: 802 drug_uid = fd_xml_drug.find('Drug_UID').text.strip() 803 if drug_uid == u'-1': 804 _log.debug('skipping textual drug') 805 continue # it's a TextualDrug, skip it 806 drug_name = fd_xml_drug.find('DrugName').text.replace(', )', ')').strip() 807 drug_form = fd_xml_drug.find('DrugForm').text.strip() 808 drug_atc = fd_xml_drug.find('DrugATC') 809 if drug_atc is None: 810 drug_atc = u'' 811 else: 812 if drug_atc.text is None: 813 drug_atc = u'' 814 else: 815 drug_atc = drug_atc.text.strip() 816 817 # create new branded drug 818 new_drug = create_branded_drug(brand_name = drug_name, preparation = drug_form, return_existing = True) 819 self.__imported_drugs.append(new_drug) 820 new_drug['is_fake_brand'] = False 821 new_drug['atc'] = drug_atc 822 new_drug['external_code_type'] = u'FreeDiams::%s::%s' % (db_id, drug_id_name) 823 new_drug['external_code'] = drug_uid 824 new_drug['pk_data_source'] = data_src_pk 825 new_drug.save() 826 827 # parse XML for composition records 828 fd_xml_components = fd_xml_drug.getiterator('Composition') 829 comp_data = {} 830 for fd_xml_comp in fd_xml_components: 831 832 data = {} 833 834 amount = regex.match(r'\d+[.,]{0,1}\d*', fd_xml_comp.attrib['strenght'].strip()) # sic, typo 835 if amount is None: 836 amount = 99999 837 else: 838 amount = amount.group() 839 data['amount'] = amount 840 841 unit = regex.sub(r'\d+[.,]{0,1}\d*', u'', fd_xml_comp.attrib['strenght'].strip()).strip() # sic, typo 842 if unit == u'': 843 unit = u'*?*' 844 data['unit'] = unit 845 846 molecule_name = fd_xml_comp.attrib['molecularName'].strip() 847 if molecule_name != u'': 848 create_consumable_substance(substance = molecule_name, atc = None, amount = amount, unit = unit) 849 data['molecule_name'] = molecule_name 850 851 inn_name = fd_xml_comp.attrib['inn'].strip() 852 if inn_name != u'': 853 create_consumable_substance(substance = inn_name, atc = None, amount = amount, unit = unit) 854 data['inn_name'] = molecule_name 855 856 if molecule_name == u'': 857 data['substance'] = inn_name 858 _log.info('linking INN [%s] rather than molecularName as component', inn_name) 859 else: 860 data['substance'] = molecule_name 861 862 data['nature'] = fd_xml_comp.attrib['nature'].strip() 863 data['nature_ID'] = fd_xml_comp.attrib['natureLink'].strip() 864 865 # merge composition records of SA/FT nature 866 try: 867 old_data = comp_data[data['nature_ID']] 868 # normalize INN 869 if old_data['inn_name'] == u'': 870 old_data['inn_name'] = data['inn_name'] 871 if data['inn_name'] == u'': 872 data['inn_name'] = old_data['inn_name'] 873 # normalize molecule 874 if old_data['molecule_name'] == u'': 875 old_data['molecule_name'] = data['molecule_name'] 876 if data['molecule_name'] == u'': 877 data['molecule_name'] = old_data['molecule_name'] 878 # FT: transformed form 879 # SA: active substance 880 # it would be preferable to use the SA record because that's what's *actually* 881 # contained in the drug, however FreeDiams does not list the amount thereof 882 # (rather that of the INN) 883 if data['nature'] == u'FT': 884 comp_data[data['nature_ID']] = data 885 else: 886 comp_data[data['nature_ID']] = old_data 887 888 # or create new record 889 except KeyError: 890 comp_data[data['nature_ID']] = data 891 892 # actually create components from (possibly merged) composition records 893 for key, data in comp_data.items(): 894 new_drug.add_component ( 895 substance = data['substance'], 896 atc = None, 897 amount = data['amount'], 898 unit = data['unit'] 899 )
900 #============================================================
901 -class cGelbeListeWindowsInterface(cDrugDataSourceInterface):
902 """Support v8.2 CSV file interface only.""" 903 904 version = u'Gelbe Liste/MMI v8.2 interface' 905 default_encoding = 'cp1250' 906 bdt_line_template = u'%03d6210#%s\r\n' # Medikament verordnet auf Kassenrezept 907 bdt_line_base_length = 8 908 #--------------------------------------------------------
909 - def __init__(self):
910 911 cDrugDataSourceInterface.__init__(self) 912 913 _log.info(u'%s (native Windows)', cGelbeListeWindowsInterface.version) 914 915 self.path_to_binary = r'C:\Programme\MMI PHARMINDEX\glwin.exe' 916 self.args = r'-KEEPBACKGROUND -PRESCRIPTIONFILE %s -CLOSETOTRAY' 917 918 paths = gmTools.gmPaths() 919 920 self.default_csv_filename = os.path.join(paths.home_dir, '.gnumed', 'tmp', 'rezept.txt') 921 self.default_csv_filename_arg = os.path.join(paths.home_dir, '.gnumed', 'tmp') 922 self.interactions_filename = os.path.join(paths.home_dir, '.gnumed', 'tmp', 'gm2mmi.bdt') 923 self.data_date_filename = r'C:\Programme\MMI PHARMINDEX\datadate.txt' 924 925 self.__data_date = None 926 self.__online_update_date = None
927 928 # use adjusted config.dat 929 #--------------------------------------------------------
930 - def get_data_source_version(self, force_reload=False):
931 932 if self.__data_date is not None: 933 if not force_reload: 934 return { 935 'data': self.__data_date, 936 'online_update': self.__online_update_date 937 } 938 try: 939 open(self.data_date_filename, 'wb').close() 940 except StandardError: 941 _log.error('problem querying the MMI drug database for version information') 942 _log.exception('cannot create MMI drug database version file [%s]', self.data_date_filename) 943 self.__data_date = None 944 self.__online_update_date = None 945 return { 946 'data': u'?', 947 'online_update': u'?' 948 } 949 950 cmd = u'%s -DATADATE' % self.path_to_binary 951 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): 952 _log.error('problem querying the MMI drug database for version information') 953 self.__data_date = None 954 self.__online_update_date = None 955 return { 956 'data': u'?', 957 'online_update': u'?' 958 } 959 960 try: 961 version_file = open(self.data_date_filename, 'rU') 962 except StandardError: 963 _log.error('problem querying the MMI drug database for version information') 964 _log.exception('cannot open MMI drug database version file [%s]', self.data_date_filename) 965 self.__data_date = None 966 self.__online_update_date = None 967 return { 968 'data': u'?', 969 'online_update': u'?' 970 } 971 972 self.__data_date = version_file.readline()[:10] 973 self.__online_update_date = version_file.readline()[:10] 974 version_file.close() 975 976 return { 977 'data': self.__data_date, 978 'online_update': self.__online_update_date 979 }
980 #--------------------------------------------------------
981 - def create_data_source_entry(self):
982 versions = self.get_data_source_version() 983 984 return create_data_source ( 985 long_name = u'Medikamentendatenbank "mmi PHARMINDEX" (Gelbe Liste)', 986 short_name = u'GL/MMI', 987 version = u'Daten: %s, Preise (Onlineupdate): %s' % (versions['data'], versions['online_update']), 988 source = u'Medizinische Medien Informations GmbH, Am Forsthaus Gravenbruch 7, 63263 Neu-Isenburg', 989 language = u'de' 990 )
991 #--------------------------------------------------------
992 - def switch_to_frontend(self, blocking=False, cmd=None):
993 994 try: 995 # must make sure csv file exists 996 open(self.default_csv_filename, 'wb').close() 997 except IOError: 998 _log.exception('problem creating GL/MMI <-> GNUmed exchange file') 999 return False 1000 1001 if cmd is None: 1002 cmd = (u'%s %s' % (self.path_to_binary, self.args)) % self.default_csv_filename_arg 1003 1004 if os.name == 'nt': 1005 blocking = True 1006 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking): 1007 _log.error('problem switching to the MMI drug database') 1008 # apparently on the first call MMI does not 1009 # consistently return 0 on success 1010 # return False 1011 1012 return True
1013 #--------------------------------------------------------
1014 - def __let_user_select_drugs(self):
1015 1016 # better to clean up interactions file 1017 open(self.interactions_filename, 'wb').close() 1018 1019 if not self.switch_to_frontend(blocking = True): 1020 return None 1021 1022 return cGelbeListeCSVFile(filename = self.default_csv_filename)
1023 #--------------------------------------------------------
1024 - def import_drugs_as_substances(self):
1025 1026 selected_drugs = self.__let_user_select_drugs() 1027 if selected_drugs is None: 1028 return None 1029 1030 new_substances = [] 1031 1032 for drug in selected_drugs: 1033 atc = None # hopefully MMI eventually supports atc-per-substance in a drug... 1034 if len(drug['wirkstoffe']) == 1: 1035 atc = drug['atc'] 1036 for wirkstoff in drug['wirkstoffe']: 1037 new_substances.append(create_consumable_substance(substance = wirkstoff, atc = atc, amount = amount, unit = unit)) 1038 1039 selected_drugs.close() 1040 1041 return new_substances
1042 #--------------------------------------------------------
1043 - def import_drugs(self):
1044 1045 selected_drugs = self.__let_user_select_drugs() 1046 if selected_drugs is None: 1047 return None 1048 1049 data_src_pk = self.create_data_source_entry() 1050 1051 new_drugs = [] 1052 new_substances = [] 1053 1054 for entry in selected_drugs: 1055 1056 _log.debug('importing drug: %s %s', entry['name'], entry['darreichungsform']) 1057 1058 if entry[u'hilfsmittel']: 1059 _log.debug('skipping Hilfsmittel') 1060 continue 1061 1062 if entry[u'erstattbares_medizinprodukt']: 1063 _log.debug('skipping sonstiges Medizinprodukt') 1064 continue 1065 1066 # create branded drug (or get it if it already exists) 1067 drug = create_branded_drug(brand_name = entry['name'], preparation = entry['darreichungsform']) 1068 if drug is None: 1069 drug = get_drug_by_brand(brand_name = entry['name'], preparation = entry['darreichungsform']) 1070 new_drugs.append(drug) 1071 1072 # update fields 1073 drug['is_fake_brand'] = False 1074 drug['atc'] = entry['atc'] 1075 drug['external_code_type'] = u'DE-PZN' 1076 drug['external_code'] = entry['pzn'] 1077 drug['fk_data_source'] = data_src_pk 1078 drug.save() 1079 1080 # add components to brand 1081 atc = None # hopefully MMI eventually supports atc-per-substance in a drug... 1082 if len(entry['wirkstoffe']) == 1: 1083 atc = entry['atc'] 1084 for wirkstoff in entry['wirkstoffe']: 1085 drug.add_component(substance = wirkstoff, atc = atc) 1086 1087 # create as consumable substances, too 1088 atc = None # hopefully MMI eventually supports atc-per-substance in a drug... 1089 if len(entry['wirkstoffe']) == 1: 1090 atc = entry['atc'] 1091 for wirkstoff in entry['wirkstoffe']: 1092 new_substances.append(create_consumable_substance(substance = wirkstoff, atc = atc, amount = amount, unit = unit)) 1093 1094 return new_drugs, new_substances
1095 #--------------------------------------------------------
1096 - def check_interactions(self, drug_ids_list=None, substances=None):
1097 """For this to work the BDT interaction check must be configured in the MMI.""" 1098 1099 if drug_ids_list is None: 1100 if substances is None: 1101 return 1102 if len(substances) < 2: 1103 return 1104 drug_ids_list = [ (s.external_code_type, s.external_code) for s in substances ] 1105 drug_ids_list = [ code_value for code_type, code_value in drug_ids_list if (code_value is not None) and (code_type == u'DE-PZN')] 1106 1107 else: 1108 if len(drug_ids_list) < 2: 1109 return 1110 1111 if drug_ids_list < 2: 1112 return 1113 1114 bdt_file = codecs.open(filename = self.interactions_filename, mode = 'wb', encoding = cGelbeListeWindowsInterface.default_encoding) 1115 1116 for pzn in drug_ids_list: 1117 pzn = pzn.strip() 1118 lng = cGelbeListeWindowsInterface.bdt_line_base_length + len(pzn) 1119 bdt_file.write(cGelbeListeWindowsInterface.bdt_line_template % (lng, pzn)) 1120 1121 bdt_file.close() 1122 1123 self.switch_to_frontend(blocking = True)
1124 #--------------------------------------------------------
1125 - def show_info_on_drug(self, drug=None):
1126 self.switch_to_frontend(blocking = True)
1127 #--------------------------------------------------------
1128 - def show_info_on_substance(self, substance=None):
1129 1130 cmd = None 1131 1132 if substance.external_code_type == u'DE-PZN': 1133 cmd = u'%s -PZN %s' % (self.path_to_binary, substance.external_code) 1134 1135 if cmd is None: 1136 name = gmTools.coalesce ( 1137 substance['brand'], 1138 substance['substance'] 1139 ) 1140 cmd = u'%s -NAME %s' % (self.path_to_binary, name) 1141 1142 # better to clean up interactions file 1143 open(self.interactions_filename, 'wb').close() 1144 1145 self.switch_to_frontend(cmd = cmd)
1146 #============================================================
1147 -class cGelbeListeWineInterface(cGelbeListeWindowsInterface):
1148
1149 - def __init__(self):
1150 cGelbeListeWindowsInterface.__init__(self) 1151 1152 _log.info(u'%s (WINE extension)', cGelbeListeWindowsInterface.version) 1153 1154 # FIXME: if -CLOSETOTRAY is used GNUmed cannot detect the end of MMI 1155 self.path_to_binary = r'wine "C:\Programme\MMI PHARMINDEX\glwin.exe"' 1156 self.args = r'"-PRESCRIPTIONFILE %s -KEEPBACKGROUND"' 1157 1158 paths = gmTools.gmPaths() 1159 1160 self.default_csv_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'windows', 'temp', 'mmi2gm.csv') 1161 self.default_csv_filename_arg = r'c:\windows\temp\mmi2gm.csv' 1162 self.interactions_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'windows', 'temp', 'gm2mmi.bdt') 1163 self.data_date_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'Programme', 'MMI PHARMINDEX', 'datadate.txt')
1164 #============================================================
1165 -class cIfapInterface(cDrugDataSourceInterface):
1166 """empirical CSV interface""" 1167
1168 - def __init__(self):
1169 pass
1170
1171 - def print_transfer_file(self, filename=None):
1172 1173 try: 1174 csv_file = open(filename, 'rb') # FIXME: encoding ? 1175 except: 1176 _log.exception('cannot access [%s]', filename) 1177 csv_file = None 1178 1179 field_names = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split() 1180 1181 if csv_file is None: 1182 return False 1183 1184 csv_lines = csv.DictReader ( 1185 csv_file, 1186 fieldnames = field_names, 1187 delimiter = ';' 1188 ) 1189 1190 for line in csv_lines: 1191 print "--------------------------------------------------------------------"[:31] 1192 for key in field_names: 1193 tmp = ('%s ' % key)[:30] 1194 print '%s: %s' % (tmp, line[key]) 1195 1196 csv_file.close()
1197 1198 # narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % ( 1199 # line['Packungszahl'].strip(), 1200 # line['Handelsname'].strip(), 1201 # line['Form'].strip(), 1202 # line[u'Packungsgr\xf6\xdfe'].strip(), 1203 # line['Abpackungsmenge'].strip(), 1204 # line['Einheit'].strip(), 1205 # line['Hersteller'].strip(), 1206 # line['PZN'].strip() 1207 # ) 1208 #============================================================ 1209 drug_data_source_interfaces = { 1210 'Deutschland: Gelbe Liste/MMI (Windows)': cGelbeListeWindowsInterface, 1211 'Deutschland: Gelbe Liste/MMI (WINE)': cGelbeListeWineInterface, 1212 'FreeDiams (FR, US, CA, ZA)': cFreeDiamsInterface 1213 } 1214 1215 #============================================================ 1216 #============================================================ 1217 # substances in use across all patients 1218 #------------------------------------------------------------ 1219 _SQL_get_consumable_substance = u""" 1220 SELECT *, xmin 1221 FROM ref.consumable_substance 1222 WHERE %s 1223 """ 1224
1225 -class cConsumableSubstance(gmBusinessDBObject.cBusinessDBObject):
1226 1227 _cmd_fetch_payload = _SQL_get_consumable_substance % u"pk = %s" 1228 _cmds_store_payload = [ 1229 u"""UPDATE ref.consumable_substance SET 1230 description = %(description)s, 1231 atc_code = gm.nullify_empty_string(%(atc_code)s), 1232 amount = %(amount)s, 1233 unit = gm.nullify_empty_string(%(unit)s) 1234 WHERE 1235 pk = %(pk)s 1236 AND 1237 xmin = %(xmin)s 1238 AND 1239 -- must not currently be used with a patient directly 1240 NOT EXISTS ( 1241 SELECT 1 1242 FROM clin.substance_intake 1243 WHERE 1244 fk_drug_component IS NULL 1245 AND 1246 fk_substance = %(pk)s 1247 LIMIT 1 1248 ) 1249 AND 1250 -- must not currently be used with a patient indirectly, either 1251 NOT EXISTS ( 1252 SELECT 1 1253 FROM clin.substance_intake 1254 WHERE 1255 fk_drug_component IS NOT NULL 1256 AND 1257 fk_drug_component = ( 1258 SELECT r_ls2b.pk 1259 FROM ref.lnk_substance2brand r_ls2b 1260 WHERE fk_substance = %(pk)s 1261 ) 1262 LIMIT 1 1263 ) 1264 -- -- must not currently be used with a branded drug 1265 -- -- (but this would make it rather hard fixing branded drugs which contain only this substance) 1266 -- NOT EXISTS ( 1267 -- SELECT 1 1268 -- FROM ref.lnk_substance2brand 1269 -- WHERE fk_substance = %(pk)s 1270 -- LIMIT 1 1271 -- ) 1272 RETURNING 1273 xmin 1274 """ 1275 ] 1276 _updatable_fields = [ 1277 u'description', 1278 u'atc_code', 1279 u'amount', 1280 u'unit' 1281 ] 1282 #--------------------------------------------------------
1283 - def save_payload(self, conn=None):
1284 success, data = super(self.__class__, self).save_payload(conn = conn) 1285 1286 if not success: 1287 return (success, data) 1288 1289 if self._payload[self._idx['atc_code']] is not None: 1290 atc = self._payload[self._idx['atc_code']].strip() 1291 if atc != u'': 1292 gmATC.propagate_atc ( 1293 substance = self._payload[self._idx['description']].strip(), 1294 atc = atc 1295 ) 1296 1297 return (success, data)
1298 #-------------------------------------------------------- 1299 # properties 1300 #--------------------------------------------------------
1301 - def _get_is_in_use_by_patients(self):
1302 cmd = u""" 1303 SELECT 1304 EXISTS ( 1305 SELECT 1 1306 FROM clin.substance_intake 1307 WHERE 1308 fk_drug_component IS NULL 1309 AND 1310 fk_substance = %(pk)s 1311 LIMIT 1 1312 ) OR EXISTS ( 1313 SELECT 1 1314 FROM clin.substance_intake 1315 WHERE 1316 fk_drug_component IS NOT NULL 1317 AND 1318 fk_drug_component IN ( 1319 SELECT r_ls2b.pk 1320 FROM ref.lnk_substance2brand r_ls2b 1321 WHERE fk_substance = %(pk)s 1322 ) 1323 LIMIT 1 1324 )""" 1325 args = {'pk': self.pk_obj} 1326 1327 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1328 return rows[0][0]
1329 1330 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x) 1331 #--------------------------------------------------------
1332 - def _get_is_drug_component(self):
1333 cmd = u""" 1334 SELECT EXISTS ( 1335 SELECT 1 1336 FROM ref.lnk_substance2brand 1337 WHERE fk_substance = %(pk)s 1338 LIMIT 1 1339 )""" 1340 args = {'pk': self.pk_obj} 1341 1342 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1343 return rows[0][0]
1344 1345 is_drug_component = property(_get_is_drug_component, lambda x:x)
1346 #------------------------------------------------------------
1347 -def get_consumable_substances(order_by=None):
1348 if order_by is None: 1349 order_by = u'true' 1350 else: 1351 order_by = u'true ORDER BY %s' % order_by 1352 cmd = _SQL_get_consumable_substance % order_by 1353 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 1354 return [ cConsumableSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk'}) for r in rows ]
1355 #------------------------------------------------------------
1356 -def create_consumable_substance(substance=None, atc=None, amount=None, unit=None):
1357 1358 substance = substance 1359 if atc is not None: 1360 atc = atc.strip() 1361 1362 converted, amount = gmTools.input2decimal(amount) 1363 if not converted: 1364 raise ValueError('<amount> must be a number: %s (%s)', amount, type(amount)) 1365 1366 args = { 1367 'desc': substance.strip(), 1368 'amount': amount, 1369 'unit': unit.strip(), 1370 'atc': atc 1371 } 1372 cmd = u""" 1373 SELECT pk FROM ref.consumable_substance 1374 WHERE 1375 lower(description) = lower(%(desc)s) 1376 AND 1377 amount = %(amount)s 1378 AND 1379 unit = %(unit)s 1380 """ 1381 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1382 1383 if len(rows) == 0: 1384 cmd = u""" 1385 INSERT INTO ref.consumable_substance (description, atc_code, amount, unit) VALUES ( 1386 %(desc)s, 1387 gm.nullify_empty_string(%(atc)s), 1388 %(amount)s, 1389 gm.nullify_empty_string(%(unit)s) 1390 ) RETURNING pk""" 1391 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 1392 1393 gmATC.propagate_atc(substance = substance, atc = atc) 1394 1395 return cConsumableSubstance(aPK_obj = rows[0]['pk'])
1396 #------------------------------------------------------------
1397 -def delete_consumable_substance(substance=None):
1398 args = {'pk': substance} 1399 cmd = u""" 1400 DELETE FROM ref.consumable_substance 1401 WHERE 1402 pk = %(pk)s 1403 AND 1404 1405 -- must not currently be used with a patient 1406 NOT EXISTS ( 1407 SELECT 1 1408 FROM clin.v_pat_substance_intake 1409 WHERE pk_substance = %(pk)s 1410 LIMIT 1 1411 ) 1412 AND 1413 1414 -- must not currently be used with a branded drug 1415 NOT EXISTS ( 1416 SELECT 1 1417 FROM ref.lnk_substance2brand 1418 WHERE fk_substance = %(pk)s 1419 LIMIT 1 1420 )""" 1421 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1422 return True
1423 #------------------------------------------------------------
1424 -class cSubstanceMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
1425 1426 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE | regex.LOCALE) 1427 _query1 = u""" 1428 SELECT 1429 pk::text, 1430 (description || ' ' || amount || ' ' || unit) as subst 1431 FROM ref.consumable_substance 1432 WHERE description %(fragment_condition)s 1433 ORDER BY subst 1434 LIMIT 50""" 1435 _query2 = u""" 1436 SELECT 1437 pk::text, 1438 (description || ' ' || amount || ' ' || unit) as subst 1439 FROM ref.consumable_substance 1440 WHERE 1441 %(fragment_condition)s 1442 ORDER BY subst 1443 LIMIT 50""" 1444 1445 #--------------------------------------------------------
1446 - def getMatchesByPhrase(self, aFragment):
1447 """Return matches for aFragment at start of phrases.""" 1448 1449 if cSubstanceMatchProvider._pattern.match(aFragment): 1450 self._queries = [cSubstanceMatchProvider._query2] 1451 fragment_condition = """description ILIKE %(desc)s 1452 AND 1453 amount::text ILIKE %(amount)s""" 1454 self._args['desc'] = u'%s%%' % regex.sub(r'\s*\d+$', u'', aFragment) 1455 self._args['amount'] = u'%s%%' % regex.sub(r'^\D+\s*', u'', aFragment) 1456 else: 1457 self._queries = [cSubstanceMatchProvider._query1] 1458 fragment_condition = u"ILIKE %(fragment)s" 1459 self._args['fragment'] = u"%s%%" % aFragment 1460 1461 return self._find_matches(fragment_condition)
1462 #--------------------------------------------------------
1463 - def getMatchesByWord(self, aFragment):
1464 """Return matches for aFragment at start of words inside phrases.""" 1465 1466 if cSubstanceMatchProvider._pattern.match(aFragment): 1467 self._queries = [cSubstanceMatchProvider._query2] 1468 1469 desc = regex.sub(r'\s*\d+$', u'', aFragment) 1470 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False) 1471 1472 fragment_condition = """description ~* %(desc)s 1473 AND 1474 amount::text ILIKE %(amount)s""" 1475 1476 self._args['desc'] = u"( %s)|(^%s)" % (desc, desc) 1477 self._args['amount'] = u'%s%%' % regex.sub(r'^\D+\s*', u'', aFragment) 1478 else: 1479 self._queries = [cSubstanceMatchProvider._query1] 1480 fragment_condition = u"~* %(fragment)s" 1481 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False) 1482 self._args['fragment'] = u"( %s)|(^%s)" % (aFragment, aFragment) 1483 1484 return self._find_matches(fragment_condition)
1485 #--------------------------------------------------------
1486 - def getMatchesBySubstr(self, aFragment):
1487 """Return matches for aFragment as a true substring.""" 1488 1489 if cSubstanceMatchProvider._pattern.match(aFragment): 1490 self._queries = [cSubstanceMatchProvider._query2] 1491 fragment_condition = """description ILIKE %(desc)s 1492 AND 1493 amount::text ILIKE %(amount)s""" 1494 self._args['desc'] = u'%%%s%%' % regex.sub(r'\s*\d+$', u'', aFragment) 1495 self._args['amount'] = u'%s%%' % regex.sub(r'^\D+\s*', u'', aFragment) 1496 else: 1497 self._queries = [cSubstanceMatchProvider._query1] 1498 fragment_condition = u"ILIKE %(fragment)s" 1499 self._args['fragment'] = u"%%%s%%" % aFragment 1500 1501 return self._find_matches(fragment_condition)
1502 #============================================================
1503 -class cSubstanceIntakeEntry(gmBusinessDBObject.cBusinessDBObject):
1504 """Represents a substance currently taken by a patient.""" 1505 1506 _cmd_fetch_payload = u"SELECT * FROM clin.v_pat_substance_intake WHERE pk_substance_intake = %s" 1507 _cmds_store_payload = [ 1508 u"""UPDATE clin.substance_intake SET 1509 clin_when = %(started)s, 1510 discontinued = %(discontinued)s, 1511 discontinue_reason = gm.nullify_empty_string(%(discontinue_reason)s), 1512 schedule = gm.nullify_empty_string(%(schedule)s), 1513 aim = gm.nullify_empty_string(%(aim)s), 1514 narrative = gm.nullify_empty_string(%(notes)s), 1515 intake_is_approved_of = %(intake_is_approved_of)s, 1516 fk_episode = %(pk_episode)s, 1517 1518 preparation = ( 1519 case 1520 when %(pk_brand)s is NULL then %(preparation)s 1521 else NULL 1522 end 1523 )::text, 1524 1525 is_long_term = ( 1526 case 1527 when ( 1528 (%(is_long_term)s is False) 1529 and 1530 (%(duration)s is NULL) 1531 ) is True then null 1532 else %(is_long_term)s 1533 end 1534 )::boolean, 1535 1536 duration = ( 1537 case 1538 when %(is_long_term)s is True then null 1539 else %(duration)s 1540 end 1541 )::interval 1542 WHERE 1543 pk = %(pk_substance_intake)s 1544 AND 1545 xmin = %(xmin_substance_intake)s 1546 RETURNING 1547 xmin as xmin_substance_intake 1548 """ 1549 ] 1550 _updatable_fields = [ 1551 u'started', 1552 u'discontinued', 1553 u'discontinue_reason', 1554 u'preparation', 1555 u'intake_is_approved_of', 1556 u'schedule', 1557 u'duration', 1558 u'aim', 1559 u'is_long_term', 1560 u'notes', 1561 u'pk_episode' 1562 ] 1563 #--------------------------------------------------------
1564 - def format(self, left_margin=0, date_format='%Y %B %d', one_line=True, allergy=None):
1565 if one_line: 1566 return self.format_as_one_line(left_margin = left_margin, date_format = date_format) 1567 1568 return self.format_as_multiple_lines(left_margin = left_margin, date_format = date_format, allergy = allergy)
1569 #--------------------------------------------------------
1570 - def format_as_one_line(self, left_margin=0, date_format='%Y %B %d'):
1571 1572 if self._payload[self._idx['duration']] is None: 1573 duration = gmTools.bool2subst ( 1574 self._payload[self._idx['is_long_term']], 1575 _('long-term'), 1576 _('short-term'), 1577 _('?short-term') 1578 ) 1579 else: 1580 duration = gmDateTime.format_interval ( 1581 self._payload[self._idx['duration']], 1582 accuracy_wanted = gmDateTime.acc_days 1583 ) 1584 1585 line = u'%s%s (%s %s): %s %s%s %s (%s)' % ( 1586 u' ' * left_margin, 1587 self._payload[self._idx['started']].strftime(date_format), 1588 gmTools.u_right_arrow, 1589 duration, 1590 self._payload[self._idx['substance']], 1591 self._payload[self._idx['amount']], 1592 self._payload[self._idx['unit']], 1593 self._payload[self._idx['preparation']], 1594 gmTools.bool2subst(self._payload[self._idx['is_currently_active']], _('ongoing'), _('inactive'), _('?ongoing')) 1595 ) 1596 1597 return line
1598 #--------------------------------------------------------
1599 - def format_as_multiple_lines(self, left_margin=0, date_format='%Y %B %d', allergy=None):
1600 1601 txt = _('Substance intake entry (%s, %s) [#%s] \n') % ( 1602 gmTools.bool2subst ( 1603 boolean = self._payload[self._idx['is_currently_active']], 1604 true_return = gmTools.bool2subst ( 1605 boolean = self._payload[self._idx['seems_inactive']], 1606 true_return = _('active, needs check'), 1607 false_return = _('active'), 1608 none_return = _('assumed active') 1609 ), 1610 false_return = _('inactive') 1611 ), 1612 gmTools.bool2subst ( 1613 boolean = self._payload[self._idx['intake_is_approved_of']], 1614 true_return = _('approved'), 1615 false_return = _('unapproved') 1616 ), 1617 self._payload[self._idx['pk_substance_intake']] 1618 ) 1619 1620 if allergy is not None: 1621 certainty = gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected')) 1622 txt += u'\n' 1623 txt += u' !! ---- Cave ---- !!\n' 1624 txt += u' %s (%s): %s (%s)\n' % ( 1625 allergy['l10n_type'], 1626 certainty, 1627 allergy['descriptor'], 1628 gmTools.coalesce(allergy['reaction'], u'')[:40] 1629 ) 1630 txt += u'\n' 1631 1632 txt += u' ' + _('Substance: %s [#%s]\n') % (self._payload[self._idx['substance']], self._payload[self._idx['pk_substance']]) 1633 txt += u' ' + _('Preparation: %s\n') % self._payload[self._idx['preparation']] 1634 txt += u' ' + _('Amount per dose: %s %s') % (self._payload[self._idx['amount']], self._payload[self._idx['unit']]) 1635 if self.ddd is not None: 1636 txt += u' (DDD: %s %s)' % (self.ddd['ddd'], self.ddd['unit']) 1637 txt += u'\n' 1638 txt += gmTools.coalesce(self._payload[self._idx['atc_substance']], u'', _(' ATC (substance): %s\n')) 1639 1640 txt += u'\n' 1641 1642 txt += gmTools.coalesce ( 1643 self._payload[self._idx['brand']], 1644 u'', 1645 _(' Brand name: %%s [#%s]\n') % self._payload[self._idx['pk_brand']] 1646 ) 1647 txt += gmTools.coalesce(self._payload[self._idx['atc_brand']], u'', _(' ATC (brand): %s\n')) 1648 1649 txt += u'\n' 1650 1651 txt += gmTools.coalesce(self._payload[self._idx['schedule']], u'', _(' Regimen: %s\n')) 1652 1653 if self._payload[self._idx['is_long_term']]: 1654 duration = u' %s %s' % (gmTools.u_right_arrow, gmTools.u_infinity) 1655 else: 1656 if self._payload[self._idx['duration']] is None: 1657 duration = u'' 1658 else: 1659 duration = u' %s %s' % (gmTools.u_right_arrow, gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days)) 1660 1661 txt += _(' Started %s%s%s\n') % ( 1662 gmDateTime.pydt_strftime ( 1663 self._payload[self._idx['started']], 1664 format = date_format, 1665 accuracy = gmDateTime.acc_days 1666 ), 1667 duration, 1668 gmTools.bool2subst(self._payload[self._idx['is_long_term']], _(' (long-term)'), _(' (short-term)'), u'') 1669 ) 1670 1671 if self._payload[self._idx['discontinued']] is not None: 1672 txt += _(' Discontinued %s\n') % ( 1673 gmDateTime.pydt_strftime ( 1674 self._payload[self._idx['discontinued']], 1675 format = date_format, 1676 accuracy = gmDateTime.acc_days 1677 ) 1678 ) 1679 txt += _(' Reason: %s\n') % self._payload[self._idx['discontinue_reason']] 1680 1681 txt += u'\n' 1682 1683 txt += gmTools.coalesce(self._payload[self._idx['aim']], u'', _(' Aim: %s\n')) 1684 txt += gmTools.coalesce(self._payload[self._idx['episode']], u'', _(' Episode: %s\n')) 1685 txt += gmTools.coalesce(self._payload[self._idx['health_issue']], u'', _(' Health issue: %s\n')) 1686 txt += gmTools.coalesce(self._payload[self._idx['notes']], u'', _(' Advice: %s\n')) 1687 1688 txt += u'\n' 1689 1690 txt += _(u'Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % { 1691 'row_ver': self._payload[self._idx['row_version']], 1692 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]), 1693 'mod_by': self._payload[self._idx['modified_by']] 1694 } 1695 1696 return txt
1697 #--------------------------------------------------------
1698 - def turn_into_allergy(self, encounter_id=None, allergy_type='allergy'):
1699 allg = gmAllergy.create_allergy ( 1700 allergene = self._payload[self._idx['substance']], 1701 allg_type = allergy_type, 1702 episode_id = self._payload[self._idx['pk_episode']], 1703 encounter_id = encounter_id 1704 ) 1705 allg['substance'] = gmTools.coalesce ( 1706 self._payload[self._idx['brand']], 1707 self._payload[self._idx['substance']] 1708 ) 1709 allg['reaction'] = self._payload[self._idx['discontinue_reason']] 1710 allg['atc_code'] = gmTools.coalesce(self._payload[self._idx['atc_substance']], self._payload[self._idx['atc_brand']]) 1711 if self._payload[self._idx['external_code_brand']] is not None: 1712 allg['substance_code'] = u'%s::::%s' % (self._payload[self._idx['external_code_type_brand']], self._payload[self._idx['external_code_brand']]) 1713 1714 if self._payload[self._idx['pk_brand']] is None: 1715 allg['generics'] = self._payload[self._idx['substance']] 1716 else: 1717 comps = [ c['substance'] for c in self.containing_drug.components ] 1718 if len(comps) == 0: 1719 allg['generics'] = self._payload[self._idx['substance']] 1720 else: 1721 allg['generics'] = u'; '.join(comps) 1722 1723 allg.save() 1724 return allg
1725 #-------------------------------------------------------- 1726 # properties 1727 #--------------------------------------------------------
1728 - def _get_ddd(self):
1729 1730 try: self.__ddd 1731 except AttributeError: self.__ddd = None 1732 1733 if self.__ddd is not None: 1734 return self.__ddd 1735 1736 if self._payload[self._idx['atc_substance']] is not None: 1737 ddd = gmATC.atc2ddd(atc = self._payload[self._idx['atc_substance']]) 1738 if len(ddd) != 0: 1739 self.__ddd = ddd[0] 1740 else: 1741 if self._payload[self._idx['atc_brand']] is not None: 1742 ddd = gmATC.atc2ddd(atc = self._payload[self._idx['atc_brand']]) 1743 if len(ddd) != 0: 1744 self.__ddd = ddd[0] 1745 1746 return self.__ddd
1747 1748 ddd = property(_get_ddd, lambda x:x) 1749 #--------------------------------------------------------
1750 - def _get_external_code(self):
1751 drug = self.containing_drug 1752 1753 if drug is None: 1754 return None 1755 1756 return drug.external_code
1757 1758 external_code = property(_get_external_code, lambda x:x) 1759 #--------------------------------------------------------
1760 - def _get_external_code_type(self):
1761 drug = self.containing_drug 1762 1763 if drug is None: 1764 return None 1765 1766 return drug.external_code_type
1767 1768 external_code_type = property(_get_external_code_type, lambda x:x) 1769 #--------------------------------------------------------
1770 - def _get_containing_drug(self):
1771 if self._payload[self._idx['pk_brand']] is None: 1772 return None 1773 1774 return cBrandedDrug(aPK_obj = self._payload[self._idx['pk_brand']])
1775 1776 containing_drug = property(_get_containing_drug, lambda x:x) 1777 #--------------------------------------------------------
1778 - def _get_parsed_schedule(self):
1779 tests = [ 1780 # lead, trail 1781 ' 1-1-1-1 ', 1782 # leading dose 1783 '1-1-1-1', 1784 '22-1-1-1', 1785 '1/3-1-1-1', 1786 '/4-1-1-1' 1787 ] 1788 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}$" 1789 for test in tests: 1790 print test.strip(), ":", regex.match(pattern, test.strip())
1791 #------------------------------------------------------------
1792 -def substance_intake_exists(pk_component=None, pk_substance=None, pk_identity=None):
1793 args = {'comp': pk_component, 'subst': pk_substance, 'pat': pk_identity} 1794 1795 where_clause = u""" 1796 fk_encounter IN ( 1797 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1798 ) 1799 AND 1800 """ 1801 1802 if pk_substance is not None: 1803 where_clause += u'fk_substance = %(subst)s' 1804 if pk_component is not None: 1805 where_clause += u'fk_drug_component = %(comp)s' 1806 1807 cmd = u"""SELECT exists ( 1808 SELECT 1 FROM clin.substance_intake 1809 WHERE 1810 %s 1811 LIMIT 1 1812 )""" % where_clause 1813 1814 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1815 return rows[0][0]
1816 #------------------------------------------------------------
1817 -def create_substance_intake(pk_substance=None, pk_component=None, preparation=None, encounter=None, episode=None):
1818 1819 args = { 1820 'enc': encounter, 1821 'epi': episode, 1822 'comp': pk_component, 1823 'subst': pk_substance, 1824 'prep': preparation 1825 } 1826 1827 if pk_component is None: 1828 cmd = u""" 1829 INSERT INTO clin.substance_intake ( 1830 fk_encounter, 1831 fk_episode, 1832 intake_is_approved_of, 1833 fk_substance, 1834 preparation 1835 ) VALUES ( 1836 %(enc)s, 1837 %(epi)s, 1838 False, 1839 %(subst)s, 1840 %(prep)s 1841 ) 1842 RETURNING pk""" 1843 else: 1844 cmd = u""" 1845 INSERT INTO clin.substance_intake ( 1846 fk_encounter, 1847 fk_episode, 1848 intake_is_approved_of, 1849 fk_drug_component 1850 ) VALUES ( 1851 %(enc)s, 1852 %(epi)s, 1853 False, 1854 %(comp)s 1855 ) 1856 RETURNING pk""" 1857 1858 try: 1859 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 1860 except gmPG2.dbapi.InternalError, e: 1861 if e.pgerror is None: 1862 raise 1863 if 'prevent_duplicate_component' in e.pgerror: 1864 _log.exception('will not create duplicate substance intake entry') 1865 _log.error(e.pgerror) 1866 return None 1867 raise 1868 1869 return cSubstanceIntakeEntry(aPK_obj = rows[0][0])
1870 #------------------------------------------------------------
1871 -def delete_substance_intake(substance=None):
1872 cmd = u'delete from clin.substance_intake where pk = %(pk)s' 1873 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': substance}}])
1874 #------------------------------------------------------------
1875 -def format_substance_intake_notes(emr=None, output_format=u'latex', table_type=u'by-brand'):
1876 1877 tex = u'\n{\\small\n' 1878 tex += u'\\noindent %s\n' % _('Additional notes') 1879 tex += u'\n' 1880 tex += u'\\noindent \\begin{tabularx}{\\textwidth}{|X|l|X|p{7.5cm}|}\n' 1881 tex += u'\\hline\n' 1882 tex += u'%s {\\scriptsize (%s)} & %s & %s \\\\ \n' % (_('Substance'), _('Brand'), _('Strength'), _('Advice')) 1883 tex += u'\\hline\n' 1884 tex += u'%s\n' 1885 tex += u'\n' 1886 tex += u'\\end{tabularx}\n' 1887 tex += u'}\n' 1888 1889 current_meds = emr.get_current_substance_intake ( 1890 include_inactive = False, 1891 include_unapproved = False, 1892 order_by = u'brand, substance' 1893 ) 1894 1895 # create lines 1896 lines = [] 1897 for med in current_meds: 1898 lines.append(u'%s%s %s & %s%s & %s \\\\ \n \\hline \n' % ( 1899 med['substance'], 1900 gmTools.coalesce(med['brand'], u'', u' {\\scriptsize (%s)}'), 1901 med['preparation'], 1902 med['amount'], 1903 med['unit'], 1904 gmTools.coalesce(med['notes'], u'', u'{\\scriptsize %s}') 1905 )) 1906 1907 return tex % u' \n'.join(lines)
1908 1909 #------------------------------------------------------------
1910 -def format_substance_intake(emr=None, output_format=u'latex', table_type=u'by-brand'):
1911 1912 tex = u'\\noindent %s {\\tiny (%s)\\par}\n' % (_('Medication list'), _('ordered by brand')) 1913 tex += u'\n' 1914 tex += u'\\noindent \\begin{tabular}{|l|l|}\n' 1915 tex += u'\\hline\n' 1916 tex += u'%s & %s \\\\ \n' % (_('Drug'), _('Regimen')) 1917 tex += u'\\hline\n' 1918 tex += u'\n' 1919 tex += u'\\hline\n' 1920 tex += u'%s\n' 1921 tex += u'\n' 1922 tex += u'\\end{tabular}\n' 1923 1924 current_meds = emr.get_current_substance_intake ( 1925 include_inactive = False, 1926 include_unapproved = False, 1927 order_by = u'brand, substance' 1928 ) 1929 1930 # aggregate data 1931 line_data = {} 1932 for med in current_meds: 1933 identifier = gmTools.coalesce(med['brand'], med['substance']) 1934 1935 try: 1936 line_data[identifier] 1937 except KeyError: 1938 line_data[identifier] = {'brand': u'', 'preparation': u'', 'schedule': u'', 'aims': [], 'strengths': []} 1939 1940 line_data[identifier]['brand'] = identifier 1941 line_data[identifier]['strengths'].append(u'%s%s' % (med['amount'], med['unit'].strip())) 1942 line_data[identifier]['preparation'] = med['preparation'] 1943 line_data[identifier]['schedule'] = gmTools.coalesce(med['schedule'], u'') 1944 if med['aim'] not in line_data[identifier]['aims']: 1945 line_data[identifier]['aims'].append(med['aim']) 1946 1947 # create lines 1948 already_seen = [] 1949 lines = [] 1950 line1_template = u'%s %s & %s \\\\' 1951 line2_template = u' & {\\scriptsize %s\\par} \\\\' 1952 1953 for med in current_meds: 1954 identifier = gmTools.coalesce(med['brand'], med['substance']) 1955 1956 if identifier in already_seen: 1957 continue 1958 1959 already_seen.append(identifier) 1960 1961 lines.append (line1_template % ( 1962 line_data[identifier]['brand'], 1963 line_data[identifier]['preparation'], 1964 line_data[identifier]['schedule'] 1965 )) 1966 1967 strengths = u'/'.join(line_data[identifier]['strengths']) 1968 if strengths == u'': 1969 template = u' & {\\scriptsize %s\\par} \\\\' 1970 for aim in line_data[identifier]['aims']: 1971 lines.append(template % aim) 1972 else: 1973 if len(line_data[identifier]['aims']) == 0: 1974 template = u'%s & \\\\' 1975 lines.append(template % strengths) 1976 else: 1977 template = u'%s & {\\scriptsize %s\\par} \\\\' 1978 lines.append(template % (strengths, line_data[identifier]['aims'][0])) 1979 template = u' & {\\scriptsize %s\\par} \\\\' 1980 for aim in line_data[identifier]['aims'][1:]: 1981 lines.append(template % aim) 1982 1983 lines.append(u'\\hline') 1984 1985 return tex % u' \n'.join(lines)
1986 #============================================================ 1987 _SQL_get_drug_components = u'SELECT * FROM ref.v_drug_components WHERE %s' 1988
1989 -class cDrugComponent(gmBusinessDBObject.cBusinessDBObject):
1990 1991 _cmd_fetch_payload = _SQL_get_drug_components % u'pk_component = %s' 1992 _cmds_store_payload = [ 1993 u"""UPDATE ref.lnk_substance2brand SET 1994 fk_brand = %(pk_brand)s, 1995 fk_substance = %(pk_consumable_substance)s 1996 WHERE 1997 NOT EXISTS ( 1998 SELECT 1 1999 FROM clin.substance_intake 2000 WHERE fk_drug_component = %(pk_component)s 2001 LIMIT 1 2002 ) 2003 AND 2004 pk = %(pk_component)s 2005 AND 2006 xmin = %(xmin_lnk_substance2brand)s 2007 RETURNING 2008 xmin AS xmin_lnk_substance2brand 2009 """ 2010 ] 2011 _updatable_fields = [ 2012 u'pk_brand', 2013 u'pk_consumable_substance' 2014 ] 2015 #-------------------------------------------------------- 2016 # properties 2017 #--------------------------------------------------------
2018 - def _get_containing_drug(self):
2019 return cBrandedDrug(aPK_obj = self._payload[self._idx['pk_brand']])
2020 2021 containing_drug = property(_get_containing_drug, lambda x:x) 2022 #--------------------------------------------------------
2023 - def _get_is_in_use_by_patients(self):
2024 return self._payload[self._idx['is_in_use']]
2025 2026 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x) 2027 #--------------------------------------------------------
2028 - def _get_substance(self):
2029 return cConsumableSubstance(aPK_obj = self._payload[self._idx['pk_consumable_substance']])
2030 2031 substance = property(_get_substance, lambda x:x)
2032 #------------------------------------------------------------
2033 -def get_drug_components():
2034 cmd = _SQL_get_drug_components % u'true ORDER BY brand, substance' 2035 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 2036 return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
2037 #------------------------------------------------------------
2038 -class cDrugComponentMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
2039 2040 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE | regex.LOCALE) 2041 _query_desc_only = u""" 2042 SELECT DISTINCT ON (component) 2043 pk_component, 2044 (substance || ' ' || amount || unit || ' ' || preparation || ' (' || brand || ')') 2045 AS component 2046 FROM ref.v_drug_components 2047 WHERE 2048 substance %(fragment_condition)s 2049 OR 2050 brand %(fragment_condition)s 2051 ORDER BY component 2052 LIMIT 50""" 2053 _query_desc_and_amount = u""" 2054 2055 SELECT DISTINCT ON (component) 2056 pk_component, 2057 (substance || ' ' || amount || unit || ' ' || preparation || ' (' || brand || ')') 2058 AS component 2059 FROM ref.v_drug_components 2060 WHERE 2061 %(fragment_condition)s 2062 ORDER BY component 2063 LIMIT 50""" 2064 #--------------------------------------------------------
2065 - def getMatchesByPhrase(self, aFragment):
2066 """Return matches for aFragment at start of phrases.""" 2067 2068 if cDrugComponentMatchProvider._pattern.match(aFragment): 2069 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount] 2070 fragment_condition = """(substance ILIKE %(desc)s OR brand ILIKE %(desc)s) 2071 AND 2072 amount::text ILIKE %(amount)s""" 2073 self._args['desc'] = u'%s%%' % regex.sub(r'\s*\d+$', u'', aFragment) 2074 self._args['amount'] = u'%s%%' % regex.sub(r'^\D+\s*', u'', aFragment) 2075 else: 2076 self._queries = [cDrugComponentMatchProvider._query_desc_only] 2077 fragment_condition = u"ILIKE %(fragment)s" 2078 self._args['fragment'] = u"%s%%" % aFragment 2079 2080 return self._find_matches(fragment_condition)
2081 #--------------------------------------------------------
2082 - def getMatchesByWord(self, aFragment):
2083 """Return matches for aFragment at start of words inside phrases.""" 2084 2085 if cDrugComponentMatchProvider._pattern.match(aFragment): 2086 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount] 2087 2088 desc = regex.sub(r'\s*\d+$', u'', aFragment) 2089 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False) 2090 2091 fragment_condition = """(substance ~* %(desc)s OR brand ~* %(desc)s) 2092 AND 2093 amount::text ILIKE %(amount)s""" 2094 2095 self._args['desc'] = u"( %s)|(^%s)" % (desc, desc) 2096 self._args['amount'] = u'%s%%' % regex.sub(r'^\D+\s*', u'', aFragment) 2097 else: 2098 self._queries = [cDrugComponentMatchProvider._query_desc_only] 2099 fragment_condition = u"~* %(fragment)s" 2100 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False) 2101 self._args['fragment'] = u"( %s)|(^%s)" % (aFragment, aFragment) 2102 2103 return self._find_matches(fragment_condition)
2104 #--------------------------------------------------------
2105 - def getMatchesBySubstr(self, aFragment):
2106 """Return matches for aFragment as a true substring.""" 2107 2108 if cDrugComponentMatchProvider._pattern.match(aFragment): 2109 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount] 2110 fragment_condition = """(substance ILIKE %(desc)s OR brand ILIKE %(desc)s) 2111 AND 2112 amount::text ILIKE %(amount)s""" 2113 self._args['desc'] = u'%%%s%%' % regex.sub(r'\s*\d+$', u'', aFragment) 2114 self._args['amount'] = u'%s%%' % regex.sub(r'^\D+\s*', u'', aFragment) 2115 else: 2116 self._queries = [cDrugComponentMatchProvider._query_desc_only] 2117 fragment_condition = u"ILIKE %(fragment)s" 2118 self._args['fragment'] = u"%%%s%%" % aFragment 2119 2120 return self._find_matches(fragment_condition)
2121 2122 #============================================================
2123 -class cBrandedDrug(gmBusinessDBObject.cBusinessDBObject):
2124 """Represents a drug as marketed by a manufacturer.""" 2125 2126 _cmd_fetch_payload = u"SELECT * FROM ref.v_branded_drugs WHERE pk_brand = %s" 2127 _cmds_store_payload = [ 2128 u"""UPDATE ref.branded_drug SET 2129 description = %(brand)s, 2130 preparation = %(preparation)s, 2131 atc_code = gm.nullify_empty_string(%(atc)s), 2132 external_code = gm.nullify_empty_string(%(external_code)s), 2133 external_code_type = gm.nullify_empty_string(%(external_code_type)s), 2134 is_fake = %(is_fake_brand)s, 2135 fk_data_source = %(pk_data_source)s 2136 WHERE 2137 pk = %(pk_brand)s 2138 AND 2139 xmin = %(xmin_branded_drug)s 2140 RETURNING 2141 xmin AS xmin_branded_drug 2142 """ 2143 ] 2144 _updatable_fields = [ 2145 u'brand', 2146 u'preparation', 2147 u'atc', 2148 u'is_fake_brand', 2149 u'external_code', 2150 u'external_code_type', 2151 u'pk_data_source' 2152 ] 2153 #--------------------------------------------------------
2154 - def save_payload(self, conn=None):
2155 success, data = super(self.__class__, self).save_payload(conn = conn) 2156 2157 if not success: 2158 return (success, data) 2159 2160 if self._payload[self._idx['atc']] is not None: 2161 atc = self._payload[self._idx['atc']].strip() 2162 if atc != u'': 2163 gmATC.propagate_atc ( 2164 substance = self._payload[self._idx['brand']].strip(), 2165 atc = atc 2166 ) 2167 2168 return (success, data)
2169 #--------------------------------------------------------
2170 - def set_substances_as_components(self, substances=None):
2171 2172 if self.is_in_use_by_patients: 2173 return False 2174 2175 args = {'brand': self._payload[self._idx['pk_brand']]} 2176 2177 queries = [{'cmd': u"DELETE FROM ref.lnk_substance2brand WHERE fk_brand = %(brand)s", 'args': args}] 2178 cmd = u'INSERT INTO ref.lnk_substance2brand (fk_brand, fk_substance) VALUES (%%(brand)s, %s)' 2179 for s in substances: 2180 queries.append({'cmd': cmd % s['pk'], 'args': args}) 2181 2182 gmPG2.run_rw_queries(queries = queries) 2183 self.refetch_payload() 2184 2185 return True
2186 #--------------------------------------------------------
2187 - def add_component(self, substance=None, atc=None, amount=None, unit=None, pk_substance=None):
2188 2189 args = { 2190 'brand': self.pk_obj, 2191 'subst': substance, 2192 'atc': atc, 2193 'pk_subst': pk_substance 2194 } 2195 2196 if pk_substance is None: 2197 consumable = create_consumable_substance(substance = substance, atc = atc, amount = amount, unit = unit) 2198 args['pk_subst'] = consumable['pk'] 2199 2200 # already a component 2201 cmd = u""" 2202 SELECT pk_component 2203 FROM ref.v_drug_components 2204 WHERE 2205 pk_brand = %(brand)s 2206 AND 2207 (( 2208 (lower(substance) = lower(%(subst)s)) 2209 OR 2210 (lower(atc_substance) = lower(%(atc)s)) 2211 OR 2212 (pk_consumable_substance = %(pk_subst)s) 2213 ) IS TRUE) 2214 """ 2215 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2216 2217 if len(rows) > 0: 2218 return 2219 2220 # create it 2221 cmd = u""" 2222 INSERT INTO ref.lnk_substance2brand (fk_brand, fk_substance) 2223 VALUES (%(brand)s, %(pk_subst)s) 2224 """ 2225 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2226 self.refetch_payload()
2227 #------------------------------------------------------------
2228 - def remove_component(self, substance=None):
2229 if len(self._payload[self._idx['components']]) == 1: 2230 _log.error('cannot remove the only component of a drug') 2231 return False 2232 2233 args = {'brand': self.pk_obj, 'comp': substance} 2234 cmd = u""" 2235 DELETE FROM ref.lnk_substance2brand 2236 WHERE 2237 fk_brand = %(brand)s 2238 AND 2239 fk_substance = %(comp)s 2240 AND 2241 NOT EXISTS ( 2242 SELECT 1 2243 FROM clin.substance_intake 2244 WHERE fk_drug_component = %(comp)s 2245 LIMIT 1 2246 ) 2247 """ 2248 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2249 self.refetch_payload() 2250 2251 return True
2252 #-------------------------------------------------------- 2253 # properties 2254 #--------------------------------------------------------
2255 - def _get_external_code(self):
2256 if self._payload[self._idx['external_code']] is None: 2257 return None 2258 2259 return self._payload[self._idx['external_code']]
2260 2261 external_code = property(_get_external_code, lambda x:x) 2262 #--------------------------------------------------------
2263 - def _get_external_code_type(self):
2264 2265 # FIXME: maybe evaluate fk_data_source ? 2266 if self._payload[self._idx['external_code_type']] is None: 2267 return None 2268 2269 return self._payload[self._idx['external_code_type']]
2270 2271 external_code_type = property(_get_external_code_type, lambda x:x) 2272 #--------------------------------------------------------
2273 - def _get_components(self):
2274 cmd = _SQL_get_drug_components % u'pk_brand = %(brand)s' 2275 args = {'brand': self._payload[self._idx['pk_brand']]} 2276 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2277 return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
2278 2279 components = property(_get_components, lambda x:x) 2280 #--------------------------------------------------------
2282 if self._payload[self._idx['pk_substances']] is None: 2283 return [] 2284 cmd = _SQL_get_consumable_substance % u'pk IN %(pks)s' 2285 args = {'pks': tuple(self._payload[self._idx['pk_substances']])} 2286 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2287 return [ cConsumableSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk'}) for r in rows ]
2288 2289 components_as_substances = property(_get_components_as_substances, lambda x:x) 2290 #--------------------------------------------------------
2291 - def _get_is_vaccine(self):
2292 cmd = u'SELECT EXISTS (SELECT 1 FROM clin.vaccine WHERE fk_brand = %(fk_brand)s)' 2293 args = {'fk_brand': self._payload[self._idx['pk_brand']]} 2294 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2295 return rows[0][0]
2296 2297 is_vaccine = property(_get_is_vaccine, lambda x:x) 2298 #--------------------------------------------------------
2299 - def _get_is_in_use_by_patients(self):
2300 cmd = u""" 2301 SELECT EXISTS ( 2302 SELECT 1 2303 FROM clin.substance_intake 2304 WHERE 2305 fk_drug_component IS NOT NULL 2306 AND 2307 fk_drug_component IN ( 2308 SELECT r_ls2b.pk 2309 FROM ref.lnk_substance2brand r_ls2b 2310 WHERE fk_brand = %(pk)s 2311 ) 2312 LIMIT 1 2313 )""" 2314 args = {'pk': self.pk_obj} 2315 2316 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2317 return rows[0][0]
2318 2319 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
2320 #------------------------------------------------------------
2321 -def get_branded_drugs():
2322 cmd = u'SELECT pk FROM ref.branded_drug ORDER BY description' 2323 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 2324 return [ cBrandedDrug(aPK_obj = r['pk']) for r in rows ]
2325 #------------------------------------------------------------
2326 -def get_drug_by_brand(brand_name=None, preparation=None):
2327 args = {'brand': brand_name, 'prep': preparation} 2328 2329 cmd = u'SELECT pk FROM ref.branded_drug WHERE lower(description) = lower(%(brand)s) AND lower(preparation) = lower(%(prep)s)' 2330 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2331 2332 if len(rows) == 0: 2333 return None 2334 2335 return cBrandedDrug(aPK_obj = rows[0]['pk'])
2336 #------------------------------------------------------------
2337 -def create_branded_drug(brand_name=None, preparation=None, return_existing=False):
2338 2339 if preparation is None: 2340 preparation = _('units') 2341 2342 if preparation.strip() == u'': 2343 preparation = _('units') 2344 2345 if return_existing: 2346 drug = get_drug_by_brand(brand_name = brand_name, preparation = preparation) 2347 if drug is not None: 2348 return drug 2349 2350 cmd = u'INSERT INTO ref.branded_drug (description, preparation) VALUES (%(brand)s, %(prep)s) RETURNING pk' 2351 args = {'brand': brand_name, 'prep': preparation} 2352 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 2353 2354 return cBrandedDrug(aPK_obj = rows[0]['pk'])
2355 #------------------------------------------------------------
2356 -def delete_branded_drug(brand=None):
2357 queries = [] 2358 args = {'pk': brand} 2359 2360 # delete components 2361 cmd = u""" 2362 DELETE FROM ref.lnk_substance2brand 2363 WHERE 2364 fk_brand = %(pk)s 2365 AND 2366 NOT EXISTS ( 2367 SELECT 1 2368 FROM clin.v_pat_substance_intake 2369 WHERE pk_brand = %(pk)s 2370 LIMIT 1 2371 ) 2372 """ 2373 queries.append({'cmd': cmd, 'args': args}) 2374 2375 # delete drug 2376 cmd = u""" 2377 DELETE FROM ref.branded_drug 2378 WHERE 2379 pk = %(pk)s 2380 AND 2381 NOT EXISTS ( 2382 SELECT 1 2383 FROM clin.v_pat_substance_intake 2384 WHERE pk_brand = %(pk)s 2385 LIMIT 1 2386 ) 2387 """ 2388 queries.append({'cmd': cmd, 'args': args}) 2389 2390 gmPG2.run_rw_queries(queries = queries)
2391 #============================================================ 2392 # main 2393 #------------------------------------------------------------ 2394 if __name__ == "__main__": 2395 2396 if len(sys.argv) < 2: 2397 sys.exit() 2398 2399 if sys.argv[1] != 'test': 2400 sys.exit() 2401 2402 from Gnumed.pycommon import gmLog2 2403 from Gnumed.pycommon import gmI18N 2404 from Gnumed.business import gmPerson 2405 2406 gmI18N.activate_locale() 2407 # gmDateTime.init() 2408 #--------------------------------------------------------
2409 - def test_MMI_interface():
2410 mmi = cGelbeListeWineInterface() 2411 print mmi 2412 print "interface definition:", mmi.version 2413 print "database versions: ", mmi.get_data_source_version()
2414 #--------------------------------------------------------
2415 - def test_MMI_file():
2416 mmi_file = cGelbeListeCSVFile(filename = sys.argv[2]) 2417 for drug in mmi_file: 2418 print "-------------" 2419 print '"%s" (ATC: %s / PZN: %s)' % (drug['name'], drug['atc'], drug['pzn']) 2420 for stoff in drug['wirkstoffe']: 2421 print " Wirkstoff:", stoff 2422 raw_input() 2423 if mmi_file.has_unknown_fields is not None: 2424 print "has extra data under [%s]" % gmTools.default_csv_reader_rest_key 2425 for key in mmi_file.csv_fieldnames: 2426 print key, '->', drug[key] 2427 raw_input() 2428 mmi_file.close()
2429 #--------------------------------------------------------
2430 - def test_mmi_switch_to():
2431 mmi = cGelbeListeWineInterface() 2432 mmi.switch_to_frontend(blocking = True)
2433 #--------------------------------------------------------
2434 - def test_mmi_let_user_select_drugs():
2435 mmi = cGelbeListeWineInterface() 2436 mmi_file = mmi.__let_user_select_drugs() 2437 for drug in mmi_file: 2438 print "-------------" 2439 print '"%s" (ATC: %s / PZN: %s)' % (drug['name'], drug['atc'], drug['pzn']) 2440 for stoff in drug['wirkstoffe']: 2441 print " Wirkstoff:", stoff 2442 print drug 2443 mmi_file.close()
2444 #--------------------------------------------------------
2445 - def test_mmi_import_drugs():
2446 mmi = cGelbeListeWineInterface() 2447 mmi.import_drugs()
2448 #--------------------------------------------------------
2449 - def test_mmi_interaction_check():
2450 mmi = cGelbeListeInterface() 2451 print mmi 2452 print "interface definition:", mmi.version 2453 # Metoprolol + Hct vs Citalopram 2454 diclofenac = '7587712' 2455 phenprocoumon = '4421744' 2456 mmi.check_interactions(drug_ids_list = [diclofenac, phenprocoumon])
2457 #-------------------------------------------------------- 2458 # FreeDiams 2459 #--------------------------------------------------------
2460 - def test_fd_switch_to():
2461 gmPerson.set_active_patient(patient = gmPerson.cIdentity(aPK_obj = 12)) 2462 fd = cFreeDiamsInterface() 2463 fd.patient = gmPerson.gmCurrentPatient() 2464 # fd.switch_to_frontend(blocking = True) 2465 fd.import_fd2gm_file_as_drugs(filename = sys.argv[2])
2466 #--------------------------------------------------------
2467 - def test_fd_show_interactions():
2468 gmPerson.set_active_patient(patient = gmPerson.cIdentity(aPK_obj = 12)) 2469 fd = cFreeDiamsInterface() 2470 fd.patient = gmPerson.gmCurrentPatient() 2471 fd.check_interactions(substances = fd.patient.get_emr().get_current_substance_intake(include_unapproved = True))
2472 #-------------------------------------------------------- 2473 # generic 2474 #--------------------------------------------------------
2475 - def test_create_substance_intake():
2476 drug = create_substance_intake ( 2477 pk_component = 2, 2478 encounter = 1, 2479 episode = 1 2480 ) 2481 print drug
2482 #--------------------------------------------------------
2483 - def test_show_components():
2484 drug = cBrandedDrug(aPK_obj = sys.argv[2]) 2485 print drug 2486 print drug.components
2487 #--------------------------------------------------------
2488 - def test_get_consumable_substances():
2489 for s in get_consumable_substances(): 2490 print s
2491 #--------------------------------------------------------
2492 - def test_drug2renal_insufficiency_url():
2493 drug2renal_insufficiency_url(search_term = 'Metoprolol')
2494 #-------------------------------------------------------- 2495 # MMI/Gelbe Liste 2496 #test_MMI_interface() 2497 #test_MMI_file() 2498 #test_mmi_switch_to() 2499 #test_mmi_let_user_select_drugs() 2500 #test_mmi_import_substances() 2501 #test_mmi_import_drugs() 2502 2503 # FreeDiams 2504 #test_fd_switch_to() 2505 #test_fd_show_interactions() 2506 2507 # generic 2508 #test_interaction_check() 2509 #test_create_substance_intake() 2510 #test_show_components() 2511 #test_get_consumable_substances() 2512 2513 test_drug2renal_insufficiency_url() 2514 #============================================================ 2515