Skip to content
Closed
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Follow the repository guidance in `CLAUDE.md` for commands, style, changelog ent

Follow [Test design and CI cost](CLAUDE.md#test-design-and-ci-cost) before adding or expanding tests. Use variable-named YAML tests for policy calculations; reserve Python tests for behavior YAML cannot exercise. Preserve Core/data compatibility and isolation coverage, avoid repeated full-model construction, and do not add CI runners or parallel heavy processes without explicit authorization. Document the purpose and measured cost of new simulation-heavy Python tests in the PR.

For read-only parameter lookups and baseline simulations in Python tests, reuse the already-loaded `system` from `policyengine_us.system` instead of constructing another `CountryTaxBenefitSystem()`. Create a fresh `Simulation` for each independent case; sharing baseline policy must not share mutable simulation state. Never modify the shared baseline's parameters or variable definitions. Keep dedicated systems where construction, mutation, reform, or cache/isolation behavior requires them, and explain that need in a code comment or the PR.

## Partner API Contract Tests

Files under `policyengine_us/tests/policy/baseline/partners/**` are API partner contract tests.
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ CI time and memory are part of test design. Full model construction, parameter-t

- **Use YAML for policy calculations.** Add household examples, thresholds, edge cases, and mixed-household/vectorized cases to the corresponding variable-named YAML files. Do not add a Python test when the existing YAML framework can exercise the same behavior. Avoid generic policy test files such as `regression.yaml` or `vectorized.yaml`.
- **Justify Python coverage.** Use Python for behavior YAML cannot exercise, such as Core integration, data compatibility, simulation APIs, cache/mutation isolation, or tooling. Search existing tests first and explain the distinct failure the new test detects. Do not duplicate a policy formula in Python merely to test it against itself; retain independent reference calculations when they provide meaningful invariant or compatibility coverage.
- **Minimize expensive setup safely.** Use the smallest population, dataset, and set of years that exercise the behavior. Reuse read-only reference models or fixtures where isolation permits. Do not share mutable simulations, reforms, parameter trees, or caches across cases that need independent state, and do not repeatedly construct a full model just to read parameters.
- **Minimize expensive setup safely.** Use the smallest population, dataset, and set of years that exercise the behavior.
- **Reuse the already-loaded baseline.** For read-only parameter lookups and baseline simulations in Python tests, use `from policyengine_us.system import system`. Read `system.parameters(...)` or pass `tax_benefit_system=system` to a fresh `Simulation`. Do not construct another `CountryTaxBenefitSystem()` solely to read baseline parameters or run an ordinary baseline simulation, including in a module-scoped fixture.
- **Keep individual simulations independent.** Create a fresh `Simulation` for each independent case, with separate inputs, calculated arrays, caches, and branches. Never modify the shared baseline's parameters or variable definitions, and do not share mutable reforms or parameter trees across cases. Keep dedicated systems for constructor/import checks and tests whose mutation, reform, or cache/isolation behavior requires independent model state; explain the reason in a code comment or the PR. Do not replace those systems with the shared baseline merely to reduce setup time.
- **Measure simulation-heavy additions.** For new or expanded Python tests that build models, copy policy trees, or load datasets, report before/after elapsed time (including setup) and peak memory where available. Identify the affected CI group and check its existing timing/memory reports. Label local measurements separately from Linux CI results; if resource measurements are unavailable, say so rather than claiming the addition is cheap. Use existing CI artifacts instead of triggering extra full runs solely to benchmark.
- **Keep the existing runner budget.** Do not add jobs, matrix entries, or concurrent heavy processes to absorb test growth without explicit authorization. Assign tests by purpose: simulation/data compatibility belongs with Microsimulation, policy cases with their policy suites, and tooling with code-health checks. Check the Makefile and workflow selectors so moving a file does not omit coverage or accidentally duplicate it within the full suite; selective feedback may intentionally overlap.
- **Preserve memory isolation.** Keep heavy groups in separate, sequential processes on the existing runner. Passing individually does not establish that groups fit together in one process or in parallel. Leave headroom for the runner and future growth; never treat a run just below the memory limit as a safe budget.
Expand Down
1 change: 1 addition & 0 deletions changelog.d/trim-rest-test-cost.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reduce repeated baseline setup and YAML scanning in Rest tests, remove duplicate checks, and move ordinary Medicare and self-employment policy cases from Python into YAML while retaining Python API and calculation-order coverage.
15 changes: 0 additions & 15 deletions policyengine_us/tests/code_health/test_batched_exit_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,6 @@ def test_a_child_killed_before_reporting_is_not_credited_a_pass():
assert result["status"] == "failed"


def test_the_shim_reports_the_real_runner_status():
"""End to end against policyengine-core, not a scripted stand-in.

An empty directory collects nothing, so pytest exits 5 - a status the
summary line never carries and the runner now reports.
"""
import tempfile

with tempfile.TemporaryDirectory() as empty:
result = batched.run_batch([empty], "Batch 1", stream=False)
assert batched.BATCH_EXIT_MARKER in result["output"]
assert result["returncode"] == batched.PYTEST_NO_TESTS_COLLECTED
assert result["status"] == "failed"


def test_a_child_that_never_prints_a_summary_is_judged_by_its_exit_status():
"""Crashes before the summary line (OOM kill, import error) still count."""
crashing = [sys.executable, "-u", "-c", "import sys; sys.exit(1)"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def test_directory_batch_collects_yaml_only(small_country, monkeypatch):

def test_python_only_directory_is_not_a_passing_yaml_batch(small_country):
result = batched.run_batch([str(small_country)], "no YAML", stream=False)
assert batched.BATCH_EXIT_MARKER in result["output"]
assert result["returncode"] == batched.PYTEST_NO_TESTS_COLLECTED, result["output"]
assert result["status"] == "failed"
assert not (small_country.parent / "python-imported").exists()
Expand Down
7 changes: 5 additions & 2 deletions policyengine_us/tests/code_health/test_uprating_placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
PARAMETERS_ROOT = REPO / "parameters"


class _NoTimestampSafeLoader(yaml.SafeLoader):
SafeLoader = getattr(yaml, "CSafeLoader", yaml.SafeLoader)


class _NoTimestampSafeLoader(SafeLoader):
"""`yaml.SafeLoader` with the implicit timestamp resolver disabled.

Several parameter files use `0000-01-01` as a "since the beginning of
Expand All @@ -25,7 +28,7 @@ class _NoTimestampSafeLoader(yaml.SafeLoader):
for tag, regexp in resolvers
if tag != "tag:yaml.org,2002:timestamp"
]
for key, resolvers in yaml.SafeLoader.yaml_implicit_resolvers.items()
for key, resolvers in SafeLoader.yaml_implicit_resolvers.items()
}


Expand Down
32 changes: 1 addition & 31 deletions policyengine_us/tests/core/test_business_income_regressions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import pytest
"""Country Simulation input remapping is a Python API contract."""

from policyengine_us import Simulation

Expand Down Expand Up @@ -34,33 +34,3 @@ def test_simulation_moves_sstb_self_employment_input_before_lsr():
== 10_000
)
assert simulation.calculate("sstb_self_employment_income", 2024)[0] == 10_000


def test_taxable_self_employment_income_uses_farm_operations_income():
simulation = Simulation(
situation={
"people": {
"person": {
"farm_operations_income": 10_000,
},
},
},
)

assert simulation.calculate("taxable_self_employment_income", 2024)[
0
] == pytest.approx(9_235)


def test_taxable_self_employment_income_excludes_schedule_j_farm_income():
simulation = Simulation(
situation={
"people": {
"person": {
"farm_income": 10_000,
},
},
},
)

assert simulation.calculate("taxable_self_employment_income", 2024)[0] == 0
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
import numpy as np
import pytest

from policyengine_us import CountryTaxBenefitSystem, Simulation
from policyengine_us import Simulation
from policyengine_us.system import system

YEARS = [2024, 2025]
N_RANDOM = 300
Expand Down Expand Up @@ -178,9 +179,9 @@ def units():

@pytest.fixture(scope="module")
def reference_parameters():
# The reference calculations only read policy. Build an independent model
# once per module instead of rebuilding it for each assertion and year.
return CountryTaxBenefitSystem().parameters
# Read the existing baseline policy; each comparison still builds its
# own simulation with independent inputs and cached calculations.
return system.parameters


@pytest.fixture(scope="module", params=YEARS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
import numpy as np
import pytest

from policyengine_us import CountryTaxBenefitSystem, Simulation
from policyengine_us import Simulation
from policyengine_us.system import system

YEARS = [2024, 2025]
N_RANDOM = 300
Expand Down Expand Up @@ -219,9 +220,9 @@ def units():

@pytest.fixture(scope="module")
def reference_parameters():
# The reference calculations only read policy. Build an independent model
# once per module instead of rebuilding it for each assertion and year.
return CountryTaxBenefitSystem().parameters
# Read the existing baseline policy; each comparison still builds its
# own simulation with independent inputs and cached calculations.
return system.parameters


@pytest.fixture(scope="module", params=YEARS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@
not generated here.
"""

from functools import cache

import numpy as np
import pytest

from policyengine_us import CountryTaxBenefitSystem, Simulation
from policyengine_us import Simulation
from policyengine_us.system import system
from policyengine_core.reforms import Reform

N = 600
Expand Down Expand Up @@ -161,13 +160,8 @@ def _worksheet_amounts(year, statuses):
)


@cache
def _system():
return CountryTaxBenefitSystem()


def _parameter_amounts(year, statuses):
p = _system().parameters(f"{year}-01-01").gov.irs.deductions.itemized.limitation
p = system.parameters(f"{year}-01-01").gov.irs.deductions.itemized.limitation
return np.asarray(p.applicable_amount[np.asarray(statuses)])


Expand Down
34 changes: 0 additions & 34 deletions policyengine_us/tests/core/test_ssi_disability_criteria_inputs.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,51 @@
gross_medicare_part_b_premium: 3_000
output:
medicare_cost: 11_500 # $14,500 spending - $3,000 Part B premium

# Policy examples moved from Python; retain their original inputs and outputs.
- name: Medicare cost uses gross part b before msp offset
period: 2025
relative_error_margin: 1.0e-06
input:
age:
'2025': 65
is_medicare_eligible:
'2025': true
medicare_enrolled:
'2025': true
medicare_quarters_of_coverage:
'2025': 40
gross_medicare_part_b_premium:
'2025': 2220
base_part_b_premium:
'2025': 2220
msp_income_eligible:
2025-01: true
msp_asset_eligible:
2025-01: true
output:
medicare_part_b_premium: 0
medicare_cost: 12280
- name: Gross medicare part b premium if enrolled preserves irmaa
period: 2025
relative_error_margin: 1.0e-06
input:
age:
'2025': 65
is_medicare_eligible:
'2025': true
medicare_enrolled:
'2025': true
medicare_quarters_of_coverage:
'2025': 40
gross_medicare_part_b_premium:
'2025': 4440
base_part_b_premium:
'2025': 2220
msp_income_eligible:
2025-01: false
msp_asset_eligible:
2025-01: false
output:
gross_medicare_part_b_premium_if_enrolled: 4440
medicare_cost: 10060
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Policy examples moved from Python; retain their original inputs and outputs.
- name: Gross medicare part b premium handles direct filing status inputs
period: 2025
relative_error_margin: 1.0e-06
input:
people:
person_1:
age:
'2025': 65
base_part_b_premium:
'2025': 2220
is_medicare_eligible:
'2025': true
tax_exempt_interest_income:
'2023': 0
person_2:
age:
'2025': 65
base_part_b_premium:
'2025': 2220
is_medicare_eligible:
'2025': true
tax_exempt_interest_income:
'2023': 0
person_3:
age:
'2025': 65
base_part_b_premium:
'2025': 2220
is_medicare_eligible:
'2025': true
tax_exempt_interest_income:
'2023': 0
households:
household:
members:
- person_1
- person_2
- person_3
tax_units:
joint_tax_unit:
members:
- person_1
- person_2
filing_status:
'2025': JOINT
adjusted_gross_income:
'2023': 1000000
single_tax_unit:
members:
- person_3
filing_status:
'2025': SINGLE
adjusted_gross_income:
'2023': 50000
spm_units:
spm_unit:
members:
- person_1
- person_2
- person_3
families:
family:
members:
- person_1
- person_2
- person_3
marital_units:
marital_unit_1:
members:
- person_1
- person_2
marital_unit_2:
members:
- person_3
output:
gross_medicare_part_b_premium:
- 7546.8
- 7546.8
- 2220
Loading
Loading