Working on #1 - FDA import process works. 3 hours for first import, 1.25 hours on subsequent updates.

This commit is contained in:
2013-10-27 18:35:35 -04:00
parent e0232aa02d
commit 0a7bf8f174
21 changed files with 759 additions and 472 deletions

View File

@@ -1,13 +1,8 @@
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
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)

View File

@@ -1,4 +1,22 @@
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

View File

@@ -1,8 +1,107 @@
import mercy.db
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.__database = mercy.db.Database()
self.__db = mercy.MercyApplication.get_db()
def read(self, fname):
raise Exception("FDAImporter.read doesn't do anything yet")
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

View File

@@ -1,6 +1,7 @@
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()
@@ -8,10 +9,11 @@ db = mercy.MercyApplication.get_db()
class Drug(SimpleModel, db.Model):
__tablename__ = "drugbank_drugs"
id = sa.Column(sa.String, primary_key=True, unique=True)
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)
ndc_id = sa.Column(sa.String, sa.ForeignKey('fda_products.id'), nullable=True)
fda_product_id = sa.Column(sa.String, sa.ForeignKey(Product.productid), nullable=True)
wikipedia = sa.Column(sa.String, nullable=True)
__repr_keys__ = { 'id': basestring,
@@ -21,7 +23,8 @@ class Drug(SimpleModel, db.Model):
class Price(SimpleModel, db.Model):
__tablename__ = "drugbank_prices"
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), primary_key=True, nullable=False)
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)
@@ -30,40 +33,45 @@ class Price(SimpleModel, db.Model):
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)
name = sa.Column(sa.String, nullable=False, unique=True)
class CategoryMap(SimpleModel, db.Model):
__tablename__ = "drugbank_category_maps"
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), primary_key=True, nullable=False)
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)
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"
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), primary_key=True, nullable=False)
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)
name = sa.Column(sa.String, nullable=False, unique=True)
class ManufacturerMap(SimpleModel, db.Model):
__tablename__ = "drugbank_manufacturer_maps"
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), primary_key=True, nullable=False)
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"
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), primary_key=True, nullable=False)
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"
drug_id = sa.Column(sa.String, sa.ForeignKey(Drug.id), primary_key=True, nullable=False)
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)

View File

@@ -8,8 +8,9 @@ db = mercy.MercyApplication.get_db()
class Product(SimpleModel, db.Model):
__tablename__ = 'fda_products'
id = sa.Column(sa.String, primary_key=True)
ndc = sa.Column(sa.String, nullable=False)
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)
@@ -27,11 +28,36 @@ class Product(SimpleModel, db.Model):
class ProductSubstance(SimpleModel, db.Model):
__tablename__ = 'fda_product_substances'
fda_product_id = sa.Column(sa.String,
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)
substanceName = sa.Column(sa.String, nullable=False)
strengthNumber = sa.Column(sa.Float, nullable=False)
strengthUnit = sa.Column(sa.String, nullable=False)
pharmaClasses = sa.Column(pgdialect.ARRAY(sa.String), nullable=False)

View File

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

View File

@@ -1,14 +1,6 @@
from mercy.MercyApplication import MercyApplication
import mercy.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())
app = get_application()
if __name__ == "__main__":
app.run()

View File

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

View File

@@ -8,7 +8,7 @@ if __name__ == "__main__":
name="mercy",
url="https://www.github.com/akesterson/mercy",
version=mercy.version.VERSION,
description="A flask application that facilitates paying for prescriptions",
description="A web application that facilitates charitable prescription payments",
long_description="",
author=("Andrew Kesterson"),
author_email="andrew@aklabs.net",
@@ -16,9 +16,12 @@ if __name__ == "__main__":
install_requires=["flask",
"sqlalchemy",
"alembic",
"psycopg2"],
scripts=[],
packages=["mercy"],
"psycopg2",
"flask-sqlalchemy"],
scripts=["scripts/mercy.wsgi"],
packages=["mercy",
"mercy/models",
"mercy/importers"],
data_files=[],
classifiers=[
'Development Status :: 1 - Planning',

View File

@@ -5,52 +5,116 @@ import mercy.models
import mercy.importers.fda
import mercy.exceptions
VALID_ROWS=[]
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(
__file__,
os.path.dirname(__file__),
"..",
"fixtures",
"fda_database.tar.gz"
"fda_database.txt"
)
)
FIXTUREFILE_BAD=os.path.abspath(
os.path.join(
__file__,
os.path.dirname(__file__),
"..",
"fixtures",
"fda_database_bad.tar.gz"
"fda_database_bad.txt"
)
)
FIXTUREFILE_CORRUPT=os.path.abspath(
os.path.join(
__file__,
"..",
"fixtures",
"fda_database_corrupt.tar.gz"
)
)
@raises(mercy.exceptions.CorruptTarError)
def test_fda_import_fails_on_corrupt_tar():
importer = mercy.importers.fda.FDAImporter()
impoter.read(FIXTUREFILE_CORRUPT)
def test_fda_import_populates_table():
importer = FDAImporter().read(FIXTUREFILE)
rows = mercy.models.fda.Product.query.all()
for i in range(0, len(rows)):
row = rows[i]
canned_row = CANNED_ROWS[i]
assert(len(row) == len(canned_row))
for j in canned_row.keys():
assert(row[j] == canned_row[j])
@raises(AttributeError, KeyError, ValueError)
def test_fda_import_rejects_bad_records:
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_BAD)
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 +1,98 @@
TAG="build,0.0,0"
BRANCH="master"
MAJOR="0.0"
BUILD="0"
SHA1="9b83e26ec3d0e3d1526a4226a8ac0c8580a77bcf"
BUILD="1"
SHA1="e2bc6022649f3af82800d75ba876411f1ed83fa9"
OS_NAME="${OS_NAME:-win}"
OS_VERSION="${OS_VERSION:-}"
ARCH="${ARCH:-i686}"
VERSION="0.0-0"
ARCH="${ARCH:-x86_64}"
VERSION="0.0-1"
BUILDHOST="akesterson-pc"
BUILDUSER="akesterson-pc\akesterson"
BUILDDIR="/c/Users/akesterson/source/upstream/git/akesterson/mercy"
BUILDUSER="akesterson"
BUILDDIR="/cygdrive/c/Users/akesterson/source/upstream/git/akesterson/mercy"
SOURCE="git@github.com:akesterson/mercy.git"
REBUILDING=0
CHANGELOG=""
REBUILDING=1
CHANGELOG="2013-10-19 23:35:39 -0400 Andrew Kesterson <andrew@aklabs.net>
#6 : Make fda_products.genericName an index
[e2bc602] (HEAD, master)
2013-10-19 23:24:38 -0400 Andrew Kesterson <andrew@aklabs.net>
Merge branch 'master' of github.com:akesterson/mercy
[ecef739] (origin/master, origin/HEAD)
2013-10-19 23:23:53 -0400 Andrew Kesterson <andrew@aklabs.net>
Added downgrade to initial DB script
[803d120]
2013-10-19 23:08:23 -0400 Andrew Kesterson <andrew@aklabs.net>
Removed more junk
[1753230]
2013-10-19 23:06:23 -0400 Andrew Kesterson <andrew@aklabs.net>
Merge branch 'master' of www.aklabs.net:~/mercy/
[6b49f61]
2013-10-19 23:05:52 -0400 Andrew Kesterson <andrew@aklabs.net>
Removed abunch of junk
[8caeb4d]
2013-10-19 22:58:57 -0400 Andrew Kesterson <andrew@aklabs.net>
Working on #6. I think I should break out the puppet module stuff into a separate issue, since that risks really putting this all off target.
[dbf64e8]
2013-10-19 22:45:53 -0400 Andrew Kesterson <andrew@aklabs.net>
Midstream
[64fec8d]
2013-10-19 22:45:35 -0400 Andrew Kesterson <andrew@aklabs.net>
Midstream
[646df51]
2013-10-19 22:44:59 -0400 Andrew Kesterson <andrew@aklabs.net>
Midstream
[dcec82b]
2013-10-19 22:43:59 -0400 Andrew Kesterson <andrew@aklabs.net>
Midstream
[4332d08]
2013-10-19 22:30:49 -0400 Andrew Kesterson <andrew@aklabs.net>
Midstream
[c4f610a]
2013-10-19 22:28:46 -0400 Andrew Kesterson <andrew@aklabs.net>
Midstream
[56d46c5]
2013-10-19 17:59:56 -0400 Andrew Kesterson <andrew@aklabs.net>
Updated some design docs
[86e8db6]
2013-10-19 10:38:45 -0400 Andrew Kesterson <andrew@aklabs.net>
Added nagios directory
[45cfa0b]
2013-10-19 10:37:30 -0400 Andrew Kesterson <andrew@aklabs.net>
Added puppet module skeleton (also puppet module tool is lame)
[363d5ef]
2013-10-19 09:53:37 -0400 Andrew Kesterson <andrew@aklabs.net>
Initial commit
[9895e8e]"