From 979944913bad3eb21d13e16ffe0bd07f69dccff5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 16 Oct 2026 22:18:47 -0400 Subject: [PATCH] Import the UK model module directly in the contract's household route scripts/check_uk_candidate.py sets POLICYENGINE_SKIP_COUNTRY_IMPORTS, which leaves policyengine.uk as None, so every household-route row of the reform-contract gate errored ('NoneType' object has no attribute 'calculate_household') and every candidate since the gate went live was rejected. Fixes #650. Co-Authored-By: Claude Opus 5.5 --- changelog.d/650.fixed.md | 1 + .../uk/budget_reform_contract.py | 7 +++++-- tests/test_uk_budget_reform_contract.py | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 changelog.d/650.fixed.md diff --git a/changelog.d/650.fixed.md b/changelog.d/650.fixed.md new file mode 100644 index 000000000..dedc6a83a --- /dev/null +++ b/changelog.d/650.fixed.md @@ -0,0 +1 @@ +The UK Budget reform contract's household route imports the UK model module directly, so it runs in the qualification gate, whose scripts skip the country imports that set `policyengine.uk`; every household-route row there had errored. diff --git a/src/policyengine/tax_benefit_models/uk/budget_reform_contract.py b/src/policyengine/tax_benefit_models/uk/budget_reform_contract.py index 8f14c956d..bd471af72 100644 --- a/src/policyengine/tax_benefit_models/uk/budget_reform_contract.py +++ b/src/policyengine/tax_benefit_models/uk/budget_reform_contract.py @@ -1257,10 +1257,13 @@ class _WrapperHouseholdRoute: route = "wrapper.household" def _call(self, case: Case, year: int, reform, variables: Sequence[str]): - import policyengine as pe + # The module ``policyengine.uk`` names, imported directly: a process + # that sets POLICYENGINE_SKIP_COUNTRY_IMPORTS, as the qualification + # scripts do, leaves ``policyengine.uk`` as None. + from policyengine.tax_benefit_models import uk household = case.household - return pe.uk.calculate_household( + return uk.calculate_household( people=[dict(person) for person in household.people], benunit=dict(household.benunit), household=dict(household.household), diff --git a/tests/test_uk_budget_reform_contract.py b/tests/test_uk_budget_reform_contract.py index a0a6b80d7..3ca9b75f3 100644 --- a/tests/test_uk_budget_reform_contract.py +++ b/tests/test_uk_budget_reform_contract.py @@ -2004,3 +2004,18 @@ def array(tag, variable, year): order = status[(population.POPULATION_QUERY_ORDER, "unreformed")] assert order["status"] == "fail" assert order["detail"]["income_tax:2026_matches"] is False + + +def test_the_household_route_runs_where_country_imports_are_skipped(monkeypatch): + """scripts/check_uk_candidate.py sets POLICYENGINE_SKIP_COUNTRY_IMPORTS, so + ``policyengine.uk`` is None in the qualification gate; every household-route + row there errored with "'NoneType' object has no attribute + 'calculate_household'".""" + import policyengine + + monkeypatch.setattr(policyengine, "uk", None) + case = next(case for case in CASES if case.id == "april_rate_change") + rows = evaluate_case(case, "wrapper.household") + statuses = {row["status"] for row in rows} + # The route reads variables, not parameters, which it reports unsupported. + assert "pass" in statuses and statuses <= {"pass", "unsupported"}, rows