From 5f3ce45735dc236e521b470780bebe16dadfaab8 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 8 Oct 2026 00:42:44 -0400 Subject: [PATCH 1/3] Reduce redundant setup and scans in Rest tests --- changelog.d/trim-rest-test-cost.fixed.md | 1 + .../code_health/test_batched_exit_status.py | 15 --------------- .../test_batched_yaml_collection.py | 1 + .../code_health/test_uprating_placement.py | 7 +++++-- .../test_dependent_losses_agi_invariants.py | 9 +++++---- ...endent_net_investment_income_invariants.py | 9 +++++---- ...temized_deduction_limitation_properties.py | 12 +++--------- .../medicare/test_irmaa_first_modeled_year.py | 4 ++-- .../hhs/medicare/test_part_b_msp_offset.py | 4 ++-- .../test_ky_non_refundable_credit_order.py | 4 ++-- .../geographic/test_three_digit_zip_code.py | 5 ++--- policyengine_us/tests/test_batched.py | 4 +++- policyengine_us/tests/test_parameter_files.py | 19 +++++-------------- .../unit/test_spm_measurement_boundary.py | 4 +++- 14 files changed, 39 insertions(+), 59 deletions(-) create mode 100644 changelog.d/trim-rest-test-cost.fixed.md diff --git a/changelog.d/trim-rest-test-cost.fixed.md b/changelog.d/trim-rest-test-cost.fixed.md new file mode 100644 index 00000000000..f7ba8c42f5f --- /dev/null +++ b/changelog.d/trim-rest-test-cost.fixed.md @@ -0,0 +1 @@ +Reduce repeated baseline setup in Rest tests, remove duplicate runner and YAML syntax checks, and accelerate test YAML scans. diff --git a/policyengine_us/tests/code_health/test_batched_exit_status.py b/policyengine_us/tests/code_health/test_batched_exit_status.py index 10d5421f9c8..98605c4b558 100644 --- a/policyengine_us/tests/code_health/test_batched_exit_status.py +++ b/policyengine_us/tests/code_health/test_batched_exit_status.py @@ -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)"] diff --git a/policyengine_us/tests/code_health/test_batched_yaml_collection.py b/policyengine_us/tests/code_health/test_batched_yaml_collection.py index d2e6d20234c..e17b82b4fa4 100644 --- a/policyengine_us/tests/code_health/test_batched_yaml_collection.py +++ b/policyengine_us/tests/code_health/test_batched_yaml_collection.py @@ -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() diff --git a/policyengine_us/tests/code_health/test_uprating_placement.py b/policyengine_us/tests/code_health/test_uprating_placement.py index 5b82e900d1d..a87c173147a 100644 --- a/policyengine_us/tests/code_health/test_uprating_placement.py +++ b/policyengine_us/tests/code_health/test_uprating_placement.py @@ -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 @@ -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() } diff --git a/policyengine_us/tests/core/test_dependent_losses_agi_invariants.py b/policyengine_us/tests/core/test_dependent_losses_agi_invariants.py index 432e36b895f..6e982a9cf92 100644 --- a/policyengine_us/tests/core/test_dependent_losses_agi_invariants.py +++ b/policyengine_us/tests/core/test_dependent_losses_agi_invariants.py @@ -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 @@ -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) diff --git a/policyengine_us/tests/core/test_dependent_net_investment_income_invariants.py b/policyengine_us/tests/core/test_dependent_net_investment_income_invariants.py index f9c54139074..470b6e9d2b3 100644 --- a/policyengine_us/tests/core/test_dependent_net_investment_income_invariants.py +++ b/policyengine_us/tests/core/test_dependent_net_investment_income_invariants.py @@ -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 @@ -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) diff --git a/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py b/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py index 36c428c7094..22cf958366e 100644 --- a/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py +++ b/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py @@ -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 @@ -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)]) diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py index 3c3c41f93f9..07dacbee173 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py @@ -7,10 +7,10 @@ import pytest -from policyengine_us import CountryTaxBenefitSystem, Simulation +from policyengine_us import Simulation +from policyengine_us.system import system as SYSTEM from policyengine_us.tools.parameters import FIRST_MODELED_YEAR -SYSTEM = CountryTaxBenefitSystem() FILING_STATUSES = ["SINGLE", "JOINT", "SEPARATE", "HEAD_OF_HOUSEHOLD"] INCOMES = [0, 30_000, 150_000, 500_000] diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py index 611c0bf7592..ece233bf56c 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py @@ -1,9 +1,9 @@ import pytest -from policyengine_us import CountryTaxBenefitSystem, Simulation +from policyengine_us import Simulation +from policyengine_us.system import system as SYSTEM -SYSTEM = CountryTaxBenefitSystem() PERIOD = "2025" diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/test_ky_non_refundable_credit_order.py b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/test_ky_non_refundable_credit_order.py index 420166510e3..6b5e11aa9c9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/test_ky_non_refundable_credit_order.py +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/test_ky_non_refundable_credit_order.py @@ -10,7 +10,7 @@ the divergence is caught in CI. """ -from policyengine_us import CountryTaxBenefitSystem +from policyengine_us.system import system EXPECTED_2022_ORDER = [ "ky_personal_tax_credits", @@ -21,7 +21,7 @@ def test_ky_non_refundable_credit_order_2022(): - parameters = CountryTaxBenefitSystem().parameters + parameters = system.parameters credit_list = parameters.gov.states.ky.tax.income.credits.non_refundable( "2022-01-01" ) diff --git a/policyengine_us/tests/policy/baseline/household/demographic/geographic/test_three_digit_zip_code.py b/policyengine_us/tests/policy/baseline/household/demographic/geographic/test_three_digit_zip_code.py index dff9b01a7d9..a07a9429103 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/geographic/test_three_digit_zip_code.py +++ b/policyengine_us/tests/policy/baseline/household/demographic/geographic/test_three_digit_zip_code.py @@ -4,9 +4,8 @@ which removed the formula, breaking ACA PTC for LA County households. """ -from policyengine_us import CountryTaxBenefitSystem, Simulation - -system = CountryTaxBenefitSystem() +from policyengine_us import Simulation +from policyengine_us.system import system def _make_sim(zip_code): diff --git a/policyengine_us/tests/test_batched.py b/policyengine_us/tests/test_batched.py index 73f9e510640..c097a10c743 100644 --- a/policyengine_us/tests/test_batched.py +++ b/policyengine_us/tests/test_batched.py @@ -20,6 +20,8 @@ import yaml +SafeLoader = getattr(yaml, "CSafeLoader", yaml.SafeLoader) + # policyengine-core collects both YAML suffixes # (policyengine_core/tools/test_runner.py accepts ".yaml" and ".yml"), so any @@ -95,7 +97,7 @@ def file_reform_combos(yaml_file: Path) -> FrozenSet[ComboKey]: surfaces any genuine syntax error. """ try: - cases = yaml.safe_load(yaml_file.read_text()) + cases = yaml.load(yaml_file.read_text(), Loader=SafeLoader) except (OSError, yaml.YAMLError): return frozenset() if not isinstance(cases, list): diff --git a/policyengine_us/tests/test_parameter_files.py b/policyengine_us/tests/test_parameter_files.py index 06f47a58368..eb2b6def166 100644 --- a/policyengine_us/tests/test_parameter_files.py +++ b/policyengine_us/tests/test_parameter_files.py @@ -44,7 +44,10 @@ } -class NoDatesSafeLoader(yaml.SafeLoader): +SafeLoader = getattr(yaml, "CSafeLoader", yaml.SafeLoader) + + +class NoDatesSafeLoader(SafeLoader): pass @@ -54,7 +57,7 @@ class NoDatesSafeLoader(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() } @@ -87,18 +90,6 @@ def _enum_breakdown_parameter_errors(path, data): return [] -def test_parameter_yaml_files_are_syntax_parseable(): - errors = [] - - for path in sorted(PARAMETERS_DIR.rglob("*.yaml")): - try: - yaml.compose(path.read_text()) - except yaml.YAMLError as exc: - errors.append(f"{path.relative_to(PARAMETERS_DIR.parent)}: {exc}") - - assert errors == [] - - def test_calworks_yaml_fixes_preserve_effective_dates(): max_au_size = yaml.safe_load( ( diff --git a/policyengine_us/tests/unit/test_spm_measurement_boundary.py b/policyengine_us/tests/unit/test_spm_measurement_boundary.py index 2c75172b353..ee53ebb9973 100644 --- a/policyengine_us/tests/unit/test_spm_measurement_boundary.py +++ b/policyengine_us/tests/unit/test_spm_measurement_boundary.py @@ -5,6 +5,8 @@ import yaml +BaseLoader = getattr(yaml, "CBaseLoader", yaml.BaseLoader) + def test_spm_caps_have_only_spm_resource_consumers(): root = Path(__file__).resolve().parents[2] @@ -96,7 +98,7 @@ def scalar_values(value): # Include nested values in brackets/breakdowns. BaseLoader preserves # year-zero date keys without constructing unsupported datetime objects. for path in (root / "parameters").rglob("*.yaml"): - parameter = yaml.load(path.read_text(), Loader=yaml.BaseLoader) + parameter = yaml.load(path.read_text(), Loader=BaseLoader) for value in scalar_values(parameter): if value in consumers: violations.append(f"{path.relative_to(root)} names {value}") From 330ae230c5d6615e7cc7b0d5f30642da18107f7b Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 8 Oct 2026 01:40:57 -0400 Subject: [PATCH 2/3] Document baseline reuse and simulation isolation in Python tests --- AGENTS.md | 2 ++ CLAUDE.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 4cb191a92ba..26150d44e8a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/CLAUDE.md b/CLAUDE.md index de813eb790d..7046202ba02 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. From 25ac672f8c3e8ff421fc435f8eb29e194edd1e48 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 8 Oct 2026 02:02:34 -0400 Subject: [PATCH 3/3] Remove duplicate Python tests and move policy examples to YAML --- changelog.d/trim-rest-test-cost.fixed.md | 2 +- .../core/test_business_income_regressions.py | 32 +- .../test_ssi_disability_criteria_inputs.py | 34 -- .../gov/hhs/medicare/costs/medicare_cost.yaml | 48 +++ .../gross_medicare_part_b_premium.yaml | 80 ++++ .../medicare_irmaa_magi_two_years_prior.yaml | 42 ++ .../eligibility/medicare_part_b_premium.yaml | 49 +++ .../eligibility/msp_asset_eligible.yaml | 86 ++++ .../msp_part_a_premium_coverage.yaml | 30 ++ .../msp_part_b_premium_coverage.yaml | 64 +++ .../hhs/medicare/test_part_b_msp_offset.py | 377 +----------------- .../taxable_self_employment_income.yaml | 16 + 12 files changed, 419 insertions(+), 441 deletions(-) delete mode 100644 policyengine_us/tests/core/test_ssi_disability_criteria_inputs.py create mode 100644 policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/gross_medicare_part_b_premium.yaml diff --git a/changelog.d/trim-rest-test-cost.fixed.md b/changelog.d/trim-rest-test-cost.fixed.md index f7ba8c42f5f..c545f650550 100644 --- a/changelog.d/trim-rest-test-cost.fixed.md +++ b/changelog.d/trim-rest-test-cost.fixed.md @@ -1 +1 @@ -Reduce repeated baseline setup in Rest tests, remove duplicate runner and YAML syntax checks, and accelerate test YAML scans. +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. diff --git a/policyengine_us/tests/core/test_business_income_regressions.py b/policyengine_us/tests/core/test_business_income_regressions.py index 0edd081739f..a5ae8e481a4 100644 --- a/policyengine_us/tests/core/test_business_income_regressions.py +++ b/policyengine_us/tests/core/test_business_income_regressions.py @@ -1,4 +1,4 @@ -import pytest +"""Country Simulation input remapping is a Python API contract.""" from policyengine_us import Simulation @@ -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 diff --git a/policyengine_us/tests/core/test_ssi_disability_criteria_inputs.py b/policyengine_us/tests/core/test_ssi_disability_criteria_inputs.py deleted file mode 100644 index 1813ef8983a..00000000000 --- a/policyengine_us/tests/core/test_ssi_disability_criteria_inputs.py +++ /dev/null @@ -1,34 +0,0 @@ -from policyengine_us import Simulation - - -def test_data_provided_ssi_disability_criteria_is_used_directly(): - simulation = Simulation( - situation={ - "people": { - "person1": { - "meets_ssi_disability_criteria": {2024: True}, - "is_disabled": {2024: False}, - } - } - } - ) - - result = simulation.calculate("meets_ssi_disability_criteria", 2024) - - assert result[0] - - -def test_broad_disability_does_not_populate_ssi_disability_criteria(): - simulation = Simulation( - situation={ - "people": { - "person1": { - "is_disabled": {2024: True}, - } - } - } - ) - - result = simulation.calculate("meets_ssi_disability_criteria", 2024) - - assert not result[0] diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml index 5cb855c4319..f0d44359919 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml @@ -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 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/gross_medicare_part_b_premium.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/gross_medicare_part_b_premium.yaml new file mode 100644 index 00000000000..f5ce8ab5f78 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/gross_medicare_part_b_premium.yaml @@ -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 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml index 4fb58ad3f70..6fe7b79e958 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml @@ -282,3 +282,45 @@ members: [parent, child] output: medicare_irmaa_magi_two_years_prior: 105_000 + +# Policy examples moved from Python; retain their original inputs and outputs. +- name: Medicare irmaa magi two years prior falls back to lagged income + period: 2025 + relative_error_margin: 1.0e-06 + input: + age: + '2025': 65 + base_part_b_premium: + '2025': 2220 + is_medicare_eligible: + '2025': true + tax_exempt_interest_income: + '2023': 6002 + filing_status: + '2025': SINGLE + adjusted_gross_income: + '2023': 100000 + output: + medicare_irmaa_magi_two_years_prior: 106002 + gross_medicare_part_b_premium: 3108 +- name: Medicare irmaa magi two years prior input drives irmaa + period: 2025 + relative_error_margin: 1.0e-06 + input: + age: + '2025': 65 + base_part_b_premium: + '2025': 2220 + is_medicare_eligible: + '2025': true + tax_exempt_interest_income: + '2023': 0 + filing_status: + '2025': SINGLE + adjusted_gross_income: + '2023': 50000 + medicare_irmaa_magi_two_years_prior: + '2025': 500000 + output: + medicare_irmaa_magi_two_years_prior: 500000 + gross_medicare_part_b_premium: 7546.8 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml index 5ad0e7b72d0..7159831e9c7 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml @@ -193,3 +193,52 @@ msp_asset_eligible: false output: medicare_part_b_premium: 8_278.8 + +# Policy examples moved from Python; retain their original inputs and outputs. +- name: Medicare part b premium preserves only irmaa above msp support + 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: true + msp_asset_eligible: + 2025-01: true + output: + medicare_part_b_premium: 2220 +- name: Medicare part b premium is zero when not enrolled + period: 2025 + relative_error_margin: 1.0e-06 + input: + age: + '2025': 65 + is_medicare_eligible: + '2025': true + medicare_enrolled: + '2025': false + 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: + msp_part_b_premium_coverage: 0 + medicare_part_b_premium: 0 + gross_medicare_part_b_premium: 2220 + gross_medicare_part_b_premium_if_enrolled: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/eligibility/msp_asset_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/eligibility/msp_asset_eligible.yaml index 6114fffe57d..d7cc83614b4 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/eligibility/msp_asset_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/eligibility/msp_asset_eligible.yaml @@ -103,3 +103,89 @@ state_code: MA output: msp_asset_eligible: true + +# Policy examples moved from Python; retain their original inputs and outputs. +- name: Historical msp asset eligibility uses federal default + period: 2014-01 + absolute_error_margin: 0 + input: + ssi_countable_resources: + '2014': 0 + state_code: AR + output: + msp_asset_eligible: true +- name: Historical msp asset limits before 2013 + period: 2012-01 + absolute_error_margin: 0 + input: + people: + below_limit: + ssi_countable_resources: + '2012': 6940 + above_limit: + ssi_countable_resources: + '2012': 7000 + spouse_1: + ssi_countable_resources: + '2012': 5000 + spouse_2: + ssi_countable_resources: + '2012': 5500 + households: + household: + members: + - below_limit + - above_limit + - spouse_1 + - spouse_2 + state_code: AR + tax_units: + tax_unit_1: + members: + - below_limit + tax_unit_2: + members: + - above_limit + tax_unit_3: + members: + - spouse_1 + - spouse_2 + spm_units: + spm_unit_1: + members: + - below_limit + spm_unit_2: + members: + - above_limit + spm_unit_3: + members: + - spouse_1 + - spouse_2 + families: + family_1: + members: + - below_limit + family_2: + members: + - above_limit + family_3: + members: + - spouse_1 + - spouse_2 + marital_units: + marital_unit_1: + members: + - below_limit + marital_unit_2: + members: + - above_limit + marital_unit_3: + members: + - spouse_1 + - spouse_2 + output: + msp_asset_eligible: + - true + - false + - false + - false diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml index 6a7ece615b0..f480187019c 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml @@ -24,3 +24,33 @@ base_part_a_premium: 6_780 output: msp_part_a_premium_coverage: 0 + +# Policy examples moved from Python; retain their original inputs and outputs. +- name: Msp part a premium coverage scales with qmb eligible months + period: 2025 + absolute_error_margin: 1.0e-06 + input: + age: + '2025': 65 + is_medicare_eligible: + '2025': true + medicare_enrolled: + '2025': true + base_part_a_premium: + '2025': 6216 + is_qmb_eligible: + 2025-01: true + 2025-02: true + 2025-03: true + 2025-04: false + 2025-05: false + 2025-06: false + 2025-07: false + 2025-08: false + 2025-09: false + 2025-10: false + 2025-11: false + 2025-12: false + output: + msp_part_a_premium_coverage: 1554 + medicare_part_a_premium: 4662 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml index 0c450945ee9..bcf8173920e 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml @@ -27,3 +27,67 @@ base_part_b_premium: 2_220 output: msp_part_b_premium_coverage: 0 + +# Policy examples moved from Python; retain their original inputs and outputs. +- name: Msp part b premium coverage pays standard premium + 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: true + msp_asset_eligible: + 2025-01: true + output: + msp_part_b_premium_coverage: 2220 +- name: Msp part b premium coverage scales with eligible months + period: 2025 + absolute_error_margin: 1.0e-06 + input: + age: + '2025': 65 + is_medicare_eligible: + '2025': true + medicare_enrolled: + '2025': true + base_part_b_premium: + '2025': 2220 + msp_income_eligible: + 2025-01: true + 2025-02: true + 2025-03: true + 2025-04: false + 2025-05: false + 2025-06: false + 2025-07: false + 2025-08: false + 2025-09: false + 2025-10: false + 2025-11: false + 2025-12: false + msp_asset_eligible: + 2025-01: true + 2025-02: true + 2025-03: true + 2025-04: false + 2025-05: false + 2025-06: false + 2025-07: false + 2025-08: false + 2025-09: false + 2025-10: false + 2025-11: false + 2025-12: false + output: + msp_part_b_premium_coverage: 555 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py index ece233bf56c..4010060afaf 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py @@ -1,244 +1,11 @@ +"""Keep calculation-order coverage in Python; policy examples live in YAML.""" + import pytest from policyengine_us import Simulation from policyengine_us.system import system as SYSTEM -PERIOD = "2025" - - -def make_simulation( - *, - medicare_enrolled: bool, - gross_part_b_premium: float, - base_part_b_premium: float, - msp_income_eligible: bool, - msp_asset_eligible: bool, -) -> Simulation: - return Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "person": { - "age": {PERIOD: 65}, - "is_medicare_eligible": {PERIOD: True}, - "medicare_enrolled": {PERIOD: medicare_enrolled}, - "medicare_quarters_of_coverage": {PERIOD: 40}, - "gross_medicare_part_b_premium": {PERIOD: gross_part_b_premium}, - "base_part_b_premium": {PERIOD: base_part_b_premium}, - "msp_income_eligible": {f"{PERIOD}-01": msp_income_eligible}, - "msp_asset_eligible": {f"{PERIOD}-01": msp_asset_eligible}, - } - }, - "households": {"household": {"members": ["person"]}}, - "tax_units": {"tax_unit": {"members": ["person"]}}, - "spm_units": {"spm_unit": {"members": ["person"]}}, - "families": {"family": {"members": ["person"]}}, - "marital_units": {"marital_unit": {"members": ["person"]}}, - }, - ) - - -def test_msp_part_b_premium_coverage_pays_standard_premium(): - sim = make_simulation( - medicare_enrolled=True, - gross_part_b_premium=4_440, - base_part_b_premium=2_220, - msp_income_eligible=True, - msp_asset_eligible=True, - ) - - assert sim.calculate("msp_part_b_premium_coverage", PERIOD)[0] == pytest.approx( - 2_220 - ) - - -def test_medicare_part_b_premium_preserves_only_irmaa_above_msp_support(): - sim = make_simulation( - medicare_enrolled=True, - gross_part_b_premium=4_440, - base_part_b_premium=2_220, - msp_income_eligible=True, - msp_asset_eligible=True, - ) - - assert sim.calculate("medicare_part_b_premium", PERIOD)[0] == pytest.approx(2_220) - - -def test_medicare_part_b_premium_is_zero_when_msp_covers_standard_premium(): - sim = make_simulation( - medicare_enrolled=True, - gross_part_b_premium=2_220, - base_part_b_premium=2_220, - msp_income_eligible=True, - msp_asset_eligible=True, - ) - - assert sim.calculate("medicare_part_b_premium", PERIOD)[0] == pytest.approx(0) - - -def test_medicare_cost_uses_gross_part_b_before_msp_offset(): - sim = make_simulation( - medicare_enrolled=True, - gross_part_b_premium=2_220, - base_part_b_premium=2_220, - msp_income_eligible=True, - msp_asset_eligible=True, - ) - - assert sim.calculate("medicare_part_b_premium", PERIOD)[0] == pytest.approx(0) - assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(12_280) - - -def test_medicare_part_b_premium_is_zero_when_not_enrolled(): - sim = make_simulation( - medicare_enrolled=False, - gross_part_b_premium=2_220, - base_part_b_premium=2_220, - msp_income_eligible=True, - msp_asset_eligible=True, - ) - - assert sim.calculate("msp_part_b_premium_coverage", PERIOD)[0] == pytest.approx(0) - assert sim.calculate("medicare_part_b_premium", PERIOD)[0] == pytest.approx(0) - assert sim.calculate("gross_medicare_part_b_premium", PERIOD)[0] == pytest.approx( - 2_220 - ) - assert sim.calculate("gross_medicare_part_b_premium_if_enrolled", PERIOD)[ - 0 - ] == pytest.approx(0) - - -def test_msp_part_b_premium_coverage_scales_with_eligible_months(): - monthly_eligibility = { - f"{PERIOD}-{month:02d}": month <= 3 for month in range(1, 13) - } - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "person": { - "age": {PERIOD: 65}, - "is_medicare_eligible": {PERIOD: True}, - "medicare_enrolled": {PERIOD: True}, - "base_part_b_premium": {PERIOD: 2_220}, - "msp_income_eligible": monthly_eligibility, - "msp_asset_eligible": monthly_eligibility, - } - }, - "households": {"household": {"members": ["person"]}}, - "tax_units": {"tax_unit": {"members": ["person"]}}, - "spm_units": {"spm_unit": {"members": ["person"]}}, - "families": {"family": {"members": ["person"]}}, - "marital_units": {"marital_unit": {"members": ["person"]}}, - }, - ) - - assert sim.calculate("msp_part_b_premium_coverage", PERIOD)[0] == pytest.approx( - 555, - abs=1e-6, - ) - - -def test_msp_part_a_premium_coverage_scales_with_qmb_eligible_months(): - monthly_qmb_eligibility = { - f"{PERIOD}-{month:02d}": month <= 3 for month in range(1, 13) - } - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "person": { - "age": {PERIOD: 65}, - "is_medicare_eligible": {PERIOD: True}, - "medicare_enrolled": {PERIOD: True}, - "base_part_a_premium": {PERIOD: 6_216}, - "is_qmb_eligible": monthly_qmb_eligibility, - } - }, - "households": {"household": {"members": ["person"]}}, - "tax_units": {"tax_unit": {"members": ["person"]}}, - "spm_units": {"spm_unit": {"members": ["person"]}}, - "families": {"family": {"members": ["person"]}}, - "marital_units": {"marital_unit": {"members": ["person"]}}, - }, - ) - - assert sim.calculate("msp_part_a_premium_coverage", PERIOD)[0] == pytest.approx( - 1_554, - abs=1e-6, - ) - assert sim.calculate("medicare_part_a_premium", PERIOD)[0] == pytest.approx(4_662) - - -def test_historical_msp_asset_eligibility_uses_federal_default(): - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": {"person": {"ssi_countable_resources": {"2014": 0}}}, - "households": {"household": {"members": ["person"], "state_code": "AR"}}, - "tax_units": {"tax_unit": {"members": ["person"]}}, - "spm_units": {"spm_unit": {"members": ["person"]}}, - "families": {"family": {"members": ["person"]}}, - "marital_units": {"marital_unit": {"members": ["person"]}}, - }, - ) - - assert sim.calculate("msp_asset_eligible", "2014-01")[0] - - -def test_historical_msp_asset_limits_before_2013(): - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "below_limit": {"ssi_countable_resources": {"2012": 6_940}}, - "above_limit": {"ssi_countable_resources": {"2012": 7_000}}, - "spouse_1": {"ssi_countable_resources": {"2012": 5_000}}, - "spouse_2": {"ssi_countable_resources": {"2012": 5_500}}, - }, - "households": { - "household": { - "members": [ - "below_limit", - "above_limit", - "spouse_1", - "spouse_2", - ], - "state_code": "AR", - } - }, - "tax_units": { - "tax_unit_1": {"members": ["below_limit"]}, - "tax_unit_2": {"members": ["above_limit"]}, - "tax_unit_3": {"members": ["spouse_1", "spouse_2"]}, - }, - "spm_units": { - "spm_unit_1": {"members": ["below_limit"]}, - "spm_unit_2": {"members": ["above_limit"]}, - "spm_unit_3": {"members": ["spouse_1", "spouse_2"]}, - }, - "families": { - "family_1": {"members": ["below_limit"]}, - "family_2": {"members": ["above_limit"]}, - "family_3": {"members": ["spouse_1", "spouse_2"]}, - }, - "marital_units": { - "marital_unit_1": {"members": ["below_limit"]}, - "marital_unit_2": {"members": ["above_limit"]}, - "marital_unit_3": {"members": ["spouse_1", "spouse_2"]}, - }, - }, - ) - - assert sim.calculate("msp_asset_eligible", "2012-01").tolist() == [ - True, - False, - False, - False, - ] - - def test_medicare_part_b_premium_does_not_depend_on_calculation_order(): no_msp_eligibility = { f"{year}-{month:02d}": False @@ -273,143 +40,3 @@ def test_medicare_part_b_premium_does_not_depend_on_calculation_order(): assert ordered_result == pytest.approx(fresh_result) assert ordered_result == pytest.approx(2_220) - - -def test_gross_medicare_part_b_premium_handles_direct_filing_status_inputs(): - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "person_1": { - "age": {PERIOD: 65}, - "base_part_b_premium": {PERIOD: 2_220}, - "is_medicare_eligible": {PERIOD: True}, - "tax_exempt_interest_income": {"2023": 0}, - }, - "person_2": { - "age": {PERIOD: 65}, - "base_part_b_premium": {PERIOD: 2_220}, - "is_medicare_eligible": {PERIOD: True}, - "tax_exempt_interest_income": {"2023": 0}, - }, - "person_3": { - "age": {PERIOD: 65}, - "base_part_b_premium": {PERIOD: 2_220}, - "is_medicare_eligible": {PERIOD: 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": {PERIOD: "JOINT"}, - "adjusted_gross_income": {"2023": 1_000_000}, - }, - "single_tax_unit": { - "members": ["person_3"], - "filing_status": {PERIOD: "SINGLE"}, - "adjusted_gross_income": {"2023": 50_000}, - }, - }, - "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"]}, - }, - }, - ) - - result = sim.calculate("gross_medicare_part_b_premium", PERIOD) - assert result[0] == pytest.approx(7_546.8) - assert result[1] == pytest.approx(7_546.8) - assert result[2] == pytest.approx(2_220) - - -def test_gross_medicare_part_b_premium_if_enrolled_preserves_irmaa(): - sim = make_simulation( - medicare_enrolled=True, - gross_part_b_premium=4_440, - base_part_b_premium=2_220, - msp_income_eligible=False, - msp_asset_eligible=False, - ) - - assert sim.calculate("gross_medicare_part_b_premium_if_enrolled", PERIOD)[ - 0 - ] == pytest.approx(4_440) - assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(10_060) - - -def test_medicare_irmaa_magi_two_years_prior_falls_back_to_lagged_income(): - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "person": { - "age": {PERIOD: 65}, - "base_part_b_premium": {PERIOD: 2_220}, - "is_medicare_eligible": {PERIOD: True}, - "tax_exempt_interest_income": {"2023": 6_002}, - } - }, - "households": {"household": {"members": ["person"]}}, - "tax_units": { - "tax_unit": { - "members": ["person"], - "filing_status": {PERIOD: "SINGLE"}, - "adjusted_gross_income": {"2023": 100_000}, - } - }, - "spm_units": {"spm_unit": {"members": ["person"]}}, - "families": {"family": {"members": ["person"]}}, - "marital_units": {"marital_unit": {"members": ["person"]}}, - }, - ) - - assert sim.calculate("medicare_irmaa_magi_two_years_prior", PERIOD)[ - 0 - ] == pytest.approx(106_002) - assert sim.calculate("gross_medicare_part_b_premium", PERIOD)[0] == pytest.approx( - 3_108 - ) - - -def test_medicare_irmaa_magi_two_years_prior_input_drives_irmaa(): - sim = Simulation( - tax_benefit_system=SYSTEM, - situation={ - "people": { - "person": { - "age": {PERIOD: 65}, - "base_part_b_premium": {PERIOD: 2_220}, - "is_medicare_eligible": {PERIOD: True}, - "tax_exempt_interest_income": {"2023": 0}, - } - }, - "households": {"household": {"members": ["person"]}}, - "tax_units": { - "tax_unit": { - "members": ["person"], - "filing_status": {PERIOD: "SINGLE"}, - "adjusted_gross_income": {"2023": 50_000}, - "medicare_irmaa_magi_two_years_prior": {PERIOD: 500_000}, - } - }, - "spm_units": {"spm_unit": {"members": ["person"]}}, - "families": {"family": {"members": ["person"]}}, - "marital_units": {"marital_unit": {"members": ["person"]}}, - }, - ) - - assert sim.calculate("medicare_irmaa_magi_two_years_prior", PERIOD)[ - 0 - ] == pytest.approx(500_000) - assert sim.calculate("gross_medicare_part_b_premium", PERIOD)[0] == pytest.approx( - 7_546.8 - ) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/self_employment/taxable_self_employment_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/self_employment/taxable_self_employment_income.yaml index 4c75316f922..abe5da67fc0 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/self_employment/taxable_self_employment_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/self_employment/taxable_self_employment_income.yaml @@ -56,3 +56,19 @@ # Both non-SSTB ($50k) and SSTB ($30k) Schedule C income are subject to SE tax. # 80_000 * (1 - 0.5 * 0.153) = 80_000 * 0.9235 = 73_880 taxable_self_employment_income: 73_880 + +# Policy examples moved from Python; retain their original inputs and outputs. +- name: Taxable self employment income uses farm operations income + period: 2024 + relative_error_margin: 1.0e-06 + input: + farm_operations_income: 10000 + output: + taxable_self_employment_income: 9235 +- name: Taxable self employment income excludes schedule j farm income + period: 2024 + absolute_error_margin: 0 + input: + farm_income: 10000 + output: + taxable_self_employment_income: 0