Skip to content

Require every reported number to come from a model run - #92

Merged
MaxGhenis merged 6 commits into
mainfrom
microsim-outputs-only
Nov 11, 2026
Merged

MaxGhenis merged 6 commits into
mainfrom
microsim-outputs-only

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Why

PolicyEngine is a microsimulation model; its outputs are the product. The uk-triple-lock analysis reported a 20,000-draw uncertainty range for State Pension costs that ran the model only about 30 times. Every other draw was priced as spend × (index_TL − index_alt) / index_central from one central run, and the few full runs were described as validating that shortcut. The draws also changed only the pension rate. Every other CPI- or earnings-linked parameter, and every income in the data, stayed on the central path. Nothing in the skills told an agent this is unacceptable, so this adds it where agents and reviewers look.

What changes

  • policyengine skill: "Every reported number is a model run", a new top-level section. It lists what is banned (ratio scaling, interpolation or extrapolation between runs, elasticities or shares borrowed from one run, side models, and changing one parameter when a scenario should move the whole system). It also says what to do instead: run fewer scenarios fully and state how many runs stand behind each figure. The description gains scenarios, uncertainty, Monte Carlo so the skill loads for that work.

  • policyengine-uk skill: "Macro scenarios", with a <!-- verify: slow --> example. A CPI or earnings path has to be a Scenario applied before the data load. Verified on policyengine 5.3.0 / policyengine-uk 2.90.2 with +1pp CPI and earnings in 2027–33, by reading the code and running it:

    2034 Baseline Before data load Same changes as reform= dict
    Employment income £1,662.6bn £1,779.4bn £1,662.6bn
    Child benefit, eldest £31.79/wk £34.03/wk £31.79/wk
    New State Pension £305.94 £324.52 £305.94
    Income tax £476.2bn £510.0bn £476.2bn

    The dict form is a silent no-op. It edits yoy_growth after process_parameters(), so nothing derived from it is rebuilt. The section also covers the period-key trap ("year:2030-01-01:1", not a bare year and not the start.stop form) and the ./data/ HDF5 lock when running processes in parallel.

  • policyengine-tools: data files hold model outputs only; no scaling in the pipeline or the frontend.

  • review-program rubric: a figure published as a PolicyEngine estimate that was derived from other runs' output, instead of a run of that scenario, is always CRITICAL, whatever validation accompanies it. Hand-calculated test values are not covered.

  • Plugin release 4.0.3 (all bundles and the marketplace template).

Checks

  • uv run pytest --ignore=tests/test_skill_examples.py: 178 passed
  • scripts/build_claude_wrapper.py: builds
  • The new slow example ran as written against the policyengine 5.3.0 certified bundle (dataset enhanced_frs_2024_25) and its assertions passed.
  • skill-examples fails on 8 US examples unrelated to this PR: policyengine 6.0.0 (15 Sep, after main last passed on 13 Sep; CI now installs 6.1.2) makes pe.us.calculate_household raise SPMInputError: County selection has no county FIPS input. Tracked separately; this PR merges once that is fixed.

Review round 1 (independent Opus review)

All eight findings addressed:

  • F1: the rule now bans figures derived from other runs' output, not arithmetic on the runs being reported.
  • F2: the macro-plus-policy recipe now uses two macro simulations. Policies read by process_parameters() go into the macro scenario. It warns that + drops applied_before_data_load and that sim.baseline uses the default macro path.
  • F3: documents the horizons (triple lock to 2034, indices to 2039).
  • F4: says pe.uk Policy/Dynamic can't carry a macro path.
  • F5: reform-comparator no longer allows labelled linear scaling.
  • F6: the rubric covers published estimates, not hand-calculated test values.
  • F7: gives the precise reason reform= is a no-op.
  • F8: fixes the verify-marker wording and names the dataset.

Review round 2

The reviewer confirmed F1 and F3–F7 fixed. Two things were still open:

  • F2. The recipe claimed that merging a policy into the macro scenario makes later years of an uprated parameter follow it. It doesn't: apply_parameter_changes reloads the raw YAML, and later years uprate from the old value. That clause is gone; the skill now says to set every reported year through the after-load route, and it documents the key formats and the merge override.
  • F8. Fixed:
    • the rubric summary above;
    • the dataset line: the 5.3.0 bundle serves enhanced_frs_2024_25, and the skill's stale populace_uk_2023 default is corrected per policyengine.py#491;
    • the policyengine_uk exemption;
    • the tools-skill wording.

🤖 Generated with Claude Code

Adds a top-level rule to the policyengine skill: fiscal, distributional,
poverty and household figures come from running the microsimulation for
each scenario, never from scaling, interpolating or extrapolating other
runs' outputs or from a side model. When there are too many scenarios,
run fewer and say how many runs stand behind each figure.

The policyengine-uk skill gains a verified "Macro scenarios" section: a
CPI/earnings path must be a Scenario applied before the data load. The
same changes passed as a reform= dict are a silent no-op (verified: no
income, benefit rate, State Pension or income tax moved).

policyengine-tools says data files hold model outputs only, and the
review-program rubric makes a scaled or extrapolated figure critical.
Releases the plugin as 4.0.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits September 29, 2026 12:42
- The rule bans figures derived from other runs' output, not arithmetic on
  the runs being reported (differences, sums, ratios, sampling error).
- Macro + policy: two macro simulations; policies that process_parameters()
  reads go into the macro scenario; `+` drops applied_before_data_load;
  don't compare against sim.baseline.
- Note the triple-lock (2034) and index (2039) horizons, that pe.uk
  Policy/Dynamic can't carry a macro path, and why reform= is a no-op.
- reform-comparator no longer allows labelled linear scaling; the
  review-program rubric covers published estimates, not test values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Macro + policy: remove the claim that merging makes later years of an
  uprated parameter follow a one-year change; set every reported year
  instead. State the before-load period keys and that a repeated path
  replaces the macro one.
- policyengine skill: the macro Scenario is the one analysis use of
  policyengine_uk directly.
- policyengine-uk skill: the 5.3.0 bundle's UK default is
  enhanced_frs_2024_25 (policyengine.py#491, 21 Aug 2026).
- policyengine-tools: data files may hold arithmetic on the runs shown and
  labelled external comparators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits September 29, 2026 18:06
The certified bundle in policyengine 5.3.0 through 6.1.2 serves
enhanced_frs_2024_25 (policyengine-uk-data 1.56.16) as the UK default,
checked in both manifests on 2026-09-29. The policyengine and
policyengine-data skills and the UK skill's frontmatter still called
populace_uk_2023 the default; the round-3 review flagged the mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep main's dataset facts (Microcosm UK default, bundle 6.16.0 build ids)
in every conflicted hunk, add this PR's scenario/uncertainty/Monte Carlo
triggers to the policyengine skill, and release the bundles as 4.1.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ite UK macro-scenario section to current code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit d34fd48 into main Nov 11, 2026
2 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merge audit (PR sweep of 2026-11-11): the sweep merged main (695b307) and fixed what the first independent review asked for (c706b6f): two stale dataset lines restored to main's wording, a false parenthetical about Scenario.__add__ removed, and the UK "Macro scenarios" section rewritten so every mechanism claim matches current policyengine.py and policyengine-uk (the example is unchanged and labelled as last run on policyengine 5.3.0). An independent Opus 5.5 re-review approved head c706b6f with no blocking findings, checking each claim against policyengine.py 6.17.1 and policyengine-uk 2.174.1/2.178.0. Both checks (test, skill-examples) passed; MERGEABLE; no changes requested. Follow-up the re-review noted: skills/policyengine/SKILL.md L42-44's "model inputs, so uprating carries it through" is verified only for the UK before-load route; the US route through Reform.from_dict edits an already-uprated parameter tree.

MaxGhenis added a commit that referenced this pull request Nov 11, 2026
#92 edited the SKILL.md of three skills (policyengine, policyengine-tools,
policyengine-uk), a reference file of a fourth, the reform-comparator agent
and all eight bundles without rebuilding the manifest, so main's committed
manifest no longer matched its sources. On main's corpus, main's builder
and this branch's give the same 324 pairs and scores; only overlap order
and 7 shared_terms lists differ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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