7373 datasets ["us" ][dataset ] = f"{ datasets ['us' ][dataset ]} @{ us_dataset_version } "
7474
7575
76- run_api_v2 = os .environ .get ("GOOGLE_APPLICATION_CREDENTIALS" ) is not None
77-
78- if not run_api_v2 :
79- logger .log_text (
80- "Didn't find any GOOGLE_APPLICATION_CREDENTIALS, so will not run results with APIv2." ,
81- severity = "WARNING" ,
82- )
83-
84-
8576class CalculateEconomySimulationJob (BaseJob ):
8677 def __init__ (self ):
8778 super ().__init__ ()
88- if run_api_v2 :
89- self .api_v2 = SimulationAPIv2 ()
79+ if os .environ .get ("GOOGLE_APPLICATION_CREDENTIALS" ) is None :
80+ logger .log_text (
81+ "GOOGLE_APPLICATION_CREDENTIALS not set; unable to run simulation API." ,
82+ severity = "ERROR" ,
83+ )
84+ raise ValueError (
85+ "GOOGLE_APPLICATION_CREDENTIALS not set; unable to run simulation API."
86+ )
87+ self .sim_api = SimulationAPI ()
9088
9189 def run (
9290 self ,
@@ -99,11 +97,16 @@ def run(
9997 options : dict ,
10098 baseline_policy : dict ,
10199 reform_policy : Annotated [str , "String-formatted JSON" ],
100+ run_api_v1 : bool = False ,
102101 ):
103102 job_id = self ._set_job_id ()
104103 job_setup_options = {
105104 "job_id" : job_id ,
106- "job_type" : "CALCULATE_ECONOMY_SIMULATION_JOB" ,
105+ "job_type" : (
106+ "CALCULATE_ECONOMY_SIMULATION_JOB"
107+ if not run_api_v1
108+ else "CALCULATE_ECONOMY_SIMULATION_JOB_V1"
109+ ),
107110 "baseline_policy_id" : baseline_policy_id ,
108111 "reform_policy_id" : policy_id ,
109112 "country_id" : country_id ,
@@ -113,6 +116,13 @@ def run(
113116 "options" : options ,
114117 "baseline_policy" : baseline_policy ,
115118 "reform_policy" : reform_policy ,
119+ "workflow_id" : None ,
120+ "model_version" : COUNTRY_PACKAGE_VERSIONS [country_id ],
121+ "data_version" : (
122+ uk_dataset_version
123+ if country_id == "uk"
124+ else us_dataset_version
125+ ),
116126 }
117127 logger .log_struct (
118128 {
@@ -122,6 +132,7 @@ def run(
122132 severity = "INFO" ,
123133 )
124134
135+ options_hash = None
125136 try :
126137 # Configure inputs
127138 # Note for anyone modifying options_hash: redis-queue treats ":" as a namespace
@@ -159,17 +170,6 @@ def run(
159170 )
160171 return
161172
162- # Save identifiers for later commenting on processing status
163- identifiers = (
164- country_id ,
165- policy_id ,
166- baseline_policy_id ,
167- region ,
168- dataset ,
169- time_period ,
170- options_hash ,
171- )
172-
173173 logger .log_struct (
174174 {
175175 "message" : "No existing completed result found, proceeding with computation" ,
@@ -224,120 +224,90 @@ def run(
224224 ),
225225 )
226226
227+ # TODO: Figure out how to handle this better
228+ # If using API v1, run that job
229+ if run_api_v1 :
230+ logger .log_struct (
231+ {
232+ "message" : "Running API v1 job" ,
233+ ** job_setup_options ,
234+ }
235+ )
236+ impact = self ._run_v1_job (
237+ job_id = job_id ,
238+ baseline_policy_id = baseline_policy_id ,
239+ reform_policy_id = policy_id ,
240+ country_id = country_id ,
241+ region = region ,
242+ dataset = dataset ,
243+ time_period = time_period ,
244+ options = options ,
245+ baseline_policy = baseline_policy ,
246+ reform_policy = reform_policy ,
247+ )
248+ reform_impacts_service .set_complete_reform_impact (
249+ country_id = country_id ,
250+ reform_policy_id = policy_id ,
251+ baseline_policy_id = baseline_policy_id ,
252+ region = region ,
253+ dataset = dataset ,
254+ time_period = time_period ,
255+ options_hash = options_hash ,
256+ reform_impact_json = impact ,
257+ )
258+ return
259+
260+ # Set up sim API job
227261 logger .log_struct (
228262 {
229- "message" : "Starting computation of baseline policy " ,
263+ "message" : "Setting up sim API job " ,
230264 ** job_setup_options ,
231265 }
232266 )
267+ sim_config : dict [str , Any ] = self .sim_api ._setup_sim_options (
268+ country_id = country_id ,
269+ scope = "macro" ,
270+ reform_policy = reform_policy ,
271+ baseline_policy = baseline_policy ,
272+ time_period = time_period ,
273+ region = region ,
274+ dataset = dataset ,
275+ model_version = COUNTRY_PACKAGE_VERSIONS [country_id ],
276+ data_version = (
277+ uk_dataset_version
278+ if country_id == "uk"
279+ else us_dataset_version
280+ ),
281+ )
233282
234- # If comparing against API v2, start job
235- if run_api_v2 :
236-
237- try :
238- # Populate v2/v1 comparison config data; we will pass this
239- # to GCP logs either on error or success
240- comparison_data = {
241- ** job_setup_options ,
242- "v1_country_package_version" : COUNTRY_PACKAGE_VERSIONS [
243- country_id
244- ],
245- "v2_id" : None , # Unavailable until job starts
246- "v2_country_package_version" : None , # Unavailable until job completes
247- }
283+ sim_api_execution = self .sim_api .run (sim_config )
284+ execution_id = self .sim_api .get_execution_id (sim_api_execution )
248285
249- # Set up APIv2 job
250- logger .log_struct (
251- {
252- "message" : "Setting up APIv2 job" ,
253- ** comparison_data ,
254- }
255- )
256- sim_config : dict [str , Any ] = (
257- self .api_v2 ._setup_sim_options (
258- country_id = country_id ,
259- scope = "macro" ,
260- reform_policy = reform_policy ,
261- baseline_policy = baseline_policy ,
262- time_period = time_period ,
263- region = region ,
264- dataset = dataset ,
265- model_version = COUNTRY_PACKAGE_VERSIONS [country_id ],
266- data_version = (
267- uk_dataset_version
268- if country_id == "uk"
269- else us_dataset_version
270- ),
271- )
272- )
286+ job_setup_options ["workflow_id" ] = execution_id
273287
274- api_v2_execution = self .api_v2 .run (sim_config )
275- execution_id = self .api_v2 .get_execution_id (
276- api_v2_execution
277- )
288+ progress_log = {
289+ ** job_setup_options ,
290+ "message" : "Sim API job started" ,
291+ }
292+ logger .log_struct (progress_log , severity = "INFO" )
278293
279- comparison_data ["v2_id" ] = execution_id
280-
281- # Pass name and status to logs
282- progress_log : V2V1Comparison = (
283- V2V1Comparison .model_validate (
284- {
285- ** comparison_data ,
286- "v1_impact" : None ,
287- "v2_impact" : None ,
288- "v1_v2_diff" : None ,
289- "message" : "APIv2 job started" ,
290- }
291- )
292- )
293- logger .log_struct (
294- progress_log .model_dump (mode = "json" ), severity = "INFO"
295- )
296- except Exception as e :
297- try :
298- trace = traceback .format_exc ()
299- # Send error log to GCP
300- error_log : V2V1Comparison = (
301- V2V1Comparison .model_validate (
302- {
303- ** comparison_data ,
304- "v2_error" : trace ,
305- }
306- )
307- )
308- logger .log_struct (
309- error_log .model_dump (mode = "json" ), severity = "ERROR"
310- )
311- except Exception as e2 :
312- print ("Something really wrong." , e2 )
313- pass
314294 # Wait for job to complete
315- if run_api_v2 :
316-
317- try :
318- execution_id : str = self .api_v2 .get_execution_id (
319- api_v2_execution
320- )
321- api_v2_output = self .api_v2 .wait_for_completion (
322- api_v2_execution
323- )
295+ sim_api_output = None
296+ try :
297+ sim_api_output = self .sim_api .wait_for_completion (
298+ sim_api_execution
299+ )
324300
325- except Exception as e :
326- trace = traceback .format_exc ()
327- # If job fails, send error log to GCP
328- error_log : V2V1Comparison = V2V1Comparison .model_validate (
329- {
330- ** comparison_data ,
331- "v2_error" : trace ,
332- "v1_impact" : None ,
333- "v2_impact" : None ,
334- "v1_v2_diff" : None ,
335- "message" : "APIv2 job failed" ,
336- }
337- )
338- logger .log_struct (
339- error_log .model_dump (mode = "json" ), severity = "ERROR"
340- )
301+ except Exception as e :
302+ trace = traceback .format_exc ()
303+ # If job fails, send error log to GCP
304+ error_log = {
305+ ** job_setup_options ,
306+ "message" : "Sim API job failed" ,
307+ "error" : str (e ),
308+ "traceback" : trace ,
309+ }
310+ logger .log_struct (error_log , severity = "ERROR" )
341311
342312 # Finally, update all reform impact rows with the same baseline and reform policy IDs
343313 reform_impacts_service .set_complete_reform_impact (
@@ -348,7 +318,7 @@ def run(
348318 dataset = dataset ,
349319 time_period = time_period ,
350320 options_hash = options_hash ,
351- reform_impact_json = json .dumps (api_v2_output ),
321+ reform_impact_json = json .dumps (sim_api_output ),
352322 )
353323
354324 except Exception as e :
@@ -371,6 +341,66 @@ def run(
371341 )
372342 raise e
373343
344+ # Fallback method to be invoked via URL search param; otherwise,
345+ # to be deprecated in favor of new simulation API
346+ def _run_v1_job (
347+ self ,
348+ country_id : str ,
349+ region : str ,
350+ dataset : str ,
351+ time_period : str ,
352+ options : dict ,
353+ baseline_policy : dict ,
354+ reform_policy : Annotated [str , "String-formatted JSON" ],
355+ job_setup_options : dict [
356+ str , Any
357+ ] = {}, # Dictionary of setup options for logging purposes
358+ ) -> dict [str , Any ]:
359+ # Compute baseline economy
360+ logger .log_struct (
361+ {
362+ "message" : "Computing baseline economy using v1..." ,
363+ ** job_setup_options ,
364+ }
365+ )
366+ baseline_economy = self ._compute_economy_v1 (
367+ country_id = country_id ,
368+ region = region ,
369+ dataset = dataset ,
370+ time_period = time_period ,
371+ options = options ,
372+ policy_json = baseline_policy ,
373+ )
374+
375+ # Compute reform economy
376+ logger .log_struct (
377+ {
378+ "message" : "Computing reform economy using v1..." ,
379+ ** job_setup_options ,
380+ }
381+ )
382+ reform_economy = self ._compute_economy_v1 (
383+ country_id = country_id ,
384+ region = region ,
385+ dataset = dataset ,
386+ time_period = time_period ,
387+ options = options ,
388+ policy_json = reform_policy ,
389+ )
390+
391+ baseline_economy = baseline_economy ["result" ]
392+ reform_economy = reform_economy ["result" ]
393+ logger .log_struct (
394+ {
395+ "message" : "Computed baseline and reform economies using v1" ,
396+ ** job_setup_options ,
397+ }
398+ )
399+ impact : dict [str , Any ] = compare_economic_outputs (
400+ baseline_economy , reform_economy , country_id = country_id
401+ )
402+ return impact
403+
374404 def _set_job_id (self ) -> str :
375405 """
376406 Generate a unique job ID based on the current timestamp and a random number.
@@ -380,7 +410,8 @@ def _set_job_id(self) -> str:
380410 random_number = np .random .randint (1000 , 9999 )
381411 return f"job_{ timestamp } _{ random_number } "
382412
383- def _compute_economy (
413+ # To be deprecated in favor of new simulation API
414+ def _compute_economy_v1 (
384415 self , country_id , region , dataset , time_period , options , policy_json
385416 ):
386417 try :
@@ -615,7 +646,7 @@ def _compute_cliff_impacts(self, simulation: Microsimulation) -> Dict:
615646 }
616647
617648
618- class SimulationAPIv2 :
649+ class SimulationAPI :
619650 project : str
620651 location : str
621652 workflow : str
0 commit comments