|
58 | 58 | build_role_donor_composites, |
59 | 59 | summarize_realized_clone_translation, |
60 | 60 | ) |
| 61 | +from policyengine_us_data.datasets.cps.long_term.run_household_projection_parallel import ( |
| 62 | + merge_outputs, |
| 63 | + parse_years, |
| 64 | + validate_forwarded_args, |
| 65 | + year_output_dir, |
| 66 | +) |
61 | 67 |
|
62 | 68 |
|
63 | 69 | class ExplodingCalibrator: |
@@ -1246,6 +1252,155 @@ def test_write_support_augmentation_report_custom_filename(tmp_path): |
1246 | 1252 | assert loaded["target_year"] == 2090 |
1247 | 1253 |
|
1248 | 1254 |
|
| 1255 | +def test_parallel_projection_parse_years_supports_ranges_and_sorting(): |
| 1256 | + assert parse_years("2030,2028-2029,2030,2027") == [2027, 2028, 2029, 2030] |
| 1257 | + |
| 1258 | + |
| 1259 | +def test_parallel_projection_validate_forwarded_args_rejects_wrapper_flags(): |
| 1260 | + with pytest.raises(ValueError, match="--output-dir"): |
| 1261 | + validate_forwarded_args(["--output-dir", "/tmp/out"]) |
| 1262 | + with pytest.raises(ValueError, match="--save-h5"): |
| 1263 | + validate_forwarded_args(["--save-h5"]) |
| 1264 | + |
| 1265 | + |
| 1266 | +def _write_parallel_temp_year( |
| 1267 | + *, |
| 1268 | + root, |
| 1269 | + year, |
| 1270 | + profile, |
| 1271 | + audit, |
| 1272 | + target_source=None, |
| 1273 | + tax_assumption=None, |
| 1274 | + support_augmentation=None, |
| 1275 | +): |
| 1276 | + temp_output_dir = year_output_dir(root, year) |
| 1277 | + temp_output_dir.mkdir(parents=True, exist_ok=True) |
| 1278 | + year_h5 = temp_output_dir / f"{year}.h5" |
| 1279 | + year_h5.write_text("", encoding="utf-8") |
| 1280 | + metadata_path = write_year_metadata( |
| 1281 | + year_h5, |
| 1282 | + year=year, |
| 1283 | + base_dataset_path="test.h5", |
| 1284 | + profile=profile, |
| 1285 | + calibration_audit=audit, |
| 1286 | + target_source=target_source, |
| 1287 | + tax_assumption=tax_assumption, |
| 1288 | + support_augmentation=support_augmentation, |
| 1289 | + ) |
| 1290 | + update_dataset_manifest( |
| 1291 | + temp_output_dir, |
| 1292 | + year=year, |
| 1293 | + h5_path=year_h5, |
| 1294 | + metadata_path=metadata_path, |
| 1295 | + base_dataset_path="test.h5", |
| 1296 | + profile=profile, |
| 1297 | + calibration_audit=audit, |
| 1298 | + target_source=target_source, |
| 1299 | + tax_assumption=tax_assumption, |
| 1300 | + support_augmentation=support_augmentation, |
| 1301 | + ) |
| 1302 | + |
| 1303 | + |
| 1304 | +def test_parallel_projection_merge_outputs_rebuilds_manifest(tmp_path): |
| 1305 | + profile = get_profile("ss-payroll-tob").to_dict() |
| 1306 | + audit = { |
| 1307 | + "method_used": "entropy", |
| 1308 | + "fell_back_to_ipf": False, |
| 1309 | + "age_max_pct_error": 0.0, |
| 1310 | + "negative_weight_pct": 0.0, |
| 1311 | + "positive_weight_count": 70000, |
| 1312 | + "effective_sample_size": 5000.0, |
| 1313 | + "top_10_weight_share_pct": 1.5, |
| 1314 | + "top_100_weight_share_pct": 10.0, |
| 1315 | + "max_constraint_pct_error": 0.0, |
| 1316 | + "constraints": {}, |
| 1317 | + "validation_passed": True, |
| 1318 | + "validation_issues": [], |
| 1319 | + "calibration_quality": "exact", |
| 1320 | + } |
| 1321 | + target_source = { |
| 1322 | + "name": "oact_2025_08_05_provisional", |
| 1323 | + "source_type": "oact_note", |
| 1324 | + } |
| 1325 | + tax_assumption = { |
| 1326 | + "name": "trustees-core-thresholds-v1", |
| 1327 | + "start_year": 2035, |
| 1328 | + "end_year": 2100, |
| 1329 | + } |
| 1330 | + |
| 1331 | + _write_parallel_temp_year( |
| 1332 | + root=tmp_path, |
| 1333 | + year=2045, |
| 1334 | + profile=profile, |
| 1335 | + audit=audit, |
| 1336 | + target_source=target_source, |
| 1337 | + tax_assumption=tax_assumption, |
| 1338 | + ) |
| 1339 | + _write_parallel_temp_year( |
| 1340 | + root=tmp_path, |
| 1341 | + year=2049, |
| 1342 | + profile=profile, |
| 1343 | + audit=audit, |
| 1344 | + target_source=target_source, |
| 1345 | + tax_assumption=tax_assumption, |
| 1346 | + ) |
| 1347 | + |
| 1348 | + manifest_path = merge_outputs( |
| 1349 | + years=[2045, 2049], |
| 1350 | + output_root=tmp_path, |
| 1351 | + keep_temp=False, |
| 1352 | + ) |
| 1353 | + |
| 1354 | + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) |
| 1355 | + assert manifest["years"] == [2045, 2049] |
| 1356 | + assert manifest["target_source"]["name"] == "oact_2025_08_05_provisional" |
| 1357 | + assert manifest["tax_assumption"]["name"] == "trustees-core-thresholds-v1" |
| 1358 | + assert (tmp_path / "2045.h5").exists() |
| 1359 | + assert (tmp_path / "2049.h5.metadata.json").exists() |
| 1360 | + assert not (tmp_path / ".parallel_tmp").exists() |
| 1361 | + |
| 1362 | + |
| 1363 | +def test_parallel_projection_merge_outputs_rejects_mismatched_contract(tmp_path): |
| 1364 | + profile = get_profile("ss-payroll-tob").to_dict() |
| 1365 | + audit = { |
| 1366 | + "method_used": "entropy", |
| 1367 | + "fell_back_to_ipf": False, |
| 1368 | + "age_max_pct_error": 0.0, |
| 1369 | + "negative_weight_pct": 0.0, |
| 1370 | + "positive_weight_count": 70000, |
| 1371 | + "effective_sample_size": 5000.0, |
| 1372 | + "top_10_weight_share_pct": 1.5, |
| 1373 | + "top_100_weight_share_pct": 10.0, |
| 1374 | + "max_constraint_pct_error": 0.0, |
| 1375 | + "constraints": {}, |
| 1376 | + "validation_passed": True, |
| 1377 | + "validation_issues": [], |
| 1378 | + "calibration_quality": "exact", |
| 1379 | + } |
| 1380 | + |
| 1381 | + _write_parallel_temp_year( |
| 1382 | + root=tmp_path, |
| 1383 | + year=2062, |
| 1384 | + profile=profile, |
| 1385 | + audit=audit, |
| 1386 | + tax_assumption={"name": "trustees-core-thresholds-v1"}, |
| 1387 | + ) |
| 1388 | + _write_parallel_temp_year( |
| 1389 | + root=tmp_path, |
| 1390 | + year=2063, |
| 1391 | + profile=profile, |
| 1392 | + audit=audit, |
| 1393 | + tax_assumption={"name": "different-tax-assumption"}, |
| 1394 | + ) |
| 1395 | + |
| 1396 | + with pytest.raises(ValueError, match="Temp manifest mismatch for tax_assumption"): |
| 1397 | + merge_outputs( |
| 1398 | + years=[2062, 2063], |
| 1399 | + output_root=tmp_path, |
| 1400 | + keep_temp=True, |
| 1401 | + ) |
| 1402 | + |
| 1403 | + |
1249 | 1404 | def test_summarize_realized_clone_translation_matches_toy_clone(): |
1250 | 1405 | import pandas as pd |
1251 | 1406 |
|
|
0 commit comments