@@ -42,13 +42,116 @@ class EconomyReportSpec(BaseModel):
4242
4343
4444class ReportSpecService :
45+ def _validate_schema_version (self , schema_version : int | None ) -> None :
46+ if schema_version != REPORT_SPEC_SCHEMA_VERSION :
47+ raise ValueError (
48+ f"Unsupported report spec schema version: { schema_version } "
49+ )
50+
4551 def _get_report_output_row (self , report_output_id : int ) -> dict | None :
4652 row : Row | None = database .query (
4753 "SELECT * FROM report_outputs WHERE id = ?" ,
4854 (report_output_id ,),
4955 ).fetchone ()
5056 return dict (row ) if row is not None else None
5157
58+ def _validate_report_country (
59+ self ,
60+ report_output : dict ,
61+ simulation_1 : dict ,
62+ simulation_2 : dict | None = None ,
63+ ) -> None :
64+ report_country_id = report_output ["country_id" ]
65+ if simulation_1 ["country_id" ] != report_country_id :
66+ raise ValueError (
67+ "Simulation 1 country must match report output country to build a "
68+ "report spec"
69+ )
70+ if simulation_2 is not None and simulation_2 ["country_id" ] != report_country_id :
71+ raise ValueError (
72+ "Simulation 2 country must match report output country to build a "
73+ "report spec"
74+ )
75+
76+ def _build_household_report_spec (
77+ self ,
78+ report_output : dict ,
79+ report_kind : str ,
80+ simulation_1 : dict ,
81+ simulation_2 : dict | None ,
82+ time_period : str ,
83+ ) -> HouseholdReportSpec :
84+ if simulation_1 ["population_type" ] != "household" :
85+ raise ValueError ("Household report specs require household simulations" )
86+ if (
87+ simulation_2 is not None
88+ and simulation_2 ["population_id" ] != simulation_1 ["population_id" ]
89+ ):
90+ raise ValueError (
91+ "Household comparison report specs require matching household IDs"
92+ )
93+
94+ return HouseholdReportSpec .model_validate (
95+ {
96+ "country_id" : report_output ["country_id" ],
97+ "report_kind" : report_kind ,
98+ "time_period" : time_period ,
99+ "simulation_1" : {
100+ "population_type" : simulation_1 ["population_type" ],
101+ "population_id" : simulation_1 ["population_id" ],
102+ "policy_id" : simulation_1 ["policy_id" ],
103+ },
104+ "simulation_2" : (
105+ {
106+ "population_type" : simulation_2 ["population_type" ],
107+ "population_id" : simulation_2 ["population_id" ],
108+ "policy_id" : simulation_2 ["policy_id" ],
109+ }
110+ if simulation_2 is not None
111+ else None
112+ ),
113+ }
114+ )
115+
116+ def _build_economy_report_spec (
117+ self ,
118+ report_output : dict ,
119+ report_kind : str ,
120+ simulation_1 : dict ,
121+ simulation_2 : dict | None ,
122+ time_period : str ,
123+ dataset : str ,
124+ target : Literal ["general" , "cliff" ],
125+ options : dict [str , Any ] | None ,
126+ ) -> EconomyReportSpec :
127+ if simulation_1 ["population_type" ] != "geography" :
128+ raise ValueError ("Economy report specs require geography simulations" )
129+ if (
130+ simulation_2 is not None
131+ and simulation_2 ["population_id" ] != simulation_1 ["population_id" ]
132+ ):
133+ raise ValueError (
134+ "Economy comparison report specs require matching geography IDs"
135+ )
136+
137+ return EconomyReportSpec .model_validate (
138+ {
139+ "country_id" : report_output ["country_id" ],
140+ "report_kind" : report_kind ,
141+ "time_period" : time_period ,
142+ "region" : simulation_1 ["population_id" ],
143+ "baseline_policy_id" : simulation_1 ["policy_id" ],
144+ "reform_policy_id" : (
145+ simulation_2 ["policy_id" ]
146+ if simulation_2 is not None
147+ else simulation_1 ["policy_id" ]
148+ ),
149+ "dataset" : dataset ,
150+ "target" : target ,
151+ "options" : options or {},
152+ }
153+ )
154+
52155 def infer_report_kind (
53156 self ,
54157 simulation_1 : dict ,
@@ -88,46 +191,26 @@ def build_report_spec(
88191 ) -> ReportSpec :
89192 report_kind = self .infer_report_kind (simulation_1 , simulation_2 )
90193 time_period = report_output ["year" ]
194+ self ._validate_report_country (report_output , simulation_1 , simulation_2 )
91195
92196 if report_kind in HOUSEHOLD_REPORT_KINDS :
93- return HouseholdReportSpec .model_validate (
94- {
95- "country_id" : report_output ["country_id" ],
96- "report_kind" : report_kind ,
97- "time_period" : time_period ,
98- "simulation_1" : {
99- "population_type" : simulation_1 ["population_type" ],
100- "population_id" : simulation_1 ["population_id" ],
101- "policy_id" : simulation_1 ["policy_id" ],
102- },
103- "simulation_2" : (
104- {
105- "population_type" : simulation_2 ["population_type" ],
106- "population_id" : simulation_2 ["population_id" ],
107- "policy_id" : simulation_2 ["policy_id" ],
108- }
109- if simulation_2 is not None
110- else None
111- ),
112- }
197+ return self ._build_household_report_spec (
198+ report_output = report_output ,
199+ report_kind = report_kind ,
200+ simulation_1 = simulation_1 ,
201+ simulation_2 = simulation_2 ,
202+ time_period = time_period ,
113203 )
114204
115- return EconomyReportSpec .model_validate (
116- {
117- "country_id" : report_output ["country_id" ],
118- "report_kind" : report_kind ,
119- "time_period" : time_period ,
120- "region" : simulation_1 ["population_id" ],
121- "baseline_policy_id" : simulation_1 ["policy_id" ],
122- "reform_policy_id" : (
123- simulation_2 ["policy_id" ]
124- if simulation_2 is not None
125- else simulation_1 ["policy_id" ]
126- ),
127- "dataset" : dataset ,
128- "target" : target ,
129- "options" : options or {},
130- }
205+ return self ._build_economy_report_spec (
206+ report_output = report_output ,
207+ report_kind = report_kind ,
208+ simulation_1 = simulation_1 ,
209+ simulation_2 = simulation_2 ,
210+ time_period = time_period ,
211+ dataset = dataset ,
212+ target = target ,
213+ options = options ,
131214 )
132215
133216 def _parse_json_field (self , value : str | dict | None ) -> dict | None :
@@ -149,6 +232,7 @@ def get_report_spec(self, report_output_id: int) -> ReportSpec | None:
149232 if report_output is None or report_output ["report_spec_json" ] is None :
150233 return None
151234
235+ self ._validate_schema_version (report_output ["report_spec_schema_version" ])
152236 raw_spec = self ._parse_json_field (report_output ["report_spec_json" ])
153237 return self ._parse_report_spec (report_output ["report_kind" ], raw_spec )
154238
@@ -161,6 +245,7 @@ def set_report_spec(
161245 ) -> bool :
162246 if report_spec_status not in REPORT_SPEC_STATUSES :
163247 raise ValueError (f"Unsupported report spec status: { report_spec_status } " )
248+ self ._validate_schema_version (schema_version )
164249
165250 report_output = self ._get_report_output_row (report_output_id )
166251 if report_output is None :
0 commit comments