Files
stack/notebooks/cy2027_pfs_proposed_rule.py
kert c429a29cdd
All checks were successful
CI / lint (push) Successful in 29s
CI / notebooks-smoke (push) Successful in 1m25s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 1m9s
Infra CI / zotero (push) Successful in 24s
Infra CI / docs (push) Successful in 16s
Infra CI / api (push) Successful in 2m13s
Infra CI / llm (push) Successful in 1m12s
Infra CI / mc (push) Successful in 19s
Deploy / report (push) Successful in 12s
CI / test (push) Successful in 13m29s
fix(notebooks): route CY2027 NPRM citations through bib.frlink/cfrlink (closes #642)
The CY2027 notebook defined the fr_md/cfr_md jump-link wrappers (P40
#638, P41 #641) but only used them in four spots — every other FR
citation rendered as plain text. All simple 'NN FR NNNNN' cites (intro,
CF-walk sources, BN-decomposition narrative, RVU-delta and GPCI
sources, cross-NPRM cell, QPP proposal, QP-differential sources,
comment-period callout) and interpolated registry citations now go
through fr_md(), and the bare 42 CFR 414.1430(a) cite through cfr_md().
Compound qpp citations ('91 FR 44260, 44285-44286') stay plain — the
resolver grammar takes one locator. All six page cites verified against
the bib anchor maps via stack bib fr-jump; headless re-run renders 18
federalregister.gov + 4 ecfr.gov links.
2026-08-18 10:08:19 -04:00

1596 lines
60 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import marimo
__generated_with = "0.23.1"
app = marimo.App(width="medium")
@app.cell(hide_code=True)
def _():
import marimo as mo
return (mo,)
@app.cell(hide_code=True)
def _(fr_md, mo):
mo.md(f"""
# CY2027 PFS Proposed Rule — Financial Changes & Advanced APM
CMS's CY2027 Physician Fee Schedule NPRM (CMS-1848-P, {fr_md("91 FR 43842")},
published July 16, 2026; docket CMS-2026-2377) is a **proposed rule
only** — there is no CY2027 Final Rule (CMS-1848-F) yet, and every
"CY2027" figure below is a proposal, not a finalized payment
parameter. The comment period, thresholds, and Advanced-APM
schedule captured here can all still change before the Final Rule
ships.
**What this means:** every number below is read live from
`pfs.rules.RULES` / `pfs.rules.PROPOSED` (via `proposed_for(2027)`)
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. 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
@app.cell(hide_code=True)
def _():
import altair as alt
import polars as pl
from conf import connect
from conf.display import plain_years
from pfs.rules import RULES, proposed_for
from qpp import QPP, for_payment_year
connect.theme()
# connect.theme() puts assets/ on sys.path — reuse the HTI-5 design
# 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.
con = connect.ducklake()
def q(sql):
return con.execute(sql).pl()
def cfr_md(ref, text=""):
"""eCFR jump link as markdown (P41, #641) — deterministic
cite→URL transform, no store needed; degrades to plain text on
a malformed cite so the notebook never breaks."""
try:
from bib import cfrlink
return cfrlink.md_link(ref, text=text)
except Exception:
return text or ref
def fr_md(ref, text=""):
"""FR web jump link as markdown (P40, #638) — page, ¶-ordinal,
or quote refs resolve against the bib anchor map; degrades to
plain text when bib.sqlite or the map is unavailable so the
notebook never breaks offline."""
try:
from bib import frlink
return frlink.md_link(ref, store=connect.bib(), text=text or ref)
except Exception:
return text or ref
return (
AMBER,
GRAY,
INDIGO_MED,
PLUM,
QPP,
RULES,
TEAL,
alt,
con,
cfr_md,
for_payment_year,
fr_md,
pfs_payment,
pl,
plain_years,
proposed_for,
q,
)
# ── 1. The conversion-factor walk ─────────────────────────────────────
@app.cell(hide_code=True)
def _(mo):
mo.md("""
## 1. The Conversion Factor Walk — CY2026 Final → CY2027 Proposed
CY2026 was the first year the PFS published a split conversion
factor — a qualifying-APM (QP) track and a nonqualifying-APM
(non-QP) track, each with a standard and an anesthesia CF. The
CY2027 NPRM proposes new values for all four; the chart below
places the CY2026 **Final Rule** values (the only finalized
baseline that exists) alongside the CY2027 **NPRM proposal**
(final rule pending).
""")
return
@app.cell(hide_code=True)
def _(RULES, pl, proposed_for):
_cy26 = RULES[2026]
_prop = proposed_for(2027)
_rows = [
{"vintage": "CY2026 Final", "family": "Standard", "track": "Non-QP", "cf": _cy26.conversion_factor},
{"vintage": "CY2027 Proposed (final rule pending)", "family": "Standard", "track": "Non-QP", "cf": _prop.conversion_factor},
{"vintage": "CY2026 Final", "family": "Standard", "track": "QP", "cf": _cy26.cf_qp},
{"vintage": "CY2027 Proposed (final rule pending)", "family": "Standard", "track": "QP", "cf": _prop.cf_qp},
{"vintage": "CY2026 Final", "family": "Anesthesia", "track": "Non-QP", "cf": _cy26.anesthesia_cf},
{"vintage": "CY2027 Proposed (final rule pending)", "family": "Anesthesia", "track": "Non-QP", "cf": _prop.anesthesia_cf},
{"vintage": "CY2027 Proposed (final rule pending)", "family": "Anesthesia", "track": "QP", "cf": _prop.anesthesia_cf_qp},
# NOTE: RULES[2026].anesthesia_cf models the non-QP anesthesia CF
# only — the CY2026 Final Rule anesthesia QP CF is not captured
# in the registry (see pfs.rules module docstring), so that bar
# is deliberately omitted rather than guessed. No CY2027-final
# bars exist anywhere in this table — there is no Final Rule yet.
]
cf_walk = pl.DataFrame(_rows)
cf_walk
return (cf_walk,)
@app.cell(hide_code=True)
def _(alt, cf_walk, fr_md, mo):
_vintage_order = ["CY2026 Final", "CY2027 Proposed (final rule pending)"]
_track_domain = ["Non-QP", "QP"]
cf_chart = (
alt.Chart(cf_walk.to_pandas())
.mark_bar()
.encode(
x=alt.X("vintage:N", title=None, sort=_vintage_order),
xOffset=alt.XOffset("track:N", sort=_track_domain),
y=alt.Y("cf:Q", title="Conversion factor ($/RVU)", scale=alt.Scale(zero=False)),
color=alt.Color("track:N", title="Track", scale=alt.Scale(domain=_track_domain)),
tooltip=["vintage", "family", "track", alt.Tooltip("cf:Q", format="$.4f")],
)
.properties(width=300, height=280)
.facet(column=alt.Column("family:N", title=None))
.resolve_scale(y="independent")
.properties(title="CY2026 Final vs. CY2027 Proposed PFS Conversion Factors")
)
mo.vstack(
[
cf_chart,
mo.md(f"""
*CY2026 Final anesthesia QP is not shown — that CF is not
captured in the `pfs.rules` registry (see module docstring).
No CY2027-final bars appear anywhere in this notebook — the
Final Rule (CMS-1848-F) has not been published.*
**Sources:** CY2026 Final Rule — {fr_md("90 FR 49266")}.
CY2027 NPRM — {fr_md("91 FR 43842")} (CMS-1848-P).
"""),
]
)
return
@app.cell(hide_code=True)
def _(fr_md, mo):
mo.md(f"""
### Budget-neutrality adjustor decomposition
CMS's own NPRM narrative ({fr_md("91 FR 44242")}) describes the CY2027 CF
derivation explicitly: start from the **CY2026 conversion factors
with the one-time 2.50% statutory increase backed out**, multiply
by the 0.53% budget-neutrality adjustment, then multiply by the
section 1848(d)(20) qualifying/nonqualifying-APM annual update
(+0.75% / +0.25%). Reproducing that arithmetic against the
registry's actual CY2026-final and CY2027-proposed CFs is a useful
cross-check that the registry's transcription is internally
consistent with CMS's narrative — for the **standard** CF only;
the anesthesia CFs reflect "the same overall PFS adjustments with
the addition of anesthesia-specific PE and MP adjustments"
({fr_md("91 FR 44242")}) that are not modeled as separate registry fields, so they
are excluded from this reconstruction rather than approximated.
""")
return
@app.cell(hide_code=True)
def _(RULES, fr_md, mo, pl, proposed_for):
_cy26 = RULES[2026]
_prop = proposed_for(2027)
# CMS's own stated CF-derivation components for CY2027 (91 FR 44242,
# file lines ~39088-39106): back the CY2026 CFs out of the one-time
# 2.50% statutory increase, then reapply BN + the annual update.
# NONE of these bare percentages is a field anywhere in `pfs.rules`
# or `qpp` — there is nothing to attribute-access for them. They are
# kept as literals ONLY because CMS's narrative states them as bare
# numbers, not derived from any other registry value; this is a
# cited transcription of that narrative, not a second source of
# truth for `budget_neutrality_adjustor` or the CFs themselves
# (both of which — `bn_adjustor` / `registry_cf` below — ARE read
# live from the registry).
_ONE_TIME_BACKOUT = 1.0250 # CY2026's one-time +2.50% statutory increase, backed out per 91 FR 44242
_ANNUAL_UPDATE_MULT = {
"Non-QP": 1.0025, # +0.25%/yr nonqualifying-APM update, sec. 1848(d)(20), 91 FR 44242
"QP": 1.0075, # +0.75%/yr qualifying-APM update, sec. 1848(d)(20), 91 FR 44242
}
def _reconstruct(cy2026_final_cf, track, bn_adjustor):
return (cy2026_final_cf / _ONE_TIME_BACKOUT) * bn_adjustor * _ANNUAL_UPDATE_MULT[track]
_bn_rows = [
{
"track": "Non-QP",
"cy2026_final_cf": _cy26.conversion_factor,
"one_time_backout": _ONE_TIME_BACKOUT,
"bn_adjustor": _prop.budget_neutrality_adjustor,
"annual_update_mult": _ANNUAL_UPDATE_MULT["Non-QP"],
"reconstructed_cy2027_proposed_cf": round(
_reconstruct(_cy26.conversion_factor, "Non-QP", _prop.budget_neutrality_adjustor), 4
),
"registry_cy2027_proposed_cf": _prop.conversion_factor,
},
{
"track": "QP",
"cy2026_final_cf": _cy26.cf_qp,
"one_time_backout": _ONE_TIME_BACKOUT,
"bn_adjustor": _prop.budget_neutrality_adjustor,
"annual_update_mult": _ANNUAL_UPDATE_MULT["QP"],
"reconstructed_cy2027_proposed_cf": round(
_reconstruct(_cy26.cf_qp, "QP", _prop.budget_neutrality_adjustor), 4
),
"registry_cy2027_proposed_cf": _prop.cf_qp,
},
]
bn_decomp = pl.DataFrame(_bn_rows).with_columns(
(pl.col("reconstructed_cy2027_proposed_cf") - pl.col("registry_cy2027_proposed_cf"))
.abs()
.round(4)
.alias("abs_diff")
)
mo.vstack(
[
bn_decomp,
mo.md(f"""
`reconstructed_cf = (CY2026_final_cf / one_time_backout) ×
bn_adjustor × annual_update_mult` — `bn_adjustor` and
`registry_cy2027_proposed_cf` are read live from
`RULES[2026]` / `proposed_for(2027)`. `one_time_backout`
({_ONE_TIME_BACKOUT}) and `annual_update_mult`
({_ANNUAL_UPDATE_MULT["Non-QP"]} non-QP /
{_ANNUAL_UPDATE_MULT["QP"]} QP) are **cited transcriptions
of CMS's NPRM narrative ({fr_md("91 FR 44242")},
`data/fr_downloads/2026-14327.txt:39088-39106`)**, not
registry fields — no field in `pfs.rules` or `qpp` models
the 2.50%/0.53%/0.25%/0.75% components individually, only
their combined effect on `conversion_factor` / `cf_qp`.
"""),
]
)
return
@app.cell(hide_code=True)
def _(mo, proposed_for):
_prop = proposed_for(2027)
mo.vstack(
[
mo.md("### Caveat — a drafting error in the NPRM's own summary section"),
mo.callout(
mo.md(f"""
`pfs.rules.PROPOSED[2027].notes` documents a transcription
caveat that is worth surfacing here rather than only in
source comments — quoted live below, not retyped:
> {_prop.notes}
"""),
kind="warn",
),
]
)
return
# ── 2. RVU-level deltas ────────────────────────────────────────────────
@app.cell(hide_code=True)
def _(mo):
mo.md("""
## 2. RVU-Level Deltas — Which HCPCS Codes Move the Most
`pfs.rvu_proposed` filtered to `cms_rule_id = 'CMS-1848-P'` (14,518
rows as of this ingest) is compared against `pfs.rvu` for CY2026
(the current final baseline — there is no CY2027 final table to
compare against). Both tables are deduplicated to one row per
HCPCS base code (no modifier) via `QUALIFY row_number() ... = 1`
per the P36 convention, `status_code = 'A'` (actively priced), and
— for the proposed table specifically — a non-null non-facility PE
RVU, since a meaningful share of `pfs.rvu_proposed` rows carry a
null non-facility *or* facility PE RVU (CMS's Addendum B only
populates the setting a code is actually priced in).
""")
return
@app.cell(hide_code=True)
def _(q):
rvu_delta_raw = q("""
WITH proposed AS (
SELECT hcpcs, description, work_rvu, non_fac_pe_rvu, mp_rvu,
work_rvu + non_fac_pe_rvu + mp_rvu AS total_nf_rvu
FROM pfs.rvu_proposed
WHERE cms_rule_id = 'CMS-1848-P'
AND (mod IS NULL OR mod = '')
AND status_code = 'A'
AND non_fac_pe_rvu IS NOT NULL
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
),
final_2026 AS (
SELECT hcpcs, work_rvu + non_fac_pe_rvu + mp_rvu AS total_nf_rvu
FROM pfs.rvu
WHERE year = 2026 AND (mod IS NULL OR mod = '') AND status_code = 'A'
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
)
SELECT
p.hcpcs,
p.description,
p.total_nf_rvu AS total_nf_rvu_proposed,
f26.total_nf_rvu AS total_nf_rvu_2026final
FROM proposed p
JOIN final_2026 f26 USING (hcpcs)
""")
rvu_delta_raw
return (rvu_delta_raw,)
@app.cell(hide_code=True)
def _(RULES, pl, proposed_for, rvu_delta_raw):
_cf_2026_final = RULES[2026].conversion_factor
_cf_2027_proposed = proposed_for(2027).conversion_factor
rvu_delta = rvu_delta_raw.with_columns(
(pl.col("total_nf_rvu_proposed") * _cf_2027_proposed).round(2).alias("dollar_2027proposed"),
(pl.col("total_nf_rvu_2026final") * _cf_2026_final).round(2).alias("dollar_2026final"),
).with_columns(
(pl.col("dollar_2027proposed") - pl.col("dollar_2026final")).round(2).alias("delta_vs_2026final"),
)
rvu_delta
return (rvu_delta,)
@app.cell(hide_code=True)
def _(AMBER, RULES, TEAL, alt, fr_md, mo, proposed_for, rvu_delta):
_n = 20
_winners = rvu_delta.sort("delta_vs_2026final", descending=True).head(_n)
_losers = rvu_delta.sort("delta_vs_2026final", descending=False).head(_n)
_top40 = _winners.vstack(_losers)
_plot_df = _top40.to_pandas()
_plot_df["label"] = _plot_df["hcpcs"] + "" + _plot_df["description"].str.slice(0, 40)
_plot_df["sign"] = _plot_df["delta_vs_2026final"].apply(lambda v: "Gain" if v >= 0 else "Loss")
winners_losers_chart = (
alt.Chart(_plot_df)
.mark_bar()
.encode(
x=alt.X("delta_vs_2026final:Q", title="$ change, non-QP CF (national unadjusted, GPCI=1.0)"),
y=alt.Y("label:N", title=None, sort=alt.SortField(field="delta_vs_2026final", 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", "description", alt.Tooltip("delta_vs_2026final:Q", format="$.2f")],
)
.properties(
title="Top 20 Winners / Top 20 Losers — CY2027 Proposed vs. CY2026 Final",
width=700,
height=600,
)
)
mo.vstack(
[
winners_losers_chart,
mo.md(f"""
**Sources:** `pfs.rvu_proposed` (`CMS-1848-P`) — CY2027 NPRM,
{fr_md(proposed_for(2027).federal_register_citation)}. Baseline —
`pfs.rvu` CY2026 Final ({fr_md(RULES[2026].federal_register_citation)}).
Both sides priced with each vintage's own non-QP standard CF
— this delta blends RVU-table changes with the proposed CF
change, it is not an RVU-only comparison.
"""),
]
)
return
@app.cell(hide_code=True)
def _(mo, proposed_for, rvu_delta):
mo.vstack(
[
mo.md(f"""
**Full detail — {rvu_delta.height:,} codes** matched across
the `CMS-1848-P` proposed RVU table and CY2026-final RVU
table (search the table below by HCPCS or description).
"""),
mo.ui.table(
rvu_delta.sort("delta_vs_2026final").to_pandas(),
page_size=25,
label="RVU / Payment Deltas by HCPCS",
),
]
)
return
@app.cell(hide_code=True)
def _(con, fr_md, mo, proposed_for):
_new = con.execute("""
SELECT count(*) FROM pfs.rvu_proposed p
WHERE p.cms_rule_id = 'CMS-1848-P' AND (p.mod IS NULL OR p.mod='') AND p.status_code='A' AND p.non_fac_pe_rvu IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM pfs.rvu f WHERE f.year=2026 AND f.hcpcs=p.hcpcs AND (f.mod IS NULL OR f.mod=''))
""").fetchone()[0]
_dropped = con.execute("""
SELECT count(*) FROM pfs.rvu f
WHERE f.year=2026 AND (f.mod IS NULL OR f.mod='') AND f.status_code='A'
AND NOT EXISTS (SELECT 1 FROM pfs.rvu_proposed p WHERE p.cms_rule_id = 'CMS-1848-P' AND p.hcpcs=f.hcpcs AND (p.mod IS NULL OR p.mod=''))
""").fetchone()[0]
mo.md(f"""
> **Coverage caveat:** {_new} HCPCS codes appear in the `CMS-1848-P`
> proposed RVU table with no CY2026-final counterpart (new/renumbered
> codes), and {_dropped} CY2026-final codes have no match in the
> proposed table (dropped, bundled, or excluded from the Addendum B
> extract used to build the lake table). Both groups are excluded
> from the delta analysis above rather than shown with a fabricated
> baseline.
**Source:** `pfs.rvu_proposed` — CMS-1848-P Addendum B,
{fr_md(proposed_for(2027).federal_register_citation)}. `pfs.rvu` — CY2026
PFS Final Rule Addendum B.
""")
return
@app.cell(hide_code=True)
def _(mo):
mo.md("""
### Cross-NPRM check — CY2026 NPRM vs. CY2027 NPRM (optional)
`pfs.rvu_proposed` holds **two** NPRM partitions in the same table
— `CMS-1832-P` (CY2026 NPRM, 14,169 rows) and `CMS-1848-P` (CY2027
NPRM, 14,518 rows). For codes proposed in both rulemakings, this
compares total non-facility RVUs NPRM-to-NPRM — a coarse signal of
which codes CMS has been proposing to move for two consecutive
rulemaking cycles, independent of which CF applies.
""")
return
@app.cell(hide_code=True)
def _(q):
cross_nprm = q("""
WITH cy2026_nprm AS (
SELECT hcpcs, description, work_rvu + non_fac_pe_rvu + mp_rvu AS total_nf_rvu
FROM pfs.rvu_proposed
WHERE cms_rule_id = 'CMS-1832-P' AND (mod IS NULL OR mod = '')
AND status_code = 'A' AND non_fac_pe_rvu IS NOT NULL
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
),
cy2027_nprm AS (
SELECT hcpcs, work_rvu + non_fac_pe_rvu + mp_rvu AS total_nf_rvu
FROM pfs.rvu_proposed
WHERE cms_rule_id = 'CMS-1848-P' AND (mod IS NULL OR mod = '')
AND status_code = 'A' AND non_fac_pe_rvu IS NOT NULL
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
)
SELECT
a.hcpcs, a.description,
a.total_nf_rvu AS total_nf_rvu_cy2026_nprm,
b.total_nf_rvu AS total_nf_rvu_cy2027_nprm,
round(b.total_nf_rvu - a.total_nf_rvu, 4) AS rvu_delta_nprm_to_nprm
FROM cy2026_nprm a
JOIN cy2027_nprm b USING (hcpcs)
ORDER BY abs(b.total_nf_rvu - a.total_nf_rvu) DESC
LIMIT 10
""")
return (cross_nprm,)
@app.cell(hide_code=True)
def _(cross_nprm, fr_md, mo):
mo.vstack(
[
cross_nprm,
mo.md(f"""
**Sources:** `pfs.rvu_proposed` — `CMS-1832-P` ({fr_md("90 FR 32352")})
and `CMS-1848-P` ({fr_md("91 FR 43842")}) partitions of the same table.
Total non-facility RVUs only — no dollar conversion, since
the two NPRMs' CFs are not directly comparable across a
one-year gap without also re-basing for the RVU-neutral
budget-neutrality adjustment each rulemaking applies.
"""),
]
)
return
# ── 3. GPCI & Locality ──────────────────────────────────────────────────
@app.cell(hide_code=True)
def _(mo):
mo.md("""
## 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, fr_md, 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,
{fr_md(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 _(cfr_md, 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
({cfr_md("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
Performance Period" become Qualifying APM Participants (QPs) for
the payment year two years later (`payment_year = performance_year
+ 2`). The CY2027 NPRM's own regulatory-impact-analysis recital
covers **QP Performance Period 2027 / payment year 2029** —
`qpp.QPP[2027]`, reachable as `qpp.for_payment_year(2029)`.
`qpp.QPP`'s payment-year keying was corrected under task #620
shortly before this notebook was written — thresholds are keyed to
**payment year**, not performance year, and every table below
labels both explicitly so the correction is visible rather than
silently assumed.
""")
return
@app.cell(hide_code=True)
def _(for_payment_year, mo):
_governs_2029 = for_payment_year(2029)
mo.md(f"""
`qpp.for_payment_year(2029)` resolves to QP Performance Period
**{_governs_2029.performance_year}** ({_governs_2029.citation}) —
the performance period whose determinations govern the payment
year that the CY2027 NPRM's proposed conversion factors apply to.
`qp_cf_applies` is **{_governs_2029.qp_cf_applies}** for this
performance period.
""")
return
@app.cell(hide_code=True)
def _(QPP, pl, plain_years):
_rows = []
for _perf_year, _qy in sorted(QPP.items()):
_prop = _qy.proposed
_rows.append(
{
"qp_performance_period": _perf_year,
"payment_year": _qy.payment_year,
"qp_payment_pct": _qy.qp_thresholds.payment_amount_pct,
"qp_patient_pct": _qy.qp_thresholds.patient_count_pct,
"partial_qp_payment_pct": _qy.partial_qp_thresholds.payment_amount_pct,
"partial_qp_patient_pct": _qy.partial_qp_thresholds.patient_count_pct,
"revenue_nominal_pct": _qy.risk_standards.revenue_nominal_pct,
"benchmark_nominal_pct": _qy.risk_standards.benchmark_nominal_pct,
"apm_incentive_pct": _qy.apm_incentive_pct,
"qp_cf_applies": _qy.qp_cf_applies,
"cehrt_required": _qy.cehrt_required,
"has_nprm_proposal": _prop is not None,
"citation": _qy.citation,
}
)
apm_thresholds = pl.DataFrame(_rows)
plain_years(apm_thresholds)
return (apm_thresholds,)
@app.cell(hide_code=True)
def _(QPP, cfr_md, mo):
mo.md(f"""
Columns are labeled `qp_performance_period` (the year Advanced-APM
participation is measured) and `payment_year` (the year the
resulting QP status is applied) explicitly and separately — every
threshold in this table governs by **`payment_year`**, per
{cfr_md("42 CFR 414.1430(a)")} and section 1833(z)(2) of the Act. `QPP[2027]`
(payment year {QPP[2027].payment_year}) is the entry this
notebook's CF walk (Section 1) corresponds to.
""")
return
@app.cell(hide_code=True)
def _(alt, apm_thresholds, cfr_md, fr_md, mo):
_df = apm_thresholds.to_pandas()
_df["apm_incentive_pct"] = _df["apm_incentive_pct"].fillna(0.0)
_df["status"] = _df["apm_incentive_pct"].apply(lambda v: "Paid" if v > 0 else "None under current law")
incentive_chart = (
alt.Chart(_df)
.mark_bar()
.encode(
x=alt.X("payment_year:O", title="Payment year"),
y=alt.Y("apm_incentive_pct:Q", title="APM incentive payment (% of base-year Part B paid claims)"),
color=alt.Color(
"status:N",
title=None,
scale=alt.Scale(domain=["Paid", "None under current law"]),
),
tooltip=["qp_performance_period", "payment_year", "apm_incentive_pct", "status"],
)
.properties(title="APM Incentive Payment — Not a Clean Sunset", width=600, height=300)
)
_citations = ", ".join(sorted(set(_df["citation"])))
mo.vstack(
[
incentive_chart,
mo.md(f"""
The lump-sum APM Incentive Payment does **not** sunset
cleanly after payment year 2026. Payment year 2027 (this
notebook's prior year) has no incentive under current law.
The Consolidated Appropriations Act, 2026 (CAA 2026, Pub.
L. 119-75) then **revives a 3.1% incentive for payment
year 2028 only** — CMS-1848-P proposes to codify that
revival into {cfr_md("42 CFR 414.1450(b)(1)")}
({fr_md("91 FR 44218")}-44219, {fr_md("91 FR 44286")}).
Payment year 2029 (`QPP[2027]`, the year this
notebook's own CF walk applies to) reverts to **no**
incentive — the amendatory text lists no applicable
percentage for payment year 2029 at all. So the pattern
across 2025-2029 is paid / paid / gap / revived / gap, not
a single cutoff.
**Sources:** `qpp.QPP` performance years 2023-2027
({_citations}).
"""),
]
)
return
@app.cell(hide_code=True)
def _(QPP, fr_md, mo):
_prop = QPP[2027].proposed
mo.vstack(
[
mo.md(f"""
### CMS-1848-P proposed QPP changes — proposals only, no Final Rule disposition yet
`qpp.QPP[2027].proposed.changes` ({fr_md(_prop.federal_register_citation)},
{_prop.cms_rule_id}) is the single source of truth for the
narrative below — quoted live, not retyped. Unlike the
CY2026 notebook's disposition table, there is **no**
finalized/not-finalized column here: CMS-1848-F has not
been published.
"""),
mo.callout(mo.md(_prop.changes), kind="info"),
]
)
return
@app.cell(hide_code=True)
def _(mo):
mo.md("""
### What QP status is worth in dollars (CY2027 proposed) — 3 example HCPCS
National, unadjusted (GPCI = 1.0) payment for a single unit of
service, computed as `(work_rvu + non_fac_pe_rvu + mp_rvu) × CF`,
using **CY2026-final RVUs** (the current baseline; CY2027 has no
finalized RVU table yet) priced at the **CY2027 NPRM's proposed**
QP and non-QP conversion factors:
- **99213** — established-patient office visit, low complexity (E/M)
- **27447** — total knee arthroplasty (major procedure)
- **70553** — MRI brain, without and with contrast (imaging)
""")
return
@app.cell(hide_code=True)
def _(pl, proposed_for, q):
_example_rvu = q("""
SELECT hcpcs, description, work_rvu, non_fac_pe_rvu, mp_rvu,
work_rvu + non_fac_pe_rvu + mp_rvu AS total_rvu
FROM pfs.rvu
WHERE year = 2026 AND (mod IS NULL OR mod = '')
AND hcpcs IN ('99213', '27447', '70553')
""")
_prop = proposed_for(2027)
_rows = []
for _r in _example_rvu.iter_rows(named=True):
_pay_nonqp = round(_r["total_rvu"] * _prop.conversion_factor, 2)
_pay_qp = round(_r["total_rvu"] * _prop.cf_qp, 2)
_rows.append(
{
"hcpcs": _r["hcpcs"],
"description": _r["description"],
"total_rvu_cy2026final": _r["total_rvu"],
"non_qp_payment_cy2027proposed": _pay_nonqp,
"qp_payment_cy2027proposed": _pay_qp,
"qp_differential": round(_pay_qp - _pay_nonqp, 2),
}
)
qp_differential = pl.DataFrame(_rows)
qp_differential
return (qp_differential,)
@app.cell(hide_code=True)
def _(alt, fr_md, mo, proposed_for, qp_differential):
differential_chart = (
alt.Chart(qp_differential.to_pandas())
.mark_bar()
.encode(
x=alt.X("hcpcs:N", title="HCPCS"),
y=alt.Y("qp_differential:Q", title="QP minus non-QP payment ($, national unadjusted)"),
tooltip=["hcpcs", "description", alt.Tooltip("qp_differential:Q", format="$.2f")],
)
.properties(title="Dollar Value of QP Status — CY2027 Proposed (final rule pending)", width=500, height=320)
)
mo.vstack(
[
differential_chart,
mo.md(f"""
**Sources:** RVUs — `pfs.rvu`, CY2026 Final ({fr_md("90 FR 49266")}).
CFs — `proposed_for(2027)`, CY2027 NPRM
({fr_md(proposed_for(2027).federal_register_citation)},
{proposed_for(2027).cms_rule_id}) — final rule pending.
National unadjusted means GPCI = 1.0 — actual payment
varies by locality.
"""),
]
)
return
# ── 6. Provenance ───────────────────────────────────────────────────────
@app.cell(hide_code=True)
def _(cfr_md, mo):
mo.md(f"""
## 6. Provenance & Comment-Period Status
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 ({cfr_md("42 CFR 425.400(c)")}).
""")
return
@app.cell(hide_code=True)
def _(fr_md, mo, proposed_for):
from datetime import date
_prop = proposed_for(2027)
_close = _prop.comment_close
_today = date.today()
_open = _today <= _close
_status = "OPEN" if _open else "CLOSED"
mo.callout(
mo.md(f"""
**Comment period: {_status}** — {_prop.cms_rule_id} ({fr_md(_prop.federal_register_citation)})
published {_prop.published.isoformat()}, comment period closes
**{_close.isoformat()}** (`proposed_for(2027).comment_close`,
read live from the registry; today is {_today.isoformat()}).
"""),
kind="success" if _open else "neutral",
)
return
@app.cell(hide_code=True)
def _(mo, q):
provenance = 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',
'pfs.gpci', 'pfs.gpci_proposed')
ORDER BY ingested_at DESC
""")
mo.ui.table(provenance.to_pandas(), label="Ingest Log — Tables Used in This Notebook")
return
if __name__ == "__main__":
app.run()