fix(notebooks): registry-sourced thresholds, per-figure citations, honest provenance (refs #597)
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user