From d4cae008c99b6bf69ba9eeb4989d6ff140a0eb5c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 16:53:26 -0400 Subject: [PATCH 1/5] Scale Medicare per-enrollee spending to enrollees outside nursing homes per_capita_cost (CMS NHE Table 17) averages over every Part A and/or B enrollee, including nursing home residents, whom the CPS-based microdata exclude. Add calibration.gov.hhs.medicare.noninstitutionalized_spending_ratio (0.971, from MedPAC's 2022 fee-for-service spending for long-stay nursing home residents vs other beneficiaries and their 1.7% share) and apply it in medicare_gross_cost, medicare_cost, qmb_cost_sharing and, before premiums and after the Part D low-income subsidy, cbo_medicare_benefit. Co-Authored-By: Claude Opus 5.5 --- ...edicare-noninstitutional-spending.fixed.md | 1 + .../noninstitutionalized_spending_ratio.yaml | 48 +++++++++++++++++++ .../medicare/costs/cbo_medicare_benefit.py | 25 +++++----- .../gov/hhs/medicare/costs/medicare_cost.py | 15 +++--- .../hhs/medicare/costs/medicare_gross_cost.py | 13 +++-- .../savings_programs/qmb_cost_sharing.py | 7 ++- 6 files changed, 84 insertions(+), 25 deletions(-) create mode 100644 changelog.d/medicare-noninstitutional-spending.fixed.md create mode 100644 policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml diff --git a/changelog.d/medicare-noninstitutional-spending.fixed.md b/changelog.d/medicare-noninstitutional-spending.fixed.md new file mode 100644 index 00000000000..6d446f54b84 --- /dev/null +++ b/changelog.d/medicare-noninstitutional-spending.fixed.md @@ -0,0 +1 @@ +Scale Medicare spending per enrollee to enrollees who do not live in nursing homes in medicare_gross_cost, medicare_cost, cbo_medicare_benefit and qmb_cost_sharing, since the survey data exclude nursing home residents. diff --git a/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml b/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml new file mode 100644 index 00000000000..bbeaeec6434 --- /dev/null +++ b/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml @@ -0,0 +1,48 @@ +description: PolicyEngine multiplies Medicare spending per enrollee by this ratio of Medicare spending per enrollee who is not a long-stay nursing home resident to Medicare spending per enrollee, because the household survey data exclude people living in nursing homes. +metadata: + unit: /1 + period: year + label: Medicare noninstitutionalized spending ratio + reference: + # Medicare spending per enrollee (per_capita_cost) averages over every + # enrollee, including nursing home residents, whom the Current Population + # Survey does not sample. In 2022, among beneficiaries in fee-for-service + # Medicare, annual Part A and Part B program spending averaged $31,200 for + # long-stay nursing home residents and about $11,300 for other + # beneficiaries (June 2025, file page 9), and long-stay residents were 1.7 + # percent of beneficiaries (October 2024, file page 5; 1.7 percent again in + # 2023, June 2025, file page 8). A long-stay resident had been in a nursing + # home for more than 90 days in at least one month of the year. So spending + # per other beneficiary relative to spending per beneficiary is + # 1 / (1 + 0.017 * (31_200 / 11_300 - 1)) = 0.971. + # + # The ratio is measured for fee-for-service Parts A and B and applied to + # all Medicare spending per enrollee, including Medicare Advantage, Part D + # and administration. With fee-for-service enrollment shares instead (62 + # percent of long-stay residents, 53 percent of others, July 2023, file + # page 13) it is 0.966; pricing Medicare Advantage at the long-stay + # residents' risk score ratio (2.12 / 1.01, October 2024, file page 5) gives + # about 0.975. The long-stay share was 2.6 percent in 2013 (June 2025, file + # page 8), but relative spending is published for 2022 only, so the 2022 + # ratio applies to every year from 2013, the first year of per_capita_cost. + # + # Nursing homes are the facilities the survey excludes. The Medicare + # Current Beneficiary Survey's long-term care facilities also include + # assisted living and other residential care, whose residents the survey + # samples; excluding all of them gives a larger gap (community residents' + # benefit payments per capita were 0.91 to 0.93 of the average in + # 2006-2013, Table 4.1 over Table 1.1). + - title: Medicare Payment Advisory Commission, Report to the Congress, June 2025, Chapter 5, Medicare beneficiaries in nursing homes + href: https://www.medpac.gov/wp-content/uploads/2025/06/Jun25_Ch5_MedPAC_Report_To_Congress_SEC.pdf#page=9 + - title: Medicare Payment Advisory Commission, Medicare beneficiaries in nursing homes, October 2024 public meeting + href: https://www.medpac.gov/wp-content/uploads/2023/10/Beneficiaries-in-Nursing-Homes-MedPAC-Oct-2024-SEC.pdf#page=5 + - title: Centers for Medicare and Medicaid Services, Characteristics and Perceptions of the Medicare Population, 2013, Table 4.1 + href: https://www.cms.gov/research-statistics-data-and-systems/research/mcbs/downloads/2013_characteristics_and_perceptions.zip + # CBO likewise removes institutionalized enrollees' benefits before + # assigning Medicare's average cost to survey respondents (file page 46), + # with the institutional share held at its 2012 level from 2013 (file page + # 52). + - title: Congressional Budget Office, Working Paper 2018-07, How CBO Adjusts for Survey Underreporting of Transfer Income in Its Distributional Analyses + href: https://www.cbo.gov/system/files/2018-07/54234-workingpaper.pdf#page=46 +values: + 2013-01-01: 0.971 diff --git a/policyengine_us/variables/gov/hhs/medicare/costs/cbo_medicare_benefit.py b/policyengine_us/variables/gov/hhs/medicare/costs/cbo_medicare_benefit.py index 1a4e1d96df5..f7987a8b683 100644 --- a/policyengine_us/variables/gov/hhs/medicare/costs/cbo_medicare_benefit.py +++ b/policyengine_us/variables/gov/hhs/medicare/costs/cbo_medicare_benefit.py @@ -9,11 +9,13 @@ class cbo_medicare_benefit(Variable): "Medicare benefit as the Congressional Budget Office counts it in " "household income: the government's average cost per enrollee of " "providing Medicare Parts A, B and D, net of premiums paid by or on " - "behalf of enrollees. CBO assigns the same average to every " - "enrollee and counts the Part D low-income subsidy separately as a " - "means-tested transfer, so this subtracts per-enrollee averages of " - "premium income and of the low-income subsidy from Medicare " - "spending per enrollee, rather than the person's own premiums." + "behalf of enrollees, excluding costs for institutionalized " + "enrollees. CBO assigns the same average to every enrollee and " + "counts the Part D low-income subsidy separately as a means-tested " + "transfer, so this scales Medicare spending per enrollee net of the " + "low-income subsidy to enrollees who do not live in nursing homes, " + "then subtracts per-enrollee premium income, rather than the " + "person's own premiums." ) unit = USD definition_period = YEAR @@ -21,6 +23,7 @@ class cbo_medicare_benefit(Variable): "https://www.cbo.gov/publication/61910", "https://www.cbo.gov/system/files/2024-09/60341-income.pdf#page=27", "https://www.cbo.gov/system/files/2018-07/54234-workingpaper.pdf#page=46", + "https://www.cbo.gov/system/files/2018-07/54234-workingpaper.pdf#page=52", ) defined_for = "medicare_enrolled" @@ -28,9 +31,9 @@ def formula(person, period, parameters): p = parameters(period).calibration.gov.hhs.medicare premiums = p.premium_income_per_enrollee total_premiums = premiums.part_a + premiums.part_b + premiums.part_d - net = ( - p.per_capita_cost - - total_premiums - - p.part_d_low_income_subsidy_per_enrollee - ) - return max_(net, 0) + # CBO removes institutionalized enrollees' share of Medicare benefits + # other than the low-income subsidy, which it counts separately. + noninstitutionalized_spending = ( + p.per_capita_cost - p.part_d_low_income_subsidy_per_enrollee + ) * p.noninstitutionalized_spending_ratio + return max_(noninstitutionalized_spending - total_premiums, 0) diff --git a/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py b/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py index d2b7e45b123..6ed57bb6f04 100644 --- a/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py +++ b/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py @@ -6,11 +6,11 @@ class medicare_cost(Variable): entity = Person label = "Medicare cost" documentation = ( - "Annual value of Medicare benefits received. Calculated as total " - "Medicare spending on behalf of the beneficiary, which covers Parts A " - "through D, minus the premiums that finance it: the Part A premium, " - "the gross Part B premium and the expected Part D premium. " - "This represents the net benefit value of Medicare coverage." + "Annual value of Medicare benefits received. Calculated as Medicare " + "spending per enrollee who does not live in a nursing home, which " + "covers Parts A through D, minus the premiums that finance it: the " + "Part A premium, the gross Part B premium and the expected Part D " + "premium. This represents the net benefit value of Medicare coverage." ) unit = USD definition_period = YEAR @@ -22,8 +22,9 @@ class medicare_cost(Variable): def formula(person, period, parameters): p = parameters(period).calibration.gov.hhs.medicare - # Total Medicare spending on behalf of beneficiary - per_capita_spending = p.per_capita_cost + # Medicare spending on behalf of an enrollee who does not live in a + # nursing home, the population the household survey data cover. + per_capita_spending = p.per_capita_cost * p.noninstitutionalized_spending_ratio # Premium offsets to Medicare program cost. Use the enrollment-gated # gross Part B premium before MSP offsets so MSP support does not diff --git a/policyengine_us/variables/gov/hhs/medicare/costs/medicare_gross_cost.py b/policyengine_us/variables/gov/hhs/medicare/costs/medicare_gross_cost.py index 1baeeaf188a..ee250bebe94 100644 --- a/policyengine_us/variables/gov/hhs/medicare/costs/medicare_gross_cost.py +++ b/policyengine_us/variables/gov/hhs/medicare/costs/medicare_gross_cost.py @@ -9,10 +9,12 @@ class medicare_gross_cost(Variable): "Annual Medicare spending on behalf of the beneficiary before " "subtracting beneficiary premiums: CMS Medicare spending per " "enrollee, which covers Parts A through D, including the Part D " - "low-income subsidy. CBO's standard household income counts " - "Medicare net of premiums instead (cbo_medicare_benefit); CBO adds " - "the premiums back only in an expanded sensitivity measure, which " - "it calls gross Medicare spending." + "low-income subsidy, scaled to enrollees who do not live in nursing " + "homes, the population the household survey data cover. CBO's " + "standard household income counts Medicare net of premiums instead " + "(cbo_medicare_benefit); CBO adds the premiums back only in an " + "expanded sensitivity measure, which it calls gross Medicare " + "spending." ) unit = USD definition_period = YEAR @@ -23,4 +25,5 @@ class medicare_gross_cost(Variable): defined_for = "medicare_enrolled" def formula(person, period, parameters): - return parameters(period).calibration.gov.hhs.medicare.per_capita_cost + p = parameters(period).calibration.gov.hhs.medicare + return p.per_capita_cost * p.noninstitutionalized_spending_ratio diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/qmb_cost_sharing.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/qmb_cost_sharing.py index d8e70729ad9..2016834a1df 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/qmb_cost_sharing.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/qmb_cost_sharing.py @@ -18,14 +18,17 @@ class qmb_cost_sharing(Variable): "First-pass monthly value of QMB coverage for Medicare deductibles, " "coinsurance, and copayments. The model lacks beneficiary-level " "Medicare claims, so this approximates cost sharing as a parameterized " - "share of average Medicare per-capita spending." + "share of average Medicare spending per enrollee who does not live " + "in a nursing home." ) def formula(person, period, parameters): category = person("msp_category", period) p = parameters(period).gov.hhs.medicare.savings_programs + medicare = parameters(period).calibration.gov.hhs.medicare monthly_medicare_cost = ( - parameters(period).calibration.gov.hhs.medicare.per_capita_cost + medicare.per_capita_cost + * medicare.noninstitutionalized_spending_ratio / MONTHS_IN_YEAR ) return ( From 50c85ff60c8e1bb8c7f2d39511d9a2ba20806846 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 17:31:00 -0400 Subject: [PATCH 2/5] Test the noninstitutionalized Medicare spending ratio and its consumers New test_medicare_noninstitutionalized_spending_ratio.py: the stored value is the MedPAC formula, constant and below one from 2013; the documented fee-for-service and Medicare Advantage variants bracket it; the MCBS facility definition (2006-2013 Characteristics and Perceptions tables) gives a larger gap every year; and, across reformed ratios, every consumer matches a reference implementation, stays within [0, gross], does not fall when the ratio rises, and reproduces the unscaled formulas at a ratio of one. Hand-computed YAML expectations and the core consumer tests move to the scaled spending. Co-Authored-By: Claude Opus 5.5 --- .../core/test_cbo_medicare_benefit_series.py | 23 +- .../core/test_medicare_cost_part_d_premium.py | 14 +- ...are_noninstitutionalized_spending_ratio.py | 277 ++++++++++++++++++ .../core/test_medicare_per_capita_cost.py | 32 +- .../medicare/costs/cbo_medicare_benefit.yaml | 26 +- .../gov/hhs/medicare/costs/medicare_cost.yaml | 49 ++-- .../medicare/costs/medicare_gross_cost.yaml | 21 +- .../medicare/savings_programs/msp_cost.yaml | 4 +- .../msp_federal_and_state_cost.yaml | 6 +- .../savings_programs/qmb_cost_sharing.yaml | 5 +- .../hhs/medicare/test_part_b_msp_offset.py | 14 +- .../household/cbo_household_income.yaml | 21 +- 12 files changed, 407 insertions(+), 85 deletions(-) create mode 100644 policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py diff --git a/policyengine_us/tests/core/test_cbo_medicare_benefit_series.py b/policyengine_us/tests/core/test_cbo_medicare_benefit_series.py index c3da92714a1..385af0bc399 100644 --- a/policyengine_us/tests/core/test_cbo_medicare_benefit_series.py +++ b/policyengine_us/tests/core/test_cbo_medicare_benefit_series.py @@ -3,10 +3,12 @@ CBO counts Medicare in household income at the government's average cost per enrollee, net of premiums paid by enrollees, and counts the Part D low-income subsidy separately. ``cbo_medicare_benefit`` takes CMS Medicare -spending per enrollee (``calibration.gov.hhs.medicare.per_capita_cost``) and -subtracts four series under ``calibration.gov.hhs.medicare``, each an -aggregate from the 2026 Medicare Trustees Report over Table V.B3's total -enrollment (people with Part A and/or Part B): +spending per enrollee (``calibration.gov.hhs.medicare.per_capita_cost``) less +the low-income subsidy, scales it to enrollees outside nursing homes +(``noninstitutionalized_spending_ratio``), and subtracts the premium series. +All four series under ``calibration.gov.hhs.medicare`` are aggregates from +the 2026 Medicare Trustees Report over Table V.B3's total enrollment (people +with Part A and/or Part B): - ``premium_income_per_enrollee.part_a``: Table III.B4 premiums from voluntary enrollees. @@ -34,8 +36,10 @@ standard premium times Part B enrollment plus Table V.E3's aggregate income-related premiums reproduces it within 0.5 percent, while the standard premium alone falls more than 5 percent short. -5. Bounds: every series is positive, and net Medicare spending per enrollee - stays between 0 and gross spending, in every year from 2015 to 2200. +5. Bounds: every series is positive, and net Medicare spending per enrollee, + (spending - low-income subsidy) x the noninstitutionalized ratio - + premiums, stays between 0 and scaled gross spending, in every year from + 2015 to 2200. 6. Extension: from 2036 to 2100 each series grows with Medicare spending per enrollee, so each keeps its 2035 share of spending; later years hold the 2100 value. @@ -57,6 +61,7 @@ "lis": f"{MEDICARE}.part_d_low_income_subsidy_per_enrollee", } SPENDING = f"{MEDICARE}.per_capita_cost" +RATIO = f"{MEDICARE}.noninstitutionalized_spending_ratio" FIRST_YEAR, LAST_PROJECTED_YEAR, END_YEAR = 2015, 2035, 2100 YEARS = range(FIRST_YEAR, LAST_PROJECTED_YEAR + 1) @@ -193,9 +198,11 @@ def test_part_b_premium_income_includes_income_related_premiums(): @given(st.integers(FIRST_YEAR, END_YEAR + 100)) def test_series_are_positive_and_net_spending_is_between_zero_and_gross(year): - deductions = sum(_value(series, year) for series in SERIES) + ratio = _read(RATIO, f"{year}-01-01") + premiums = sum(_value(series, year) for series in SERIES if series != "lis") + net = (_spending(year) - _value("lis", year)) * ratio - premiums assert all(_value(series, year) > 0 for series in SERIES) - assert 0 < _spending(year) - deductions < _spending(year) + assert 0 < net < _spending(year) * ratio @pytest.mark.parametrize("series", SERIES) diff --git a/policyengine_us/tests/core/test_medicare_cost_part_d_premium.py b/policyengine_us/tests/core/test_medicare_cost_part_d_premium.py index a9dc443b21d..9ba27f78c89 100644 --- a/policyengine_us/tests/core/test_medicare_cost_part_d_premium.py +++ b/policyengine_us/tests/core/test_medicare_cost_part_d_premium.py @@ -1,9 +1,10 @@ """The expected Part D premium that ``medicare_cost`` subtracts. CMS Medicare spending per enrollee (``calibration.gov.hhs.medicare. -per_capita_cost``) covers Parts A through D, gross of the premiums that -finance it. No variable records Part D enrollment, so ``medicare_cost`` -subtracts an expected Part D premium: +per_capita_cost``, scaled by ``noninstitutionalized_spending_ratio`` to +enrollees outside nursing homes) covers Parts A through D, gross of the +premiums that finance it. No variable records Part D enrollment, so +``medicare_cost`` subtracts an expected Part D premium: enrollment_share x (premiums_per_enrollee + Part D IRMAA) @@ -35,7 +36,7 @@ the Trustees' five-year enrollment points the share holds the earlier one. 6. Consumer (Hypothesis, differential): ``medicare_cost`` equals a reference implementation, max(gross - Part A - Part B - share x (premium + IRMAA), - 0), for any year and premiums; it lies in [0, ``medicare_gross_cost``], + 0) with gross = per_capita_cost x the ratio, for any year and premiums; it lies in [0, ``medicare_gross_cost``], which does not change with the Part D premium; and it does not rise with the person's Part D IRMAA. """ @@ -226,7 +227,10 @@ def _enrollee(year, part_a, part_b, irmaa): def _reference_medicare_cost(year, part_a, part_b, irmaa): - gross = _read("calibration.gov.hhs.medicare.per_capita_cost", f"{year}-01-01") + date = f"{year}-01-01" + gross = _read("calibration.gov.hhs.medicare.per_capita_cost", date) * _read( + "calibration.gov.hhs.medicare.noninstitutionalized_spending_ratio", date + ) part_d = _share(year) * (_premium(year) + irmaa) return max(gross - part_a - part_b - part_d, 0) diff --git a/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py b/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py new file mode 100644 index 00000000000..5fac42ce69a --- /dev/null +++ b/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py @@ -0,0 +1,277 @@ +"""The ratio that scales Medicare spending per enrollee to enrollees outside +nursing homes. + +``calibration.gov.hhs.medicare.per_capita_cost`` averages Medicare spending +over every enrollee, including nursing home residents, whom the household +survey data do not sample. ``noninstitutionalized_spending_ratio`` is +spending per enrollee who is not a long-stay nursing home resident over +spending per enrollee, from MedPAC's 2022 fee-for-service Part A and B +spending ($31,200 for long-stay residents, about $11,300 for others; June +2025 report, chapter 5, file page 9) and the long-stay share of beneficiaries +(1.7 percent; October 2024 slides, file page 5): + + ratio = 1 / (1 + share x (long-stay spending / other spending - 1)) + +``medicare_gross_cost``, ``medicare_cost`` and ``qmb_cost_sharing`` multiply +``per_capita_cost`` by it; ``cbo_medicare_benefit`` multiplies spending less +the Part D low-income subsidy by it, then subtracts premium income per +enrollee. + +Invariants: + +1. Transcription: the stored value is the formula applied to MedPAC's + published figures, rounded to three decimals. +2. Bounds and constancy: the ratio is strictly between 0 and 1 and is the + same on every date from 2013, the first year of ``per_capita_cost``. +3. Sensitivity: the alternatives documented in the parameter file bracket + the stored value: fee-for-service enrollment shares (62 percent of + long-stay residents, 53 percent of others) give a lower ratio, and pricing + Medicare Advantage at the CMS-HCC risk score ratio (2.12 / 1.01) gives a + higher one. +4. Definition order: excluding every resident of a Medicare Current + Beneficiary Survey long-term care facility, which includes assisted living, + gives a lower ratio in every year of the published 2006-2013 tables + (community-only over all-beneficiary benefit payments per capita), so the + nursing home ratio is the smaller of the two corrections. +5. Consumers (Hypothesis, differential): for any year, any premiums and a + ratio set by a reform (0.5, 0.8, 0.95 or 1), each consumer equals a + reference implementation; + gross spending is proportional to the ratio; ``medicare_cost`` and + ``cbo_medicare_benefit`` lie in [0, ``medicare_gross_cost``] and never fall + when the ratio rises; with the ratio at 1 every consumer reproduces the + unscaled formulas. A person not enrolled gets zero. +""" + +from functools import lru_cache + +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st +from policyengine_core.parameters.operations.get_parameter import get_parameter +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation +from policyengine_us.system import CountryTaxBenefitSystem, system + +MEDICARE = "calibration.gov.hhs.medicare" +RATIO = f"{MEDICARE}.noninstitutionalized_spending_ratio" +FIRST_YEAR, END_YEAR = 2013, 2100 + +# MedPAC, June 2025 Report to the Congress, chapter 5 (file pages 8, 9 and +# 13), and October 2024 meeting slides (file page 5). +LONG_STAY_SPENDING, OTHER_SPENDING = 31_200, 11_300 +LONG_STAY_SHARE = 0.017 +LONG_STAY_FFS_SHARE, OTHER_FFS_SHARE = 0.62, 0.53 +LONG_STAY_RISK_SCORE, OTHER_RISK_SCORE = 2.12, 1.01 + +# MCBS Characteristics and Perceptions of the Medicare Population, all +# beneficiaries: Table 1.1 beneficiaries (thousands) and Table 4.1 benefit +# payments ($ millions), total and community only. +MCBS = { + 2006: (43_867, 41_599, 305_066, 267_344), + 2007: (45_046, 42_867, 307_372, 271_509), + 2008: (46_100, 43_906, 320_032, 282_887), + 2009: (47_371, 45_053, 335_081, 289_843), + 2010: (48_454, 46_270, 365_098, 322_150), + 2011: (50_186, 47_969, 374_252, 330_907), + 2012: (51_723, 49_816, 338_395, 299_144), + 2013: (53_574, 51_568, 352_943, 312_626), +} + + +def _ratio(share, relative): + return 1 / (1 + share * (relative - 1)) + + +def _read(name, date): + return get_parameter(system.parameters, name)(date) + + +def _stored(year): + return _read(RATIO, f"{year}-01-01") + + +def test_value_is_the_medpac_formula(): + expected = _ratio(LONG_STAY_SHARE, LONG_STAY_SPENDING / OTHER_SPENDING) + assert _stored(2022) == round(expected, 3) == 0.971 + + +@given(st.integers(FIRST_YEAR, END_YEAR + 100), st.integers(1, 12)) +def test_ratio_is_a_constant_share_below_one(year, month): + value = _read(RATIO, f"{year}-{month:02d}-01") + assert 0 < value < 1 + assert value == _stored(2022) + + +def test_documented_alternatives_bracket_the_value(): + relative = LONG_STAY_SPENDING / OTHER_SPENDING + ffs_share = ( + LONG_STAY_SHARE + * LONG_STAY_FFS_SHARE + / ( + LONG_STAY_SHARE * LONG_STAY_FFS_SHARE + + (1 - LONG_STAY_SHARE) * OTHER_FFS_SHARE + ) + ) + ffs_ratio = _ratio(ffs_share, relative) + # Price Medicare Advantage enrollees at the risk score ratio, with other + # beneficiaries' spending the same in both programs. + groups = { + "long_stay_ffs": (LONG_STAY_SHARE * LONG_STAY_FFS_SHARE, relative), + "long_stay_ma": ( + LONG_STAY_SHARE * (1 - LONG_STAY_FFS_SHARE), + LONG_STAY_RISK_SCORE / OTHER_RISK_SCORE, + ), + "other": (1 - LONG_STAY_SHARE, 1), + } + average = sum(count * cost for count, cost in groups.values()) + mixed_ratio = 1 / average + assert round(ffs_ratio, 3) == 0.966 + assert round(mixed_ratio, 3) == 0.975 + assert ffs_ratio < _stored(2022) < mixed_ratio + + +@pytest.mark.parametrize("year", MCBS) +def test_mcbs_facility_definition_gives_a_larger_gap(year): + total_people, community_people, total_pay, community_pay = MCBS[year] + mcbs_ratio = (community_pay / community_people) / (total_pay / total_people) + assert 0.9 < mcbs_ratio < _stored(2022) + + +# Reformed systems take seconds to build, so the property tests draw the +# ratio from a few values and build each system once. +RATIOS = (0.5, 0.8, 0.95, 1) + + +@lru_cache(maxsize=None) +def _system(ratio): + if ratio is None: + return system + reform = Reform.from_dict( + {RATIO: {f"{FIRST_YEAR}-01-01.{END_YEAR}-12-31": ratio}}, + country_id="us", + ) + return CountryTaxBenefitSystem(reform=reform) + + +def _simulation(year, people, ratio=None): + names = list(people) + return Simulation( + tax_benefit_system=_system(ratio), + situation={ + "people": people, + "tax_units": {f"tax_unit_{name}": {"members": [name]} for name in names}, + "spm_units": {"spm_unit": {"members": names}}, + "families": {"family": {"members": names}}, + "marital_units": { + f"marital_unit_{name}": {"members": [name]} for name in names + }, + "households": {"household": {"members": names, "state_code": {year: "CA"}}}, + }, + ) + + +def _person(year, enrolled, part_a=0, part_b=0): + return { + "age": {year: 70}, + "is_medicare_eligible": {year: True}, + "medicare_enrolled": {year: enrolled}, + "base_part_a_premium": {year: part_a}, + "gross_medicare_part_b_premium": {year: part_b}, + "income_adjusted_part_d_premium_surcharge": {year: 0}, + "msp_category": {f"{year}-01": "QMB"}, + } + + +def _reference(year, ratio, part_a, part_b): + p = system.parameters(f"{year}-01-01").calibration.gov.hhs.medicare + spending = p.per_capita_cost + premiums = p.premium_income_per_enrollee + part_d = p.part_d.enrollment_share * p.part_d.premiums_per_enrollee + rate = system.parameters( + f"{year}-01-01" + ).gov.hhs.medicare.savings_programs.qmb.cost_sharing_rate + return { + "medicare_gross_cost": spending * ratio, + "medicare_cost": max(spending * ratio - part_a - part_b - part_d, 0), + "cbo_medicare_benefit": max( + (spending - p.part_d_low_income_subsidy_per_enrollee) * ratio + - (premiums.part_a + premiums.part_b + premiums.part_d), + 0, + ), + "qmb_cost_sharing": spending * ratio / 12 * rate, + } + + +def _calculate(simulation, year): + return { + name: simulation.calculate( + name, f"{year}-01" if name == "qmb_cost_sharing" else year + ) + for name in ( + "medicare_gross_cost", + "medicare_cost", + "cbo_medicare_benefit", + "qmb_cost_sharing", + ) + } + + +premiums = st.floats(0, 30_000, allow_nan=False) + + +@settings(max_examples=12, deadline=None, suppress_health_check=[HealthCheck.too_slow]) +@given( + st.integers(2015, END_YEAR), + st.sampled_from(RATIOS), + st.sampled_from(RATIOS), + st.floats(0, 12_000, allow_nan=False), + premiums, +) +def test_consumers_match_reference_for_any_ratio(year, one, other, part_a, part_b): + low, high = sorted((one, other)) + results = {} + for value in (low, high): + simulation = _simulation( + year, + { + "enrolled": _person(year, True, part_a, part_b), + "not_enrolled": _person(year, False), + }, + ratio=value, + ) + results[value] = _calculate(simulation, year) + reference = _reference(year, value, part_a, part_b) + for name, values in results[value].items(): + # Variables are float32, so compare to single precision. + assert values[0] == pytest.approx(reference[name], rel=1e-6, abs=0.01) + if name != "qmb_cost_sharing": + assert values[1] == 0 + gross = results[value]["medicare_gross_cost"][0] + for name in ("medicare_cost", "cbo_medicare_benefit"): + assert 0 <= results[value][name][0] <= gross + for name in results[low]: + assert results[low][name][0] <= results[high][name][0] + 0.01 + + +@settings(max_examples=5, deadline=None, suppress_health_check=[HealthCheck.too_slow]) +@given(st.integers(2015, END_YEAR), premiums) +def test_ratio_of_one_reproduces_unscaled_formulas(year, part_b): + unscaled = _calculate( + _simulation(year, {"enrolled": _person(year, True, 0, part_b)}, ratio=1), + year, + ) + baseline = _calculate( + _simulation(year, {"enrolled": _person(year, True, 0, part_b)}), year + ) + p = system.parameters(f"{year}-01-01").calibration.gov.hhs.medicare + assert unscaled["medicare_gross_cost"][0] == pytest.approx( + p.per_capita_cost, rel=1e-6 + ) + # Gross spending scales exactly with the ratio. + assert baseline["medicare_gross_cost"][0] == pytest.approx( + unscaled["medicare_gross_cost"][0] * _stored(year), rel=1e-6 + ) + assert baseline["qmb_cost_sharing"][0] == pytest.approx( + unscaled["qmb_cost_sharing"][0] * _stored(year), rel=1e-6 + ) diff --git a/policyengine_us/tests/core/test_medicare_per_capita_cost.py b/policyengine_us/tests/core/test_medicare_per_capita_cost.py index 9e37cb5d1e3..76c44d1bbb7 100644 --- a/policyengine_us/tests/core/test_medicare_per_capita_cost.py +++ b/policyengine_us/tests/core/test_medicare_per_capita_cost.py @@ -11,10 +11,13 @@ at the 2031-2032 rate, because the end of sequestration on August 31, 2033 lifts 2033 and 2034 growth. -``medicare_gross_cost`` returns the series for each enrolled person, -``medicare_cost`` subtracts the person's Part A, gross Part B and expected -Part D premiums (tested in test_medicare_cost_part_d_premium.py), and -``qmb_cost_sharing`` pays QMB enrollees a share of it each month. +``medicare_gross_cost`` returns the series times +``noninstitutionalized_spending_ratio`` (enrollees outside nursing homes, +tested in test_medicare_noninstitutionalized_spending_ratio.py) for each +enrolled person, ``medicare_cost`` subtracts the person's Part A, gross Part +B and expected Part D premiums from that (tested in +test_medicare_cost_part_d_premium.py), and ``qmb_cost_sharing`` pays QMB +enrollees a share of it each month. Invariants: @@ -33,11 +36,11 @@ Trustees' 2034-2035 per beneficiary growth. 5. Calendar years: every date within a year reads the January 1 value. 6. Consumers: for an enrolled person, ``medicare_gross_cost`` equals the - series; ``medicare_cost`` equals the series less the Part A premium, the - gross Part B premium and the expected Part D premium, floored at zero, so - never exceeds ``medicare_gross_cost``; a QMB enrollee's monthly - ``qmb_cost_sharing`` is the series over 12 times the QMB rate. A person - not enrolled gets zero. + series times the noninstitutionalized ratio; ``medicare_cost`` equals that + less the Part A premium, the gross Part B premium and the expected Part D + premium, floored at zero, so never exceeds ``medicare_gross_cost``; a QMB + enrollee's monthly ``qmb_cost_sharing`` is the scaled series over 12 + times the QMB rate. A person not enrolled gets zero. """ import pytest @@ -49,6 +52,7 @@ from policyengine_us.system import system SERIES = "calibration.gov.hhs.medicare.per_capita_cost" +RATIO = "calibration.gov.hhs.medicare.noninstitutionalized_spending_ratio" FIRST_YEAR, LAST_PROJECTED_YEAR, END_YEAR = 2013, 2034, 2100 YEARS = range(FIRST_YEAR, LAST_PROJECTED_YEAR + 1) GROWTH_YEARS = (2031, 2032) @@ -110,6 +114,10 @@ def _value(year): return _series(f"{year}-01-01") +def _scaled(year): + return _value(year) * get_parameter(system.parameters, RATIO)(f"{year}-01-01") + + @pytest.mark.parametrize("year", YEARS) def test_series_is_table_17_medicare_spending_per_enrollee(year): assert _value(year) == MEDICARE_SPENDING_PER_ENROLLEE[year] @@ -214,10 +222,10 @@ def test_medicare_cost_nets_premiums_from_the_series( gross = simulation.calculate("medicare_gross_cost", year)[0] net = simulation.calculate("medicare_cost", year)[0] # Variables are float32, so compare to single precision. - assert gross == pytest.approx(_value(year), rel=1e-6) + assert gross == pytest.approx(_scaled(year), rel=1e-6) part_d = system.parameters(f"{year}-01-01").calibration.gov.hhs.medicare.part_d part_d_premium = part_d.enrollment_share * part_d.premiums_per_enrollee - expected = max(_value(year) - part_a_premium - part_b_premium - part_d_premium, 0) + expected = max(_scaled(year) - part_a_premium - part_b_premium - part_d_premium, 0) assert net == pytest.approx(expected, rel=1e-6, abs=0.01) assert net <= gross @@ -246,5 +254,5 @@ def test_qmb_cost_sharing_is_the_qmb_rate_of_monthly_spending(year, month): ).gov.hhs.medicare.savings_programs.qmb.cost_sharing_rate simulation = _person_simulation(year, {"msp_category": {period: "QMB"}}) assert simulation.calculate("qmb_cost_sharing", period)[0] == pytest.approx( - _value(year) / 12 * rate, rel=1e-6 + _scaled(year) / 12 * rate, rel=1e-6 ) diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/cbo_medicare_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/cbo_medicare_benefit.yaml index f3e1652a12b..0908efc1a77 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/cbo_medicare_benefit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/cbo_medicare_benefit.yaml @@ -1,7 +1,9 @@ -# cbo_medicare_benefit = Medicare spending per enrollee - premium income per -# Medicare enrollee (Parts A, B and D) - Part D low-income subsidy per -# Medicare enrollee, the same for every enrollee. -# 2025: $17,751 - $82.26 - $2,169.44 - $215.04 - $251.12 = $15,033.14. +# cbo_medicare_benefit = (Medicare spending per enrollee - Part D low-income +# subsidy per Medicare enrollee) x the ratio of spending per enrollee outside +# nursing homes to spending per enrollee (0.971) - premium income per Medicare +# enrollee (Parts A, B and D), the same for every enrollee. +# 2025: ($17,751 - $251.12) x 0.971 - $82.26 - $2,169.44 - $215.04 +# = $16,992.38 - $2,466.74 = $14,525.64. - name: Case 1, enrolled person gets the average net of premiums and the low-income subsidy. period: 2025 @@ -11,8 +13,8 @@ is_medicare_eligible: true medicare_enrolled: true output: - medicare_gross_cost: 17_751 - cbo_medicare_benefit: 15_033.14 + medicare_gross_cost: 17_236.22 + cbo_medicare_benefit: 14_525.64 # CBO assigns the average to every enrollee, so the person with 20 quarters # of coverage (a $6,216 Part A premium in 2025) and the person paying the @@ -76,7 +78,7 @@ not_enrolled, ] output: - cbo_medicare_benefit: [15_033.14, 15_033.14, 15_033.14, 15_033.14, 0] + cbo_medicare_benefit: [14_525.64, 14_525.64, 14_525.64, 14_525.64, 0] - name: Case 3, first year of the series. period: 2015 @@ -86,8 +88,8 @@ is_medicare_eligible: true medicare_enrolled: true output: - # $11,929 - $57.57 - $1,214.54 - $224.86 - $460.52. - cbo_medicare_benefit: 9_971.51 + # ($11,929 - $460.52) x 0.971 - $57.57 - $1,214.54 - $224.86. + cbo_medicare_benefit: 9_638.92 - name: Case 4, last Trustees projection year. period: 2035 @@ -97,9 +99,9 @@ is_medicare_eligible: true medicare_enrolled: true output: - # $31,764.35 (the 2034 CMS value grown at the 2031-2032 rate) - $156.27 - # - $4,652 - $809.85 - $431.59. - cbo_medicare_benefit: 25_714.64 + # ($31,764.35, the 2034 CMS value grown at the 2031-2032 rate, - $431.59) + # x 0.971 - $156.27 - $4,652 - $809.85. + cbo_medicare_benefit: 24_805.99 - name: Case 5, not enrolled. period: 2025 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 504cd704414..cce9be572ff 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 @@ -1,6 +1,8 @@ -# The expected Part D premium is the Part D enrollment share times (Part D -# premiums per Part D enrollee + the person's Part D IRMAA). 2025: 0.8191 x -# $220.25 = $180.41 without IRMAA. +# Spending is CMS Medicare spending per enrollee times the ratio of spending +# per enrollee outside nursing homes to spending per enrollee (0.971); 2025: +# $17,751 x 0.971 = $17,236.22. The expected Part D premium is the Part D +# enrollment share times (Part D premiums per Part D enrollee + the person's +# Part D IRMAA). 2025: 0.8191 x $220.25 = $180.41 without IRMAA. - name: unit test 1 - enrolled, premium-free Part A period: 2025 @@ -14,8 +16,8 @@ tax_exempt_interest_income: 2023: 0 output: - # $17,751 spending - $2,220 Part B premium - $180.41 Part D (2025) - medicare_cost: 15_350.59 + # $17,236.22 spending - $2,220 Part B premium - $180.41 Part D (2025) + medicare_cost: 14_835.81 - name: unit test 2 - enrolled, pays full Part A premium period: 2025 @@ -29,8 +31,8 @@ tax_exempt_interest_income: 2023: 0 output: - # $17,751 - $6,216 Part A - $2,220 Part B - $180.41 Part D (2025) - medicare_cost: 9_134.59 + # $17,236.22 - $6,216 Part A - $2,220 Part B - $180.41 Part D (2025) + medicare_cost: 8_619.81 - name: unit test 3 - enrolled with IRMAA period: 2025 @@ -45,9 +47,9 @@ 2023: 0 filing_status: SINGLE output: - # $17,751 - $7,546.80 Part B with IRMAA - 0.8191 x ($220.25 + $85.80 x 12 - # Part D IRMAA) = $1,023.75 Part D (2025) - medicare_cost: 9_180.45 + # $17,236.22 - $7,546.80 Part B with IRMAA - 0.8191 x ($220.25 + $85.80 x + # 12 Part D IRMAA) = $1,023.75 Part D (2025) + medicare_cost: 8_665.67 - name: unit test 4 - eligible but not enrolled period: 2025 @@ -70,8 +72,9 @@ tax_exempt_interest_income: 2019: 0 output: - # $14,293 - $1,782 Part B (2021: $148.50*12) - 0.7811 x $306.15 Part D - medicare_cost: 12_271.87 + # $14,293 x 0.971 = $13,878.50 - $1,782 Part B (2021: $148.50*12) + # - 0.7811 x $306.15 Part D + medicare_cost: 11_857.37 - name: unit test 6 - direct Part B premium input period: 2025 @@ -82,8 +85,8 @@ medicare_quarters_of_coverage: 40 gross_medicare_part_b_premium: 3_000 output: - # $17,751 spending - $3,000 Part B premium - $180.41 Part D - medicare_cost: 14_570.59 + # $17,236.22 spending - $3,000 Part B premium - $180.41 Part D + medicare_cost: 14_055.81 - name: unit test 7 - Part D IRMAA in the 50 percent tier period: 2025 @@ -100,8 +103,9 @@ gross_medicare_part_b_premium: 2_220 output: income_adjusted_part_d_premium_surcharge: 423.6 - # $17,751 - $2,220 Part B - 0.8191 x ($220.25 + $423.60) = $527.38 Part D - medicare_cost: 15_003.62 + # $17,236.22 - $2,220 Part B - 0.8191 x ($220.25 + $423.60) = $527.38 + # Part D + medicare_cost: 14_488.84 - name: unit test 8 - projected year period: 2027 @@ -113,8 +117,9 @@ gross_medicare_part_b_premium: 2_500 income_adjusted_part_d_premium_surcharge: 0 output: - # $20,096 - $2,500 Part B - 0.8255 x $373.53 = $308.35 Part D (2027) - medicare_cost: 17_287.65 + # $20,096 x 0.971 = $19,513.22 - $2,500 Part B - 0.8255 x $373.53 = + # $308.35 Part D (2027) + medicare_cost: 16_704.87 - name: unit test 9 - Inflation Reduction Act premium reset year, with IRMAA period: 2030 @@ -126,8 +131,9 @@ gross_medicare_part_b_premium: 3_000 income_adjusted_part_d_premium_surcharge: 1_000 output: - # $23,789 - $3,000 Part B - 0.8382 x ($637.06 + $1,000) = $1,372.18 Part D - medicare_cost: 19_416.82 + # $23,789 x 0.971 = $23,099.12 - $3,000 Part B - 0.8382 x ($637.06 + + # $1,000) = $1,372.18 Part D + medicare_cost: 18_726.94 - name: unit test 10 - premiums above spending floor the value at zero period: 2025 @@ -141,6 +147,7 @@ - name: unit test 11 - gross cost does not subtract the Part D premium period: 2025 + absolute_error_margin: 0.01 input: age: 65 is_medicare_eligible: true @@ -148,4 +155,4 @@ gross_medicare_part_b_premium: 2_220 income_adjusted_part_d_premium_surcharge: 1_029.6 output: - medicare_gross_cost: 17_751 + medicare_gross_cost: 17_236.22 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_gross_cost.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_gross_cost.yaml index 15eb8725e7d..e59831f759d 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_gross_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_gross_cost.yaml @@ -1,5 +1,10 @@ +# medicare_gross_cost = CMS Medicare spending per enrollee x the ratio of +# spending per enrollee outside nursing homes to spending per enrollee (0.971). +# 2025: $17,751 x 0.971 = $17,236.22. + - name: unit test 1 - enrolled, premium-free Part A gets full gross Medicare value period: 2025 + absolute_error_margin: 0.01 input: age: 65 is_medicare_eligible: true @@ -10,11 +15,11 @@ tax_exempt_interest_income: 2023: 0 output: - # CMS Medicare spending per enrollee, 2025. - medicare_gross_cost: 17_751 + medicare_gross_cost: 17_236.22 - name: unit test 2 - premiums do not reduce gross Medicare value period: 2025 + absolute_error_margin: 0.01 input: age: 65 is_medicare_eligible: true @@ -26,5 +31,13 @@ 2023: 0 filing_status: SINGLE output: - # CMS Medicare spending per enrollee, 2025. - medicare_gross_cost: 17_751 + medicare_gross_cost: 17_236.22 + +- name: unit test 3 - not enrolled + period: 2025 + input: + age: 65 + is_medicare_eligible: true + medicare_enrolled: false + output: + medicare_gross_cost: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_cost.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_cost.yaml index 55be058ba49..9cb12d59fa3 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_cost.yaml @@ -11,8 +11,8 @@ base_part_b_premium: 2_434.8 output: # Part B premium: $202.90 * 12 = $2,434.80. - # QMB cost sharing: $18,983 * 20% = $3,796.60. - msp_cost: 6_231.4 + # QMB cost sharing: $18,983 * 0.971 * 20% = $3,686.50. + msp_cost: 6_121.3 - name: Case 2, SLMB MSP-only beneficiary receives Part B premium value. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_federal_and_state_cost.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_federal_and_state_cost.yaml index 0c526fe9c29..610ba12f6d1 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_federal_and_state_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_federal_and_state_cost.yaml @@ -10,9 +10,9 @@ base_part_a_premium: 0 base_part_b_premium: 2_434.8 output: - # CA regular FMAP is 50%; total MSP cost is $6,231.40. - msp_federal_cost: 3_115.7 - msp_state_cost: 3_115.7 + # CA regular FMAP is 50%; total MSP cost is $6,121.30. + msp_federal_cost: 3_060.65 + msp_state_cost: 3_060.65 - name: Case 2, QI cost is federally financed within allocation. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/qmb_cost_sharing.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/qmb_cost_sharing.yaml index 1fa5134a84f..695e184b496 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/qmb_cost_sharing.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/qmb_cost_sharing.yaml @@ -4,8 +4,9 @@ input: msp_category: QMB output: - # $18,983 annual Medicare spending per enrollee (2026) / 12 * 20%. - qmb_cost_sharing: 316.38 + # $18,983 annual Medicare spending per enrollee (2026) x 0.971 (enrollees + # outside nursing homes) / 12 * 20%. + qmb_cost_sharing: 307.21 - name: Case 2, non-QMB MSP categories do not receive QMB cost sharing. period: 2026-01 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 b15943e5203..5de6f86d4a3 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 @@ -87,9 +87,10 @@ def test_medicare_cost_uses_gross_part_b_before_msp_offset(): ) assert sim.calculate("medicare_part_b_premium", PERIOD)[0] == pytest.approx(0) - # $17,751 CMS Medicare spending per enrollee (2025) - $2,220 gross Part B - # - 0.8191 x $220.25 expected Part D premium. - assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(15_350.59) + # $17,751 CMS Medicare spending per enrollee (2025) x 0.971 for enrollees + # outside nursing homes - $2,220 gross Part B - 0.8191 x $220.25 expected + # Part D premium. + assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(14_835.81) def test_medicare_part_b_premium_is_zero_when_not_enrolled(): @@ -345,9 +346,10 @@ def test_gross_medicare_part_b_premium_if_enrolled_preserves_irmaa(): assert sim.calculate("gross_medicare_part_b_premium_if_enrolled", PERIOD)[ 0 ] == pytest.approx(4_440) - # $17,751 CMS Medicare spending per enrollee (2025) - $4,440 gross Part B - # - 0.8191 x $220.25 expected Part D premium. - assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(13_130.59) + # $17,751 CMS Medicare spending per enrollee (2025) x 0.971 for enrollees + # outside nursing homes - $4,440 gross Part B - 0.8191 x $220.25 expected + # Part D premium. + assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(12_615.81) def test_medicare_irmaa_magi_two_years_prior_falls_back_to_lagged_income(): diff --git a/policyengine_us/tests/policy/baseline/household/income/household/cbo_household_income.yaml b/policyengine_us/tests/policy/baseline/household/income/household/cbo_household_income.yaml index d52fdf17f34..1aca1ce94ce 100644 --- a/policyengine_us/tests/policy/baseline/household/income/household/cbo_household_income.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/household/cbo_household_income.yaml @@ -34,16 +34,17 @@ workers_compensation: 1_000 output: cbo_household_market_income: 49_102 - # CMS Medicare spending per enrollee, 2025. - medicare_gross_cost: 17_751 - # $17,751 less 2025 premium income per Medicare enrollee ($82.26 Part A, - # $2,169.44 Part B, $215.04 Part D) and the $251.12 Part D low-income - # subsidy per Medicare enrollee. - cbo_medicare_benefit: 15_033.14 - # $12,000 + $15,033.14 + $2,000 + $1,000. - cbo_household_social_insurance_benefits: 30_033.14 - cbo_household_income_before_transfers_and_taxes: 79_135.14 - cbo_household_income: 79_135.14 + # CMS Medicare spending per enrollee, 2025, x 0.971 for enrollees outside + # nursing homes: $17,751 x 0.971. + medicare_gross_cost: 17_236.22 + # ($17,751 less the $251.12 Part D low-income subsidy per Medicare + # enrollee) x 0.971, less 2025 premium income per Medicare enrollee + # ($82.26 Part A, $2,169.44 Part B, $215.04 Part D). + cbo_medicare_benefit: 14_525.64 + # $12,000 + $14,525.64 + $2,000 + $1,000. + cbo_household_social_insurance_benefits: 29_525.64 + cbo_household_income_before_transfers_and_taxes: 78_627.64 + cbo_household_income: 78_627.64 - name: CBO means-tested transfers include only the listed programs period: 2026 From f449aa2d0aead638a9947c74af41df3663b3ade9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 17:37:48 -0400 Subject: [PATCH 3/5] Cite the CPS sample frame for the nursing home exclusion BLS's CPS design page says institutional group quarters such as nursing homes have never been in the CPS sample; state that, rather than asserting that assisted living residents are sampled. Co-Authored-By: Claude Opus 5.5 --- .../medicare/noninstitutionalized_spending_ratio.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml b/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml index bbeaeec6434..3f4a37d0ac1 100644 --- a/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml +++ b/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml @@ -26,16 +26,19 @@ metadata: # page 8), but relative spending is published for 2022 only, so the 2022 # ratio applies to every year from 2013, the first year of per_capita_cost. # - # Nursing homes are the facilities the survey excludes. The Medicare - # Current Beneficiary Survey's long-term care facilities also include - # assisted living and other residential care, whose residents the survey - # samples; excluding all of them gives a larger gap (community residents' + # The Current Population Survey has never sampled institutional group + # quarters such as nursing homes. The Medicare Current Beneficiary Survey's + # long-term care facilities also include assisted living, retirement homes + # and personal care homes, which are not institutional group quarters; + # excluding all of their residents gives a larger gap (community residents' # benefit payments per capita were 0.91 to 0.93 of the average in # 2006-2013, Table 4.1 over Table 1.1). - title: Medicare Payment Advisory Commission, Report to the Congress, June 2025, Chapter 5, Medicare beneficiaries in nursing homes href: https://www.medpac.gov/wp-content/uploads/2025/06/Jun25_Ch5_MedPAC_Report_To_Congress_SEC.pdf#page=9 - title: Medicare Payment Advisory Commission, Medicare beneficiaries in nursing homes, October 2024 public meeting href: https://www.medpac.gov/wp-content/uploads/2023/10/Beneficiaries-in-Nursing-Homes-MedPAC-Oct-2024-SEC.pdf#page=5 + - title: U.S. Bureau of Labor Statistics, Handbook of Methods, Current Population Survey, Design + href: https://www.bls.gov/opub/hom/cps/design.htm - title: Centers for Medicare and Medicaid Services, Characteristics and Perceptions of the Medicare Population, 2013, Table 4.1 href: https://www.cms.gov/research-statistics-data-and-systems/research/mcbs/downloads/2013_characteristics_and_perceptions.zip # CBO likewise removes institutionalized enrollees' benefits before From c6f8aef614a17e7fccadf477073ca1f7eb175809 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 16:51:09 -0500 Subject: [PATCH 4/5] Cite the census residence rules and MCBS facility types for the ratio Cite 83 FR 5525 (sections C.11 and C.12) for counting assisted living and retirement community residents where they live and nursing facility patients at the facility, name the MCBS facility types from its Appendix B, note that the June 2025 figures replace the preliminary October 2024 slide's description, say how CBO's constant 2012 share differs from this constant ratio, and put several cited pages in reference titles. Co-Authored-By: Claude Opus 5.5 --- .../noninstitutionalized_spending_ratio.yaml | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml b/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml index 3f4a37d0ac1..b23188632ec 100644 --- a/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml +++ b/policyengine_us/parameters/calibration/gov/hhs/medicare/noninstitutionalized_spending_ratio.yaml @@ -14,38 +14,50 @@ metadata: # 2023, June 2025, file page 8). A long-stay resident had been in a nursing # home for more than 90 days in at least one month of the year. So spending # per other beneficiary relative to spending per beneficiary is - # 1 / (1 + 0.017 * (31_200 / 11_300 - 1)) = 0.971. + # 1 / (1 + 0.017 * (31_200 / 11_300 - 1)) = 0.971. The preliminary October + # 2024 slide described long-stay residents' spending as about two times + # higher; the June 2025 report's dollar figures, almost three times as + # high, replace that description. # # The ratio is measured for fee-for-service Parts A and B and applied to # all Medicare spending per enrollee, including Medicare Advantage, Part D # and administration. With fee-for-service enrollment shares instead (62 - # percent of long-stay residents, 53 percent of others, July 2023, file - # page 13) it is 0.966; pricing Medicare Advantage at the long-stay - # residents' risk score ratio (2.12 / 1.01, October 2024, file page 5) gives - # about 0.975. The long-stay share was 2.6 percent in 2013 (June 2025, file - # page 8), but relative spending is published for 2022 only, so the 2022 - # ratio applies to every year from 2013, the first year of per_capita_cost. - # - # The Current Population Survey has never sampled institutional group - # quarters such as nursing homes. The Medicare Current Beneficiary Survey's - # long-term care facilities also include assisted living, retirement homes - # and personal care homes, which are not institutional group quarters; - # excluding all of their residents gives a larger gap (community residents' - # benefit payments per capita were 0.91 to 0.93 of the average in - # 2006-2013, Table 4.1 over Table 1.1). - - title: Medicare Payment Advisory Commission, Report to the Congress, June 2025, Chapter 5, Medicare beneficiaries in nursing homes - href: https://www.medpac.gov/wp-content/uploads/2025/06/Jun25_Ch5_MedPAC_Report_To_Congress_SEC.pdf#page=9 + # percent of long-stay residents and 53 percent of others in 2023, June + # 2025, file page 13) it is 0.966; pricing Medicare Advantage at the + # long-stay residents' risk score ratio (2.12 / 1.01, October 2024, file + # page 5) gives about 0.975. The long-stay share was 2.6 percent in 2013 + # (June 2025, file page 8), but MedPAC publishes relative spending for 2022 + # only, so the 2022 ratio applies to every year from 2013, the first year of + # per_capita_cost. + - title: "Medicare Payment Advisory Commission, Report to the Congress, June 2025, Chapter 5, Medicare beneficiaries in nursing homes #page 8-9,13" + href: https://www.medpac.gov/wp-content/uploads/2025/06/Jun25_Ch5_MedPAC_Report_To_Congress_SEC.pdf#page=8 - title: Medicare Payment Advisory Commission, Medicare beneficiaries in nursing homes, October 2024 public meeting href: https://www.medpac.gov/wp-content/uploads/2023/10/Beneficiaries-in-Nursing-Homes-MedPAC-Oct-2024-SEC.pdf#page=5 + # The Current Population Survey samples housing units and some + # noninstitutional group quarters; institutional group quarters such as + # nursing homes have never been part of its sample. The census counts + # nursing facility patients at the facility, but counts residents of + # assisted living facilities and of housing for older adults, such as + # retirement communities, at the residence where they live and sleep most + # of the time (sections C.11(c), C.11(d) and C.12). - title: U.S. Bureau of Labor Statistics, Handbook of Methods, Current Population Survey, Design href: https://www.bls.gov/opub/hom/cps/design.htm - - title: Centers for Medicare and Medicaid Services, Characteristics and Perceptions of the Medicare Population, 2013, Table 4.1 + - title: "U.S. Census Bureau, Final 2020 Census Residence Criteria and Residence Situations, 83 FR 5525 #page 10-11" + href: https://www.govinfo.gov/content/pkg/FR-2018-02-08/pdf/2018-02370.pdf#page=10 + # The Medicare Current Beneficiary Survey's long-term care facilities + # include retirement homes, personal care facilities and assisted and + # foster care homes as well as nursing homes (Appendix B). Excluding + # everyone living in one at the fall interview gives a larger gap: in + # 2013, community residents' Medicare benefit payments per capita were + # 0.92 of the average over all beneficiaries (Table 4.1 over Table 1.1). + - title: Centers for Medicare and Medicaid Services, Characteristics and Perceptions of the Medicare Population, 2013, Tables 1.1 and 4.1 and Appendix B href: https://www.cms.gov/research-statistics-data-and-systems/research/mcbs/downloads/2013_characteristics_and_perceptions.zip - # CBO likewise removes institutionalized enrollees' benefits before - # assigning Medicare's average cost to survey respondents (file page 46), - # with the institutional share held at its 2012 level from 2013 (file page - # 52). - - title: Congressional Budget Office, Working Paper 2018-07, How CBO Adjusts for Survey Underreporting of Transfer Income in Its Distributional Analyses + # CBO also removes institutionalized enrollees' benefits before assigning + # Medicare's average cost to survey respondents (file page 46). It holds + # the institutional share of Medicare benefits other than the low-income + # subsidy at its 2012 level from 2013 (file page 52), where this parameter + # instead holds the ratio of spending per person constant. + - title: "Congressional Budget Office, Working Paper 2018-07, How CBO Adjusts for Survey Underreporting of Transfer Income in Its Distributional Analyses #page 46,52" href: https://www.cbo.gov/system/files/2018-07/54234-workingpaper.pdf#page=46 values: 2013-01-01: 0.971 From 1fa242be19225cb15ea1d32446bb403a9e18acf5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 16:51:09 -0500 Subject: [PATCH 5/5] Test the noninstitutionalized ratio from parameters only Drop the property tests that built four reformed tax-benefit systems and kept them cached for the rest of the core test process. The YAML tests and the reference implementations in the per-capita, Part D premium and CBO series tests cover the consumers at the stored ratio. Add a differential check that CBO's 2006 figures imply a ratio within 0.01. Co-Authored-By: Claude Opus 5.5 --- ...are_noninstitutionalized_spending_ratio.py | 228 +++++------------- 1 file changed, 54 insertions(+), 174 deletions(-) diff --git a/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py b/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py index 5fac42ce69a..0b8c5496fc4 100644 --- a/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py +++ b/policyengine_us/tests/core/test_medicare_noninstitutionalized_spending_ratio.py @@ -12,10 +12,12 @@ ratio = 1 / (1 + share x (long-stay spending / other spending - 1)) -``medicare_gross_cost``, ``medicare_cost`` and ``qmb_cost_sharing`` multiply -``per_capita_cost`` by it; ``cbo_medicare_benefit`` multiplies spending less -the Part D low-income subsidy by it, then subtracts premium income per -enrollee. +These tests read parameters only. Tests elsewhere cover the consumers at the +stored ratio: the YAML tests of ``medicare_gross_cost``, ``medicare_cost``, +``cbo_medicare_benefit`` and ``qmb_cost_sharing``, and the reference +implementations in test_medicare_per_capita_cost.py, +test_medicare_cost_part_d_premium.py and test_cbo_medicare_benefit_series.py, +which multiply by this parameter. Invariants: @@ -28,33 +30,24 @@ long-stay residents, 53 percent of others) give a lower ratio, and pricing Medicare Advantage at the CMS-HCC risk score ratio (2.12 / 1.01) gives a higher one. -4. Definition order: excluding every resident of a Medicare Current - Beneficiary Survey long-term care facility, which includes assisted living, - gives a lower ratio in every year of the published 2006-2013 tables - (community-only over all-beneficiary benefit payments per capita), so the - nursing home ratio is the smaller of the two corrections. -5. Consumers (Hypothesis, differential): for any year, any premiums and a - ratio set by a reform (0.5, 0.8, 0.95 or 1), each consumer equals a - reference implementation; - gross spending is proportional to the ratio; ``medicare_cost`` and - ``cbo_medicare_benefit`` lie in [0, ``medicare_gross_cost``] and never fall - when the ratio rises; with the ratio at 1 every consumer reproduces the - unscaled formulas. A person not enrolled gets zero. +4. Definition order: excluding everyone living in a Medicare Current + Beneficiary Survey long-term care facility, which includes assisted living + and retirement homes, gives a lower ratio in every year of the published + 2006-2013 tables (community-only over all-beneficiary benefit payments per + capita), so the nursing home ratio is the smaller of the two corrections. +5. Differential: CBO's 2006 figures, from a different method and source, + imply a ratio per recipient and per survey reporter within 0.01 of the + stored value. """ -from functools import lru_cache - import pytest -from hypothesis import HealthCheck, given, settings +from hypothesis import given from hypothesis import strategies as st from policyengine_core.parameters.operations.get_parameter import get_parameter -from policyengine_core.reforms import Reform -from policyengine_us import Simulation -from policyengine_us.system import CountryTaxBenefitSystem, system +from policyengine_us.system import system -MEDICARE = "calibration.gov.hhs.medicare" -RATIO = f"{MEDICARE}.noninstitutionalized_spending_ratio" +RATIO = "calibration.gov.hhs.medicare.noninstitutionalized_spending_ratio" FIRST_YEAR, END_YEAR = 2013, 2100 # MedPAC, June 2025 Report to the Congress, chapter 5 (file pages 8, 9 and @@ -64,9 +57,12 @@ LONG_STAY_FFS_SHARE, OTHER_FFS_SHARE = 0.62, 0.53 LONG_STAY_RISK_SCORE, OTHER_RISK_SCORE = 2.12, 1.01 -# MCBS Characteristics and Perceptions of the Medicare Population, all -# beneficiaries: Table 1.1 beneficiaries (thousands) and Table 4.1 benefit -# payments ($ millions), total and community only. +# MCBS Characteristics and Perceptions of the Medicare Population, each +# year's edition +# (https://www.cms.gov/research-statistics-data-and-systems/research/mcbs/ +# downloads/_characteristics_and_perceptions.zip), all beneficiaries: +# Table 1.1 beneficiaries (thousands) and Table 4.1 benefit payments ($ +# millions), total and community only. MCBS = { 2006: (43_867, 41_599, 305_066, 267_344), 2007: (45_046, 42_867, 307_372, 271_509), @@ -78,29 +74,38 @@ 2013: (53_574, 51_568, 352_943, 312_626), } +# CBO 2006, in billions of dollars. Working Paper 2018-07: 93 percent of +# enrollees reported Medicare in the survey and 6 percent of recipients were +# institutionalized (file pages 45-46); Medicare benefits other than the +# low-income subsidy after CBO's adjustments, $352.4, and the subsidy, $10.3 +# (file page 51). CBO (2013), The Distribution of Federal Spending and Taxes +# in 2006: $32 of Medicare spending for people in long-term care institutions +# (file page 53). 2007 Medicare Trustees Report, Table II.B1: 2006 benefits, +# $402.0, and Part A premiums, $2.6, which CBO subtracts (file page 52). +CBO_REPORTING_RATE, CBO_INSTITUTIONALIZED_SHARE = 0.93, 0.06 +CBO_ADJUSTED_BENEFITS, CBO_LOW_INCOME_SUBSIDY = 352.4, 10.3 +CBO_INSTITUTIONAL_SPENDING = 32 +TRUSTEES_BENEFITS, TRUSTEES_PART_A_PREMIUMS = 402.0, 2.6 + def _ratio(share, relative): return 1 / (1 + share * (relative - 1)) -def _read(name, date): - return get_parameter(system.parameters, name)(date) - - -def _stored(year): - return _read(RATIO, f"{year}-01-01") +def _stored(date): + return get_parameter(system.parameters, RATIO)(date) def test_value_is_the_medpac_formula(): expected = _ratio(LONG_STAY_SHARE, LONG_STAY_SPENDING / OTHER_SPENDING) - assert _stored(2022) == round(expected, 3) == 0.971 + assert _stored("2022-01-01") == round(expected, 3) == 0.971 @given(st.integers(FIRST_YEAR, END_YEAR + 100), st.integers(1, 12)) def test_ratio_is_a_constant_share_below_one(year, month): - value = _read(RATIO, f"{year}-{month:02d}-01") + value = _stored(f"{year}-{month:02d}-01") assert 0 < value < 1 - assert value == _stored(2022) + assert value == _stored("2022-01-01") def test_documented_alternatives_bracket_the_value(): @@ -128,150 +133,25 @@ def test_documented_alternatives_bracket_the_value(): mixed_ratio = 1 / average assert round(ffs_ratio, 3) == 0.966 assert round(mixed_ratio, 3) == 0.975 - assert ffs_ratio < _stored(2022) < mixed_ratio + assert ffs_ratio < _stored("2022-01-01") < mixed_ratio @pytest.mark.parametrize("year", MCBS) def test_mcbs_facility_definition_gives_a_larger_gap(year): total_people, community_people, total_pay, community_pay = MCBS[year] mcbs_ratio = (community_pay / community_people) / (total_pay / total_people) - assert 0.9 < mcbs_ratio < _stored(2022) - - -# Reformed systems take seconds to build, so the property tests draw the -# ratio from a few values and build each system once. -RATIOS = (0.5, 0.8, 0.95, 1) - - -@lru_cache(maxsize=None) -def _system(ratio): - if ratio is None: - return system - reform = Reform.from_dict( - {RATIO: {f"{FIRST_YEAR}-01-01.{END_YEAR}-12-31": ratio}}, - country_id="us", - ) - return CountryTaxBenefitSystem(reform=reform) + # The stored ratio starts in 2013, so compare every year to its value. + assert 0.9 < mcbs_ratio < _stored("2022-01-01") -def _simulation(year, people, ratio=None): - names = list(people) - return Simulation( - tax_benefit_system=_system(ratio), - situation={ - "people": people, - "tax_units": {f"tax_unit_{name}": {"members": [name]} for name in names}, - "spm_units": {"spm_unit": {"members": names}}, - "families": {"family": {"members": names}}, - "marital_units": { - f"marital_unit_{name}": {"members": [name]} for name in names - }, - "households": {"household": {"members": names, "state_code": {year: "CA"}}}, - }, - ) - - -def _person(year, enrolled, part_a=0, part_b=0): - return { - "age": {year: 70}, - "is_medicare_eligible": {year: True}, - "medicare_enrolled": {year: enrolled}, - "base_part_a_premium": {year: part_a}, - "gross_medicare_part_b_premium": {year: part_b}, - "income_adjusted_part_d_premium_surcharge": {year: 0}, - "msp_category": {f"{year}-01": "QMB"}, - } - - -def _reference(year, ratio, part_a, part_b): - p = system.parameters(f"{year}-01-01").calibration.gov.hhs.medicare - spending = p.per_capita_cost - premiums = p.premium_income_per_enrollee - part_d = p.part_d.enrollment_share * p.part_d.premiums_per_enrollee - rate = system.parameters( - f"{year}-01-01" - ).gov.hhs.medicare.savings_programs.qmb.cost_sharing_rate - return { - "medicare_gross_cost": spending * ratio, - "medicare_cost": max(spending * ratio - part_a - part_b - part_d, 0), - "cbo_medicare_benefit": max( - (spending - p.part_d_low_income_subsidy_per_enrollee) * ratio - - (premiums.part_a + premiums.part_b + premiums.part_d), - 0, - ), - "qmb_cost_sharing": spending * ratio / 12 * rate, - } - - -def _calculate(simulation, year): - return { - name: simulation.calculate( - name, f"{year}-01" if name == "qmb_cost_sharing" else year - ) - for name in ( - "medicare_gross_cost", - "medicare_cost", - "cbo_medicare_benefit", - "qmb_cost_sharing", - ) - } - - -premiums = st.floats(0, 30_000, allow_nan=False) - - -@settings(max_examples=12, deadline=None, suppress_health_check=[HealthCheck.too_slow]) -@given( - st.integers(2015, END_YEAR), - st.sampled_from(RATIOS), - st.sampled_from(RATIOS), - st.floats(0, 12_000, allow_nan=False), - premiums, -) -def test_consumers_match_reference_for_any_ratio(year, one, other, part_a, part_b): - low, high = sorted((one, other)) - results = {} - for value in (low, high): - simulation = _simulation( - year, - { - "enrolled": _person(year, True, part_a, part_b), - "not_enrolled": _person(year, False), - }, - ratio=value, - ) - results[value] = _calculate(simulation, year) - reference = _reference(year, value, part_a, part_b) - for name, values in results[value].items(): - # Variables are float32, so compare to single precision. - assert values[0] == pytest.approx(reference[name], rel=1e-6, abs=0.01) - if name != "qmb_cost_sharing": - assert values[1] == 0 - gross = results[value]["medicare_gross_cost"][0] - for name in ("medicare_cost", "cbo_medicare_benefit"): - assert 0 <= results[value][name][0] <= gross - for name in results[low]: - assert results[low][name][0] <= results[high][name][0] + 0.01 - - -@settings(max_examples=5, deadline=None, suppress_health_check=[HealthCheck.too_slow]) -@given(st.integers(2015, END_YEAR), premiums) -def test_ratio_of_one_reproduces_unscaled_formulas(year, part_b): - unscaled = _calculate( - _simulation(year, {"enrolled": _person(year, True, 0, part_b)}, ratio=1), - year, - ) - baseline = _calculate( - _simulation(year, {"enrolled": _person(year, True, 0, part_b)}), year - ) - p = system.parameters(f"{year}-01-01").calibration.gov.hhs.medicare - assert unscaled["medicare_gross_cost"][0] == pytest.approx( - p.per_capita_cost, rel=1e-6 - ) - # Gross spending scales exactly with the ratio. - assert baseline["medicare_gross_cost"][0] == pytest.approx( - unscaled["medicare_gross_cost"][0] * _stored(year), rel=1e-6 - ) - assert baseline["qmb_cost_sharing"][0] == pytest.approx( - unscaled["qmb_cost_sharing"][0] * _stored(year), rel=1e-6 +def test_cbo_2006_figures_imply_a_similar_ratio(): + benefits = TRUSTEES_BENEFITS - TRUSTEES_PART_A_PREMIUMS - CBO_LOW_INCOME_SUBSIDY + assert benefits == pytest.approx(389.1) + per_recipient = (1 - CBO_INSTITUTIONAL_SPENDING / benefits) / ( + 1 - CBO_INSTITUTIONALIZED_SHARE ) + per_reporter = CBO_ADJUSTED_BENEFITS / benefits / CBO_REPORTING_RATE + assert round(per_recipient, 3) == 0.976 + assert round(per_reporter, 3) == 0.974 + for implied in (per_recipient, per_reporter): + assert implied == pytest.approx(_stored("2013-01-01"), abs=0.01)