Skip to content

Commit 108c24d

Browse files
authored
Merge branch 'develop' into ahil-elastic
2 parents d65eb20 + 6eda172 commit 108c24d

25 files changed

+360
-76
lines changed

.github/workflows/pythonapp.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
python -m pytest
4444
- name: Run App
4545
run: |
46-
PARAMETERS=./defaults/cypress.cfg streamlit run st_app.py &
46+
PARAMETERS=./defaults/cypress.cfg ASSETS=./defaults/assets streamlit run st_app.py &
4747
- name: Cypress
4848
uses: cypress-io/github-action@v1
4949
with:

Dockerfile

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
FROM python:3.7.7-slim-buster
2+
ENV ASSETS=./defaults/assets/
23
ENV PARAMETERS=./defaults/webapp.cfg
4+
ENV PORT=8000
35
WORKDIR /app
46
COPY README.md .
57
COPY setup.cfg .
@@ -11,5 +13,4 @@ COPY src src
1113
COPY st_app.py st_app.py
1214
RUN pip install -q .
1315

14-
CMD ["streamlit", "run", "st_app.py"]
15-
16+
CMD STREAMLIT_SERVER_PORT=$PORT streamlit run st_app.py

Procfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web: PARAMETERS=defaults/webapp.cfg STREAMLIT_SERVER_PORT=$PORT streamlit run st_app.py
1+
web: PARAMETERS=./defaults/webapp.cfg ASSETS=./defaults/assets STREAMLIT_SERVER_PORT=$PORT streamlit run st_app.py
Binary file not shown.

defaults/assets/PPE_Screenshot.jpg

253 KB
Loading
Loading
1.01 MB
Loading

docs/contributing/app-dev.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ pip install streamlit
4646
## Run the Streamlit Web App
4747

4848
```bash
49+
ASSETS=./defaults/assets \
4950
PARAMETERS=-./defaults/webapp.cfg streamlit run st_app.py
5051
```
5152

5253
## Run the Command Line Interface
5354

5455
```bash
56+
ASSETS=./defaults/assets \
5557
PARAMETERS=./defaults/cli.cfg penn_chime
5658
```
5759

@@ -66,6 +68,7 @@ penn_chime --help
6668
If you want a different set of default parameters, you may use your own configuration file.
6769

6870
```bash
71+
ASSETS=./defaults/assets \
6972
PARAMETERS=./defaults/yours.cfg streamlit run st_app.py
7073
```
7174

@@ -77,7 +80,19 @@ Be sure to include `--mitigation-date` in the file if social distancing was impl
7780
If you need to run the application on a different port than the default (8000), you can set an environment variable.
7881

7982
```bash
80-
STREAMLIT_SERVER_PORT=1234 PARAMETERS=./defaults/webapp.cfg streamlit run st_app.py
83+
ASSETS=./defaults/assets \
84+
STREAMLIT_SERVER_PORT=1234 \
85+
PARAMETERS=./defaults/webapp.cfg streamlit run st_app.py
86+
```
87+
88+
### Choosing a Different Language
89+
90+
If you want to run the application in another language, do the following. You can select Japanese as the language other than English.
91+
92+
```bash
93+
ASSETS=./defaults/assets \
94+
LANG=ja \
95+
PARAMETERS=./defaults/webapp.cfg streamlit run st_app.py
8196
```
8297

8398
## Project Layout
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Contributing: Operations Support
22

3-
*Coming soon*
3+
- This repo is associated with Penn Medicine's Chime1.0 model, which uses a Streamlit app that is built on an SIR model
4+
- Contributors are encouraged to consider supporting Penn Medicine's Chime2.0 model, which still uses a Streamlit app, but leverages an enhanced Bayesian SEIR model.

heroku.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
build:
22
docker:
3-
web: Dockerfile.dash
3+
web: Dockerfile
44
config:
5-
PORT: ${PORT}
5+
PORT: ${PORT}

setup.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Setup file for chime
22
"""
3-
__version__ = "1.1.3" # update VERSION in constants.py
3+
__version__ = "1.1.4" # update VERSION in constants.py
44
__author__ = "Predictive Healthcare @ Penn Medicine"
55

6-
from setuptools import setup, find_namespace_packages
6+
from setuptools import setup, find_packages
77

88

99
setup(
@@ -19,7 +19,7 @@
1919
"Documentation": "https://codeforphilly.github.io/chime/",
2020
},
2121
package_dir={'': 'src'},
22-
packages=find_namespace_packages(where='src', exclude=('tests')),
22+
packages=find_packages(where='src', exclude=('tests')),
2323
install_requires=[
2424
"altair",
2525
"black",

src/penn_chime/cli.py

+4
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@
66
from .model.parameters import Parameters
77
from .model.sir import Sir
88

9+
910
def run(argv):
11+
"""Eun cli."""
1012
p = Parameters.create(os.environ, argv[1:])
1113
m = Sir(p)
1214

1315
for df, name in (
1416
(m.sim_sir_w_date_df, "sim_sir_w_date"),
1517
(m.admits_df, "projected_admits"),
1618
(m.census_df, "projected_census"),
19+
(m.ppe_df, 'ppe_data')
1720
):
1821
df.to_csv(f"{p.current_date}_{name}.csv")
1922

23+
2024
def main():
2125
"""Main."""
2226
run(sys.argv)

src/penn_chime/constants.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
re-run their reports
99
"""
1010
CHANGE_DATE = date(year=2020, month=4, day=8)
11-
VERSION = 'v1.1.3'
11+
VERSION = 'v1.1.4'
1212

1313
DATE_FORMAT = "%b, %d" # see https://strftime.org
1414
DOCS_URL = "https://code-for-philly.gitbook.io/chime"
@@ -17,3 +17,5 @@
1717

1818
FLOAT_INPUT_MIN = 0.0001
1919
FLOAT_INPUT_STEP = 0.1
20+
21+
PREFIT_ADDITIONAL_DAYS = 300

src/penn_chime/locales/en.yml

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ en:
33
app-new-admissions-text: "Projected number of **daily** COVID-19 admissions."
44
app-admitted-patients-title: "Admitted Patients (Census)"
55
app-admitted-patients-text: "Projected **census** of COVID-19 patients, accounting for arrivals and discharges."
6+
app-PPE-title: "Personal Protective Equipment (PPE) Calculator"
7+
app-PPE-screenshot: "Show a screenshot of the tool"
8+
app-PPE-documentation: |+
9+
Refer to our <a href="{link_to_docs}">user documentation for instructions on how to use the tool</a>.
610
app-SIR-title: "Susceptible, Infected, and Recovered"
711
app-SIR-text: "The number of susceptible, infected, and recovered individuals in the hospital catchment region at any given moment"
812
charts-date: "Date"
@@ -81,6 +85,8 @@ en:
8185
presentation-copyright: "© 2020, The Trustees of the University of Pennsylvania"
8286
presentation-download: |+
8387
<a download="{filename}" href="data:file/csv;base64,{csv}">Download {filename}</a>
88+
presentation-excel-download: |+
89+
Download the PPE Calculator here: <a download="{filename}" href="data:file/xlsx;base64,{excel}">{filename}</a>.
8490
admits_hospitalized: "Hospitalized Admissions"
8591
admits_icu: "ICU Admissions"
8692
admits_ventilated: "Ventilated Admissions"

src/penn_chime/locales/ja.yml

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ ja:
33
app-new-admissions-text: "**日毎の**COVID-19関連新規入院患者数予測"
44
app-admitted-patients-title: "累計入院患者数"
55
app-admitted-patients-text: "入院と退院を考慮したCOVID-19患者の予測**累計患者数**"
6+
app-PPE-title: "個人用防護具 (PPE) 計算機"
7+
app-PPE-screenshot: "ツールのスクリーンショットを表示"
8+
app-PPE-documentation: |+
9+
ツールの使い方については<a href="{link_to_docs}">ユーザドキュメントの説明</a>を確認してください。
610
app-SIR-title: "感受性保持者、感染者と回復者"
711
app-SIR-text: "与えられた時間範囲での、病院の担当範囲領域における感受性保持者、感染者と回復者の人数"
812
charts-date: "日付"
@@ -79,6 +83,8 @@ ja:
7983
presentation-copyright: "© 2020, The Trustees of the University of Pennsylvania"
8084
presentation-download: |+
8185
<a download="{filename}" href="data:file/csv;base64,{csv}">{filename} をダウンロード</a>
86+
presentation-excel-download: |+
87+
個人用防護具計算機のダウンロードはこちら: <a download="{filename}" href="data:file/xlsx;base64,{excel}">{filename}</a>.
8288
admits_hospitalized: "新規入院患者数"
8389
admits_icu: "新規集中治療患者数"
8490
admits_ventilated: "新規人工呼吸患者数"

src/penn_chime/model/__init__.py

Whitespace-only changes.

src/penn_chime/model/parameters.py

+31-10
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def validate(string):
137137
"ventilated": ValDisposition,
138138
"hospitalized": ValDisposition,
139139
"icu": ValDisposition,
140+
"use_log_scale": OptionalValue
140141
}
141142

142143

@@ -161,16 +162,17 @@ def validate(string):
161162
"relative_contact_rate": "Social distancing reduction rate: 0.0 - 1.0",
162163
"ventilated_days": "Average days on ventilator",
163164
"ventilated_rate": "Ventilated Rate: 0.0 - 1.0",
165+
"use_log_scale": "Flag to use logarithmic scale on charts instead of linear scale."
164166
}
165167

166168

167169
ARGS = (
168170
(
169171
"parameters",
170172
str,
171-
None,
172-
None,
173-
False,
173+
None, # Min value
174+
None, # Max value
175+
False, # Whether it is required or optional.
174176
),
175177
(
176178
"current_hospitalized",
@@ -298,15 +300,24 @@ def validate(string):
298300
1.0,
299301
True,
300302
),
303+
(
304+
"use_log_scale",
305+
bool,
306+
None,
307+
None,
308+
False
309+
)
301310
)
302311

303312

304313
def to_cli(name):
305314
return "--" + name.replace('_', '-')
306315

307-
308316
class Parameters:
309-
"""Parameters."""
317+
"""
318+
Object containing all of the parameters that can be adjusted by the user, either from the command line or using
319+
the side bar of the web app.
320+
"""
310321

311322
@classmethod
312323
def parser(cls):
@@ -315,11 +326,20 @@ def parser(cls):
315326

316327
for name, cast, min_value, max_value, required in ARGS:
317328
arg = to_cli(name)
318-
parser.add_argument(
319-
arg,
320-
type=validator(arg, cast, min_value, max_value, required),
321-
help=HELP.get(name),
322-
)
329+
if cast == bool:
330+
# This argument is a command-line flag and does not need validation.
331+
parser.add_argument(
332+
arg,
333+
action='store_true',
334+
help=HELP.get(name),
335+
)
336+
else:
337+
# Use a custom validator for any arguments that take in values.
338+
parser.add_argument(
339+
arg,
340+
type=validator(arg, cast, min_value, max_value, required),
341+
help=HELP.get(name),
342+
)
323343
return parser
324344

325345
@classmethod
@@ -396,6 +416,7 @@ def __init__(self, **kwargs):
396416
self.relative_contact_rate = None
397417
self.recovered = None
398418
self.ventilated = None
419+
self.use_log_scale = False
399420

400421
passed_and_default_parameters = {}
401422
for key, value in kwargs.items():

src/penn_chime/model/ppe.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Personal Protective Equipment."""
2+
3+
import os
4+
from typing import Dict
5+
6+
7+
class PPE:
8+
9+
def __init__(self, env: Dict[str, str]):
10+
"""__init__."""
11+
self.assets = assets = env['ASSETS']
12+
self.filename = filename = "PPE_Calculator_for_COVID-19.xlsx"
13+
self.src = os.path.join(assets, filename)
14+
self.screenshot = os.path.join(assets, 'PPE_Screenshot.jpg')

src/penn_chime/model/sir.py

+36-15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import numpy as np
1515
import pandas as pd
1616

17+
from ..constants import PREFIT_ADDITIONAL_DAYS
1718
from .parameters import Parameters
1819

1920

@@ -68,31 +69,42 @@ def __init__(self, p: Parameters):
6869

6970
if p.mitigation_date is None:
7071
self.i_day = 0 # seed to the full length
71-
raw = self.run_projection(p, [(self.beta, p.n_days)])
72+
raw = self.run_projection(p, [
73+
(self.beta, p.n_days + PREFIT_ADDITIONAL_DAYS)])
7274
self.i_day = i_day = int(get_argmin_ds(raw["census_hospitalized"], p.current_hospitalized))
7375

74-
self.raw = self.run_projection(p, self.gen_policy(p))
76+
self.raw = self.run_projection(p, self.get_policies(p))
7577

7678
logger.info('Set i_day = %s', i_day)
7779
else:
78-
projections = {}
7980
best_i_day = -1
8081
best_i_day_loss = float('inf')
81-
for i_day in range(p.n_days):
82-
self.i_day = i_day
83-
raw = self.run_projection(p, self.gen_policy(p))
82+
for self.i_day in range(p.n_days + PREFIT_ADDITIONAL_DAYS):
83+
mitigation_day = -(p.current_date - p.mitigation_date).days
84+
if mitigation_day < -self.i_day:
85+
mitigation_day = -self.i_day
86+
87+
total_days = self.i_day + p.n_days + PREFIT_ADDITIONAL_DAYS
88+
pre_mitigation_days = self.i_day + mitigation_day
89+
post_mitigation_days = total_days - pre_mitigation_days
90+
91+
raw = self.run_projection(p, [
92+
(self.beta, pre_mitigation_days),
93+
(self.beta_t, post_mitigation_days),
94+
]
95+
)
8496

8597
# Don't fit against results that put the peak before the present day
86-
if raw["census_hospitalized"].argmax() < i_day:
98+
if raw["census_hospitalized"].argmax() < self.i_day:
8799
continue
88100

89-
loss = get_loss(raw["census_hospitalized"][i_day], p.current_hospitalized)
101+
loss = get_loss(raw["census_hospitalized"][self.i_day], p.current_hospitalized)
90102
if loss < best_i_day_loss:
91103
best_i_day_loss = loss
92-
best_i_day = i_day
93-
self.raw = raw
104+
best_i_day = self.i_day
94105

95106
self.i_day = best_i_day
107+
self.raw = self.run_projection(p, self.get_policies(p))
96108

97109
logger.info(
98110
'Estimated date_first_hospitalized: %s; current_date: %s; i_day: %s',
@@ -127,7 +139,7 @@ def __init__(self, p: Parameters):
127139
intrinsic_growth_rate = get_growth_rate(p.doubling_time)
128140
self.beta = get_beta(intrinsic_growth_rate, self.gamma, self.susceptible, 0.0)
129141
self.beta_t = get_beta(intrinsic_growth_rate, self.gamma, self.susceptible, p.relative_contact_rate)
130-
self.raw = self.run_projection(p, self.gen_policy(p))
142+
self.raw = self.run_projection(p, self.get_policies(p))
131143

132144
self.population = p.population
133145
else:
@@ -162,6 +174,15 @@ def __init__(self, p: Parameters):
162174
'census_icu': self.raw['census_icu'],
163175
'census_ventilated': self.raw['census_ventilated'],
164176
})
177+
self.ppe_df = pd.DataFrame(data={
178+
'day': self.raw['day'],
179+
'date': self.raw['date'],
180+
'census_hospitalized': self.raw['census_hospitalized'],
181+
'census_icu': self.raw['census_icu'],
182+
'census_ventilated': self.raw['census_ventilated'],
183+
'admits_hospitalized': self.raw['admits_hospitalized'],
184+
})
185+
self.ppe_df = self.ppe_df[self.ppe_df['day']>=0]
165186

166187
logger.info('len(np.arange(-i_day, n_days+1)): %s', len(np.arange(-self.i_day, p.n_days+1)))
167188
logger.info('len(raw_df): %s', len(self.raw_df))
@@ -195,9 +216,9 @@ def get_argmin_doubling_time(self, p: Parameters, dts):
195216
intrinsic_growth_rate = get_growth_rate(i_dt)
196217
self.beta = get_beta(intrinsic_growth_rate, self.gamma, self.susceptible, 0.0)
197218
self.beta_t = get_beta(intrinsic_growth_rate, self.gamma, self.susceptible, p.relative_contact_rate)
198-
199-
raw = self.run_projection(p, self.gen_policy(p))
200-
219+
220+
raw = self.run_projection(p, self.get_policies(p))
221+
201222
# Skip values the would put the fit past peak
202223
peak_admits_day = raw["admits_hospitalized"].argmax()
203224
if peak_admits_day < 0:
@@ -210,7 +231,7 @@ def get_argmin_doubling_time(self, p: Parameters, dts):
210231
min_loss = pd.Series(losses).argmin()
211232
return min_loss
212233

213-
def gen_policy(self, p: Parameters) -> Sequence[Tuple[float, int]]:
234+
def get_policies(self, p: Parameters) -> Sequence[Tuple[float, int]]:
214235
if p.mitigation_date is not None:
215236
mitigation_day = -(p.current_date - p.mitigation_date).days
216237
else:

0 commit comments

Comments
 (0)