From d9d5a45e40089972141cd2df940602c173837822 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 23 Aug 2026 16:31:31 +0200 Subject: [PATCH] Pin the scotgov council-tax stock targets to Scotland The nine scotgov.council_tax_stock.* calibration targets have been held as no_fact_at_or_before_period although Chronicle carries their facts: the substring geography rule sees no "scotland" in "scotgov", pins them to the UK, and the pin is merged into the selector and matched against fact.geography.id, which Chronicle correctly stamps S92000003 for the CTAXBASE September 2025 chargeable-dwelling rows. Zero matches ever. Scope the fix to the stock family (a blanket scotgov rule would also re-pin the scottish_child_payment_spending projection, a separate decision), count both stock publishers in the family accounting, and regenerate the target surface with the committed generator against the same feed vintage the surface was built from (chronicle 1cab809, build-bundle --suite uk + build-consumer-artifact; 107,550 facts, sha256 226358e73e7c449e71a3f6dc 91a72e8e3941e0f14265943d81c990edb21c2a6c). Membership movement is exactly the nine stock targets: active 388 -> 397, no-fact holds 27 -> 18, family 9 -> 18 declared rows, nine new month-to-year uprating holds; no other target's status, pin or resolved value changes, and no existing reference is modified. Co-Authored-By: Claude Fable 5 --- .../scotgov-council-tax-stock-pins.fixed.md | 1 + .../build/uk/target_reference_membership.json | 190 ++++++++++++------ .../microcosm/build/uk/target_references.json | 171 ++++++++++++++++ .../tests/test_country_spec.py | 2 +- .../tests/test_uk_national_calibration.py | 2 +- .../tests/test_uk_target_references.py | 67 +++++- tools/generate_uk_target_references.py | 18 +- 7 files changed, 379 insertions(+), 72 deletions(-) create mode 100644 changelog.d/scotgov-council-tax-stock-pins.fixed.md diff --git a/changelog.d/scotgov-council-tax-stock-pins.fixed.md b/changelog.d/scotgov-council-tax-stock-pins.fixed.md new file mode 100644 index 00000000..e535bff2 --- /dev/null +++ b/changelog.d/scotgov-council-tax-stock-pins.fixed.md @@ -0,0 +1 @@ +Pin the nine scotgov.council_tax_stock UK calibration targets to Scotland: the substring geography rule saw no "scotland" in "scotgov" and pinned them to the UK, so the Scotland-stamped CTAXBASE chargeable-dwelling facts could never match and the whole family was held as no_fact_at_or_before_period. 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 6ef0d46f..3bbfd605 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 @@ -3,11 +3,11 @@ "target_period": 2025, "candidate_count": 417, "contract_target_count": 189, - "active_reference_count": 388, + "active_reference_count": 397, "status_counts": { - "active": 388, + "active": 397, "multi_fact": 1, - "no_fact_at_or_before_period": 27, + "no_fact_at_or_before_period": 18, "signed_excluded": 1 }, "geography_pins": { @@ -609,7 +609,7 @@ }, "scotgov.council_tax_stock.band_a": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_b": { "geography_level": "country", @@ -617,7 +617,7 @@ }, "scotgov.council_tax_stock.band_b": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_c": { "geography_level": "country", @@ -625,7 +625,7 @@ }, "scotgov.council_tax_stock.band_c": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_d": { "geography_level": "country", @@ -633,7 +633,7 @@ }, "scotgov.council_tax_stock.band_d": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_e": { "geography_level": "country", @@ -641,7 +641,7 @@ }, "scotgov.council_tax_stock.band_e": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_f": { "geography_level": "country", @@ -649,7 +649,7 @@ }, "scotgov.council_tax_stock.band_f": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_g": { "geography_level": "country", @@ -657,7 +657,7 @@ }, "scotgov.council_tax_stock.band_g": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.band_h": { "geography_level": "country", @@ -665,7 +665,7 @@ }, "scotgov.council_tax_stock.band_h": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "voa.council_tax_stock.total": { "geography_level": "country", @@ -673,7 +673,7 @@ }, "scotgov.council_tax_stock.total": { "geography_level": "country", - "geography_id": "K02000001" + "geography_id": "S92000003" }, "scotgov.scottish_child_payment_spending": { "geography_level": "country", @@ -2487,46 +2487,91 @@ "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_a", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_b", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_b", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_c", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_c", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_d", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_d", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_e", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_e", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_f", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_f", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_g", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_g", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.band_h", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.band_h", + "from": "2025-09", + "to": "2025" + }, { "name": "voa.council_tax_stock.total", "from": "2025-03", "to": "2025" }, + { + "name": "scotgov.council_tax_stock.total", + "from": "2025-09", + "to": "2025" + }, { "name": "ons.public_sector_employment", "from": "2024-12", @@ -7071,14 +7116,17 @@ ] }, "scotgov.council_tax_stock.band_a": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_a", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 498707.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_d16c53fe6a1c8f4f5886b7e5" } ] }, @@ -7098,14 +7146,17 @@ ] }, "scotgov.council_tax_stock.band_b": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_b", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 583705.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_2f95ec98ae61f9898bd47ed9" } ] }, @@ -7125,14 +7176,17 @@ ] }, "scotgov.council_tax_stock.band_c": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_c", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 426388.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_c9781708df55dc5d197e6623" } ] }, @@ -7152,14 +7206,17 @@ ] }, "scotgov.council_tax_stock.band_d": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_d", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 369621.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_ae1fe03809a505b820479598" } ] }, @@ -7179,14 +7236,17 @@ ] }, "scotgov.council_tax_stock.band_e": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_e", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 366881.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_e83153e0de40fb74471c805f" } ] }, @@ -7206,14 +7266,17 @@ ] }, "scotgov.council_tax_stock.band_f": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_f", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 221282.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_b24b6409c1473600c03999ec" } ] }, @@ -7233,14 +7296,17 @@ ] }, "scotgov.council_tax_stock.band_g": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_g", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 142084.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_c86494a855b00998122989d6" } ] }, @@ -7260,14 +7326,17 @@ ] }, "scotgov.council_tax_stock.band_h": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.band_h", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 14481.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_60d9ae638eff29fc1c88907e" } ] }, @@ -7287,14 +7356,17 @@ ] }, "scotgov.council_tax_stock.total": { - "status": "no_fact_at_or_before_period", + "status": "active", "candidates": [ { "name": "scotgov.council_tax_stock.total", - "status": "no_fact_at_or_before_period", - "matched_fact_count_overall": 0, - "matched_fact_count_at_or_before_period": 0, - "error": "ledger_reference_compile_status_no_fact_at_or_before_period" + "status": "active", + "matched_fact_count_overall": 1, + "matched_fact_count_at_or_before_period": 1, + "resolved_period": 2025, + "resolved_value": 2623149.0, + "resolved_fact_period": "2025-09", + "resolved_fact_key": "ledger_aggregate_fact_v2_ca77a0f0a777ccba05e4bef9" } ] }, @@ -7625,8 +7697,8 @@ { "family": "council_tax_stock", "status": "active_declared_rows", - "active_reference_count": 9, - "signed_rationale": "VOA council-tax stock bands are declared as nine explicit target rows, including total, and each resolves with its country-level geography and band pin." + "active_reference_count": 18, + "signed_rationale": "VOA (England and Wales) and Scottish Government CTAXBASE (Scotland) council-tax stock bands are declared as nine explicit target rows each, including total, and each resolves with its country-level geography and band pin." } ], "signed_exclusion_rationales": [ 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 055e61fe..2bc9a065 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/target_references.json +++ b/packages/microcosm-build/src/microcosm/build/uk/target_references.json @@ -8994,6 +8994,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_a", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_a", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_a", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_a", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_b", "ledger_selector": { @@ -9013,6 +9032,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_b", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_b", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_b", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_b", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_c", "ledger_selector": { @@ -9032,6 +9070,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_c", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_c", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_c", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_c", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_d", "ledger_selector": { @@ -9051,6 +9108,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_d", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_d", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_d", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_d", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_e", "ledger_selector": { @@ -9070,6 +9146,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_e", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_e", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_e", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_e", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_f", "ledger_selector": { @@ -9089,6 +9184,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_f", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_f", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_f", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_f", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_g", "ledger_selector": { @@ -9108,6 +9222,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_g", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_g", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_g", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_g", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.band_h", "ledger_selector": { @@ -9127,6 +9260,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.band_h", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "band_h", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/band_h", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.band_h", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "voa.council_tax_stock.total", "ledger_selector": { @@ -9146,6 +9298,25 @@ "uprating_from_period": "2025-03", "uprating_to_period": 2025 }, + { + "name": "scotgov.council_tax_stock.total", + "ledger_selector": { + "source_name": "scotgov", + "source_measure_id": "total", + "geography_level": "country", + "geography_id": "S92000003" + }, + "entity": "household", + "measure": "voa/council_tax/SCOTLAND/total", + "family": "council_tax_stock", + "period": 2025, + "metadata": { + "contract_target_id": "scotgov.council_tax_stock.total", + "measure_kind": "prepared_column" + }, + "uprating_from_period": "2025-09", + "uprating_to_period": 2025 + }, { "name": "ons.savings_interest_income", "ledger_selector": { diff --git a/packages/microcosm-build/tests/test_country_spec.py b/packages/microcosm-build/tests/test_country_spec.py index e4be8693..6af8d265 100644 --- a/packages/microcosm-build/tests/test_country_spec.py +++ b/packages/microcosm-build/tests/test_country_spec.py @@ -393,7 +393,7 @@ def test_uk_target_references_accept_regenerated_contract_fields(self) -> None: spec = load_country_spec("uk") references = {reference.name: reference for reference in spec.target_references} - assert len(references) == 388 + assert len(references) == 397 assert references["obr.esa"].value_operation == "sum" assert references["dwp.uc.households"].value_operation == ( "calendar_year_average" diff --git a/packages/microcosm-build/tests/test_uk_national_calibration.py b/packages/microcosm-build/tests/test_uk_national_calibration.py index 12a9cddd..49445d4f 100644 --- a/packages/microcosm-build/tests/test_uk_national_calibration.py +++ b/packages/microcosm-build/tests/test_uk_national_calibration.py @@ -16,7 +16,7 @@ from microcosm.build.uk_runtime.national_frame import validate_uk_national_frame from microcosm.frame import EntitySchema, Frame, WeightKind, Weights -ACTIVE_REFERENCE_COUNT = 388 +ACTIVE_REFERENCE_COUNT = 397 def _uc_reference(**overrides) -> LedgerTargetReference: diff --git a/packages/microcosm-build/tests/test_uk_target_references.py b/packages/microcosm-build/tests/test_uk_target_references.py index 60e70fe8..4bd50e1a 100644 --- a/packages/microcosm-build/tests/test_uk_target_references.py +++ b/packages/microcosm-build/tests/test_uk_target_references.py @@ -33,7 +33,7 @@ _value_operation_by_target_id, ) -ACTIVE_REFERENCE_COUNT = 388 +ACTIVE_REFERENCE_COUNT = 397 UK_DATA_REPO = "policyengine-" + "uk-data" FIXTURE_REFERENCE_NAMES = { @@ -184,15 +184,67 @@ def test_ons_age_total_targets_pin_exact_age_dimension_set() -> None: ]["dimensions"] == ["age"] +def test_scotgov_council_tax_stock_pins_are_scotland_and_the_rule_is_scoped() -> None: + """The CTAXBASE stock facts are stamped S92000003 in Chronicle. + + The substring pin rule sees no "scotland" in "scotgov" and used to pin the + nine stock targets to the UK, so they could never match. The fix is scoped + to the stock family: every other Scotland pin still comes from the + substring rule, and the unrelated scotgov child-payment target keeps the + UK pin it had. + """ + contract = _load_uk_resource("uk_national_targets.json") + pins = _geography_pins(contract) + stock_ids = { + str(target["target_id"]) + for target in contract["targets"] + if str(target["target_id"]).startswith("scotgov.council_tax_stock.") + } + assert stock_ids == {f"scotgov.council_tax_stock.band_{band}" for band in "abcdefgh"} | { + "scotgov.council_tax_stock.total" + } + assert {pins[target_id]["geography_id"] for target_id in stock_ids} == {"S92000003"} + assert pins["scotgov.scottish_child_payment_spending"]["geography_id"] == "K02000001" + + def haystack(target: dict) -> str: + selector = target.get("ledger_selector") or {} + return " ".join( + ( + str(target["target_id"]).lower(), + str(selector.get("source_concept", "")).lower(), + str(selector.get("source_measure_id", "")).lower(), + ) + ) + + substring_scotland = { + str(target["target_id"]) + for target in contract["targets"] + if "scotland" in haystack(target) + and "northern" not in haystack(target) + and "domestic_rates" not in haystack(target) + } + scotland_pinned = { + target_id + for target_id, pin in pins.items() + if pin["geography_id"] == "S92000003" + } + assert scotland_pinned == stock_ids | substring_scotland + + membership = _load_uk_resource("target_reference_membership.json") + for target_id in stock_ids: + assert membership["geography_pins"][target_id]["geography_id"] == "S92000003" + assert membership["targets"][target_id]["status"] == "active" + + def test_uk_target_reference_membership_report_is_packaged() -> None: membership = _load_uk_resource("target_reference_membership.json") assert membership["target_period"] == 2025 assert membership["active_reference_count"] == ACTIVE_REFERENCE_COUNT assert membership["status_counts"] == { - "active": 388, + "active": 397, "multi_fact": 1, - "no_fact_at_or_before_period": 27, + "no_fact_at_or_before_period": 18, "signed_excluded": 1, } assert membership["genuine_sum_residue"] @@ -230,11 +282,12 @@ def test_uk_target_reference_membership_report_is_packaged() -> None: { "family": "council_tax_stock", "status": "active_declared_rows", - "active_reference_count": 9, + "active_reference_count": 18, "signed_rationale": ( - "VOA council-tax stock bands are declared as nine explicit " - "target rows, including total, and each resolves with its " - "country-level geography and band pin." + "VOA (England and Wales) and Scottish Government CTAXBASE " + "(Scotland) council-tax stock bands are declared as nine " + "explicit target rows each, including total, and each resolves " + "with its country-level geography and band pin." ), }, ] diff --git a/tools/generate_uk_target_references.py b/tools/generate_uk_target_references.py index af4da650..8ccc4e3d 100644 --- a/tools/generate_uk_target_references.py +++ b/tools/generate_uk_target_references.py @@ -160,8 +160,17 @@ def _geography_pins(contract: Mapping[str, Any]) -> dict[str, dict[str, str]]: } +SCOTGOV_COUNCIL_TAX_STOCK_PREFIX = "scotgov.council_tax_stock." + + def _geography_id_for_target(target: Mapping[str, Any]) -> str: target_id = str(target["target_id"]).lower() + if target_id.startswith(SCOTGOV_COUNCIL_TAX_STOCK_PREFIX): + # The Scottish Government CTAXBASE chargeable-dwelling facts are + # stamped S92000003 in Chronicle; the substring rule below sees no + # "scotland" in "scotgov" and would fall through to the UK pin, which + # never matches a Scotland-stamped fact. + return UK_GEOGRAPHY_IDS["scotland"] selector = target.get("ledger_selector") or {} concept = str(selector.get("source_concept", "")).lower() measure = str(selector.get("source_measure_id", "")).lower() @@ -314,7 +323,7 @@ def _add_uk_membership_accounting( 1 for reference in references if reference["metadata"]["contract_target_id"].startswith( - "voa.council_tax_stock." + ("voa.council_tax_stock.", SCOTGOV_COUNCIL_TAX_STOCK_PREFIX) ) ) report["fanout_family_outcomes"] = [ @@ -352,9 +361,10 @@ def _add_uk_membership_accounting( "status": "active_declared_rows", "active_reference_count": council_tax_count, "signed_rationale": ( - "VOA council-tax stock bands are declared as nine explicit " - "target rows, including total, and each resolves with its " - "country-level geography and band pin." + "VOA (England and Wales) and Scottish Government CTAXBASE " + "(Scotland) council-tax stock bands are declared as nine " + "explicit target rows each, including total, and each resolves " + "with its country-level geography and band pin." ), }, ]