Skip to content

[v4] Accept dict reforms on Simulation(policy=/dynamic=)#297

Closed
MaxGhenis wants to merge 1 commit intov4-docs-refreshfrom
v4-dict-reforms
Closed

[v4] Accept dict reforms on Simulation(policy=/dynamic=)#297
MaxGhenis wants to merge 1 commit intov4-docs-refreshfrom
v4-dict-reforms

Conversation

@MaxGhenis
Copy link
Copy Markdown
Contributor

Summary

Last piece of the v4 agent-first reform surface: Simulation(policy={...}) and Simulation(dynamic={...}) now accept the same flat dict that pe.{uk,us}.calculate_household(reform=...) accepts.

Before

from policyengine.core import Simulation, Policy, Parameter, ParameterValue
import datetime

reform_policy = Policy(
    name="CTC base $3,000",
    parameter_values=[
        ParameterValue(
            parameter=Parameter(
                name="gov.irs.credits.ctc.amount.base[0].amount",
                tax_benefit_model_version=pe.us.model,
                data_type=float,
            ),
            start_date=datetime.date(2026, 1, 1),
            end_date=datetime.date(2026, 12, 31),
            value=3_000,
        )
    ],
)
reform_sim = Simulation(
    dataset=dataset, tax_benefit_model_version=pe.us.model,
    policy=reform_policy,
)

After

reform_sim = Simulation(
    dataset=dataset, tax_benefit_model_version=pe.us.model,
    policy={"gov.irs.credits.ctc.amount.base[0].amount": 3_000},
)

Same shape accepted by both entry points. Same effective-date semantics (scalars default to {year}-01-01 using dataset.year). Same validation (unknown paths raise with close-match suggestions).

Implementation

  • New compile_reform_to_policy / compile_reform_to_dynamic helpers in tax_benefit_models.common.reform.
  • Simulation.policy / Simulation.dynamic typed as Optional[Union[Policy, dict[str, Any]]] / Optional[Union[Dynamic, dict[str, Any]]].
  • @model_validator(mode="after") compiles dicts to full Policy / Dynamic once tax_benefit_model_version and dataset are on self.
  • Missing tax_benefit_model_version with a dict reform raises a directed error.

Tests

tests/test_dict_reforms_on_simulation.py (6 tests, no network, no HF token):

  • scalar reform → ParameterValue with start_date = {year}-01-01
  • {date: value} mapping → multiple ParameterValues with matching start_date
  • unknown parameter path → ValueError with "did you mean" hint
  • existing Policy object → passes through unchanged
  • dict without tax_benefit_model_version → raises
  • dynamic={...} coerces to Dynamic

Full suite: 397/397 pass locally. End-to-end microsim sanity-checked: Simulation(policy={"gov.irs.credits.ctc.amount.base[0].amount": 3_000}) produces -$25.5B revenue impact, identical to the hand-built Policy path.

Stack

Stacked on #295 (docs refresh). Retargets to main once the earlier stack merges.

🤖 Generated with Claude Code

Unifies the v4 reform surface: the same flat {"param.path": value} /
{"param.path": {date: value}} dict already accepted by
pe.{uk,us}.calculate_household(reform=...) now works on population
Simulation too. Dicts are compiled to Policy / Dynamic objects in a
model_validator(mode="after") using tax_benefit_model_version for
parameter-path validation and dataset.year for scalar effective-date
defaulting.

Adds compile_reform_to_policy / compile_reform_to_dynamic helpers
in tax_benefit_models.common.reform, tested directly in
tests/test_dict_reforms_on_simulation.py (6 tests covering scalar
defaulting, effective-date mappings, path validation, pass-through of
existing Policy objects, and the "no model_version" error path).

Unknown parameter paths raise with close-match suggestions (same
behaviour as the household calculator) so agents don't silently get a
no-op reform from a typo.

397/397 tests pass. End-to-end microsim with
Simulation(policy={"gov.irs.credits.ctc.amount.base[0].amount": 3000})
produces the same -$25.5B revenue impact as the manual
Policy+ParameterValue construction it replaces.
@MaxGhenis
Copy link
Copy Markdown
Contributor Author

Superseded by #298 (consolidated v4 launch PR). All commits cherry-picked cleanly onto v4.

@MaxGhenis MaxGhenis closed this Apr 19, 2026
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.

1 participant