diff --git a/Pipfile b/Pipfile
index 104c26d..d3f355e 100755
--- a/Pipfile
+++ b/Pipfile
@@ -14,10 +14,10 @@ certifi = "==2020.4.5.2"
cffi = "==1.14.0"
chardet = "==3.0.4"
click = "==7.1.2"
-cryptography = "==2.9.2"
+cryptography = ">=3.2.1"
cssutils = "==1.0.2"
diff-match-patch = "==20181111"
-httplib2 = "==0.18.1"
+httplib2 = ">=0.18.1"
idna = "==2.9"
itsdangerous = "==0.24"
kombu = "==4.6.10"
@@ -33,7 +33,7 @@ python-dateutil = "==2.8.1"
pytz = "==2017.2"
qrcode = "==6.1"
delta = {git = "https://github.com/forgeworks/quill-delta-python.git"}
-requests = "==2.23.0"
+requests = ">=2.23.0"
retrying = "==1.3.3"
six = "==1.15.0"
twilio = "==5.7.0"
@@ -41,24 +41,30 @@ urllib3 = "==1.25.9"
vine = "==1.3.0"
Cycler = "==0.10.0"
Cython = "==0.29.20"
-Flask = "==0.12.2"
+Flask = "==1.1.2"
+flask-caching = "==1.9.0"
Flask-Login = "==0.4.1"
Flask-Mail = "==0.9.1"
Flask-QRcode = "==3.0.0"
Flask-SQLAlchemy = "==2.3.2"
-Flask-WTF = "==0.14.2"
-Jinja2 = "==2.9.6"
+Flask-WTF = "==0.14.3"
+Jinja2 = ">=2.10.1"
MarkupSafe = "==1.1.1"
Pillow = "==7.1.2"
PyJWT = "==1.7.1"
PySocks = "==1.7.1"
-SQLAlchemy = "==1.3.17"
+SQLAlchemy = ">=1.3.0"
SQLAlchemy-Utils = "==0.36.5"
-Werkzeug = "==0.12.2"
-WTForms = "==2.3.1"
+Werkzeug = ">=1.0.1"
+WTForms = ">=2.3.1"
simplekv = "*"
-wtforms-sqlalchemy = "*"
-wtforms-alchemy = "*"
+scipy = "*"
+WTForms-SQLAlchemy = "*"
+WTForms-Alchemy = "*"
+pyOpenSSL = ">=19.1.0"
+importlib-metadata = "==2.0.0"
+flask-migrate = "*"
+email-validator = "*"
[requires]
python_version = "3.6"
diff --git a/run.py b/app.py
similarity index 100%
rename from run.py
rename to app.py
diff --git a/assets/fonts/OpenSans-Bold.woff b/assets/fonts/OpenSans-Bold.woff
new file mode 100644
index 0000000..9cad9cd
Binary files /dev/null and b/assets/fonts/OpenSans-Bold.woff differ
diff --git a/assets/fonts/OpenSans.woff b/assets/fonts/OpenSans.woff
new file mode 100644
index 0000000..f6b5751
Binary files /dev/null and b/assets/fonts/OpenSans.woff differ
diff --git a/assets/images/medinfo_icon.png b/assets/images/medinfo_icon.png
new file mode 100644
index 0000000..3c311a6
Binary files /dev/null and b/assets/images/medinfo_icon.png differ
diff --git a/assets/main.css b/assets/main.css
index 3424815..74bed8f 100755
--- a/assets/main.css
+++ b/assets/main.css
@@ -1,6 +1,7 @@
@font-face{
font-family: Neue_Helvetica;
src:url("/assets/fonts/neue_helvetica_reg.woff2");
+ font-display: swap;
}
body{
font-family: Neue_Helvetica;
@@ -64,7 +65,7 @@ select {
.img-circle {
horizontal-align:middle;
border-radius:50%;
- height:150px;
+ height:100px;
}
.col-lg-4 {
@@ -414,4 +415,4 @@ form tr {
border-style:none none none solid;
margin-left:120px;
padding:20px;
-}
\ No newline at end of file
+}
diff --git a/cleanup_sr.py b/cleanup_sr.py
new file mode 100644
index 0000000..f49ad15
--- /dev/null
+++ b/cleanup_sr.py
@@ -0,0 +1,32 @@
+from medtracker import *
+from sqlalchemy import func, desc
+import pandas as pd
+
+dbobj = models.SurveyResponse.query.all()
+
+objects = pd.DataFrame([i.to_dict() for i in dbobj])
+delete_count = 0
+to_delete = set()
+for name,group in objects.groupby("session_id"):
+ if len(group)>1:
+ group = group.sort_values("start_time",ascending=False)
+ print(group)
+ successful = group[(group["completed"]==1)|(group["exited"]==1)]
+ if len(successful)>0:
+ keep_id = group[(group["completed"]==1)|(group["exited"]==1)].iloc[0,].name
+ print("keeping ID:", keep_id)
+ else:
+ keep_id = None
+ for ix,row in group.iterrows():
+ if row.name==keep_id: continue
+ print("deleting ID: ",row.name)
+ to_delete.add(row.name)
+ delete_count += 1
+ else:
+ row = group.iloc[0,]
+ if (row["completed"]==False) & (row["exited"]==False):
+ print("deleting ID: ",row.name)
+ to_delete.add(row.name)
+ delete_count += 1
+
+delete_objs = [dbobj[i] for i in to_delete]
\ No newline at end of file
diff --git a/disable.py b/disable.py
new file mode 100644
index 0000000..57fc21d
--- /dev/null
+++ b/disable.py
@@ -0,0 +1,28 @@
+from medtracker import *
+from medtracker import database
+patients = models.Patient.query.all()
+devices = models.Device.query.all()
+
+kept = 0
+discarded = 0
+patients = models.Patient.query.all()
+for p in patients:
+ if p.deactivate==True:
+ continue
+ sr = [s.to_dict() for s in p.surveys]
+ df = pd.DataFrame(sr)
+ if len(df)>0:
+ if any(df.start_time > datetime.date(2020,8,1)):
+ kept += 1
+ p.deactivate = False
+ db.session.add(p)
+ else:
+ discarded += 1
+ p.deactivate = True
+ db.session.add(p)
+ else:
+ discarded += 1
+ p.deactivate = True
+ db.session.add(p)
+db.session.commit()
+print(kept,discarded)
diff --git a/medtracker/__init__.py b/medtracker/__init__.py
index 67da3b1..2544936 100755
--- a/medtracker/__init__.py
+++ b/medtracker/__init__.py
@@ -1,5 +1,5 @@
from flask import *
-from flask.ext.cache import Cache
+from flask_caching import Cache
from requests.auth import HTTPBasicAuth
import random, string, pytz, sys, random, urllib.parse, datetime, os
from werkzeug.utils import secure_filename
@@ -8,6 +8,8 @@
from flask_login import login_user, logout_user, current_user
from medtracker.config import *
from flask_qrcode import QRcode
+from flask_migrate import Migrate
+from flask_sqlalchemy import SQLAlchemy
import twilio.twiml
from twilio.rest import TwilioRestClient
@@ -18,6 +20,9 @@
from ftplib import FTP_TLS
from flask import flash
from itsdangerous import URLSafeTimedSerializer
+
+#from flask_debugtoolbar import DebugToolbarExtension
+
ts = URLSafeTimedSerializer(flask_secret_key)
#Flask init
@@ -32,7 +37,20 @@
app.config['SESSION_COOKIE_SECURE'] = False
app.config['SECRET_KEY'] = flask_secret_key
app.config['WTF_CSRF_ENABLED']=True
-app.debug = False
+app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
+#app.config["DEBUG_TB_PROFILER_ENABLED"] = True
+#app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False
+#app.debug = True
+#toolbar = DebugToolbarExtension(app)
+
+app.debug=False
+
+
+#init db
+db = SQLAlchemy(app)
+db_session = db.session
+
+migrate = Migrate(app,db,render_as_batch=True)
app.config.update(
#EMAIL SETTINGS
@@ -54,7 +72,6 @@
client = TwilioRestClient(twilio_AccountSID, twilio_AuthToken)
auth_combo=(twilio_AccountSID, twilio_AuthToken)
-from medtracker.database import db_session # to make sqlalchemy DB calls
from medtracker.views import * # web pages
from medtracker.triggers import *
diff --git a/medtracker/data/nyc_plots.py b/medtracker/data/nyc_plots.py
new file mode 100644
index 0000000..ab6df11
--- /dev/null
+++ b/medtracker/data/nyc_plots.py
@@ -0,0 +1,180 @@
+import pandas as pd
+import plotly.graph_objects as go
+import numpy as np
+import requests
+from io import StringIO
+
+def prepFigure(figure, title):
+ figure.update_xaxes(tickformat='%a
%b %d',
+ tick0 = '2020-03-22',
+ dtick = 7 * 24 * 3600000,
+ rangeselector=dict(
+ buttons=list([
+ dict(count=7, label="1w", step="day", stepmode="backward"),
+ dict(count=14, label="2w", step="day", stepmode="backward"),
+ dict(count=1, label="1m", step="month", stepmode="backward"),
+ dict(count=1, label="YTD", step="year", stepmode="todate"),
+ dict(step="all")
+ ])
+ )
+ )
+ figure.update_layout(title="" + title + "", hovermode="x",
+ legend_orientation="h",
+ margin={"r":10,"l":30})
+ return figure
+
+start_date = "2020-03-01"
+
+#confirmed cases
+read_headers = {
+ "sec-ch-ua": "\"Chromium\";v=\"92\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"92\"",
+ "sec-ch-ua-mobile": "?0",
+ "upgrade-insecure-requests": "1"
+ }
+url = "https://static.usafacts.org/public/data/covid-19/covid_confirmed_usafacts.csv"
+req = requests.get(url, headers=read_headers)
+data = StringIO(req.text)
+confirmed_df=pd.read_csv(data)
+
+nyc_counties_df = confirmed_df.loc[(confirmed_df['State'] == 'NY') & (confirmed_df['countyFIPS'].isin([36005, 36061, 36081, 36085, 36047]))]
+nyc_counties_df["County Name"] = [a.strip() for a in nyc_counties_df["County Name"]]
+nyc_counties_df=nyc_counties_df.set_index('County Name')
+nyc_counties_df=nyc_counties_df.drop(columns=['countyFIPS', 'State', 'StateFIPS'])
+nyc_counties_df = nyc_counties_df.transpose()
+nyc_counties_df = nyc_counties_df.rename_axis('Date')
+nyc_counties_df.index =pd.to_datetime(nyc_counties_df.index)
+#print(nyc_counties_df)
+nyc_counties_df['Total'] = nyc_counties_df['Bronx County'] + nyc_counties_df['Kings County'] + nyc_counties_df['New York County'] + nyc_counties_df['Queens County'] + nyc_counties_df['Richmond County']
+
+
+#confirmed deaths
+url = "https://static.usafacts.org/public/data/covid-19/covid_deaths_usafacts.csv"
+req = requests.get(url, headers=read_headers)
+data = StringIO(req.text)
+deaths_df = pd.read_csv(data)
+
+nyc_counties_deaths_df = deaths_df.loc[(deaths_df['State'] == 'NY') & (deaths_df['countyFIPS'].isin([36005, 36061, 36081, 36085, 36047]))]
+nyc_counties_deaths_df["County Name"] = [a.strip() for a in nyc_counties_deaths_df["County Name"]]
+nyc_counties_deaths_df=nyc_counties_deaths_df.set_index('County Name')
+nyc_counties_deaths_df=nyc_counties_deaths_df.drop(columns=['countyFIPS', 'State', 'StateFIPS'])
+
+nyc_counties_deaths_df = nyc_counties_deaths_df.transpose()
+nyc_counties_deaths_df = nyc_counties_deaths_df.rename_axis('Date')
+nyc_counties_deaths_df.index = pd.to_datetime(nyc_counties_deaths_df.index)
+
+nyc_counties_deaths_df['Total'] = nyc_counties_deaths_df['Bronx County'] + nyc_counties_deaths_df['Kings County'] + nyc_counties_deaths_df['New York County'] + nyc_counties_deaths_df['Queens County'] + nyc_counties_deaths_df['Richmond County']
+
+#case fatality ratio
+cfr_df = nyc_counties_deaths_df / nyc_counties_df
+
+#daily cases
+def dailydata(dfcounty):
+ dfcountydaily=dfcounty.diff(axis=0)#.fillna(0)
+ return dfcountydaily
+
+DailyCases_df=dailydata(nyc_counties_df)
+DailyDeaths_df=dailydata(nyc_counties_deaths_df)
+DailyCases_df = DailyCases_df.loc[start_date:]
+# compute 7-day exponential moving average
+DailyCases_df['EMA'] = DailyCases_df['Total'].ewm(span=7).mean()
+
+#daily deaths
+DailyDeaths_df = DailyDeaths_df.loc[start_date:]
+DailyDeaths_df.replace(0.0,np.nan, inplace=True)
+# compute 7-day exponential moving average
+DailyDeaths_df['EMA'] = DailyDeaths_df['Total'].ewm(span=7).mean()
+DailyDeaths_df = DailyDeaths_df.loc[start_date:]
+DailyDeaths_df.replace(0.0,np.nan, inplace=True)
+# compute 7-day exponential moving average
+DailyDeaths_df['EMA'] = DailyDeaths_df['Total'].ewm(span=7).mean()
+
+#NYC case hospitalizations
+url = "https://raw.githubusercontent.com/nychealth/coronavirus-data/master/trends/data-by-day.csv"
+
+nyc_case_hosp_death_df = pd.read_csv(url)
+nyc_case_hosp_death_df.index =pd.to_datetime(nyc_case_hosp_death_df.iloc[:,0])
+nyc_case_hosp_death_df = nyc_case_hosp_death_df.drop(nyc_case_hosp_death_df.columns[0], axis=1)
+nyc_case_hosp_death_df = nyc_case_hosp_death_df.rename_axis('Date')
+
+#r0 estimation
+url = "https://d14wlfuexuxgcm.cloudfront.net/covid/rt.csv"
+rt_df = pd.read_csv(url)
+rt_df = rt_df.rename(columns={"mean":"R0_mean"})
+
+#### PLOTS ####
+# case fatality ratio
+df2 = cfr_df.loc["2020-03-14":,]
+fig2 = go.Figure()
+fig2.add_scatter(x=df2.index, y=df2['Bronx County'], mode='lines', name='Bronx')
+fig2.add_scatter(x=df2.index, y=df2['Kings County'], mode='lines',name='Brooklyn')
+fig2.add_scatter(x=df2.index, y=df2['Queens County'], mode='lines', name='Queens')
+fig2.add_scatter(x=df2.index, y=df2['New York County'], mode='lines', name='Manhattan')
+fig2.add_scatter(x=df2.index, y=df2['Richmond County'], mode='lines', name='Staten Island')
+fig2.add_scatter(x=df2.index, y=df2['Total'], mode='lines+markers', name='NYC')
+prepFigure(fig2, title='NYC Case Fatality Rate - From USA Facts')
+fig2.update_layout(yaxis_title="Percent", yaxis_tickformat=".2%")
+fig2.write_json("/home/ubuntu/medtracker/medtracker/data/cfr.json")
+
+#daily new cases
+df2 = DailyCases_df
+fig2 = go.Figure()
+fig2.add_scatter(x=df2.index,y=df2['Bronx County'], mode='lines', name='Bronx')
+fig2.add_scatter(x=df2.index,y=df2['Kings County'], mode='lines', name='Brooklyn')
+fig2.add_scatter(x=df2.index,y=df2['New York County'], mode='lines', name='Manhattan')
+fig2.add_scatter(x=df2.index,y=df2['Queens County'], mode='lines', name='Queens')
+fig2.add_scatter(x=df2.index,y=df2['Richmond County'], mode='lines', name='Staten Island')
+fig2.add_scatter(x=df2.index,y=df2['Total'], mode='lines+markers', name='NYC')
+fig2.add_scatter(x=df2.index, y=df2['EMA'], mode='lines', line=dict(color='royalblue', width=4, dash='dot'), name='7 day EMA')
+prepFigure(fig2, title="NYC Daily New Cases (Source: USA Facts)")
+latest = df2.iloc[-1,:]
+fig2.add_annotation(text="On "+latest.name.strftime("%m/%d")+":\n"+str(int(latest.Total)),
+ font=dict(family="Helvetica, Arial",size=28),
+ xref="paper", yref="paper",
+ x=1, y=1, showarrow=False)
+fig2.write_json("/home/ubuntu/medtracker/medtracker/data/daily_cases.json")
+
+#daily new deaths
+df2 = DailyDeaths_df
+#df2 = temp_df
+fig2 = go.Figure()
+fig2.add_scatter(x=df2.index,y=df2['Bronx County'], mode='lines', name='Bronx')
+fig2.add_scatter(x=df2.index,y=df2['Kings County'], mode='lines', name='Brooklyn')
+fig2.add_scatter(x=df2.index,y=df2['New York County'], mode='lines', name='Manhattan')
+fig2.add_scatter(x=df2.index,y=df2['Queens County'], mode='lines', name='Queens')
+fig2.add_scatter(x=df2.index,y=df2['Richmond County'], mode='lines', name='Staten Island')
+fig2.add_scatter(x=df2.index,y=df2['Total'], mode='lines+markers', name='NYC')
+fig2.add_scatter(x=df2.index, y=df2['EMA'], mode='lines', line=dict(color='royalblue', width=4, dash='dot'), name='7 day EMA')
+prepFigure(fig2, title="NYC Daily Deaths (Source: USA Facts)")
+fig2.write_json("/home/ubuntu/medtracker/medtracker/data/daily_deaths.json")
+
+#hospitalizations
+df2 = nyc_case_hosp_death_df
+fig2 = go.Figure()
+fig2.add_scatter(x=df2.index,y=df2['HOSPITALIZED_COUNT'], mode='lines', name='Hospitalizations', )
+prepFigure(fig2,title="NYC New Hospitalizations (Source: NYC DOHMH)")
+fig2.write_json("/home/ubuntu/medtracker/medtracker/data/hospitalizations.json")
+
+#r0 estimation
+df2 = rt_df[rt_df["region"]=="NY"].sort_values("date").set_index("date")
+fig2 = go.Figure()
+fig2.add_scattergl(x=df2.index,y=df2.R0_mean.where(df2.R0_mean <= 1), line={'color': 'black'}) # below threshold
+fig2.add_scattergl(x=df2.index,y=df2.R0_mean.where(df2.R0_mean >= 1), line={'color': 'red'}) # Above threshhgold
+fig2.add_shape( #dashed line
+ type='line',
+ x0=str(df2.index.min()),
+ y0=1,
+ x1=str(df2.index.max()),
+ y1=1,
+ line=dict(
+ color='black',
+ dash="dashdot"
+ )
+)
+prepFigure(fig2,title="R0 estimate (Source: rt.live)")
+fig2.update_layout(showlegend=False)
+ro_latest = str(round(list(df2["R0_mean"])[-1],2))
+fig2.add_annotation(text="Current R0:\n"+ro_latest,
+ font=dict(family="Helvetica, Arial",size=24),
+ xref="paper", yref="paper",
+ x=1, y=1, showarrow=False)
+fig2.write_json("/home/ubuntu/medtracker/medtracker/data/r0_estimate.json")
diff --git a/medtracker/database.py b/medtracker/database.py
index 7323241..ea30561 100755
--- a/medtracker/database.py
+++ b/medtracker/database.py
@@ -1,8 +1 @@
-from medtracker import *
-from flask_sqlalchemy import SQLAlchemy
-from medtracker.config import *
-
-db = SQLAlchemy(app)
-
-# import a db_session to query db from other modules
-db_session = db.session
+#empty
diff --git a/medtracker/email_helper.py b/medtracker/email_helper.py
index 3c5aaf3..594766e 100755
--- a/medtracker/email_helper.py
+++ b/medtracker/email_helper.py
@@ -7,7 +7,7 @@
def send_email(address, subject, html):
'''send an email'''
- from_email = "info@suretify.co"
+ from_email = config.mail_server_sender
msg = Message(sender=from_email)
msg.sender = from_email
msg.recipients = [address]
@@ -15,4 +15,4 @@ def send_email(address, subject, html):
msg.html = html
mail.send(msg)
print("Sent email.")
- return None
\ No newline at end of file
+ return None
diff --git a/medtracker/forms.py b/medtracker/forms.py
index 6329765..d9b4c00 100755
--- a/medtracker/forms.py
+++ b/medtracker/forms.py
@@ -2,7 +2,7 @@
from flask_wtf import FlaskForm as Form
from wtforms.ext.sqlalchemy.fields import *
from wtforms.fields.html5 import DateField, IntegerRangeField, EmailField
-from wtforms.validators import DataRequired, Length, EqualTo, InputRequired
+from wtforms.validators import DataRequired, Length, EqualTo, InputRequired, Email, Regexp
from wtforms.widgets.core import HTMLString, html_params, escape, HiddenInput
from medtracker.models import *
from flask.views import MethodView
@@ -10,6 +10,7 @@
import re, json
from wtforms_alchemy import ModelForm, ModelFieldList
from wtforms.fields import FormField
+import re
class HiddenInteger(IntegerField):
widget = HiddenInput()
@@ -147,7 +148,8 @@ class UsernamePasswordForm(Form):
class NewUserForm(Form):
name = StringField('Full Name', validators=[DataRequired()])
- email = StringField('Email', validators=[DataRequired()])
+ email = StringField('Email', validators=[InputRequired(), Email(message="Please provide a valid email address."),
+ Regexp("(\S+(@icahn.mssm.edu|@mssm.edu|@mountsinai.org)$)",message="Please enter a Mount Sinai email address",flags=re.IGNORECASE)])
password = PasswordField('Password', validators=[DataRequired(),
Length(min=8, max=40),
EqualTo('confirm', message='Passwords must match')])
@@ -184,21 +186,48 @@ def validate_email(self, field):
raise ValidationError('Unknown email address.')
class PatientForm(Form):
- mrn = DisabledTextField('Patient Device ID')
+ mrn = DisabledTextField('Student Device ID')
+ fullname = StringField('Full Name', description="Please enter your name", validators=[InputRequired(), Length(min=3, max=50, message="Full name must be between 3 and 50 characters.")])
+ email = StringField('School Email Address', description="Please enter a Mount Sinai email address",
+ validators=[InputRequired(), Email(message="Please provide a valid email address."),
+ Regexp("(\S+(@icahn.mssm.edu|@mssm.edu|@mountsinai.org)$)",message="Please enter a Mount Sinai email address",flags=re.IGNORECASE)])
+ lifenumber = StringField('Life Number', description="e.g. 2211234",
+ validators=[InputRequired(), Length(min=7,max=7,message="Life number must be correct length."),
+ Regexp('^\d+$', message="Life number must only contain digits.")])
+ phone = StringField('Phone number (optional)')
+ age = StringField('Age (optional)')
+
program = RadioField("What program are you in?", choices=PROGRAM_CHOICES)
year = SelectField("What is your anticipated graduation date?",choices=[(i,i) for ix,i in enumerate(range(2021,2030))],coerce=int)
location = RadioField("Where are you currently living?", choices=LOCATION_CHOICES)
- fullname = StringField('Name (optional)')
- age = StringField('Age (optional)')
- email = StringField('Email address (optional)')
- phone = StringField('Phone number (optional)')
+
class PatientEditForm(Form):
- mrn = HiddenField('Patient Device ID')
+ mrn = DisabledTextField('Student Device ID')
+ fullname = StringField('Full Name', description="Please enter your name", validators=[InputRequired(), Length(min=3, max=50, message="Full name must be between 3 and 50 characters.")])
+ email = StringField('School Email Address', description="Please enter a Mount Sinai email address",
+ validators=[InputRequired(), Email(message="Please provide a valid email address."),
+ Regexp("(\S+(@icahn.mssm.edu|@mssm.edu|@mountsinai.org)$)",message="Please enter a Mount Sinai email address",flags=re.IGNORECASE)])
+ lifenumber = StringField('Life Number', description="e.g. 2211234",
+ validators=[InputRequired(), Length(min=7,max=7,message="Life number must be correct length."),
+ Regexp('^\d+$', message="Life number must only contain digits.")])
+
program = RadioField("What program are you in?", choices=PROGRAM_CHOICES)
year = SelectField("What is your anticipated graduation date?",choices=[(i,i) for ix,i in enumerate(range(2021,2030))],coerce=int)
location = RadioField("Where are you currently living?", choices=LOCATION_CHOICES)
- fullname = StringField('Name (optional)')
+
+ phone = StringField('Phone number (optional)')
age = StringField('Age (optional)')
- email = StringField('Email address (optional)')
- phone = StringField('Phone number (optional)')
\ No newline at end of file
+
+class PatientSearchForm(Form):
+
+ mrn = TextField('Student Device ID')
+ fullname = StringField('Full Name')
+ email = StringField('Email Address')
+ lifenumber = StringField('Life Number', description="e.g. 2211234")
+ phone = StringField('Phone number')
+ age = StringField('Age')
+ program = StringField("Program")
+ year = StringField("Graduation Year")
+ location = StringField("Location")
+
diff --git a/medtracker/models.py b/medtracker/models.py
index 969ec52..31e0b25 100755
--- a/medtracker/models.py
+++ b/medtracker/models.py
@@ -1,5 +1,4 @@
from medtracker import *
-from medtracker.database import db
from medtracker.config import *
from sqlalchemy_utils import EncryptedType, ChoiceType
from passlib.apps import custom_app_context as pwd_context
@@ -104,7 +103,7 @@ def __init__(self, **kwargs):
def to_dict(self):
return {col.name: getattr(self, col.name) for col in self.__table__.columns}
-
+
class Survey(db.Model):
__tablename__ = 'survey'
id = db.Column(db.Integer, primary_key=True)
@@ -202,7 +201,7 @@ def response(self,value):
if type(value)!=list:
value = [value]
self._response = ";".join([str(a) for a in value])
-
+
def __init__(self,**kwargs):
super(QuestionResponse, self).__init__(**kwargs)
self.time = datetime.datetime.utcnow()
@@ -210,11 +209,7 @@ def __init__(self,**kwargs):
def to_dict(self):
outdict = {col.name: getattr(self, col.name) for col in self.__table__.columns}
outdict["response"] = self._response
- outdict["question_title"] = self._question.body
- outdict["question_choices"] = self._question.choices
- outdict["question_type"] = self._question.kind.code
- outdict["survey_title"] = self._question.survey.title
- outdict["survey_id"] = self._question.survey.id
+
return outdict
class SurveyResponse(db.Model):
@@ -229,11 +224,11 @@ class SurveyResponse(db.Model):
exited = db.Column(db.Boolean,default=False)
completed = db.Column(db.Boolean, default=False)
message = db.Column(db.Text)
- responses = db.relationship("QuestionResponse",backref="parent", lazy="joined",cascade="all,delete-orphan")
+ responses = db.relationship("QuestionResponse",backref="parent", lazy="joined", cascade="all,delete-orphan")
def __str__(self):
return '%s' % self.session_id
-
+
def __init__(self,**kwargs):
super(SurveyResponse, self).__init__(**kwargs)
self.start_time = datetime.datetime.utcnow()
@@ -247,18 +242,18 @@ def exit(self):
self.end_time = datetime.datetime.utcnow()
self.exited = True
self.completed = False
-
+
def to_dict(self):
return {col.name: getattr(self, col.name) for col in self.__table__.columns}
class Trigger(db.Model):
__tablename__ = 'trigger'
-
+
id = db.Column(db.Integer, primary_key=True)
question_id = db.Column(db.Integer, db.ForeignKey('question.id'))
conditions = db.relationship("TriggerCondition",backref="trigger",cascade="all,delete-orphan")
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
-
+
yes_type = db.Column(ChoiceType(TRIGGER_KINDS))
dest_yes = db.Column(db.Integer, db.ForeignKey('question.id'))
dest_yes_question = db.relationship("Question",foreign_keys=[dest_yes])
@@ -290,7 +285,7 @@ class TriggerCondition(db.Model):
class Question(db.Model):
__tablename__ = 'question'
-
+
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String)
description = db.Column(db.Text)
@@ -316,7 +311,7 @@ class QuestionMeta(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String)
question_id = db.Column(db.Integer, db.ForeignKey('question.id'))
-
+
def to_dict(self):
return {col.name: getattr(self, col.name) for col in self.__table__.columns}
@@ -326,7 +321,6 @@ class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(EncryptedType(db.String, flask_secret_key), unique=True)
- username = db.Column(EncryptedType(db.String, flask_secret_key), unique=True)
name = db.Column(EncryptedType(db.String, flask_secret_key))
password_hash = db.Column(db.String(256))
active = db.Column(db.Boolean, default=False)
@@ -369,15 +363,21 @@ class Patient(db.Model):
__tablename__= "patients"
id = db.Column(db.Integer, primary_key=True)
- mrn = db.Column(EncryptedType(db.String, flask_secret_key))
+ mrn = db.Column(EncryptedType(db.String, flask_secret_key), unique=True)
fullname = db.Column(EncryptedType(db.String, flask_secret_key))
- age = db.Column(EncryptedType(db.String, flask_secret_key))
- phone = db.Column(EncryptedType(db.String, flask_secret_key))
email = db.Column(EncryptedType(db.String, flask_secret_key))
+ lifenumber = db.Column(EncryptedType(db.String, flask_secret_key))
location = db.Column(ChoiceType(LOCATION_CHOICES))
program = db.Column(ChoiceType(PROGRAM_CHOICES))
year = db.Column(db.Integer)
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
+ age = db.Column(EncryptedType(db.String, flask_secret_key))
+ phone = db.Column(EncryptedType(db.String, flask_secret_key))
+
+ google_email = db.Column(EncryptedType(db.String, flask_secret_key))
+ google_token = db.Column(EncryptedType(db.String, flask_secret_key))
+
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id')) #this is the "owner" of the student/patient record
+
surveys = db.relationship("SurveyResponse", backref='patient', lazy="dynamic", cascade="all, delete-orphan")
responses = db.relationship("QuestionResponse", backref='patient', lazy="dynamic", cascade="all, delete-orphan")
progress = db.relationship("Progress", backref='patient', lazy="dynamic", cascade="all, delete-orphan")
@@ -392,8 +392,8 @@ class Device(db.Model):
__tablename__= "devices"
id = db.Column(db.Integer, primary_key=True)
- device_id = db.Column(db.String)
+ device_id = db.Column(db.String, unique=True)
creation_time = db.Column(db.DateTime, default=func.now())
def to_dict(self):
- return {col.name: getattr(self, col.name) for col in self.__table__.columns}
\ No newline at end of file
+ return {col.name: getattr(self, col.name) for col in self.__table__.columns}
diff --git a/medtracker/templates/404.html b/medtracker/templates/404.html
index 19a3f4d..ded79f2 100755
--- a/medtracker/templates/404.html
+++ b/medtracker/templates/404.html
@@ -9,7 +9,7 @@
We can't find that page. Please try a different request.
-}})

Details: {{message}}{% endif %}
Something's gone terribly wrong. We're working on it! (Error 500)
- + {% if message %}Details:
{{message}}
ISMMS Health Check is a new lightweight web app to simplify health workflows at ISMMS - through automated follow-up of medical protocols or critical health values. We have created if-this-then-that + through automated follow-up of medical protocols for critical health values. We have created if-this-then-that style triggers for health data that takes the burden off health care providers to perform follow-up while collecting data from patients and providers automatically.
+Developed by Ryan Neff (MD/PhD Class of 2023) on behalf of the Mount Sinai Department of Medical Education (Dr. Valerie Parkas, Dr. Beverly Forsyth, and Dr. Rainier Soriano) and Mount Sinai Student Health (Dr. Lori Zbar). Special thanks to Michelle Sainte at MedEd and everyone at Mount Sinai Information Technology (Ben Maisano, Paul Laurence, Ricardo Somarriba).
|
+
+
+
+
+
|
+
|
+
+
+
+
+
|
+
Errors: {{form.errors}}
- {% endif %} {% block form %}In order to complete this survey, you first need to register your device to save and view your responses. This process will only happen once and take a few seconds. Clearing your browser cookies will delete your registration.
Please use the same device and browser for future visits to skip this step and track your responses.
@@ -25,7 +22,7 @@These questions are completely optional and will only be used to contact you should your symptoms change. They will be kept private and confidential. Only Mount Sinai Student Health has access to this information. It will not be shared with other Mount Sinai faculty or staff.
- {{render_field(form.email)}} {{render_field(form.phone)}} - {{render_field(form.fullname)}} {{render_field(form.age)}} diff --git a/medtracker/templates/index.html b/medtracker/templates/index.html index 469d796..b282549 100755 --- a/medtracker/templates/index.html +++ b/medtracker/templates/index.html @@ -2,7 +2,7 @@ {% block title %}Welcome | ISMMS Health Check{% endblock %} {% block body %}Check your symptoms for COVID-19 daily from anywhere, whether at home or on-the go. No login necessary.
@@ -10,12 +10,12 @@
How does this work?
+How does this work?
Take the quick screening tool daily to screen for possible COVID-19 symptoms. No need to login and you may choose to keep your responses anonymous.
+Take the quick screening tool daily to screen for possible COVID-19 symptoms. Coming soon: save your responses across your devices with your Mount Sinai Google Apps account.
If you screen positive, stay home and protect others from the spread of the novel coronavirus.
Where can I find updates on COVID-19 policies?
+