Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f2e215a
Make the shared UK copy, section hubs and header label country-aware
MaxGhenis Oct 14, 2026
5fa293d
Group UK parameters and variables by UK department, not US agencies
MaxGhenis Oct 14, 2026
31cc7a9
Show the UK coverage tracker from policyengine-uk's programme registry
MaxGhenis Oct 14, 2026
762fe28
Rewrite the UK data pipeline from the policyengine-uk-data 1.58.0 rel…
MaxGhenis Oct 14, 2026
9fd94f5
Show UK calibration targets and validation from the pinned release
MaxGhenis Oct 14, 2026
8662731
Show PolicyEngine UK's own behavioural responses on the UK page
MaxGhenis Oct 14, 2026
d2c565c
Say programmes on the UK parameter explorer; sentence-case entity labels
MaxGhenis Oct 14, 2026
4a9e5e3
Show what each UK validation figure measures
MaxGhenis Oct 14, 2026
cefcc58
Link policyengine-uk-data#582 from the UK validation page
MaxGhenis Oct 14, 2026
ec7d25a
Keep the UK validation route and ignore list clear of #47 and #49's l…
MaxGhenis Oct 14, 2026
3f1ec00
Cite LA tenure and private rent targets as the build's code does
MaxGhenis Oct 14, 2026
7ae3b37
Describe the reported-anchor take-up fill precisely
MaxGhenis Oct 14, 2026
cf01664
Document the snapshot trees and how to refresh the UK release pages
MaxGhenis Oct 14, 2026
97630d2
Score the UK validation run against the targets calibration fitted
MaxGhenis Oct 14, 2026
566633a
Correct the UK calibration and validation pages against the release's…
MaxGhenis Oct 14, 2026
3e73b59
Correct the UK pipeline stages against the 1.58.0 build's code
MaxGhenis Oct 14, 2026
5660c21
Describe the UK pay channels that are on by default, and the OBR modu…
MaxGhenis Oct 14, 2026
427c1ec
Make the UK overview, walkthrough and section hubs claim only what holds
MaxGhenis Oct 14, 2026
c869776
Keep registry and source tree refreshes consistent
MaxGhenis Oct 14, 2026
313ed8e
Restore UK variable departments and clarify explorer groups
MaxGhenis Oct 14, 2026
a396f60
Verify UK coverage links against matching registry snapshots
MaxGhenis Oct 14, 2026
c665ffb
Link the policyengine-uk-data issues the UK pages describe
MaxGhenis Oct 14, 2026
350dffb
Document the UK class-map snapshots and fallback
MaxGhenis Oct 14, 2026
f56c116
Preserve parameter source links with provenance snapshots
MaxGhenis Oct 14, 2026
4745dd0
Address the independent review of the UK pages at 427c1ec
MaxGhenis Oct 14, 2026
841e4dc
Merge the UK rules explorer fixes (rules lane)
MaxGhenis Oct 14, 2026
22c3b04
Move the UK pages to the pin in policyengine.py 6.9.0
MaxGhenis Oct 21, 2026
d0084e1
Show what changed upstream since the pinned release, and the unreacha…
MaxGhenis Oct 21, 2026
3cdb4e8
Close the re-review's four remaining findings, and note the unproject…
MaxGhenis Oct 21, 2026
e051638
Don't repeat an OBR line's area when its label already names it
MaxGhenis Oct 21, 2026
0b3d95b
Cite the SPI projection fix, and make a new data pin force a reread
MaxGhenis Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ policyengine-uk-data

# Metadata and registry snapshots generated at build time
public/metadata-*.json
public/variable-tree-*.json
public/variable-modules-*.json
public/param-tree-*.json
public/programs-*.json
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,22 @@ version is included when its metadata endpoint responds. The coverage page uses
this provenance to show which model version the coverage reflects and, when
available, the version served by the production API.

The same run snapshots each repository's `parameters/` and `variables/` file trees
(`public/param-tree-<country>.json`, `public/variable-tree-<country>.json`) at that
commit. Both trees record `{ source, paths }`. The UK variable tree also records
`modules`, an exact class-to-module map read from that commit's source archive,
because the UK metadata API serves no module names. The UK coverage tracker
verifies parameter, source and computation-tree links only against trees whose
repository and commit match its programme registry snapshot.

If the archive is unavailable, module enrichment falls back to unique file stems.
UK variables whose modules remain unknown appear under “Unclassified variables”.
The registry and both trees are cleared before each refresh so a failed fetch
cannot reuse a stale snapshot.

The program loader tries the static snapshot first, then production API metadata,
then the bundled hardcoded programs. Snapshot URLs use the public base prefix so
then the bundled hardcoded programs (US only; the UK page says the registry could
not be loaded). Snapshot URLs use the public base prefix so
they work both locally and under `/us/model`.

## Daily registry refresh
Expand All @@ -55,3 +69,47 @@ The workflow sends a POST request to the hook. If the secret is absent, it logs
notice and exits successfully. The workflow must be on the default branch for
scheduled runs to apply. The Vercel build must run `bun run build` so each
deployment fetches a fresh snapshot.

## UK data release pages

The UK pipeline, calibration targets and validation pages quote
`src/data/ukDataRelease.json` and `src/data/ukTargetChecks.json`, not typed-in
numbers. Both describe the UK data release that policyengine.py pins
(`src/policyengine/data/bundle/manifest.json`, `data_releases.uk`). When that pin
moves, regenerate them:

1. Put the pinned release's files in one folder: the bundle manifest, the
release manifest at the revision the bundle names, `frs_<year>.h5`,
`enhanced_frs_<year>.h5`, both weight files (from the gated
`policyengine/policyengine-uk-data-private` Hugging Face repo), and the two
calibration logs (GitHub Actions artifacts of the uk-data publish run:
`gh run download <run> -R PolicyEngine/policyengine-uk-data`).
2. Dump the release's target registry with `scripts/dump_uk_target_registry.py`,
and score the saved file with the pinned model with
`scripts/run_uk_target_check.py` (commands in each script's docstring).
3. Run `scripts/extract_uk_release.py --artifacts <folder> --out
src/data/ukDataRelease.json --rows-out src/data/ukTargetChecks.json`. It checks
every file's sha256 against the release manifest and refuses a release other
than the pin.
The target check records the dataset's sha256, data package version and
year, and the extractor refuses a check of any other file, package or year.
4. Run `UK_RELEASE_ARTIFACTS=<folder> UK_DATA_SOURCE=<policyengine-uk-data at
the pin> uv run --no-project --with pytest --with hypothesis --with h5py
--with tables --with pandas --with numpy pytest
scripts/test_extract_uk_release.py`, and
`UK_MODEL_SOURCE=<policyengine-uk at the pin> bun run test`. The first
checks the extractor's labels, areas and notes against the data package's
code; the second runs policyengine-uk's own behavioural functions against
the behavioural page. Then reread the pipeline stage copy in
`src/data/ukPipelineStages.ts` against the new release's
`datasets/create_datasets.py`: the counts update themselves, the mechanisms
do not. `UK_DATA_VERSION_DESCRIBED` (pipeline copy) and `UK_MODEL_VERSION`
(behavioural page) must name the new pin before the tests pass.

The household files hold UK Data Service microdata. Only the aggregates the
extractor writes may leave that folder.

The UK behavioural responses page mirrors policyengine-uk's parameters and
`dynamics/progression.py` (`src/data/ukBehaviouralResponses.ts`); run `bun run
test` with `UK_MODEL_SOURCE` set to a policyengine-uk checkout at the pinned
version to compare them.
3 changes: 2 additions & 1 deletion app/data/calibration/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CalibrationPage from '../../../src/views/data/CalibrationPageClient';
import UkCalibrationPage from '../../../src/views/data/UkCalibrationPage';
import CalibrationComparison from '../../../src/components/comparison/CalibrationComparison';
import { loadComparisonData } from '../../../src/data/comparisons';
import { detectComparePeers } from '../../../src/components/comparison/detectComparePeers';
Expand All @@ -18,7 +19,7 @@ export default async function CalibrationRoute({
);

if (!parsed.compareMode) {
return <CalibrationPage country={country} />;
return country === 'uk' ? <UkCalibrationPage /> : <CalibrationPage country={country} />;
}

const data = loadComparisonData();
Expand Down
33 changes: 11 additions & 22 deletions app/data/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
import SectionOverview from '../../src/components/layout/SectionOverview';
import { detectCountry } from '../../src/components/comparison/detectCountry';
import { dataSection } from '../../src/data/sectionOverviews';

export default function DataOverviewPage() {
export default async function DataOverviewPage({
searchParams,
}: {
searchParams: Promise<{ country?: string | string[] }>;
}) {
const country = await detectCountry((await searchParams).country);
const copy = dataSection[country];
return (
<SectionOverview
category="Model"
title="Data"
description="How PolicyEngine builds the microdata behind its simulations — from raw survey records to calibrated, validated datasets that match official population totals."
children={[
{
label: 'Pipeline',
description:
'Each stage that turns raw CPS, FRS, and SCF records into PolicyEngine-ready microdata.',
path: '/data/pipeline',
},
{
label: 'Calibration targets',
description:
'The population totals (program enrolment, income aggregates, demographic counts) used to reweight the underlying survey.',
path: '/data/calibration',
},
{
label: 'Validation',
description:
'How simulated outputs are cross-checked against published official statistics.',
path: '/data/validation',
},
]}
description={copy.description}
children={copy.children}
/>
);
}
6 changes: 6 additions & 0 deletions app/data/validation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ValidationPage from '../../../src/views/data/ValidationPage';
import ValidationComparison from '../../../src/components/comparison/ValidationComparison';
import { loadComparisonData } from '../../../src/data/comparisons';
import { detectComparePeers } from '../../../src/components/comparison/detectComparePeers';
import UkValidationPage from '../../../src/views/data/UkValidationPage';

export default async function ValidationRoute({
searchParams,
Expand All @@ -17,6 +18,11 @@ export default async function ValidationRoute({
sp.country,
);

// The UK page reports the pinned UK release; TAXSIM covers US taxes only.
if (!parsed.compareMode && country === 'uk') {
return <UkValidationPage />;
}

if (!parsed.compareMode) {
return <ValidationPage country={country} />;
}
Expand Down
33 changes: 11 additions & 22 deletions app/rules/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
import SectionOverview from '../../src/components/layout/SectionOverview';
import { detectCountry } from '../../src/components/comparison/detectCountry';
import { rulesSection } from '../../src/data/sectionOverviews';

export default function RulesOverviewPage() {
export default async function RulesOverviewPage({
searchParams,
}: {
searchParams: Promise<{ country?: string | string[] }>;
}) {
const country = await detectCountry((await searchParams).country);
const copy = rulesSection[country];
return (
<SectionOverview
category="Model"
title="Rules"
description="How PolicyEngine encodes tax-and-benefit rules — which programs are covered, the legislative parameters that drive calculations, and the input and output variables exposed to households and microsimulations."
children={[
{
label: 'Coverage tracker',
description:
'Which federal, state, and local tax-and-benefit programs PolicyEngine and peer models implement.',
path: '/rules/coverage',
},
{
label: 'Parameters',
description:
'Browse every legislative parameter that drives the model — thresholds, rates, brackets, eligibility cut-offs.',
path: '/rules/parameters',
},
{
label: 'Variables',
description:
'Inspect the input and output variables computed at the person, family, tax unit, and household level.',
path: '/rules/variables',
},
]}
description={copy.description}
children={copy.children}
/>
);
}
5 changes: 5 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"eslint": "^9.39.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^7.0.1",
"fast-check": "4.3.0",
"globals": "^16.5.0",
"jsdom": "^28.1.0",
"typescript": "~5.9.3",
Expand Down
4 changes: 4 additions & 0 deletions proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,9 @@ export const config = {
'/((?!_next|.*\\..*).*)',
'/:country(us|uk)/programs-:registry(us|uk).json',
'/:country(us|uk)/model/programs-:registry(us|uk).json',
'/:country(us|uk)/param-tree-:registry(us|uk).json',
'/:country(us|uk)/model/param-tree-:registry(us|uk).json',
'/:country(us|uk)/variable-tree-:registry(us|uk).json',
'/:country(us|uk)/model/variable-tree-:registry(us|uk).json',
],
};
32 changes: 32 additions & 0 deletions scripts/dump_uk_target_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Dump policyengine-uk-data's calibration target registry to JSON.

Run it against the data package at the release tag, with the model version
that built the release, e.g. for policyengine-uk-data 1.58.1:

git -C policyengine-uk-data archive 1.58.1 | tar -x -C /tmp/uk-data-1.58.1
uv run --no-project --python 3.13 --with policyengine-uk==2.122.2 \\
--with policyengine-core==3.32.13 --with pydantic --with pyyaml \\
--with /tmp/uk-data-1.58.1 python scripts/dump_uk_target_registry.py registry_targets.json

The registry reads only target statistics bundled with the package, no microdata.
"""

import json
import sys

from policyengine_uk_data.targets.registry import get_all_targets


def main(out_path: str) -> None:
targets = []
for target in get_all_targets():
row = target.model_dump(exclude={"custom_compute"})
row["values"] = {str(year): value for year, value in row["values"].items()}
targets.append(row)
with open(out_path, "w") as f:
json.dump(targets, f, indent=1, default=str)
print(f"wrote {len(targets)} targets to {out_path}")


if __name__ == "__main__":
main(sys.argv[1])
Loading