fix(notebooks): registry-sourced thresholds, per-figure citations, honest provenance (refs #597)

This commit is contained in:
kert
2026-08-13 14:30:04 -04:00
parent d35e50d575
commit 58234ebb70

View File

@@ -35,6 +35,10 @@ def _(mo):
**What this means:** every number below is read live from **What this means:** every number below is read live from
`pfs.rules.RULES` / `qpp.QPP` (the repo's rule registries) and the `pfs.rules.RULES` / `qpp.QPP` (the repo's rule registries) and the
`pfs`/`cms` DuckLake schemas — nothing here is a hard-coded figure. `pfs`/`cms` DuckLake schemas — nothing here is a hard-coded figure.
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.
""") """)
return return
@@ -168,44 +172,60 @@ def _(RULES, mo, pl):
_cy26 = RULES[2026] _cy26 = RULES[2026]
_prop = _cy26.proposed _prop = _cy26.proposed
def _reconstruct(annual_update_pct, bn_adjustor): # CMS's own stated CF-update components (90 FR 32802) — a one-time
return _cy25_cf * 1.0250 * (1 + annual_update_pct / 100) * bn_adjustor # statutory increase plus the MACRA 2015 sec. 1848(d)(20) annual
# updates. NONE of these three percentages is a field anywhere in
# `pfs.rules` or `qpp` — there is nothing to attribute-access. 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_INCREASE_MULT = 1.0250 # +2.50% single-year statutory increase, 90 FR 32802
_ANNUAL_UPDATE_MULT = {
"Non-QP": 1.0025, # +0.25%/yr nonqualifying-APM update, 90 FR 32802
"QP": 1.0075, # +0.75%/yr qualifying-APM update, 90 FR 32802
}
def _reconstruct(track, bn_adjustor):
return _cy25_cf * _ONE_TIME_INCREASE_MULT * _ANNUAL_UPDATE_MULT[track] * bn_adjustor
_bn_rows = [ _bn_rows = [
{ {
"vintage": "CY2026 Proposed", "vintage": "CY2026 Proposed",
"track": "Non-QP", "track": "Non-QP",
"one_time_pct": 2.50, "one_time_mult": _ONE_TIME_INCREASE_MULT,
"annual_update_pct": 0.25, "annual_update_mult": _ANNUAL_UPDATE_MULT["Non-QP"],
"bn_adjustor": _prop.budget_neutrality_adjustor, "bn_adjustor": _prop.budget_neutrality_adjustor,
"reconstructed_cf": round(_reconstruct(0.25, _prop.budget_neutrality_adjustor), 4), "reconstructed_cf": round(_reconstruct("Non-QP", _prop.budget_neutrality_adjustor), 4),
"registry_cf": _prop.conversion_factor, "registry_cf": _prop.conversion_factor,
}, },
{ {
"vintage": "CY2026 Proposed", "vintage": "CY2026 Proposed",
"track": "QP", "track": "QP",
"one_time_pct": 2.50, "one_time_mult": _ONE_TIME_INCREASE_MULT,
"annual_update_pct": 0.75, "annual_update_mult": _ANNUAL_UPDATE_MULT["QP"],
"bn_adjustor": _prop.budget_neutrality_adjustor, "bn_adjustor": _prop.budget_neutrality_adjustor,
"reconstructed_cf": round(_reconstruct(0.75, _prop.budget_neutrality_adjustor), 4), "reconstructed_cf": round(_reconstruct("QP", _prop.budget_neutrality_adjustor), 4),
"registry_cf": _prop.cf_qp, "registry_cf": _prop.cf_qp,
}, },
{ {
"vintage": "CY2026 Final", "vintage": "CY2026 Final",
"track": "Non-QP", "track": "Non-QP",
"one_time_pct": 2.50, "one_time_mult": _ONE_TIME_INCREASE_MULT,
"annual_update_pct": 0.25, "annual_update_mult": _ANNUAL_UPDATE_MULT["Non-QP"],
"bn_adjustor": _cy26.budget_neutrality_adjustor, "bn_adjustor": _cy26.budget_neutrality_adjustor,
"reconstructed_cf": round(_reconstruct(0.25, _cy26.budget_neutrality_adjustor), 4), "reconstructed_cf": round(_reconstruct("Non-QP", _cy26.budget_neutrality_adjustor), 4),
"registry_cf": _cy26.conversion_factor, "registry_cf": _cy26.conversion_factor,
}, },
{ {
"vintage": "CY2026 Final", "vintage": "CY2026 Final",
"track": "QP", "track": "QP",
"one_time_pct": 2.50, "one_time_mult": _ONE_TIME_INCREASE_MULT,
"annual_update_pct": 0.75, "annual_update_mult": _ANNUAL_UPDATE_MULT["QP"],
"bn_adjustor": _cy26.budget_neutrality_adjustor, "bn_adjustor": _cy26.budget_neutrality_adjustor,
"reconstructed_cf": round(_reconstruct(0.75, _cy26.budget_neutrality_adjustor), 4), "reconstructed_cf": round(_reconstruct("QP", _cy26.budget_neutrality_adjustor), 4),
"registry_cf": _cy26.cf_qp, "registry_cf": _cy26.cf_qp,
}, },
] ]
@@ -216,13 +236,17 @@ def _(RULES, mo, pl):
mo.vstack( mo.vstack(
[ [
bn_decomp, bn_decomp,
mo.md(""" mo.md(f"""
`reconstructed_cf = CY2025_CF × 1.0250 × (1 + annual_update) × bn_adjustor` `reconstructed_cf = CY2025_CF × one_time_mult × annual_update_mult × bn_adjustor`
— `bn_adjustor` and `registry_cf` are read from `RULES[2026]` / — `bn_adjustor` and `registry_cf` are read live from
`RULES[2026].proposed`; the 2.50% one-time increase and the `RULES[2026]` / `RULES[2026].proposed`. `one_time_mult`
0.25%/0.75% annual updates are CMS's own stated components ({_ONE_TIME_INCREASE_MULT}) and `annual_update_mult`
(90 FR 32802), not registry fields, transcribed here only for ({_ANNUAL_UPDATE_MULT["Non-QP"]} non-QP /
the cross-check. {_ANNUAL_UPDATE_MULT["QP"]} QP) are **cited transcriptions of
CMS's NPRM narrative (90 FR 32802)**, not registry fields —
no field in `pfs.rules` or `qpp` models the 2.50%/0.25%/0.75%
statutory update components individually, only their combined
effect on `conversion_factor` / `cf_qp`.
"""), """),
] ]
) )
@@ -337,7 +361,7 @@ def _(mo):
@app.cell(hide_code=True) @app.cell(hide_code=True)
def _(AMBER, TEAL, alt, baseline_picker, mo, rvu_delta): def _(AMBER, RULES, TEAL, alt, baseline_picker, mo, rvu_delta):
_col = baseline_picker.value _col = baseline_picker.value
_n = 20 _n = 20
@@ -370,7 +394,18 @@ def _(AMBER, TEAL, alt, baseline_picker, mo, rvu_delta):
) )
) )
mo.vstack([winners_losers_chart]) mo.vstack(
[
winners_losers_chart,
mo.md(f"""
**Sources:** `pfs.rvu_proposed` — CMS-1832-P NPRM,
{RULES[2026].proposed.federal_register_citation}. Baseline —
`pfs.rvu` CY2025 Final ({RULES[2025].federal_register_citation})
or CY2026 Final ({RULES[2026].federal_register_citation}), per
the comparison selected above.
"""),
]
)
return return
@@ -581,16 +616,21 @@ def _(alt, apm_thresholds, mo):
.properties(title="APM Incentive Payment Sunset", width=600, height=300) .properties(title="APM Incentive Payment Sunset", width=600, height=300)
) )
_citations = ", ".join(sorted(set(_df["citation"])))
mo.vstack( mo.vstack(
[ [
incentive_chart, incentive_chart,
mo.md(""" mo.md(f"""
Payment year 2026 (QP Performance Period 2024) is, under Payment year 2026 (QP Performance Period 2024) is, under
current law, the **last** year with a lump-sum APM incentive current law, the **last** year with a lump-sum APM incentive
payment (1.88% of base-year paid claims). From payment year payment (1.88% of base-year paid claims). From payment year
2027 onward, QP status stops paying a bonus and starts 2027 onward, QP status stops paying a bonus and starts
(instead) determining *which conversion factor applies* — see (instead) determining *which conversion factor applies* — see
below. below.
**Sources:** `qpp.QPP` performance years 2023–2026
({_citations}).
"""), """),
] ]
) )
@@ -599,20 +639,28 @@ def _(alt, apm_thresholds, mo):
@app.cell(hide_code=True) @app.cell(hide_code=True)
def _(QPP, mo): def _(QPP, mo):
_proposed = QPP[2026].proposed _cy = QPP[2026]
_proposed = _cy.proposed
_qp = _cy.qp_thresholds
_partial = _cy.partial_qp_thresholds
mo.md(f""" mo.md(f"""
### CY2026 NPRM proposed QPP changes — proposed vs. finalized disposition ### CY2026 NPRM proposed QPP changes — proposed vs. finalized disposition
Every FR page-pincite in the "Disposition" column below is quoted
**verbatim from `qpp.QPP[2026].proposed.changes`**
({_proposed.federal_register_citation}, {_proposed.cms_rule_id}) — that
field is the single source of truth for this table; nothing here is a
second, independently-typed citation.
| Proposal | Disposition | | Proposal | Disposition |
|---|---| |---|---|
| Individual-level QP determination (new Threshold Score calc, Sec. 414.1425(b)(3)) | **Finalized as proposed** (90 FR 49923–49928, 49980) | | Individual-level QP determination (new Threshold Score calc, Sec. 414.1425(b)(3)) | **Finalized as proposed** (90 FR 49923–49928, 49980) |
| Attribution-eligible beneficiary definition — expand 6th criterion beyond E/M services | **Finalized WITH MODIFICATION** — dual E/M + covered-professional-services methodology (90 FR 49980) | | Attribution-eligible beneficiary definition — expand 6th criterion beyond E/M services | **Finalized WITH MODIFICATION** — dual E/M + covered-professional-services methodology (90 FR 49980) |
| Sunset the Medical Home Model 50-clinician limit (Sec. 414.1415(c)(7)) after the 2025 QP Performance Period | **Finalized as proposed** (90 FR 49929) | | Sunset the Medical Home Model 50-clinician limit (Sec. 414.1415(c)(7)) after the 2025 QP Performance Period | **Finalized as proposed** (90 FR 49929) |
| QP / Partial QP percentage thresholds | **No numeric change proposed** — remain 75%/50% (QP), 50%/35% (Partial QP) | | QP / Partial QP percentage thresholds | **No numeric change proposed** — remain `qp_thresholds` {_qp.payment_amount_pct:.0f}%/{_qp.patient_count_pct:.0f}% (QP), `partial_qp_thresholds` {_partial.payment_amount_pct:.0f}%/{_partial.patient_count_pct:.0f}% (Partial QP) |
| Nominal-amount risk standards (42 CFR 414.1415(c)(3)(i)) / CEHRT requirement | **No change proposed** — cross-referenced by section only | | Nominal-amount risk standards (42 CFR 414.1415(c)(3)(i)) / CEHRT requirement | **No change proposed** — cross-referenced by section only |
Full sourced narrative: `qpp.QPP[2026].proposed.changes` Full sourced narrative: `qpp.QPP[2026].proposed.changes`.
({_proposed.federal_register_citation}, {_proposed.cms_rule_id}).
""") """)
return return
@@ -708,8 +756,11 @@ def _(mo):
## 5. Provenance ## 5. Provenance
Ingest-log entries (`cms.ingest_log`) for every table this notebook Ingest-log entries (`cms.ingest_log`) for every table this notebook
reads from, so every figure above can be traced back to a specific reads from — `pfs.rvu` and `pfs.rvu_proposed` — so every figure above
ingest run, source file, and Federal Register citation. 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.)
""") """)
return return
@@ -720,7 +771,7 @@ def _(mo, q):
SELECT run_id, ingested_at, module, table_name, rule_id, SELECT run_id, ingested_at, module, table_name, rule_id,
source_file, sha256, rows, fr_citation, pincite_key source_file, sha256, rows, fr_citation, pincite_key
FROM cms.ingest_log FROM cms.ingest_log
WHERE table_name IN ('pfs.rvu', 'pfs.rvu_proposed', 'pfs.gpci') WHERE table_name IN ('pfs.rvu', 'pfs.rvu_proposed')
ORDER BY ingested_at DESC ORDER BY ingested_at DESC
""") """)
mo.ui.table(provenance.to_pandas(), label="Ingest Log — Tables Used in This Notebook") mo.ui.table(provenance.to_pandas(), label="Ingest Log — Tables Used in This Notebook")