From e820befa7d29b24d2e5979161f635985b9515885 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 16 Oct 2026 17:56:57 -0400 Subject: [PATCH] Run the UK contract's household route when country imports are skipped scripts/check_uk_candidate.py sets POLICYENGINE_SKIP_COUNTRY_IMPORTS=1, which binds policyengine.uk to None. The wrapper.household route reached calculate_household through it, so every household row errored and every UK candidate was rejected (2.134.5-1.58.2). Import the country module directly; it exposes the same function regardless of the flag. Fixes #645 Co-Authored-By: Claude Opus 5.5 --- changelog.d/645.fixed.md | 1 + .../uk/budget_reform_contract.py | 8 +++- tests/test_uk_budget_reform_contract.py | 46 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 changelog.d/645.fixed.md diff --git a/changelog.d/645.fixed.md b/changelog.d/645.fixed.md new file mode 100644 index 000000000..2b4678bed --- /dev/null +++ b/changelog.d/645.fixed.md @@ -0,0 +1 @@ +The UK reform contract's household route now runs when country imports are skipped, so the candidate script's `POLICYENGINE_SKIP_COUNTRY_IMPORTS` no longer turns every `wrapper.household` row into an error and rejects every UK candidate. 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..b2dc837ed 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,14 @@ class _WrapperHouseholdRoute: route = "wrapper.household" def _call(self, case: Case, year: int, reform, variables: Sequence[str]): - import policyengine as pe + # Import the country module itself, not ``policyengine.uk``: callers such + # as scripts/check_uk_candidate.py set POLICYENGINE_SKIP_COUNTRY_IMPORTS, + # which leaves ``policyengine.uk`` as None. It is the same + # ``calculate_household`` that ``policyengine.uk`` exposes. + from policyengine.tax_benefit_models import uk as pe_uk household = case.household - return pe.uk.calculate_household( + return pe_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 7f13d2803..6de242dd5 100644 --- a/tests/test_uk_budget_reform_contract.py +++ b/tests/test_uk_budget_reform_contract.py @@ -9,6 +9,10 @@ import datetime import json +import os +import subprocess +import sys +import textwrap import pytest @@ -1373,3 +1377,45 @@ def test_every_report_row_has_a_reform_identity_field(): assert "reform_identity" in row, (section, row) if row["reform_identity"] is None: assert row["reform_identity_unavailable"], (section, row) + + +def test_the_household_route_runs_when_country_imports_are_skipped(): + # scripts/check_uk_candidate.py sets POLICYENGINE_SKIP_COUNTRY_IMPORTS=1 + # before the qualification gates run this contract, and that leaves + # ``policyengine.uk`` as None. Reaching the wrapper through it made every + # wrapper.household row an error, so every UK candidate was rejected. + code = textwrap.dedent( + """ + import json + import os + + os.environ["POLICYENGINE_SKIP_COUNTRY_IMPORTS"] = "1" + import policyengine + + assert policyengine.uk is None + from policyengine.tax_benefit_models.uk.budget_reform_contract import ( + CASES, + evaluate_case, + ) + + case = next(case for case in CASES if case.id == "april_rate_change") + rows = evaluate_case(case, "wrapper.household") + print(json.dumps([[row["status"], row.get("detail")] for row in rows])) + """ + ) + env = { + key: value + for key, value in os.environ.items() + if key != "POLICYENGINE_SKIP_COUNTRY_IMPORTS" + } + result = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True, + env=env, + timeout=900, + ) + assert result.returncode == 0, result.stderr + rows = json.loads(result.stdout.strip().splitlines()[-1]) + assert rows + assert [row for row in rows if row[0] == "error"] == [], rows