@@ -1114,6 +1114,137 @@ def test_patch_report_output_explicit_run_id_updates_only_that_run(test_db):
11141114 assert rerun_after ["output" ] == json .dumps ({"result" : "explicit rerun" })
11151115
11161116
1117+ def test_patch_report_output_explicit_run_id_through_legacy_id_updates_canonical_run (
1118+ test_db ,
1119+ ):
1120+ simulation = simulation_service .create_simulation (
1121+ country_id = "us" ,
1122+ population_id = "household_route_legacy_explicit_report_run" ,
1123+ population_type = "household" ,
1124+ policy_id = 79 ,
1125+ )
1126+ canonical_report = report_output_service .create_report_output (
1127+ country_id = "us" ,
1128+ simulation_1_id = simulation ["id" ],
1129+ simulation_2_id = None ,
1130+ year = "2026" ,
1131+ )
1132+ report_output_service .update_report_output (
1133+ country_id = "us" ,
1134+ report_id = canonical_report ["id" ],
1135+ status = "complete" ,
1136+ output = json .dumps ({"result" : "initial" }),
1137+ )
1138+ initial_run = test_db .query (
1139+ "SELECT * FROM report_output_runs WHERE report_output_id = ?" ,
1140+ (canonical_report ["id" ],),
1141+ ).fetchone ()
1142+ rerun = report_run_service .create_report_output_run (
1143+ canonical_report ["id" ], trigger_type = "rerun"
1144+ )
1145+ test_db .query (
1146+ """
1147+ INSERT INTO report_outputs (
1148+ id, country_id, simulation_1_id, simulation_2_id, api_version, status, year,
1149+ report_identity_hash, report_identity_schema_version
1150+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1151+ """ ,
1152+ (
1153+ 3002 ,
1154+ "us" ,
1155+ simulation ["id" ],
1156+ None ,
1157+ "legacy-report-cache-version" ,
1158+ "complete" ,
1159+ "2026" ,
1160+ canonical_report ["report_identity_hash" ],
1161+ canonical_report ["report_identity_schema_version" ],
1162+ ),
1163+ )
1164+ report_output_id_map_service .set_mapping (
1165+ legacy_report_output_id = 3002 ,
1166+ canonical_report_output_id = canonical_report ["id" ],
1167+ )
1168+
1169+ client = create_test_client ()
1170+ response = client .patch (
1171+ "/us/report" ,
1172+ json = {
1173+ "id" : 3002 ,
1174+ "report_output_run_id" : rerun ["id" ],
1175+ "status" : "complete" ,
1176+ "output" : json .dumps ({"result" : "legacy explicit rerun" }),
1177+ },
1178+ )
1179+
1180+ assert response .status_code == 200
1181+ payload = response .get_json ()
1182+ assert payload ["result" ]["id" ] == 3002
1183+
1184+ initial_run_after = test_db .query (
1185+ "SELECT * FROM report_output_runs WHERE id = ?" ,
1186+ (initial_run ["id" ],),
1187+ ).fetchone ()
1188+ rerun_after = test_db .query (
1189+ "SELECT * FROM report_output_runs WHERE id = ?" ,
1190+ (rerun ["id" ],),
1191+ ).fetchone ()
1192+ assert initial_run_after ["output" ] == json .dumps ({"result" : "initial" })
1193+ assert rerun_after ["report_output_id" ] == canonical_report ["id" ]
1194+ assert rerun_after ["output" ] == json .dumps ({"result" : "legacy explicit rerun" })
1195+
1196+
1197+ def test_create_report_rerun_via_canonical_id_creates_canonical_linked_runs (test_db ):
1198+ simulation = simulation_service .create_simulation (
1199+ country_id = "us" ,
1200+ population_id = "household_route_canonical_rerun" ,
1201+ population_type = "household" ,
1202+ policy_id = 80 ,
1203+ )
1204+ canonical_report = report_output_service .create_report_output (
1205+ country_id = "us" ,
1206+ simulation_1_id = simulation ["id" ],
1207+ simulation_2_id = None ,
1208+ year = "2026" ,
1209+ )
1210+ report_output_service .update_report_output (
1211+ country_id = "us" ,
1212+ report_id = canonical_report ["id" ],
1213+ status = "complete" ,
1214+ output = json .dumps ({"result" : "canonical" }),
1215+ )
1216+
1217+ client = create_test_client ()
1218+ response = client .post (f"/us/report/{ canonical_report ['id' ]} /rerun" , json = {})
1219+
1220+ assert response .status_code == 201
1221+ result = response .get_json ()["result" ]
1222+ assert result ["requested_report_output_id" ] == canonical_report ["id" ]
1223+ assert result ["report_output_id" ] == canonical_report ["id" ]
1224+ assert len (result ["simulation_run_ids" ]) == 1
1225+
1226+ report_runs = test_db .query (
1227+ """
1228+ SELECT * FROM report_output_runs
1229+ WHERE report_output_id = ?
1230+ ORDER BY run_sequence
1231+ """ ,
1232+ (canonical_report ["id" ],),
1233+ ).fetchall ()
1234+ assert len (report_runs ) == 2
1235+ assert report_runs [0 ]["trigger_type" ] == "initial"
1236+ assert report_runs [1 ]["id" ] == result ["report_output_run_id" ]
1237+ assert report_runs [1 ]["trigger_type" ] == "rerun"
1238+ assert report_runs [1 ]["status" ] == "pending"
1239+
1240+ simulation_run = test_db .query (
1241+ "SELECT * FROM simulation_runs WHERE id = ?" ,
1242+ (result ["simulation_run_ids" ][0 ],),
1243+ ).fetchone ()
1244+ assert simulation_run ["report_output_run_id" ] == result ["report_output_run_id" ]
1245+ assert simulation_run ["input_position" ] == 1
1246+
1247+
11171248def test_create_report_rerun_via_legacy_id_creates_canonical_linked_runs (test_db ):
11181249 simulation = simulation_service .create_simulation (
11191250 country_id = "us" ,
@@ -1181,6 +1312,174 @@ def test_create_report_rerun_via_legacy_id_creates_canonical_linked_runs(test_db
11811312 assert simulation_run ["input_position" ] == 1
11821313
11831314
1315+ def test_create_report_rerun_for_comparison_report_creates_two_linked_simulation_runs (
1316+ test_db ,
1317+ ):
1318+ baseline_simulation = simulation_service .create_simulation (
1319+ country_id = "us" ,
1320+ population_id = "state/nc" ,
1321+ population_type = "geography" ,
1322+ policy_id = 81 ,
1323+ )
1324+ reform_simulation = simulation_service .create_simulation (
1325+ country_id = "us" ,
1326+ population_id = "state/nc" ,
1327+ population_type = "geography" ,
1328+ policy_id = 82 ,
1329+ )
1330+ report = report_output_service .create_report_output (
1331+ country_id = "us" ,
1332+ simulation_1_id = baseline_simulation ["id" ],
1333+ simulation_2_id = reform_simulation ["id" ],
1334+ year = "2026" ,
1335+ )
1336+ report_output_service .update_report_output (
1337+ country_id = "us" ,
1338+ report_id = report ["id" ],
1339+ status = "complete" ,
1340+ output = json .dumps ({"result" : "comparison" }),
1341+ )
1342+
1343+ client = create_test_client ()
1344+ response = client .post (f"/us/report/{ report ['id' ]} /rerun" , json = {})
1345+
1346+ assert response .status_code == 201
1347+ result = response .get_json ()["result" ]
1348+ assert result ["report_output_id" ] == report ["id" ]
1349+ assert len (result ["simulation_run_ids" ]) == 2
1350+
1351+ linked_simulation_runs = test_db .query (
1352+ """
1353+ SELECT * FROM simulation_runs
1354+ WHERE report_output_run_id = ?
1355+ ORDER BY input_position
1356+ """ ,
1357+ (result ["report_output_run_id" ],),
1358+ ).fetchall ()
1359+ assert [run ["simulation_id" ] for run in linked_simulation_runs ] == [
1360+ baseline_simulation ["id" ],
1361+ reform_simulation ["id" ],
1362+ ]
1363+ assert [run ["input_position" ] for run in linked_simulation_runs ] == [1 , 2 ]
1364+ assert [run ["status" ] for run in linked_simulation_runs ] == [
1365+ "pending" ,
1366+ "pending" ,
1367+ ]
1368+
1369+
1370+ def test_create_report_rerun_rejects_report_with_missing_linked_simulation (test_db ):
1371+ test_db .query (
1372+ """
1373+ INSERT INTO report_outputs (
1374+ country_id, simulation_1_id, simulation_2_id, api_version, status, year
1375+ ) VALUES (?, ?, ?, ?, ?, ?)
1376+ """ ,
1377+ ("us" , 987654 , None , get_report_output_cache_version ("us" ), "complete" , "2026" ),
1378+ )
1379+ report = test_db .query (
1380+ "SELECT * FROM report_outputs ORDER BY id DESC LIMIT 1"
1381+ ).fetchone ()
1382+
1383+ client = create_test_client ()
1384+ response = client .post (f"/us/report/{ report ['id' ]} /rerun" , json = {})
1385+
1386+ assert response .status_code == 400
1387+ assert "Simulation #987654 not found" in response .get_data (as_text = True )
1388+
1389+ report_runs = test_db .query (
1390+ "SELECT * FROM report_output_runs WHERE report_output_id = ?" ,
1391+ (report ["id" ],),
1392+ ).fetchall ()
1393+ assert report_runs == []
1394+
1395+
1396+ def test_report_rerun_http_lifecycle_patches_linked_runs_and_reads_display (
1397+ test_db ,
1398+ ):
1399+ client = create_test_client ()
1400+ simulation_response = client .post (
1401+ "/us/simulation" ,
1402+ json = {
1403+ "population_id" : "household_route_http_lifecycle" ,
1404+ "population_type" : "household" ,
1405+ "policy_id" : 83 ,
1406+ },
1407+ )
1408+ assert simulation_response .status_code == 201
1409+ simulation = simulation_response .get_json ()["result" ]
1410+
1411+ report_response = client .post (
1412+ "/us/report" ,
1413+ json = {
1414+ "simulation_1_id" : simulation ["id" ],
1415+ "simulation_2_id" : None ,
1416+ "year" : "2026" ,
1417+ },
1418+ )
1419+ assert report_response .status_code == 201
1420+ report = report_response .get_json ()["result" ]
1421+
1422+ initial_patch_response = client .patch (
1423+ "/us/report" ,
1424+ json = {
1425+ "id" : report ["id" ],
1426+ "status" : "complete" ,
1427+ "output" : json .dumps ({"result" : "initial report" }),
1428+ },
1429+ )
1430+ assert initial_patch_response .status_code == 200
1431+
1432+ rerun_response = client .post (f"/us/report/{ report ['id' ]} /rerun" , json = {})
1433+ assert rerun_response .status_code == 201
1434+ rerun = rerun_response .get_json ()["result" ]
1435+ assert len (rerun ["simulation_run_ids" ]) == 1
1436+
1437+ simulation_patch_response = client .patch (
1438+ "/us/simulation" ,
1439+ json = {
1440+ "id" : simulation ["id" ],
1441+ "simulation_run_id" : rerun ["simulation_run_ids" ][0 ],
1442+ "status" : "complete" ,
1443+ "output" : json .dumps ({"result" : "rerun simulation" }),
1444+ },
1445+ )
1446+ assert simulation_patch_response .status_code == 200
1447+
1448+ report_patch_response = client .patch (
1449+ "/us/report" ,
1450+ json = {
1451+ "id" : report ["id" ],
1452+ "report_output_run_id" : rerun ["report_output_run_id" ],
1453+ "status" : "complete" ,
1454+ "output" : json .dumps ({"result" : "rerun report" }),
1455+ },
1456+ )
1457+ assert report_patch_response .status_code == 200
1458+
1459+ get_response = client .get (f"/us/report/{ report ['id' ]} " )
1460+ assert get_response .status_code == 200
1461+ result = get_response .get_json ()["result" ]
1462+ assert result ["id" ] == report ["id" ]
1463+ assert result ["status" ] == "complete"
1464+ assert result ["output" ] == json .dumps ({"result" : "rerun report" })
1465+
1466+ report_rows = test_db .query ("SELECT * FROM report_outputs" ).fetchall ()
1467+ report_runs = test_db .query (
1468+ "SELECT * FROM report_output_runs WHERE report_output_id = ?" ,
1469+ (report ["id" ],),
1470+ ).fetchall ()
1471+ linked_simulation_runs = test_db .query (
1472+ """
1473+ SELECT * FROM simulation_runs
1474+ WHERE report_output_run_id = ?
1475+ """ ,
1476+ (rerun ["report_output_run_id" ],),
1477+ ).fetchall ()
1478+ assert len (report_rows ) == 1
1479+ assert len (report_runs ) == 2
1480+ assert len (linked_simulation_runs ) == 1
1481+
1482+
11841483def test_patch_report_output_persists_run_metadata_fields (test_db ):
11851484 simulation = simulation_service .create_simulation (
11861485 country_id = "us" ,
0 commit comments