Module Gnumed.business.gmDrugDataSources
Code handling drug data sources (such as databases).
license: GPL v2 or later
Expand source code
# -*- coding: utf-8 -*-
"""Code handling drug data sources (such as databases).
license: GPL v2 or later
"""
#============================================================
__author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
import sys
import csv
import os
import logging
import subprocess
import re as regex
from xml.etree import ElementTree as etree
if __name__ == '__main__':
sys.path.insert(0, '../../')
_ = lambda x:x
from Gnumed.pycommon import gmTools
from Gnumed.pycommon import gmShellAPI
from Gnumed.business import gmMedication
from Gnumed.business import gmCoding
_log = logging.getLogger('gm.meds')
#============================================================
# generic drug data source interface class
#------------------------------------------------------------
class cDrugDataSourceInterface(object):
#--------------------------------------------------------
def __init__(self):
self.patient = None
self.reviewer = None
self.custom_path_to_binary = None
#--------------------------------------------------------
def get_data_source_version(self):
raise NotImplementedError
#--------------------------------------------------------
def create_data_source_entry(self):
raise NotImplementedError
#--------------------------------------------------------
def switch_to_frontend(self, blocking=False):
raise NotImplementedError
#--------------------------------------------------------
def import_drugs(self):
self.switch_to_frontend()
#--------------------------------------------------------
def check_interactions(self, substance_intakes=None):
self.switch_to_frontend()
#--------------------------------------------------------
def show_info_on_drug(self, substance_intake=None):
self.switch_to_frontend()
#--------------------------------------------------------
def show_info_on_substance(self, substance_intake=None):
self.switch_to_frontend()
#--------------------------------------------------------
def prescribe(self, substance_intakes=None):
self.switch_to_frontend()
return []
#============================================================
# Gelbe Liste
#------------------------------------------------------------
# wishlist:
# - --conf-file= for glwin.exe
# - wirkstoff: Konzentration auch in Multiprodukten
# - wirkstoff: ATC auch in Multiprodukten
# - Suche nach ATC per CLI
class cGelbeListeCSVFile(object):
"""Iterator over a Gelbe Liste/MMI v8.2 CSV file."""
version = 'Gelbe Liste/MMI v8.2 CSV file interface'
default_transfer_file_windows = r"c:\rezept.txt"
#default_encoding = 'cp1252'
default_encoding = 'cp1250'
csv_fieldnames = [
'name',
'packungsgroesse', # obsolete, use "packungsmenge"
'darreichungsform',
'packungstyp',
'festbetrag',
'avp',
'hersteller',
'rezepttext',
'pzn',
'status_vertrieb',
'status_rezeptpflicht',
'status_fachinfo',
'btm',
'atc',
'anzahl_packungen',
'zuzahlung_pro_packung',
'einheit',
'schedule_morgens',
'schedule_mittags',
'schedule_abends',
'schedule_nachts',
'status_dauermedikament',
'status_hausliste',
'status_negativliste',
'ik_nummer',
'status_rabattvertrag',
'wirkstoffe',
'wirkstoffmenge',
'wirkstoffeinheit',
'wirkstoffmenge_bezug',
'wirkstoffmenge_bezugseinheit',
'status_import',
'status_lifestyle',
'status_ausnahmeliste',
'packungsmenge',
'apothekenpflicht',
'status_billigere_packung',
'rezepttyp',
'besonderes_arzneimittel', # Abstimmungsverfahren SGB-V
't_rezept_pflicht', # Thalidomid-Rezept
'erstattbares_medizinprodukt',
'hilfsmittel',
'hzv_rabattkennung',
'hzv_preis'
]
boolean_fields = [
'status_rezeptpflicht',
'status_fachinfo',
'btm',
'status_dauermedikament',
'status_hausliste',
'status_negativliste',
'status_rabattvertrag',
'status_import',
'status_lifestyle',
'status_ausnahmeliste',
'apothekenpflicht',
'status_billigere_packung',
'besonderes_arzneimittel', # Abstimmungsverfahren SGB-V
't_rezept_pflicht',
'erstattbares_medizinprodukt',
'hilfsmittel'
]
#--------------------------------------------------------
def __init__(self, filename=None):
_log.info(cGelbeListeCSVFile.version)
self.filename = filename
if filename is None:
self.filename = cGelbeListeCSVFile.default_transfer_file_windows
_log.debug('reading Gelbe Liste/MMI drug data from [%s]', self.filename)
self.csv_file = open(filename, mode = 'rt', encoding = cGelbeListeCSVFile.default_encoding)
self.csv_lines = gmTools.unicode_csv_reader (
self.csv_file,
fieldnames = cGelbeListeCSVFile.csv_fieldnames,
delimiter = ';',
quotechar = '"',
dict = True
)
#--------------------------------------------------------
def __iter__(self):
return self
#--------------------------------------------------------
def next(self):
line = self.csv_lines.next()
for field in cGelbeListeCSVFile.boolean_fields:
line[field] = (line[field].strip() == 'T')
# split field "Wirkstoff" by ";"
if line['wirkstoffe'].strip() == '':
line['wirkstoffe'] = []
else:
line['wirkstoffe'] = [ wirkstoff.strip() for wirkstoff in line['wirkstoffe'].split(';') ]
return line
#--------------------------------------------------------
def close(self, truncate=True):
try: self.csv_file.close()
except Exception: pass
if truncate:
try: os.open(self.filename, 'wb').close
except Exception: pass
#--------------------------------------------------------
def _get_has_unknown_fields(self):
return (gmTools.default_csv_reader_rest_key in self.csv_fieldnames)
has_unknown_fields = property(_get_has_unknown_fields)
#============================================================
class cGelbeListeWindowsInterface(cDrugDataSourceInterface):
"""Support v8.2 CSV file interface only."""
version = 'Gelbe Liste/MMI v8.2 interface'
default_encoding = 'cp1250'
bdt_line_template = '%03d6210#%s\r\n' # Medikament verordnet auf Kassenrezept
bdt_line_base_length = 8
#--------------------------------------------------------
def __init__(self):
cDrugDataSourceInterface.__init__(self)
_log.info('%s (native Windows)', cGelbeListeWindowsInterface.version)
self.path_to_binary = r'C:\Programme\MMI PHARMINDEX\glwin.exe'
self.args = r'-KEEPBACKGROUND -PRESCRIPTIONFILE %s -CLOSETOTRAY'
paths = gmTools.gmPaths()
self.default_csv_filename = os.path.join(paths.tmp_dir, 'rezept.txt')
self.default_csv_filename_arg = paths.tmp_dir
self.interactions_filename = os.path.join(paths.tmp_dir, 'gm2mmi.bdt')
self.data_date_filename = r'C:\Programme\MMI PHARMINDEX\datadate.txt'
self.__data_date = None
self.__online_update_date = None
# use adjusted config.dat
#--------------------------------------------------------
def get_data_source_version(self, force_reload=False):
if self.__data_date is not None:
if not force_reload:
return {
'data': self.__data_date,
'online_update': self.__online_update_date
}
try:
open(self.data_date_filename, 'wb').close()
except Exception:
_log.error('problem querying the MMI drug database for version information')
_log.exception('cannot create MMI drug database version file [%s]', self.data_date_filename)
self.__data_date = None
self.__online_update_date = None
return {
'data': '?',
'online_update': '?'
}
cmd = '%s -DATADATE' % self.path_to_binary
if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True):
_log.error('problem querying the MMI drug database for version information')
self.__data_date = None
self.__online_update_date = None
return {
'data': '?',
'online_update': '?'
}
try:
version_file = open(self.data_date_filename, mode = 'rt', encoding = 'utf8')
except Exception:
_log.error('problem querying the MMI drug database for version information')
_log.exception('cannot open MMI drug database version file [%s]', self.data_date_filename)
self.__data_date = None
self.__online_update_date = None
return {
'data': '?',
'online_update': '?'
}
self.__data_date = version_file.readline()[:10]
self.__online_update_date = version_file.readline()[:10]
version_file.close()
return {
'data': self.__data_date,
'online_update': self.__online_update_date
}
#--------------------------------------------------------
def create_data_source_entry(self):
versions = self.get_data_source_version()
return gmCoding.create_data_source (
long_name = 'Medikamentendatenbank "mmi PHARMINDEX" (Gelbe Liste)',
short_name = 'GL/MMI',
version = 'Daten: %s, Preise (Onlineupdate): %s' % (versions['data'], versions['online_update']),
source = 'Medizinische Medien Informations GmbH, Am Forsthaus Gravenbruch 7, 63263 Neu-Isenburg',
language = 'de'
)
#--------------------------------------------------------
def switch_to_frontend(self, blocking=False, cmd=None):
try:
# must make sure csv file exists
open(self.default_csv_filename, 'wb').close()
except IOError:
_log.exception('problem creating GL/MMI <-> GNUmed exchange file')
return False
if cmd is None:
cmd = ('%s %s' % (self.path_to_binary, self.args)) % self.default_csv_filename_arg
if os.name == 'nt':
blocking = True
if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking):
_log.error('problem switching to the MMI drug database')
# apparently on the first call MMI does not
# consistently return 0 on success
# return False
return True
#--------------------------------------------------------
def __let_user_select_drugs(self):
# better to clean up interactions file
open(self.interactions_filename, 'wb').close()
if not self.switch_to_frontend(blocking = True):
return None
return cGelbeListeCSVFile(filename = self.default_csv_filename)
#--------------------------------------------------------
def import_drugs_as_substances(self):
selected_drugs = self.__let_user_select_drugs()
if selected_drugs is None:
return None
new_substances = []
for drug in selected_drugs:
atc = None # hopefully MMI eventually supports atc-per-substance in a drug...
if len(drug['wirkstoffe']) == 1:
atc = drug['atc']
for wirkstoff in drug['wirkstoffe']:
#new_substances.append(gmMedication.create_substance_dose(substance = wirkstoff, atc = atc, amount = amount, unit = unit))
print(atc)
pass
selected_drugs.close()
return new_substances
#--------------------------------------------------------
def import_drugs(self):
selected_drugs = self.__let_user_select_drugs()
if selected_drugs is None:
return None
data_src_pk = self.create_data_source_entry()
new_drugs = []
new_substances = []
for entry in selected_drugs:
_log.debug('importing drug: %s %s', entry['name'], entry['darreichungsform'])
if entry['hilfsmittel']:
_log.debug('skipping Hilfsmittel')
continue
if entry['erstattbares_medizinprodukt']:
_log.debug('skipping sonstiges Medizinprodukt')
continue
# create drug product (or get it if it already exists)
drug = gmMedication.create_drug_product(product_name = entry['name'], preparation = entry['darreichungsform'])
if drug is None:
drug = gmMedication.get_drug_by_name(product_name = entry['name'], preparation = entry['darreichungsform'])
new_drugs.append(drug)
# update fields
drug['is_fake_product'] = False
drug['atc'] = entry['atc']
drug['external_code_type'] = 'DE-PZN'
drug['external_code'] = entry['pzn']
drug['fk_data_source'] = data_src_pk
drug.save()
# add components to drug
atc = None # hopefully MMI eventually supports atc-per-substance in a drug...
if len(entry['wirkstoffe']) == 1:
atc = entry['atc']
for wirkstoff in entry['wirkstoffe']:
drug.add_component(substance = wirkstoff, atc = atc)
# create as substance doses, too
atc = None # hopefully MMI eventually supports atc-per-substance in a drug...
if len(entry['wirkstoffe']) == 1:
atc = entry['atc']
for wirkstoff in entry['wirkstoffe']:
#new_substances.append(gmMedication.create_substance_dose(substance = wirkstoff, atc = atc, amount = amount, unit = unit))
pass
return new_drugs, new_substances
#--------------------------------------------------------
def check_interactions(self, drug_ids_list=None, substances=None):
"""For this to work the BDT interaction check must be configured in the MMI."""
if drug_ids_list is None:
if substances is None:
return
if len(substances) < 2:
return
drug_ids_list = [ (s.external_code_type, s.external_code) for s in substances ]
drug_ids_list = [ code_value for code_type, code_value in drug_ids_list if (code_value is not None) and (code_type == 'DE-PZN')]
else:
if len(drug_ids_list) < 2:
return
if drug_ids_list < 2:
return
bdt_file = open(self.interactions_filename, mode = 'wt', encoding = cGelbeListeWindowsInterface.default_encoding)
for pzn in drug_ids_list:
pzn = pzn.strip()
lng = cGelbeListeWindowsInterface.bdt_line_base_length + len(pzn)
bdt_file.write(cGelbeListeWindowsInterface.bdt_line_template % (lng, pzn))
bdt_file.close()
self.switch_to_frontend(blocking = True)
#--------------------------------------------------------
def show_info_on_drug(self, drug=None):
self.switch_to_frontend(blocking = True)
#--------------------------------------------------------
def show_info_on_substance(self, substance=None):
cmd = None
if substance.external_code_type == 'DE-PZN':
cmd = '%s -PZN %s' % (self.path_to_binary, substance.external_code)
if cmd is None:
name = gmTools.coalesce (
substance['drug_product'],
substance['substance']
)
cmd = '%s -NAME %s' % (self.path_to_binary, name)
# better to clean up interactions file
open(self.interactions_filename, 'wb').close()
self.switch_to_frontend(cmd = cmd)
#============================================================
class cGelbeListeWineInterface(cGelbeListeWindowsInterface):
def __init__(self):
cGelbeListeWindowsInterface.__init__(self)
_log.info('%s (WINE extension)', cGelbeListeWindowsInterface.version)
# FIXME: if -CLOSETOTRAY is used GNUmed cannot detect the end of MMI
self.path_to_binary = r'wine "C:\Programme\MMI PHARMINDEX\glwin.exe"'
self.args = r'"-PRESCRIPTIONFILE %s -KEEPBACKGROUND"'
paths = gmTools.gmPaths()
self.default_csv_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'windows', 'temp', 'mmi2gm.csv')
self.default_csv_filename_arg = r'c:\windows\temp\mmi2gm.csv'
self.interactions_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'windows', 'temp', 'gm2mmi.bdt')
self.data_date_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'Programme', 'MMI PHARMINDEX', 'datadate.txt')
#============================================================
# FreeDiams
#------------------------------------------------------------
class cFreeDiamsInterface(cDrugDataSourceInterface):
version = 'FreeDiams interface'
default_encoding = 'utf8'
default_dob_format = '%Y/%m/%d'
map_gender2mf = {
'm': 'M',
'f': 'F',
'tf': 'H',
'tm': 'H',
'h': 'H'
}
#--------------------------------------------------------
def __init__(self):
cDrugDataSourceInterface.__init__(self)
_log.info(cFreeDiamsInterface.version)
self.__imported_drugs = []
self.__gm2fd_filename = gmTools.get_unique_filename(prefix = r'gm2freediams-', suffix = r'.xml')
_log.debug('GNUmed -> FreeDiams "exchange-in" file: %s', self.__gm2fd_filename)
self.__fd2gm_filename = gmTools.get_unique_filename(prefix = r'freediams2gm-', suffix = r'.xml')
_log.debug('GNUmed <-> FreeDiams "exchange-out"/"prescription" file: %s', self.__fd2gm_filename)
# this file can be modified by the user as needed (therefore in user_config_dir):
self.__fd4gm_config_file = os.path.join(gmTools.gmPaths().user_config_dir, 'freediams4gm.conf')
_log.debug('FreeDiams config file for GNUmed use: %s', self.__fd4gm_config_file)
self.path_to_binary = None
self.__detect_binary()
#--------------------------------------------------------
def get_data_source_version(self):
# ~/.freediams/config.ini: [License] -> AcceptedVersion=....
if not self.__detect_binary():
return False
freediams = subprocess.Popen (
args = '--version', # --version or -version or -v
executable = self.path_to_binary,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
# close_fds = True, # Windows can't do that in conjunction with stdout/stderr = ... :-(
universal_newlines = True
)
data, errors = freediams.communicate()
version = regex.search(r'FreeDiams\s\d.\d.\d', data).group().split()[1]
_log.debug('FreeDiams %s', version)
return version
#--------------------------------------------------------
def create_data_source_entry(self):
return gmCoding.create_data_source (
long_name = '"FreeDiams" Drug Database Frontend',
short_name = 'FreeDiams',
version = self.get_data_source_version(),
source = 'http://ericmaeker.fr/FreeMedForms/di-manual/index.html',
language = 'fr' # actually to be multi-locale
)
#--------------------------------------------------------
def switch_to_frontend(self, blocking=False, mode='interactions'):
"""https://ericmaeker.fr/FreeMedForms/di-manual/en/html/ligne_commandes.html"""
_log.debug('calling FreeDiams in [%s] mode', mode)
self.__imported_drugs = []
if not self.__detect_binary():
return False
self.__create_gm2fd_file(mode = mode)
args = '--exchange-in="%s"' % (self.__gm2fd_filename)
cmd = r'%s %s' % (self.path_to_binary, args)
if os.name == 'nt':
blocking = True
if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking):
_log.error('problem switching to the FreeDiams drug database')
return False
if blocking == True:
self.import_fd2gm_file_as_drugs()
return True
#--------------------------------------------------------
def import_drugs(self):
self.switch_to_frontend(blocking = True)
#--------------------------------------------------------
def check_interactions(self, substance_intakes=None):
if substance_intakes is None:
return
if len(substance_intakes) < 2:
return
self.__create_prescription_file(substance_intakes = substance_intakes)
self.switch_to_frontend(mode = 'interactions', blocking = False)
#--------------------------------------------------------
def show_info_on_drug(self, substance_intake=None):
if substance_intake is None:
return
self.__create_prescription_file(substance_intakes = [substance_intake])
self.switch_to_frontend(mode = 'interactions', blocking = False)
#--------------------------------------------------------
def show_info_on_substance(self, substance_intake=None):
self.show_info_on_drug(substance_intake = substance_intake)
#--------------------------------------------------------
def prescribe(self, substance_intakes=None):
if substance_intakes is None:
if not self.__export_latest_prescription():
self.__create_prescription_file()
else:
self.__create_prescription_file(substance_intakes = substance_intakes)
self.switch_to_frontend(mode = 'prescription', blocking = True)
self.import_fd2gm_file_as_prescription()
return self.__imported_drugs
#--------------------------------------------------------
# internal helpers
#--------------------------------------------------------
def __detect_binary(self):
if self.path_to_binary is not None:
return True
found, cmd = gmShellAPI.find_first_binary(binaries = [
r'/usr/bin/freediams',
r'freediams',
r'/Applications/FreeDiams.app/Contents/MacOs/FreeDiams',
r'C:\Program Files (x86)\FreeDiams\freediams.exe',
r'C:\Program Files\FreeDiams\freediams.exe',
r'c:\programs\freediams\freediams.exe',
r'freediams.exe'
])
if found:
self.path_to_binary = cmd
return True
try:
self.custom_path_to_binary
except AttributeError:
_log.error('cannot find FreeDiams binary, no custom path set')
return False
if self.custom_path_to_binary is None:
_log.error('cannot find FreeDiams binary')
return False
found, cmd = gmShellAPI.detect_external_binary(binary = self.custom_path_to_binary)
if found:
self.path_to_binary = cmd
return True
_log.error('cannot find FreeDiams binary')
return False
#--------------------------------------------------------
def __export_latest_prescription(self):
if self.patient is None:
_log.debug('cannot export latest FreeDiams prescriptions w/o patient')
return False
docs = self.patient.get_document_folder()
prescription = docs.get_latest_freediams_prescription()
if prescription is None:
_log.debug('no FreeDiams prescription available')
return False
for part in prescription.parts:
if part['filename'] == 'freediams-prescription.xml':
if part.save_to_file(filename = self.__fd2gm_filename) is not None:
return True
_log.error('cannot export latest FreeDiams prescription to XML file')
return False
#--------------------------------------------------------
def __create_prescription_file(self, substance_intakes=None):
"""FreeDiams calls this exchange-out or prescription file.
CIS stands for Unique Speciality Identifier (eg bisoprolol 5 mg, gel).
CIS is AFSSAPS specific, but pharmacist can retrieve drug name with the CIS.
AFSSAPS is the French FDA.
CIP stands for Unique Presentation Identifier (eg 30 pills plaq)
CIP if you want to specify the packaging of the drug (30 pills
thermoformed tablet...) -- actually not really usefull for french
doctors.
# .external_code_type: u'FR-CIS'
# .external_cod: the CIS value
OnlyForTest:
OnlyForTest drugs will be processed by the IA Engine but
not printed (regardless of FreeDiams mode). They are shown
in gray in the prescription view.
Select-only is a mode where FreeDiams creates a list of drugs
not a full prescription. In this list, users can add ForTestOnly
drug if they want to
1. print the list without some drugs
2. but including these drugs in the IA engine calculation
Select-Only mode does not have any relation with the ForTestOnly drugs.
IsTextual:
What is the use and significance of the
<IsTextual>true/false</IsTextual>
flag when both <DrugName> and <TextualDrugName> exist ?
This tag must be setted even if it sounds like a duplicated
data. This tag is needed inside FreeDiams code.
INN:
GNUmed will pass the substance in <TextualDrugName
and will also pass <INN>True</INN>.
Eric: Nop, this is not useful because pure textual drugs
are not processed but just shown.
"""
# virginize file
open(self.__fd2gm_filename, 'wb').close()
# make sure we've got something to do
if substance_intakes is None:
if self.patient is None:
_log.warning('cannot create prescription file because there is neither a patient nor a substance intake list')
# do fail because __export_latest_prescription() should not have been called without patient
return False
emr = self.patient.emr
substance_intakes = emr.get_current_medications (
include_inactive = False
)
drug_snippets = []
# process FD drugs
fd_intakes = [ i for i in substance_intakes if (
(i['external_code_type_product'] is not None)
and
(i['external_code_type_product'].startswith('FreeDiams::'))
)]
intakes_pooled_by_product = {}
for intake in fd_intakes:
# this will leave only one entry per drug
# but FreeDiams knows the components ...
intakes_pooled_by_product[intake['drug_product']] = intake
del fd_intakes
drug_snippet = """<Prescription>
<Drug u1="%s" u2="" old="%s" u3="" db="%s"> <!-- "old" needs to be the same as "u1" if not known -->
<DrugName>%s</DrugName> <!-- just for identification when reading XML files -->
</Drug>
</Prescription>"""
last_db_id = 'CA_HCDPD'
for intake in intakes_pooled_by_product.values():
last_db_id = gmTools.xml_escape_string(text = intake['external_code_type_product'].replace('FreeDiams::', '').split('::')[0])
drug_snippets.append(drug_snippet % (
gmTools.xml_escape_string(text = intake['external_code_product'].strip()),
gmTools.xml_escape_string(text = intake['external_code_product'].strip()),
last_db_id,
gmTools.xml_escape_string(text = intake['drug_product'].strip())
))
# process non-FD drugs
non_fd_intakes = [ i for i in substance_intakes if (
(
(i['external_code_type_product'] is None)
or
(not i['external_code_type_product'].startswith('FreeDiams::'))
)
)]
non_fd_product_intakes = [ i for i in non_fd_intakes if i['drug_product'] is not None ]
non_fd_substance_intakes = [ i for i in non_fd_intakes if i['drug_product'] is None ]
del non_fd_intakes
drug_snippet = """<Prescription>
<Drug u1="-1" u2="" old="" u3="" db="">
<DrugName>%s</DrugName>
</Drug>
<Dose Note="%s" IsTextual="true" IsAld="false"/>
</Prescription>"""
# <DrugUidName></DrugUidName>
# <DrugForm></DrugForm>
# <DrugRoute></DrugRoute>
# <DrugStrength/>
for intake in non_fd_substance_intakes:
drug_name = '%s %s%s (%s)' % (
intake['substance'],
intake['amount'],
intake['unit'],
intake['l10n_preparation']
)
drug_snippets.append(drug_snippet % (
gmTools.xml_escape_string(text = drug_name.strip()),
gmTools.xml_escape_string(text = gmTools.coalesce(intake['schedule'], ''))
))
intakes_pooled_by_product = {}
for intake in non_fd_product_intakes:
prod = '%s %s' % (intake['drug_product'], intake['l10n_preparation'])
try:
intakes_pooled_by_product[prod].append(intake)
except KeyError:
intakes_pooled_by_product[prod] = [intake]
for product, comps in intakes_pooled_by_product.items():
drug_name = '%s\n' % product
for comp in comps:
drug_name += ' %s %s%s\n' % (
comp['substance'],
comp['amount'],
comp['unit']
)
drug_snippets.append(drug_snippet % (
gmTools.xml_escape_string(text = drug_name.strip()),
gmTools.xml_escape_string(text = gmTools.coalesce(comps[0]['schedule'], ''))
))
# assemble XML file
xml = """<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE FreeMedForms>
<FreeDiams>
<FullPrescription version="0.7.2">
%s
</FullPrescription>
</FreeDiams>
"""
xml_file = open(self.__fd2gm_filename, mode = 'wt', encoding = 'utf8')
xml_file.write(xml % '\n\t\t'.join(drug_snippets))
xml_file.close()
return True
#--------------------------------------------------------
def __create_gm2fd_file(self, mode='interactions'):
if mode == 'interactions':
mode = 'select-only'
elif mode == 'prescription':
mode = 'prescriber'
else:
mode = 'select-only'
xml_file = open(self.__gm2fd_filename, mode = 'wt', encoding = 'utf8')
xml = """<?xml version="1.0" encoding="UTF-8"?>
<FreeDiams_In version="0.5.0">
<EMR name="GNUmed" uid="unused"/>
<ConfigFile value="%s"/>
<ExchangeOut value="%s" format="xml"/>
<!-- <DrugsDatabase uid="can be set to a specific DB"/> -->
<Ui editmode="%s" blockPatientDatas="1"/>
%%s
</FreeDiams_In>
""" % (
self.__fd4gm_config_file,
self.__fd2gm_filename,
mode
)
if self.patient is None:
xml_file.write(xml % '')
xml_file.close()
return
name = self.patient.get_active_name()
if self.patient['dob'] is None:
dob = ''
else:
dob = self.patient['dob'].strftime(cFreeDiamsInterface.default_dob_format)
emr = self.patient.emr
allgs = emr.get_allergies()
atc_allgs = [
a['atc_code'] for a in allgs if ((a['atc_code'] is not None) and (a['type'] == 'allergy'))
]
atc_sens = [
a['atc_code'] for a in allgs if ((a['atc_code'] is not None) and (a['type'] == 'sensitivity'))
]
inn_allgs = [
a['allergene'] for a in allgs if ((a['allergene'] is not None) and (a['type'] == 'allergy'))
]
inn_sens = [
a['allergene'] for a in allgs if ((a['allergene'] is not None) and (a['type'] == 'sensitivity'))
]
# this is rather fragile: FreeDiams won't know what type of UID this is
# (but it will assume it is of the type of the drug database in use)
# but eventually FreeDiams puts all drugs into one database :-)
uid_allgs = [
a['substance_code'] for a in allgs if ((a['substance_code'] is not None) and (a['type'] == 'allergy'))
]
uid_sens = [
a['substance_code'] for a in allgs if ((a['substance_code'] is not None) and (a['type'] == 'sensitivity'))
]
patient_xml = """<Patient>
<Identity
lastnames="%s"
firstnames="%s"
uid="%s"
dob="%s"
gender="%s"
/>
<!-- can be <7 characters class codes: -->
<ATCAllergies value="%s"/>
<ATCIntolerances value="%s"/>
<InnAllergies value="%s"/>
<InnIntolerances value="%s"/>
<DrugsUidAllergies value="%s"/>
<DrugsUidIntolerances value="%s"/>
<!--
# FIXME: search by LOINC code and add (as soon as supported by FreeDiams ...)
<Creatinine value="12" unit="mg/l or mmol/l"/>
<Weight value="70" unit="kg or pd" />
<WeightInGrams value="70"/>
<Height value="170" unit="cm or "/>
<HeightInCentimeters value="170"/>
<ICD10 value="J11.0;A22;Z23"/>
-->
</Patient>
""" % (
gmTools.xml_escape_string(text = name['lastnames']),
gmTools.xml_escape_string(text = name['firstnames']),
self.patient.ID,
dob,
cFreeDiamsInterface.map_gender2mf[self.patient['gender']],
gmTools.xml_escape_string(text = ';'.join(atc_allgs)),
gmTools.xml_escape_string(text = ';'.join(atc_sens)),
gmTools.xml_escape_string(text = ';'.join(inn_allgs)),
gmTools.xml_escape_string(text = ';'.join(inn_sens)),
gmTools.xml_escape_string(text = ';'.join(uid_allgs)),
gmTools.xml_escape_string(text = ';'.join(uid_sens))
)
xml_file.write(xml % patient_xml)
xml_file.close()
#--------------------------------------------------------
def import_fd2gm_file_as_prescription(self, filename=None):
if filename is None:
filename = self.__fd2gm_filename
_log.debug('importing FreeDiams prescription information from [%s]', filename)
fd2gm_xml = etree.ElementTree()
fd2gm_xml.parse(filename)
pdfs = fd2gm_xml.findall('ExtraDatas/Printed')
if len(pdfs) == 0:
_log.debug('no PDF prescription files listed')
return
fd_filenames = []
for pdf in pdfs:
fd_filenames.append(pdf.attrib['file'])
_log.debug('listed PDF prescription files: %s', fd_filenames)
docs = self.patient.get_document_folder()
emr = self.patient.emr
prescription = docs.add_prescription (
encounter = emr.active_encounter['pk_encounter'],
episode = emr.add_episode (
episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE,
is_open = False
)['pk_episode']
)
prescription['ext_ref'] = 'FreeDiams'
prescription.save()
fd_filenames.append(filename)
success, msg, parts = prescription.add_parts_from_files(files = fd_filenames)
if not success:
_log.error(msg)
return
for part in parts:
part['obj_comment'] = _('copy of printed prescription')
part.save()
xml_part = parts[-1]
xml_part['filename'] = 'freediams-prescription.xml'
xml_part['obj_comment'] = _('prescription data')
xml_part.save()
# are we the intended reviewer ?
from Gnumed.business.gmStaff import gmCurrentProvider
me = gmCurrentProvider()
# if so: auto-sign the prescription
if xml_part['pk_intended_reviewer'] == me['pk_staff']:
prescription.set_reviewed(technically_abnormal = False, clinically_relevant = False)
#--------------------------------------------------------
def import_fd2gm_file_as_drugs(self, filename=None):
"""
If returning textual prescriptions (say, drugs which FreeDiams
did not know) then "IsTextual" will be True and UID will be -1.
"""
if filename is None:
filename = self.__fd2gm_filename
# FIXME: do not import IsTextual drugs, or rather, make that configurable
fd2gm_xml = etree.ElementTree()
fd2gm_xml.parse(filename)
data_src_pk = self.create_data_source_entry()
xml_version = fd2gm_xml.find('FullPrescription').attrib['version']
_log.debug('fd2gm file version: %s', xml_version)
if xml_version in ['0.6.0', '0.7.2']:
return self.__import_fd2gm_file_as_drugs_0_6_0(fd2gm_xml = fd2gm_xml, pk_data_source = data_src_pk)
return self.__import_fd2gm_file_as_drugs_0_5(fd2gm_xml = fd2gm_xml, pk_data_source = data_src_pk)
#--------------------------------------------------------
def __import_fd2gm_file_as_drugs_0_6_0(self, fd2gm_xml=None, pk_data_source=None):
# drug_id_name = db_def.attrib['drugUidName']
fd_xml_prescriptions = fd2gm_xml.findall('FullPrescription/Prescription')
self.__imported_drugs = []
for fd_xml_prescription in fd_xml_prescriptions:
drug_uid = fd_xml_prescription.find('Drug').attrib['u1'].strip()
if drug_uid == '-1':
_log.debug('skipping textual drug')
continue
drug_db = fd_xml_prescription.find('Drug').attrib['db'].strip()
drug_uid_name = fd_xml_prescription.find('Drug/DrugUidName').text.strip()
#drug_uid_name = u'<%s>' % drug_db
drug_name = fd_xml_prescription.find('Drug/DrugName').text.replace(', )', ')').strip()
drug_form = fd_xml_prescription.find('Drug/DrugForm').text.strip()
# drug_atc = fd_xml_prescription.find('DrugATC')
# if drug_atc is None:
# drug_atc = u''
# else:
# if drug_atc.text is None:
# drug_atc = u''
# else:
# drug_atc = drug_atc.text.strip()
# create new drug product
new_drug = gmMedication.create_drug_product(product_name = drug_name, preparation = drug_form, return_existing = True)
self.__imported_drugs.append(new_drug)
new_drug['is_fake_product'] = False
# new_drug['atc'] = drug_atc
new_drug['external_code_type'] = 'FreeDiams::%s::%s' % (drug_db, drug_uid_name)
new_drug['external_code'] = drug_uid
new_drug['pk_data_source'] = pk_data_source
new_drug.save()
# parse XML for composition records
fd_xml_components = fd_xml_prescription.getiterator('Composition')
comp_data = {}
for fd_xml_comp in fd_xml_components:
data = {}
xml_strength = fd_xml_comp.attrib['strength'].strip()
amount = regex.match(r'^\d+[.,]{0,1}\d*', xml_strength)
if amount is None:
amount = 99999
else:
amount = amount.group()
data['amount'] = amount
#unit = regex.sub(r'\d+[.,]{0,1}\d*', u'', xml_strength).strip()
unit = (xml_strength[len(amount):]).strip()
if unit == '':
unit = '*?*'
data['unit'] = unit
# hopefully, FreeDiams gets their act together, eventually:
atc = regex.match(r'[A-Za-z]\d\d[A-Za-z]{2}\d\d', fd_xml_comp.attrib['atc'].strip())
if atc is None:
data['atc'] = None
else:
atc = atc.group()
data['atc'] = atc
molecule_name = fd_xml_comp.attrib['molecularName'].strip()
if molecule_name != '':
gmMedication.create_substance_dose(substance = molecule_name, atc = atc, amount = amount, unit = unit)
data['molecule_name'] = molecule_name
inn_name = fd_xml_comp.attrib['inn'].strip()
if inn_name != '':
gmMedication.create_substance_dose(substance = inn_name, atc = atc, amount = amount, unit = unit)
#data['inn_name'] = molecule_name
data['inn_name'] = inn_name
if molecule_name == '':
data['substance'] = inn_name
_log.info('linking INN [%s] rather than molecularName as component', inn_name)
else:
data['substance'] = molecule_name
data['nature'] = fd_xml_comp.attrib['nature'].strip()
data['nature_ID'] = fd_xml_comp.attrib['natureLink'].strip()
# merge composition records of SA/FT nature
try:
old_data = comp_data[data['nature_ID']]
# normalize INN
if old_data['inn_name'] == '':
old_data['inn_name'] = data['inn_name']
if data['inn_name'] == '':
data['inn_name'] = old_data['inn_name']
# normalize molecule
if old_data['molecule_name'] == '':
old_data['molecule_name'] = data['molecule_name']
if data['molecule_name'] == '':
data['molecule_name'] = old_data['molecule_name']
# normalize ATC
if old_data['atc'] == '':
old_data['atc'] = data['atc']
if data['atc'] == '':
data['atc'] = old_data['atc']
# FT: transformed form
# SA: active substance
# it would be preferable to use the SA record because that's what's *actually*
# contained in the drug, however FreeDiams does not list the amount thereof
# (rather that of the INN)
# FT and SA records of the same component carry the same nature_ID
if data['nature'] == 'FT':
comp_data[data['nature_ID']] = data
else:
comp_data[data['nature_ID']] = old_data
# or create new record
except KeyError:
comp_data[data['nature_ID']] = data
# actually create components from (possibly merged) composition records
for key, data in comp_data.items():
new_drug.add_component (
substance = data['substance'],
atc = data['atc'],
amount = data['amount'],
unit = data['unit']
)
#--------------------------------------------------------
def __import_fd2gm_file_as_drugs_0_5(self, fd2gm_xml=None, pk_data_source=None):
db_def = fd2gm_xml.find('DrugsDatabaseName')
db_id = db_def.text.strip()
drug_id_name = db_def.attrib['drugUidName']
fd_xml_drug_entries = fd2gm_xml.findall('FullPrescription/Prescription')
self.__imported_drugs = []
for fd_xml_drug in fd_xml_drug_entries:
drug_uid = fd_xml_drug.find('Drug_UID').text.strip()
if drug_uid == '-1':
_log.debug('skipping textual drug')
continue # it's a TextualDrug, skip it
drug_name = fd_xml_drug.find('DrugName').text.replace(', )', ')').strip()
drug_form = fd_xml_drug.find('DrugForm').text.strip()
drug_atc = fd_xml_drug.find('DrugATC')
if drug_atc is None:
drug_atc = ''
else:
if drug_atc.text is None:
drug_atc = ''
else:
drug_atc = drug_atc.text.strip()
# create new drug product
new_drug = gmMedication.create_drug_product(product_name = drug_name, preparation = drug_form, return_existing = True)
self.__imported_drugs.append(new_drug)
new_drug['is_fake_product'] = False
new_drug['atc'] = drug_atc
new_drug['external_code_type'] = 'FreeDiams::%s::%s' % (db_id, drug_id_name)
new_drug['external_code'] = drug_uid
new_drug['pk_data_source'] = pk_data_source
new_drug.save()
# parse XML for composition records
fd_xml_components = fd_xml_drug.getiterator('Composition')
comp_data = {}
for fd_xml_comp in fd_xml_components:
data = {}
amount = regex.match(r'\d+[.,]{0,1}\d*', fd_xml_comp.attrib['strenght'].strip()) # sic, typo
if amount is None:
amount = 99999
else:
amount = amount.group()
data['amount'] = amount
unit = regex.sub(r'\d+[.,]{0,1}\d*', '', fd_xml_comp.attrib['strenght'].strip()).strip() # sic, typo!
if unit == '':
unit = '*?*'
data['unit'] = unit
molecule_name = fd_xml_comp.attrib['molecularName'].strip()
if molecule_name != '':
gmMedication.create_substance_dose(substance = molecule_name, atc = None, amount = amount, unit = unit)
data['molecule_name'] = molecule_name
inn_name = fd_xml_comp.attrib['inn'].strip()
if inn_name != '':
gmMedication.create_substance_dose(substance = inn_name, atc = None, amount = amount, unit = unit)
data['inn_name'] = molecule_name
if molecule_name == '':
data['substance'] = inn_name
_log.info('linking INN [%s] rather than molecularName as component', inn_name)
else:
data['substance'] = molecule_name
data['nature'] = fd_xml_comp.attrib['nature'].strip()
data['nature_ID'] = fd_xml_comp.attrib['natureLink'].strip()
# merge composition records of SA/FT nature
try:
old_data = comp_data[data['nature_ID']]
# normalize INN
if old_data['inn_name'] == '':
old_data['inn_name'] = data['inn_name']
if data['inn_name'] == '':
data['inn_name'] = old_data['inn_name']
# normalize molecule
if old_data['molecule_name'] == '':
old_data['molecule_name'] = data['molecule_name']
if data['molecule_name'] == '':
data['molecule_name'] = old_data['molecule_name']
# FT: transformed form
# SA: active substance
# it would be preferable to use the SA record because that's what's *actually*
# contained in the drug, however FreeDiams does not list the amount thereof
# (rather that of the INN)
if data['nature'] == 'FT':
comp_data[data['nature_ID']] = data
else:
comp_data[data['nature_ID']] = old_data
# or create new record
except KeyError:
comp_data[data['nature_ID']] = data
# actually create components from (possibly merged) composition records
for key, data in comp_data.items():
new_drug.add_component (
substance = data['substance'],
amount = data['amount'],
unit = data['unit']
)
#============================================================
# Ifap
#------------------------------------------------------------
class cIfapInterface(cDrugDataSourceInterface):
"""empirical CSV interface"""
def __init__(self):
pass
def print_transfer_file(self, filename=None):
try:
csv_file = open(filename, mode = 'rt', encoding = 'latin1') # FIXME: encoding correct ?
except Exception:
_log.exception('cannot access [%s]', filename)
csv_file = None
field_names = 'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split()
if csv_file is None:
return False
csv_lines = csv.DictReader (
csv_file,
fieldnames = field_names,
delimiter = ';'
)
for line in csv_lines:
print("--------------------------------------------------------------------"[:31])
for key in field_names:
tmp = ('%s ' % key)[:30]
print('%s: %s' % (tmp, line[key]))
csv_file.close()
# narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % (
# line['Packungszahl'].strip(),
# line['Handelsname'].strip(),
# line['Form'].strip(),
# line[u'Packungsgr\xf6\xdfe'].strip(),
# line['Abpackungsmenge'].strip(),
# line['Einheit'].strip(),
# line['Hersteller'].strip(),
# line['PZN'].strip()
# )
#============================================================
drug_data_source_interfaces = {
'Deutschland: Gelbe Liste/MMI (Windows)': cGelbeListeWindowsInterface,
'Deutschland: Gelbe Liste/MMI (WINE)': cGelbeListeWineInterface,
'FreeDiams (FR, US, CA, ZA)': cFreeDiamsInterface
}
#============================================================
# main
#------------------------------------------------------------
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit()
if sys.argv[1] != 'test':
sys.exit()
from Gnumed.business import gmPerson
#--------------------------------------------------------
def test_MMI_interface():
mmi = cGelbeListeWineInterface()
print(mmi)
print("interface definition:", mmi.version)
print("database versions: ", mmi.get_data_source_version())
#--------------------------------------------------------
def test_MMI_file():
mmi_file = cGelbeListeCSVFile(filename = sys.argv[2])
for drug in mmi_file:
print("-------------")
print('"%s" (ATC: %s / PZN: %s)' % (drug['name'], drug['atc'], drug['pzn']))
for stoff in drug['wirkstoffe']:
print(" Wirkstoff:", stoff)
input()
if mmi_file.has_unknown_fields is not None:
print("has extra data under [%s]" % gmTools.default_csv_reader_rest_key)
for key in mmi_file.csv_fieldnames:
print(key, '->', drug[key])
input()
mmi_file.close()
#--------------------------------------------------------
def test_mmi_switch_to():
mmi = cGelbeListeWineInterface()
mmi.switch_to_frontend(blocking = True)
#--------------------------------------------------------
def test_mmi_let_user_select_drugs():
mmi = cGelbeListeWineInterface()
mmi_file = mmi.__let_user_select_drugs()
for drug in mmi_file:
print("-------------")
print('"%s" (ATC: %s / PZN: %s)' % (drug['name'], drug['atc'], drug['pzn']))
for stoff in drug['wirkstoffe']:
print(" Wirkstoff:", stoff)
print(drug)
mmi_file.close()
#--------------------------------------------------------
def test_mmi_import_drugs():
mmi = cGelbeListeWineInterface()
mmi.import_drugs()
#--------------------------------------------------------
def test_mmi_interaction_check():
mmi = cGelbeListeWineInterface()
print(mmi)
print("interface definition:", mmi.version)
# Metoprolol + Hct vs Citalopram
diclofenac = '7587712'
phenprocoumon = '4421744'
mmi.check_interactions(drug_ids_list = [diclofenac, phenprocoumon])
#--------------------------------------------------------
# FreeDiams
#--------------------------------------------------------
def test_fd_switch_to():
gmPerson.set_active_patient(patient = gmPerson.cPerson(aPK_obj = 12))
fd = cFreeDiamsInterface()
fd.patient = gmPerson.gmCurrentPatient()
# fd.switch_to_frontend(blocking = True)
fd.import_fd2gm_file_as_drugs(filename = sys.argv[2])
#--------------------------------------------------------
def test_fd_show_interactions():
gmPerson.set_active_patient(patient = gmPerson.cPerson(aPK_obj = 12))
fd = cFreeDiamsInterface()
fd.patient = gmPerson.gmCurrentPatient()
fd.check_interactions(substance_intakes = fd.patient.emr.get_current_medications())
#--------------------------------------------------------
# MMI/Gelbe Liste
#test_MMI_interface()
#test_MMI_file()
#test_mmi_switch_to()
#test_mmi_let_user_select_drugs()
#test_mmi_import_substances()
#test_mmi_import_drugs()
# FreeDiams
#test_fd_switch_to()
#test_fd_show_interactions()
Classes
class cDrugDataSourceInterface
-
Expand source code
class cDrugDataSourceInterface(object): #-------------------------------------------------------- def __init__(self): self.patient = None self.reviewer = None self.custom_path_to_binary = None #-------------------------------------------------------- def get_data_source_version(self): raise NotImplementedError #-------------------------------------------------------- def create_data_source_entry(self): raise NotImplementedError #-------------------------------------------------------- def switch_to_frontend(self, blocking=False): raise NotImplementedError #-------------------------------------------------------- def import_drugs(self): self.switch_to_frontend() #-------------------------------------------------------- def check_interactions(self, substance_intakes=None): self.switch_to_frontend() #-------------------------------------------------------- def show_info_on_drug(self, substance_intake=None): self.switch_to_frontend() #-------------------------------------------------------- def show_info_on_substance(self, substance_intake=None): self.switch_to_frontend() #-------------------------------------------------------- def prescribe(self, substance_intakes=None): self.switch_to_frontend() return []
Subclasses
Methods
def check_interactions(self, substance_intakes=None)
-
Expand source code
def check_interactions(self, substance_intakes=None): self.switch_to_frontend()
def create_data_source_entry(self)
-
Expand source code
def create_data_source_entry(self): raise NotImplementedError
def get_data_source_version(self)
-
Expand source code
def get_data_source_version(self): raise NotImplementedError
def import_drugs(self)
-
Expand source code
def import_drugs(self): self.switch_to_frontend()
def prescribe(self, substance_intakes=None)
-
Expand source code
def prescribe(self, substance_intakes=None): self.switch_to_frontend() return []
def show_info_on_drug(self, substance_intake=None)
-
Expand source code
def show_info_on_drug(self, substance_intake=None): self.switch_to_frontend()
def show_info_on_substance(self, substance_intake=None)
-
Expand source code
def show_info_on_substance(self, substance_intake=None): self.switch_to_frontend()
def switch_to_frontend(self, blocking=False)
-
Expand source code
def switch_to_frontend(self, blocking=False): raise NotImplementedError
class cFreeDiamsInterface
-
Expand source code
class cFreeDiamsInterface(cDrugDataSourceInterface): version = 'FreeDiams interface' default_encoding = 'utf8' default_dob_format = '%Y/%m/%d' map_gender2mf = { 'm': 'M', 'f': 'F', 'tf': 'H', 'tm': 'H', 'h': 'H' } #-------------------------------------------------------- def __init__(self): cDrugDataSourceInterface.__init__(self) _log.info(cFreeDiamsInterface.version) self.__imported_drugs = [] self.__gm2fd_filename = gmTools.get_unique_filename(prefix = r'gm2freediams-', suffix = r'.xml') _log.debug('GNUmed -> FreeDiams "exchange-in" file: %s', self.__gm2fd_filename) self.__fd2gm_filename = gmTools.get_unique_filename(prefix = r'freediams2gm-', suffix = r'.xml') _log.debug('GNUmed <-> FreeDiams "exchange-out"/"prescription" file: %s', self.__fd2gm_filename) # this file can be modified by the user as needed (therefore in user_config_dir): self.__fd4gm_config_file = os.path.join(gmTools.gmPaths().user_config_dir, 'freediams4gm.conf') _log.debug('FreeDiams config file for GNUmed use: %s', self.__fd4gm_config_file) self.path_to_binary = None self.__detect_binary() #-------------------------------------------------------- def get_data_source_version(self): # ~/.freediams/config.ini: [License] -> AcceptedVersion=.... if not self.__detect_binary(): return False freediams = subprocess.Popen ( args = '--version', # --version or -version or -v executable = self.path_to_binary, stdout = subprocess.PIPE, stderr = subprocess.PIPE, # close_fds = True, # Windows can't do that in conjunction with stdout/stderr = ... :-( universal_newlines = True ) data, errors = freediams.communicate() version = regex.search(r'FreeDiams\s\d.\d.\d', data).group().split()[1] _log.debug('FreeDiams %s', version) return version #-------------------------------------------------------- def create_data_source_entry(self): return gmCoding.create_data_source ( long_name = '"FreeDiams" Drug Database Frontend', short_name = 'FreeDiams', version = self.get_data_source_version(), source = 'http://ericmaeker.fr/FreeMedForms/di-manual/index.html', language = 'fr' # actually to be multi-locale ) #-------------------------------------------------------- def switch_to_frontend(self, blocking=False, mode='interactions'): """https://ericmaeker.fr/FreeMedForms/di-manual/en/html/ligne_commandes.html""" _log.debug('calling FreeDiams in [%s] mode', mode) self.__imported_drugs = [] if not self.__detect_binary(): return False self.__create_gm2fd_file(mode = mode) args = '--exchange-in="%s"' % (self.__gm2fd_filename) cmd = r'%s %s' % (self.path_to_binary, args) if os.name == 'nt': blocking = True if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking): _log.error('problem switching to the FreeDiams drug database') return False if blocking == True: self.import_fd2gm_file_as_drugs() return True #-------------------------------------------------------- def import_drugs(self): self.switch_to_frontend(blocking = True) #-------------------------------------------------------- def check_interactions(self, substance_intakes=None): if substance_intakes is None: return if len(substance_intakes) < 2: return self.__create_prescription_file(substance_intakes = substance_intakes) self.switch_to_frontend(mode = 'interactions', blocking = False) #-------------------------------------------------------- def show_info_on_drug(self, substance_intake=None): if substance_intake is None: return self.__create_prescription_file(substance_intakes = [substance_intake]) self.switch_to_frontend(mode = 'interactions', blocking = False) #-------------------------------------------------------- def show_info_on_substance(self, substance_intake=None): self.show_info_on_drug(substance_intake = substance_intake) #-------------------------------------------------------- def prescribe(self, substance_intakes=None): if substance_intakes is None: if not self.__export_latest_prescription(): self.__create_prescription_file() else: self.__create_prescription_file(substance_intakes = substance_intakes) self.switch_to_frontend(mode = 'prescription', blocking = True) self.import_fd2gm_file_as_prescription() return self.__imported_drugs #-------------------------------------------------------- # internal helpers #-------------------------------------------------------- def __detect_binary(self): if self.path_to_binary is not None: return True found, cmd = gmShellAPI.find_first_binary(binaries = [ r'/usr/bin/freediams', r'freediams', r'/Applications/FreeDiams.app/Contents/MacOs/FreeDiams', r'C:\Program Files (x86)\FreeDiams\freediams.exe', r'C:\Program Files\FreeDiams\freediams.exe', r'c:\programs\freediams\freediams.exe', r'freediams.exe' ]) if found: self.path_to_binary = cmd return True try: self.custom_path_to_binary except AttributeError: _log.error('cannot find FreeDiams binary, no custom path set') return False if self.custom_path_to_binary is None: _log.error('cannot find FreeDiams binary') return False found, cmd = gmShellAPI.detect_external_binary(binary = self.custom_path_to_binary) if found: self.path_to_binary = cmd return True _log.error('cannot find FreeDiams binary') return False #-------------------------------------------------------- def __export_latest_prescription(self): if self.patient is None: _log.debug('cannot export latest FreeDiams prescriptions w/o patient') return False docs = self.patient.get_document_folder() prescription = docs.get_latest_freediams_prescription() if prescription is None: _log.debug('no FreeDiams prescription available') return False for part in prescription.parts: if part['filename'] == 'freediams-prescription.xml': if part.save_to_file(filename = self.__fd2gm_filename) is not None: return True _log.error('cannot export latest FreeDiams prescription to XML file') return False #-------------------------------------------------------- def __create_prescription_file(self, substance_intakes=None): """FreeDiams calls this exchange-out or prescription file. CIS stands for Unique Speciality Identifier (eg bisoprolol 5 mg, gel). CIS is AFSSAPS specific, but pharmacist can retrieve drug name with the CIS. AFSSAPS is the French FDA. CIP stands for Unique Presentation Identifier (eg 30 pills plaq) CIP if you want to specify the packaging of the drug (30 pills thermoformed tablet...) -- actually not really usefull for french doctors. # .external_code_type: u'FR-CIS' # .external_cod: the CIS value OnlyForTest: OnlyForTest drugs will be processed by the IA Engine but not printed (regardless of FreeDiams mode). They are shown in gray in the prescription view. Select-only is a mode where FreeDiams creates a list of drugs not a full prescription. In this list, users can add ForTestOnly drug if they want to 1. print the list without some drugs 2. but including these drugs in the IA engine calculation Select-Only mode does not have any relation with the ForTestOnly drugs. IsTextual: What is the use and significance of the <IsTextual>true/false</IsTextual> flag when both <DrugName> and <TextualDrugName> exist ? This tag must be setted even if it sounds like a duplicated data. This tag is needed inside FreeDiams code. INN: GNUmed will pass the substance in <TextualDrugName and will also pass <INN>True</INN>. Eric: Nop, this is not useful because pure textual drugs are not processed but just shown. """ # virginize file open(self.__fd2gm_filename, 'wb').close() # make sure we've got something to do if substance_intakes is None: if self.patient is None: _log.warning('cannot create prescription file because there is neither a patient nor a substance intake list') # do fail because __export_latest_prescription() should not have been called without patient return False emr = self.patient.emr substance_intakes = emr.get_current_medications ( include_inactive = False ) drug_snippets = [] # process FD drugs fd_intakes = [ i for i in substance_intakes if ( (i['external_code_type_product'] is not None) and (i['external_code_type_product'].startswith('FreeDiams::')) )] intakes_pooled_by_product = {} for intake in fd_intakes: # this will leave only one entry per drug # but FreeDiams knows the components ... intakes_pooled_by_product[intake['drug_product']] = intake del fd_intakes drug_snippet = """<Prescription> <Drug u1="%s" u2="" old="%s" u3="" db="%s"> <!-- "old" needs to be the same as "u1" if not known --> <DrugName>%s</DrugName> <!-- just for identification when reading XML files --> </Drug> </Prescription>""" last_db_id = 'CA_HCDPD' for intake in intakes_pooled_by_product.values(): last_db_id = gmTools.xml_escape_string(text = intake['external_code_type_product'].replace('FreeDiams::', '').split('::')[0]) drug_snippets.append(drug_snippet % ( gmTools.xml_escape_string(text = intake['external_code_product'].strip()), gmTools.xml_escape_string(text = intake['external_code_product'].strip()), last_db_id, gmTools.xml_escape_string(text = intake['drug_product'].strip()) )) # process non-FD drugs non_fd_intakes = [ i for i in substance_intakes if ( ( (i['external_code_type_product'] is None) or (not i['external_code_type_product'].startswith('FreeDiams::')) ) )] non_fd_product_intakes = [ i for i in non_fd_intakes if i['drug_product'] is not None ] non_fd_substance_intakes = [ i for i in non_fd_intakes if i['drug_product'] is None ] del non_fd_intakes drug_snippet = """<Prescription> <Drug u1="-1" u2="" old="" u3="" db=""> <DrugName>%s</DrugName> </Drug> <Dose Note="%s" IsTextual="true" IsAld="false"/> </Prescription>""" # <DrugUidName></DrugUidName> # <DrugForm></DrugForm> # <DrugRoute></DrugRoute> # <DrugStrength/> for intake in non_fd_substance_intakes: drug_name = '%s %s%s (%s)' % ( intake['substance'], intake['amount'], intake['unit'], intake['l10n_preparation'] ) drug_snippets.append(drug_snippet % ( gmTools.xml_escape_string(text = drug_name.strip()), gmTools.xml_escape_string(text = gmTools.coalesce(intake['schedule'], '')) )) intakes_pooled_by_product = {} for intake in non_fd_product_intakes: prod = '%s %s' % (intake['drug_product'], intake['l10n_preparation']) try: intakes_pooled_by_product[prod].append(intake) except KeyError: intakes_pooled_by_product[prod] = [intake] for product, comps in intakes_pooled_by_product.items(): drug_name = '%s\n' % product for comp in comps: drug_name += ' %s %s%s\n' % ( comp['substance'], comp['amount'], comp['unit'] ) drug_snippets.append(drug_snippet % ( gmTools.xml_escape_string(text = drug_name.strip()), gmTools.xml_escape_string(text = gmTools.coalesce(comps[0]['schedule'], '')) )) # assemble XML file xml = """<?xml version = "1.0" encoding = "UTF-8"?> <!DOCTYPE FreeMedForms> <FreeDiams> <FullPrescription version="0.7.2"> %s </FullPrescription> </FreeDiams> """ xml_file = open(self.__fd2gm_filename, mode = 'wt', encoding = 'utf8') xml_file.write(xml % '\n\t\t'.join(drug_snippets)) xml_file.close() return True #-------------------------------------------------------- def __create_gm2fd_file(self, mode='interactions'): if mode == 'interactions': mode = 'select-only' elif mode == 'prescription': mode = 'prescriber' else: mode = 'select-only' xml_file = open(self.__gm2fd_filename, mode = 'wt', encoding = 'utf8') xml = """<?xml version="1.0" encoding="UTF-8"?> <FreeDiams_In version="0.5.0"> <EMR name="GNUmed" uid="unused"/> <ConfigFile value="%s"/> <ExchangeOut value="%s" format="xml"/> <!-- <DrugsDatabase uid="can be set to a specific DB"/> --> <Ui editmode="%s" blockPatientDatas="1"/> %%s </FreeDiams_In> """ % ( self.__fd4gm_config_file, self.__fd2gm_filename, mode ) if self.patient is None: xml_file.write(xml % '') xml_file.close() return name = self.patient.get_active_name() if self.patient['dob'] is None: dob = '' else: dob = self.patient['dob'].strftime(cFreeDiamsInterface.default_dob_format) emr = self.patient.emr allgs = emr.get_allergies() atc_allgs = [ a['atc_code'] for a in allgs if ((a['atc_code'] is not None) and (a['type'] == 'allergy')) ] atc_sens = [ a['atc_code'] for a in allgs if ((a['atc_code'] is not None) and (a['type'] == 'sensitivity')) ] inn_allgs = [ a['allergene'] for a in allgs if ((a['allergene'] is not None) and (a['type'] == 'allergy')) ] inn_sens = [ a['allergene'] for a in allgs if ((a['allergene'] is not None) and (a['type'] == 'sensitivity')) ] # this is rather fragile: FreeDiams won't know what type of UID this is # (but it will assume it is of the type of the drug database in use) # but eventually FreeDiams puts all drugs into one database :-) uid_allgs = [ a['substance_code'] for a in allgs if ((a['substance_code'] is not None) and (a['type'] == 'allergy')) ] uid_sens = [ a['substance_code'] for a in allgs if ((a['substance_code'] is not None) and (a['type'] == 'sensitivity')) ] patient_xml = """<Patient> <Identity lastnames="%s" firstnames="%s" uid="%s" dob="%s" gender="%s" /> <!-- can be <7 characters class codes: --> <ATCAllergies value="%s"/> <ATCIntolerances value="%s"/> <InnAllergies value="%s"/> <InnIntolerances value="%s"/> <DrugsUidAllergies value="%s"/> <DrugsUidIntolerances value="%s"/> <!-- # FIXME: search by LOINC code and add (as soon as supported by FreeDiams ...) <Creatinine value="12" unit="mg/l or mmol/l"/> <Weight value="70" unit="kg or pd" /> <WeightInGrams value="70"/> <Height value="170" unit="cm or "/> <HeightInCentimeters value="170"/> <ICD10 value="J11.0;A22;Z23"/> --> </Patient> """ % ( gmTools.xml_escape_string(text = name['lastnames']), gmTools.xml_escape_string(text = name['firstnames']), self.patient.ID, dob, cFreeDiamsInterface.map_gender2mf[self.patient['gender']], gmTools.xml_escape_string(text = ';'.join(atc_allgs)), gmTools.xml_escape_string(text = ';'.join(atc_sens)), gmTools.xml_escape_string(text = ';'.join(inn_allgs)), gmTools.xml_escape_string(text = ';'.join(inn_sens)), gmTools.xml_escape_string(text = ';'.join(uid_allgs)), gmTools.xml_escape_string(text = ';'.join(uid_sens)) ) xml_file.write(xml % patient_xml) xml_file.close() #-------------------------------------------------------- def import_fd2gm_file_as_prescription(self, filename=None): if filename is None: filename = self.__fd2gm_filename _log.debug('importing FreeDiams prescription information from [%s]', filename) fd2gm_xml = etree.ElementTree() fd2gm_xml.parse(filename) pdfs = fd2gm_xml.findall('ExtraDatas/Printed') if len(pdfs) == 0: _log.debug('no PDF prescription files listed') return fd_filenames = [] for pdf in pdfs: fd_filenames.append(pdf.attrib['file']) _log.debug('listed PDF prescription files: %s', fd_filenames) docs = self.patient.get_document_folder() emr = self.patient.emr prescription = docs.add_prescription ( encounter = emr.active_encounter['pk_encounter'], episode = emr.add_episode ( episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE, is_open = False )['pk_episode'] ) prescription['ext_ref'] = 'FreeDiams' prescription.save() fd_filenames.append(filename) success, msg, parts = prescription.add_parts_from_files(files = fd_filenames) if not success: _log.error(msg) return for part in parts: part['obj_comment'] = _('copy of printed prescription') part.save() xml_part = parts[-1] xml_part['filename'] = 'freediams-prescription.xml' xml_part['obj_comment'] = _('prescription data') xml_part.save() # are we the intended reviewer ? from Gnumed.business.gmStaff import gmCurrentProvider me = gmCurrentProvider() # if so: auto-sign the prescription if xml_part['pk_intended_reviewer'] == me['pk_staff']: prescription.set_reviewed(technically_abnormal = False, clinically_relevant = False) #-------------------------------------------------------- def import_fd2gm_file_as_drugs(self, filename=None): """ If returning textual prescriptions (say, drugs which FreeDiams did not know) then "IsTextual" will be True and UID will be -1. """ if filename is None: filename = self.__fd2gm_filename # FIXME: do not import IsTextual drugs, or rather, make that configurable fd2gm_xml = etree.ElementTree() fd2gm_xml.parse(filename) data_src_pk = self.create_data_source_entry() xml_version = fd2gm_xml.find('FullPrescription').attrib['version'] _log.debug('fd2gm file version: %s', xml_version) if xml_version in ['0.6.0', '0.7.2']: return self.__import_fd2gm_file_as_drugs_0_6_0(fd2gm_xml = fd2gm_xml, pk_data_source = data_src_pk) return self.__import_fd2gm_file_as_drugs_0_5(fd2gm_xml = fd2gm_xml, pk_data_source = data_src_pk) #-------------------------------------------------------- def __import_fd2gm_file_as_drugs_0_6_0(self, fd2gm_xml=None, pk_data_source=None): # drug_id_name = db_def.attrib['drugUidName'] fd_xml_prescriptions = fd2gm_xml.findall('FullPrescription/Prescription') self.__imported_drugs = [] for fd_xml_prescription in fd_xml_prescriptions: drug_uid = fd_xml_prescription.find('Drug').attrib['u1'].strip() if drug_uid == '-1': _log.debug('skipping textual drug') continue drug_db = fd_xml_prescription.find('Drug').attrib['db'].strip() drug_uid_name = fd_xml_prescription.find('Drug/DrugUidName').text.strip() #drug_uid_name = u'<%s>' % drug_db drug_name = fd_xml_prescription.find('Drug/DrugName').text.replace(', )', ')').strip() drug_form = fd_xml_prescription.find('Drug/DrugForm').text.strip() # drug_atc = fd_xml_prescription.find('DrugATC') # if drug_atc is None: # drug_atc = u'' # else: # if drug_atc.text is None: # drug_atc = u'' # else: # drug_atc = drug_atc.text.strip() # create new drug product new_drug = gmMedication.create_drug_product(product_name = drug_name, preparation = drug_form, return_existing = True) self.__imported_drugs.append(new_drug) new_drug['is_fake_product'] = False # new_drug['atc'] = drug_atc new_drug['external_code_type'] = 'FreeDiams::%s::%s' % (drug_db, drug_uid_name) new_drug['external_code'] = drug_uid new_drug['pk_data_source'] = pk_data_source new_drug.save() # parse XML for composition records fd_xml_components = fd_xml_prescription.getiterator('Composition') comp_data = {} for fd_xml_comp in fd_xml_components: data = {} xml_strength = fd_xml_comp.attrib['strength'].strip() amount = regex.match(r'^\d+[.,]{0,1}\d*', xml_strength) if amount is None: amount = 99999 else: amount = amount.group() data['amount'] = amount #unit = regex.sub(r'\d+[.,]{0,1}\d*', u'', xml_strength).strip() unit = (xml_strength[len(amount):]).strip() if unit == '': unit = '*?*' data['unit'] = unit # hopefully, FreeDiams gets their act together, eventually: atc = regex.match(r'[A-Za-z]\d\d[A-Za-z]{2}\d\d', fd_xml_comp.attrib['atc'].strip()) if atc is None: data['atc'] = None else: atc = atc.group() data['atc'] = atc molecule_name = fd_xml_comp.attrib['molecularName'].strip() if molecule_name != '': gmMedication.create_substance_dose(substance = molecule_name, atc = atc, amount = amount, unit = unit) data['molecule_name'] = molecule_name inn_name = fd_xml_comp.attrib['inn'].strip() if inn_name != '': gmMedication.create_substance_dose(substance = inn_name, atc = atc, amount = amount, unit = unit) #data['inn_name'] = molecule_name data['inn_name'] = inn_name if molecule_name == '': data['substance'] = inn_name _log.info('linking INN [%s] rather than molecularName as component', inn_name) else: data['substance'] = molecule_name data['nature'] = fd_xml_comp.attrib['nature'].strip() data['nature_ID'] = fd_xml_comp.attrib['natureLink'].strip() # merge composition records of SA/FT nature try: old_data = comp_data[data['nature_ID']] # normalize INN if old_data['inn_name'] == '': old_data['inn_name'] = data['inn_name'] if data['inn_name'] == '': data['inn_name'] = old_data['inn_name'] # normalize molecule if old_data['molecule_name'] == '': old_data['molecule_name'] = data['molecule_name'] if data['molecule_name'] == '': data['molecule_name'] = old_data['molecule_name'] # normalize ATC if old_data['atc'] == '': old_data['atc'] = data['atc'] if data['atc'] == '': data['atc'] = old_data['atc'] # FT: transformed form # SA: active substance # it would be preferable to use the SA record because that's what's *actually* # contained in the drug, however FreeDiams does not list the amount thereof # (rather that of the INN) # FT and SA records of the same component carry the same nature_ID if data['nature'] == 'FT': comp_data[data['nature_ID']] = data else: comp_data[data['nature_ID']] = old_data # or create new record except KeyError: comp_data[data['nature_ID']] = data # actually create components from (possibly merged) composition records for key, data in comp_data.items(): new_drug.add_component ( substance = data['substance'], atc = data['atc'], amount = data['amount'], unit = data['unit'] ) #-------------------------------------------------------- def __import_fd2gm_file_as_drugs_0_5(self, fd2gm_xml=None, pk_data_source=None): db_def = fd2gm_xml.find('DrugsDatabaseName') db_id = db_def.text.strip() drug_id_name = db_def.attrib['drugUidName'] fd_xml_drug_entries = fd2gm_xml.findall('FullPrescription/Prescription') self.__imported_drugs = [] for fd_xml_drug in fd_xml_drug_entries: drug_uid = fd_xml_drug.find('Drug_UID').text.strip() if drug_uid == '-1': _log.debug('skipping textual drug') continue # it's a TextualDrug, skip it drug_name = fd_xml_drug.find('DrugName').text.replace(', )', ')').strip() drug_form = fd_xml_drug.find('DrugForm').text.strip() drug_atc = fd_xml_drug.find('DrugATC') if drug_atc is None: drug_atc = '' else: if drug_atc.text is None: drug_atc = '' else: drug_atc = drug_atc.text.strip() # create new drug product new_drug = gmMedication.create_drug_product(product_name = drug_name, preparation = drug_form, return_existing = True) self.__imported_drugs.append(new_drug) new_drug['is_fake_product'] = False new_drug['atc'] = drug_atc new_drug['external_code_type'] = 'FreeDiams::%s::%s' % (db_id, drug_id_name) new_drug['external_code'] = drug_uid new_drug['pk_data_source'] = pk_data_source new_drug.save() # parse XML for composition records fd_xml_components = fd_xml_drug.getiterator('Composition') comp_data = {} for fd_xml_comp in fd_xml_components: data = {} amount = regex.match(r'\d+[.,]{0,1}\d*', fd_xml_comp.attrib['strenght'].strip()) # sic, typo if amount is None: amount = 99999 else: amount = amount.group() data['amount'] = amount unit = regex.sub(r'\d+[.,]{0,1}\d*', '', fd_xml_comp.attrib['strenght'].strip()).strip() # sic, typo! if unit == '': unit = '*?*' data['unit'] = unit molecule_name = fd_xml_comp.attrib['molecularName'].strip() if molecule_name != '': gmMedication.create_substance_dose(substance = molecule_name, atc = None, amount = amount, unit = unit) data['molecule_name'] = molecule_name inn_name = fd_xml_comp.attrib['inn'].strip() if inn_name != '': gmMedication.create_substance_dose(substance = inn_name, atc = None, amount = amount, unit = unit) data['inn_name'] = molecule_name if molecule_name == '': data['substance'] = inn_name _log.info('linking INN [%s] rather than molecularName as component', inn_name) else: data['substance'] = molecule_name data['nature'] = fd_xml_comp.attrib['nature'].strip() data['nature_ID'] = fd_xml_comp.attrib['natureLink'].strip() # merge composition records of SA/FT nature try: old_data = comp_data[data['nature_ID']] # normalize INN if old_data['inn_name'] == '': old_data['inn_name'] = data['inn_name'] if data['inn_name'] == '': data['inn_name'] = old_data['inn_name'] # normalize molecule if old_data['molecule_name'] == '': old_data['molecule_name'] = data['molecule_name'] if data['molecule_name'] == '': data['molecule_name'] = old_data['molecule_name'] # FT: transformed form # SA: active substance # it would be preferable to use the SA record because that's what's *actually* # contained in the drug, however FreeDiams does not list the amount thereof # (rather that of the INN) if data['nature'] == 'FT': comp_data[data['nature_ID']] = data else: comp_data[data['nature_ID']] = old_data # or create new record except KeyError: comp_data[data['nature_ID']] = data # actually create components from (possibly merged) composition records for key, data in comp_data.items(): new_drug.add_component ( substance = data['substance'], amount = data['amount'], unit = data['unit'] )
Ancestors
Class variables
var default_dob_format
var default_encoding
var map_gender2mf
var version
Methods
def check_interactions(self, substance_intakes=None)
-
Expand source code
def check_interactions(self, substance_intakes=None): if substance_intakes is None: return if len(substance_intakes) < 2: return self.__create_prescription_file(substance_intakes = substance_intakes) self.switch_to_frontend(mode = 'interactions', blocking = False)
def create_data_source_entry(self)
-
Expand source code
def create_data_source_entry(self): return gmCoding.create_data_source ( long_name = '"FreeDiams" Drug Database Frontend', short_name = 'FreeDiams', version = self.get_data_source_version(), source = 'http://ericmaeker.fr/FreeMedForms/di-manual/index.html', language = 'fr' # actually to be multi-locale )
def get_data_source_version(self)
-
Expand source code
def get_data_source_version(self): # ~/.freediams/config.ini: [License] -> AcceptedVersion=.... if not self.__detect_binary(): return False freediams = subprocess.Popen ( args = '--version', # --version or -version or -v executable = self.path_to_binary, stdout = subprocess.PIPE, stderr = subprocess.PIPE, # close_fds = True, # Windows can't do that in conjunction with stdout/stderr = ... :-( universal_newlines = True ) data, errors = freediams.communicate() version = regex.search(r'FreeDiams\s\d.\d.\d', data).group().split()[1] _log.debug('FreeDiams %s', version) return version
def import_drugs(self)
-
Expand source code
def import_drugs(self): self.switch_to_frontend(blocking = True)
def import_fd2gm_file_as_drugs(self, filename=None)
-
If returning textual prescriptions (say, drugs which FreeDiams did not know) then "IsTextual" will be True and UID will be -1.
Expand source code
def import_fd2gm_file_as_drugs(self, filename=None): """ If returning textual prescriptions (say, drugs which FreeDiams did not know) then "IsTextual" will be True and UID will be -1. """ if filename is None: filename = self.__fd2gm_filename # FIXME: do not import IsTextual drugs, or rather, make that configurable fd2gm_xml = etree.ElementTree() fd2gm_xml.parse(filename) data_src_pk = self.create_data_source_entry() xml_version = fd2gm_xml.find('FullPrescription').attrib['version'] _log.debug('fd2gm file version: %s', xml_version) if xml_version in ['0.6.0', '0.7.2']: return self.__import_fd2gm_file_as_drugs_0_6_0(fd2gm_xml = fd2gm_xml, pk_data_source = data_src_pk) return self.__import_fd2gm_file_as_drugs_0_5(fd2gm_xml = fd2gm_xml, pk_data_source = data_src_pk)
def import_fd2gm_file_as_prescription(self, filename=None)
-
Expand source code
def import_fd2gm_file_as_prescription(self, filename=None): if filename is None: filename = self.__fd2gm_filename _log.debug('importing FreeDiams prescription information from [%s]', filename) fd2gm_xml = etree.ElementTree() fd2gm_xml.parse(filename) pdfs = fd2gm_xml.findall('ExtraDatas/Printed') if len(pdfs) == 0: _log.debug('no PDF prescription files listed') return fd_filenames = [] for pdf in pdfs: fd_filenames.append(pdf.attrib['file']) _log.debug('listed PDF prescription files: %s', fd_filenames) docs = self.patient.get_document_folder() emr = self.patient.emr prescription = docs.add_prescription ( encounter = emr.active_encounter['pk_encounter'], episode = emr.add_episode ( episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE, is_open = False )['pk_episode'] ) prescription['ext_ref'] = 'FreeDiams' prescription.save() fd_filenames.append(filename) success, msg, parts = prescription.add_parts_from_files(files = fd_filenames) if not success: _log.error(msg) return for part in parts: part['obj_comment'] = _('copy of printed prescription') part.save() xml_part = parts[-1] xml_part['filename'] = 'freediams-prescription.xml' xml_part['obj_comment'] = _('prescription data') xml_part.save() # are we the intended reviewer ? from Gnumed.business.gmStaff import gmCurrentProvider me = gmCurrentProvider() # if so: auto-sign the prescription if xml_part['pk_intended_reviewer'] == me['pk_staff']: prescription.set_reviewed(technically_abnormal = False, clinically_relevant = False)
def prescribe(self, substance_intakes=None)
-
Expand source code
def prescribe(self, substance_intakes=None): if substance_intakes is None: if not self.__export_latest_prescription(): self.__create_prescription_file() else: self.__create_prescription_file(substance_intakes = substance_intakes) self.switch_to_frontend(mode = 'prescription', blocking = True) self.import_fd2gm_file_as_prescription() return self.__imported_drugs
def show_info_on_drug(self, substance_intake=None)
-
Expand source code
def show_info_on_drug(self, substance_intake=None): if substance_intake is None: return self.__create_prescription_file(substance_intakes = [substance_intake]) self.switch_to_frontend(mode = 'interactions', blocking = False)
def show_info_on_substance(self, substance_intake=None)
-
Expand source code
def show_info_on_substance(self, substance_intake=None): self.show_info_on_drug(substance_intake = substance_intake)
def switch_to_frontend(self, blocking=False, mode='interactions')
-
Expand source code
def switch_to_frontend(self, blocking=False, mode='interactions'): """https://ericmaeker.fr/FreeMedForms/di-manual/en/html/ligne_commandes.html""" _log.debug('calling FreeDiams in [%s] mode', mode) self.__imported_drugs = [] if not self.__detect_binary(): return False self.__create_gm2fd_file(mode = mode) args = '--exchange-in="%s"' % (self.__gm2fd_filename) cmd = r'%s %s' % (self.path_to_binary, args) if os.name == 'nt': blocking = True if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking): _log.error('problem switching to the FreeDiams drug database') return False if blocking == True: self.import_fd2gm_file_as_drugs() return True
class cGelbeListeCSVFile (filename=None)
-
Iterator over a Gelbe Liste/MMI v8.2 CSV file.
Expand source code
class cGelbeListeCSVFile(object): """Iterator over a Gelbe Liste/MMI v8.2 CSV file.""" version = 'Gelbe Liste/MMI v8.2 CSV file interface' default_transfer_file_windows = r"c:\rezept.txt" #default_encoding = 'cp1252' default_encoding = 'cp1250' csv_fieldnames = [ 'name', 'packungsgroesse', # obsolete, use "packungsmenge" 'darreichungsform', 'packungstyp', 'festbetrag', 'avp', 'hersteller', 'rezepttext', 'pzn', 'status_vertrieb', 'status_rezeptpflicht', 'status_fachinfo', 'btm', 'atc', 'anzahl_packungen', 'zuzahlung_pro_packung', 'einheit', 'schedule_morgens', 'schedule_mittags', 'schedule_abends', 'schedule_nachts', 'status_dauermedikament', 'status_hausliste', 'status_negativliste', 'ik_nummer', 'status_rabattvertrag', 'wirkstoffe', 'wirkstoffmenge', 'wirkstoffeinheit', 'wirkstoffmenge_bezug', 'wirkstoffmenge_bezugseinheit', 'status_import', 'status_lifestyle', 'status_ausnahmeliste', 'packungsmenge', 'apothekenpflicht', 'status_billigere_packung', 'rezepttyp', 'besonderes_arzneimittel', # Abstimmungsverfahren SGB-V 't_rezept_pflicht', # Thalidomid-Rezept 'erstattbares_medizinprodukt', 'hilfsmittel', 'hzv_rabattkennung', 'hzv_preis' ] boolean_fields = [ 'status_rezeptpflicht', 'status_fachinfo', 'btm', 'status_dauermedikament', 'status_hausliste', 'status_negativliste', 'status_rabattvertrag', 'status_import', 'status_lifestyle', 'status_ausnahmeliste', 'apothekenpflicht', 'status_billigere_packung', 'besonderes_arzneimittel', # Abstimmungsverfahren SGB-V 't_rezept_pflicht', 'erstattbares_medizinprodukt', 'hilfsmittel' ] #-------------------------------------------------------- def __init__(self, filename=None): _log.info(cGelbeListeCSVFile.version) self.filename = filename if filename is None: self.filename = cGelbeListeCSVFile.default_transfer_file_windows _log.debug('reading Gelbe Liste/MMI drug data from [%s]', self.filename) self.csv_file = open(filename, mode = 'rt', encoding = cGelbeListeCSVFile.default_encoding) self.csv_lines = gmTools.unicode_csv_reader ( self.csv_file, fieldnames = cGelbeListeCSVFile.csv_fieldnames, delimiter = ';', quotechar = '"', dict = True ) #-------------------------------------------------------- def __iter__(self): return self #-------------------------------------------------------- def next(self): line = self.csv_lines.next() for field in cGelbeListeCSVFile.boolean_fields: line[field] = (line[field].strip() == 'T') # split field "Wirkstoff" by ";" if line['wirkstoffe'].strip() == '': line['wirkstoffe'] = [] else: line['wirkstoffe'] = [ wirkstoff.strip() for wirkstoff in line['wirkstoffe'].split(';') ] return line #-------------------------------------------------------- def close(self, truncate=True): try: self.csv_file.close() except Exception: pass if truncate: try: os.open(self.filename, 'wb').close except Exception: pass #-------------------------------------------------------- def _get_has_unknown_fields(self): return (gmTools.default_csv_reader_rest_key in self.csv_fieldnames) has_unknown_fields = property(_get_has_unknown_fields)
Class variables
var boolean_fields
var csv_fieldnames
var default_encoding
var default_transfer_file_windows
var version
Instance variables
var has_unknown_fields
-
Expand source code
def _get_has_unknown_fields(self): return (gmTools.default_csv_reader_rest_key in self.csv_fieldnames)
Methods
def close(self, truncate=True)
-
Expand source code
def close(self, truncate=True): try: self.csv_file.close() except Exception: pass if truncate: try: os.open(self.filename, 'wb').close except Exception: pass
def next(self)
-
Expand source code
def next(self): line = self.csv_lines.next() for field in cGelbeListeCSVFile.boolean_fields: line[field] = (line[field].strip() == 'T') # split field "Wirkstoff" by ";" if line['wirkstoffe'].strip() == '': line['wirkstoffe'] = [] else: line['wirkstoffe'] = [ wirkstoff.strip() for wirkstoff in line['wirkstoffe'].split(';') ] return line
class cGelbeListeWindowsInterface
-
Support v8.2 CSV file interface only.
Expand source code
class cGelbeListeWindowsInterface(cDrugDataSourceInterface): """Support v8.2 CSV file interface only.""" version = 'Gelbe Liste/MMI v8.2 interface' default_encoding = 'cp1250' bdt_line_template = '%03d6210#%s\r\n' # Medikament verordnet auf Kassenrezept bdt_line_base_length = 8 #-------------------------------------------------------- def __init__(self): cDrugDataSourceInterface.__init__(self) _log.info('%s (native Windows)', cGelbeListeWindowsInterface.version) self.path_to_binary = r'C:\Programme\MMI PHARMINDEX\glwin.exe' self.args = r'-KEEPBACKGROUND -PRESCRIPTIONFILE %s -CLOSETOTRAY' paths = gmTools.gmPaths() self.default_csv_filename = os.path.join(paths.tmp_dir, 'rezept.txt') self.default_csv_filename_arg = paths.tmp_dir self.interactions_filename = os.path.join(paths.tmp_dir, 'gm2mmi.bdt') self.data_date_filename = r'C:\Programme\MMI PHARMINDEX\datadate.txt' self.__data_date = None self.__online_update_date = None # use adjusted config.dat #-------------------------------------------------------- def get_data_source_version(self, force_reload=False): if self.__data_date is not None: if not force_reload: return { 'data': self.__data_date, 'online_update': self.__online_update_date } try: open(self.data_date_filename, 'wb').close() except Exception: _log.error('problem querying the MMI drug database for version information') _log.exception('cannot create MMI drug database version file [%s]', self.data_date_filename) self.__data_date = None self.__online_update_date = None return { 'data': '?', 'online_update': '?' } cmd = '%s -DATADATE' % self.path_to_binary if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): _log.error('problem querying the MMI drug database for version information') self.__data_date = None self.__online_update_date = None return { 'data': '?', 'online_update': '?' } try: version_file = open(self.data_date_filename, mode = 'rt', encoding = 'utf8') except Exception: _log.error('problem querying the MMI drug database for version information') _log.exception('cannot open MMI drug database version file [%s]', self.data_date_filename) self.__data_date = None self.__online_update_date = None return { 'data': '?', 'online_update': '?' } self.__data_date = version_file.readline()[:10] self.__online_update_date = version_file.readline()[:10] version_file.close() return { 'data': self.__data_date, 'online_update': self.__online_update_date } #-------------------------------------------------------- def create_data_source_entry(self): versions = self.get_data_source_version() return gmCoding.create_data_source ( long_name = 'Medikamentendatenbank "mmi PHARMINDEX" (Gelbe Liste)', short_name = 'GL/MMI', version = 'Daten: %s, Preise (Onlineupdate): %s' % (versions['data'], versions['online_update']), source = 'Medizinische Medien Informations GmbH, Am Forsthaus Gravenbruch 7, 63263 Neu-Isenburg', language = 'de' ) #-------------------------------------------------------- def switch_to_frontend(self, blocking=False, cmd=None): try: # must make sure csv file exists open(self.default_csv_filename, 'wb').close() except IOError: _log.exception('problem creating GL/MMI <-> GNUmed exchange file') return False if cmd is None: cmd = ('%s %s' % (self.path_to_binary, self.args)) % self.default_csv_filename_arg if os.name == 'nt': blocking = True if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking): _log.error('problem switching to the MMI drug database') # apparently on the first call MMI does not # consistently return 0 on success # return False return True #-------------------------------------------------------- def __let_user_select_drugs(self): # better to clean up interactions file open(self.interactions_filename, 'wb').close() if not self.switch_to_frontend(blocking = True): return None return cGelbeListeCSVFile(filename = self.default_csv_filename) #-------------------------------------------------------- def import_drugs_as_substances(self): selected_drugs = self.__let_user_select_drugs() if selected_drugs is None: return None new_substances = [] for drug in selected_drugs: atc = None # hopefully MMI eventually supports atc-per-substance in a drug... if len(drug['wirkstoffe']) == 1: atc = drug['atc'] for wirkstoff in drug['wirkstoffe']: #new_substances.append(gmMedication.create_substance_dose(substance = wirkstoff, atc = atc, amount = amount, unit = unit)) print(atc) pass selected_drugs.close() return new_substances #-------------------------------------------------------- def import_drugs(self): selected_drugs = self.__let_user_select_drugs() if selected_drugs is None: return None data_src_pk = self.create_data_source_entry() new_drugs = [] new_substances = [] for entry in selected_drugs: _log.debug('importing drug: %s %s', entry['name'], entry['darreichungsform']) if entry['hilfsmittel']: _log.debug('skipping Hilfsmittel') continue if entry['erstattbares_medizinprodukt']: _log.debug('skipping sonstiges Medizinprodukt') continue # create drug product (or get it if it already exists) drug = gmMedication.create_drug_product(product_name = entry['name'], preparation = entry['darreichungsform']) if drug is None: drug = gmMedication.get_drug_by_name(product_name = entry['name'], preparation = entry['darreichungsform']) new_drugs.append(drug) # update fields drug['is_fake_product'] = False drug['atc'] = entry['atc'] drug['external_code_type'] = 'DE-PZN' drug['external_code'] = entry['pzn'] drug['fk_data_source'] = data_src_pk drug.save() # add components to drug atc = None # hopefully MMI eventually supports atc-per-substance in a drug... if len(entry['wirkstoffe']) == 1: atc = entry['atc'] for wirkstoff in entry['wirkstoffe']: drug.add_component(substance = wirkstoff, atc = atc) # create as substance doses, too atc = None # hopefully MMI eventually supports atc-per-substance in a drug... if len(entry['wirkstoffe']) == 1: atc = entry['atc'] for wirkstoff in entry['wirkstoffe']: #new_substances.append(gmMedication.create_substance_dose(substance = wirkstoff, atc = atc, amount = amount, unit = unit)) pass return new_drugs, new_substances #-------------------------------------------------------- def check_interactions(self, drug_ids_list=None, substances=None): """For this to work the BDT interaction check must be configured in the MMI.""" if drug_ids_list is None: if substances is None: return if len(substances) < 2: return drug_ids_list = [ (s.external_code_type, s.external_code) for s in substances ] drug_ids_list = [ code_value for code_type, code_value in drug_ids_list if (code_value is not None) and (code_type == 'DE-PZN')] else: if len(drug_ids_list) < 2: return if drug_ids_list < 2: return bdt_file = open(self.interactions_filename, mode = 'wt', encoding = cGelbeListeWindowsInterface.default_encoding) for pzn in drug_ids_list: pzn = pzn.strip() lng = cGelbeListeWindowsInterface.bdt_line_base_length + len(pzn) bdt_file.write(cGelbeListeWindowsInterface.bdt_line_template % (lng, pzn)) bdt_file.close() self.switch_to_frontend(blocking = True) #-------------------------------------------------------- def show_info_on_drug(self, drug=None): self.switch_to_frontend(blocking = True) #-------------------------------------------------------- def show_info_on_substance(self, substance=None): cmd = None if substance.external_code_type == 'DE-PZN': cmd = '%s -PZN %s' % (self.path_to_binary, substance.external_code) if cmd is None: name = gmTools.coalesce ( substance['drug_product'], substance['substance'] ) cmd = '%s -NAME %s' % (self.path_to_binary, name) # better to clean up interactions file open(self.interactions_filename, 'wb').close() self.switch_to_frontend(cmd = cmd)
Ancestors
Subclasses
Class variables
var bdt_line_base_length
var bdt_line_template
var default_encoding
var version
Methods
def check_interactions(self, drug_ids_list=None, substances=None)
-
For this to work the BDT interaction check must be configured in the MMI.
Expand source code
def check_interactions(self, drug_ids_list=None, substances=None): """For this to work the BDT interaction check must be configured in the MMI.""" if drug_ids_list is None: if substances is None: return if len(substances) < 2: return drug_ids_list = [ (s.external_code_type, s.external_code) for s in substances ] drug_ids_list = [ code_value for code_type, code_value in drug_ids_list if (code_value is not None) and (code_type == 'DE-PZN')] else: if len(drug_ids_list) < 2: return if drug_ids_list < 2: return bdt_file = open(self.interactions_filename, mode = 'wt', encoding = cGelbeListeWindowsInterface.default_encoding) for pzn in drug_ids_list: pzn = pzn.strip() lng = cGelbeListeWindowsInterface.bdt_line_base_length + len(pzn) bdt_file.write(cGelbeListeWindowsInterface.bdt_line_template % (lng, pzn)) bdt_file.close() self.switch_to_frontend(blocking = True)
def create_data_source_entry(self)
-
Expand source code
def create_data_source_entry(self): versions = self.get_data_source_version() return gmCoding.create_data_source ( long_name = 'Medikamentendatenbank "mmi PHARMINDEX" (Gelbe Liste)', short_name = 'GL/MMI', version = 'Daten: %s, Preise (Onlineupdate): %s' % (versions['data'], versions['online_update']), source = 'Medizinische Medien Informations GmbH, Am Forsthaus Gravenbruch 7, 63263 Neu-Isenburg', language = 'de' )
def get_data_source_version(self, force_reload=False)
-
Expand source code
def get_data_source_version(self, force_reload=False): if self.__data_date is not None: if not force_reload: return { 'data': self.__data_date, 'online_update': self.__online_update_date } try: open(self.data_date_filename, 'wb').close() except Exception: _log.error('problem querying the MMI drug database for version information') _log.exception('cannot create MMI drug database version file [%s]', self.data_date_filename) self.__data_date = None self.__online_update_date = None return { 'data': '?', 'online_update': '?' } cmd = '%s -DATADATE' % self.path_to_binary if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): _log.error('problem querying the MMI drug database for version information') self.__data_date = None self.__online_update_date = None return { 'data': '?', 'online_update': '?' } try: version_file = open(self.data_date_filename, mode = 'rt', encoding = 'utf8') except Exception: _log.error('problem querying the MMI drug database for version information') _log.exception('cannot open MMI drug database version file [%s]', self.data_date_filename) self.__data_date = None self.__online_update_date = None return { 'data': '?', 'online_update': '?' } self.__data_date = version_file.readline()[:10] self.__online_update_date = version_file.readline()[:10] version_file.close() return { 'data': self.__data_date, 'online_update': self.__online_update_date }
def import_drugs(self)
-
Expand source code
def import_drugs(self): selected_drugs = self.__let_user_select_drugs() if selected_drugs is None: return None data_src_pk = self.create_data_source_entry() new_drugs = [] new_substances = [] for entry in selected_drugs: _log.debug('importing drug: %s %s', entry['name'], entry['darreichungsform']) if entry['hilfsmittel']: _log.debug('skipping Hilfsmittel') continue if entry['erstattbares_medizinprodukt']: _log.debug('skipping sonstiges Medizinprodukt') continue # create drug product (or get it if it already exists) drug = gmMedication.create_drug_product(product_name = entry['name'], preparation = entry['darreichungsform']) if drug is None: drug = gmMedication.get_drug_by_name(product_name = entry['name'], preparation = entry['darreichungsform']) new_drugs.append(drug) # update fields drug['is_fake_product'] = False drug['atc'] = entry['atc'] drug['external_code_type'] = 'DE-PZN' drug['external_code'] = entry['pzn'] drug['fk_data_source'] = data_src_pk drug.save() # add components to drug atc = None # hopefully MMI eventually supports atc-per-substance in a drug... if len(entry['wirkstoffe']) == 1: atc = entry['atc'] for wirkstoff in entry['wirkstoffe']: drug.add_component(substance = wirkstoff, atc = atc) # create as substance doses, too atc = None # hopefully MMI eventually supports atc-per-substance in a drug... if len(entry['wirkstoffe']) == 1: atc = entry['atc'] for wirkstoff in entry['wirkstoffe']: #new_substances.append(gmMedication.create_substance_dose(substance = wirkstoff, atc = atc, amount = amount, unit = unit)) pass return new_drugs, new_substances
def import_drugs_as_substances(self)
-
Expand source code
def import_drugs_as_substances(self): selected_drugs = self.__let_user_select_drugs() if selected_drugs is None: return None new_substances = [] for drug in selected_drugs: atc = None # hopefully MMI eventually supports atc-per-substance in a drug... if len(drug['wirkstoffe']) == 1: atc = drug['atc'] for wirkstoff in drug['wirkstoffe']: #new_substances.append(gmMedication.create_substance_dose(substance = wirkstoff, atc = atc, amount = amount, unit = unit)) print(atc) pass selected_drugs.close() return new_substances
def show_info_on_drug(self, drug=None)
-
Expand source code
def show_info_on_drug(self, drug=None): self.switch_to_frontend(blocking = True)
def show_info_on_substance(self, substance=None)
-
Expand source code
def show_info_on_substance(self, substance=None): cmd = None if substance.external_code_type == 'DE-PZN': cmd = '%s -PZN %s' % (self.path_to_binary, substance.external_code) if cmd is None: name = gmTools.coalesce ( substance['drug_product'], substance['substance'] ) cmd = '%s -NAME %s' % (self.path_to_binary, name) # better to clean up interactions file open(self.interactions_filename, 'wb').close() self.switch_to_frontend(cmd = cmd)
def switch_to_frontend(self, blocking=False, cmd=None)
-
Expand source code
def switch_to_frontend(self, blocking=False, cmd=None): try: # must make sure csv file exists open(self.default_csv_filename, 'wb').close() except IOError: _log.exception('problem creating GL/MMI <-> GNUmed exchange file') return False if cmd is None: cmd = ('%s %s' % (self.path_to_binary, self.args)) % self.default_csv_filename_arg if os.name == 'nt': blocking = True if not gmShellAPI.run_command_in_shell(command = cmd, blocking = blocking): _log.error('problem switching to the MMI drug database') # apparently on the first call MMI does not # consistently return 0 on success # return False return True
class cGelbeListeWineInterface
-
Support v8.2 CSV file interface only.
Expand source code
class cGelbeListeWineInterface(cGelbeListeWindowsInterface): def __init__(self): cGelbeListeWindowsInterface.__init__(self) _log.info('%s (WINE extension)', cGelbeListeWindowsInterface.version) # FIXME: if -CLOSETOTRAY is used GNUmed cannot detect the end of MMI self.path_to_binary = r'wine "C:\Programme\MMI PHARMINDEX\glwin.exe"' self.args = r'"-PRESCRIPTIONFILE %s -KEEPBACKGROUND"' paths = gmTools.gmPaths() self.default_csv_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'windows', 'temp', 'mmi2gm.csv') self.default_csv_filename_arg = r'c:\windows\temp\mmi2gm.csv' self.interactions_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'windows', 'temp', 'gm2mmi.bdt') self.data_date_filename = os.path.join(paths.home_dir, '.wine', 'drive_c', 'Programme', 'MMI PHARMINDEX', 'datadate.txt')
Ancestors
Inherited members
class cIfapInterface
-
empirical CSV interface
Expand source code
class cIfapInterface(cDrugDataSourceInterface): """empirical CSV interface""" def __init__(self): pass def print_transfer_file(self, filename=None): try: csv_file = open(filename, mode = 'rt', encoding = 'latin1') # FIXME: encoding correct ? except Exception: _log.exception('cannot access [%s]', filename) csv_file = None field_names = 'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split() if csv_file is None: return False csv_lines = csv.DictReader ( csv_file, fieldnames = field_names, delimiter = ';' ) for line in csv_lines: print("--------------------------------------------------------------------"[:31]) for key in field_names: tmp = ('%s ' % key)[:30] print('%s: %s' % (tmp, line[key])) csv_file.close()
Ancestors
Methods
def print_transfer_file(self, filename=None)
-
Expand source code
def print_transfer_file(self, filename=None): try: csv_file = open(filename, mode = 'rt', encoding = 'latin1') # FIXME: encoding correct ? except Exception: _log.exception('cannot access [%s]', filename) csv_file = None field_names = 'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split() if csv_file is None: return False csv_lines = csv.DictReader ( csv_file, fieldnames = field_names, delimiter = ';' ) for line in csv_lines: print("--------------------------------------------------------------------"[:31]) for key in field_names: tmp = ('%s ' % key)[:30] print('%s: %s' % (tmp, line[key])) csv_file.close()