diff --git a/.gitignore b/.gitignore index 5d381cc..4cb5c72 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,9 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +*bin/ +avenv +lib64 +pyvenv.cfg +*.swp diff --git a/FormulariesMatching/FormulariesMatching/__init__.py b/FormulariesMatching/FormulariesMatching/__init__.py new file mode 100644 index 0000000..53f2575 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/__init__.py @@ -0,0 +1,50 @@ +from flask import (Flask,render_template) +import os +from .db_interface import get_connection_details, check_connection + + + + + + +def create_app(test_config=None): + # create and configure the app + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + SECRET_KEY='6e674d6e41b733270fd01c6257b3a1b4769eb80f3f773cd0fe8eff25f350fc1f', + POSTGRES_DB="aact_db", + POSTGRES_USER="root", + POSTGRES_HOST="localhost", + POSTGRES_PORT=5432, + POSTGRES_PASSWORD="root", + ) + + + # ensure the instance folder exists + try: + os.makedirs(app.instance_path) + except OSError: + pass + + # a simple page that says hello + @app.route('/') + def hello(): + return render_template("index.html") + + + @app.route('/debug') + def debug_info(): + return { + "connection": get_connection_details(), + } + + + + from . import db_interface + #db_interface.check_connection(app) + + from . import formularies_matching + app.register_blueprint(formularies_matching.bp) + + + return app diff --git a/FormulariesMatching/FormulariesMatching/db_interface.py b/FormulariesMatching/FormulariesMatching/db_interface.py new file mode 100644 index 0000000..93f5fe9 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/db_interface.py @@ -0,0 +1,83 @@ +import psycopg as psyco +from datetime import datetime +from flask import current_app,g + + +def get_db(**kwargs): + + if "db" not in g: + g.db = psyco.connect( + dbname=current_app.config["POSTGRES_DB"] + ,user=current_app.config["POSTGRES_USER"] + ,host=current_app.config["POSTGRES_HOST"] + ,port=current_app.config["POSTGRES_PORT"] + ,password=current_app.config["POSTGRES_PASSWORD"] + ,**kwargs + ) + return g.db + +def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + +def get_connection_details(): + return { + "dbname":current_app.config["POSTGRES_DB"] + ,"user":current_app.config["POSTGRES_USER"] + ,"host":current_app.config["POSTGRES_HOST"] + ,"port":current_app.config["POSTGRES_PORT"] + ,"password":current_app.config["POSTGRES_PASSWORD"] + } + +def check_connection(app): + db = get_db() + with db.cursor() as curse: + curse.execute("select count(*) from \"DiseaseBurden\".trial_to_icd10") + curse.fetchall() + #just checking if everything is going to fail + return True + + + +def get_trial_summary(db_conn,nct_id): + sql_summary =""" +select + s.nct_id, + brief_title , + official_title , + bs.description as brief_description, + dd.description as detailed_description +from ctgov.studies s + left join ctgov.brief_summaries bs + on bs.nct_id = s.nct_id + left join ctgov.detailed_descriptions dd + on dd.nct_id = s.nct_id +where s.nct_id = %s +; +""" + sql_conditions=""" +--conditions mentioned +select * from ctgov.conditions c +where c.nct_id = %s +; +""" + sql_keywords=""" +select nct_id ,downcase_name +from ctgov.keywords k +where k.nct_id = %s +; +""" + with db_conn.cursor() as curse: + curse.execute(sql_summary,[nct_id]) + summary = curse.fetchall() + + curse.execute(sql_keywords,[nct_id]) + keywords = curse.fetchall() + + curse.execute(sql_conditions,[nct_id]) + conditions = curse.fetchall() + + return {"summary":summary, "keywords":keywords, "conditions":conditions} + diff --git a/FormulariesMatching/FormulariesMatching/formularies_db.py.template b/FormulariesMatching/FormulariesMatching/formularies_db.py.template new file mode 100644 index 0000000..e4732b9 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/formularies_db.py.template @@ -0,0 +1,25 @@ +import psycopg as psyco +from datetime import datetime + +from flask import current_app, g + + + + +def get_all_formulary_groups(db_conn): + ''' + Get the list of active formulary groups + TODO: IMplement for the given formulary + ''' + pass + +def get_formulary_groups_per_NCTID(db_conn, nct_id): + ''' + Get the list of formulary groups associated with + the drugs found in a trial identified by NCTID + ''' + pass + + +def store_trial_to_formulary_group_matches(): + pass diff --git a/FormulariesMatching/FormulariesMatching/formularies_matching.py b/FormulariesMatching/FormulariesMatching/formularies_matching.py new file mode 100644 index 0000000..8c9d3fe --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/formularies_matching.py @@ -0,0 +1,47 @@ +import functools +from flask import (Blueprint, flash, g, redirect, render_template, request, session, url_for) +from datetime import datetime + +from FormulariesMatching.db_interface import ( + get_db, + get_connection_details, + get_trial_summary, + ) + + + +#setup blueprint +bp = Blueprint("formularies validation", __name__, url_prefix='/link/formularies') + +@bp.route("/", methods=['GET']) +def get_remaining(): + #get db connection + #db_conn = get_db() + + #get required data + connection_valid = get_connection_details() + + #return html + return connection_valid + + +@bp.route("/", methods=['GET',"POST"]) +def match_trial_to_formulary_groups(nct_id): + #get db connection + db_conn = get_db() + + if request.method == "GET": + pass + #get list of potential matches for each of the formularies + #get trial summary + summary = get_trial_summary(db_conn,nct_id) + + #render template + return summary + + elif request.method == "POST": + pass + #build array of data to insert + + else: + pass diff --git a/FormulariesMatching/FormulariesMatching/icd10_db.py b/FormulariesMatching/FormulariesMatching/icd10_db.py new file mode 100644 index 0000000..523ee52 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/icd10_db.py @@ -0,0 +1,175 @@ +import psycopg2 as psyco +from psycopg2 import extras +from datetime import datetime + +import click #used for cli commands. Not needed for what I am doing. +from flask import current_app, g + +def get_db(**kwargs): + + if "db" not in g: + g.db = psyco.connect( + dbname=current_app.config["POSTGRES_DB"] + ,user=current_app.config["POSTGRES_USER"] + ,host=current_app.config["POSTGRES_HOST"] + ,port=current_app.config["POSTGRES_PORT"] + ,password=current_app.config["POSTGRES_PASSWORD"] + ,**kwargs + ) + return g.db + +def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + +def check_initialization(app): + db = get_db() + with db.cursor() as curse: + curse.execute("select count(*) from \"DiseaseBurden\".trial_to_icd10") + curse.fetchall() + #just checking if everything is going to fail + +def init_database(app): + #check_initialization(app) + app.teardown_appcontext(close_db) + + + + +def select_remaing_trials_to_analyze(db_conn): + ''' + This will get the set of trials that need to be analyzed. + ''' + sql = ''' + select distinct nct_id + from "DiseaseBurden".trial_to_icd10 tti + where tti.approved is null + order by nct_id + ; + ''' + with db_conn.cursor() as cursor: + cursor.execute(sql) + return cursor.fetchall() + + +def select_analyzed_trials(db_conn): + ''' + This will get the set of trials that have been analyzed. + ''' + sql = ''' + select distinct nct_id, max(approval_timestamp) + from "DiseaseBurden".trial_to_icd10 tti + where tti.approved in ('accepted','rejected') + group by nct_id + order by max(approval_timestamp) desc + ; + ''' + with db_conn.cursor() as cursor: + cursor.execute(sql) + return cursor.fetchall() + +def select_unmatched_trials(db_conn): + ''' + This will get the set of trials that have been analyzed. + ''' + sql = ''' + select distinct nct_id + from "DiseaseBurden".trial_to_icd10 tti + where tti.approved = 'unmatched' + order by nct_id + ; + ''' + with db_conn.cursor() as cursor: + cursor.execute(sql) + return cursor.fetchall() + + +def get_trial_conditions_and_proposed_matches(db_conn, nct_id): + sql = ''' + select * + from "DiseaseBurden".trial_to_icd10 tti + where nct_id = %s + ''' + with db_conn.cursor() as cursor: + cursor.execute(sql,[nct_id]) + return cursor.fetchall() + + +def store_validation(db_conn, list_of_insert_data): + sql = """ + update "DiseaseBurden".trial_to_icd10 + set approved=%s, approval_timestamp=%s + where id=%s + ; + """ + with db_conn.cursor() as cursor: + for l in list_of_insert_data: + cursor.execute(sql, l) + db_conn.commit() + +def get_trial_summary(db_conn,nct_id): + sql_summary =""" +select + s.nct_id, + brief_title , + official_title , + bs.description as brief_description, + dd.description as detailed_description +from ctgov.studies s + left join ctgov.brief_summaries bs + on bs.nct_id = s.nct_id + left join ctgov.detailed_descriptions dd + on dd.nct_id = s.nct_id +where s.nct_id = %s +; +""" + sql_conditions=""" +--conditions mentioned +select * from ctgov.conditions c +where c.nct_id = %s +; +""" + sql_keywords=""" +select nct_id ,downcase_name +from ctgov.keywords k +where k.nct_id = %s +; +""" + with db_conn.cursor() as curse: + curse.execute(sql_summary,[nct_id]) + summary = curse.fetchall() + + curse.execute(sql_keywords,[nct_id]) + keywords = curse.fetchall() + + curse.execute(sql_conditions,[nct_id]) + conditions = curse.fetchall() + + return {"summary":summary, "keywords":keywords, "conditions":conditions} + +def get_list_icd10_codes(db_conn): + sql = """ + select distinct code + from "DiseaseBurden".icd10_to_cause itc + order by code; + """ + with db_conn.cursor() as curse: + curse.execute(sql) + codes = curse.fetchall() + + return [ x[0] for x in codes ] + +def record_suggested_matches(db_conn, nct_id,condition,icd10_code): + sql1 = """ + INSERT INTO "DiseaseBurden".trial_to_icd10 + (nct_id,"condition",ui,"source",approved,approval_timestamp) + VALUES (%s,%s,%s,'hand matched','accepted',%s) + ; + """ + + + with db_conn.cursor() as curse: + curse.execute(sql1,[nct_id,condition,icd10_code,datetime.now()]) + db_conn.commit() diff --git a/FormulariesMatching/FormulariesMatching/templates/.formulary_index.html.swp b/FormulariesMatching/FormulariesMatching/templates/.formulary_index.html.swp new file mode 100644 index 0000000..4543b25 Binary files /dev/null and b/FormulariesMatching/FormulariesMatching/templates/.formulary_index.html.swp differ diff --git a/FormulariesMatching/FormulariesMatching/templates/base.html b/FormulariesMatching/FormulariesMatching/templates/base.html new file mode 100644 index 0000000..15a7859 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/templates/base.html @@ -0,0 +1,22 @@ + +{% block title %}{% endblock %} - ClinicalTrialsProject + + + + +
+
+ {% block header %}{% endblock %} +
+ {% block content %}{% endblock %} +
diff --git a/FormulariesMatching/FormulariesMatching/templates/formulary_index.html b/FormulariesMatching/FormulariesMatching/templates/formulary_index.html new file mode 100644 index 0000000..a141319 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/templates/formulary_index.html @@ -0,0 +1,49 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %} ICD-10 to Trial Conditions Validation {% endblock %}

+{% endblock %} + +{% block content %} + +

Trials to Validate

+ + + +{% for trial in list_to_validate %} + +{% endfor %} +
Trials
+ + {{ trial [0] }} + +
+ +

Trials that have been Validated

+ + + +{% for trial in validated_list %} + +{% endfor %} +
Trials Links
+ + {{ trial [0] }} + + (Most recently updated {{trial[1]}}) +
+ +

Trials that don't have a good match

+ + + +{% for trial in unmatched_list %} + +{% endfor %} +
Trial Links
+ + {{ trial [0] }} + +
+ +{% endblock %} diff --git a/FormulariesMatching/FormulariesMatching/templates/icd10_index.html b/FormulariesMatching/FormulariesMatching/templates/icd10_index.html new file mode 100644 index 0000000..a141319 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/templates/icd10_index.html @@ -0,0 +1,49 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %} ICD-10 to Trial Conditions Validation {% endblock %}

+{% endblock %} + +{% block content %} + +

Trials to Validate

+ + + +{% for trial in list_to_validate %} + +{% endfor %} +
Trials
+ + {{ trial [0] }} + +
+ +

Trials that have been Validated

+ + + +{% for trial in validated_list %} + +{% endfor %} +
Trials Links
+ + {{ trial [0] }} + + (Most recently updated {{trial[1]}}) +
+ +

Trials that don't have a good match

+ + + +{% for trial in unmatched_list %} + +{% endfor %} +
Trial Links
+ + {{ trial [0] }} + +
+ +{% endblock %} diff --git a/FormulariesMatching/FormulariesMatching/templates/index.html b/FormulariesMatching/FormulariesMatching/templates/index.html new file mode 100644 index 0000000..bde5a58 --- /dev/null +++ b/FormulariesMatching/FormulariesMatching/templates/index.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} + +{% block header %} +

+{% block title %} +Formulary Matching +{% endblock %} +

+{% endblock %} + + + + +{% block content %} + +This is the home page for matching things to clinical trials + +There are a few major efforts related to matching. + +The first is to match trials to IDC10 codes + +The second is to link trials to formulary groups. + +{% endblock %} diff --git a/FormulariesMatching/requirements.txt b/FormulariesMatching/requirements.txt new file mode 100644 index 0000000..3b12535 --- /dev/null +++ b/FormulariesMatching/requirements.txt @@ -0,0 +1,6 @@ +flask +psycopg[binary] +datetime +watchdog +waitress +python-dotenv diff --git a/FormulariesMatching/setup.py b/FormulariesMatching/setup.py new file mode 100644 index 0000000..08e5e24 --- /dev/null +++ b/FormulariesMatching/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +setup( + name='FormulariesMatching', + packages=['FormulariesMatching'], + include_package_data=True, + install_requires=[ + 'flask', + 'psycopg[binary]', + 'datetime', + ], +) diff --git a/FormulariesMatching/start.sh b/FormulariesMatching/start.sh new file mode 100755 index 0000000..6868a58 --- /dev/null +++ b/FormulariesMatching/start.sh @@ -0,0 +1 @@ +waitress-serve --port=5000 --call 'Icd10ConditionsMatching:create_app' diff --git a/sample.env b/sample.env index 6e2f542..d708fb6 100644 --- a/sample.env +++ b/sample.env @@ -1,6 +1,6 @@ #Postgres Info -POSTGRES_HOST= +POSTGRES_HOST=will-office POSTGRES_DB=aact_db POSTGRES_PORT=5432 POSTGRES_USER=root