Skip to content

Add calibration package checkpointing, target config, and hyperparameter CLI#538

Draft
baogorek wants to merge 4 commits intomainfrom
calibration-pipeline-improvements
Draft

Add calibration package checkpointing, target config, and hyperparameter CLI#538
baogorek wants to merge 4 commits intomainfrom
calibration-pipeline-improvements

Conversation

@baogorek
Copy link
Collaborator

@baogorek baogorek commented Feb 17, 2026

Fixes #533
Fixes #534

Summary

  • Calibration package checkpointing: --build-only saves the expensive matrix build as a pickle, --package-path loads it for fast re-fitting with different hyperparameters or target sets
  • Target config YAML: Declarative exclusion rules (target_config.yaml) replace hardcoded target filtering; checked-in config reproduces the junkyard's 22 excluded groups
  • Hyperparameter CLI flags: --beta, --lambda-l2, --learning-rate are now tunable from the command line and Modal runner
  • Modal runner improvements: Streaming subprocess output, support for new flags
  • Documentation: docs/calibration.md covers all workflows (single-pass, build-then-fit, package re-filtering, Modal, portable fitting)

Note: This branch includes commits from #537 (PUF impute) since the calibration pipeline depends on that work. The calibration-specific changes are in the top commit.

Test plan

  • pytest policyengine_us_data/tests/test_calibration/test_unified_calibration.py — CLI arg parsing tests
  • pytest policyengine_us_data/tests/test_calibration/test_target_config.py — target config filtering + package round-trip tests
  • Manual: make calibrate-build produces package, --package-path loads it and fits

🤖 Generated with Claude Code

baogorek and others added 4 commits February 16, 2026 08:37
…530)

Adds puf_impute.py and source_impute.py from PR #516 (by @MaxGhenis),
refactors extended_cps.py to delegate to the new modules, and integrates
both into the unified calibration pipeline. The core fix removes the
subsample(10_000) call that dropped high-income PUF records before QRF
training, which caused a hard AGI ceiling at ~$6.26M after uprating.

Co-Authored-By: Max Ghenis <mghenis@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ibration loader

Replace weight-proportional PUF subsample with stratified approach that
force-includes top 0.5% by AGI and randomly samples rest to 20K, preserving
the high-income tail the QRF needs. Remove random state assignment from SIPP
and SCF in source_impute.py since these surveys lack state identifiers. Fix
unified_calibration.py to handle TIME_PERIOD_ARRAYS dataset format. Add
`make calibrate` target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ter CLI

- Add build-only mode to save calibration matrix as pickle package
- Add target config YAML for declarative target exclusion rules
- Add CLI flags for beta, lambda_l2, learning_rate hyperparameters
- Add streaming subprocess output in Modal runner
- Add calibration pipeline documentation
- Add tests for target config filtering and CLI arg parsing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
raw_data = source_sim.dataset.load_dataset()
data_dict = {}
for var in raw_data:
data_dict[var] = {2024: raw_data[var][...]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this fails when trying to run calibration because load_dataset() returns dicts, not h5py datasets

Suggested change
data_dict[var] = {2024: raw_data[var][...]}
if isinstance(raw_data[var], dict):
vals = list(raw_data[var].values())
data_dict[var] = {2024: vals[0]}
else:
data_dict[var] = {2024: np.array(raw_data[var])}

dataset_path=dataset_path,
puf_dataset_path=puf_dataset_path,
state_fips=base_states,
time_period=2024,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make this and other instances where time_period=2024 is hardcoded flexibly derive the time period from the dataset?

@@ -1 +1 @@
"""Non-PUF QRF imputations with state_fips as predictor.
Copy link
Collaborator

@juaristi22 juaristi22 Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude recommends adding new files like source_impute.py and puf_impute.py to the __innit__ file, probably wouldn't hurt though not urgent

- `storage/calibration/unified_diagnostics.csv` --- per-target error report
- `storage/calibration/unified_run_config.json` --- full run configuration

### 2. Build-then-fit (recommended for iteration)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would we want to support this option for the modal runner as well? i think currently the modal runner is not wired to do so and save the calibration package, so it could only be used for local / kaggle notebook buiilds

Person-level state FIPS array.
"""
hh_ids_person = data.get("person_household_id", {}).get(time_period)
if hh_ids_person is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will person_household_id ever not be available?
the fallback assumes every household has the same number of people and could lead to wrong state assignments, but we might be able to get rid of it altogether, if we can safely assume that person_household_id will always be in the data

Copy link
Collaborator

@juaristi22 juaristi22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments, but generally LGTM, I was also able to run the calibration job in modal (after removing the ellipsis in unified_calibration.py)!

Small note: if im not mistaken this pr addressess issue #534. Seems like #310 was referenced in it as something that would be addressed together, but this pr does not save the calibration_log.csv among its outputs. Do we want to add it at this point?

@juaristi22 juaristi22 force-pushed the calibration-pipeline-improvements branch from 4c51b32 to 61523d8 Compare February 18, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add calibration package checkpoint to unified_calibration pipeline Target selection config for calibration optimizer input

2 participants