Skip to content

Commit ad577e6

Browse files
committed
Make parameter setting and validation almost completely declarative
1 parent 555870c commit ad577e6

File tree

3 files changed

+58
-56
lines changed

3 files changed

+58
-56
lines changed

src/penn_chime/parameters.py

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Optional
1010

1111
from .validators import (
12-
Positive, OptionalStrictlyPositive, StrictlyPositive, Rate, Date, OptionalDate, ValDisposition
12+
OptionalValue, Positive, OptionalStrictlyPositive, StrictlyPositive, Rate, Date, OptionalDate, ValDisposition
1313
)
1414

1515
# Parameters for each disposition (hospitalized, icu, ventilated)
@@ -45,62 +45,55 @@ def __init__(self, **kwargs):
4545
self.population = population
4646

4747

48-
class Parameters:
49-
"""Parameters."""
48+
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),
5062

51-
def __init__(
52-
self,
53-
*,
54-
current_hospitalized: int,
55-
hospitalized: Disposition,
56-
icu: Disposition,
57-
relative_contact_rate: float,
58-
mitigation_date: Optional[date] = None,
59-
ventilated: Disposition,
60-
current_date: Optional[date] = None,
61-
date_first_hospitalized: Optional[date] = None,
62-
doubling_time: Optional[float] = None,
63-
infectious_days: int = 14,
64-
market_share: float = 1.0,
65-
max_y_axis: Optional[int] = None,
66-
n_days: int = 100,
67-
population: Optional[int] = None,
68-
recovered: int = 0,
69-
region: Optional[Regions] = None,
70-
):
71-
self.current_hospitalized = Positive(value=current_hospitalized)
72-
ValDisposition(value=hospitalized)
73-
ValDisposition(value=icu)
74-
ValDisposition(value=ventilated)
75-
76-
self.hospitalized = hospitalized
77-
self.icu = icu
78-
self.ventilated = ventilated
79-
80-
if region is not None and population is None:
81-
self.region = region
82-
self.population = StrictlyPositive(value=region.population)
83-
elif population is not None:
84-
self.region = None
85-
self.population = StrictlyPositive(value=population)
86-
else:
87-
raise AssertionError('population or regions must be provided.')
63+
"hospitalized": (ValDisposition, None),
64+
"icu": (ValDisposition, None),
65+
"ventilated": (ValDisposition, None),
66+
}
8867

89-
if current_date is None:
90-
current_date = date.today()
91-
self.current_date = Date(value=current_date)
9268

93-
self.date_first_hospitalized = OptionalDate(value=date_first_hospitalized)
94-
self.doubling_time = OptionalStrictlyPositive(value=doubling_time)
69+
class Parameters:
70+
"""Parameters."""
71+
72+
def __init__(self, **kwargs):
73+
passed_and_default_parameters = {}
74+
for key, value in kwargs.items():
75+
if key not in ACCEPTED_PARAMETERS:
76+
raise ValueError(f"Unexpected parameter {key}")
77+
passed_and_default_parameters[key] = value
78+
79+
for key, (validator, default_value) in ACCEPTED_PARAMETERS.items():
80+
if key not in passed_and_default_parameters:
81+
passed_and_default_parameters[key] = default_value
82+
83+
for key, value in passed_and_default_parameters.items():
84+
validator = ACCEPTED_PARAMETERS[key][0]
85+
try:
86+
validator(value=value)
87+
except (TypeError, ValueError) as ve:
88+
raise ValueError(f"For parameter {key}, with value {value}, validation returned error \"{ve}\"")
89+
setattr(self, key, value)
9590

96-
self.relative_contact_rate = Rate(value=relative_contact_rate)
97-
self.mitigation_date = OptionalDate(value=mitigation_date)
91+
if self.region is None and self.population is None:
92+
raise AssertionError('population or regions must be provided.')
9893

99-
self.infectious_days = StrictlyPositive(value=infectious_days)
100-
self.market_share = Rate(value=market_share)
101-
self.max_y_axis = OptionalStrictlyPositive(value=max_y_axis)
102-
self.n_days = StrictlyPositive(value=n_days)
103-
self.recovered = Positive(value=recovered)
94+
if self.current_date is None:
95+
self.current_date = date.today()
96+
Date(value=self.current_date)
10497

10598
self.labels = {
10699
"hospitalized": "Hospitalized",
@@ -114,7 +107,7 @@ def __init__(
114107
}
115108

116109
self.dispositions = {
117-
"hospitalized": hospitalized,
118-
"icu": icu,
119-
"ventilated": ventilated,
110+
"hospitalized": self.hospitalized,
111+
"icu": self.icu,
112+
"ventilated": self.ventilated,
120113
}

src/penn_chime/validators/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""the callable validator design pattern"""
22

3-
from .validators import EPSILON, Bounded, OptionalBounded, Rate, Date, OptionalDate, ValDisposition
3+
from .validators import EPSILON, OptionalValue, Bounded, OptionalBounded, Rate, Date, OptionalDate, ValDisposition
44

5+
OptionalValue = OptionalValue()
56
OptionalStrictlyPositive = OptionalBounded(lower_bound=EPSILON)
67
StrictlyPositive = Bounded(lower_bound=EPSILON)
78
Positive = Bounded(lower_bound=-EPSILON)

src/penn_chime/validators/validators.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77

88
EPSILON = 1.e-7
99

10+
class OptionalValue(Validator):
11+
"""Any value at all"""
12+
def __init__(self) -> None:
13+
pass
14+
15+
def validate(self, value):
16+
pass
17+
1018
class Bounded(Validator):
1119
"""A bounded number."""
1220
def __init__(

0 commit comments

Comments
 (0)