Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions changelog.d/1240-high-value-homes.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
The UK build now places high-value homes where HM Land Registry sees them (microcosm#1240, María's rulings of 2026-10-13; the Chronicle inputs are PolicyEngine/chronicle#343 via #349).

**A derived local target family, `high_value_homes`.** `tools/build_uk_high_value_home_stock.py` reads the pinned Price Paid yearly files, the UK HPI full file and the NSPL (all registered raw-only in Chronicle; every file is verified against its manifest), reduces 29.8 million category A transfers of detached, semi-detached, terraced and flat dwellings to the latest non-bulk sale of each of 15.8 million dwellings (identity: postcode, PAON and SAON), uprates each sale by its local authority's and property type's HPI to the calendar-2024 average, places it through the NSPL output area and the committed England-and-Wales atomic support, and counts dwellings over £1m, £1.5m and £2m (April-2026 prices, deflated with the England index) by constituency, local authority, region and nation. The counts are owner-occupied by the census share of the private-sector stock per authority, rescaled to the OBR costing note's 60 per cent control on homes over £2m, and corrected for stock never sold since 1995 by the private-sector VOA stock over the valued dwellings. They ride in `uk/high_value_home_stock_facts.json` in consumer-fact row shape under the Microcosm-owned `microcosm.derived_fact.` namespace, are merged beside the Chronicle feed when the local surface compiles, and bind three contract targets at constituency and local-authority grain for England and Wales (`microcosm.high_value_homes.over_1m|over_1_5m|over_2m`), with the `high_value_home_stock_derived_estimate` fence adjudicated to 2026-12-31. The frame measures the stored `main_residence_value` of owner households at the dataset period. The provenance and the sensitivities (no coverage correction; the census share without the control) are in `uk/high_value_home_stock.provenance.json`.

**Per-clone home values and bands in the dense graph.** Two nodes after geography derivation, `uk.full.property.value` and `uk.full.property.band` (`--property-localisation price_paid`, the default; `none` keeps the spine's values for measurement builds), read each owner clone's home value off its assigned constituency's uprated sale distribution (`uk/high_value_home_value_knots.json`, winsorised at £20m) at the household's weighted rank among its region's owners, and its council-tax band off its LSOA's VOA band mix (`uk/voa_council_tax_band_mix_lsoa.json`) at the same uniform (owner-occupiers only; renters keep the spine's band). The uniform is keyed on the spine household, so every pool clone of one household shares it whatever K is; weights are untouched and property wealth moves by the value's change. Scotland and Northern Ireland keep the spine's values and bands. The `uk_support` gate binds the two rewritten columns to the knots' cap through `uk/price_paid_property_support_bounds.json`.

**A spine stage, `high_value_home_support`,** after `regional_property_uprating` copies every owner household in the top five per cent of its region's value rank six ways at equal weight, each copy carrying `high_value_support_copy_index`, the lineage the geography identity keys on; the value node gives each copy its own keyed rank inside the stratum, so the tail households spread across seats. Mass is conserved exactly (`uk_stage_high_value_home_support_mass`).

**Valuation basis.** `uk/regional_land_values.json` moves from the December-2025 HPI level to the calendar-2024 average, so the stored `main_residence_value` sits at the dataset period's prices and the engine's annual uprating projects it once rather than twice.

**Diagnostics.** Two report-only gates on the calibrated pool: `uk_high_value_homes_benchmarks` (England owner-occupied homes by surcharge band beside OBR Table 1.1, the regional split of homes over £2m beside the derived stock, and the seat rank correlation against the derived stock and VOA band H) and `uk_high_value_homes_support` (areas by band of distinct source households above £1.5m and £2m, with dominance). The per-area support table is a candidate artifact (`.high_value_home_support.csv`) that stays in the run directory; the shipped details carry counts only, with bands and regions resting on fewer than ten distinct source households suppressed (plus one more so the total cannot recover them) and the stage gate's survey counts rounded to the nearest ten; the benchmarks gate evaluates the regional gap over every region but ships the largest over the unsuppressed regions only. The benchmarks gate also sets the derived stock's England private-sector count over £2m beside OBR Table 1.1, the like-for-like comparison.

**Feed.** The Chronicle pin moves to `735820c` (PolicyEngine/chronicle#349: the OBR HVCTS costing and the ONS HPSSA by constituency, plus the three raw-only registrations), 950,242 rows; `uk/obr_hvcts_costing_facts.json` is vendored for the benchmarks gate.

**Loss scales.** The local solve doctrine's scale rule becomes `default_target_loss_scales_with_family_floors`: the canonical `max(|target|, 1)` with a floor of 100 homes for the `high_value_homes/` cells, the support gate's assessment threshold, so a seat with a handful of derived homes over £2m is not charged a unit-scale miss (`target_weights.UK_LOCAL_LOSS_SCALE_FLOORS`; receipted on the prepared solve).

**Licence.** Every derived resource carries its inputs' attribution statements: HM Land Registry data © Crown copyright and database right 2026 under the Open Government Licence v3.0 (Price Paid, UK HPI), the NSPL's OS and Royal Mail notices with ONS's OGL statement, and OGL v3.0 for the VOA, census and OBR inputs. The stock builder's provenance now carries the HPSSA parity receipt (category A sales and medians by 2024 constituency, fiscal year and property type against the ONS rows in the feed) and takes an explicit `--built-on`, so a rerun reproduces the committed bytes.
10 changes: 10 additions & 0 deletions docs/uk-chronicle-feed-repin.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,13 @@ The comparison with the 2023 production surface is retired (microcosm#1257, Mar
2026-10-13). Two compile-parity receipts remain, both at the 2025 calibration period: the national
one against the frozen incumbent registry, and the local one. Earlier entries above that name three
receipts describe the state at their time.

The `735820c` re-pin (microcosm#1240, PolicyEngine/chronicle#343 via #349, on top of `942a1fa`)
brought the inputs for the High Value Council Tax Surcharge work: the OBR's April 2026 costing
note (63 rows: properties in scope by surcharge band and the non-owner-occupied share) and the
ONS house price statistics for small areas by 2024 constituency to the year ending March 2026
(22,940 rows), plus three raw-only registrations that carry no facts (the HM Land Registry Price
Paid yearly files, the UK HPI full file and the NSPL) and are read by
`tools/build_uk_high_value_home_stock.py` against the registered sha256 pins (950,242 rows, up
23,003). No existing package moved, so no compiled target changed; one vendor selection
(`obr_hvcts_costing_facts.json`) pins the OBR note for the benchmarks gate.
5 changes: 4 additions & 1 deletion docs/uk-full-build-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ uv run --no-sync python tools/build_uk_full.py --release-role dense \

The three atomic-area supports are the artifacts pinned in `uk/uk_atomic_area_supports.provenance.json` and `uk/spec/sources.yaml` (built by `tools/build_uk_atomic_area_supports.py` from the published ONS, NRS and NISRA lookups; publisher registration PolicyEngine/chronicle#269). `--geography-assignment atomic` is the default and requires all three; `--atomic-support-sha256-{ew,scotland,ni}` pin them like `--ladder-sha256`, and a release candidate requires all four pins. `--geography-assignment legacy` keeps the previous sequential ladder draw for measurement builds, takes no supports, and is refused by `--release-candidate`.

After derivation, `--property-localisation price_paid` (the default, atomic assignment only) runs the microcosm#1240 value and band nodes: every owner clone in England and Wales reads its `main_residence_value` off its assigned constituency's uprated Land Registry sale distribution at the household's weighted rank among its FRS region's owners (one keyed uniform per spine household, shared by all of its pool clones; high-value support copies take a keyed rank inside the top stratum), `property_wealth` moves by the same amount, and `council_tax_band` is drawn from the assigned LSOA's VOA band mix at the same uniform (owner-occupiers only: the mix counts every dwelling, so renters keep the spine's band). The two resources default to the committed `uk/high_value_home_value_knots.json` and `uk/voa_council_tax_band_mix_lsoa.json`; `--price-paid-quantiles` and `--voa-band-mix` name other bytes with `--price-paid-quantiles-sha256` and `--voa-band-mix-sha256` pins, and a release candidate must build on the committed resources. `--property-localisation none` keeps the spine's values and bands on every clone, for measurement builds only; `--release-candidate` refuses it. The quantile draw is a typed artifact of the value node that the band node and `uk.full.pool` consume, so the checkpoint binds the draw and its resource digests. The high-value support copies are ranked beside their root, so the owner mass at or above the stratum stays the selected share. The two resources and the derived stock carry the publishers' attribution statements (`licence` block): Price Paid and the UK HPI are HM Land Registry data © Crown copyright and database right 2026 under the Open Government Licence v3.0; the NSPL carries the OS and Royal Mail notices with ONS's OGL statement; the VOA, census and OBR inputs are OGL v3.0.

The checkpoint must bind the exact frame content, the current spine stage roster and the gate-report bytes. A spine build records its H5's file digest in the sidecar (`output {filename, sha256, size_bytes}`), in a `<h5>.sha256` file beside it, in its `spine_h5_creation` stage event and in its Logbook `pipeline` verdict (`artifact_sha256`); when the sidecar carries `output.sha256`, the full build refuses an `--input-h5` whose measured digest differs (older sidecars without the key bind by content identity alone). The bound-spine node compares the checkpoint's gate report digests with the branch's own gate declarations and refuses a spine whose gate manifest differs from them, so `--input-h5` needs a spine built by a branch with the same declarations; every acceptance spine on disk when this registration landed predates them and is not admitted. Historical candidate H5 files and reviewed-bypass sidecars are not alternate build sources. Chronicle facts and manifest must match the independently reviewed national and local feed declarations; filtering targets does not relax source validation.

The target registry binds Census household and demographic rows from the reviewed Chronicle feed, including the approved Northern Ireland constituency geography. Geography is assigned after expansion by the shared atomic-geography operators (microcosm#931): `uk.full.identity` keys every household with `household_draw_key` from the spine's explicit lineage (source household id, SPI support channel and clone index, CGT clone and donor flags) and the pool clone index; `uk.full.geography.assign` draws one atomic area per household (E&W 2021 Output Area, Scotland 2022 Output Area, NI 2021 Data Zone) by census household count within the household's FRS region, with a keyed `sha256-u53-v1` stream so a household's draw never depends on row order, K or any other household; `uk.full.geography.derive` reads every larger geography off the support's versioned mappings; `uk.full.geography.local_authority` resolves the engine's `local_authority` key from the derived authority code (the same resolver the ladder path uses); the shared `uk.full.geography.gate` and the UK distribution gate `uk.full.geography_gate` follow, and `uk.full.pool` refuses unless the shared gate passed. The OA ladder still supplies the constituency and local-authority rosters, household dispersion and lookup support for target compilation; its household counts are not a second source of calibration targets. Source receipts retain the Chronicle identity, the paired ladder digest and the geography binding (assignment mode, definition sha256, support pins, identity column, stream), so target values and the geography used to assign households can be audited separately.
Expand Down Expand Up @@ -60,7 +62,7 @@ For smaller outputs, add `--dataset-households 100000` to use the common informe

## Graph owners and shared contracts

The spine graph is the 40-stage source roster declared by the UK spec, built by `uk_runtime.spine_build` (the spine tool moved into the package; `tools/build_uk_frs_spine.py` is a six-line shim over it). The superseded `frs_hmrc_retained_leaves` and `hmrc_spi_income` stages and the `UK_SPINE_EXCLUSIONS` list that hid them are removed; the active HMRC path is `frs_hmrc_spine_leaves`, `spi_support_channel`, `spi_income_band_donors` and `hmrc_spi_income_spine`, and the FRS HMRC leaf columns come from `uk_runtime.frs_hmrc_source`. The spine's assembled and transferred gate batteries are graph nodes (`uk_runtime.graph_evidence`); stage evidence and fit-weight records are read back from the content store through the shared `stage_evidence` artifact rather than from in-memory collectors; the HMRC replay sidecar is rebuilt from the SPI stage's checkpoint metadata; the spine sidecar records the operation inventory and the graph manifest, and the graph manifest is saved as `spine.graph.json` under the checkpoint root.
The spine graph is the 41-stage source roster declared by the UK spec, built by `uk_runtime.spine_build` (the spine tool moved into the package; `tools/build_uk_frs_spine.py` is a six-line shim over it). The superseded `frs_hmrc_retained_leaves` and `hmrc_spi_income` stages and the `UK_SPINE_EXCLUSIONS` list that hid them are removed; the active HMRC path is `frs_hmrc_spine_leaves`, `spi_support_channel`, `spi_income_band_donors` and `hmrc_spi_income_spine`, and the FRS HMRC leaf columns come from `uk_runtime.frs_hmrc_source`. The spine's assembled and transferred gate batteries are graph nodes (`uk_runtime.graph_evidence`); stage evidence and fit-weight records are read back from the content store through the shared `stage_evidence` artifact rather than from in-memory collectors; the HMRC replay sidecar is rebuilt from the SPI stage's checkpoint metadata; the spine sidecar records the operation inventory and the graph manifest, and the graph manifest is saved as `spine.graph.json` under the checkpoint root.

| Operation | Graph owner |
| --- | --- |
Expand All @@ -72,6 +74,7 @@ The spine graph is the 40-stage source roster declared by the UK spec, built by
| Post-clone household identity | `uk.full.identity` |
| Atomic-area support import, identity-keyed assignment, derivation and shared integrity gate | `uk.full.geography.support.{0,1,2}`, `uk.full.geography.assign`, `uk.full.geography.derive`, `uk.full.geography.gate` (shared `geography.*@1` kernels) |
| Engine local-authority key from the derived authority code | `uk.full.geography.local_authority` |
| Per-clone home value off the assigned constituency's Land Registry sale distribution, and council-tax band off the LSOA's VOA mix at the same uniform (microcosm#1240; `--property-localisation price_paid`) | `uk.full.property.value`, `uk.full.property.band` |
| UK geography distribution gate | `uk.full.geography_gate` |
| Legacy sequential ladder draw (`--geography-assignment legacy`, measurement builds only) | `uk.full.locations`, `uk.full.geography_mapping`, `uk.full.geography_gate` |
| Full pinned source/register compilation | `uk.full.target_compilation` |
Expand Down
2 changes: 2 additions & 0 deletions packages/microcosm-build/src/microcosm/build/country_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
"export_surface",
"exported_nonzero",
"formula_owned_export",
"high_value_home_benchmarks",
"high_value_home_support",
"input_mass_parity",
"ledger_compile_parity",
"macro_realism",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
"replace_sentinels",
"split_component_by_share",
"split_top_wealth_support_households",
"split_top_value_owner_households",
"split_liable_gainers_by_residential_probability",
"stack_income_band_donor_households",
"stack_zero_weight_donors",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8860,6 +8860,73 @@
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"stratum_lower",
"copies",
"stratum_rule",
"copy_rule",
"id_remapping",
"flag_column",
"copies_column",
"copy_index_column",
"values",
"weight_kind_out",
"conservation",
"declared_factor",
"reason"
],
"properties": {
"kind": {
"const": "split_top_value_owner_households"
},
"stratum_lower": {
"type": "number",
"exclusiveMinimum": 0,
"exclusiveMaximum": 1
},
"copies": {
"type": "integer",
"minimum": 2
},
"stratum_rule": {
"type": "string"
},
"copy_rule": {
"type": "string"
},
"id_remapping": {
"type": "string"
},
"flag_column": {
"type": "string"
},
"copies_column": {
"type": "string"
},
"copy_index_column": {
"type": "string"
},
"values": {
"type": "string"
},
"weight_kind_out": {
"type": "string"
},
"conservation": {
"type": "string"
},
"declared_factor": {
"type": "number"
},
"reason": {
"type": "string"
}
}
},
{
"type": "object",
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
},
"source_fact_feed": {
"source_repo": "PolicyEngine/chronicle",
"source_commit": "942a1fa0adc50d645734481c88f403ac2175e896",
"source_commit": "735820cf76684aae1dfee4cffb48224d59bb7aab",
"build": "build-bundle --suite uk -> build-consumer-artifact",
"artifact_schema_version": "policyengine_ledger.consumer_artifact.v2",
"fact_row_count": 927239,
"facts_sha256": "a45ae8570206af49d1dc570fe0f9b2ae83ed3b432ac5e430f088f488f0d256da",
"manifest_sha256": "f7af3ec965dc7951f2fbd25b4e06fa6c4b945ed289e4b99d27d3a77c45d3988c"
"fact_row_count": 950242,
"facts_sha256": "3033e437ed61d7d5e970f4fef34a2a5523fe068770a7798e5768af656f2a48a4",
"manifest_sha256": "2777dc33969a3aff449896aad3dcb0b2492c18896d6ab656918c04cd2e18551c"
},
"selections": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
"country": "uk",
"scope": "uk_calibration",
"source_repo": "PolicyEngine/chronicle",
"source_commit": "942a1fa0adc50d645734481c88f403ac2175e896",
"source_commit": "735820cf76684aae1dfee4cffb48224d59bb7aab",
"build": "build-bundle --suite uk -> build-consumer-artifact",
"artifact_schema_version": "policyengine_ledger.consumer_artifact.v2",
"consumer_fact_schema_versions": [
"chronicle.consumer_fact.v4"
],
"consumer_fact_schema_sha256": "cc9efb90e9c73913bc76a89e9df435a9ca6ff49942e63dc8d2bf183c65a70224",
"fact_row_count": 927239,
"facts_sha256": "a45ae8570206af49d1dc570fe0f9b2ae83ed3b432ac5e430f088f488f0d256da",
"manifest_sha256": "f7af3ec965dc7951f2fbd25b4e06fa6c4b945ed289e4b99d27d3a77c45d3988c"
"fact_row_count": 950242,
"facts_sha256": "3033e437ed61d7d5e970f4fef34a2a5523fe068770a7798e5768af656f2a48a4",
"manifest_sha256": "2777dc33969a3aff449896aad3dcb0b2492c18896d6ab656918c04cd2e18551c"
}
Loading
Loading