5
5
"""
6
6
7
7
from collections import namedtuple
8
- from datetime import date
8
+ from datetime import date , datetime
9
9
from typing import Optional
10
10
11
11
from .validators import (
12
- Positive , OptionalStrictlyPositive , StrictlyPositive , Rate , Date , OptionalDate
12
+ OptionalValue , Positive , OptionalStrictlyPositive , StrictlyPositive , Rate , Date , OptionalDate , ValDisposition
13
13
)
14
14
15
15
# Parameters for each disposition (hospitalized, icu, ventilated)
@@ -45,62 +45,60 @@ def __init__(self, **kwargs):
45
45
self .population = population
46
46
47
47
48
- class Parameters :
49
- """Parameters."""
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)
53
+ ACCEPTED_PARAMETERS = {
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 ),
71
+ }
50
72
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
- Rate (value = hospitalized .rate ), Rate (value = icu .rate ), Rate (value = ventilated .rate )
73
- StrictlyPositive (value = hospitalized .days ), StrictlyPositive (value = icu .days ),
74
- StrictlyPositive (value = ventilated .days )
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.' )
88
73
89
- if current_date is None :
90
- current_date = date .today ()
91
- self .current_date = Date (value = current_date )
74
+ class Parameters :
75
+ """Parameters."""
92
76
93
- self .date_first_hospitalized = OptionalDate (value = date_first_hospitalized )
94
- self .doubling_time = OptionalStrictlyPositive (value = doubling_time )
77
+ def __init__ (self , ** kwargs ):
78
+ passed_and_default_parameters = {}
79
+ for key , value in kwargs .items ():
80
+ if key not in ACCEPTED_PARAMETERS :
81
+ raise ValueError (f"Unexpected parameter { key } " )
82
+ passed_and_default_parameters [key ] = value
83
+
84
+ for key , (validator , default_value , cast , help ) in ACCEPTED_PARAMETERS .items ():
85
+ if key not in passed_and_default_parameters :
86
+ passed_and_default_parameters [key ] = default_value
87
+
88
+ for key , value in passed_and_default_parameters .items ():
89
+ validator = ACCEPTED_PARAMETERS [key ][0 ]
90
+ try :
91
+ validator (value = value )
92
+ except (TypeError , ValueError ) as ve :
93
+ raise ValueError (f"For parameter '{ key } ', with value '{ value } ', validation returned error \" { ve } \" " )
94
+ setattr (self , key , value )
95
95
96
- self .relative_contact_rate = Rate ( value = relative_contact_rate )
97
- self . mitigation_date = OptionalDate ( value = mitigation_date )
96
+ if self .region is None and self . population is None :
97
+ raise AssertionError ( 'population or regions must be provided.' )
98
98
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 )
99
+ if self .current_date is None :
100
+ self .current_date = date .today ()
101
+ Date (value = self .current_date )
104
102
105
103
self .labels = {
106
104
"hospitalized" : "Hospitalized" ,
@@ -114,7 +112,7 @@ def __init__(
114
112
}
115
113
116
114
self .dispositions = {
117
- "hospitalized" : hospitalized ,
118
- "icu" : icu ,
119
- "ventilated" : ventilated ,
115
+ "hospitalized" : self . hospitalized ,
116
+ "icu" : self . icu ,
117
+ "ventilated" : self . ventilated ,
120
118
}
0 commit comments