From dd062088a9456fb71be3e0b7eaa1addfeaf17d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:21:08 +0200 Subject: [PATCH 1/4] Floor the size refit's Horvitz-Thompson baseline probabilities: --baseline-pi-floor, candidate-only and recorded (#355) A boundary row drawn at an inclusion probability of a few in a million starts the compact refit at millions of households; after the baseline is normalised to the pool mass the certainties are left too little to reach the targets under the 10x stretch bound. Q50 (spine-q, 2026-09-10): 24 such rows held 92% of the baseline, the refit ended 24% short of the pool mass at 10.2x the dense loss. Re-refitting the same checkpoint with the probabilities floored at 0.001 or above lands at 1.14x the dense loss with the pool mass intact. The floor applies to the baseline only: the search, the draw and the selected rows are unchanged, so a checkpoint resume may use it. Default 0 keeps today's behaviour; the size receipt records the floor, the rows it trimmed and the certainties' share of the baseline mass, and names the stretch reference normalized_horvitz_thompson_w_over_q_floored. --- .../build/uk_runtime/dataset_size.py | 48 +++++++++++++++++-- .../build/uk_runtime/local_rowwise.py | 9 +++- .../tests/test_uk_local_rowwise.py | 46 ++++++++++++++++++ .../tests/test_uk_rowwise_candidate.py | 7 +++ tools/build_uk_rowwise_candidate.py | 24 ++++++++++ 5 files changed, 130 insertions(+), 4 deletions(-) diff --git a/packages/microcosm-build/src/microcosm/build/uk_runtime/dataset_size.py b/packages/microcosm-build/src/microcosm/build/uk_runtime/dataset_size.py index a358d5fd88..6d39f70a8e 100644 --- a/packages/microcosm-build/src/microcosm/build/uk_runtime/dataset_size.py +++ b/packages/microcosm-build/src/microcosm/build/uk_runtime/dataset_size.py @@ -96,6 +96,15 @@ def _check_size_inputs(frame: Frame, dense: CalibrationResult, households: int) return n +def _check_baseline_pi_floor(value: object) -> float: + if not isinstance(value, float | int) or isinstance(value, bool): + raise ValueError("baseline_pi_floor must be a number in [0, 1].") + floor = float(value) + if not (0.0 <= floor <= 1.0): + raise ValueError("baseline_pi_floor must be a number in [0, 1].") + return floor + + def _check_pi_hi(pi_hi: object) -> float: if not isinstance(pi_hi, float | int) or isinstance(pi_hi, bool): raise ValueError("pi_hi must be a number in (0, 1].") @@ -203,6 +212,7 @@ def refit_uk_dataset_size( learning_rate: float, seed: int, pi_hi: float = 1.0, + baseline_pi_floor: float = 0.0, selection: UKSizeSelection | None = None, progress_callback: ProgressCallback | None = None, ) -> UKDatasetSize: @@ -220,6 +230,18 @@ def refit_uk_dataset_size( promotes learned near-certain gates and is a reviewed candidate-run setting recorded in the size receipt, never a release default. + ``baseline_pi_floor`` trims the Horvitz–Thompson baseline the refit starts + from and stretches against: each selected row's dense weight is divided by + ``max(q, baseline_pi_floor)`` instead of its inclusion probability ``q`` + before the baseline is normalised to the pool mass. ``0.0`` (default) is + the untrimmed baseline. A boundary row drawn at ``q`` of a few in a + million otherwise starts at millions of households and, after the + normalisation, leaves the certainties too little mass to reach the + targets under the stretch bound (microcosm#355, Q50 2026-09-10: 24 such + rows held 92 % of the baseline; the refit ended 24 % short of the pool + mass at 10× the dense loss). Candidate-only, recorded in the size receipt + with the number of rows it trimmed and the certainties' baseline share. + ``selection`` skips the informed L0 search and draws from an existing :class:`UKSizeSelection` (a checkpoint restored by :mod:`microcosm.build.uk_runtime.size_checkpoint`); it must have been @@ -227,6 +249,7 @@ def refit_uk_dataset_size( """ n = _check_size_inputs(frame, dense, households) pi_hi = _check_pi_hi(pi_hi) + baseline_pi_floor = _check_baseline_pi_floor(baseline_pi_floor) if households == n: return UKDatasetSize( dense, @@ -312,13 +335,16 @@ def refit_uk_dataset_size( if not np.isin(np.flatnonzero(init_protected), support).all(): raise RuntimeError("exact-count selection lost a protected carrier.") frozen = _frozen_targets(frame, dense, support) + q = np.asarray(q, dtype=np.float64) + baseline_q = q if baseline_pi_floor == 0.0 else np.maximum(q, baseline_pi_floor) + baseline_floored_rows = int(np.count_nonzero(q < baseline_pi_floor)) refit = refit_l0_selection( frame, frozen, search_result, support=support, k=households, - support_inclusion_probabilities=q, + support_inclusion_probabilities=baseline_q, mass_reason=dense.options["mass_reason"], progress_callback=_phased(progress_callback, "size_refit"), **common, @@ -333,6 +359,12 @@ def refit_uk_dataset_size( raise RuntimeError("compact refit changed target values.") errors_dense = np.asarray([d.final_estimate for d in dense.diagnostics]) errors_small = np.asarray([d.final_estimate for d in refit.diagnostics]) + baseline = np.asarray(refit.initial_weights, dtype=np.float64) + baseline_total = float(baseline.sum()) + certainty_rows = q >= 1.0 + baseline_name = "normalized_horvitz_thompson_w_over_q" + ( + "_floored" if baseline_pi_floor > 0.0 else "" + ) return UKDatasetSize( refit, support, @@ -362,8 +394,18 @@ def refit_uk_dataset_size( "refit_epochs": epochs, "pool_row_indices": support.tolist(), "inclusion_probabilities": q.tolist(), - "refit_baseline": "normalized_horvitz_thompson_w_over_q", - "stretch_reference": "normalized_horvitz_thompson_w_over_q", + "refit_baseline": baseline_name, + "stretch_reference": baseline_name, + "baseline_pi_floor": baseline_pi_floor, + "baseline_floored_rows": baseline_floored_rows, + # How the normalised baseline splits between the certainties and + # the boundary draws: the certainties' 10x ceiling has to cover + # the pool mass for the refit to reach the targets at all. + "baseline_mass_share_certainties": ( + float(baseline[certainty_rows].sum() / baseline_total) + if baseline_total > 0.0 + else None + ), "dense_loss": dense.final_loss, "compact_loss": refit.final_loss, "max_target_scaled_change": float( diff --git a/packages/microcosm-build/src/microcosm/build/uk_runtime/local_rowwise.py b/packages/microcosm-build/src/microcosm/build/uk_runtime/local_rowwise.py index ccac6a85d9..0b105cc71b 100644 --- a/packages/microcosm-build/src/microcosm/build/uk_runtime/local_rowwise.py +++ b/packages/microcosm-build/src/microcosm/build/uk_runtime/local_rowwise.py @@ -1058,6 +1058,7 @@ def solve_uk_rowwise_weights_under_doctrine( seed: int = 0, selection_seed: int | None = None, selection_pi_hi: float = 1.0, + baseline_pi_floor: float = 0.0, size_checkpoint_dir: Path | None = None, resume_size_checkpoint: Path | None = None, checkpoint_identity: Mapping[str, Any] | None = None, @@ -1077,7 +1078,10 @@ def solve_uk_rowwise_weights_under_doctrine( checkpoint instead of solving and searching again, refusing when the identity, the pool or the target surface differ. The draw's threshold (``selection_pi_hi``) may differ from the one the search stopped on; the - size receipt records both. + size receipt records both. ``baseline_pi_floor`` trims the refit's + Horvitz–Thompson baseline (see + :func:`~microcosm.build.uk_runtime.dataset_size.refit_uk_dataset_size`); + it is a refit setting, so a resumed checkpoint may use a different one. ``progress`` receives one readable line per hundred epochs of the dense solve, of every budget probe and of the refit, one line per finished @@ -1314,6 +1318,7 @@ def solve_uk_rowwise_weights_under_doctrine( learning_rate=learning_rate, seed=size_seed, pi_hi=selection_pi_hi, + baseline_pi_floor=baseline_pi_floor, selection=size_selection, progress_callback=progress_callback, ) @@ -1660,6 +1665,7 @@ def rotated_uk_local_holdout( solve_seed: int = 0, selection_seed: int | None = None, selection_pi_hi: float = 1.0, + baseline_pi_floor: float = 0.0, ) -> dict[str, object]: """Run five local-row rotations with national rows fixed in training.""" @@ -1707,6 +1713,7 @@ def rotated_uk_local_holdout( seed=solve_seed, selection_seed=selection_seed, selection_pi_hi=selection_pi_hi, + baseline_pi_floor=baseline_pi_floor, ) held_targets = problem.targets[holdout_indices] held_estimates = np.asarray( diff --git a/packages/microcosm-build/tests/test_uk_local_rowwise.py b/packages/microcosm-build/tests/test_uk_local_rowwise.py index 54ebeb7809..e595249fc6 100644 --- a/packages/microcosm-build/tests/test_uk_local_rowwise.py +++ b/packages/microcosm-build/tests/test_uk_local_rowwise.py @@ -1447,6 +1447,52 @@ def test_size_refit_pi_hi_promotes_learned_certainties_and_is_recorded(): ) +def test_size_refit_baseline_pi_floor_trims_the_baseline_and_is_recorded(): + from microcosm.build.uk_runtime.dataset_size import refit_uk_dataset_size + from microcosm.calibrate import Target, TargetSet, calibrate + + frame = _clone_frame() + dense = calibrate( + frame, + TargetSet( + [Target("count", "household", lambda f: np.ones(f.n("household")), 3)] + ), + epochs=2, + ) + common = dict(households=2, epochs=2, learning_rate=0.02, seed=7, pi_hi=0.5) + plain = refit_uk_dataset_size(frame, dense, **common) + floored = refit_uk_dataset_size(frame, dense, baseline_pi_floor=1.0, **common) + assert plain.receipt["baseline_pi_floor"] == 0.0 + assert plain.receipt["refit_baseline"] == "normalized_horvitz_thompson_w_over_q" + assert plain.receipt["stretch_reference"] == plain.receipt["refit_baseline"] + assert plain.receipt["baseline_floored_rows"] == 0 + assert floored.receipt["baseline_pi_floor"] == 1.0 + assert ( + floored.receipt["refit_baseline"] + == "normalized_horvitz_thompson_w_over_q_floored" + ) + assert floored.receipt["stretch_reference"] == floored.receipt["refit_baseline"] + # The same draw either way: the floor is a refit setting, not a selection one. + assert floored.receipt["pool_row_indices"] == plain.receipt["pool_row_indices"] + assert ( + floored.receipt["inclusion_probabilities"] + == (plain.receipt["inclusion_probabilities"]) + ) + q = np.asarray(plain.receipt["inclusion_probabilities"]) + assert floored.receipt["baseline_floored_rows"] == int((q < 1.0).sum()) + for receipt in (plain.receipt, floored.receipt): + share = receipt["baseline_mass_share_certainties"] + assert share is None or 0.0 <= share <= 1.0 + # With every probability floored at one, the baseline is the dense weights + # renormalised to the pool mass, so no row is inflated by its draw. + baseline = np.asarray(floored.result.initial_weights) + dense_selected = np.asarray(dense.weights)[np.asarray(floored.support)] + assert np.allclose(baseline / baseline.sum(), dense_selected / dense_selected.sum()) + for bad in (-0.1, 1.5, True): + with pytest.raises(ValueError, match="baseline_pi_floor"): + refit_uk_dataset_size(frame, dense, baseline_pi_floor=bad, **common) + + def test_size_refit_refuses_unsupported_nonzero_targets_by_name(): from microcosm.build.uk_runtime.dataset_size import ( refit_uk_dataset_size, diff --git a/packages/microcosm-build/tests/test_uk_rowwise_candidate.py b/packages/microcosm-build/tests/test_uk_rowwise_candidate.py index e89559c4e5..c6fc30b472 100644 --- a/packages/microcosm-build/tests/test_uk_rowwise_candidate.py +++ b/packages/microcosm-build/tests/test_uk_rowwise_candidate.py @@ -2245,6 +2245,10 @@ def test_size_candidate_exports_compact_links_and_cannot_claim_dense_release( assert size["seed"] == 11 assert manifest["parameters"]["selection_pi_hi"] == 1.0 assert size["selection_pi_hi"] == 1.0 + assert manifest["parameters"]["baseline_pi_floor"] == 0.0 + assert size["baseline_pi_floor"] == 0.0 + assert size["baseline_floored_rows"] == 0 + assert size["refit_baseline"] == "normalized_horvitz_thompson_w_over_q" assert size["selection_receipt"]["pi_hi"] == 1.0 assert size["selection_feasibility"]["requested_pi_hi"] == 1.0 assert size["selection_feasibility"]["feasible_at_requested_pi_hi"] is True @@ -2319,6 +2323,9 @@ def test_selection_seed_requires_a_dataset_size(tmp_path): (["--selection-pi-hi", "0.95"], "requires --dataset-households"), (["--dataset-households", "10", "--selection-pi-hi", "0"], r"in \(0, 1\]"), (["--dataset-households", "10", "--selection-pi-hi", "1.5"], r"in \(0, 1\]"), + (["--baseline-pi-floor", "0.01"], "requires --dataset-households"), + (["--dataset-households", "10", "--baseline-pi-floor", "-0.1"], r"in \[0, 1\]"), + (["--dataset-households", "10", "--baseline-pi-floor", "1.5"], r"in \[0, 1\]"), ], ) def test_selection_pi_hi_is_candidate_only_and_bounded(tmp_path, argv_tail, message): diff --git a/tools/build_uk_rowwise_candidate.py b/tools/build_uk_rowwise_candidate.py index 02cfaa7661..38aa5dbd96 100644 --- a/tools/build_uk_rowwise_candidate.py +++ b/tools/build_uk_rowwise_candidate.py @@ -722,6 +722,21 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: "recorded in the size receipt. Requires --dataset-households." ), ) + parser.add_argument( + "--baseline-pi-floor", + type=float, + default=0.0, + help=( + "Floor on the inclusion probability the refit's Horvitz-Thompson " + "baseline divides each selected row's dense weight by: a boundary " + "row drawn at a few in a million otherwise starts at millions of " + "households and starves every other row under the stretch bound " + "(microcosm#355, Q50 2026-09-10). 0 (default) is the untrimmed " + "baseline. Candidate-only; recorded in the size receipt with the " + "rows it trimmed. Requires --dataset-households; a resumed " + "checkpoint may use a different floor." + ), + ) parser.add_argument( "--no-size-checkpoint", action="store_true", @@ -1152,6 +1167,7 @@ def _run_candidate( seed=args.seed, selection_seed=args.selection_seed, selection_pi_hi=args.selection_pi_hi, + baseline_pi_floor=args.baseline_pi_floor, size_checkpoint_dir=out_dir if write_checkpoint else None, resume_size_checkpoint=resume_checkpoint, checkpoint_identity=checkpoint_identity, @@ -1277,6 +1293,7 @@ def _run_candidate( solve_seed=args.seed, selection_seed=args.selection_seed, selection_pi_hi=args.selection_pi_hi, + baseline_pi_floor=args.baseline_pi_floor, ) args._rotated_holdout = rotated_holdout @@ -2710,6 +2727,9 @@ def _parameters(args: argparse.Namespace, *, source_year: int) -> dict[str, Any] "selection_pi_hi": None if args.dataset_households is None else float(args.selection_pi_hi), + "baseline_pi_floor": None + if args.dataset_households is None + else float(args.baseline_pi_floor), "size_checkpoint": bool( args.dataset_households is not None and not args.no_size_checkpoint @@ -2954,6 +2974,10 @@ def _validate_cli_args(args: argparse.Namespace) -> None: raise ValueError("--selection-pi-hi must be in (0, 1].") if args.selection_pi_hi != 1.0 and args.dataset_households is None: raise ValueError("--selection-pi-hi requires --dataset-households.") + if not (0.0 <= args.baseline_pi_floor <= 1.0): + raise ValueError("--baseline-pi-floor must be in [0, 1].") + if args.baseline_pi_floor != 0.0 and args.dataset_households is None: + raise ValueError("--baseline-pi-floor requires --dataset-households.") if args.no_size_checkpoint and args.dataset_households is None: raise ValueError("--no-size-checkpoint requires --dataset-households.") if args.resume_size_checkpoint is not None: From fa32778c86ebd17dd4bd335f2355277b62ee20a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:40:39 +0200 Subject: [PATCH 2/4] Size evaluation accepts the floored stretch reference a --baseline-pi-floor refit records (#355) --- .../build/uk_runtime/size_evaluation.py | 12 ++++++++---- .../tests/test_uk_size_evaluation.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/microcosm-build/src/microcosm/build/uk_runtime/size_evaluation.py b/packages/microcosm-build/src/microcosm/build/uk_runtime/size_evaluation.py index 017082a3f6..b2a17d44e3 100644 --- a/packages/microcosm-build/src/microcosm/build/uk_runtime/size_evaluation.py +++ b/packages/microcosm-build/src/microcosm/build/uk_runtime/size_evaluation.py @@ -339,13 +339,17 @@ def add( stretch_reference = weights_manifest.get( "stretch_reference", "pool_design" if not is_size else None ) + # The refit's stretch reference is the normalised Horvitz-Thompson + # baseline, trimmed or not (--baseline-pi-floor records which). + accepted_stretch_references = ( + "normalized_horvitz_thompson_w_over_q", + "normalized_horvitz_thompson_w_over_q_floored", + ) add( "stretch_reference", - stretch_reference == "normalized_horvitz_thompson_w_over_q" - if is_size - else None, + stretch_reference in accepted_stretch_references if is_size else None, stretch_reference, - "normalized_horvitz_thompson_w_over_q", + " | ".join(accepted_stretch_references), ) selection_receipt = _mapping(size_receipt.get("selection_receipt")) add( diff --git a/packages/microcosm-build/tests/test_uk_size_evaluation.py b/packages/microcosm-build/tests/test_uk_size_evaluation.py index 6797dea6dd..f8b4abf4b4 100644 --- a/packages/microcosm-build/tests/test_uk_size_evaluation.py +++ b/packages/microcosm-build/tests/test_uk_size_evaluation.py @@ -583,6 +583,24 @@ def test_run_acceptance_statuses_and_bad_digest(tmp_path: Path) -> None: "not_applicable", } + # A refit whose baseline was trimmed (--baseline-pi-floor) names its stretch + # reference with the _floored suffix; the acceptance accepts either name. + manifest_path = path / "rowwise_candidate_manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + floored_name = "normalized_horvitz_thompson_w_over_q_floored" + manifest["weights"]["stretch_reference"] = floored_name + manifest["solve"]["dataset_size"]["stretch_reference"] = floored_name + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + floored = run_acceptance( + load_run(path, label="candidate"), + expected_households=6, + expected_pool=30, + expected_epochs=10, + ) + stretch = next(row for row in floored["checks"] if row["id"] == "stretch_reference") + assert stretch["status"] == "pass" + assert stretch["observed"] == floored_name + (path / "solve_diagnostics.csv").write_text("wrong\n", encoding="utf-8") bad = run_acceptance(run) assert not bad["passed"] From cb95dfba7ce614645b14ad0af5d8ebbd93210c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Fri, 11 Sep 2026 00:35:28 +0200 Subject: [PATCH 3/4] Receipts: the 10 September rebuild on main (spine-q, feed ec7169b, eFRS 1.57.3), Q50's mass-starved refit and its mechanism, the floor experiment, Q50f and D2 (#355) --- experiments/355-uk-dataset-size-receipts.md | 200 ++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/experiments/355-uk-dataset-size-receipts.md b/experiments/355-uk-dataset-size-receipts.md index 4248512391..32f10f48f0 100644 --- a/experiments/355-uk-dataset-size-receipts.md +++ b/experiments/355-uk-dataset-size-receipts.md @@ -730,3 +730,203 @@ additive. Green on the rebased tree: calibrate 236 (with the frozen gates contro local rowwise 52, registers 93, spec pins + parity + coverage tool, multispine 187; ruff and the partition verification clean. Reply to Vahid drafted for María's go. + +## Rebuild on main after the 2026-09-09/10 spine PRs (María, 2026-09-10 ~12:00Z) + +María: "a lot of spine fixing PRs went in in the last 24 hrs" → rebuild the national + local dataset with the +L0 penalty at 55,000 records, 2,000 epochs, `pi_hi` kept at 0.5 ("so that we don't use a lot of compute for +minimal improvement"), run the same evaluation the 55k-candidates-2026-09 report went through, and add a +report to PolicyEngine/uk-dataset-evaluation that compares the new dataset and its dense version with the +enhanced FRS. + +### Tree and engine + +Branch `uk-55k-rebuild-2026-09-10` = origin/main **0afb1235** (merge of #889) in worktree `repos/populace-877` +(the merged `uk-dataset-sizes-355` branch left in place). Since spine-p's 573c4b43 main took #881 (policy-year +rule), #883 (UC claim and target contracts), #891 (national UC targets on paid claims, explicit source windows), +#892 (UC capital graph inputs, five #796 fit exemptions retired), #877 (this machinery), #899 (CI concurrency) +and #889 (CGT measured against FY2024-25 individual observations; the OBR fit exemption retired). The venv +matches main's lock under the CI recipe (`uv sync --all-packages --locked --extra us --extra uk` → "Would make +no changes"): policyengine-uk **2.97.0**, core 3.31.0, torch 2.12.0. `git_dirty` 0 for every run. + +### P0 — inputs and pins + +| input | path | sha256 | note | +|---|---|---|---| +| spine-q | `data/ukds/acceptance/spine-q-355/spine-q.h5` | cf1f9dda198a06819241ed3358f68dd99c675eb7ca45fa766a3ed4b07eb0337f | built 2026-09-10 12:24–12:30Z from the clean tree at 0afb1235 (`build_twin.sh` recipe, all licensed tabs): 28 stages, `age_tail` second, policyengine-uk 2.97.0, 15/15 spine gates passed (release_id `uk-frs-spine-20260910T122422Z`), TFC routed share 1.0, 52,846 households / 61,213 benunits / 113,590 persons, mass 29,247,433.0 — the same entity counts as spine-p. Logbook row c3e1e9a3…. | +| Chronicle consumer artifact **ec7169b** | `data/ukds/acceptance/chronicle-uk-artifact-ec7169b/` | facts 4a50ee9568a01bbb57f73d927084ed6b4b9e52249b51a2338455874ae6e382b5, manifest a95d0ee9f87f36947eaecdb3de29cf81a91e47ccaa822fed42da677eedca877f | 131,450 rows; the feed main's **national** references stand on (`national_chronicle_feed.json`, #891/#889). The local census pin (`local_target_census.py`) still says 6fb700e; the driver takes one feed and both compilations pass on ec7169b (dry run + preflight below), so the run stands on ec7169b for both grains. Not 6fb700e: the ten #891 paid-claim UC targets and the #889 CGT selectors need facts that feed does not carry. | +| OA ladder | `populace-877/build/uk/uk_oa_ladder_2021.npz` | 9c6d56b90d2e975d750106b175020a54c5ec6acf42ef8909d304a9d7fc3868a7 | unchanged | +| enhanced FRS **1.57.3** | HF private repo revision 25af520a (2026-09-04, "Upload data files for version 1.57.3"; uk-data PR #480, one year semantics for the FRS build) | `enhanced_frs_2024_25.h5` ef34c1ae28219367981fbc3c1144f58ea1f8a77554165fe02ff395b04c5ffea5 (127,549,424 B); `parliamentary_constituency_weights.h5` 09e86696…; `local_authority_weights.h5` 383b1104… | the latest published incumbent; the September 9 report used 1.57.2 (7b0a06f0). Release manifest claims policyengine-uk 2.93.0 / core 3.31.1; loaded here under 2.97.0 (national loss under the uk-data venv's 2.89.2 / 1.56.14, as before). | + +Spine-q against spine-p (`tools/compare_uk_h5_payload.py`, `spine-q-355/diff-vs-spine-p/payload_diff.json`): +same keys, row counts and indexes; one new person column `is_uc_claimant` (#883/#891 claimant contract); +values moved in 3 benunit columns (`frs_benunit_capital` and `uc_reported_capital` 1,643 rows, +`would_claim_uc` 431), 37 household columns (the energy/gas/electricity consumption draws on nearly every +household, property wealth and main-residence value on ~8,400, the LCFS consumption categories on +400–1,400 each, bus/rail/fuel spending, financial wealth) and 5 person columns (`universal_credit_reported` +2,297 rows, `student_loan_balance` 34, three under 10). The CGT stages did not change any stored column +(#889 changed the targets and their measurement, not the imputation). + +Incumbent surfaces re-extracted for 1.57.3: `355-dataset-size/incumbent-2025-1.57.3/` (microcosm +`extract_uk_local_incumbent_surface.py`, 40 s, 2.6 GB: 52,846 households, 650 constituencies, 360 local +authorities) and the national loss matrix (uk-data `get_loss_results` on the 1.57.3 file, 637 targets, 433 +within 10 %, 26 s; three salary-sacrifice targets dropped by uk-data itself on HTTP 410). + +Preflight `--env --pins pins-spine-q-ec7169b.txt`: OK. + +### Runs (root `data/ukds/acceptance/355-dataset-size/spine-q/`) + +Runner `run_size_candidate_q.sh` (spine-q, ec7169b, ladder, seed 42, `/usr/bin/time -l`), chain +`chain-q.sh` (Q50 then D2), evaluator `evaluate_size_run_q.sh`, legs `run-efrs-legs-q.sh`. + +**S0 dry run** (`--dry-run --dataset-households 55000 --epochs 2000 --selection-pi-hi 0.5 --skip-holdout`): +exit 0, 111 s, 5.2 GB. Joint surface **20,794 targets × 792,690 households** (364 national + 20,430 local; +the same shape as spine-p's S0: #891 replaced values, not rows), K=15, blocks 1, ladder mass 28,060,832, +vintages unchanged (UC 2025-05, HMRC 2023, ONS housing 2021/2022/2025-12, ONS population 2024). + +**Q50 = `f100-k15-h55000-e2000-p50-s42`** (`--dataset-households 55000 --epochs 2000 --selection-pi-hi 0.5 +--skip-holdout`, chained on P95b's row 392f00cf…): launched 2026-09-10 12:35Z. + +**Q50 DONE 2026-09-10 19:54:22Z, exit 1 = gate battery blocked the candidate (every stage ran, all artifacts +written).** 26,339 s wall (7.3 h; 12:35–19:54Z), 8.0 GB peak RSS, Logbook row 296bbe9b… chained on P95b's +392f00cf…. Dense solve 2,000 epochs → loss **0.015073** (median |rel| 0.73 %, local within 10 % 98.8 %, +national within 10 % 355/364), marginally better than P50's 0.015084 on the old inputs. Budget search +(feasibility-aware, open-probability mass, tolerance 2,750): nine probes — λ 1e-3 (mass 10,066), 1e-5 +(22,787), 1e-6 (59,612, certainties exceed k), 3.16e-6 (40,599), 1.78e-6 (50,117), 1.334e-6 (54,660: 581 +draws from mass 267.4, misses the draw check by ~5 mass units), 1.155e-6 (57,087, exceed), 1.241e-6 +(55,863, exceed), **1.2864e-6 drawable**: mass 55,221, certainties 54,976, 24 boundary draws from mass 273.8 +(max 0.456). P50's landing on spine-p was 1.3335e-6 with 405 draws; the drawable window at 0.5 is a few +hundred rows of mass wide and the bisection needed nine probes (P50's earlier search stopped on the first +in-band probe). Checkpoint written before the draw (`size_selection_checkpoint.{npz,json}`). + +**RED FLAG — the compact refit is mass-starved.** Compact loss **0.15430 = 10.2× dense** (P50 2.3×; the +plan's red flag is 5×); median |rel| **16.0 %** (P50 0.79 %); local rows within 10 % **35.6 %** (P50 93.0 %), +past 25 % 4,680 (P50 463); national within 10 % **168/364** (P50 323); refit total **22.30 m** households +(P50 27.00 m; pool 29.25 m); Kish ESS **1,504** (P50 33,672); max/median positive weight **1,399** (P50 22), +so `uk_local_weight_ratio` fails beside area support and target fit (P50 failed the last two only); +constituencies under ESS 50 191 (P50 211), local authorities 31 (P50 42). Headline national rows compact vs +dense: UK population −16.6 % (dense 0.0 %), state pension −34.6 % (−13.9 %), income tax −28.2 % (−19.6 %), +council tax −32.6 % (−11.0 %), lone households over 65 −39 %, lone households under 65 −41 %. + +Mechanism (from `dataset_size_selection.csv`, not the optimizer): the refit starts from the normalised +Horvitz–Thompson baseline w/π scaled to the pool mass and may stretch each weight at most 10× that baseline. +The 24 boundary draws carry π between 4.6e-6 and 6.5e-5, so their baselines are 0.7–3.3 m households each +and together take **92.0 %** of the 29.25 m; the 54,976 certainties are left 8.0 % (median baseline 0.69× +design) and their 10× ceiling sums to **23.3 m**, below the ~29 m the targets need. The refit therefore +pins 91.3 % of rows at the ceiling (P50 69.7 %), crushes the giants to 0.0002× (they still end with weights +up to 518k) and lands 24 % short of the pool's mass. P50 was the milder case of the same artefact: its 405 +draws held 88.8 % of the baseline mass, but the certainties' ceiling (32.8 m) still covered the need, which +is where its 7.7 % mass loss came from. Floors on π for the baseline (analytic, from the same CSV): at 0.01 +the certainties would hold 96.7 % of the mass with a 283 m ceiling (P50: 66 %, 193 m); at 0.1, 99.7 % and +291 m; the dense weights renormalised to the pool mass give 100 % and 292 m. + +An experiment (scratchpad `refit_floor_experiment.py`, resume of Q50's checkpoint with +`dataset_size.refit_l0_selection` patched to floor the baseline's inclusion probabilities; refit only, +aborted before any artifact; results `refit_floor_results.json`) measures the refit at floors 0.001 / 0.01 +/ 0.1 / 1.0 against an unfloored control. Results below. Any change to the stretch reference is a doctrine +change in `dataset_size.py` and María's ruling; nothing in the run tree was altered. + +**Refit-floor experiment results (20:35–21:06Z, 1,857 s, 7.2 GB; `spine-q/experiments/refit-floor/`):** the +control reproduces Q50 exactly; every floor from 0.001 up lands on one plateau. + +| baseline π floor | loss (× dense 0.015073) | mass (m) | Kish ESS | max/median | rows at 10× ceiling | local within 10 % / past 25 % / median | national within 10 % / 25 % | UK pop · state pension · income tax · lone 65+ | +|---|---|---|---|---|---|---|---|---| +| none (Q50 as built) | 0.15430 (10.24×) | 22.30 | 1,504 | 1,399 | 91.3 % | 35.6 % / 4,680 / 16.0 % | 168 / 316 | −16.6 % · −34.6 % · −28.2 % · −39.1 % | +| 0.001 | 0.01729 (1.15×) | 29.07 | 16,785 | 57 | 1.0 % | 98.5 % / 63 / 0.88 % | 353 / 364 | −0.1 % · −20.2 % · −19.4 % · −0.3 % | +| 0.01 | 0.01723 (1.14×) | 29.13 | 16,322 | 61 | 0.6 % | 98.6 % / 62 / 0.91 % | 353 / 364 | 0.0 % · −20.0 % · −19.2 % · −0.2 % | +| 0.1 | 0.01721 (1.14×) | 29.14 | 16,336 | 61 | 0.6 % | 98.7 % / 59 / 0.89 % | 353 / 364 | 0.0 % · −19.7 % · −19.2 % · 0.0 % | +| 1.0 (dense weights renormalised) | 0.01721 (1.14×) | 29.14 | 16,204 | 65 | 0.5 % | 98.6 % / 60 / 0.89 % | 353 / 364 | 0.0 % · −19.8 % · −19.3 % · +0.2 % | + +Dense reference on the same rows: local within 10 % 98.8 %, past 25 % 53, national within 10 % 355/364, +state pension −13.9 %, income tax −19.6 %. So with the tiny-π blow-up removed the 55,000-row refit fits the +20,794 targets essentially as the 792,690-row solve does (loss 1.14×, the same 60-odd rows past 25 %, the +composition rows within a fraction of a percent); state pension is the one headline row the compact file +still loses (−20 % against the dense −14 %). Not measured here: per-area ESS and the gate battery (the +experiment stops at the refit); the whole-frame Kish ESS of ~16,300 against Q50's 1,504 and P50's 33,672 says +the area floors will still be the binding question at 55k. The lever is a doctrine decision on the size +method's stretch reference (`dataset_size.py`, `refit_l0_selection`'s `support_inclusion_probabilities`); +once ruled, a re-refit from Q50's checkpoint costs ~4 min plus export and battery. + +**D2 = `f100-k15-dense-e2000-s42`** (`--epochs 2000 --skip-holdout`, no size request; same spine, feed, ladder, +seed; chained on Q50's row 296bbe9b…): launched 19:54:22Z, **done 20:50:59Z, exit 1 = gate battery blocked +the candidate** (`uk_local_target_fit` + `uk_local_weight_ratio`, as R17), 3,396 s wall, 9.1 GB peak, H5 +2,437,100,602 B (792,690 households, all positive), mass 29.15 m (−0.3 %). **D2 is Q50's dense reference +exported:** final loss 0.015073282276521414 in both manifests; all 20,430 local rows of D2's +`solve_diagnostics.csv` match Q50's `dense_reference_diagnostics.csv` estimate for estimate (max |diff| 0); +the 364 national rows agree to 3e-5 (float32 export rounding). The progress traces matched digit for digit +at every reported epoch (0.016776 / 0.016049 / 0.015651 / 0.015673). So the dense solve on main is +deterministic across runs and the "dense reference inside the size run" and "a dense run on the same +inputs" are one object; every downstream leg can run on D2 as the dense file. + +### Ruling (María, 2026-09-10 ~21:15Z): evaluate the floored refit + +"should we be calibrating and evaluating the one with much better performance (0.001?)" → yes. Code: commit +**ca435e20** on `uk-55k-rebuild-2026-09-10` adds `--baseline-pi-floor` (driver) → +`solve_uk_rowwise_weights_under_doctrine(baseline_pi_floor=)` → `refit_uk_dataset_size(baseline_pi_floor=)`: +the refit's Horvitz–Thompson baseline divides each selected row's dense weight by `max(q, floor)`; the +search, the draw and the selected rows are untouched, so a checkpoint resume may floor. Default 0 keeps the +untrimmed baseline; candidate-only; the size receipt records `baseline_pi_floor`, `baseline_floored_rows`, +`baseline_mass_share_certainties` and names the stretch reference +`normalized_horvitz_thompson_w_over_q_floored`. Tests: a refit test (floor 1.0 reproduces the dense weights +renormalised; same draw either way; receipt fields; range refusals) and the driver's CLI bounds; local_rowwise +size tests 20, driver 11, spec-engine pins + graph 605 — all green; ruff clean. No pin moved (dataset_size.py +is not an attested calibrate module). + +**Q50f = `f100-k15-h55000-e2000-p50-f001-s42`** (`--resume-size-checkpoint --baseline-pi-floor 0.001`, +same seeds/epochs/pins, chained on D2's row a97aaeeb…): launched 21:21Z at ca435e20, tree clean. + +**Q50f DONE 21:37:11Z, exit 1 = gate battery blocked (`uk_local_area_support`, `uk_local_target_fit`; the weight-ratio +gate passes):** 962 s wall (16 min: pool re-derived and verified against the checkpoint, dense solve and search restored, +draw at the same seed → the same 55,000 rows, refit, export, battery), 9.7 GB, Logbook row chained on D2's a97aaeeb…. +`selection_reused` true, `baseline_pi_floor` 0.001, `baseline_floored_rows` 23 (of the 24 boundary draws), certainties' +share of the baseline mass 75.7 %, stretch reference `normalized_horvitz_thompson_w_over_q_floored`. **Compact loss +0.01729 = 1.15× dense** (the experiment's number exactly), median |rel| 0.88 %, local rows within 10 % **98.5 %** (past +25 % 63; dense 98.8 % / 53), by family 92.3 % (council tax) to 99.9 % (age), national within 10 % **353/364** (dense +355), within 25 % 364/364; refit total **29.07 m** (pool 29.25 m); whole-frame Kish ESS **16,785**; max/median positive +weight 57; 1.0 % of rows at the 10× ceiling; realised max ratio vs design 271 (vs the floored stretch reference 10.0). + +**The trade-off the floor exposes — area support:** constituency ESS median **27.4** (min 7.8), **643/650 under the +floor of 50**; local authorities median 45.7, **218/361 under**. Q50 (191/650, median 60.9) and P50 (211/650, 59.2) +scored better on ESS only because their mass-starved refits pinned 70–91 % of rows at the ceiling, which flattens the +weights; with the baseline trimmed the refit keeps the dense solve's weight inequality on 55,000 rows (about 85 per +constituency), and ESS per area lands near half the floor. Fit and per-area ESS pull against each other at this size; +the floor removes an artefact, it does not settle the size question (S4 at 110k is the next measurement). + +**Evaluations (after-chain, no solve running):** Q50 full evaluation 20:52–21:17Z (step 30 refused: the frozen scoring +register is R17's 19,105 refs against the surface's 19,419 — known; step 50's incumbent scorecard failed because the tool +resolves `--incumbent-h5` to the HF blob path without `.h5`, which policyengine-uk's single-year loader rejects → a copy at +`355-dataset-size/efrs-1.57.3/enhanced_frs_2024_25.h5` (same sha) and the scorecard re-run by hand; the leg runners now +point at the copy). Q50 frozen vs recomputed: 339 of 364 rows matched, 13 over 1 %, max 22 % (private-school pupils −22 %, +VOA council-tax bands +3–15 %, land values +7 % / +22 %, SLC repayments +4 %). D2 steps 20 + 40 21:17–21:27Z. eFRS legs +(Q50 and D2 against 1.57.3, T6 for Q50) from 21:27Z. **A16 retirement evidence on D2:** of the 46 excluded national rows +the surface evaluator resolves, D2 lands 9 within 25 % (4 within 10 %): `SINGLE_annual_payment_9_600_to_10_800` 2 %, +`LONE_PARENT_annual_payment_13_200_to_14_400` 3 %, `LONE_PARENT_…_14_400_to_15_600` 4 %, `LONE_PARENT_…_18_000_to_19_200` +7 %, `LONE_PARENT_…_12_000_to_13_200` 11 %, `LONE_PARENT_…_16_800_to_18_000` 12 %, `LONE_PARENT_…_15_600_to_16_800` 13 %, +`SINGLE_…_14_400_to_15_600` 16 %, `hmrc/self_employment_income_count_income_band_500_000_to_1_000_000` 20 %; the other 37 +stay 25–95 % off. Retirement is a signed register decision (María); nothing retired. D2 on the incumbent's national surface: +564 measured rows, 80.9 % within 10 %, 89.7 % within 25 %, median 1.8 % (the eFRS 1.57.3 on its own targets: 433/637 within +10 %). + +**Q50f evaluation (22:08–22:31Z, all steps; step 30 refused on the frozen register as before) and legs (22:31Z–):** +paired with D2 on all 20,794 rows (no target value differs); frozen vs recomputed national rows: 12 over 1 %, max 11.9 % +(VOA council-tax bands +3 to +12 %, SLC repayments +11 %, private-school pupils −6 %; Q50 reached 22 %). Incumbent +surface (eFRS 1.57.3 extraction): local rows Q50f within 25 % **94.0 %** vs eFRS 87.6 %, within 10 % 70.2 % vs 76.8 % +(constituency 60.1/70.4 within 10 %, 93.3/81.1 within 25 %; local authority 83.8/85.3, 94.9/96.3); national rows (564 +measured) Q50f within 10 % **79.4 %** vs eFRS 68.8 %, within 25 % 89.5 % vs 83.3 %, median 1.8 % vs 4.6 % (candidate ahead on +SPI bands 90/58, DWP UC 62/32, household composition 60/20, OBR 57/48; eFRS ahead on VOA CT stock by region 77/97 and ONS +population by region 93/96). Q50 for comparison: local 33.8 % / 77.9 %, national 29.8 % / 69.7 %. +Downstream on policyengine-uk 2.97.0: **T5** Q50f 7/8 (UC taper −35.4 vs −17.2 ± 15 fails; VAT +22.7 passes), D2 6/8 +(UC taper; VAT +21.1 misses ±10 by 0.2), Q50 7/8 (its underweight file scales impacts down: UC taper −30.5 passes, VAT ++20.7 fails), eFRS 1.57.3 8/8 (UC taper −22.6, VAT +33.2); the six income-tax/benefit reforms agree within £1bn across +Q50f, D2 and the eFRS. **T4** Q50f passes UC spend (80.4 vs 79.3 bn), UC caseload (6.67 vs 6.76 m), carer element, population +(69.7 vs 69.5 m); fails social-rented housing element (unmeasured), LCWRA (7.6 m vs 2.4 m: measurement), income tax (269 vs +331 bn; eFRS 292), multi-family (definition gap); D2 the same four; Q50 fails seven. **T3** Q50f vs D2 152 pass / 33 attention / +6 fail (Q50 vs D2 136/44/10); Q50f vs eFRS 103/55/33, D2 vs eFRS 106/55/30, Q50 vs eFRS 102/52/37 (the eFRS fails are the +model-family list: reported AFCS/BSP, corporate wealth, education and fuel spending, bus fares, legacy benefits, student +loan balance). **#731 scorecard (2.97.0):** population Q50f 69.97 / Q50 58.43 / D2 70.04 / eFRS 69.94 m; households 29.39 / +22.55 / 29.47 / 31.46 m; state pension 124.3 / 101.9 / 134.1 / 133.8 bn; income tax 287.7 / 255.9 / 286.8 / 312.5 bn; UC 83.6 / +70.9 / 84.2 / 80.2 bn; UC families 6.78 / 6.12 / 6.76 / 6.38 m; child benefit 17.3 / 15.1 / 17.0 / 17.6 bn; council tax 48.3 / +38.1 / 49.3 / 52.4 bn; poverty BHC 10.2 / 10.2 / 9.9 / 11.0 %; top-1 % share 8.5 / 9.1 / 8.1 / 6.9 %; Gini 0.374 / 0.371 / +0.371 / 0.376. **T6 (uk-data venv, eFRS 1.57.3 as the comparison file):** Q50 autumn budget 807 ok / 485 flag (closer 25/37), +RF UC 10/17 (closer 6/10), childcare 17/16 (closer 7/17), all "review"; Q50f's replays ran after 22:33Z (results in the +evaluation page's T6 tables). From ecfefee130414c4920cfda958e6bdd4d97101fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Tue, 15 Sep 2026 14:46:29 +0200 Subject: [PATCH 4/4] Bind the UC element targets on paid claims and an OBR UC total on the chronicle 474a0ae feed (#882) Seven dwp_universal_credit rows (LCWRA, carer, housing any/social/private tenure, childcare, deductions) bind the chronicle#260 Payment Indicator crosses on the calendar-2025 paid-claim basis of the ten broad rows; obr.universal_credit sums the two OBR welfare-cap cells (expected_member_count 2). Three rows are measured but held out of the objective on the reviewed exclusion register: carer and childcare as model concept gaps (-45 % / +155 % against the bound targets at the 10 September baseline) and the any-tenure housing row as a structural bias (DWP's 'Yes' includes an other/unknown tenure the model cannot carry: 112,518 of 4,037,650 in calendar 2025). LCWRA stays in the objective by the stated rule: a concept mismatch goes to the register only when it puts the row outside the 25 percent release bound at the baseline. The entries cite microcosm#882 and docs/uk-uc-baseline-2026-09-10.md, signed in review of #921 on 2026-09-15 with a one-month window, and the register test asserts the tranche. Rebuilt on main after #904 and #906: the feed is the one #904 pinned for both grains (chronicle 474a0ae, chronicle#263 labels over the #260 packages), the region tier from #906 is untouched, and nothing here binds a sub-national row. National references and membership regenerate with main's schema-8 generator: 595 -> 603 active, 7 deferred, 7 signed out; no existing reference changes. Both national compile-parity receipts gain the new rows as ledger-only entries (eight at 2025, seven at 2023: the OBR total does not compile at 2023) and nothing else moves; the vendored fact resources check clean. Also: docs/uk-uc-baseline-2026-09-10.md records the Q50f/D2 UC readout, both feed pins, the compile-time targets and the element sizing from the new tools/diagnose_uk_uc_elements.py; a changelog fragment covers the #355 --baseline-pi-floor knob riding this branch. Co-Authored-By: Claude Fable 5.1 --- changelog.d/355-baseline-pi-floor.added.md | 1 + changelog.d/882-uk-uc-baseline.added.md | 1 + .../882-uk-uc-element-targets.added.md | 1 + docs/uk-uc-baseline-2026-09-10.md | 92 ++ .../uk/calibration_measure_exclusions.json | 41 +- ...ity_incumbent_2025_signed_differences.json | 62 +- ...ty_production_2023_signed_differences.json | 55 +- .../build/uk/target_reference_membership.json | 168 +++- .../microcosm/build/uk/target_references.json | 342 +++++++ .../build/uk/uk_population_targets.json | 889 +++++++++++++++++- .../tests/test_country_spec.py | 2 +- .../tests/test_uk_measure_simulation.py | 43 +- .../tests/test_uk_national_calibration.py | 2 +- .../tests/test_uk_population_targets.py | 16 +- .../tests/test_uk_target_references.py | 43 +- .../tests/test_uk_uc_source_periods.py | 27 +- tools/diagnose_uk_uc_elements.py | 141 +++ 17 files changed, 1890 insertions(+), 36 deletions(-) create mode 100644 changelog.d/355-baseline-pi-floor.added.md create mode 100644 changelog.d/882-uk-uc-baseline.added.md create mode 100644 changelog.d/882-uk-uc-element-targets.added.md create mode 100644 docs/uk-uc-baseline-2026-09-10.md create mode 100644 tools/diagnose_uk_uc_elements.py diff --git a/changelog.d/355-baseline-pi-floor.added.md b/changelog.d/355-baseline-pi-floor.added.md new file mode 100644 index 0000000000..a43c9ffa9a --- /dev/null +++ b/changelog.d/355-baseline-pi-floor.added.md @@ -0,0 +1 @@ +Add `--baseline-pi-floor` to the UK rowwise candidate build: the size refit's Horvitz-Thompson stretch reference divides by max(π, floor), so a handful of near-zero-probability boundary draws no longer hold most of the refit mass; candidate-only, recorded in the size receipt, default 0 keeps the old behaviour (#355). diff --git a/changelog.d/882-uk-uc-baseline.added.md b/changelog.d/882-uk-uc-baseline.added.md new file mode 100644 index 0000000000..9c1d36bb37 --- /dev/null +++ b/changelog.d/882-uk-uc-baseline.added.md @@ -0,0 +1 @@ +Record the 10 September 2026 UC baseline (Q50f/D2 on spine-q) and add `tools/diagnose_uk_uc_elements.py`, which sizes UC element support conditioned on paid awards (#882). diff --git a/changelog.d/882-uk-uc-element-targets.added.md b/changelog.d/882-uk-uc-element-targets.added.md new file mode 100644 index 0000000000..667789b3a7 --- /dev/null +++ b/changelog.d/882-uk-uc-element-targets.added.md @@ -0,0 +1 @@ +Bind the DWP Universal Credit element counts (LCWRA, carer, housing by tenure, childcare, deductions) on paid claims and an OBR total-UC-expenditure row, with the carer and childcare rows held on the reviewed exclusion register until their model gaps are repaired (#882). diff --git a/docs/uk-uc-baseline-2026-09-10.md b/docs/uk-uc-baseline-2026-09-10.md new file mode 100644 index 0000000000..8cca7ec5b8 --- /dev/null +++ b/docs/uk-uc-baseline-2026-09-10.md @@ -0,0 +1,92 @@ +# UK Universal Credit baseline, 10 September 2026 rebuild + +This section records the Universal Credit (UC) readout of the 10 September 2026 rebuild, the baseline the element bindings and the later UC repairs are measured against. It is a measurement at pinned inputs, not a certification: neither file is shippable, and no target or exclusion changes on the strength of these numbers. Issue record: [microcosm#882](https://github.com/PolicyEngine/microcosm/issues/882#issuecomment-5633606144). Evaluation page: https://uk-dataset-evaluation.vercel.app/55k-rebuild-2026-09-10/. + +## Pins + +| item | value | +|---|---| +| spine | spine-q, `data/ukds/acceptance/spine-q-355/spine-q.h5`, sha `cf1f9dda198a06819241ed3358f68dd99c675eb7ca45fa766a3ed4b07eb0337f` (28 stages, 52,846 / 61,213 / 113,590 households / benefit units / persons) | +| code | main `0afb1235` plus `--baseline-pi-floor` (this branch's floor-knob commits) | +| Chronicle feed (baseline build) | `ec7169b5db40b9f54117c80f70f14efc1dd0fedd`, facts `4a50ee9568a01bbb57f73d927084ed6b4b9e52249b51a2338455874ae6e382b5`, 131,450 rows | +| Chronicle feed (element targets, section below) | `474a0ae100e9dbfa43c167e9e200ec07dcfc6643`, facts `bb12d77a661ef1649c2907211bfd58bc031dcf7d41d2e5d39a09d63eb7266d3d`, 141,400 rows (chronicle#260 packages with chronicle#263 labels) | +| engine | policyengine-uk 2.97.0, core 3.31.0 | +| surface | 20,794 rows: 364 national, 20,430 local (constituency and local authority) | +| doctrine | `grain_equal`, 2,000 epochs, learning rate 0.15, maximum weight ratio 10, target loss cap 10, seed 42, no sparsity penalty in the solve | +| Q50f | 55,000-row L0 candidate, `--selection-pi-hi 0.5 --baseline-pi-floor 0.001`, run `spine-q/f100-k15-h55000-e2000-p50-f001-s42` | +| D2 | dense twin, 792,690 rows, run `spine-q/f100-k15-dense-e2000-s42` | + +Receipts for the build itself: `experiments/355-uk-dataset-size-receipts.md`. + +## Bound UC rows + +The ten broad paid-claim rows (calendar-2025 targets from #891), relative error from each run's `calibration_diagnostics.json`: + +| row | target | Q50f | D2 | +|---|---:|---:|---:| +| `dwp.uc.households` | 6,197,311 | +2.80 % | +2.99 % | +| `dwp.uc.households_single_no_children` | 2,990,070 | +1.90 % | +1.85 % | +| `dwp.uc.households_single_with_children` | 2,138,780 | +2.08 % | +3.84 % | +| `dwp.uc.households_couple_no_children` | 241,131 | +0.76 % | +2.25 % | +| `dwp.uc.households_couple_with_children` | 826,161 | +8.71 % | +5.26 % | +| `dwp.uc.households_children_1` | 1,190,124 | +0.03 % | +0.03 % | +| `dwp.uc.households_children_2` | 1,049,896 | +8.38 % | +9.23 % | +| `dwp.uc.households_children_3` | 477,888 | +6.13 % | +5.57 % | +| `dwp.uc.households_children_4` | 170,714 | −0.38 % | +0.97 % | +| `dwp.uc.households_children_5_or_more` | 76,320 | −0.67 % | −0.11 % | + +For scale, the #892 fresh build measured the headline at −4.71 %, lone parents at −14.01 % and one child at −19.45 %. + +The other bound rows: all 15 two-child-limit rows within 2.03 % on both files; Scotland child under one −0.97 % / −0.41 %; benefit-cap capped households −0.03 % / +0.08 %; the 82 bound payment bands within 4.34 % (Q50f) / 5.08 % (D2). Of the 108 bound national UC rows, 105 are within 5 % on Q50f (104 on D2) and none is past 10 %. + +Local UC rows (3,508 across constituencies and local authorities): Q50f 99.29 % within 10 %, 100 % within 25 %, worst 15.6 %; D2 99.40 % / 100 % / 12.6 %. The childless bucket is the weakest inside the family (60.6 % within 5 % against 93–95 % for the child buckets). + +## Excluded UC rows, as measured by the incumbent-surface evaluator + +The three childless-couple payment bands from £26,400 upward stay at −100 % on both files (no model record). Of the 16 bands excluded under uk-data#452, seven are within 25 % on both files: lone-parent £12,000–13,200 (+14.7 % / +10.9 %), £13,200–14,400 (−15.7 % / −3.2 %), £14,400–15,600 (+10.2 % / +3.9 %), £15,600–16,800 (−10.7 % / −12.6 %), £16,800–18,000 (+14.1 % / +11.7 %), single £9,600–10,800 (+13.9 % / +2.1 %), and the HMRC self-employment count at £500k–1m. D2 alone adds lone-parent £18,000–19,200 (−6.9 %) and single £14,400–15,600 (−15.9 %). The rest stay 26–143 % off. Retirement is a signed register decision. + +The OBR in-cap / outside-cap rows measure −96.7 % / +503 % on both files. Their sum (£79.29bn, FY2025/26) against the model's total UC: Q50f £80.38bn (+1.4 %), D2 £80.82bn (+1.9 %), enhanced FRS 1.57.3 £75.85bn (−4.3 %). + +## Element support, conditioned on paid UC + +Measured on Q50f at its calibrated weights with `tools/diagnose_uk_uc_elements.py` (Great Britain, benefit units with `universal_credit > 0`, policy year 2025) against the April–December 2025 means of the DWP all-claims series then in the feed: + +| element | model, paid UC | published mean | gap | +|---|---:|---:|---:| +| LCWRA entitlement (`uc_LCWRA_element > 0`) | 2.62m | 2.42m | +8 % | +| deductions (`uc_deductions > 0`) | 2.99m (46.6 % of paid) | 3.0–3.1m (46–47 %) | ≈0 % | +| housing element, any tenure | 4.01m | 4.30m | −7 % | +| housing element, social rented only | 2.35m | not then in the feed | — | +| carer element | 0.60m | 1.13m | −47 % | +| childcare element | 456k (£2.06bn) | 184k Jan–Aug 2025 (≈£0.8bn) | +148 % | + +Three readings behind the table. The feed's housing series was labelled `Social Rented Sector` but was the all-tenure count (its Chronicle package grouped tenure codes 1+2+3; corrected in chronicle#260). The evaluation page's T4 admin legs measured elements without a UC award condition, which is why they showed LCWRA at +216 % (7.29m unconditioned) and carer at +5 % (1.14m unconditioned). Nil-payment open claims cannot be identified in the model: the would-claim ∧ eligible ∧ positive-maximum proxy gives 14.3m units, so element rows bind on paid claims through the element × Payment Indicator crosses chronicle#260 added. + +The two gaps are model concepts, not weights. The engine's carer condition is Carer's Allowance receipt (`is_carer_for_benefits` = `receives_carer_benefit`) while the UC carer element needs 35 hours a week of care for a qualifying-benefit recipient without Carer's Allowance; the engine's `care_hours` input is never filled by the spine. The childcare element is paid to every benefit unit where all adults work and childcare costs are reported, with no take-up or incidence lever. + +## Downstream legs against enhanced FRS 1.57.3 + +T5 "UC taper to 20 %": Q50f −35.4bn, D2 −34.9bn against an expected −17.2 ± 15 (eFRS −22.6bn). T6 Resolution Foundation UC: 13 ok / 14 flagged, closer on 8 of 10 benchmarks; UC expenditure on the 2029-30 basis £91.1bn against RF's £86bn; mean monthly UC childcare element £636 against RF's £420. T6 childcare: UC childcare element £2.15bn against a £0.81bn benchmark. + +## Support, concentration and the non-UC rows + +Q50f: Kish ESS 16,785 (0.305 of 55,000), max/median weight 57, household mass 29.07m (−0.6 %), per-area support gate failed (643 of 650 constituencies under ESS 50). D2: Kish ESS 130,504, max/median 296 (fails the reviewed 100 ceiling), mass 29.15m (−0.3 %). Protected non-UC rows on the joint solve: income tax −19.4 % / −19.6 %, NI −3.9 % / −3.9 %, state pension −20.2 % / −13.9 %, child benefit +23.0 % / +21.1 %; the ONS lone-parent-with-dependent-children household row +21.9 % / +19.0 %. The #882 national-only diagnostic arm (family_equal, 1,500 updates) had income tax −12.7 %, state pension −6.4 %, child benefit +17.7 %; whether the UC rows or the local surface account for the difference cannot be read from these receipts. + +## Element targets on the chronicle#260 feed (paid-claim basis) + +Compile-time target values are calendar-2025 means of the Payment Indicator = Yes cells (childcare: the all-claims ODS count; deductions: March to December 2025), from the chronicle `474a0ae` artifact (chronicle#260 packages with chronicle#263 labels). Model values are Great Britain benefit units with `universal_credit > 0` and the element `> 0` at each file's calibrated weights, from `tools/diagnose_uk_uc_elements.py` (receipts `elements_q50f.json`, `elements_d2.json`). + +| target | published (CY2025 paid mean) | Q50f | D2 | Q50f gap | D2 gap | +|---|---:|---:|---:|---:|---:| +| `dwp.uc.households_lcwra_element` (LCWRA entitlement) | 2,272,425 | 2,619,296 | 2,731,661 | +15.3 % | +20.2 % | +| `dwp.uc.households_carer_element` (carer entitlement) | 1,078,514 | 595,807 | 602,749 | -44.8 % | -44.1 % | +| `dwp.uc.households_housing_element` (housing entitlement, any tenure) | 4,037,650 | 4,006,395 | 4,063,719 | -0.8 % | +0.6 % | +| `dwp.uc.households_housing_element_social_rented` (housing entitlement, social rented) | 2,354,588 | 2,348,245 | 2,474,679 | -0.3 % | +5.1 % | +| `dwp.uc.households_housing_element_private_rented` (housing entitlement, private rented) | 1,570,545 | 1,658,150 | 1,589,040 | +5.6 % | +1.2 % | +| `dwp.uc.households_childcare_element` (childcare element (all claims, ODS)) | 178,667 | 455,634 | 451,401 | +155.0 % | +152.6 % | +| `dwp.uc.households_with_deduction` (households with a deduction) | 3,130,000 | 2,990,545 | 2,991,319 | -4.5 % | -4.4 % | +| `obr.universal_credit` (total UC expenditure, UK, FY2025/26) | £79.29bn | £80.38bn | £80.82bn | +1.4 % | +1.9 % | + +Deductions and the two tenure rows are within 6 % on both files and LCWRA within 15 % (Q50f) / 20 % (D2); those four rows are active. LCWRA stays in the objective by the rule its binding note states: a concept mismatch goes to the register only when it puts the row outside the 25 % release bound at the baseline. Carer and childcare are measured but held out of the objective on the reviewed exclusion register until their model gaps are repaired (see the element-support section above). The any-tenure housing row is also measured but held out: DWP's 'Yes' is social plus private plus 'Other or unknown' tenure, the model has no other/unknown tenure, so its any-tenure count is identically the two tenure rows and the published total exceeds what the model can reach by the other/unknown cell (112,518 in calendar 2025, 2.8 %); fitting all three would over-fill the tenure rows. The register entries are signed by María in review of the element PR (2026-09-15) with a one-month window. The paid-claim targets sit below the April–December all-claims means quoted in the earlier section because the calendar window includes the lower January–March 2025 months and excludes nil-payment claims. + +Paid GB caseload for scale: Q50f 6,416,983, D2 6,428,401; share of paid units with earnings 51.6 % / 49.6 %. diff --git a/packages/microcosm-build/src/microcosm/build/uk/calibration_measure_exclusions.json b/packages/microcosm-build/src/microcosm/build/uk/calibration_measure_exclusions.json index d327461dc6..2ac5a2ba74 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/calibration_measure_exclusions.json +++ b/packages/microcosm-build/src/microcosm/build/uk/calibration_measure_exclusions.json @@ -363,7 +363,7 @@ }, { "name": "ons.savings_interest_income", - "reason": "The spine carries \u00a313.8bn of savings interest income against the ONS \u00a395.5bn (-86% at initialization on spine-m, -89% after the solve): the FRS interest income capture, not the weights. Unreachable by reweighting within the stretch bound: a frame-side gap, not a weight question; while bound it pulls the joint solve toward weight concentration (housing benefit alone triples the max/median ratio under grain_equal). One-month window: correct the spine or re-adjudicate.", + "reason": "The spine carries £13.8bn of savings interest income against the ONS £95.5bn (-86% at initialization on spine-m, -89% after the solve): the FRS interest income capture, not the weights. Unreachable by reweighting within the stretch bound: a frame-side gap, not a weight question; while bound it pulls the joint solve toward weight concentration (housing benefit alone triples the max/median ratio under grain_equal). One-month window: correct the spine or re-adjudicate.", "tracking": "microcosm#866", "approved_by": "juaristi22", "adjudication": "microcosm#762 (A16, ruling 2026-09-03; receipts R8-R10 in experiments/762-uk-rowwise-candidate-receipts.md)", @@ -372,7 +372,7 @@ }, { "name": "obr.housing_benefit", - "reason": "The spine carries \u00a32.7bn of housing benefit against the OBR \u00a312.1bn (-78% at initialization on spine-m). Under grain_equal the solver chases the aggregate by multiplying the few HB households (\u00a39.4bn at 512 epochs, \u00a311.4bn at 1500), which halves the effective sample and doubles Glasgow's social-rent count; under uniform it is ignored. Unreachable by reweighting within the stretch bound: a frame-side gap, not a weight question; while bound it pulls the joint solve toward weight concentration (housing benefit alone triples the max/median ratio under grain_equal). One-month window: correct the spine or re-adjudicate.", + "reason": "The spine carries £2.7bn of housing benefit against the OBR £12.1bn (-78% at initialization on spine-m). Under grain_equal the solver chases the aggregate by multiplying the few HB households (£9.4bn at 512 epochs, £11.4bn at 1500), which halves the effective sample and doubles Glasgow's social-rent count; under uniform it is ignored. Unreachable by reweighting within the stretch bound: a frame-side gap, not a weight question; while bound it pulls the joint solve toward weight concentration (housing benefit alone triples the max/median ratio under grain_equal). One-month window: correct the spine or re-adjudicate.", "tracking": "microcosm#867", "approved_by": "juaristi22", "adjudication": "microcosm#762 (A16, ruling 2026-09-03; receipts R8-R10 in experiments/762-uk-rowwise-candidate-receipts.md)", @@ -411,7 +411,7 @@ "reason": "Zero-support channel under the corrected UC composition proxy: since #879 the payment-distribution rows are measured on the relationship-based uc_calibration_family_type, and no benefit unit in the spine is a couple without children with an annual UC award in this band (final estimate 0.0, -100% on #879's corrected comparison; no pool carrier on the K=15 rowwise clone of spine-p either, where the #355 size selection refuses the row by name). The dense seam carries the row as a capped miss; a size selection cannot carry it. The band catalogue and the unbounded top band are tracked in microcosm#736; the sibling 26,400-27,600 band already sits in this register.", "tracking": "microcosm#736", "approved_by": "juaristi22", - "adjudication": "microcosm#355 (dataset-size experiment; ruling by Mar\u00eda 2026-09-08 after the 45,800 smoke's by-name refusal)", + "adjudication": "microcosm#355 (dataset-size experiment; ruling by María 2026-09-08 after the 45,800 smoke's by-name refusal)", "approved_on": "2026-09-08", "expires_on": "2026-12-08" }, @@ -420,7 +420,7 @@ "reason": "Zero-support channel under the corrected UC composition proxy: since #879 the payment-distribution rows are measured on the relationship-based uc_calibration_family_type, and no benefit unit in the spine is a couple without children with an annual UC award in this band (final estimate 0.0, -100% on #879's corrected comparison; no pool carrier on the K=15 rowwise clone of spine-p either, where the #355 size selection refuses the row by name). The dense seam carries the row as a capped miss; a size selection cannot carry it. The band catalogue and the unbounded top band are tracked in microcosm#736; the sibling 26,400-27,600 band already sits in this register.", "tracking": "microcosm#736", "approved_by": "juaristi22", - "adjudication": "microcosm#355 (dataset-size experiment; ruling by Mar\u00eda 2026-09-08 after the 45,800 smoke's by-name refusal)", + "adjudication": "microcosm#355 (dataset-size experiment; ruling by María 2026-09-08 after the 45,800 smoke's by-name refusal)", "approved_on": "2026-09-08", "expires_on": "2026-12-08" }, @@ -429,7 +429,7 @@ "reason": "Spine support absent for band H in the North East region-tier cell: the spine carries no council-tax band-H household there (spine-m held 170 band-H households from 49 raw FRS households, none in the North East, North West or Yorkshire and the Humber; microcosm#762 A14), so the cell measures 0 against the VOA stock (-100% on the #905 spine-q comparison, docs/evidence/uk-region-tier-905/calibration-comparison.json) while the other 78 VOA region cells fit within 5%. The same class is signed for the local band-H family under council_tax_band_h_spine_support_absent; the cell binds when a spine vintage carries broader band-H support.", "tracking": "microcosm#796", "approved_by": "juaristi22", - "adjudication": "microcosm#905 (region-tier activation; ruling by Mar\u00eda 2026-09-14 on PR #906, the #762 A14 spine-support class)", + "adjudication": "microcosm#905 (region-tier activation; ruling by María 2026-09-14 on PR #906, the #762 A14 spine-support class)", "approved_on": "2026-09-14", "expires_on": "2026-11-26" }, @@ -438,7 +438,7 @@ "reason": "Spine support absent for band H in the North West region-tier cell: the spine carries no council-tax band-H household there (spine-m held 170 band-H households from 49 raw FRS households, none in the North East, North West or Yorkshire and the Humber; microcosm#762 A14), so the cell measures 0 against the VOA stock (-100% on the #905 spine-q comparison, docs/evidence/uk-region-tier-905/calibration-comparison.json) while the other 78 VOA region cells fit within 5%. The same class is signed for the local band-H family under council_tax_band_h_spine_support_absent; the cell binds when a spine vintage carries broader band-H support.", "tracking": "microcosm#796", "approved_by": "juaristi22", - "adjudication": "microcosm#905 (region-tier activation; ruling by Mar\u00eda 2026-09-14 on PR #906, the #762 A14 spine-support class)", + "adjudication": "microcosm#905 (region-tier activation; ruling by María 2026-09-14 on PR #906, the #762 A14 spine-support class)", "approved_on": "2026-09-14", "expires_on": "2026-11-26" }, @@ -447,9 +447,36 @@ "reason": "Spine support absent for band H in the Yorkshire and The Humber region-tier cell: the spine carries no council-tax band-H household there (spine-m held 170 band-H households from 49 raw FRS households, none in the North East, North West or Yorkshire and the Humber; microcosm#762 A14), so the cell measures 0 against the VOA stock (-100% on the #905 spine-q comparison, docs/evidence/uk-region-tier-905/calibration-comparison.json) while the other 78 VOA region cells fit within 5%. The same class is signed for the local band-H family under council_tax_band_h_spine_support_absent; the cell binds when a spine vintage carries broader band-H support.", "tracking": "microcosm#796", "approved_by": "juaristi22", - "adjudication": "microcosm#905 (region-tier activation; ruling by Mar\u00eda 2026-09-14 on PR #906, the #762 A14 spine-support class)", + "adjudication": "microcosm#905 (region-tier activation; ruling by María 2026-09-14 on PR #906, the #762 A14 spine-support class)", "approved_on": "2026-09-14", "expires_on": "2026-11-26" + }, + { + "name": "dwp/uc/elements/carer", + "reason": "Model concept gap, not a weight question: the engine's carer condition is Carer's Allowance receipt (is_carer_for_benefits = receives_carer_benefit) while the UC carer element needs 35 hours a week of care for a qualifying-benefit recipient with no Carer's Allowance requirement; the spine never fills the engine's care_hours input. Measured at the 10 September 2026 baseline against the bound calendar-2025 paid target: Q50f 595,807 / D2 602,749 paid-UC benefit units against 1,078,514 (-45%). Fitting it would move half the carriers' weight against a definition mismatch. Repair: care hours from the FRS care file and a regulation-based carer condition, then activation.", + "tracking": "microcosm#882", + "approved_by": "juaristi22", + "adjudication": "microcosm#882 (element sizing at the 10 September 2026 baseline, docs/uk-uc-baseline-2026-09-10.md; María's sign-off in review of microcosm#921 on 2026-09-15, setting the one-month window)", + "approved_on": "2026-09-15", + "expires_on": "2026-10-15" + }, + { + "name": "dwp/uc/elements/childcare", + "reason": "Model concept gap, not a weight question: the engine pays the childcare element to every benefit unit where all adults work and childcare costs are reported, with no take-up or incidence lever, while DWP publishes 160-193k households a month receiving it in 2025. Measured at the 10 September 2026 baseline against the bound calendar-2025 target: Q50f 455,634 / D2 451,401 paid-UC benefit units (GBP 2.06bn) against 178,667 published (+155%). Repair: an incidence stage modelled on uc_deduction_attributes drawing to the published count, then activation.", + "tracking": "microcosm#882", + "approved_by": "juaristi22", + "adjudication": "microcosm#882 (element sizing at the 10 September 2026 baseline, docs/uk-uc-baseline-2026-09-10.md; María's sign-off in review of microcosm#921 on 2026-09-15, setting the one-month window)", + "approved_on": "2026-09-15", + "expires_on": "2026-10-15" + }, + { + "name": "dwp/uc/elements/housing", + "reason": "Structural bias, not a weight question: DWP's any-tenure housing-entitlement count is social plus private plus 'Other or unknown' tenure, and the model has no other/unknown tenure (uc_housing_costs_element pays only council, housing-association and private renters), so the model's any-tenure count is identically the social plus private rows, which are bound and fit; the row overstates the reachable total by the other/unknown cell, 112,518 of 4,037,650 in calendar 2025 (2.8 percent; the published any-tenure mean, which the three tenure cells reproduce to the unit). Fitting all three housing rows would ask the solver to over-fill the tenure rows by that amount. Measured on every evaluation as the housing-element caseload diagnostic (Q50f -0.8 percent, D2 +0.6 percent at the baseline).", + "tracking": "microcosm#882", + "approved_by": "juaristi22", + "adjudication": "microcosm#882 (element sizing at the 10 September 2026 baseline, docs/uk-uc-baseline-2026-09-10.md; María's sign-off in review of microcosm#921 on 2026-09-15, setting the one-month window)", + "approved_on": "2026-09-15", + "expires_on": "2026-10-15" } ] } diff --git a/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_incumbent_2025_signed_differences.json b/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_incumbent_2025_signed_differences.json index fb287cd0d2..e984f9f632 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_incumbent_2025_signed_differences.json +++ b/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_incumbent_2025_signed_differences.json @@ -1,11 +1,11 @@ { - "compiled_count": 595, + "compiled_count": 603, "counts_by_kind": { "calibration_drift": 280, "fixture_only": 80, - "ledger_only": 38 + "ledger_only": 46 }, - "difference_count": 398, + "difference_count": 406, "differences": [ { "fixture_value": 1608000.0, @@ -623,6 +623,55 @@ "period": 2025, "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." }, + { + "kind": "ledger_only", + "ledger_value": 1078514.5, + "name": "dwp.uc.households_carer_element", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 178666.66666666666, + "name": "dwp.uc.households_childcare_element", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 4037650.3333333335, + "name": "dwp.uc.households_housing_element", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 1570545.4166666667, + "name": "dwp.uc.households_housing_element_private_rented", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 2354587.5833333335, + "name": "dwp.uc.households_housing_element_social_rented", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 2272425.1666666665, + "name": "dwp.uc.households_lcwra_element", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 3130000.0, + "name": "dwp.uc.households_with_deduction", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, { "kind": "ledger_only", "ledger_value": 597920.0, @@ -784,6 +833,13 @@ "period": 2025, "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." }, + { + "kind": "ledger_only", + "ledger_value": 79286349139.54411, + "name": "obr.universal_credit", + "period": 2025, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, { "kind": "ledger_only", "ledger_value": 43427000000.0, diff --git a/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_production_2023_signed_differences.json b/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_production_2023_signed_differences.json index a9477d4ce5..3160a3d311 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_production_2023_signed_differences.json +++ b/packages/microcosm-build/src/microcosm/build/uk/ledger_compile_parity_production_2023_signed_differences.json @@ -1,11 +1,11 @@ { - "compiled_count": 337, + "compiled_count": 344, "counts_by_kind": { "calibration_drift": 120, "fixture_only": 17, - "ledger_only": 205 + "ledger_only": 212 }, - "difference_count": 342, + "difference_count": 349, "differences": [ { "fixture_value": 82000.0, @@ -182,6 +182,20 @@ "period": 2023, "reason": "Inherited PR #891 contract: current paid-UC counts use the explicit January-December 2025 source windows on the ec7169 national feed. This compares that declaration with the frozen 2023 production baseline; it does not redatum the observations to the comparison year or create a new year ruling." }, + { + "kind": "ledger_only", + "ledger_value": 1078514.5, + "name": "dwp.uc.households_carer_element", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 178666.66666666666, + "name": "dwp.uc.households_childcare_element", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, { "kind": "ledger_only", "ledger_value": 1190123.8333333333, @@ -231,6 +245,34 @@ "period": 2023, "reason": "Inherited PR #891 contract: current paid-UC counts use the explicit January-December 2025 source windows on the ec7169 national feed. This compares that declaration with the frozen 2023 production baseline; it does not redatum the observations to the comparison year or create a new year ruling." }, + { + "kind": "ledger_only", + "ledger_value": 4037650.3333333335, + "name": "dwp.uc.households_housing_element", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 1570545.4166666667, + "name": "dwp.uc.households_housing_element_private_rented", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 2354587.5833333335, + "name": "dwp.uc.households_housing_element_social_rented", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, + { + "kind": "ledger_only", + "ledger_value": 2272425.1666666665, + "name": "dwp.uc.households_lcwra_element", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, { "kind": "ledger_only", "ledger_value": 2990070.1666666665, @@ -245,6 +287,13 @@ "period": 2023, "reason": "Inherited PR #891 contract: current paid-UC counts use the explicit January-December 2025 source windows on the ec7169 national feed. This compares that declaration with the frozen 2023 production baseline; it does not redatum the observations to the comparison year or create a new year ruling." }, + { + "kind": "ledger_only", + "ledger_value": 3130000.0, + "name": "dwp.uc.households_with_deduction", + "period": 2023, + "reason": "Ledger-compiled row has no fixture counterpart at this comparison period." + }, { "kind": "ledger_only", "ledger_value": 624500000.0, diff --git a/packages/microcosm-build/src/microcosm/build/uk/target_reference_membership.json b/packages/microcosm-build/src/microcosm/build/uk/target_reference_membership.json index 17cc9a2534..5d5692b460 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/target_reference_membership.json +++ b/packages/microcosm-build/src/microcosm/build/uk/target_reference_membership.json @@ -1,11 +1,11 @@ { "source_fact_feed": "chronicle-uk-474a0ae1-consumer-facts", "target_period": 2025, - "candidate_count": 609, - "contract_target_count": 210, - "active_reference_count": 595, + "candidate_count": 617, + "contract_target_count": 218, + "active_reference_count": 603, "status_counts": { - "active": 595, + "active": 603, "no_fact_at_or_before_period": 7, "signed_excluded": 7 }, @@ -1605,6 +1605,38 @@ "nithc.public_transport_support": { "geography_level": "country", "geography_id": "N92000002" + }, + "dwp.uc.households_lcwra_element": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "dwp.uc.households_carer_element": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "dwp.uc.households_housing_element": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "dwp.uc.households_housing_element_social_rented": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "dwp.uc.households_housing_element_private_rented": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "dwp.uc.households_childcare_element": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "dwp.uc.households_with_deduction": { + "geography_level": "country", + "geography_id": "K03000001" + }, + "obr.universal_credit": { + "geography_level": "country", + "geography_id": "K02000001" } }, "genuine_sum_residue": [ @@ -1612,6 +1644,7 @@ "dfi_ni.bus.passenger_receipts", "nithc.public_transport_support", "obr.esa", + "obr.universal_credit", "ons.population.age_0_9_by_region@E12000001", "ons.population.age_0_9_by_region@E12000002", "ons.population.age_0_9_by_region@E12000003", @@ -11977,6 +12010,133 @@ "resolved_fact_key": "ledger_aggregate_fact_v2_be8eb7011b6f65fa65d52fcb" } ] + }, + "dwp.uc.households_lcwra_element": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_lcwra_element", + "status": "active", + "matched_fact_count_overall": 12, + "matched_fact_count_at_or_before_period": 12, + "matched_fact_count_in_source_window": 12, + "resolved_period": 2025, + "resolved_value": 2272425.1666666665, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_f59149c4c514e1acffb728a8" + } + ] + }, + "dwp.uc.households_carer_element": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_carer_element", + "status": "active", + "matched_fact_count_overall": 12, + "matched_fact_count_at_or_before_period": 12, + "matched_fact_count_in_source_window": 12, + "resolved_period": 2025, + "resolved_value": 1078514.5, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_d56ddfa58d7ebb3ff0178b0a" + } + ] + }, + "dwp.uc.households_housing_element": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_housing_element", + "status": "active", + "matched_fact_count_overall": 12, + "matched_fact_count_at_or_before_period": 12, + "matched_fact_count_in_source_window": 12, + "resolved_period": 2025, + "resolved_value": 4037650.3333333335, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_04b7ec6d195e6682f0421672" + } + ] + }, + "dwp.uc.households_housing_element_social_rented": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_housing_element_social_rented", + "status": "active", + "matched_fact_count_overall": 12, + "matched_fact_count_at_or_before_period": 12, + "matched_fact_count_in_source_window": 12, + "resolved_period": 2025, + "resolved_value": 2354587.5833333335, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_109598f6aaa42fe1387d30d1" + } + ] + }, + "dwp.uc.households_housing_element_private_rented": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_housing_element_private_rented", + "status": "active", + "matched_fact_count_overall": 12, + "matched_fact_count_at_or_before_period": 12, + "matched_fact_count_in_source_window": 12, + "resolved_period": 2025, + "resolved_value": 1570545.4166666667, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_7a31b88dfcd4b3d0c758a1c3" + } + ] + }, + "dwp.uc.households_childcare_element": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_childcare_element", + "status": "active", + "matched_fact_count_overall": 12, + "matched_fact_count_at_or_before_period": 12, + "matched_fact_count_in_source_window": 12, + "resolved_period": 2025, + "resolved_value": 178666.66666666666, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_0c1aa0cc56d86c50d6b2560a" + } + ] + }, + "dwp.uc.households_with_deduction": { + "status": "active", + "candidates": [ + { + "name": "dwp.uc.households_with_deduction", + "status": "active", + "matched_fact_count_overall": 10, + "matched_fact_count_at_or_before_period": 10, + "matched_fact_count_in_source_window": 10, + "resolved_period": 2025, + "resolved_value": 3130000.0, + "resolved_fact_period": "2025-12", + "resolved_fact_key": "ledger_aggregate_fact_v2_f7bfdb41a462c417535602d2" + } + ] + }, + "obr.universal_credit": { + "status": "active", + "candidates": [ + { + "name": "obr.universal_credit", + "status": "active", + "matched_fact_count_overall": 14, + "matched_fact_count_at_or_before_period": 4, + "resolved_period": 2025, + "resolved_value": 79286349139.54411, + "resolved_fact_period": "2025", + "resolved_fact_key": "ledger_aggregate_fact_v2_e680fe36115d1372d12072e1" + } + ] } }, "fanout_family_outcomes": [ diff --git a/packages/microcosm-build/src/microcosm/build/uk/target_references.json b/packages/microcosm-build/src/microcosm/build/uk/target_references.json index 5ee020aa19..2df3ea4f07 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/target_references.json +++ b/packages/microcosm-build/src/microcosm/build/uk/target_references.json @@ -208,6 +208,8 @@ "dwp.esa_income_claimants": "dwp.legacy_benefits", "dwp.jsa_claimants": "dwp.legacy_benefits", "dwp.uc.households": "dwp.universal_credit", + "dwp.uc.households_carer_element": "dwp.universal_credit", + "dwp.uc.households_childcare_element": "dwp.universal_credit", "dwp.uc.households_children_1": "dwp.universal_credit", "dwp.uc.households_children_2": "dwp.universal_credit", "dwp.uc.households_children_3": "dwp.universal_credit", @@ -215,8 +217,13 @@ "dwp.uc.households_children_5_or_more": "dwp.universal_credit", "dwp.uc.households_couple_no_children": "dwp.universal_credit", "dwp.uc.households_couple_with_children": "dwp.universal_credit", + "dwp.uc.households_housing_element": "dwp.universal_credit", + "dwp.uc.households_housing_element_private_rented": "dwp.universal_credit", + "dwp.uc.households_housing_element_social_rented": "dwp.universal_credit", + "dwp.uc.households_lcwra_element": "dwp.universal_credit", "dwp.uc.households_single_no_children": "dwp.universal_credit", "dwp.uc.households_single_with_children": "dwp.universal_credit", + "dwp.uc.households_with_deduction": "dwp.universal_credit", "dwp.uc.payment_distribution_couple_no_children": "dwp.universal_credit", "dwp.uc.payment_distribution_couple_with_children": "dwp.universal_credit", "dwp.uc.payment_distribution_lone_parent": "dwp.universal_credit", @@ -282,6 +289,7 @@ "obr.state_pension": "obr.efo_expenditure", "obr.statutory_maternity_pay": "obr.efo_expenditure", "obr.tv_licence_fee": "obr.efo_expenditure", + "obr.universal_credit": "obr.efo_expenditure", "obr.universal_credit_in_cap": "obr.efo_expenditure", "obr.universal_credit_outside_cap": "obr.efo_expenditure", "obr.vat": "obr.efo_receipts", @@ -406,6 +414,8 @@ "dwp.esa_income_claimants": "Employment and Support Allowance income claimants", "dwp.jsa_claimants": "Jobseeker\u2019s Allowance claimants", "dwp.uc.households": "Universal Credit households", + "dwp.uc.households_carer_element": "Households with carer entitlement", + "dwp.uc.households_childcare_element": "Households receiving the childcare element", "dwp.uc.households_children_1": "Households children 1", "dwp.uc.households_children_2": "Households children 2", "dwp.uc.households_children_3": "Households children 3", @@ -413,8 +423,13 @@ "dwp.uc.households_children_5_or_more": "Households children 5 or more", "dwp.uc.households_couple_no_children": "Households couple no children", "dwp.uc.households_couple_with_children": "Households couple with children", + "dwp.uc.households_housing_element": "Households with housing entitlement", + "dwp.uc.households_housing_element_private_rented": "Households with housing entitlement, private rented", + "dwp.uc.households_housing_element_social_rented": "Households with housing entitlement, social rented", + "dwp.uc.households_lcwra_element": "Households with LCWRA entitlement", "dwp.uc.households_single_no_children": "Households single no children", "dwp.uc.households_single_with_children": "Households single with children", + "dwp.uc.households_with_deduction": "Households with a deduction", "dwp.uc.payment_distribution_couple_no_children": "Payment distribution couple no children", "dwp.uc.payment_distribution_couple_with_children": "Payment distribution couple with children", "dwp.uc.payment_distribution_lone_parent": "Payment distribution lone parent", @@ -480,6 +495,7 @@ "obr.state_pension": "State pension", "obr.statutory_maternity_pay": "Statutory maternity pay", "obr.tv_licence_fee": "TV licence fee", + "obr.universal_credit": "Universal Credit expenditure", "obr.universal_credit_in_cap": "Universal Credit inside the benefit cap", "obr.universal_credit_outside_cap": "Universal Credit outside the benefit cap", "obr.vat": "VAT", @@ -17973,6 +17989,332 @@ "expected_member_count": 2, "uprating_from_period": "2024", "uprating_to_period": 2025 + }, + { + "name": "dwp.uc.households_lcwra_element", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.limited_capability_for_work_entitlement", + "dimensions": [ + "limited_capability_for_work_entitlement", + "payment_indicator" + ], + "dimension_values": { + "limited_capability_for_work_entitlement": "LCWRA", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/lcwra", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_lcwra_element", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "dwp.uc.households_carer_element", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.carer_entitlement", + "dimensions": [ + "carer_entitlement", + "payment_indicator" + ], + "dimension_values": { + "carer_entitlement": "Yes", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/carer", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_carer_element", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "dwp.uc.households_housing_element", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.housing_entitlement_tenure", + "dimensions": [ + "housing_entitlement_tenure", + "payment_indicator" + ], + "dimension_values": { + "housing_entitlement_tenure": "Yes", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/housing", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_housing_element", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "dwp.uc.households_housing_element_social_rented", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.housing_entitlement_tenure", + "dimensions": [ + "housing_entitlement_tenure", + "payment_indicator" + ], + "dimension_values": { + "housing_entitlement_tenure": "Social Rented Sector", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/housing_social_rented", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_housing_element_social_rented", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "dwp.uc.households_housing_element_private_rented", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.housing_entitlement_tenure", + "dimensions": [ + "housing_entitlement_tenure", + "payment_indicator" + ], + "dimension_values": { + "housing_entitlement_tenure": "Private Rented Sector", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/housing_private_rented", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_housing_element_private_rented", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "dwp.uc.households_childcare_element", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households_receiving_childcare_element", + "groupby_dimension": "dwp.uc_element", + "dimensions": [ + "uc_element" + ], + "dimension_values": { + "uc_element": "childcare" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/childcare", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_childcare_element", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "dwp.uc.households_with_deduction", + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households_with_deduction", + "source_measure_id": "units_with_deduction", + "period_type": "month", + "period_value": [ + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ], + "geography_level": "country", + "geography_id": "K03000001" + }, + "entity": "benunit", + "measure": "dwp/uc/elements/deductions", + "family": "dwp_universal_credit", + "period": 2025, + "metadata": { + "contract_target_id": "dwp.uc.households_with_deduction", + "measure_kind": "prepared_column", + "uk_uc_expected_source_months": "[\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\"]" + }, + "period_match_policy": "source_window", + "value_operation": "monthly_window_average" + }, + { + "name": "obr.universal_credit", + "ledger_selector": { + "source_name": "obr", + "source_concept": [ + "obr.universal_credit_in_cap", + "obr.universal_credit_outside_cap" + ], + "entity_name": "person", + "geography_level": "country", + "geography_id": "K02000001" + }, + "entity": "benunit", + "measure": "obr/universal_credit", + "family": "obr", + "period": 2025, + "metadata": { + "contract_target_id": "obr.universal_credit", + "measure_kind": "prepared_column" + }, + "assertion_policy": "allow_source_projection", + "value_operation": "sum", + "expected_member_count": 2 } ] } diff --git a/packages/microcosm-build/src/microcosm/build/uk/uk_population_targets.json b/packages/microcosm-build/src/microcosm/build/uk/uk_population_targets.json index 6b21581cfb..43c3fe4844 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/uk_population_targets.json +++ b/packages/microcosm-build/src/microcosm/build/uk/uk_population_targets.json @@ -705,7 +705,15 @@ "scotgov.bus.government_support": "No incumbent target: uk-data uplifted DfT England support to the UK by population; microcosm#890 binds the Scotland leg from Scottish Transport Statistics.", "welshgov.bus.public_support": "No incumbent target: uk-data uplifted DfT England support to the UK by population; microcosm#890 binds the Wales leg (local-authority route) from the StatsWales revenue outturn.", "dfi_ni.bus.passenger_receipts": "No incumbent target: uk-data uplifted DfT England fares to the UK by population; microcosm#890 binds the Northern Ireland leg from DfI Public Transport Statistics.", - "nithc.public_transport_support": "No incumbent target: uk-data uplifted DfT England support to the UK by population; microcosm#890 binds Northern Ireland public transport support from the NITHC accounts as a joint bus-plus-rail cell." + "nithc.public_transport_support": "No incumbent target: uk-data uplifted DfT England support to the UK by population; microcosm#890 binds Northern Ireland public transport support from the NITHC accounts as a joint bus-plus-rail cell.", + "dwp.uc.households_lcwra_element": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "dwp.uc.households_carer_element": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "dwp.uc.households_housing_element": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "dwp.uc.households_housing_element_social_rented": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "dwp.uc.households_housing_element_private_rented": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "dwp.uc.households_childcare_element": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "dwp.uc.households_with_deduction": "#882 UC element target from the chronicle#260 Payment Indicator crosses; the incumbent registry has no element rows.", + "obr.universal_credit": "#882 UC expenditure total over the two OBR welfare-cap cells; the incumbent binds only the split, which is excluded." }, "suppressed_ancestors": { "hmrc/salary_sacrifice_it_relief_basic_rate": { @@ -947,7 +955,15 @@ "scotgov.bus.government_support", "welshgov.bus.public_support", "dfi_ni.bus.passenger_receipts", - "nithc.public_transport_support" + "nithc.public_transport_support", + "dwp.uc.households_lcwra_element", + "dwp.uc.households_carer_element", + "dwp.uc.households_housing_element", + "dwp.uc.households_housing_element_social_rented", + "dwp.uc.households_housing_element_private_rented", + "dwp.uc.households_childcare_element", + "dwp.uc.households_with_deduction", + "obr.universal_credit" ] }, "profile_parity": { @@ -13280,6 +13296,875 @@ "category_id": "nithc.public_transport_finances", "label": "Bus and rail support" }, + { + "target_id": "dwp.uc.households_lcwra_element", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.lcwra_element", + "operator": ">", + "value": 0 + } + ], + "source_months": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/lcwra", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_LCWRA_element", + "operator": ">", + "value": 0 + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive LCWRA element among positive UC awards, against DWP's Stat-Xplore element entitlement crossed with Payment Indicator = Yes (chronicle#260), the same paid-claim basis as the family-type rows. The publisher group value 'LCWRA' covers both rates; before April 2026 every LCWRA household is 'LCWRA - Higher'. The engine's LCWRA flag is the is_disabled_for_benefits input, not a work-capability assessment; the row stays in the objective because the mismatch leaves it inside the 25 percent release bound at the 10 September 2026 baseline (+15 percent on Q50f, +20 percent on D2), while carer and childcare, whose concept gaps put them outside it, sit on the measure-exclusion register. Whether the passthrough itself warrants an engine change is a Step 3 question under #882. Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/lcwra", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.limited_capability_for_work_entitlement", + "dimensions": [ + "limited_capability_for_work_entitlement", + "payment_indicator" + ], + "dimension_values": { + "limited_capability_for_work_entitlement": "LCWRA", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households with LCWRA entitlement" + }, + { + "target_id": "dwp.uc.households_carer_element", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.carer_element", + "operator": ">", + "value": 0 + } + ], + "source_months": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/carer", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_carer_element", + "operator": ">", + "value": 0 + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive carer element among positive UC awards, against DWP's Stat-Xplore element entitlement crossed with Payment Indicator = Yes (chronicle#260), the same paid-claim basis as the family-type rows. The engine's carer condition is Carer's Allowance receipt (is_carer_for_benefits); the UC carer element needs 35 hours a week of care without Carer's Allowance, so the row undercounts by construction until the spine carries care hours (#882). Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/carer", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.carer_entitlement", + "dimensions": [ + "carer_entitlement", + "payment_indicator" + ], + "dimension_values": { + "carer_entitlement": "Yes", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households with carer entitlement" + }, + { + "target_id": "dwp.uc.households_housing_element", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.housing_costs_element", + "operator": ">", + "value": 0 + } + ], + "source_months": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/housing", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_housing_costs_element", + "operator": ">", + "value": 0 + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive housing costs element among positive UC awards, against DWP's Stat-Xplore element entitlement crossed with Payment Indicator = Yes (chronicle#260), the same paid-claim basis as the family-type rows. 'Yes' is DWP's any-tenure housing entitlement value: social plus private plus 'Other or unknown' tenure, which the model cannot carry (uc_housing_costs_element pays only council, housing-association and private renters and TenureType has no other/unknown value), so the model's any-tenure count is identically the social plus private rows and this row overstates the reachable total by the other/unknown cell (112,518 of 4,037,650 in calendar 2025, 2.8 percent). It is therefore measured but held out of the objective on the measure-exclusion register; the two tenure rows carry the housing element in the fit. Never sum it with the tenure rows. Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/housing", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.housing_entitlement_tenure", + "dimensions": [ + "housing_entitlement_tenure", + "payment_indicator" + ], + "dimension_values": { + "housing_entitlement_tenure": "Yes", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households with housing entitlement" + }, + { + "target_id": "dwp.uc.households_housing_element_social_rented", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.housing_costs_element", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.household.tenure", + "operator": "in", + "value": [ + "rent_from_council", + "rent_from_housing_association" + ] + } + ], + "source_months": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/housing_social_rented", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_housing_costs_element", + "operator": ">", + "value": 0 + }, + { + "variable": "benunit_tenure_type", + "operator": "in", + "value": [ + "RENT_FROM_COUNCIL", + "RENT_FROM_HA" + ] + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive housing costs element among positive UC awards, against DWP's Stat-Xplore element entitlement crossed with Payment Indicator = Yes (chronicle#260), the same paid-claim basis as the family-type rows. Tenure from the benefit unit's household (benunit_tenure_type): council and housing-association renters. Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/housing_social_rented", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.housing_entitlement_tenure", + "dimensions": [ + "housing_entitlement_tenure", + "payment_indicator" + ], + "dimension_values": { + "housing_entitlement_tenure": "Social Rented Sector", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households with housing entitlement, social rented" + }, + { + "target_id": "dwp.uc.households_housing_element_private_rented", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.housing_costs_element", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.household.tenure", + "operator": "==", + "value": "rent_privately" + } + ], + "source_months": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/housing_private_rented", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_housing_costs_element", + "operator": ">", + "value": 0 + }, + { + "variable": "benunit_tenure_type", + "operator": "==", + "value": "RENT_PRIVATELY" + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive housing costs element among positive UC awards, against DWP's Stat-Xplore element entitlement crossed with Payment Indicator = Yes (chronicle#260), the same paid-claim basis as the family-type rows. Tenure from the benefit unit's household (benunit_tenure_type): private renters. Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/housing_private_rented", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households", + "groupby_dimension": "dwp.housing_entitlement_tenure", + "dimensions": [ + "housing_entitlement_tenure", + "payment_indicator" + ], + "dimension_values": { + "housing_entitlement_tenure": "Private Rented Sector", + "payment_indicator": "Yes" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households with housing entitlement, private rented" + }, + { + "target_id": "dwp.uc.households_childcare_element", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.childcare_element", + "operator": ">", + "value": 0 + } + ], + "source_months": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/childcare", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_childcare_element", + "operator": ">", + "value": 0 + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive childcare element among positive UC awards, against DWP's childcare element statistics Table 1 (chronicle#260): an all-claims count rounded to the nearest 1,000 with no payment-indicator split, so the published side is up to the nil-payment share (about 8 percent) above a paid-claim basis. The engine pays the element to every benefit unit where all adults work and childcare costs are reported, with no take-up lever, so the row overcounts by construction until that lever exists (#882). Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/childcare", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households_receiving_childcare_element", + "groupby_dimension": "dwp.uc_element", + "dimensions": [ + "uc_element" + ], + "dimension_values": { + "uc_element": "childcare" + }, + "source_measure_id": "benefit_units", + "period_type": "month", + "period_value": [ + "2025-01", + "2025-02", + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households receiving the childcare element" + }, + { + "target_id": "dwp.uc.households_with_deduction", + "family": "dwp_universal_credit", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefit_unit.count", + "filters": [ + { + "concept": "uk.benefits.universal_credit.amount", + "operator": ">", + "value": 0 + }, + { + "concept": "uk.benefits.universal_credit.deductions", + "operator": ">", + "value": 0 + } + ], + "source_months": [ + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "bindings": { + "policyengine": { + "metric_name": "dwp/uc/elements/deductions", + "value_variable": "benunit_count", + "from_entity": "benunit", + "filters": [ + { + "variable": "universal_credit", + "operator": ">", + "value": 0 + }, + { + "variable": "uc_deductions", + "operator": ">", + "value": 0 + } + ], + "notes": "Mean monthly paid GB benefit units over the declared months. Benefit units with a positive deduction among positive UC awards, against DWP's Universal Credit deductions statistics Table 1 (households with a deduction; DWP publishes rounded counts), whose published history starts in March 2025; the ten months March to December 2025 are the declared window. The deduction incidence stage (#850) draws to the published share, so this row is a regression guard. Count only the Great Britain regions, matching K03000001; DWP removes Northern Ireland-office claims from these statistics. Source: https://www.gov.uk/government/publications/universal-credit-statistics-background-information-and-methodology/universal-credit-statistics-background-information-and-methodology#creating-a-great-britain-dataset Source: https://github.com/PolicyEngine/chronicle/pull/260", + "household_conditions": [ + { + "variable": "region", + "entity": "household", + "reduce": "any", + "operator": "in", + "value": [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND" + ], + "map_to": "benunit" + } + ] + }, + "axiom": { + "metric_name": "dwp/uc/elements/deductions", + "status": "pending", + "value_rule": "uk.benefit_unit.count" + } + }, + "ledger_selector": { + "source_name": "dwp", + "source_concept": "dwp.uc_households_with_deduction", + "source_measure_id": "units_with_deduction", + "period_type": "month", + "period_value": [ + "2025-03", + "2025-04", + "2025-05", + "2025-06", + "2025-07", + "2025-08", + "2025-09", + "2025-10", + "2025-11", + "2025-12" + ] + }, + "value_operation": "monthly_window_average", + "period_match_policy": "source_window", + "category_id": "dwp.universal_credit", + "label": "Households with a deduction" + }, + { + "target_id": "obr.universal_credit", + "family": "obr", + "geography_levels": [ + "country" + ], + "measurement": { + "entity": "benunit", + "concept": "uk.benefits.universal_credit.amount", + "map_to": "household" + }, + "bindings": { + "policyengine": { + "metric_name": "obr/universal_credit", + "value_variable": "universal_credit", + "from_entity": "benunit", + "map_to": "household", + "notes": "Total UC expenditure: the sum of the OBR EFO welfare-cap cells (universal_credit_in_cap + universal_credit_outside_cap, FY2025/26 incl. the provisional March 2026 month) against the model's annual award after the benefit cap and deductions at policy year 2025, UK incl. Northern Ireland. The split rows stay excluded: the model cannot attribute spend to the welfare-cap concept (microcosm#757). Cash advances and third-party payments are not modelled (#882)." + }, + "axiom": { + "metric_name": "obr/universal_credit", + "status": "pending", + "value_rule": "uk.benefits.universal_credit.amount" + } + }, + "assertion_policy": "allow_source_projection", + "ledger_selector": { + "source_name": "obr", + "source_concept": [ + "obr.universal_credit_in_cap", + "obr.universal_credit_outside_cap" + ], + "entity_name": "person" + }, + "value_operation": "sum", + "expected_member_count": 2, + "category_id": "obr.efo_expenditure", + "label": "Universal Credit expenditure" + }, { "target_id": "hmrc.self_employment_income.amount", "family": "hmrc", diff --git a/packages/microcosm-build/tests/test_country_spec.py b/packages/microcosm-build/tests/test_country_spec.py index 71693418f3..feba44e922 100644 --- a/packages/microcosm-build/tests/test_country_spec.py +++ b/packages/microcosm-build/tests/test_country_spec.py @@ -1096,7 +1096,7 @@ def test_uk_target_references_accept_regenerated_contract_fields(self) -> None: references = {reference.name: reference for reference in spec.target_references} assert ( - len(references) == 595 + len(references) == 603 ) # microcosm#905: 424 - 18 country rows + 189 region-tier cells assert references["obr.esa"].value_operation == "sum" assert references["dwp.uc.households"].value_operation == ( diff --git a/packages/microcosm-build/tests/test_uk_measure_simulation.py b/packages/microcosm-build/tests/test_uk_measure_simulation.py index 1fd06936ce..91a40d0240 100644 --- a/packages/microcosm-build/tests/test_uk_measure_simulation.py +++ b/packages/microcosm-build/tests/test_uk_measure_simulation.py @@ -589,8 +589,19 @@ def test_exclusion_applier_warns_within_week_of_expiry(): "ons.savings_interest_income": 1, "obr.housing_benefit": 1, "dwp.jsa_claimants": 1, + # microcosm#882 (2026-09-15): the three UC element rows measured but + # held out of the objective — carer and childcare as model concept gaps, + # the any-tenure housing row as a structural bias — signed by María in + # review of microcosm#921 with a one-month window. + "dwp/uc/elements/": 3, } +_UC_ELEMENT_REGISTER_ROWS = ( + "dwp/uc/elements/carer", + "dwp/uc/elements/childcare", + "dwp/uc/elements/housing", +) + _A16_UNREACHABLE_ROWS = ( "ons.savings_interest_income", "obr.housing_benefit", @@ -603,7 +614,7 @@ def test_exclusion_applier_warns_within_week_of_expiry(): def test_packaged_exclusions_load(): exclusions = load_uk_calibration_measure_exclusions() names = [entry["name"] for entry in exclusions] - assert len(names) == len(set(names)) == 50 + assert len(names) == len(set(names)) == 53 band_h_region_cells = [ entry for entry in exclusions @@ -668,6 +679,36 @@ def test_packaged_exclusions_load(): assert entry["tracking"] == a16_issues[entry["name"]], entry["name"] assert "A16" in entry["adjudication"], entry["name"] + # The 2026-09-15 tranche is #882's element rows: carer and childcare are + # model concept gaps (the engine's carer condition is Carer's Allowance + # receipt; the childcare element has no take-up lever) and the any-tenure + # housing row a structural bias (DWP's 'Yes' includes an other/unknown + # tenure the model cannot carry). Each is adjudicated to microcosm#882 + # and the committed baseline doc, signed in review of microcosm#921, and + # windowed to one month so the repair or re-adjudication is forced. + elements = [e for e in exclusions if e["approved_on"] == "2026-09-15"] + assert sorted(e["name"] for e in elements) == sorted(_UC_ELEMENT_REGISTER_ROWS) + for entry in elements: + assert entry["expires_on"] == "2026-10-15", entry["name"] + assert entry["tracking"] == "microcosm#882", entry["name"] + assert entry["approved_by"] == "juaristi22", entry["name"] + assert "microcosm#882" in entry["adjudication"], entry["name"] + assert "microcosm#921" in entry["adjudication"], entry["name"] + assert "docs/uk-uc-baseline-2026-09-10.md" in entry["adjudication"], entry[ + "name" + ] + housing = next(e for e in elements if e["name"] == "dwp/uc/elements/housing") + assert "112,518 of 4,037,650" in housing["reason"] + # The four element rows that stay in the objective are not on the register. + for riding in ( + "dwp/uc/elements/lcwra", + "dwp/uc/elements/housing_social_rented", + "dwp/uc/elements/housing_private_rented", + "dwp/uc/elements/deductions", + "obr/universal_credit", + ): + assert riding not in names, riding + # The lever targets are deliberately NOT excluded: the six UC # caseload / two-child-limit cells ride the would_claim_uc lever run. # Exclusions cannot move surviving cells because band edges are pinned diff --git a/packages/microcosm-build/tests/test_uk_national_calibration.py b/packages/microcosm-build/tests/test_uk_national_calibration.py index 2f614a4bad..f1bef2a2fb 100644 --- a/packages/microcosm-build/tests/test_uk_national_calibration.py +++ b/packages/microcosm-build/tests/test_uk_national_calibration.py @@ -41,7 +41,7 @@ from microcosm.calibrate import TargetRegistry, TargetSpec from microcosm.frame import EntitySchema, Frame, WeightKind, Weights -ACTIVE_REFERENCE_COUNT = 595 +ACTIVE_REFERENCE_COUNT = 603 def _uc_reference(**overrides) -> LedgerTargetReference: diff --git a/packages/microcosm-build/tests/test_uk_population_targets.py b/packages/microcosm-build/tests/test_uk_population_targets.py index 5d629b4990..85188f037e 100644 --- a/packages/microcosm-build/tests/test_uk_population_targets.py +++ b/packages/microcosm-build/tests/test_uk_population_targets.py @@ -181,7 +181,7 @@ def test_uk_population_targets_shape_order_and_registry_accounting() -> None: "operation": "sum", "assertion_policy": "observed_only", } - assert len(resource["targets"]) == 244 + assert len(resource["targets"]) == 252 providers = resource["hierarchy"]["providers"] categories = resource["hierarchy"]["categories"] @@ -195,10 +195,10 @@ def test_uk_population_targets_shape_order_and_registry_accounting() -> None: target_ids = [target["target_id"] for target in resource["targets"]] registry_scope = resource["registry_parity"]["scope_target_ids"] profile_scope = resource["profile_parity"]["scope_target_ids"] - assert len(registry_scope) == 210 + assert len(registry_scope) == 218 assert len(profile_scope) == 34 - assert target_ids[:210] == registry_scope - assert target_ids[210:] == profile_scope + assert target_ids[:218] == registry_scope + assert target_ids[218:] == profile_scope parity = resource["registry_parity"] assert parity["pinned_ref"] == "12a1e028afeef08d8b2d74ee03fd9de3a78b2dd3" @@ -212,7 +212,7 @@ def test_uk_population_targets_shape_order_and_registry_accounting() -> None: assert mapped_target_ids | set(unmapped_declarations) == set(registry_scope) assert all(reason for reason in unmapped_declarations.values()) assert len(mapped_target_ids) == 183 - assert len(unmapped_declarations) == 27 + assert len(unmapped_declarations) == 35 suppressed_ancestors = parity["suppressed_ancestors"] assert len(suppressed_ancestors) == 5 assert set(suppressed_ancestors).isdisjoint(parity["mapped"]) @@ -331,7 +331,7 @@ def test_uk_population_targets_have_unique_target_ids() -> None: resource = _load() target_ids = [target["target_id"] for target in resource["targets"]] - assert len(target_ids) == 244 + assert len(target_ids) == 252 assert len(target_ids) == len(set(target_ids)) @@ -769,7 +769,7 @@ def test_uc_payment_bands_share_administrative_family_but_keep_source_window() - def test_paid_joint_diagnostics_do_not_add_active_targets() -> None: targets = _load()["targets"] - assert len(targets) == 244 + assert len(targets) == 252 assert not any( f.get("variable") == "uc_calibration_child_entitlement" for target in targets @@ -791,7 +791,7 @@ def test_uc_gb_bindings_match_the_committed_source_geography_and_finite_bands(): if row["metadata"]["contract_target_id"].startswith("dwp.uc.") and row["ledger_selector"]["geography_id"] == "K03000001" } - assert len(gb_ids) == 29 + assert len(gb_ids) == 36 for target_id in gb_ids: binding = _target_by_id(resource, target_id)["bindings"]["policyengine"] geographic = [ diff --git a/packages/microcosm-build/tests/test_uk_target_references.py b/packages/microcosm-build/tests/test_uk_target_references.py index 776cc98a3f..ab6d042cbc 100644 --- a/packages/microcosm-build/tests/test_uk_target_references.py +++ b/packages/microcosm-build/tests/test_uk_target_references.py @@ -60,7 +60,7 @@ _value_operation_by_target_id, ) -ACTIVE_REFERENCE_COUNT = 595 +ACTIVE_REFERENCE_COUNT = 603 REGION_TIER_LEVEL = {code: level for level, code in UK_REGION_TIER} UK_DATA_REPO = "policyengine-" + "uk-data" @@ -518,6 +518,18 @@ def test_ons_age_total_targets_pin_exact_age_dimension_set() -> None: ] == ["age"] +UC_ELEMENT_TARGET_IDS = frozenset( + { + "dwp.uc.households_lcwra_element", + "dwp.uc.households_carer_element", + "dwp.uc.households_housing_element", + "dwp.uc.households_housing_element_social_rented", + "dwp.uc.households_housing_element_private_rented", + "dwp.uc.households_childcare_element", + } +) + + def test_uc_composition_targets_pin_paid_cells_and_explicit_month_windows() -> None: contract = _load_uk_resource("uk_population_targets.json") targets = {target["target_id"]: target for target in contract["targets"]} @@ -557,6 +569,24 @@ def test_uc_composition_targets_pin_paid_cells_and_explicit_month_windows() -> N assert pins["child_entitlement"] == ["No", "Yes"] assert selector["source_measure_id"] == "benefit_units" assert operations[target_id] == "monthly_window_sum_average" + elif target_id in UC_ELEMENT_TARGET_IDS: + # #882 element rows: one paid-claim cell per month from the + # chronicle#260 crosses (the childcare count is the ODS all-claims + # series and carries no payment indicator), averaged over the + # declared calendar-2025 window. The month list makes them sum + # candidates like the ten broad rows; the declared operation + # averages instead. + if target_id != "dwp.uc.households_childcare_element": + assert "payment_indicator" in selector["dimensions"] + assert pins["payment_indicator"] == "Yes" + assert selector["source_measure_id"] == "benefit_units" + assert operations[target_id] == "monthly_window_average" + assert target["period_match_policy"] == "source_window" + assert target_id in sum_target_ids + assert selector["period_value"] == [ + f"2025-{month:02d}" for month in range(1, 13) + ] + continue else: # Merely naming a dimension set still does not request a sum. assert target_id not in sum_target_ids @@ -667,7 +697,7 @@ def test_uk_target_reference_membership_report_is_packaged() -> None: assert membership["target_period"] == 2025 assert membership["active_reference_count"] == ACTIVE_REFERENCE_COUNT assert membership["status_counts"] == { - "active": 595, + "active": 603, "no_fact_at_or_before_period": 7, "signed_excluded": 7, } @@ -904,6 +934,15 @@ def test_uk_generator_averages_paid_monthly_sums_and_preserves_other_uc_operatio monthly_average_ids = { f"dwp.uc.households_children_{children}" for children in ("1", "2", "3", "4", "5_or_more") + } | { + # #882 element rows: paid-claim crosses on declared month windows. + "dwp.uc.households_lcwra_element", + "dwp.uc.households_carer_element", + "dwp.uc.households_housing_element", + "dwp.uc.households_housing_element_social_rented", + "dwp.uc.households_housing_element_private_rented", + "dwp.uc.households_childcare_element", + "dwp.uc.households_with_deduction", } uc_target_ids = { target["target_id"] diff --git a/packages/microcosm-build/tests/test_uk_uc_source_periods.py b/packages/microcosm-build/tests/test_uk_uc_source_periods.py index 189ab87a99..9f0efed7d8 100644 --- a/packages/microcosm-build/tests/test_uk_uc_source_periods.py +++ b/packages/microcosm-build/tests/test_uk_uc_source_periods.py @@ -453,7 +453,8 @@ def test_shipped_uc_monthly_references_preserve_each_declared_window(): for reference in references if reference.family == "dwp_universal_credit" ] - assert len(monthly) == 111 + assert len(monthly) == 118 + calendar_2025 = [f"2025-{month:02}" for month in range(1, 13)] new_paid = { "dwp.uc.households", *{ @@ -471,9 +472,23 @@ def test_shipped_uc_monthly_references_preserve_each_declared_window(): }, } assert len(new_paid) == 10 + # The #882 element rows bind the chronicle#260 Payment Indicator crosses on + # the same calendar-2025 paid basis; the deductions statistics start in + # March 2025, so that row declares the ten published months. + element_windows = { + "dwp.uc.households_lcwra_element": calendar_2025, + "dwp.uc.households_carer_element": calendar_2025, + "dwp.uc.households_housing_element": calendar_2025, + "dwp.uc.households_housing_element_social_rented": calendar_2025, + "dwp.uc.households_housing_element_private_rented": calendar_2025, + "dwp.uc.households_childcare_element": calendar_2025, + "dwp.uc.households_with_deduction": [ + f"2025-{month:02}" for month in range(3, 13) + ], + } for reference in monthly: - if reference.name in new_paid: - expected_months = [f"2025-{month:02}" for month in range(1, 13)] + if reference.name in new_paid or reference.name in element_windows: + expected_months = element_windows.get(reference.name, calendar_2025) assert reference.ledger_selector["period_value"] == expected_months if reference.value_operation == "monthly_window_sum_average": assert reference.period_match_policy == "source_window" @@ -484,7 +499,10 @@ def test_shipped_uc_monthly_references_preserve_each_declared_window(): assert reference.value_operation == "monthly_window_average" assert reference.period_match_policy == "source_window" assert not reference.value_operands - assert reference.name.startswith("dwp.uc.households_children_") + assert ( + reference.name.startswith("dwp.uc.households_children_") + or reference.name in element_windows + ) assert reference.uprating_from_period is None assert reference.uprating_to_period is None else: @@ -492,6 +510,7 @@ def test_shipped_uc_monthly_references_preserve_each_declared_window(): assert reference.value_operation == "calendar_year_average" assert json.loads(reference.metadata[EXPECTED_SOURCE_MONTHS]) == expected_months assert new_paid <= {reference.name for reference in monthly} + assert set(element_windows) <= {reference.name for reference in monthly} assert not any( EXPECTED_SOURCE_MONTHS in reference.metadata for reference in references diff --git a/tools/diagnose_uk_uc_elements.py b/tools/diagnose_uk_uc_elements.py new file mode 100644 index 0000000000..83982aa948 --- /dev/null +++ b/tools/diagnose_uk_uc_elements.py @@ -0,0 +1,141 @@ +"""Size Universal Credit element support on a calibrated UK dataset. + +Counts Great Britain benefit units carrying each UC element among paid UC +awards (``universal_credit > 0``), the comparison basis of the DWP element +targets, and reports the unconditioned counts the T4 admin legs used, so the +two readings are never confused again. Development diagnostic: it never +recalibrates and writes only an aggregate JSON. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np +import pandas as pd + +GB_REGIONS = ( + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND", +) +SOCIAL_TENURES = ("RENT_FROM_COUNCIL", "RENT_FROM_HA") +ELEMENTS = { + "lcwra": "uc_LCWRA_element", + "carer": "uc_carer_element", + "housing": "uc_housing_costs_element", + "childcare": "uc_childcare_element", + "child": "uc_child_element", + "deductions": "uc_deductions", +} + + +def _benunit(simulation, variable: str, year: int) -> np.ndarray: + return np.asarray( + simulation.calculate(variable, year, map_to="benunit").values, dtype=float + ) + + +def _first_person_label(simulation, variable: str, year: int) -> np.ndarray: + """Household categorical carried to benefit units through the first member.""" + + labels = np.asarray(simulation.calculate(variable, year, map_to="person")).astype( + str + ) + person_benunit = np.asarray( + simulation.calculate("benunit_id", year, map_to="person").values + ) + benunit_ids = np.asarray(simulation.calculate("benunit_id", year).values) + first = pd.Series(labels).groupby(person_benunit).first() + return first.reindex(benunit_ids).to_numpy().astype(str) + + +def measure(dataset: str, year: int) -> dict: + from policyengine_uk import Microsimulation + + simulation = Microsimulation(dataset=dataset) + weight = _benunit(simulation, "benunit_weight", year) + region = _first_person_label(simulation, "region", year) + tenure = _first_person_label(simulation, "tenure_type", year) + universal_credit = _benunit(simulation, "universal_credit", year) + would_claim = _benunit(simulation, "would_claim_uc", year) > 0 + eligible = _benunit(simulation, "is_uc_eligible", year) > 0 + maximum = _benunit(simulation, "uc_maximum_amount", year) + earnings = _benunit(simulation, "employment_income", year) + _benunit( + simulation, "self_employment_income", year + ) + + great_britain = np.isin(region, GB_REGIONS) + paid = (universal_credit > 0) & great_britain + entitled = would_claim & eligible & (maximum > 0) & great_britain + social = np.isin(tenure, SOCIAL_TENURES) + private = tenure == "RENT_PRIVATELY" + + def count(mask: np.ndarray) -> float: + return float(weight[mask].sum()) + + elements: dict[str, dict[str, float]] = {} + for name, variable in ELEMENTS.items(): + amount = _benunit(simulation, variable, year) + positive = amount > 0 + elements[name] = { + "variable": variable, + "paid_uc_gb": count(positive & paid), + "entitled_proxy_gb": count(positive & entitled), + "unconditioned_gb": count(positive & great_britain), + "amount_among_paid_gbp": float((amount * weight)[paid].sum()), + "share_of_paid": count(positive & paid) / count(paid), + } + housing = _benunit(simulation, ELEMENTS["housing"], year) > 0 + elements["housing_social_rented"] = { + "variable": "uc_housing_costs_element (benunit tenure social)", + "paid_uc_gb": count(housing & social & paid), + "unconditioned_gb": count(housing & social & great_britain), + } + elements["housing_private_rented"] = { + "variable": "uc_housing_costs_element (benunit tenure private)", + "paid_uc_gb": count(housing & private & paid), + "unconditioned_gb": count(housing & private & great_britain), + } + return { + "dataset": str(dataset), + "year": year, + "basis": { + "paid_uc_gb": "universal_credit > 0 and first member in a GB region", + "entitled_proxy_gb": ( + "would_claim_uc and is_uc_eligible and uc_maximum_amount > 0; " + "not an open-claim identification" + ), + "unconditioned_gb": "element > 0 with no UC award condition", + }, + "paid_uc_gb": count(paid), + "entitled_proxy_gb": count(entitled), + "paid_with_earnings_share": count(paid & (earnings > 0)) / count(paid), + "elements": elements, + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--dataset", required=True, help="calibrated UK H5 path") + parser.add_argument("--year", type=int, default=2025) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + result = measure(args.dataset, args.year) + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n") + print(json.dumps(result["elements"], indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main()