Skip to content
Draft
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
1 change: 1 addition & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Ran Stage 12 US national simulations as 20 region-group calls each, and built an independent, immutable Stage 12 cache of three annual US datasets and 60 current-law baselines before each deployment, publishing it only after a recorded determinism check and recording its release in the v2 version manifest under a new key, while the existing key keeps a copy that earlier readers accept
- Made the budget refuse a household count outside its country's plausible range (above 200 million in the US or 60 million in the UK, negative or not finite), raising ImplausibleEntityCountError instead of publishing a count like #767's
changed:
- Made only US state runs compute congressional district impacts; national runs (no region, or us) and region-group runs, and so segmented national runs, their reduce and Stage 12 aggregation of a national report, now return congressional_district_impact null (Stage 12 aggregation of a state report still computes that state's districts), because nationwide reports take each district from a separate run of its state on that state's dataset, which matches the state's own report; the beta integration check of a US national result now expects congressional_district_impact null, and the state checks still expect the state's districts
- Enforced the file-length rule sim.quality.python-file-length, now that every tracked Python file is within its limit: its empty baseline and its downgrade section are gone, and its tier is metric rather than mechanical, because it measures a number against a threshold, with the at-the-limit and one-over fixtures as its below and above proof
- Moved the entrypoint's backend calls, response telemetry and comparison dispatch out of app.py (925 lines, now 583) into entry_runtime.py, which app.py re-exports and which still logs under the policyengine_simulation_entry.app logger; the routes stay in app.py, so FastAPI and ruff read their annotations as before; the 2,251-line deployment asset test file is split into seven test files and a support module named test_deployment_assets_support.py, the rules registry names each moved test by its new file, and its guard-test discovery now covers tests/test_deployment_assets*.py
- Moved the worker's credential setup, request year and policy parsing, and region resolution out of simulation_runtime.py (1,111 lines, now 651) into gcp_credentials.py, request_policy.py and region_resolution.py; simulation_runtime.py keeps dataset selection and loading, simulation building, local-area breakdowns and the run itself, which tests replace by name there, and re-exports the moved names, so every caller still imports them from simulation_runtime; the shared helpers _parse_year, _normalise_policy, _compile_interval_policy, _runtime_policy (now wrapper_policy), _resolve_region, _resolve_dataset_reference and _require_local_area_breakdown are now public, and the credential messages still log under the simulation_runtime logger; the 3,048-line output builder test file is split into seven test files and a module of shared fakes, all named test_simulation_output_*.py so the weighted-frame rule still checks them
Expand Down
63 changes: 48 additions & 15 deletions projects/policyengine-apis-integ/tests/simulation/test_calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"no_change_percentage",
"population",
}
_AT_LARGE_DISTRICT_IDS = {"AK-01", "DC-01", "DE-01", "ND-01", "SD-01", "VT-01", "WY-01"}
# From the generated client, so a stage added to the schema is accepted here.
_JOB_STAGES = frozenset(stage.value for stage in JobStage)

Expand Down Expand Up @@ -196,7 +195,6 @@ def assert_congressional_district_results(
economy_result: object,
*,
expected_district_prefix: str | None = None,
expected_district_ids: set[str] | None = None,
) -> None:
economy_result = _as_mapping(economy_result, label="economy result")
assert "congressional_district_impact" in economy_result, (
Expand Down Expand Up @@ -237,13 +235,22 @@ def assert_congressional_district_results(
f"got {district_ids}"
)

if expected_district_ids is not None:
district_ids = set(_district_ids(district_results))
missing = expected_district_ids - district_ids
assert not missing, (
f"Missing expected district IDs {sorted(missing)} "
f"from result IDs: {sorted(district_ids)}"
)

def assert_no_congressional_district_results(economy_result: object) -> None:
"""Check a US national result has congressional_district_impact set to null.

Only state runs compute districts: nationwide reports take them from a
separate run of each state.
"""
economy_result = _as_mapping(economy_result, label="economy result")
assert "congressional_district_impact" in economy_result, (
f"Missing 'congressional_district_impact' in result: {economy_result.keys()}"
)
district_impact = economy_result["congressional_district_impact"]
assert district_impact is None, (
"Expected no congressional_district_impact in a US national result, "
f"got {district_impact!r}"
)


_UK_AREA_RESULT_KEYS = ("constituency_impact", "local_authority_impact")
Expand Down Expand Up @@ -323,6 +330,34 @@ def test_result_assertions_accept_generated_client_objects() -> None:
assert_congressional_district_results(result, expected_district_prefix="UT-")


def test_national_us_assertions_accept_a_result_without_districts() -> None:
assert_no_congressional_district_results(
_GeneratedClientObject({"budget": {}, "congressional_district_impact": None})
)
assert_no_congressional_district_results({"congressional_district_impact": None})


@pytest.mark.parametrize(
"district_impact",
[
{"districts": []},
{"districts": [{"district": "UT-01"}]},
_GeneratedClientObject({"districts": [{"district": "UT-01"}]}),
],
ids=["no-districts-listed", "districts-listed", "generated-client-object"],
)
def test_national_us_assertions_reject_district_results(district_impact) -> None:
with pytest.raises(AssertionError, match="congressional_district_impact"):
assert_no_congressional_district_results(
{"congressional_district_impact": district_impact}
)


def test_national_us_assertions_reject_a_result_without_the_district_field() -> None:
with pytest.raises(AssertionError, match="congressional_district_impact"):
assert_no_congressional_district_results({"budget": {}})


_BREAKDOWN_CONSTITUENCY = {
"constituency_code": "E14001063",
"constituency_name": "Aldershot",
Expand Down Expand Up @@ -613,9 +648,10 @@ def test_calculate_default_model(
):
"""A simulation on the default model version returns economic impact data.

Given a simulation request with default model version
Given a US national simulation request with default model version
When the simulation is submitted and polled to completion
Then the result contains expected economic impact data.
Then the result contains expected economic impact data and no
congressional districts, which only state runs compute.
"""
# Given
request = SimulationRequest.from_dict(
Expand Down Expand Up @@ -649,10 +685,7 @@ def test_calculate_default_model(

economy_result = result.result
assert_economy_result_sections(economy_result)
assert_congressional_district_results(
economy_result,
expected_district_ids=_AT_LARGE_DISTRICT_IDS,
)
assert_no_congressional_district_results(economy_result)


@pytest.mark.beta_only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ def build_national_output(
"""Reduce the children's microdata into the full national macro output dict.

Runs the existing ``SimulationOutputBuilder`` over stand-in simulations
with ``resolved_region_code=country`` so the national congressional
district table populates. No model recompute occurs.
with ``resolved_region_code=country``, as for any national run, so the
output has no congressional districts: nationwide reports take those from
separate state runs. No model recompute occurs.
"""
country = inputs.country
country_module = inputs.country_module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,18 @@ def _integer_record_value(record: Mapping[str, Any], key: str) -> int | None:


def _should_build_us_congressional_district_impact(region_code: str | None) -> bool:
if region_code is None:
return True
normalized_region_code = region_code.lower()
return normalized_region_code == "us" or normalized_region_code.startswith(
("state/", "region_group/")
)
"""Return whether a US run for this region computes congressional districts.

Only state runs do, such as ``state/ca`` in any case. National runs (no
region, or ``us``) and region-group runs compute none, so neither do
segmented national runs, their reduce or Stage 12 aggregation of a
national report: a nationwide report takes its districts from a separate
run of each state on that state's dataset, which gives each district the
same figures as its state's own report. Stage 12 aggregation of a state
report still computes that state's districts. District and place runs
compute none.
"""
return region_code is not None and region_code.lower().startswith("state/")


def build_congressional_district_impact(
Expand All @@ -238,10 +244,11 @@ def build_congressional_district_impact(
*,
region_code: str | None = None,
) -> CongressionalDistrictImpactOutput | None:
"""Compute US congressional district impacts for national, state or group runs.
"""Compute US congressional district impacts for a state run.

None for other countries or regions, and when the calculation fails (the
failure is logged; SPM selection errors still propagate).
None for other countries and other regions, including national and
region-group runs, and when the calculation fails (the failure is logged;
SPM selection errors still propagate).
"""
if country != "us":
return None
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Unit tests for region-group resolution (B2) and the CD-impact gate (B3)."""
"""Unit tests for region-group resolution (B2) and the district-impact regions (B3)."""

from unittest.mock import MagicMock

Expand All @@ -11,7 +11,7 @@
from policyengine_simulation_executor import region_resolution
from policyengine_simulation_executor import simulation_runtime as sr
from policyengine_simulation_executor.simulation_output_geographic import (
_should_build_us_congressional_district_impact as gate,
_should_build_us_congressional_district_impact as should_build_districts,
)


Expand Down Expand Up @@ -119,13 +119,16 @@ def test__resolve_region_dispatches_to_group(self, monkeypatch):
assert isinstance(res.scoping_strategy, RegionGroupStrategy)


class TestCongressionalDistrictGate:
def test__allows_national_state_and_group(self):
assert gate(None) is True
assert gate("us") is True
assert gate("state/ca") is True
assert gate("region_group/state/hi+state/ia") is True
class TestCongressionalDistrictRegions:
def test__allows_states(self):
assert should_build_districts("state/ca") is True
assert should_build_districts("STATE/CA") is True

def test__rejects_national_and_group(self):
assert should_build_districts(None) is False
assert should_build_districts("us") is False
assert should_build_districts("region_group/state/hi+state/ia") is False

def test__rejects_district_and_place(self):
assert gate("congressional_district/CA-01") is False
assert gate("place/CA-12345") is False
assert should_build_districts("congressional_district/CA-01") is False
assert should_build_districts("place/CA-12345") is False
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,51 @@ def fake_build_congressional_district_impact(
assert calls == [("us", baseline, reform, "state/ut")]


@pytest.fixture
def district_calculations(monkeypatch):
"""Record each call to policyengine.py's district calculation, in its place."""
calculations = []

def record_district_calculation(baseline, reform):
calculations.append((baseline, reform))
return SimpleNamespace(district_results=[])

monkeypatch.setattr(
"policyengine.outputs.congressional_district_impact."
"compute_us_congressional_district_impacts",
record_district_calculation,
)
return calculations


@pytest.mark.parametrize(
"region_code",
[None, "us", "region_group/state/hi+state/ia"],
)
def test_builder_skips_districts_for_national_and_region_group_runs(
district_calculations, region_code
):
baseline, reform = macro_baseline_reform()
builder = make_output_builder(
"us", baseline, reform, BuilderOptions(resolved_region_code=region_code)
)

assert builder._build_congressional_district_impact() is None
assert district_calculations == []


def test_builder_builds_districts_for_state_runs(district_calculations):
baseline, reform = macro_baseline_reform()
builder = make_output_builder(
"us", baseline, reform, BuilderOptions(resolved_region_code="state/ut")
)

assert builder._build_congressional_district_impact() == (
CongressionalDistrictImpactOutput(districts=[])
)
assert district_calculations == [(baseline, reform)]


def test_builder_records_output_timings_without_real_calculation(monkeypatch):
baseline = object()
reform = object()
Expand Down
Loading