Compare commits

...

11 Commits

34 changed files with 1104 additions and 593 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
virtualenv
version.sh

173
Makefile
View File

@@ -1,90 +1,85 @@
MAJOR:=$(shell bash -c 'source version.sh ; echo $$MAJOR')
BUILD:=$(shell bash -c 'source version.sh ; echo $$BUILD')
OS_NAME:=$(shell bash -c 'source version.sh ; echo $$OS_NAME')
ifeq "$(OS_NAME)" "win"
PIP=$(shell pwd)/virtualenv/Scripts/pip
VIRTUALENV_PKGS_DIR=$(shell pwd)/virtualenv/Lib/site-packages
else
PIP=$(shell pwd)/virtualenv/bin/pip
VIRTUALENV_PKGS_DIR=$(shell pwd)/virtualenv/lib/site-packages
endif
VIRTUALENV=$(shell which virtualenv)
PYTHON=$(shell which python)
PYTHON_FILES=setup.py mercy/version.py $(shell find mercy -iname "*py")
PYTHON_SDIST=./dist/mercy-$(MAJOR)-$(BUILD).tar.gz
.PHONY: clean
clean:
rm -fr dist/*
find mercy -iname "*pyc" -exec rm -vf \{\} \;
cd puppet && make clean
############## Targets for puppet module
puppet/version.sh: version.sh
cp version.sh $@
.PHONY: puppet
puppet: puppet_dist
.PHONY: puppet_dist
puppet_dist: puppet/version.sh
cd puppet && make dist
.PHONY: puppet_install
puppet_install: puppet/version.sh
cd puppet && make install
.PHONY: puppet_uninstall
puppet_uninstall: puppet/version.sh
cd puppet && make uninstall
################ /puppet module
############### Targets for python app
dist: $(PYTHON_SDIST) puppet
sdist: $(PYTHON_SDIST)
mercy/version.py: version.sh
bash -c 'source version.sh && echo "VERSION=\"$${MAJOR}-$${BUILD}\"" > $@'
$(PYTHON_SDIST): $(PYTHON_FILES)
$(PYTHON) setup.py sdist --formats=gztar
uninstall:
rm -fr $(VIRTUALENV_PKGS_DIR)/mercy-* || echo 'not installed'
install: $(PYTHON_SDIST) virtualenv
$(PIP) install $(PYTHON_SDIST) --upgrade
.PHONY: virtualenv
virtualenv:
if [ ! -e $(PIP) ]; then \
$(VIRTUALENV) --no-site-packages --distribute virtualenv ; \
fi
################## /python app
################## Targets for supporting development work
databases: databases/fda_ndc.zip databases/drugbank.xml.zip
.PHONY: databases/fda_ndc.zip
databases/fda_ndc.zip:
rm -fr databases/fda_ndc*
mkdir -p databases/fda_ndc
rm -f fda_ndc.zip
DBPAGE=$$(wget -O - http://www.fda.gov/Drugs/InformationOnDrugs/default.htm --quiet | grep -Eo 'National Drug Code Directory Search(</strong>)*(</a>)*(<br />)*<a href="[a-zA-Z0-9\:/\.]+">More information about the database</a>' | cut -d \" -f 2); \
LINK=$$(wget -O - http://www.fda.gov/$${DBPAGE} --quiet | grep ">NDC Database File" | cut -d \" -f 2) ; \
wget -O $@ http://www.fda.gov/$${LINK}
cd databases/fda_ndc && unzip -e ../fda_ndc.zip
.PHONY: databases/drugbank.xml.zip
databases/drugbank.xml.zip:
rm -fr databases/drugbank*
mkdir -p databases/drugbank
wget -O $@ http://www.drugbank.ca/system/downloads/current/drugbank.xml.zip
MAJOR:=$(shell bash -c 'source version.sh ; echo $$MAJOR')
BUILD:=$(shell bash -c 'source version.sh ; echo $$BUILD')
OS_NAME:=$(shell bash -c 'source version.sh ; echo $$OS_NAME')
PIP=$(shell pwd)/virtualenv/bin/pip
VIRTUALENV_PKGS_DIR=$(shell pwd)/virtualenv/lib/python2.7/site-packages
VIRTUALENV=$(shell which virtualenv)
PYTHON=$(shell which python)
PYTHON_FILES=setup.py mercy/version.py $(shell find mercy -iname "*py")
PYTHON_SDIST=./dist/mercy-$(MAJOR)-$(BUILD).tar.gz
.PHONY: clean
clean:
rm -fr dist/*
find mercy -iname "*pyc" -exec rm -vf \{\} \;
cd puppet && make clean
############## Targets for puppet module
puppet/version.sh: version.sh
cp version.sh $@
.PHONY: puppet
puppet: puppet_dist
.PHONY: puppet_dist
puppet_dist: puppet/version.sh
cd puppet && make dist
.PHONY: puppet_install
puppet_install: puppet/version.sh
cd puppet && make install
.PHONY: puppet_uninstall
puppet_uninstall: puppet/version.sh
cd puppet && make uninstall
################ /puppet module
############### Targets for python app
dist: $(PYTHON_SDIST) puppet
sdist: $(PYTHON_SDIST)
mercy/version.py: version.sh
bash -c 'source version.sh && echo "VERSION=\"$${MAJOR}-$${BUILD}\"" > $@'
$(PYTHON_SDIST): $(PYTHON_FILES)
$(PYTHON) setup.py sdist --formats=gztar
uninstall:
rm -fr $(VIRTUALENV_PKGS_DIR)/mercy-* || echo 'not installed'
install: $(PYTHON_SDIST) virtualenv
$(PIP) install $(PYTHON_SDIST) --upgrade
.PHONY: virtualenv
virtualenv:
if [ ! -e $(PIP) ]; then \
$(VIRTUALENV) --no-site-packages --distribute virtualenv ; \
fi
################## /python app
################## Targets for supporting development work
databases: databases/fda_ndc.zip databases/drugbank.xml.zip
.PHONY: databases/fda_ndc.zip
databases/fda_ndc.zip:
rm -fr databases/fda_ndc*
mkdir -p databases/fda_ndc
rm -f fda_ndc.zip
DBPAGE=$$(wget -O - http://www.fda.gov/Drugs/InformationOnDrugs/default.htm --quiet | grep -Eo 'National Drug Code Directory Search(</strong>)*(</a>)*(<br />)*<a href="[a-zA-Z0-9\:/\.]+">More information about the database</a>' | cut -d \" -f 2); \
LINK=$$(wget -O - http://www.fda.gov/$${DBPAGE} --quiet | grep ">NDC Database File" | cut -d \" -f 2) ; \
wget -O $@ http://www.fda.gov/$${LINK}
cd databases/fda_ndc && unzip -e ../fda_ndc.zip
.PHONY: databases/drugbank.xml.zip
databases/drugbank.xml.zip:
rm -fr databases/drugbank*
mkdir -p databases/drugbank
wget -O $@ http://www.drugbank.ca/system/downloads/current/drugbank.xml.zip
cd databases/drugbank && unzip -e ../drugbank.xml.zip

View File

@@ -1,28 +1,31 @@
mercy
=====
Mercy is a web application designed to facilitate payments between people who need prescriptions but can't afford them, and charitable people who can afford them
The Idea
========
* Patient: "Here's my prescription, Mr Pharmacist"
* Pharmacist: "Sure thing. That'll be $150 USD."
* Patient: "I can't afford that! Can you submit it to Mercy for me?"
* Pharmacist: "Sure thing. We'll call you when it gets filled."
... Meanwhile, back at stately Charitable Person Manor ...
* Charitable Person's Phone: *DING*
* Charitable Person: "Huh? Oh! Mercy is telling me there's a new prescription I can pay for. I love helping people!"
* Charitable person clicks some buttons
* Charitable Person: "*CLICK* Paid! Enjoy some good health, stranger. Now to post it on my facebook wall and collect the new achievement for buying my first Leukemia drug..."
... Back at the pharmacy ...
* Pharmacist : "Hello, Patient? I just wanted to let you know that your prescription is filled and has been paid in full."
* Patient: "That's so great! I'll come pick it up right away. Now I won't die because my medication is too *!#&$# expensive!"
Then everyone rides dinosaurs into the sunset, happy and healthy. It's pretty awesome.
(That's the idea anyway. WE'll see if reality follows.)
mercy
=====
This project comprises the entirety of The Mercy Project; everything needed to deploy and serve The Mercy Project currently lives in this repository.
If you came here looking for more information about what (in general) The Mercy Project is, please see http://www.mercyproject.us for more information
Python Application
===================
The mercy/ directory contains all of the python code used to serve the Mercy website and perform all necessary functions.
Alembic ORM database upgrade scripts
====================================
The alembic/ directory contains scripts used by the alembic flask extension to automatically upgrade and downgrade the postgres database used by Mercy.
Puppet Modules for Deployment
==============================
The puppet code necessary to deploy Mercy onto Linux servers lives in the puppet/ directory.
Nagios Checks for Monitoring the Mercy Platform
===============================================
These live in the nagios/ directory.
Automated Test Suites
=====================
tests/ currently holds any and all automated testing (Python Nosetests, currently) for all of the code listed above.

View File

@@ -1,49 +1,49 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
sqlalchemy.url = postgresql://mercy:mercy@postgresql.aklabs.net/mercy
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
sqlalchemy.url = postgresql://mercy:mercy@postgresql.aklabs.net/mercy
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -1,71 +1,79 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engine = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata
)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
import mercy.MercyApplication
import mercy.models
import mercy.config
db = mercy.MercyApplication.get_db()
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = db.Model.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
alembic_config = config.get_section(config.config_ini_section)
alembic_config['sqlalchemy.url'] = mercy.config.SQLALCHEMY_URI
engine = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata
)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

Binary file not shown.

View File

@@ -0,0 +1,158 @@
"""Initial schema
Revision ID: 2b64ad923738
Revises: None
Create Date: 2013-10-27 11:46:11.475707
"""
# revision identifiers, used by Alembic.
revision = '2b64ad923738'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('fda_products',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('productid', sa.String(), index=True, unique=True, nullable=False),
sa.Column('ndc', sa.String(), index=True, nullable=False),
sa.Column('type', sa.String(), nullable=False),
sa.Column('proprietaryName', sa.String(), nullable=False),
sa.Column('proprietaryNameSuffix', sa.String(), nullable=True),
sa.Column('genericName', sa.String(), nullable=False),
sa.Column('marketingCategoryName', sa.String(), nullable=False),
sa.Column('labelerName', sa.String(), nullable=False),
sa.Column('deaSchedule', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('drugbank_packagers',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('url', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('drugbank_manufacturers',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('fda_pharma_classes',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('fda_product_substances',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('drugbank_categories',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('fda_pharma_class_maps',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('product_id', sa.Integer(), nullable=False),
sa.Column('pharma_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['pharma_id'], ['fda_pharma_classes.id'], ),
sa.ForeignKeyConstraint(['product_id'], ['fda_products.id'], ),
sa.PrimaryKeyConstraint('id', 'pharma_id')
)
op.create_table('drugbank_drugs',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('dbid', sa.String(), unique=True, nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('indication', sa.String(), nullable=False),
sa.Column('fda_product_id', sa.String(), nullable=True),
sa.Column('wikipedia', sa.String(), nullable=True),
sa.ForeignKeyConstraint(['fda_product_id'], ['fda_products.productid'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('fda_product_substance_map',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('product_id', sa.Integer(), nullable=False),
sa.Column('substance_id', sa.Integer(), nullable=False),
sa.Column('quantity', sa.Float(), nullable=False),
sa.Column('units', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['product_id'], ['fda_products.id'], ),
sa.ForeignKeyConstraint(['substance_id'], ['fda_product_substances.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('drugbank_synonyms',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('drug_id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['drug_id'], ['drugbank_drugs.id'], ),
sa.PrimaryKeyConstraint('drug_id')
)
op.create_table('drugbank_packager_maps',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('drug_id', sa.Integer(), nullable=False),
sa.Column('packager_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['drug_id'], ['drugbank_drugs.id'], ),
sa.ForeignKeyConstraint(['packager_id'], ['drugbank_packagers.id'], ),
sa.PrimaryKeyConstraint('drug_id')
)
op.create_table('drugbank_genericnames',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('drug_id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['drug_id'], ['drugbank_drugs.id'], ),
sa.PrimaryKeyConstraint('drug_id')
)
op.create_table('drugbank_prices',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('drug_id', sa.Integer(), nullable=False),
sa.Column('description', sa.String(), nullable=False),
sa.Column('currency', sa.String(), nullable=False),
sa.Column('cost', sa.Float(), nullable=False),
sa.Column('unit', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['drug_id'], ['drugbank_drugs.id'], ),
sa.PrimaryKeyConstraint('drug_id')
)
op.create_table('drugbank_manufacturer_maps',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('drug_id', sa.Integer(), nullable=False),
sa.Column('manufacturer_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['drug_id'], ['drugbank_drugs.id'], ),
sa.ForeignKeyConstraint(['manufacturer_id'], ['drugbank_manufacturers.id'], ),
sa.PrimaryKeyConstraint('drug_id')
)
op.create_table('drugbank_category_maps',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('drug_id', sa.Integer(), nullable=False),
sa.Column('category_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['category_id'], ['drugbank_categories.id'], ),
sa.ForeignKeyConstraint(['drug_id'], ['drugbank_drugs.id'], ),
sa.PrimaryKeyConstraint('drug_id')
)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('drugbank_category_maps')
op.drop_table('drugbank_manufacturer_maps')
op.drop_table('drugbank_prices')
op.drop_table('drugbank_genericnames')
op.drop_table('drugbank_packager_maps')
op.drop_table('drugbank_synonyms')
op.drop_table('fda_product_substance_map')
op.drop_table('drugbank_drugs')
op.drop_table('fda_pharma_class_maps')
op.drop_table('drugbank_categories')
op.drop_table('fda_product_substances')
op.drop_table('fda_pharma_classes')
op.drop_table('fda_products')
op.drop_table('drugbank_manufacturers')
op.drop_table('drugbank_packagers')
### end Alembic commands ###

View File

@@ -1,100 +0,0 @@
"""create fda_product table
Revision ID: 58f6a99bd6ec
Revises: None
Create Date: 2013-10-19 21:21:03.977000
"""
# revision identifiers, used by Alembic.
revision = '58f6a99bd6ec'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'fda_products',
sa.Column('id', sa.String, primary_key=True),
sa.Column('ndc', sa.String, nullable=False),
sa.Column('type', sa.String, nullable=False),
sa.Column('proprietaryName', sa.String, nullable=False, index=True),
sa.Column('proprietaryNameSuffix', sa.String),
sa.Column('genericName', sa.String, nullable=False),
sa.Column('marketingCategoryName', sa.String, nullable=False),
sa.Column('labelerName', sa.String, nullable=False),
sa.Column('deaSchedule', sa.String, nullable=False)
)
op.create_table(
'fda_product_substances',
sa.Column('fda_product_id',
sa.String,
sa.ForeignKey('fda_products.id'),
nullable=False),
sa.Column('substanceName', sa.String, nullable=False),
sa.Column('strengthNumber', sa.String, nullable=False),
sa.Column('strengthUnit', sa.String, nullable=False),
sa.Column('pharmaClasses', sa.String, nullable=False)
)
op.create_table(
'drugbank_drugs',
sa.Column('id', sa.String, primary_key=True, unique=True),
sa.Column('name', sa.String, nullable=False, index=True),
sa.Column('indication', sa.String, nullable=False),
sa.Column('ndc_id', sa.String, sa.ForeignKey('fda_products.id'), nullable=True),
sa.Column('wikipedia', sa.String, nullable=True)
)
op.create_table(
'drugbank_prices',
sa.Column('id', sa.String, sa.ForeignKey('drugbank_drugs.id'), nullable=False),
sa.Column('description', sa.String, nullable=False),
sa.Column('currency', sa.String, nullable=False),
sa.Column('cost', sa.Float, nullable=False, index=True),
sa.Column('unit', sa.String, nullable=False)
)
op.create_table(
'drugbank_categories',
sa.Column('id', sa.Integer, primary_key=True, autoincrement=True),
sa.Column('name', sa.String, nullable=False)
)
op.create_table(
'drugbank_drug_categories',
sa.Column('id', sa.String, sa.ForeignKey('drugbank_drugs.id'), nullable=False),
sa.Column('category_id', sa.Integer, sa.ForeignKey('drugbank_categories.id'), nullable=False)
)
op.create_table(
'drugbank_packagers',
sa.Column('id', sa.String, sa.ForeignKey('drugbank_drugs.id'), nullable=False),
sa.Column('name', sa.String, nullable=False),
sa.Column('url', sa.String, nullable=False)
)
op.create_table(
'drugbank_manufacturers',
sa.Column('id', sa.String, sa.ForeignKey('drugbank_drugs.id'), nullable=False),
sa.Column('name', sa.String, nullable=False),
sa.Column('generic', sa.Boolean, nullable=False)
)
op.create_table(
'drugbank_genericnames',
sa.Column('id', sa.String, sa.ForeignKey('drugbank_drugs.id'), nullable=False),
sa.Column('name', sa.String, index=True, nullable=False)
)
op.create_table(
'drugbank_synonyms',
sa.Column('id', sa.String, sa.ForeignKey('drugbank_drugs.id'), nullable=False),
sa.Column('name', sa.String, index=True, nullable=False)
)
def downgrade():
op.drop_table("drugbank_synonyms")
op.drop_table("drugbank_genericnames")
op.drop_table("drugbank_manufacturers")
op.drop_table("drugbank_packagers")
op.drop_table("drugbank_drug_categories")
op.drop_table("drugbank_categories")
op.drop_table("drugbank_prices")
op.drop_table("drugbank_drugs")
op.drop_table("fda_product_substances")
op.drop_table("fda_products")

View File

@@ -1,4 +1,22 @@
import flask
class MercyApplication(flask.Flask):
pass
import flask
import mercy.config
from flask.ext.sqlalchemy import SQLAlchemy
class MercyApplication(flask.Flask):
pass
app = None
db = None
def get_db():
global db
if not db:
db = SQLAlchemy(get_app())
return db
def get_app():
global app
if not app:
app = MercyApplication("mercy")
app.config['SQLALCHEMY_DATABASE_URI'] = mercy.config.SQLALCHEMY_URI
return app

1
mercy/config.py Normal file
View File

@@ -0,0 +1 @@
SQLALCHEMY_URI = 'postgresql://mercy:mercy@postgresql.aklabs.net/mercy'

3
mercy/exceptions.py Normal file
View File

@@ -0,0 +1,3 @@
class CorruptTarError(Exception):
pass

View File

View File

@@ -0,0 +1,31 @@
import mercy.MercyApplication
import xml.dom.pulldom
from mercy.models.drugbank import *
import sqlalchemy.exc
import xpath
class DrugBankImporter:
def __init__(self, *args, **kwargs):
self.__db = mercy.MercyApplication.get_db()
def _saveobj(self, obj):
self.__db.session.add(obj)
self.__db.session.commit()
def read(self, fname):
events = xml.dom.pulldom.parse(fname)
for event, node in events:
if event == xml.dom.pulldom.START_ELEMENT and node.tagName == 'drug':
events.expandNode(node)
self.__convert_drug(node)
def __convert_drug(self, node):
drug = Drug()
drug.name = xpath.findvalue('name', node)
drug.indication = xpath.findvalue('indication', node)
drug.fda_product_id = xpath.findvalue('external-identifiers/external-identifier[starts-with(resource, "National Drug Code Directory")]/identifier', node)
drug.wikipedia = xpath.findvalue('external-links/external-link[starts-with(resource, "Wikipedia")]/url', node)
if not drug.fda_product_id:
return
print str(drug)
#self._saveobj(drug)

107
mercy/importers/fda.py Normal file
View File

@@ -0,0 +1,107 @@
import mercy.MercyApplication
import xml.etree.cElementTree as ET
from mercy.models.fda import Product
from mercy.models.fda import ProductSubstance
from mercy.models.fda import ProductSubstanceMap
from mercy.models.fda import PharmaceuticalClass
from mercy.models.fda import PharmaceuticalClassMap
import sqlalchemy.exc
import csv
class FDAImporter:
def __init__(self, *args, **kwargs):
self.__db = mercy.MercyApplication.get_db()
def read(self, fname, startIdx=0):
with open(fname, "r") as ifile:
reader = csv.DictReader(ifile, delimiter="\t")
idx = 0
for row in reader:
if idx < startIdx:
print "Skipping from {} to {}".format(idx, startIdx)
idx += 1
continue
retries = 0
print "{} : {}".format(idx, row)
while retries < 3 :
try:
self._convert_row(row)
retries = 3
except sqlalchemy.exc.DatabaseError, e:
retries += 1
if retries == 3:
raise e
else:
continue
idx += 1
def _saveobj(self, obj):
self.__db.session.add(obj)
self.__db.session.commit()
def _convert_row(self, row):
# The FDA CSV is HIGHLY irregular. This function is needlessly large because of that/
product = Product.query.filter_by(productid=row['PRODUCTID']).first()
if not product:
product = Product()
product.productid = row['PRODUCTID']
product.ndc = row['PRODUCTNDC']
product.type = row['PRODUCTTYPENAME']
product.proprietaryName = row['PROPRIETARYNAME']
product.proprietaryNameSuffix = row['PROPRIETARYNAMESUFFIX']
product.genericName = row['NONPROPRIETARYNAME']
product.marketingCategoryName = row['MARKETINGCATEGORYNAME']
product.labelerName = row['LABELERNAME']
product.deaSchedule = (row['DEASCHEDULE'] if row['DEASCHEDULE'] else '')
self._saveobj(product)
# Strip the substances off of the product and make objects for them
substanceNames = [x.strip().lstrip() for x in row['SUBSTANCENAME'].split(';')]
tmpQtys = [x.strip().lstrip() for x in row['ACTIVE_NUMERATOR_STRENGTH'].split(';')]
# The list addition here is to make sure we have an equal number of values in
# quantities as we do in substance names, because the FDA CSV does not ensure
# that all substance names have a quantity or unit listed. So unknown quantities
# get entered as '0', unknown units of measure become '?'.
substanceQtys = [(float(x) if x else 0.0) for x in tmpQtys] + ([0] * (len(substanceNames) - len(tmpQtys)))
substanceUnits = [x.strip().lstrip() for x in row['ACTIVE_INGRED_UNIT'].split(';')]
substanceUnits += (['?'] * (len(substanceNames) - len(substanceUnits)))
for idx in range(0, len(substanceNames)):
substance = None
substanceName = substanceNames[idx]
substance = ProductSubstance.query.filter_by(name=substanceName).first()
if not substance:
substance = ProductSubstance()
substance.name = substanceName
self._saveobj(substance)
substanceMap = ProductSubstanceMap.query.filter_by(product_id=product.id,
substance_id=substance.id,
quantity=substanceQtys[idx],
units=substanceUnits[idx]).first()
if not substanceMap:
substanceMap = ProductSubstanceMap()
substanceMap.product_id = product.id
substanceMap.substance_id = substance.id
substanceMap.quantity = substanceQtys[idx]
substanceMap.units = substanceUnits[idx]
self._saveobj(substanceMap)
pharmaClassList = row.get('PHARM_CLASSES')
pharmaClasses = [x.strip().lstrip() for x in (pharmaClassList if pharmaClassList else '').split(',')]
for pharmaClass in pharmaClasses:
pharmaObj = PharmaceuticalClass.query.filter_by(name=pharmaClass).first()
if not pharmaObj:
pharmaObj = PharmaceuticalClass()
pharmaObj.name = pharmaClass
self._saveobj(pharmaObj)
mapObj = PharmaceuticalClassMap.query.filter_by(
product_id=product.id,
pharma_id=pharmaObj.id).first()
if not mapObj:
mapObj = PharmaceuticalClassMap()
mapObj.product_id = product.id
mapObj.pharma_id = pharmaObj.id
self._saveobj(mapObj)
return

3
mercy/models/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
import simplemodel
import fda
import drugbank

78
mercy/models/drugbank.py Normal file
View File

@@ -0,0 +1,78 @@
import sqlalchemy as sa
from mercy.models.simplemodel import SimpleModel
import mercy.MercyApplication
from mercy.models.fda import Product
import sqlalchemy.dialects.postgresql as pgdialect
db = mercy.MercyApplication.get_db()
class Drug(SimpleModel, db.Model):
__tablename__ = "drugbank_drugs"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
dbid = sa.Column(sa.String, index=True, unique=True)
name = sa.Column(sa.String, nullable=False, index=True)
indication = sa.Column(sa.String, nullable=False)
fda_product_id = sa.Column(sa.String, sa.ForeignKey(Product.productid), nullable=True)
wikipedia = sa.Column(sa.String, nullable=True)
__repr_keys__ = { 'id': basestring,
'name': basestring,
'fda_product_id': basestring,
'wikipedia': basestring
}
class Price(SimpleModel, db.Model):
__tablename__ = "drugbank_prices"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
drug_id = sa.Column(sa.Integer, sa.ForeignKey(Drug.id), nullable=False)
description = sa.Column(sa.String, nullable=False)
currency = sa.Column(sa.String, nullable=False)
cost = sa.Column(sa.Float, nullable=False, index=True)
unit = sa.Column(sa.String, nullable=False)
class CategoryName(SimpleModel, db.Model):
__tablename__ = "drugbank_categories"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
name = sa.Column(sa.String, nullable=False, unique=True)
class CategoryMap(SimpleModel, db.Model):
__tablename__ = "drugbank_category_maps"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
drug_id = sa.Column(sa.Integer, sa.ForeignKey(Drug.id), nullable=False)
category_id = sa.Column(sa.Integer, sa.ForeignKey(CategoryName.id), nullable=False)
class Packager(SimpleModel, db.Model):
__tablename__ = "drugbank_packagers"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
name = sa.Column(sa.String, nullable=False, unique=True)
url = sa.Column(sa.String, nullable=True)
class PackagerMap(SimpleModel, db.Model):
__tablename__ = "drugbank_packager_maps"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), nullable=False)
packager_id = sa.Column(sa.Integer, sa.ForeignKey(Packager.id), nullable=False)
class Manufacturer(SimpleModel, db.Model):
__tablename__ = "drugbank_manufacturers"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
name = sa.Column(sa.String, nullable=False, unique=True)
class ManufacturerMap(SimpleModel, db.Model):
__tablename__ = "drugbank_manufacturer_maps"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
drug_id = sa.Column(sa.Integer, sa.ForeignKey(Drug.id), nullable=False)
manufacturer_id = sa.Column(sa.Integer, sa.ForeignKey(Manufacturer.id), nullable=False)
class GenericName(SimpleModel, db.Model):
__tablename__ = "drugbank_genericnames"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
drug_id = sa.Column(sa.Integer, sa.ForeignKey(Drug.id), nullable=False)
name = sa.Column(sa.String, nullable=False)
class Synonym(SimpleModel, db.Model):
__tablename__ = "drugbank_synonyms"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
drug_id = sa.Column(sa.Integer, sa.ForeignKey(Drug.id), nullable=False)
name = sa.Column(sa.String, nullable=False)

63
mercy/models/fda.py Normal file
View File

@@ -0,0 +1,63 @@
import sqlalchemy as sa
from mercy.models.simplemodel import SimpleModel
import mercy.MercyApplication
import sqlalchemy.dialects.postgresql as pgdialect
db = mercy.MercyApplication.get_db()
class Product(SimpleModel, db.Model):
__tablename__ = 'fda_products'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
productid = sa.Column(sa.String, index=True, unique=True, nullable=False)
ndc = sa.Column(sa.String, index=True, nullable=False)
type = sa.Column(sa.String, nullable=False)
proprietaryName = sa.Column(sa.String, nullable=False, index=True)
proprietaryNameSuffix = sa.Column(sa.String)
genericName = sa.Column(sa.String, nullable=False, index=True)
marketingCategoryName = sa.Column(sa.String, nullable=False)
labelerName = sa.Column(sa.String, nullable=False)
deaSchedule = sa.Column(sa.String, nullable=False)
__repr_keys__ = { 'id': basestring,
'ndc': basestring,
'genericName': basestring,
'proprietaryName': basestring,
'proprietaryNameSuffix': basestring}
class ProductSubstance(SimpleModel, db.Model):
__tablename__ = 'fda_product_substances'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
name = sa.Column(sa.String, nullable=False)
class ProductSubstanceMap(SimpleModel, db.Model):
__tablename__ = 'fda_product_substance_map'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
product_id = sa.Column(sa.Integer,
sa.ForeignKey(Product.id),
nullable=False)
substance_id = sa.Column(sa.Integer,
sa.ForeignKey(ProductSubstance.id),
nullable=False,
index=True)
quantity = sa.Column(sa.Float, nullable=False)
units = sa.Column(sa.String, nullable=False)
class PharmaceuticalClass(SimpleModel, db.Model):
__tablename__ = "fda_pharma_classes"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
name = sa.Column(sa.String, nullable=False, unique=True)
class PharmaceuticalClassMap(SimpleModel, db.Model):
__tablename__ = "fda_pharma_class_maps"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True, nullable=False)
product_id = sa.Column(sa.Integer,
sa.ForeignKey(Product.id),
nullable=False)
pharma_id = sa.Column(sa.Integer,
sa.ForeignKey(PharmaceuticalClass.id),
primary_key=True,
nullable=False)

View File

@@ -0,0 +1,26 @@
import mercy.MercyApplication
db = mercy.MercyApplication.get_db()
class SimpleModel():
def __init__(self, *args, **kwargs):
db.Model.__init__(self, *args, **kwargs)
for (k, v) in kwargs.iteritems():
if hasattr(self, k):
setattr(self, k, v)
else:
raise AttributeError("Invalid attribute {} => {}".format(k, v))
def __repr__(self, *args, **kwargs):
try:
getattr(self.__class__, "__repr_keys__")
except AttributeError, e:
return db.Model.__repr__(self, *args, **kwargs)
values = []
for (name, otype) in self.__class__.__repr_keys__.iteritems():
if otype == basestring:
values.append("'{}'".format(str(getattr(self, name))))
else:
values.append(str(getattr(self, name)))
return "<{}({})>".format(self.__class__.__name__, ', '.join(values))

View File

@@ -1 +1 @@
VERSION="0.0-0"
VERSION="0.0-1"

View File

@@ -1,4 +1,4 @@
mercy/nagios
============
mercy/nagios
============
This is a directory full of nagios plugins meant to monitor the Mercy application's health.

View File

@@ -1,32 +1,32 @@
MAJOR:=$(shell bash -c 'source version.sh ; echo $$MAJOR')
BUILD:=$(shell bash -c 'source version.sh ; echo $$BUILD')
MODULEPATH:=$(shell puppet config print modulepath)
ifeq ($(MODULEPATH),)
$(error "I can't figure out your puppet modulepath, this will cause all kinds of bad things to happen with 'make install' and 'make uninstall'")
endif
PUPPET_DEPS=$(find mercy -type f)
PUPPET_DIST=mercy/pkg/akesterson-mercy-$(MAJOR).$(BUILD).tar.gz
.PHONY: clean
clean:
rm pkg/*
rm Modulefile
mercy/Modulefile: Modulefile.template.sh version.sh
source version.sh && bash Modulefile.template.sh > Modulefile
$(PUPPET_DIST): $(PUPPET_DEPS) mercy/Modulefile
rm -f $(PUPPET_DIST)
mkdir -p $$(dirname $(PUPPET_DIST))
echo 'We would use puppet module tool here but we dont because it doesnt work on windows buildhosts and cant upload from the command line anyway'
tar --exclude=mercy/Modulefile --exclude=mercy/pkg -czvf $(PUPPET_DIST) mercy/
dist: $(PUPPET_DIST)
all: $(PUPPET_DIST)
install: $(PUPPET_DIST)
tar -zxvf $(PUPPET_DIST) -C $(MODULEPATH)
uninstall:
MAJOR:=$(shell bash -c 'source version.sh ; echo $$MAJOR')
BUILD:=$(shell bash -c 'source version.sh ; echo $$BUILD')
MODULEPATH:=$(shell puppet config print modulepath)
ifeq ($(MODULEPATH),)
$(error "I can't figure out your puppet modulepath, this will cause all kinds of bad things to happen with 'make install' and 'make uninstall'")
endif
PUPPET_DEPS=$(find mercy -type f)
PUPPET_DIST=mercy/pkg/akesterson-mercy-$(MAJOR).$(BUILD).tar.gz
.PHONY: clean
clean:
rm pkg/*
rm Modulefile
mercy/Modulefile: Modulefile.template.sh version.sh
source version.sh && bash Modulefile.template.sh > Modulefile
$(PUPPET_DIST): $(PUPPET_DEPS) mercy/Modulefile
rm -f $(PUPPET_DIST)
mkdir -p $$(dirname $(PUPPET_DIST))
echo 'We would use puppet module tool here but we dont because it doesnt work on windows buildhosts and cant upload from the command line anyway'
tar --exclude=mercy/Modulefile --exclude=mercy/pkg -czvf $(PUPPET_DIST) mercy/
dist: $(PUPPET_DIST)
all: $(PUPPET_DIST)
install: $(PUPPET_DIST)
tar -zxvf $(PUPPET_DIST) -C $(MODULEPATH)
uninstall:
rm -fr $(MODULEPATH)/mercy

View File

@@ -1,12 +1,12 @@
#!/bin/bash
cat <<EOF
name 'akesterson-mercy'
version '${MAJOR}-${BUILD}'
dependency 'apache/apache'
summary 'Puppet module for deploying the mercy web application'
description 'Puppet module for deploying the mercy web application'
project_page http://github.com/akesterson/mercy/tree/master/puppet
license 'MIT'
author 'Andrew Kesterson <andrew@aklabs.net>'
EOF
#!/bin/bash
cat <<EOF
name 'akesterson-mercy'
version '${MAJOR}-${BUILD}'
dependency 'apache/apache'
summary 'Puppet module for deploying the mercy web application'
description 'Puppet module for deploying the mercy web application'
project_page http://github.com/akesterson/mercy/tree/master/puppet
license 'MIT'
author 'Andrew Kesterson <andrew@aklabs.net>'
EOF

View File

@@ -1,49 +1,49 @@
mercy
=====
A puppet module for installing the Mercy Flask application
Requirements
============
This puppet module assumes that you have:
* A RabbitMQ broker that we can use for Celery tasks
* Working apache on localhost, with mod_wsgi enabled and with a puppet service we can notify
* Working python 2.7 and pip on localhost
* Working postgres with a username, password, and database for mercy to use
Aside from that, the module has no dependencies.
Usage
=========
class { 'mercy':
# ---- These are required, they have no defaults
environment => 'dev|production',
version => 'absent|latest|MAJOR-MINOR',
ensure => 'running|stopped'.
rabbitmq_uri => 'RABBITMQ_BROKER_URI',
# ---- Everything below is optional
process_user => 'mercy',
process_group => 'mercy',
process_threads => 5,
servername => $::fqdn,
rabbitmq_user => 'mercy',
rabbitmq_pw => 'mercy',
rabbitmq_vhost => 'mercy',
vhost_dir => '/etc/apache/httpd/conf.d',
apache_service => 'httpd',
port => 443,
postgres_uri => 'localhost',
postgres_user => 'mercy',
postgres_pw => 'mercy',
postgres_db => 'mercy'
}
If 'environment' is dev, then the mercy application will be installed via a tarball located in ./mercy/files/mercy-${ensure}.tar.gz. If 'environment' is production, then mercy will be installed via pip and ensured at the given version.
Events
======
This puppet module will notify your apache service whenever the vhost is modified, or the mercy application version is updated, since an apache graceful restart will be required in either case.
mercy
=====
A puppet module for installing the Mercy Flask application
Requirements
============
This puppet module assumes that you have:
* A RabbitMQ broker that we can use for Celery tasks
* Working apache on localhost, with mod_wsgi enabled and with a puppet service we can notify
* Working python 2.7 and pip on localhost
* Working postgres with a username, password, and database for mercy to use
Aside from that, the module has no dependencies.
Usage
=========
class { 'mercy':
# ---- These are required, they have no defaults
environment => 'dev|production',
version => 'absent|latest|MAJOR-MINOR',
ensure => 'running|stopped'.
rabbitmq_uri => 'RABBITMQ_BROKER_URI',
# ---- Everything below is optional
process_user => 'mercy',
process_group => 'mercy',
process_threads => 5,
servername => $::fqdn,
rabbitmq_user => 'mercy',
rabbitmq_pw => 'mercy',
rabbitmq_vhost => 'mercy',
vhost_dir => '/etc/apache/httpd/conf.d',
apache_service => 'httpd',
port => 443,
postgres_uri => 'localhost',
postgres_user => 'mercy',
postgres_pw => 'mercy',
postgres_db => 'mercy'
}
If 'environment' is dev, then the mercy application will be installed via a tarball located in ./mercy/files/mercy-${ensure}.tar.gz. If 'environment' is production, then mercy will be installed via pip and ensured at the given version.
Events
======
This puppet module will notify your apache service whenever the vhost is modified, or the mercy application version is updated, since an apache graceful restart will be required in either case.

View File

@@ -1,22 +1,22 @@
class mercy(
$environment,
$version,
$ensure,
$rabbitmq_uri,
$process_user => 'mercy',
$process_group => 'mercy',
$process_threads => 5,
$servername => $::fqdn,
$rabbitmq_user => 'mercy',
$rabbitmq_pw => 'mercy',
$rabbitmq_vhost => 'mercy',
$vhost_dir => '/etc/apache/httpd/conf.d',
$apache_service => 'httpd',
$port => 443,
$postgres_uri => 'localhost',
$postgres_user => 'mercy',
$postgres_pw => 'mercy',
$postgres_db => 'mercy')
{
include 'mercy::params'
}
class mercy(
$environment,
$version,
$ensure,
$rabbitmq_uri,
$process_user => 'mercy',
$process_group => 'mercy',
$process_threads => 5,
$servername => $::fqdn,
$rabbitmq_user => 'mercy',
$rabbitmq_pw => 'mercy',
$rabbitmq_vhost => 'mercy',
$vhost_dir => '/etc/apache/httpd/conf.d',
$apache_service => 'httpd',
$port => 443,
$postgres_uri => 'localhost',
$postgres_user => 'mercy',
$postgres_pw => 'mercy',
$postgres_db => 'mercy')
{
include 'mercy::params'
}

View File

@@ -1,4 +1,4 @@
class mercy::params
{
}
class mercy::params
{
}

View File

@@ -1,12 +1,12 @@
<VirtualHost * :<%= scope.lookupvar('::mercy::port') %>>
ServerName <%= scope.lookupvar('::fqdn') %>
WSGIDaemonProcess mercy user=<%= scope.lookupvar('::mercy::process_user') %> group=<%= scope.lookupvar('::mercy::process_group') %> threads=<%= scope.lookupvar('::mercy::process_threads') %>
WSGIScriptAlias / /opt/mercy/scripts/mercy.wsgi
<Directory /opt/mercy>
WSGIProcessGroup mercy
WSGIApplicationGroup %{GLOBAL}
Order deny, allow
Allow from all
</Directory>
<VirtualHost * :<%= scope.lookupvar('::mercy::port') %>>
ServerName <%= scope.lookupvar('::fqdn') %>
WSGIDaemonProcess mercy user=<%= scope.lookupvar('::mercy::process_user') %> group=<%= scope.lookupvar('::mercy::process_group') %> threads=<%= scope.lookupvar('::mercy::process_threads') %>
WSGIScriptAlias / /opt/mercy/scripts/mercy.wsgi
<Directory /opt/mercy>
WSGIProcessGroup mercy
WSGIApplicationGroup %{GLOBAL}
Order deny, allow
Allow from all
</Directory>
</VirtualHost>

View File

@@ -1,49 +1,49 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = /opt/mercy/alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
sqlalchemy.url = postgresql://<%= scope.lookupvar('::mercy::postgres_user') %>:<%+ scope.lookupvar('::mercy::postgres_pw') %>@<%= scope.lookupvar('::mercy::postgres_uri') %>/<%= scope.lookupvar('::mercy::postgres_db') %>
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = /opt/mercy/alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
sqlalchemy.url = postgresql://<%= scope.lookupvar('::mercy::postgres_user') %>:<%+ scope.lookupvar('::mercy::postgres_pw') %>@<%= scope.lookupvar('::mercy::postgres_uri') %>/<%= scope.lookupvar('::mercy::postgres_db') %>
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
import sys
import mercy.importers.drugbank
sys.exit(mercy.importers.drugbank.DrugBankImporter().read(sys.argv[1]))

10
scripts/mercy-import-fda Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import sys
import mercy.importers.fda
startIdx = 0
if ( len(sys.argv) >= 3 ):
startIdx = int(sys.argv[2])
sys.exit(mercy.importers.fda.FDAImporter().read(sys.argv[1], startIdx=startIdx))

View File

@@ -1,14 +1,6 @@
from mercy.MercyApplication import MercyApplication
class ScriptNameStripper(object):
def __init__(self, app):
self.app = app
def __call_(self, environ, start_response):
environ['SCRIPT_NAME'] = ''
return self.app(environ, start_response)
app = ScriptNameStripper(MercyApplication())
if __name__ == "__main__":
app.run()
import mercy.MercyApplication
app = get_application()
if __name__ == "__main__":
app.run()

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python
from mercy.MercyApplication import MercyApplication
if __name__ == "__main__":
app = MercyApplication("mercy")
app.run()
#!/usr/bin/env python
from mercy.MercyApplication import app
if __name__ == "__main__":
app.run()

View File

@@ -1,34 +1,38 @@
from distutils.core import setup
import mercy.version
import os
import sys
if __name__ == "__main__":
setup(
name="mercy",
url="https://www.github.com/akesterson/mercy",
version=mercy.version.VERSION,
description="A flask application that facilitates paying for prescriptions",
long_description="",
author=("Andrew Kesterson"),
author_email="andrew@aklabs.net",
license="MIT",
install_requires=["flask",
"sqlalchemy",
"alembic",
"psycopg2"],
scripts=[],
packages=["mercy"],
data_files=[],
classifiers=[
'Development Status :: 1 - Planning',
'Environment :: Web Environment',
'Framework :: Flask',
'Intended Audience :: Healthcare Industry',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 2.7',
'Topic :: Other/Nonlisted Topic',
],
)
from distutils.core import setup
import mercy.version
import os
import sys
if __name__ == "__main__":
setup(
name="mercy",
url="https://www.github.com/akesterson/mercy",
version=mercy.version.VERSION,
description="A web application that facilitates charitable prescription payments",
long_description="",
author=("Andrew Kesterson"),
author_email="andrew@aklabs.net",
license="MIT",
install_requires=["flask",
"sqlalchemy",
"alembic",
"psycopg2",
"flask-sqlalchemy",
"py-dom-xpath"],
scripts=["scripts/mercy.wsgi"],
packages=["mercy",
"mercy/models",
"mercy/importers"],
data_files=[],
classifiers=[
'Development Status :: 1 - Planning',
'Environment :: Web Environment',
'Framework :: Flask',
'Intended Audience :: Healthcare Industry',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 2.7',
'Topic :: Other/Nonlisted Topic',
],
)

View File

@@ -0,0 +1,120 @@
import os
import nose
from nose.tools import raises
import mercy.models
import mercy.importers.fda
import mercy.exceptions
COMPARISON_KEYS={
'productid': 'PRODUCTID',
'ndc': 'PRODUCTNDC',
'type': 'PRODUCTTYPENAME',
'proprietaryName': 'PROPRIETARYNAME',
'proprietaryNameSuffix': 'PROPRIETARYNAMESUFFIX',
'genericName': 'NONPROPRIETARYNAME',
'marketingCategoryName': 'MARKETINGCATEGORYNAME',
'labelerName': 'LABELERNAME',
'deaSchedule': 'DEASCHEDULE'
}
CSV_KEYS=[
'PRODUCTID',
'PRODUCTNDC',
'PRODUCTTYPENAME',
'PROPRIETARYNAME',
'PROPRIETARYNAMESUFFIX',
'NONPROPRIETARYNAME',
'DOSAGEFORMNAME',
'ROUTENAME',
'STARTMARKETINGDATE',
'ENDMARKETINGDATE',
'MARKETINGCATEGORYNAME',
'APPLICATIONNUMBER',
'LABELERNAME',
'SUBSTANCENAME',
'ACTIVE_NUMERATOR_STRENGTH',
'ACTIVE_INGRED_UNIT',
'PHARM_CLASSES',
'DEASCHEDULE'
]
CANNED_ROWS=[
{'PRODUCTID': '0002-3230_b1642902-4a44-495a-8790-39598b168276',
'PRODUCTNDC': '0002-3230',
'PRODUCTTYPENAME': 'HUMAN PRESCRIPTION DRUG',
'PROPRIETARYNAME': 'Symbyax',
'PROPRIETARYNAMESUFFIX': '',
'NONPROPRIETARYNAME': 'Olanzapine and Fluoxetine hydrochloride',
'DOSAGEFORMNAME': 'CAPSULE',
'ROUTENAME': 'ORAL',
'STARTMARKETINGDATE': '20070409',
'ENDMARKETINGDATE': '',
'MARKETINGCATEGORYNAME': 'NDA',
'APPLICATIONNUMBER': 'NDA021520',
'LABELERNAME': 'Eli Lilly and Company',
'SUBSTANCENAME': 'FLUOXETINE HYDROCHLORIDE; OLANZAPINE',
'ACTIVE_NUMERATOR_STRENGTH': '25; 3',
'ACTIVE_INGRED_UNIT': 'mg/1; mg/1',
'PHARM_CLASSES': 'Atypical Antipsychotic [EPC],Serotonin Reuptake Inhibitor [EPC],Serotonin Uptake Inhibitors [MoA]',
'DEASCHEDULE': ''
},
{'PRODUCTID': '0002-3231_b1642902-4a44-495a-8790-39598b168276',
'PRODUCTNDC': '0002-3231',
'PRODUCTTYPENAME': 'HUMAN PRESCRIPTION DRUG',
'PROPRIETARYNAME': 'Symbyax',
'PROPRIETARYNAMESUFFIX': '',
'NONPROPRIETARYNAME': 'Olanzapine and Fluoxetine hydrochloride',
'DOSAGEFORMNAME': 'CAPSULE',
'ROUTENAME': 'ORAL',
'STARTMARKETINGDATE': '20070409',
'ENDMARKETINGDATE': '',
'MARKETINGCATEGORYNAME': 'NDA',
'APPLICATIONNUMBER': 'NDA021521',
'LABELERNAME': 'Eli Lilly and Company',
'SUBSTANCENAME': 'FLUOXETINE HYDROCHLORIDE; OLANZAPINE',
'ACTIVE_NUMERATOR_STRENGTH': '25; 6',
'ACTIVE_INGRED_UNIT': 'mg/1; mg/1',
'PHARM_CLASSES': 'Atypical Antipsychotic [EPC],Serotonin Reuptake Inhibitor [EPC],Serotonin Uptake Inhibitors [MoA]',
'DEASCHEDULE': ''
}
]
FIXTUREFILE=os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..",
"fixtures",
"fda_database.txt"
)
)
FIXTUREFILE_BAD=os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..",
"fixtures",
"fda_database_bad.txt"
)
)
def test_fda_import_populates_table():
with open(FIXTUREFILE, 'w') as ofile:
ofile.write("{}\n".format('\t'.join(CSV_KEYS)))
for row in CANNED_ROWS:
values = []
for key in CSV_KEYS:
values.append(row[key])
ofile.write("{}\n".format('\t'.join(values)))
importer = mercy.importers.fda.FDAImporter()
importer.read(FIXTUREFILE)
for row in CANNED_ROWS:
product = mercy.models.fda.Product.query.filter_by(productid = row['PRODUCTID']).first()
assert(product)
for (k, v) in COMPARISON_KEYS.iteritems():
assert(getattr(product, k) == row[v])
mapquery = mercy.models.fda.ProductSubstanceMap.query
substanceMaps = [x for x in mapquery.filter_by(product_id=product.id)]
assert(len(substanceMaps) == len(row['ACTIVE_NUMERATOR_STRENGTH'].split(';')))
# TO DO : This test doesn't look at the contents of the
# substances or substance maps, only that the right
# number of substance maps come out

View File

@@ -1,15 +0,0 @@
TAG="build,0.0,0"
BRANCH="master"
MAJOR="0.0"
BUILD="0"
SHA1="9b83e26ec3d0e3d1526a4226a8ac0c8580a77bcf"
OS_NAME="${OS_NAME:-win}"
OS_VERSION="${OS_VERSION:-}"
ARCH="${ARCH:-i686}"
VERSION="0.0-0"
BUILDHOST="akesterson-pc"
BUILDUSER="akesterson-pc\akesterson"
BUILDDIR="/c/Users/akesterson/source/upstream/git/akesterson/mercy"
SOURCE="git@github.com:akesterson/mercy.git"
REBUILDING=0
CHANGELOG=""