Skip to content

Commit 67e8f9f

Browse files
committed
Convert Parameters to declaratively specify all the variables it expects to receive, and adapt the CLI to automatically take those as arguments
1 parent 6ea4cd8 commit 67e8f9f

File tree

2 files changed

+63
-77
lines changed

2 files changed

+63
-77
lines changed

src/penn_chime/cli.py

Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
Action,
55
ArgumentParser,
66
)
7-
from datetime import datetime
87

98
from pandas import DataFrame
109

@@ -21,8 +20,16 @@ def __call__(self, parser, namespace, values, option_string=None):
2120
parser.parse_args(f.read().split(), namespace)
2221

2322

24-
def cast_date(string):
25-
return datetime.strptime(string, '%Y-%m-%d').date()
23+
def declarative_validator(cast):
24+
"""Validator."""
25+
26+
def validate(string):
27+
"""Validate."""
28+
if string == '' and cast != str:
29+
return None
30+
return cast(string)
31+
32+
return validate
2633

2734

2835
def validator(arg, cast, min_value, max_value, required=True):
@@ -49,31 +56,18 @@ def parse_args():
4956
parser = ArgumentParser(description=f"penn_chime: {CHANGE_DATE}")
5057
parser.add_argument("--file", type=open, action=FromFile)
5158

59+
for name, (params_validator, default, cast, help) in ACCEPTED_PARAMETERS.items():
60+
if cast is None:
61+
continue
62+
63+
parser.add_argument(
64+
"--" + name.replace('_', '-'),
65+
type=declarative_validator(cast),
66+
default=default,
67+
help=help
68+
)
69+
5270
for arg, cast, min_value, max_value, help, required in (
53-
(
54-
"--current-hospitalized",
55-
int,
56-
0,
57-
None,
58-
"Currently hospitalized COVID-19 patients (>= 0)",
59-
True,
60-
),
61-
(
62-
"--date-first-hospitalized",
63-
cast_date,
64-
None,
65-
None,
66-
"Current date",
67-
False,
68-
),
69-
(
70-
"--doubling-time",
71-
float,
72-
0.0,
73-
None,
74-
"Doubling time before social distancing (days)",
75-
True,
76-
),
7771
("--hospitalized-days", int, 0, None, "Average hospital length of stay (in days)", True),
7872
(
7973
"--hospitalized-rate",
@@ -85,25 +79,7 @@ def parse_args():
8579
),
8680
("--icu-days", int, 0, None, "Average days in ICU", True),
8781
("--icu-rate", float, 0.0, 1.0, "ICU rate: 0.0 - 1.0", True),
88-
(
89-
"--market_share",
90-
float,
91-
0.00001,
92-
1.0,
93-
"Hospital market share (0.00001 - 1.0)",
94-
True,
95-
),
96-
("--infectious-days", float, 0.0, None, "Infectious days", True),
97-
("--n-days", int, 0, None, "Number of days to project >= 0", True),
98-
(
99-
"--relative-contact-rate",
100-
float,
101-
0.0,
102-
1.0,
103-
"Social distancing reduction rate: 0.0 - 1.0",
104-
True,
105-
),
106-
("--population", int, 1, None, "Regional population >= 1", True),
82+
10783
("--ventilated-days", int, 0, None, "Average days on ventilator", True),
10884
("--ventilated-rate", float, 0.0, 1.0, "Ventilated Rate: 0.0 - 1.0", True),
10985
):
@@ -119,19 +95,24 @@ def main():
11995
"""Main."""
12096
a = parse_args()
12197

98+
del a.file
99+
100+
hospitalized = Disposition(a.hospitalized_rate, a.hospitalized_days)
101+
icu = Disposition(a.icu_rate, a.icu_days)
102+
ventilated = Disposition(a.ventilated_rate, a.ventilated_days)
103+
104+
del a.hospitalized_days
105+
del a.hospitalized_rate
106+
del a.icu_days
107+
del a.icu_rate
108+
del a.ventilated_days
109+
del a.ventilated_rate
110+
122111
p = Parameters(
123-
current_hospitalized=a.current_hospitalized,
124-
date_first_hospitalized=a.date_first_hospitalized,
125-
doubling_time=a.doubling_time,
126-
infectious_days=a.infectious_days,
127-
market_share=a.market_share,
128-
n_days=a.n_days,
129-
relative_contact_rate=a.relative_contact_rate,
130-
population=a.population,
131-
132-
hospitalized=Disposition(a.hospitalized_rate, a.hospitalized_days),
133-
icu=Disposition(a.icu_rate, a.icu_days),
134-
ventilated=Disposition(a.ventilated_rate, a.ventilated_days),
112+
hospitalized=hospitalized,
113+
icu=icu,
114+
ventilated=ventilated,
115+
**vars(a)
135116
)
136117

137118
m = Model(p)

src/penn_chime/parameters.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from collections import namedtuple
8-
from datetime import date
8+
from datetime import date, datetime
99
from typing import Optional
1010

1111
from .validators import (
@@ -45,24 +45,29 @@ def __init__(self, **kwargs):
4545
self.population = population
4646

4747

48+
def cast_date(string):
49+
return datetime.strptime(string, '%Y-%m-%d').date()
50+
51+
52+
# Dictionary from parameter names to Tuples containing (validator, default value, cast function, help text)
4853
ACCEPTED_PARAMETERS = {
49-
"current_hospitalized": (Positive, None),
50-
"current_date": (OptionalDate, None),
51-
"date_first_hospitalized": (OptionalDate, None),
52-
"doubling_time": (OptionalStrictlyPositive, None),
53-
"relative_contact_rate": (Rate, None),
54-
"mitigation_date": (OptionalDate, None),
55-
"infectious_days": (StrictlyPositive, 14),
56-
"market_share": (Rate, 1.0),
57-
"max_y_axis": (OptionalStrictlyPositive, None),
58-
"n_days": (StrictlyPositive, 100),
59-
"recovered": (Positive, 0),
60-
"population": (OptionalStrictlyPositive, None),
61-
"region": (OptionalValue, None),
62-
63-
"hospitalized": (ValDisposition, None),
64-
"icu": (ValDisposition, None),
65-
"ventilated": (ValDisposition, None),
54+
"current_hospitalized": (Positive, None, int, "Currently hospitalized COVID-19 patients (>= 0)"),
55+
"current_date": (OptionalDate, None, cast_date, "Date on which the forecast should be based"),
56+
"date_first_hospitalized": (OptionalDate, None, cast_date, "Date the first patient was hospitalized"),
57+
"doubling_time": (OptionalStrictlyPositive, None, float, "Doubling time before social distancing (days)"),
58+
"relative_contact_rate": (Rate, None, float, "Social distancing reduction rate: 0.0 - 1.0"),
59+
"mitigation_date": (OptionalDate, None, cast_date, "Date on which social distancing measures too effect"),
60+
"infectious_days": (StrictlyPositive, 14, int, "Infectious days"),
61+
"market_share": (Rate, 1.0, float, "Hospital market share (0.00001 - 1.0)"),
62+
"max_y_axis": (OptionalStrictlyPositive, None, int, None),
63+
"n_days": (StrictlyPositive, 100, int, "Number of days to project >= 0"),
64+
"recovered": (Positive, 0, int, "Number of patients already recovered (not yet implemented)"),
65+
"population": (OptionalStrictlyPositive, None, int, "Regional population >= 1"),
66+
"region": (OptionalValue, None, None, "No help available"),
67+
68+
"hospitalized": (ValDisposition, None, None, None),
69+
"icu": (ValDisposition, None, None, None),
70+
"ventilated": (ValDisposition, None, None, None),
6671
}
6772

6873

@@ -76,7 +81,7 @@ def __init__(self, **kwargs):
7681
raise ValueError(f"Unexpected parameter {key}")
7782
passed_and_default_parameters[key] = value
7883

79-
for key, (validator, default_value) in ACCEPTED_PARAMETERS.items():
84+
for key, (validator, default_value, cast, help) in ACCEPTED_PARAMETERS.items():
8085
if key not in passed_and_default_parameters:
8186
passed_and_default_parameters[key] = default_value
8287

0 commit comments

Comments
 (0)