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