feat(notebooks): CY2027 NPRM — GPCI & locality section + primary-care (ACO attribution) per-service analysis (closes #631, closes #632)

This commit is contained in:
kert
2026-08-17 12:24:01 -04:00
parent fc26d5fc54
commit 8c4e13c854

View File

@@ -29,11 +29,12 @@ def _(mo):
and `qpp.QPP` (the repo's rule registries) and the `pfs`/`cms`
DuckLake schemas — nothing here is a hard-coded figure. Columns are
explicitly labeled **"final rule pending"** rather than fabricating
a CY2027-final column that does not exist yet. All dollar figures
use **national, GPCI-unadjusted RVUs (GPCI = 1.0)** — a deliberate
scope simplification, disclosed once here rather than on every
figure; real payment varies by locality via `pfs.gpci`, which this
notebook does not join.
a CY2027-final column that does not exist yet. Dollar figures in
Sections 1-2 and 4 are **national, GPCI-unadjusted (GPCI = 1.0)**
unless a figure says otherwise; **Section 3** carries the locality
story — the CY2027 NPRM's proposed GPCIs (`pfs.gpci_proposed`,
Addendum E) against the final-rule history in `pfs.gpci`, with
locality-adjusted payment examples.
""")
return
@@ -50,9 +51,13 @@ def _():
connect.theme()
# connect.theme() puts assets/ on sys.path — reuse the HTI-5 design
# tokens directly instead of re-typing hex literals for the gain/loss
# diverging pair used in Section 2.
from fhirworx import AMBER, TEAL
# tokens directly instead of re-typing hex literals: AMBER/TEAL are
# the gain/loss diverging pair (Sections 2-4), INDIGO_MED the
# default series, PLUM/GRAY the fixed categorical follow-ons for
# the GPCI locality lines (Section 3).
from fhirworx import AMBER, GRAY, INDIGO_MED, PLUM, TEAL
from pfs.calcs.payment import payment as pfs_payment
# PFS reference data lives in the DuckLake lakehouse (M5, #514) —
# notebooks connect read-only.
@@ -61,7 +66,22 @@ def _():
def q(sql):
return con.execute(sql).pl()
return AMBER, QPP, RULES, TEAL, alt, con, for_payment_year, pl, proposed_for, q
return (
AMBER,
GRAY,
INDIGO_MED,
PLUM,
QPP,
RULES,
TEAL,
alt,
con,
for_payment_year,
pfs_payment,
pl,
proposed_for,
q,
)
# ── 1. The conversion-factor walk ─────────────────────────────────────
@@ -493,13 +513,777 @@ def _(cross_nprm, mo):
return
# ── 3. Advanced APM ─────────────────────────────────────────────────────
# ── 3. GPCI & Locality ──────────────────────────────────────────────────
@app.cell(hide_code=True)
def _(mo):
mo.md("""
## 3. Advanced APM (QP) Requirements
## 3. GPCI & Locality — Proposed CY2027 Geographic Indices
Geographic Practice Cost Indices scale each RVU component to local
input costs: `payment = (work_rvu x work_gpci + pe_rvu x pe_gpci +
mp_rvu x mp_gpci) x CF`. Every dollar figure in Sections 1-2 is
national/unadjusted (all GPCIs = 1.0); this section is where
locality enters, from `pfs.gpci_proposed` — the NPRM **Addendum E**
workbooks, ingested per rule partition exactly like
`pfs.rvu_proposed`.
GPCIs update on a **biennial** cycle, so the table holds three
partition-years: the CY2026 NPRM (`CMS-1832-P`) proposed CY2026
values *and* CY2027 phase-in values, and the CY2027 NPRM
(`CMS-1848-P`) re-proposes CY2027. That makes "did CMS change its
mind about 2027?" a plain self-join, done below.
> **Work-floor caveat:** the proposed work GPCIs are published
> **without** the statutory 1.0 work floor — that is the Addendum E
> column heading's own wording, while the CY2025 baseline column in
> the CY2026-NPRM workbook was published *with* the floor. Values
> here are shown exactly as published; whether a floor applies to
> actual CY2027 payment depends on legislation not adjudicated in
> this notebook.
""")
return
@app.cell(hide_code=True)
def _(q):
gpci_2027p = q("""
WITH prop AS (
SELECT mac, state, locality, locality_name,
work_gpci, pe_gpci, mp_gpci
FROM pfs.gpci_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND gpci_year = 2027
),
fin AS (
SELECT mac, locality,
work_gpci AS work_gpci_2026f,
pe_gpci AS pe_gpci_2026f,
mp_gpci AS mp_gpci_2026f
FROM pfs.gpci WHERE year = 2026
)
SELECT p.*,
f.work_gpci_2026f, f.pe_gpci_2026f, f.mp_gpci_2026f,
round(p.work_gpci - f.work_gpci_2026f, 4) AS d_work,
round(p.pe_gpci - f.pe_gpci_2026f, 4) AS d_pe,
round(p.mp_gpci - f.mp_gpci_2026f, 4) AS d_mp,
round((p.work_gpci + p.pe_gpci + p.mp_gpci) / 3
- (f.work_gpci_2026f + f.pe_gpci_2026f + f.mp_gpci_2026f) / 3,
4) AS d_mean
FROM prop p
JOIN fin f USING (mac, locality)
""")
gpci_2027p
return (gpci_2027p,)
@app.cell(hide_code=True)
def _(AMBER, TEAL, alt, gpci_2027p, mo, pl, proposed_for):
_n = 15
_up = (gpci_2027p["d_mean"] > 0).sum()
_down = (gpci_2027p["d_mean"] < 0).sum()
_flat = (gpci_2027p["d_mean"] == 0).sum()
_movers = (
gpci_2027p.sort("d_mean", descending=True)
.head(_n)
.vstack(gpci_2027p.sort("d_mean").head(_n).sort("d_mean", descending=True))
.with_columns(
(pl.col("locality_name") + " (" + pl.col("state") + ")").alias("label"),
pl.when(pl.col("d_mean") >= 0)
.then(pl.lit("Gain"))
.otherwise(pl.lit("Loss"))
.alias("sign"),
)
)
gpci_movers_chart = (
alt.Chart(_movers.to_pandas())
.mark_bar()
.encode(
x=alt.X("d_mean:Q", title="Mean GPCI change (unweighted mean of work/PE/MP)"),
y=alt.Y("label:N", title=None, sort=alt.SortField(field="d_mean", order="descending")),
color=alt.Color(
"sign:N",
title=None,
scale=alt.Scale(domain=["Gain", "Loss"], range=[TEAL, AMBER]),
legend=alt.Legend(orient="top"),
),
tooltip=[
"locality_name", "state", "mac", "locality",
alt.Tooltip("d_work:Q", format="+.4f"),
alt.Tooltip("d_pe:Q", format="+.4f"),
alt.Tooltip("d_mp:Q", format="+.4f"),
],
)
.properties(
title="GPCI Movers — CY2027 Proposed vs. CY2026 Final, Top ±15 Localities",
width=640,
height=520,
)
)
mo.vstack(
[
gpci_movers_chart,
mo.md(f"""
Across the {gpci_2027p.height} payment localities matched to
the CY2026-final GPCI table: **{_up} rise, {_down} fall,
{_flat} unchanged** on the unweighted mean of the three
components (a GAF proxy — CMS's official GAFs in Addendum D
are utilization-weighted and not loaded here).
**Sources:** `pfs.gpci_proposed` (`CMS-1848-P`, gpci_year
2027) — CY2027 NPRM Addendum E,
{proposed_for(2027).federal_register_citation}. Baseline —
`pfs.gpci` year 2026 (CY2026 Final).
"""),
]
)
return
@app.cell(hide_code=True)
def _(mo, q):
_drift = q("""
WITH projected AS (
SELECT mac, locality, locality_name, state,
work_gpci AS w_1832, pe_gpci AS p_1832, mp_gpci AS m_1832
FROM pfs.gpci_proposed
WHERE cms_rule_id = 'CMS-1832-P' AND gpci_year = 2027
),
reproposed AS (
SELECT mac, locality,
work_gpci AS w_1848, pe_gpci AS p_1848, mp_gpci AS m_1848
FROM pfs.gpci_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND gpci_year = 2027
)
SELECT a.locality_name, a.state,
a.w_1832, b.w_1848, a.p_1832, b.p_1848, a.m_1832, b.m_1848,
round(abs(b.w_1848 - a.w_1832) + abs(b.p_1848 - a.p_1832)
+ abs(b.m_1848 - a.m_1832), 4) AS total_abs_drift
FROM projected a
JOIN reproposed b USING (mac, locality)
ORDER BY total_abs_drift DESC
""")
_changed = _drift.filter(_drift["total_abs_drift"] > 0)
if _changed.height == 0:
_msg = (
f"**All {_drift.height} localities carry identical CY2027 GPCIs in "
"both rulemakings** — the CY2027 NPRM re-proposes the CY2026 "
"NPRM's phase-in values unchanged."
)
else:
_msg = (
f"**{_changed.height} of {_drift.height} localities moved** between "
"the CY2026 NPRM's projected 2027 values and the CY2027 NPRM's "
"re-proposed values (largest movers below)."
)
_parts = [
mo.md(f"""
### Did CMS change its 2027 mind? — CMS-1832-P projection vs. CMS-1848-P proposal
{_msg}
""")
]
if _changed.height > 0:
_parts.append(
mo.ui.table(
_changed.head(15).to_pandas(),
label="Largest 1832-P → 1848-P CY2027 GPCI revisions",
)
)
mo.vstack(_parts)
return
@app.cell(hide_code=True)
def _(GRAY, INDIGO_MED, PLUM, alt, mo, pl, q):
# Long-term: highest- and lowest-cost localities (by CY2026-final mean
# GPCI) against the all-locality median, 2016-2026 final + 2027
# proposed (flagged).
_extremes = q("""
SELECT mac, locality, locality_name, state,
(work_gpci + pe_gpci + mp_gpci) / 3 AS mean_gpci
FROM pfs.gpci WHERE year = 2026
ORDER BY mean_gpci DESC
""")
_hi = _extremes.row(0, named=True)
_lo = _extremes.row(-1, named=True)
_hist = q(f"""
WITH sel AS (
SELECT year, locality_name, work_gpci, pe_gpci, mp_gpci
FROM pfs.gpci
WHERE (mac = '{_hi["mac"]}' AND locality = '{_hi["locality"]}')
OR (mac = '{_lo["mac"]}' AND locality = '{_lo["locality"]}')
),
med AS (
SELECT year, 'All-locality median' AS locality_name,
median(work_gpci) AS work_gpci,
median(pe_gpci) AS pe_gpci,
median(mp_gpci) AS mp_gpci
FROM pfs.gpci GROUP BY year
),
prop AS (
SELECT gpci_year AS year, locality_name, work_gpci, pe_gpci, mp_gpci
FROM pfs.gpci_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND gpci_year = 2027
AND ((mac = '{_hi["mac"]}' AND locality = '{_hi["locality"]}')
OR (mac = '{_lo["mac"]}' AND locality = '{_lo["locality"]}'))
),
propmed AS (
SELECT gpci_year AS year, 'All-locality median' AS locality_name,
median(work_gpci) AS work_gpci,
median(pe_gpci) AS pe_gpci,
median(mp_gpci) AS mp_gpci
FROM pfs.gpci_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND gpci_year = 2027
GROUP BY gpci_year
)
SELECT *, false AS proposed FROM sel
UNION ALL SELECT *, false FROM med
UNION ALL SELECT *, true FROM prop
UNION ALL SELECT *, true FROM propmed
ORDER BY year, locality_name
""")
_long = _hist.unpivot(
index=["year", "locality_name", "proposed"],
on=["work_gpci", "pe_gpci", "mp_gpci"],
variable_name="component",
value_name="gpci",
).with_columns(
pl.col("component").replace(
{"work_gpci": "Work", "pe_gpci": "PE", "mp_gpci": "MP"}
)
)
_domain = [_hi["locality_name"], _lo["locality_name"], "All-locality median"]
_range = [INDIGO_MED, PLUM, GRAY]
_charts = []
for _comp in ("Work", "PE", "MP"):
_cdf = _long.filter(pl.col("component") == _comp).to_pandas()
_line = (
alt.Chart(_cdf[~_cdf["proposed"]])
.mark_line(point=True)
.encode(
x=alt.X("year:O", title="Year"),
y=alt.Y("gpci:Q", title="GPCI", scale=alt.Scale(zero=False)),
color=alt.Color(
"locality_name:N",
title=None,
scale=alt.Scale(domain=_domain, range=_range),
legend=alt.Legend(orient="bottom") if _comp == "PE" else None,
),
tooltip=["locality_name", "year:O", alt.Tooltip("gpci:Q", format=".4f")],
)
)
_prop_pts = (
alt.Chart(_cdf[_cdf["proposed"]])
.mark_point(shape="diamond", size=90, filled=True)
.encode(
x="year:O",
y="gpci:Q",
color=alt.Color(
"locality_name:N",
scale=alt.Scale(domain=_domain, range=_range),
legend=None,
),
tooltip=["locality_name", "year:O", alt.Tooltip("gpci:Q", format=".4f")],
)
)
_charts.append(
(_line + _prop_pts).properties(title=f"{_comp} GPCI", width=240, height=220)
)
gpci_longterm_chart = alt.hconcat(*_charts)
mo.vstack(
[
gpci_longterm_chart,
mo.md(f"""
Final-rule GPCIs 2016-2026 (`pfs.gpci`) for the
highest-cost locality (**{_hi["locality_name"]}**,
{_hi["state"]}) and lowest-cost locality
(**{_lo["locality_name"]}**, {_lo["state"]}) by CY2026 mean
GPCI, against the all-locality median. Diamond markers at
2027 are the **CY2027 NPRM proposal — final rule pending**,
with the work-floor caveat above applying to the Work
panel.
"""),
]
)
return
@app.cell(hide_code=True)
def _(gpci_2027p, mo, pfs_payment, pl, proposed_for, q):
# Locality-adjusted payment for the Section-1 example trio at the
# best/worst CY2027-proposed localities vs the national (GPCI=1.0)
# rate, all priced at the CY2027 proposed non-QP CF.
_best = gpci_2027p.sort("d_mean", descending=True).row(0, named=True)
_worst = gpci_2027p.sort("d_mean").row(0, named=True)
_rvu = q("""
SELECT hcpcs, description, work_rvu, non_fac_pe_rvu, mp_rvu
FROM pfs.rvu
WHERE year = 2026 AND (mod IS NULL OR mod = '')
AND hcpcs IN ('99213', '27447', '70553')
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
""")
_localities = pl.DataFrame(
[
{
"mac": r["mac"],
"locality": r["locality"],
"where": f"{r['locality_name']} ({r['state']})",
"work_gpci": r["work_gpci"],
"pe_gpci": r["pe_gpci"],
"mp_gpci": r["mp_gpci"],
}
for r in (_best, _worst)
]
+ [
{
"mac": "NATL",
"locality": "00",
"where": "National (GPCI = 1.0)",
"work_gpci": 1.0,
"pe_gpci": 1.0,
"mp_gpci": 1.0,
}
]
)
_rvu_x_loc = _rvu.join(_localities.select("mac", "locality"), how="cross")
_priced = pfs_payment(
_rvu_x_loc,
_localities.select("mac", "locality", "work_gpci", "pe_gpci", "mp_gpci"),
cf=proposed_for(2027).conversion_factor,
)
gpci_payment_table = (
_priced.join(_localities.select("mac", "locality", "where"), on=["mac", "locality"])
.with_columns(pl.col("payment_amount").round(2))
.pivot(on="where", index=["hcpcs", "description"], values="payment_amount")
)
mo.vstack(
[
mo.ui.table(gpci_payment_table.to_pandas(), label="Locality-adjusted payment — CY2027 proposed non-QP CF"),
mo.md(f"""
`pfs.calcs.payment.payment` (the module's locality formula)
applied to CY2026-final RVUs at the CY2027 **proposed**
non-QP CF ({proposed_for(2027).conversion_factor}) — final
rule pending. Localities shown are the CY2027 proposal's
biggest mean-GPCI gainer (**{_best["locality_name"]}**) and
decliner (**{_worst["locality_name"]}**), with the
national GPCI = 1.0 rate for reference.
"""),
]
)
return
# ── 4. Primary Care Services (ACO attribution set) ──────────────────────
@app.cell(hide_code=True)
def _(mo, q):
pcs_codes = q("""
SELECT hcpcs_code AS hcpcs, trim(description) AS pcs_description
FROM cms.primary_care_service_code
ORDER BY hcpcs
""")
assert pcs_codes.height == 109, (
f"expected 109 designated primary care service codes, got {pcs_codes.height}"
)
mo.md(f"""
## 4. Primary Care Services — the ACO Attribution Set
The **{pcs_codes.height} designated primary care service codes**
used for Medicare Shared Savings Program beneficiary assignment (42
CFR § 425.400(c)), read live from
`cms.primary_care_service_code` — the attribution reference list
published to the lake from the aco module. These are the services
whose billing patterns decide which ACO a beneficiary is attributed
to, so their PFS valuation moves both clinician revenue **and** the
attribution denominators every MSSP financial calculation stands
on.
Two disclosures, once: (1) this is the **current-law list applied
retrospectively** — the designation set itself changes across
rulemakings and is not versioned by year in this repo; (2) all
aggregate statements are **unweighted by utilization** (the lake
carries no claims-volume reference), so each code counts equally.
""")
return (pcs_codes,)
@app.cell(hide_code=True)
def _(RULES, pl, proposed_for, q):
_pqm_raw = q("""
WITH pcs AS (
SELECT hcpcs_code AS hcpcs, trim(description) AS pcs_description
FROM cms.primary_care_service_code
),
prop AS (
SELECT hcpcs, status_code AS status_2027p,
work_rvu AS work_2027p,
work_rvu + coalesce(non_fac_pe_rvu, fac_pe_rvu) + mp_rvu
AS total_2027p
FROM pfs.rvu_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND (mod IS NULL OR mod = '')
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
),
fin AS (
SELECT hcpcs, status_code AS status_2026f,
work_rvu AS work_2026f,
work_rvu + coalesce(non_fac_pe_rvu, fac_pe_rvu) + mp_rvu
AS total_2026f
FROM pfs.rvu
WHERE year = 2026 AND (mod IS NULL OR mod = '')
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
)
SELECT pcs.hcpcs, pcs.pcs_description,
fin.status_2026f, fin.work_2026f, fin.total_2026f,
prop.status_2027p, prop.work_2027p, prop.total_2027p
FROM pcs
LEFT JOIN fin USING (hcpcs)
LEFT JOIN prop USING (hcpcs)
ORDER BY pcs.hcpcs
""")
_cf26 = RULES[2026].conversion_factor
_cf27p = proposed_for(2027).conversion_factor
pqm_near = _pqm_raw.with_columns(
(pl.col("total_2026f") * _cf26).round(2).alias("dollar_2026final"),
(pl.col("total_2027p") * _cf27p).round(2).alias("dollar_2027proposed"),
).with_columns(
(pl.col("dollar_2027proposed") - pl.col("dollar_2026final"))
.round(2)
.alias("dollar_delta"),
pl.when(pl.col("total_2026f").is_not_null() & pl.col("total_2027p").is_not_null())
.then(
(100 * (pl.col("dollar_2027proposed") - pl.col("dollar_2026final"))
/ pl.col("dollar_2026final")).round(2)
)
.alias("pct_change"),
pl.when(pl.col("total_2026f").is_not_null() & pl.col("total_2027p").is_not_null())
.then(pl.lit("both"))
.when(pl.col("total_2027p").is_not_null())
.then(pl.lit("new_in_2027"))
.when(pl.col("total_2026f").is_not_null())
.then(pl.lit("missing_from_2027"))
.otherwise(pl.lit("absent_both"))
.alias("coverage"),
)
assert pqm_near.height == 109, pqm_near.height
return (pqm_near,)
@app.cell(hide_code=True)
def _(RULES, mo, pqm_near, proposed_for):
mo.vstack(
[
mo.md(f"""
### Near-term — every designated code, CY2026 final vs. CY2027 proposed
One row per designated code: CY2026-final and
CY2027-proposed work/total RVUs (non-facility PE where
priced, facility otherwise), dollars at each vintage's own
standard CF (CY2026 final {RULES[2026].conversion_factor};
CY2027 proposed non-QP
{proposed_for(2027).conversion_factor} — **final rule
pending**), national unadjusted; Section 3 carries the
locality adjustment.
"""),
mo.ui.table(
pqm_near.sort("pct_change", nulls_last=True).to_pandas(),
page_size=25,
label="All 109 designated primary care services — near-term comparison",
),
]
)
return
@app.cell(hide_code=True)
def _(AMBER, TEAL, alt, mo, pl, pqm_near):
_both = pqm_near.filter(pl.col("coverage") == "both").with_columns(
pl.when(pl.col("pct_change") >= 0)
.then(pl.lit("Gain"))
.otherwise(pl.lit("Loss"))
.alias("sign")
)
_one_sided = pqm_near.filter(pl.col("coverage") != "both")
pqm_near_chart = (
alt.Chart(_both.to_pandas())
.mark_bar()
.encode(
x=alt.X("pct_change:Q", title="% change in national payment, CY2026 final → CY2027 proposed"),
y=alt.Y("hcpcs:N", title=None, sort=alt.SortField(field="pct_change", order="descending")),
color=alt.Color(
"sign:N",
title=None,
scale=alt.Scale(domain=["Gain", "Loss"], range=[TEAL, AMBER]),
legend=alt.Legend(orient="top"),
),
tooltip=[
"hcpcs", "pcs_description",
alt.Tooltip("dollar_2026final:Q", format="$.2f"),
alt.Tooltip("dollar_2027proposed:Q", format="$.2f"),
alt.Tooltip("pct_change:Q", format="+.2f"),
],
)
.properties(
title="Designated Primary Care Services — % Payment Change (every matched code)",
width=640,
height=max(300, 11 * _both.height),
)
)
_sided_lines = "\n".join(
f" - `{r['hcpcs']}` — {r['pcs_description']} (*{r['coverage'].replace('_', ' ')}*)"
for r in _one_sided.iter_rows(named=True)
)
mo.vstack(
[
pqm_near_chart,
mo.md(f"""
**{_both.height} of 109** designated codes price in both
tables and appear above. The remaining
**{_one_sided.height}** are one-sided or unpriced and are
listed explicitly rather than plotted with a fabricated
baseline:
{_sided_lines}
The one-sided groups are computed live, so their
composition tracks the data: as of this ingest every
designated code that prices anywhere prices in **both**
tables (the APCM G-codes already carry CY2026-final RVUs),
and the unmatched remainder is entirely `absent_both` —
deleted or replaced legacy codes (telephone E/M 99441-3,
prolonged 99354-5, consult-era visits, pre-APCM
check-in/CCM G-codes) that stay on the attribution
designation list without a current PFS price.
"""),
]
)
return
@app.cell(hide_code=True)
def _(RULES, pl, proposed_for, q):
_hist = q("""
WITH pcs AS (
SELECT hcpcs_code AS hcpcs, trim(description) AS pcs_description
FROM cms.primary_care_service_code
),
fin AS (
SELECT year, hcpcs,
work_rvu + coalesce(non_fac_pe_rvu, fac_pe_rvu) + mp_rvu
AS total_rvu
FROM pfs.rvu
WHERE (mod IS NULL OR mod = '')
QUALIFY row_number() OVER (PARTITION BY hcpcs, year ORDER BY hcpcs) = 1
),
prop AS (
SELECT 2027 AS year, hcpcs,
work_rvu + coalesce(non_fac_pe_rvu, fac_pe_rvu) + mp_rvu
AS total_rvu
FROM pfs.rvu_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND (mod IS NULL OR mod = '')
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
)
SELECT pcs.hcpcs, pcs.pcs_description, u.year, u.total_rvu,
u.year = 2027 AS proposed
FROM pcs
JOIN (SELECT * FROM fin UNION ALL SELECT * FROM prop) u USING (hcpcs)
WHERE u.total_rvu IS NOT NULL
ORDER BY pcs.hcpcs, u.year
""")
_cf = pl.DataFrame(
{
"year": [y for y in sorted(RULES) if y >= 2015] + [2027],
"cf": [RULES[y].conversion_factor for y in sorted(RULES) if y >= 2015]
+ [proposed_for(2027).conversion_factor],
}
)
pqm_long = (
_hist.join(_cf, on="year")
.with_columns((pl.col("total_rvu") * pl.col("cf")).round(2).alias("dollar"))
.with_columns(
(
100
* (pl.col("total_rvu") / pl.col("total_rvu").shift(1).over("hcpcs") - 1)
)
.round(2)
.alias("rvu_yoy_pct")
)
)
return (pqm_long,)
@app.cell(hide_code=True)
def _(AMBER, TEAL, alt, mo, pqm_long):
_hm = pqm_long.to_pandas()
pqm_heatmap = (
alt.Chart(_hm)
.mark_rect(stroke="#F7F5F0", strokeWidth=1)
.encode(
x=alt.X("year:O", title="Year (2027 = proposed)"),
y=alt.Y("hcpcs:N", title=None),
color=alt.Color(
"rvu_yoy_pct:Q",
title="RVU YoY %",
scale=alt.Scale(domainMid=0, range=[AMBER, "#EDEBE6", TEAL]),
),
tooltip=[
"hcpcs", "pcs_description", "year:O",
alt.Tooltip("total_rvu:Q", format=".2f"),
alt.Tooltip("dollar:Q", format="$.2f"),
alt.Tooltip("rvu_yoy_pct:Q", format="+.2f"),
],
)
.properties(
title="Total-RVU Year-over-Year % Change — Every Designated Code, 2015-2027p",
width=560,
height=11 * _hm["hcpcs"].nunique(),
)
)
mo.vstack(
[
pqm_heatmap,
mo.md("""
Long-term valuation of **each individual designated code**:
cells are year-over-year % change in total RVUs (first
available year blank). Sustained white rows are codes CMS
has left untouched for a decade; the visible vertical band
at 2021 is the office/outpatient E/M revaluation. Hover any
cell for that code-year's RVUs and dollars.
"""),
]
)
return
@app.cell(hide_code=True)
def _(INDIGO_MED, alt, mo, pl, pqm_long):
_span = (
pqm_long.sort("hcpcs", "year")
.group_by("hcpcs", "pcs_description")
.agg(
pl.col("dollar").first().alias("dollar_first"),
pl.col("dollar").last().alias("dollar_last"),
pl.len().alias("n_years"),
)
.filter(pl.col("n_years") >= 5)
.with_columns((pl.col("dollar_last") - pl.col("dollar_first")).abs().alias("abs_move"))
.sort("abs_move", descending=True)
.head(12)
)
_sel = pqm_long.filter(pl.col("hcpcs").is_in(_span["hcpcs"]))
_rows = []
_codes = _span["hcpcs"].to_list()
for _i in range(0, len(_codes), 4):
_row_charts = []
for _code in _codes[_i : _i + 4]:
_cdf = _sel.filter(pl.col("hcpcs") == _code).to_pandas()
_solid = (
alt.Chart(_cdf[~_cdf["proposed"]])
.mark_line(point=True, color=INDIGO_MED)
.encode(
x=alt.X("year:O", title=None, axis=alt.Axis(values=[2015, 2020, 2026])),
y=alt.Y("dollar:Q", title=None, scale=alt.Scale(zero=False)),
tooltip=["hcpcs", "year:O", alt.Tooltip("dollar:Q", format="$.2f")],
)
)
_prop = (
alt.Chart(_cdf[_cdf["year"] >= 2026])
.mark_line(point=alt.OverlayMarkDef(shape="diamond", size=70), color=INDIGO_MED, strokeDash=[4, 3])
.encode(
x="year:O",
y="dollar:Q",
tooltip=["hcpcs", "year:O", alt.Tooltip("dollar:Q", format="$.2f")],
)
)
_row_charts.append(
(_solid + _prop).properties(title=str(_code), width=150, height=120)
)
_rows.append(alt.hconcat(*_row_charts))
pqm_multiples = alt.vconcat(*_rows)
mo.vstack(
[
pqm_multiples,
mo.md("""
The 12 designated codes with the largest absolute national
dollar move across their observed span (min. 5 priced
years). Solid line = final rules at each year's own final
CF; dashed segment to the diamond = **CY2027 proposed, at
the proposed non-QP CF — final rule pending**. Dollar
trajectories mix RVU changes with CF changes deliberately —
this is realized-price history, not resource-weight
history (the heatmap above isolates RVUs).
"""),
]
)
return
@app.cell(hide_code=True)
def _(gpci_2027p, mo, pl, pqm_near, proposed_for):
# Cross-cut: what the GPCI proposal does to one bread-and-butter
# attribution code (99213) at the Section-3 extreme localities.
_r = pqm_near.filter(pl.col("hcpcs") == "99213").row(0, named=True)
_best = gpci_2027p.sort("d_mean", descending=True).row(0, named=True)
_worst = gpci_2027p.sort("d_mean").row(0, named=True)
_natl = _r["dollar_2027proposed"]
_rows = pl.DataFrame(
[
{
"where": f"{loc['locality_name']} ({loc['state']})",
"work_gpci": loc["work_gpci"],
"pe_gpci": loc["pe_gpci"],
"mp_gpci": loc["mp_gpci"],
"mean_gpci_delta_vs_2026f": loc["d_mean"],
}
for loc in (_best, _worst)
]
)
mo.vstack(
[
mo.md(f"""
### GPCI × attribution cross-cut — 99213 under the CY2027 proposal
`99213` (established-patient office visit — the single most
attribution-relevant E/M code) prices at
**${_natl:.2f}** nationally under the CY2027 proposed
non-QP CF. At the proposal's extreme localities that
becomes a spread of locality-adjusted rates (Section 3's
payment table carries the exact figures for the example
trio); the GPCI movement below is what drives it.
"""),
mo.ui.table(_rows.to_pandas(), label="Extreme localities — proposed CY2027 GPCIs (vs CY2026-final mean)"),
]
)
return
# ── 5. Advanced APM ─────────────────────────────────────────────────────
@app.cell(hide_code=True)
def _(mo):
mo.md("""
## 5. Advanced APM (QP) Requirements
Under MACRA 2015, clinicians who participate heavily enough in an
Advanced Alternative Payment Model (Advanced APM) during a "QP
@@ -722,20 +1506,23 @@ def _(alt, mo, proposed_for, qp_differential):
return
# ── 4. Provenance ───────────────────────────────────────────────────────
# ── 6. Provenance ───────────────────────────────────────────────────────
@app.cell(hide_code=True)
def _(mo):
mo.md("""
## 4. Provenance & Comment-Period Status
## 6. Provenance & Comment-Period Status
Ingest-log entries (`cms.ingest_log`) for every table this notebook
reads from — `pfs.rvu` and `pfs.rvu_proposed` — so every figure
above can be traced back to a specific ingest run, source file, and
Federal Register citation. (`pfs.gpci` is not queried anywhere in
this notebook — see the intro's GPCI = 1.0 scope note — so it is
deliberately excluded here rather than claimed as a source.)
Ingest-log entries (`cms.ingest_log`) for every FR-ingested table
this notebook reads — `pfs.rvu`, `pfs.rvu_proposed`, `pfs.gpci`,
and `pfs.gpci_proposed` — so every figure above can be traced back
to a specific ingest run, source file, and Federal Register
citation. `cms.primary_care_service_code` (Section 4's 109-code
attribution list) is a reference table published from the aco
module's CCLF/attribution data, not an FR ingest, so it carries no
ingest-log row; its provenance is the MSSP assignment
specification (42 CFR § 425.400(c)).
""")
return
@@ -768,7 +1555,8 @@ def _(mo, q):
SELECT run_id, ingested_at, module, table_name, rule_id,
source_file, sha256, rows, fr_citation, pincite_key
FROM cms.ingest_log
WHERE table_name IN ('pfs.rvu', 'pfs.rvu_proposed')
WHERE table_name IN ('pfs.rvu', 'pfs.rvu_proposed',
'pfs.gpci', 'pfs.gpci_proposed')
ORDER BY ingested_at DESC
""")
mo.ui.table(provenance.to_pandas(), label="Ingest Log — Tables Used in This Notebook")