Warn and drop deprecated inputs instead of crashing#1494
Merged
Conversation
When a partner posts a household payload containing a removed model variable (e.g. `medical_out_of_pocket_expenses`, deleted in policyengine-us 1.673.0), the simulation crashes with VariableNotFoundError and the partner sees HTTP 500 — the entire calculation is lost, not just the medical-expense piece. Add a deprecation registry and a `drop_deprecated_inputs` helper that strips removed variables before validation, surfacing a structured `DeprecatedVariableWarning` next to existing period warnings. Partners get a 200 with full output for everything that doesn't depend on the removed input, plus an actionable migration hint in the response body. Outputs that did depend on the dropped input fall back to defaults (e.g. itemized medical deduction, SNAP excess medical deduction, HUD medical expense deduction, Medicaid medically-needy spend-down). This is a soft landing, not a transparent shim — premium-heavy callers will need to migrate their value to `health_insurance_premiums` to retain those outputs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ormat `test__given_invalid_household_shape__returns_400` posts a string for `household` and expects a 400. Without a type guard, the warn-and-drop helper hits `.items()` on a string before validation can reject it. Add an `isinstance(household, dict)` short-circuit so non-dict shapes flow straight to the existing Pydantic validator. Also reformat the two new test files with `ruff format` to satisfy the Lint job. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
anth-volk
approved these changes
May 6, 2026
Collaborator
anth-volk
left a comment
There was a problem hiding this comment.
This is a critical fix, so let's merge, but I want to flag: drop_deprecated_inputs directly mutates the household instead of deep-copying, opening us up to a class of unexpected/unclear bugs down the road. Recommend making this non-mutative via deep-copy in a future PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a partner posts a household payload containing a removed model variable, the simulation crashes with
VariableNotFoundError→ HTTP 500 and the partner sees no output at all — not just for the affected variable, but for everything they asked for. PR #1493 surfaced this withmedical_out_of_pocket_expenses(deleted inpolicyengine-us1.673.0); other deprecations will likely break the same way.This PR adds a deprecation registry and a
drop_deprecated_inputshelper that strips removed variables from the inbound payload before validation, surfacing a structured warning to the response next to the existing period warnings. The partner gets a 200 with full output for every non-dependent variable, plus an actionable migration hint.It also handles deprecated variables used as scan axes. If
axes[].namereferences a removed variable, that axis is dropped with a location-specific warning before the simulation builder expands axes, avoiding the same 500 path.Behavior
medical_out_of_pocket_expensesaxes[].name = medical_out_of_pocket_expensesTradeoff
This is a warn-and-drop, not a value-preserving shim. The partner's value is silently discarded; outputs that need that input fall back to defaults. For partners who passed
0(e.g. MyFriendBen pre-#1493), this is a no-op semantically. For partners who passed a non-zero value with premium spend folded in, they'll need to migrate the premium portion tohealth_insurance_premiumsto recover those outputs — the warning hint says exactly that.Files
policyengine_household_api/utils/deprecated_inputs.py— new registry + helper for entity inputs and scan axespolicyengine_household_api/endpoints/household.py— wire the helper into/calculatebefore validation; merge warnings into the existing response fieldtests/unit/utils/test_deprecated_inputs.py— unit coverage for drop+warn, no-deprecated passthrough, multi-person, axes safety, deprecated axis dropping, members-list safety, and message formattests/unit/endpoints/test_household.py— end-to-end coverage confirming requests withmedical_out_of_pocket_expensesas an input or axis return 200 + warning + correct unaffected outputschangelog.d/warn-and-drop-deprecated-inputs.added.md— changelog fragmentFuture deprecations
Each new entry in
DEPRECATED_VARIABLESships with a one-line migration hint. The registry is the single place to add future removals (e.g. when other umbrella inputs are decomposed) so we don't have to recur through this design.Test plan
uv run pytest tests/unit/utils/test_deprecated_inputs.py tests/unit/endpoints/test_household.pyuv run ruff check policyengine_household_api/utils/deprecated_inputs.py tests/unit/utils/test_deprecated_inputs.py tests/unit/endpoints/test_household.py🤖 Generated with Claude Code