|
import streamlit as st |
|
import altair as alt |
|
import numpy as np |
|
import pandas as pd |
|
|
|
st.markdown( |
|
""" |
|
<style> |
|
@font-face { |
|
font-family: 'Tangerine'; |
|
font-style: normal; |
|
font-weight: 400; |
|
src: url(https://fonts.gstatic.com/s/tangerine/v12/IurY6Y5j_oScZZow4VOxCZZM.woff2) format('woff2'); |
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|
} |
|
|
|
html, body, [class*="css"] { |
|
font-family: 'Public Sans', sans-serif; |
|
# font-size: 1rem; |
|
} |
|
</style> |
|
|
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
|
|
|
|
|
|
st.subheader("Configuration") |
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
symptoms_chance = st.slider( |
|
'Chances of developing symptoms if infected (per day)', min_value=0.0, max_value=1.0, value=0.5, step=0.01) |
|
|
|
|
|
with col1: |
|
mean_days_inf_asympt = st.slider( |
|
'Mean number of days as infectious asymptomatic (without routine testing)', min_value=1, max_value=14, value=4, step=1) |
|
base_p00 = 1-(1/mean_days_inf_asympt) |
|
base_p01 = (1-symptoms_chance)*(1/mean_days_inf_asympt) |
|
base_p03 = (symptoms_chance)*(1/mean_days_inf_asympt) |
|
|
|
|
|
with col2: |
|
mean_days_inf_sympt = st.slider( |
|
'Mean number of days as infectious symptomatic (when testing on symptoms only)', min_value=1, max_value=14, value=2, step=1) |
|
base_p11 = 1-(1/mean_days_inf_sympt) |
|
base_p12 = (1/mean_days_inf_sympt) |
|
|
|
|
|
efficiency = st.radio( |
|
"Performance of device", |
|
('Standard', 'Conservative')) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sens_list_standard = {0.0: 0.0, |
|
0.005: 0.05, |
|
0.014: 0.1, |
|
0.021: 0.15, |
|
0.05: 0.295, |
|
0.1: 0.434, |
|
0.2: 0.6, |
|
0.3: 0.72, |
|
0.4: 0.79, |
|
0.5: 0.86, |
|
0.6: 0.9, |
|
0.7: 0.925, |
|
0.8: 0.97, |
|
0.9: 0.99, |
|
1.0: 1.0} |
|
|
|
sens_list_conservative = { |
|
0: 0, |
|
0.012: 0.050, |
|
0.026: 0.105, |
|
0.049: 0.149, |
|
0.072: 0.198, |
|
0.096: 0.248, |
|
0.120: 0.297, |
|
0.146: 0.347, |
|
0.184: 0.396, |
|
0.222: 0.446, |
|
0.255: 0.495, |
|
0.300: 0.545, |
|
0.349: 0.594, |
|
0.401: 0.644, |
|
0.467: 0.693, |
|
0.547: 0.743, |
|
0.621: 0.792, |
|
0.699: 0.842, |
|
0.787: 0.891, |
|
0.868: 0.941, |
|
1: 1 |
|
} |
|
|
|
if efficiency == 'Standard': |
|
sens_list = sens_list_standard |
|
else: |
|
sens_list = sens_list_conservative |
|
|
|
def roc_func(x): |
|
return sens_list[x] |
|
|
|
def roc_random(x): |
|
return x |
|
|
|
test_efficiency = np.array([7, 30, 10000]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_efficiency = np.array([7, 30, 10000]) |
|
FPR = list(sens_list.keys()) |
|
days_inf = np.zeros((len(test_efficiency), len(FPR))) |
|
temp_df = [] |
|
for tau_count, t_e in enumerate(test_efficiency): |
|
tau = 1/t_e |
|
for fi_count, fi in enumerate(FPR): |
|
pi = roc_func(fi) |
|
|
|
p = np.array([ |
|
[base_p00*(1-tau)*(1-pi), base_p01*(1-tau) * |
|
(1-pi), 1-(1-tau)*(1-pi), base_p03*(1-tau)*(1-pi)], |
|
[0, base_p11*(1-tau)*(1-pi), base_p12*(1+tau+pi-tau*pi), 0.0], |
|
[0, 0, 1.0, 0.0], |
|
[0, 0, 0.0, 1.0] |
|
]) |
|
|
|
m1 = 1/(1-p[0, 0]) |
|
m2 = 1/(1-p[1, 1]) |
|
p2 = p[0, 1]/(p[0, 1]+p[0, 2]+p[0, 3]) |
|
days_inf[int(tau_count), int(fi_count)] = m1 + p2*m2 |
|
routine_tests_required = 30 * days_inf[2] |
|
|
|
|
|
|
|
no_wearables = [] |
|
tau = 1/10000 |
|
for fi_count, fi in enumerate(FPR): |
|
pi = roc_random(fi) |
|
|
|
p = np.array([ |
|
[base_p00*(1-tau)*(1-pi), base_p01*(1-tau) * |
|
(1-pi), 1-(1-tau)*(1-pi), base_p03*(1-tau)*(1-pi)], |
|
[0, base_p11*(1-tau)*(1-pi), base_p12*(1+tau+pi-tau*pi), 0.0], |
|
[0, 0, 1.0, 0.0], |
|
[0, 0, 0.0, 1.0] |
|
]) |
|
|
|
m1 = 1/(1-p[0, 0]) |
|
m2 = 1/(1-p[1, 1]) |
|
p2 = p[0, 1]/(p[0, 1]+p[0, 2]+p[0, 3]) |
|
no_wearables.append(m1 + p2*m2) |
|
|
|
cost = np.array(FPR)*30 |
|
no_wearable_cost = cost |
|
|
|
|
|
wearable_cost = (1-(1-np.array(FPR))*(1-1/test_efficiency[2]))*30 |
|
wearable_days_inf = days_inf[2] |
|
|
|
|
|
chart_data = pd.DataFrame( |
|
{'Tests required per month': no_wearable_cost, |
|
'Routine testing': no_wearables, |
|
'Wearable-triggered testing': wearable_days_inf}) |
|
|
|
|
|
|
|
chart_data_melted = chart_data.melt('Tests required per month') |
|
print(chart_data_melted) |
|
chart = ( |
|
alt.Chart( |
|
data=chart_data_melted, |
|
title="", |
|
height=400, |
|
) |
|
.mark_line() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.encode( |
|
x='Tests required per month', |
|
y=alt.Y('value:Q', axis=alt.Axis(title='Average case infectious days')), |
|
|
|
color='variable:N', |
|
strokeWidth=alt.value(6) |
|
) |
|
.configure_axis( |
|
labelFontSize=20, |
|
titleFontSize=20 |
|
) |
|
|
|
) |
|
|
|
st.subheader("Outcome") |
|
st.altair_chart(chart, use_container_width=True) |
|
|
|
|
|
|
|
|
|
|
|
|