1347 lines
53 KiB
Python
1347 lines
53 KiB
Python
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"""
|
|
# Community-Based Palliative Care RFI — Evidence Base & Payment Context
|
|
|
|
The CY2027 PFS NPRM (CMS-1848-P, {fr_md("91 FR 43842")}, docket
|
|
CMS-2026-2377) carries a Request for Information on
|
|
**Community-Based Palliative Care** at {fr_md("91 FR 43949")}-43950.
|
|
This notebook is the P42/P43 deliverable behind the comment we will
|
|
file to that docket: a PRISMA 2020 systematic evidence base for the
|
|
RFI's five questions, plus the payment context for the code
|
|
families the RFI itself names — read live from the repo's lake and
|
|
registries, nothing hard-coded.
|
|
|
|
The RFI asks five things:
|
|
|
|
- **(a)** where CMS should focus **fraud, waste, and abuse**
|
|
scrutiny in community-based palliative care;
|
|
- **(b)** whether eligibility for a serious-illness service should
|
|
be gated on **certified life expectancy**, and what interval the
|
|
evidence supports;
|
|
- **(c)** whether eligibility is better defined by **impact on
|
|
daily function (ADLs)** and/or **caregiver strain**;
|
|
- **(d)** which **care-management service elements**
|
|
(interdisciplinary teams, home visits, timely follow-up) should
|
|
be required — explicitly tied to section II.E of the same rule;
|
|
- **(e)** how **quality** of palliative care management should be
|
|
measured (MIPS Value Pathway reporting, CBE 3665 "feeling heard
|
|
and understood").
|
|
|
|
CMS asks that every statement be supported by peer-reviewed or
|
|
institutional evidence — so the evidence base *is* the response.
|
|
""")
|
|
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
|
|
|
|
connect.theme()
|
|
|
|
# connect.theme() puts assets/ on sys.path — reuse the HTI-5 design
|
|
# tokens: AMBER/TEAL are the gain/loss diverging pair, INDIGO_MED
|
|
# the default series, PLUM/GRAY categorical follow-ons.
|
|
from fhirworx import AMBER, GRAY, INDIGO_MED, PLUM, TEAL
|
|
|
|
# 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) — 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) — degrades to plain
|
|
text when bib.sqlite or the anchor 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,
|
|
RULES,
|
|
TEAL,
|
|
alt,
|
|
cfr_md,
|
|
con,
|
|
connect,
|
|
fr_md,
|
|
pl,
|
|
plain_years,
|
|
proposed_for,
|
|
q,
|
|
)
|
|
|
|
|
|
# ── 1. PRISMA evidence base ─────────────────────────────────────────────
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 1. PRISMA 2020 Evidence Base — `project:palliative-rfi`
|
|
|
|
**Identification** (run 2026-08-18,
|
|
`dev/scripts/search_pubmed_palliative_rfi.py`), three arms:
|
|
|
|
1. **Database searches** — eight relevance-sorted, **uncapped**
|
|
PubMed E-utilities searches: one per RFI part (a-e), plus three
|
|
standards-anchored searches supporting the proposed code family
|
|
in Section 3 (CAPC program standards, NASW serious-illness
|
|
social work practice, Joint Commission palliative
|
|
certification).
|
|
2. **Forward snowball** — NCBI elink *cited-by* chasing from five
|
|
verified anchor publications: the CAPC screening-criteria
|
|
consensus (Weissman & Meier 2011) and CAPC metrics consensus
|
|
(2010), the RFI's own two serious-illness-identification cites
|
|
(Kelley 2017; Kelley & Bollens-Lund 2018), and the scale paper
|
|
behind CBE 3665.
|
|
3. **Standards & cited documents** — the three professional
|
|
standards themselves plus the five sources the RFI cites are
|
|
stored directly as grey-literature items.
|
|
|
|
Everything lands in bib.sqlite and syncs to Zotero tagged
|
|
`module:palliative-rfi` with `rfi:{a-e|codeset}`,
|
|
`standard:{capc|nasw|tjc}`, and `source:{pubmed|snowball|standard|
|
|
rfi-cited}` — the identification table below is **queried live
|
|
from those tags**, so it tracks the corpus as it grows.
|
|
|
|
**Screening** (stage 2, title/abstract) runs through the repo's
|
|
PRISMA pipeline (`stack prisma screen palliative-rfi`) against the
|
|
project's criteria/reasons anchors in Zotero, using a **local
|
|
self-hosted model (qwen2.5:14b via Ollama on the server GPU)** —
|
|
decisions, reason codes, and rationales land back in Zotero as
|
|
tags and notes. The flow below is computed **live from those
|
|
tags**, so it always reflects current screening progress.
|
|
|
|
> **Methods disclosures:** single-reviewer LLM screening (no human
|
|
> dual-screening yet); records the 14B screener marked `uncertain`
|
|
> were adjudicated in a second pass by a larger model
|
|
> (qwen2.5:32b) with the first-pass rationale withheld for
|
|
> independence; grey literature (standards documents,
|
|
> OIG/GAO/MedPAC/CMMI) bypasses clinical screening per the
|
|
> project criteria. **Stage-3 full-text eligibility** runs locally
|
|
> (qwen2.5:14b, 32k context, per-item markdown capped at 60k chars
|
|
> with an explicit truncation marker — the criteria stay in
|
|
> window); items the 14B persistently returned empty responses for
|
|
> were escalated to **qwen2.5:32b** (20k-char cap to fit its
|
|
> context), mirroring the stage-2 adjudication pattern. Full-text
|
|
> retrieval: Unpaywall → PMC OA (S3) → Europe PMC → mirror
|
|
> fallback; items with no retrievable full text resolve
|
|
> `uncertain / unavailable-fulltext` rather than silently
|
|
> dropping. The flow diagram above reflects whatever stage the
|
|
> pipeline has reached — it is computed live.
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(connect, mo, pl):
|
|
# Identification composition, live from Zotero tags.
|
|
_zcon = connect.zotero()
|
|
_rows = _zcon.execute("""
|
|
WITH proj AS (
|
|
SELECT it.itemID
|
|
FROM itemTags it JOIN tags t ON it.tagID = t.tagID
|
|
WHERE t.name = 'module:palliative-rfi'
|
|
AND it.itemID NOT IN (
|
|
SELECT it2.itemID FROM itemTags it2
|
|
JOIN tags t2 ON it2.tagID = t2.tagID
|
|
WHERE t2.name LIKE 'prisma:%')
|
|
)
|
|
SELECT t.name, count(DISTINCT it.itemID) AS n
|
|
FROM itemTags it JOIN tags t ON it.tagID = t.tagID
|
|
WHERE it.itemID IN (SELECT itemID FROM proj)
|
|
AND (t.name LIKE 'rfi:%' OR t.name LIKE 'standard:%'
|
|
OR t.name LIKE 'source:%')
|
|
GROUP BY t.name ORDER BY t.name
|
|
""").fetchall()
|
|
ident_comp = pl.DataFrame([{"tag": name, "records": n} for name, n in _rows])
|
|
mo.vstack(
|
|
[
|
|
mo.md("**Identification composition (live from Zotero):**"),
|
|
mo.ui.table(ident_comp.to_pandas(), label="Records per identification tag"),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(connect, mo):
|
|
from prisma import flow as _flow
|
|
|
|
class _RoDb:
|
|
"""Duck-typed shim: prisma.flow only touches ``.con``."""
|
|
|
|
def __init__(self, con):
|
|
self.con = con
|
|
|
|
_zcon = connect.zotero()
|
|
flow_counts = _flow.count(_RoDb(_zcon), "palliative-rfi")
|
|
flow_mermaid = _flow.mermaid(flow_counts, project="palliative-rfi")
|
|
|
|
_screened_pct = (
|
|
100 * flow_counts.screened / flow_counts.identified
|
|
if flow_counts.identified
|
|
else 0.0
|
|
)
|
|
_advancing = flow_counts.screened - flow_counts.excluded_stage2
|
|
|
|
mo.vstack(
|
|
[
|
|
mo.mermaid(flow_mermaid),
|
|
mo.md(f"""
|
|
**{flow_counts.identified:,} identified** →
|
|
**{flow_counts.screened:,} screened** ({_screened_pct:.0f}%
|
|
of identified) → **{_advancing:,} advance toward
|
|
full-text eligibility** (stage 3 pending;
|
|
{flow_counts.included:,} through full-text so far).
|
|
Diagram and counts are regenerated from Zotero tags on
|
|
every notebook run, so they track screening progress
|
|
automatically.
|
|
"""),
|
|
]
|
|
)
|
|
return (flow_counts,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(AMBER, GRAY, TEAL, alt, connect, mo, pl):
|
|
# Screening status per RFI domain, live from Zotero tags.
|
|
_zcon = connect.zotero()
|
|
_rows = _zcon.execute("""
|
|
WITH proj AS (
|
|
SELECT it.itemID
|
|
FROM itemTags it JOIN tags t ON it.tagID = t.tagID
|
|
WHERE t.name = 'project:palliative-rfi'
|
|
AND it.itemID NOT IN (
|
|
SELECT it2.itemID FROM itemTags it2
|
|
JOIN tags t2 ON it2.tagID = t2.tagID
|
|
WHERE t2.name LIKE 'prisma:%')
|
|
),
|
|
dom AS (
|
|
SELECT it.itemID, t.name AS rfi
|
|
FROM itemTags it JOIN tags t ON it.tagID = t.tagID
|
|
WHERE t.name IN ('rfi:a','rfi:b','rfi:c','rfi:d','rfi:e','rfi:codeset')
|
|
AND it.itemID IN (SELECT itemID FROM proj)
|
|
),
|
|
decision AS (
|
|
SELECT it.itemID, t.name AS decision
|
|
FROM itemTags it JOIN tags t ON it.tagID = t.tagID
|
|
WHERE t.name IN ('screen:include','screen:exclude','screen:uncertain')
|
|
)
|
|
SELECT dom.rfi,
|
|
coalesce(decision.decision, 'unscreened') AS decision,
|
|
count(*) AS n
|
|
FROM dom LEFT JOIN decision ON dom.itemID = decision.itemID
|
|
GROUP BY dom.rfi, decision.decision
|
|
ORDER BY dom.rfi
|
|
""").fetchall()
|
|
|
|
_labels = {
|
|
"rfi:a": "(a) fraud/waste/abuse",
|
|
"rfi:b": "(b) prognosis eligibility",
|
|
"rfi:c": "(c) function/caregiver",
|
|
"rfi:d": "(d) delivery & mgmt",
|
|
"rfi:e": "(e) quality measures",
|
|
"rfi:codeset": "code-family standards arm",
|
|
}
|
|
rfi_status = pl.DataFrame(
|
|
[
|
|
{"rfi_part": _labels[r], "decision": d.removeprefix("screen:"), "n": n}
|
|
for r, d, n in _rows
|
|
]
|
|
)
|
|
|
|
_order = ["include", "uncertain", "exclude", "unscreened"]
|
|
rfi_status_chart = (
|
|
alt.Chart(rfi_status.to_pandas())
|
|
.mark_bar()
|
|
.encode(
|
|
x=alt.X("n:Q", title="Records"),
|
|
y=alt.Y("rfi_part:N", title=None),
|
|
color=alt.Color(
|
|
"decision:N",
|
|
title="Stage-2 decision",
|
|
sort=_order,
|
|
scale=alt.Scale(
|
|
domain=_order,
|
|
range=[TEAL, "#C9A227", AMBER, GRAY],
|
|
),
|
|
),
|
|
order=alt.Order("color_decision_sort_index:Q"),
|
|
tooltip=["rfi_part", "decision", "n"],
|
|
)
|
|
.properties(
|
|
title="Screening Status by RFI Domain (live)", width=620, height=220
|
|
)
|
|
)
|
|
|
|
mo.vstack(
|
|
[
|
|
rfi_status_chart,
|
|
mo.md("""
|
|
Records retrieved by more than one domain search carry
|
|
multiple `rfi:*` tags and count once per domain here —
|
|
the PRISMA flow above deduplicates; this chart
|
|
deliberately does not, since a paper can inform two RFI
|
|
answers.
|
|
"""),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(connect, mo, pl, plain_years):
|
|
# Included studies, live: one row per include, with domain tags.
|
|
_zcon = connect.zotero()
|
|
_rows = _zcon.execute("""
|
|
WITH inc AS (
|
|
SELECT it.itemID
|
|
FROM itemTags it JOIN tags t ON it.tagID = t.tagID
|
|
WHERE t.name = 'screen:include'
|
|
AND it.itemID IN (
|
|
SELECT it2.itemID FROM itemTags it2
|
|
JOIN tags t2 ON it2.tagID = t2.tagID
|
|
WHERE t2.name = 'project:palliative-rfi')
|
|
)
|
|
SELECT
|
|
(SELECT idv.value FROM itemData id
|
|
JOIN itemDataValues idv ON idv.valueID = id.valueID
|
|
WHERE id.itemID = inc.itemID
|
|
AND id.fieldID = (SELECT fieldID FROM fields
|
|
WHERE fieldName = 'title')) AS title,
|
|
(SELECT idv.value FROM itemData id
|
|
JOIN itemDataValues idv ON idv.valueID = id.valueID
|
|
WHERE id.itemID = inc.itemID
|
|
AND id.fieldID = (SELECT fieldID FROM fields
|
|
WHERE fieldName = 'publicationTitle')) AS journal,
|
|
(SELECT group_concat(t.name, ', ') FROM itemTags it
|
|
JOIN tags t ON it.tagID = t.tagID
|
|
WHERE it.itemID = inc.itemID
|
|
AND t.name LIKE 'rfi:%') AS rfi_parts,
|
|
(SELECT group_concat(t.name, ', ') FROM itemTags it
|
|
JOIN tags t ON it.tagID = t.tagID
|
|
WHERE it.itemID = inc.itemID
|
|
AND t.name LIKE 'year:%') AS year
|
|
FROM inc
|
|
ORDER BY year DESC
|
|
""").fetchall()
|
|
|
|
included = pl.DataFrame(
|
|
[
|
|
{
|
|
"year": (y or "").removeprefix("year:"),
|
|
"title": ti or "",
|
|
"journal": j or "",
|
|
"rfi_parts": r or "",
|
|
}
|
|
for ti, j, r, y in _rows
|
|
]
|
|
)
|
|
|
|
mo.vstack(
|
|
[
|
|
mo.md(f"""
|
|
### Included at title/abstract stage — {included.height} records (live)
|
|
|
|
Stage-2 includes plus their RFI-domain tags. Every include
|
|
still awaits stage-3 full-text eligibility, so this list
|
|
can only shrink; the LLM's per-record rationale sits on
|
|
each Zotero item as a child note for reviewer audit.
|
|
"""),
|
|
mo.ui.table(
|
|
plain_years(included).to_pandas(),
|
|
page_size=25,
|
|
label="Stage-2 included records",
|
|
),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
# ── 2. Payment context ─────────────────────────────────────────────────
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(cfr_md, fr_md, mo):
|
|
mo.md(f"""
|
|
## 2. Payment Context — the Code Families the RFI Names
|
|
|
|
The RFI's background ({fr_md("91 FR 43949")}) frames the question
|
|
around whether current **E/M, care management, and ACP** billing
|
|
reflects palliative practice, and part (e) names **APCM (HCPCS
|
|
G0556-G0558)** explicitly. This section prices those families —
|
|
CY2026 Final (the current baseline) against the CY2027 NPRM
|
|
proposal — from `pfs.rvu` / `pfs.rvu_proposed`, at each vintage's
|
|
own non-QP standard CF from `pfs.rules`, national unadjusted
|
|
(GPCI = 1.0). Codes on the MSSP primary-care attribution list
|
|
({cfr_md("42 CFR 425.400(c)")}, `cms.primary_care_service_code`)
|
|
are flagged: for those, valuation moves clinician revenue **and**
|
|
ACO attribution denominators at once.
|
|
|
|
Families and code sets are declared in this cell's source —
|
|
membership in each PFS table is queried, never assumed, and codes
|
|
a table doesn't price are reported as such.
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(RULES, pl, proposed_for, q):
|
|
_families = {
|
|
"ACP": ["99497", "99498"],
|
|
"CCM": ["99437", "99439", "99487", "99489", "99490", "99491"],
|
|
"PCM": ["99424", "99425", "99426", "99427"],
|
|
"TCM": ["99495", "99496"],
|
|
"APCM": ["G0556", "G0557", "G0558"],
|
|
}
|
|
_fam_rows = [
|
|
{"hcpcs": c, "family": fam} for fam, codes in _families.items() for c in codes
|
|
]
|
|
fam_df = pl.DataFrame(_fam_rows)
|
|
_codes_sql = ",".join(f"'{c}'" for c in fam_df["hcpcs"])
|
|
|
|
_raw = q(f"""
|
|
WITH fin AS (
|
|
SELECT hcpcs, description,
|
|
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 = '')
|
|
AND hcpcs IN ({_codes_sql})
|
|
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
|
|
),
|
|
prop AS (
|
|
SELECT hcpcs, description AS description_p,
|
|
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 = '')
|
|
AND hcpcs IN ({_codes_sql})
|
|
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
|
|
),
|
|
pcs AS (
|
|
SELECT hcpcs_code AS hcpcs, 1 AS attribution_code
|
|
FROM cms.primary_care_service_code
|
|
)
|
|
SELECT coalesce(fin.hcpcs, prop.hcpcs) AS hcpcs,
|
|
coalesce(fin.description, prop.description_p) AS description,
|
|
fin.total_2026f, prop.total_2027p,
|
|
coalesce(pcs.attribution_code, 0) = 1 AS mssp_attribution
|
|
FROM fin
|
|
FULL JOIN prop ON fin.hcpcs = prop.hcpcs
|
|
LEFT JOIN pcs ON coalesce(fin.hcpcs, prop.hcpcs) = pcs.hcpcs
|
|
""")
|
|
|
|
_cf26 = RULES[2026].conversion_factor
|
|
_cf27p = proposed_for(2027).conversion_factor
|
|
family_pay = (
|
|
fam_df.join(_raw, on="hcpcs", how="left")
|
|
.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"),
|
|
)
|
|
.sort("family", "hcpcs")
|
|
)
|
|
family_pay
|
|
return (family_pay,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(AMBER, RULES, TEAL, alt, family_pay, fr_md, mo, pl, proposed_for):
|
|
_priced = family_pay.filter(
|
|
pl.col("dollar_2026final").is_not_null()
|
|
& pl.col("dollar_2027proposed").is_not_null()
|
|
).with_columns(
|
|
pl.when(pl.col("pct_change") >= 0)
|
|
.then(pl.lit("Gain"))
|
|
.otherwise(pl.lit("Loss"))
|
|
.alias("sign"),
|
|
(
|
|
pl.col("hcpcs")
|
|
+ pl.when(pl.col("mssp_attribution"))
|
|
.then(pl.lit(" ●"))
|
|
.otherwise(pl.lit(""))
|
|
).alias("code_label"),
|
|
)
|
|
_unpriced = family_pay.filter(
|
|
pl.col("dollar_2026final").is_null() | pl.col("dollar_2027proposed").is_null()
|
|
)
|
|
|
|
family_chart = (
|
|
alt.Chart(_priced.to_pandas())
|
|
.mark_bar()
|
|
.encode(
|
|
x=alt.X(
|
|
"pct_change:Q",
|
|
title="% change, CY2026 final → CY2027 proposed (national, GPCI=1.0)",
|
|
),
|
|
y=alt.Y(
|
|
"code_label: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",
|
|
"family",
|
|
"description",
|
|
"mssp_attribution",
|
|
alt.Tooltip("dollar_2026final:Q", format="$.2f"),
|
|
alt.Tooltip("dollar_2027proposed:Q", format="$.2f"),
|
|
alt.Tooltip("pct_change:Q", format="+.2f"),
|
|
],
|
|
)
|
|
.properties(
|
|
title="ACP / Care-Management / APCM Codes — Proposed Payment Change",
|
|
width=620,
|
|
height=max(220, 16 * _priced.height),
|
|
)
|
|
.facet(row=alt.Row("family:N", title=None))
|
|
.resolve_scale(y="independent")
|
|
)
|
|
|
|
_missing_lines = (
|
|
"\n".join(
|
|
f" - `{r['hcpcs']}` ({r['family']}) — priced in "
|
|
+ (
|
|
"CY2026 final only"
|
|
if r["dollar_2026final"] is not None
|
|
else "CY2027 proposal only"
|
|
if r["dollar_2027proposed"] is not None
|
|
else "neither table"
|
|
)
|
|
for r in _unpriced.iter_rows(named=True)
|
|
)
|
|
or " - (none — every declared code prices in both tables)"
|
|
)
|
|
|
|
mo.vstack(
|
|
[
|
|
family_chart,
|
|
mo.md(f"""
|
|
● = MSSP designated primary care service (attribution
|
|
code). One-sided or unpriced codes, excluded from the
|
|
chart rather than shown with a fabricated baseline:
|
|
|
|
{_missing_lines}
|
|
|
|
**Sources:** `pfs.rvu` CY2026 Final
|
|
({fr_md(RULES[2026].federal_register_citation)});
|
|
`pfs.rvu_proposed` `CMS-1848-P`
|
|
({fr_md(proposed_for(2027).federal_register_citation)}) —
|
|
**final rule pending**. Dollars blend RVU and CF changes
|
|
deliberately: this is what the proposal does to realized
|
|
national payment for each service.
|
|
"""),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(AMBER, TEAL, alt, family_pay, mo, pl, q):
|
|
# Long-term RVU trajectory for the declared codes: YoY % heatmap,
|
|
# final rules 2015-2026 + CY2027 proposal.
|
|
_codes_sql = ",".join(f"'{c}'" for c in family_pay["hcpcs"])
|
|
_hist = q(f"""
|
|
WITH 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 = '') AND year >= 2015
|
|
AND hcpcs IN ({_codes_sql})
|
|
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 = '')
|
|
AND hcpcs IN ({_codes_sql})
|
|
QUALIFY row_number() OVER (PARTITION BY hcpcs ORDER BY hcpcs) = 1
|
|
)
|
|
SELECT * FROM fin UNION ALL SELECT * FROM prop
|
|
ORDER BY hcpcs, year
|
|
""")
|
|
|
|
_long = (
|
|
_hist.filter(pl.col("total_rvu").is_not_null())
|
|
.sort("hcpcs", "year")
|
|
.with_columns(
|
|
(
|
|
100
|
|
* (pl.col("total_rvu") / pl.col("total_rvu").shift(1).over("hcpcs") - 1)
|
|
)
|
|
.round(2)
|
|
.alias("rvu_yoy_pct")
|
|
)
|
|
.join(family_pay.select("hcpcs", "family"), on="hcpcs")
|
|
)
|
|
|
|
family_heatmap = (
|
|
alt.Chart(_long.to_pandas())
|
|
.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",
|
|
"family",
|
|
"year:O",
|
|
alt.Tooltip("total_rvu:Q", format=".2f"),
|
|
alt.Tooltip("rvu_yoy_pct:Q", format="+.2f"),
|
|
],
|
|
)
|
|
.properties(
|
|
title="Total-RVU Year-over-Year % — RFI Code Families, 2015-2027p",
|
|
width=560,
|
|
height=16 * _long["hcpcs"].n_unique(),
|
|
)
|
|
)
|
|
mo.vstack(
|
|
[
|
|
family_heatmap,
|
|
mo.md("""
|
|
First priced year per code is blank (no prior year). Late
|
|
first-appearances are themselves informative: PCM
|
|
(99424-7) enters in 2022, APCM (G0556-8) in 2025 — the
|
|
between-visit care infrastructure the RFI's part (d)
|
|
builds on is recent, which is part of why CMS is asking
|
|
what the *next* care-management construct should require.
|
|
"""),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
# ── 3. Proposed palliative care management code family ─────────────────
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(fr_md, mo):
|
|
mo.md(f"""
|
|
## 3. Proposed Palliative Care Management Code Family
|
|
|
|
RFI parts (d) and (e) ({fr_md("91 FR 43950")}) ask which service
|
|
elements a future care-management service for seriously ill
|
|
beneficiaries must include and how its quality should be
|
|
safeguarded. Rather than answer in the abstract, the comment will
|
|
propose a **three-code palliative care management family** —
|
|
structured the way CMS already structures ACP (initial +
|
|
follow-on) and APCM (monthly bundle) — with draft descriptors
|
|
grounded in three professional standards, each captured in the
|
|
evidence base with its citing literature snowballed
|
|
(`rfi:codeset` arm above):
|
|
|
|
- **CAPC** — program standards for specialty palliative care
|
|
(basic standards, May 2025) and the CAPC consensus reports on
|
|
screening criteria (Weissman & Meier 2011) and clinical/customer
|
|
metrics (2010);
|
|
- **NASW** — *Practice Standards for Serious Illness Care: Hospice
|
|
and Palliative Social Work*, especially Standard 3 (Assessment),
|
|
Standard 4 (Intervention/Treatment Planning), Standard 7
|
|
(Documentation), and Standard 8 (Interdisciplinary Teamwork);
|
|
- **The Joint Commission** — the PAL performance measure set
|
|
behind Advanced Certification for Palliative Care: PAL-01 Pain
|
|
Screening, PAL-02 Pain Assessment, PAL-03 Dyspnea Screening,
|
|
PAL-04 Treatment Preferences and Goals of Care, PAL-05
|
|
Treatment Preferences Discharge Document, with the measure-set
|
|
population defined by a face-to-face encounter with a member of
|
|
the palliative care core interdisciplinary team (physician,
|
|
RN/APN, chaplain/spiritual-care professional, social worker).
|
|
|
|
> **Draft descriptors offered for comment — not valued codes.**
|
|
> Nothing below models RUC valuation, RVU inputs, or budget
|
|
> neutrality; eligibility for the family is the subject of RFI
|
|
> parts (b)-(c) and is deliberately left to that evidence. The
|
|
> point is to give CMS a concrete, standards-anchored structure
|
|
> to react to.
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo, pl):
|
|
proposed_codes = pl.DataFrame(
|
|
[
|
|
{
|
|
"code": "PCM-1",
|
|
"short_descriptor": "Palliative care screening & initial assessment",
|
|
"draft_descriptor": (
|
|
"Palliative care screening and comprehensive initial "
|
|
"assessment of a patient with serious illness, by a "
|
|
"member of the palliative care core interdisciplinary "
|
|
"team; including validated symptom screening (pain, "
|
|
"dyspnea), assessment of functional status and "
|
|
"caregiver strain, psychosocial and spiritual "
|
|
"screening, and documentation of screening results in "
|
|
"the medical record; first 60 minutes"
|
|
),
|
|
"capc_basis": "Screening-criteria consensus (Weissman & Meier 2011); daily symptom-assessment metric domain",
|
|
"nasw_basis": "Standard 3 — Assessment",
|
|
"tjc_basis": "PAL-01 Pain Screening; PAL-02 Pain Assessment; PAL-03 Dyspnea Screening; core-team population definition",
|
|
"rfi_parts": "b, c, d",
|
|
"pfs_analogs": "99497 (ACP, first 30 min); 99495 (TCM contact window)",
|
|
},
|
|
{
|
|
"code": "PCM-2",
|
|
"short_descriptor": "Palliative care planning",
|
|
"draft_descriptor": (
|
|
"Development and documentation of a patient-centered "
|
|
"palliative care plan by the interdisciplinary team "
|
|
"with patient and caregiver participation; including "
|
|
"goals of care, treatment preferences, advance "
|
|
"directive reconciliation, and shared "
|
|
"decision-making; recorded as an electronic care "
|
|
"plan available at transitions of care, including "
|
|
"discharge"
|
|
),
|
|
"capc_basis": "Patient-centered goals-of-care metric domain (2010 consensus)",
|
|
"nasw_basis": "Standard 4 — Intervention/Treatment Planning; Standard 7 — Documentation",
|
|
"tjc_basis": "PAL-04 Treatment Preferences and Goals of Care; PAL-05 Treatment Preferences Discharge Document",
|
|
"rfi_parts": "d",
|
|
"pfs_analogs": "99497/99498 (ACP discussion); care-plan element of 99490 (CCM)",
|
|
},
|
|
{
|
|
"code": "PCM-3",
|
|
"short_descriptor": "Palliative care follow-up",
|
|
"draft_descriptor": (
|
|
"Palliative care follow-up management, per calendar "
|
|
"month: symptom reassessment and care-plan revision; "
|
|
"continuity with a designated team member; access to "
|
|
"timely clinical support; coordination with treating "
|
|
"physicians; patient and caregiver education and "
|
|
"support; and timely follow-up after emergency "
|
|
"department visit or facility discharge"
|
|
),
|
|
"capc_basis": "Transitions-across-care-sites and caregiver-support metric domains (2010 consensus)",
|
|
"nasw_basis": "Standard 8 — Interdisciplinary Teamwork; Standard 6 — Empowerment & Advocacy",
|
|
"tjc_basis": "Core-team continuity underlying the PAL population definition",
|
|
"rfi_parts": "d, e",
|
|
"pfs_analogs": "G0556-G0558 (APCM monthly bundle); 99490/99487 (CCM monthly)",
|
|
},
|
|
]
|
|
)
|
|
mo.vstack(
|
|
[
|
|
mo.ui.table(
|
|
proposed_codes.to_pandas(),
|
|
label="Proposed palliative care management family — draft descriptors & standards crosswalk",
|
|
),
|
|
mo.md("""
|
|
Service elements in the follow-up descriptor deliberately
|
|
track the RFI's own part-(d) example list (continuity with
|
|
a designated team member, timely clinical support,
|
|
comprehensive symptom and caregiver assessment, electronic
|
|
care plans, coordination with treating physicians,
|
|
patient/caregiver education, timely follow-up after
|
|
ED/discharge) — the crosswalk shows each element is also a
|
|
professional-standards requirement, not an invention of
|
|
this comment.
|
|
"""),
|
|
]
|
|
)
|
|
return (proposed_codes,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(family_pay, mo, pl, proposed_codes):
|
|
# Live valuation anchors: what the named PFS analogs pay today and
|
|
# under the CY2027 proposal (from Section 2's family_pay frame).
|
|
_analog_codes = [
|
|
"99497",
|
|
"99498",
|
|
"99495",
|
|
"99490",
|
|
"99487",
|
|
"G0556",
|
|
"G0557",
|
|
"G0558",
|
|
]
|
|
analog_anchor = (
|
|
family_pay.filter(pl.col("hcpcs").is_in(_analog_codes))
|
|
.select(
|
|
"hcpcs",
|
|
"family",
|
|
"description",
|
|
"dollar_2026final",
|
|
"dollar_2027proposed",
|
|
"mssp_attribution",
|
|
)
|
|
.sort("family", "hcpcs")
|
|
)
|
|
mo.vstack(
|
|
[
|
|
mo.md("""
|
|
### Valuation anchors (live from the lake)
|
|
|
|
The analog codes each proposed descriptor cites, priced at
|
|
CY2026 final and the CY2027 proposal — the payment
|
|
neighborhood a valued palliative care management family
|
|
would plausibly land in, and the reference points a RUC
|
|
crosswalk would start from. (Read live from Section 2's
|
|
pricing frame; re-runs track every future ingest.)
|
|
"""),
|
|
mo.ui.table(
|
|
analog_anchor.to_pandas(),
|
|
label="PFS analogs for the proposed family",
|
|
),
|
|
mo.md("""
|
|
A quality safeguard consistent with RFI part (e): tie the
|
|
monthly follow-up code (PCM-3) to reporting of CBE 3665
|
|
("feeling heard and understood") the way APCM ties
|
|
G0556-G0558 to the primary-care MIPS Value Pathway — the
|
|
included-evidence table in Section 1 carries the measure's
|
|
supporting literature (`rfi:e`).
|
|
"""),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
# ── 4. Prior rulemaking comment record ─────────────────────────────────
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 4. Prior Rulemaking Comment Record — Palliative Care
|
|
|
|
CMS asks in the RFI what it should know about community-based
|
|
palliative care; part of the answer is that **commenters have been
|
|
telling CMS for a decade**. The bib store holds every comment from
|
|
the last ten PFS rulemaking dockets (CY2018-CY2027). A lexical
|
|
sweep (`dev/scripts/tag_palliative_comments.py`, stem
|
|
`palliativ*` over the extracted comment text, else the enriched
|
|
abstract) tags each referencing comment `topic:palliative` in bib
|
|
and Zotero, with mention counts and sentence snippets on the item.
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(connect, mo, pl):
|
|
import json as _json
|
|
|
|
# keep the Store referenced for the cell — its __del__ closes the con
|
|
_bstore = connect.bib()
|
|
_bcon = _bstore._con()
|
|
_rows = _bcon.execute("""
|
|
SELECT i.key, i.title, i.url, i.extra_json,
|
|
(SELECT substr(t.name, 6) FROM item_tags it
|
|
JOIN tags t ON t.id = it.tag_id
|
|
WHERE it.item_id = i.id AND t.name LIKE 'year:%') AS year,
|
|
(SELECT substr(t.name, 6) FROM item_tags it
|
|
JOIN tags t ON t.id = it.tag_id
|
|
WHERE it.item_id = i.id AND t.name LIKE 'rule:%') AS rule,
|
|
(SELECT substr(t.name, 12) FROM item_tags it
|
|
JOIN tags t ON t.id = it.tag_id
|
|
WHERE it.item_id = i.id AND t.name LIKE 'reg-docket:%') AS docket
|
|
FROM items i
|
|
WHERE i.id IN (
|
|
SELECT it2.item_id FROM item_tags it2
|
|
JOIN tags t2 ON t2.id = it2.tag_id
|
|
WHERE t2.name = 'topic:palliative'
|
|
)
|
|
ORDER BY year, i.title
|
|
""").fetchall()
|
|
|
|
pall_comments = pl.DataFrame(
|
|
[
|
|
{
|
|
"year": r["year"] or "",
|
|
"rule": r["rule"] or "",
|
|
"docket": r["docket"] or "",
|
|
"comment": r["title"] or "",
|
|
"url": r["url"] or "",
|
|
"mentions": (
|
|
_json.loads(r["extra_json"]).get("palliative_mentions", 0)
|
|
if r["extra_json"]
|
|
else 0
|
|
),
|
|
"snippet": (
|
|
(_json.loads(r["extra_json"]).get("palliative_snippets") or [""])[0]
|
|
if r["extra_json"]
|
|
else ""
|
|
),
|
|
}
|
|
for r in _rows
|
|
]
|
|
)
|
|
|
|
_meta_row = _bcon.execute(
|
|
"SELECT value FROM bib_meta WHERE key = 'palliative_sweep'"
|
|
).fetchone()
|
|
pall_sweep = _json.loads(_meta_row[0]) if _meta_row else {"dockets": {}}
|
|
return pall_comments, pall_sweep
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(TEAL, alt, mo, pall_comments, pall_sweep, pl):
|
|
_per_year = (
|
|
pall_comments.group_by("year")
|
|
.agg(pl.len().alias("comments"), pl.col("mentions").sum().alias("mentions"))
|
|
.sort("year")
|
|
)
|
|
_covered = sum(d["with_text"] for d in pall_sweep["dockets"].values())
|
|
_total = sum(d["total"] for d in pall_sweep["dockets"].values())
|
|
|
|
pall_year_chart = (
|
|
alt.Chart(_per_year.to_pandas())
|
|
.mark_bar(color=TEAL)
|
|
.encode(
|
|
x=alt.X("year:N", title="Comment year"),
|
|
y=alt.Y("comments:Q", title="Comments referencing palliative care"),
|
|
tooltip=["year", "comments", "mentions"],
|
|
)
|
|
.properties(
|
|
title="Palliative-Care Mentions Across Ten PFS Comment Cycles (live)",
|
|
width=620,
|
|
height=220,
|
|
)
|
|
)
|
|
|
|
mo.vstack(
|
|
[
|
|
pall_year_chart,
|
|
mo.md(f"""
|
|
**Denominator disclosure:** the sweep covers the
|
|
{_covered:,} of {_total:,} docketed comments with locally
|
|
retrievable text (extracted attachment/inline text for the
|
|
ten PFS dockets, enriched abstracts otherwise) — counts
|
|
are a floor, not a census. The 2018 spike is the CY2019
|
|
E/M office-visit code-collapse proposal, which drew an
|
|
organized response from hospice-and-palliative-medicine
|
|
clinicians (see themes below).
|
|
"""),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo, pall_comments, pl):
|
|
_tbl = (
|
|
pall_comments.sort("mentions", descending=True)
|
|
.with_columns(
|
|
pl.format("[{}]({})", pl.col("comment"), pl.col("url")).alias(
|
|
"comment_link"
|
|
)
|
|
)
|
|
.select(
|
|
pl.col("year"),
|
|
pl.col("rule"),
|
|
pl.col("comment_link").alias("comment"),
|
|
pl.col("mentions"),
|
|
pl.col("snippet"),
|
|
)
|
|
)
|
|
mo.vstack(
|
|
[
|
|
mo.md(f"""
|
|
### The {pall_comments.height} referencing comments (live from bib)
|
|
|
|
Every comment tagged `topic:palliative`, most-mentions
|
|
first, with its first extracted snippet. Each link opens
|
|
the comment on regulations.gov; the same items are in
|
|
Zotero under the `topic:palliative` tag for citation from
|
|
the P44 comment draft.
|
|
"""),
|
|
mo.ui.table(
|
|
_tbl.to_pandas(),
|
|
page_size=15,
|
|
label="Prior-cycle comments referencing palliative care",
|
|
),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
### What prior-cycle commenters told CMS
|
|
|
|
Reading the tagged set, five themes recur — each maps onto an RFI
|
|
part, and each is a decade-long thread, not a one-off:
|
|
|
|
1. **The specialty comments every cycle.** The American Academy of
|
|
Hospice and Palliative Medicine appears in all nine prior
|
|
dockets ([2017](https://www.regulations.gov/comment/CMS-2017-0092-1574),
|
|
[2018](https://www.regulations.gov/comment/CMS-2018-0076-15128),
|
|
[2019](https://www.regulations.gov/comment/CMS-2019-0111-40551),
|
|
[2020](https://www.regulations.gov/comment/CMS-2020-0088-28866),
|
|
[2021](https://www.regulations.gov/comment/CMS-2021-0119-35330),
|
|
[2022](https://www.regulations.gov/comment/CMS-2022-0113-24293),
|
|
[2023](https://www.regulations.gov/comment/CMS-2023-0121-20479),
|
|
[2024](https://www.regulations.gov/comment/CMS-2024-0256-5617),
|
|
[2025](https://www.regulations.gov/comment/CMS-2025-0304-12014)),
|
|
joined repeatedly by the National Coalition for Hospice and
|
|
Palliative Care
|
|
([2018](https://www.regulations.gov/comment/CMS-2018-0076-13835),
|
|
[2023](https://www.regulations.gov/comment/CMS-2023-0121-20746)),
|
|
CAPC ([2017](https://www.regulations.gov/comment/CMS-2017-0092-1374),
|
|
[2018](https://www.regulations.gov/comment/CMS-2018-0076-15030)),
|
|
NHPCO ([2021](https://www.regulations.gov/comment/CMS-2021-0119-34010),
|
|
[2023](https://www.regulations.gov/comment/CMS-2023-0121-21390)),
|
|
and nursing/PA counterparts
|
|
([HPNA 2019](https://www.regulations.gov/comment/CMS-2019-0111-8492),
|
|
[PAHPM 2019](https://www.regulations.gov/comment/CMS-2019-0111-29255)).
|
|
2. **Payment adequacy for long, complex visits — RFI part (d).**
|
|
The 2018 spike is HPM clinicians opposing the CY2019 E/M
|
|
code-collapse, citing AMA analysis of a ~20% office-visit cut
|
|
for the specialty
|
|
([physician letters](https://www.regulations.gov/comment/CMS-2018-0076-14483),
|
|
[UPenn program director](https://www.regulations.gov/comment/CMS-2018-0076-13362));
|
|
the same adequacy argument returns for the G2211-style
|
|
complexity add-on and Principal Illness Navigation in
|
|
[2024](https://www.regulations.gov/comment/CMS-2024-0256-6729),
|
|
asking CMS to make "palliative care in the community
|
|
sustainable under Part B."
|
|
3. **Team-based and non-physician delivery — RFI parts (c)/(d).**
|
|
Recurring asks to recognize interdisciplinary palliative teams
|
|
(chaplains, social workers, nurses) and PA/NP practice
|
|
([OHSU PA 2019](https://www.regulations.gov/comment/CMS-2019-0111-31124),
|
|
[NHPCO on PAs in hospice 2019](https://www.regulations.gov/comment/CMS-2019-0111-40582)).
|
|
4. **Quality measurement — RFI part (e).** From 2023 on,
|
|
commenters push MIPS measure #495 ("Ambulatory Palliative Care
|
|
Patients' Experience of Feeling Heard and Understood") and the
|
|
NCP Clinical Practice Guidelines / NASW standards as the
|
|
measurement backbone
|
|
([2023](https://www.regulations.gov/comment/CMS-2023-0121-20811),
|
|
[AGS 2024](https://www.regulations.gov/comment/CMS-2024-0256-6812),
|
|
[2025](https://www.regulations.gov/comment/CMS-2025-0304-13362)) —
|
|
the same instruments Section 3's PCM-3 safeguard cites.
|
|
5. **Access framing beyond fee-for-service — RFI parts (b)/(d).**
|
|
Telehealth equivalence for home-based palliative care
|
|
([2025](https://www.regulations.gov/comment/CMS-2025-0304-12156)),
|
|
palliative care as a preventive service per Hawaii's Medicaid
|
|
state plan
|
|
([2025](https://www.regulations.gov/comment/CMS-2025-0304-12096)),
|
|
and underutilization despite evidence
|
|
([2024](https://www.regulations.gov/comment/CMS-2024-0256-4853)).
|
|
|
|
For the P44 draft this exhibit supports one sentence CMS cannot
|
|
easily dismiss: the RFI's questions have standing, organized,
|
|
decade-deep constituencies on the record — and the record shows
|
|
exactly where existing codes fell short each time.
|
|
""")
|
|
return
|
|
|
|
|
|
# ── 5. Claims utilization (methodology exhibit) ────────────────────────
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 5. Claims Utilization — Methodology Exhibit (Synthetic CCLF)
|
|
|
|
How often are the care-management services the RFI names actually
|
|
billed, for whom, and do they reach the seriously ill? This section
|
|
demonstrates the analysis on the repo's **synthetic Tuva CCLF**
|
|
(962 beneficiaries, claims 2016-2018) so the identical SQL can run
|
|
against real CCLFs when available.
|
|
|
|
> **Scope disclosure (per #648):** synthetic claims support
|
|
> *methodology* exhibits, not empirical utilization claims. Two
|
|
> structural limits of this data: the 2016-2018 window predates
|
|
> PCM (2020) and APCM G0556-G0558 (2025), so those families are
|
|
> structurally absent; and Tuva's synthetic code frequencies do
|
|
> not model real billing behavior. What the exhibit *does* show is
|
|
> the shape of the question — and even here the sparsity echoes
|
|
> CMS's own finding that "uptake of the care management codes has
|
|
> been limited" (91 FR 43938, § II.E).
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(connect, mo, pl):
|
|
_acon = connect.duckdb()
|
|
claims_util = _acon.execute("""
|
|
WITH fam AS (
|
|
SELECT hcpcs_code, person_id,
|
|
CASE
|
|
WHEN hcpcs_code IN ('99497','99498') THEN 'ACP'
|
|
WHEN hcpcs_code IN ('99490','99439','99487','99489',
|
|
'99491','99437') THEN 'CCM'
|
|
WHEN hcpcs_code IN ('99424','99425','99426','99427')
|
|
THEN 'PCM'
|
|
WHEN hcpcs_code IN ('99495','99496') THEN 'TCM'
|
|
WHEN hcpcs_code IN ('G0556','G0557','G0558') THEN 'APCM'
|
|
WHEN hcpcs_code BETWEEN '99341' AND '99350'
|
|
THEN 'Home E/M'
|
|
END AS family
|
|
FROM input_layer.medical_claim
|
|
),
|
|
serious AS (
|
|
SELECT DISTINCT person_id
|
|
FROM input_layer.medical_claim,
|
|
UNNEST([diagnosis_code_1, diagnosis_code_2,
|
|
diagnosis_code_3, diagnosis_code_4,
|
|
diagnosis_code_5]) AS t(dx)
|
|
WHERE dx IS NOT NULL AND (
|
|
dx LIKE 'C%' OR dx LIKE 'I50%' OR dx LIKE 'J44%'
|
|
OR dx LIKE 'G30%' OR dx LIKE 'N186%' OR dx LIKE 'I255%'
|
|
OR dx LIKE 'G20%' OR dx LIKE 'K74%')
|
|
)
|
|
SELECT fam.family,
|
|
count(*) AS claim_lines,
|
|
count(DISTINCT fam.person_id) AS beneficiaries,
|
|
count(DISTINCT CASE WHEN fam.person_id IN
|
|
(SELECT person_id FROM serious)
|
|
THEN fam.person_id END) AS with_serious_illness_dx
|
|
FROM fam
|
|
WHERE fam.family IS NOT NULL
|
|
GROUP BY fam.family
|
|
ORDER BY claim_lines DESC
|
|
""").pl()
|
|
_serious_n = _acon.execute("""
|
|
SELECT count(DISTINCT person_id)
|
|
FROM input_layer.medical_claim,
|
|
UNNEST([diagnosis_code_1, diagnosis_code_2, diagnosis_code_3,
|
|
diagnosis_code_4, diagnosis_code_5]) AS t(dx)
|
|
WHERE dx IS NOT NULL AND (
|
|
dx LIKE 'C%' OR dx LIKE 'I50%' OR dx LIKE 'J44%'
|
|
OR dx LIKE 'G30%' OR dx LIKE 'N186%' OR dx LIKE 'I255%'
|
|
OR dx LIKE 'G20%' OR dx LIKE 'K74%')
|
|
""").fetchone()[0]
|
|
|
|
_tbl = claims_util.with_columns(
|
|
(pl.col("with_serious_illness_dx") / pl.col("beneficiaries") * 100)
|
|
.round(0)
|
|
.alias("pct_serious")
|
|
)
|
|
mo.vstack(
|
|
[
|
|
mo.ui.table(
|
|
_tbl.to_pandas(),
|
|
label="Palliative-relevant service utilization (synthetic CCLF, live)",
|
|
),
|
|
mo.md(f"""
|
|
Reading it the way the RFI would: **care-management billing
|
|
concentrates in the seriously ill** (75-91% of each
|
|
family's users carry a serious-illness diagnosis — cancer,
|
|
HF, COPD, dementia, ESRD, Parkinson's, cirrhosis) **yet
|
|
reaches only a sliver of them** — {_serious_n} of 962
|
|
synthetic beneficiaries carry such a diagnosis, and the
|
|
largest family (CCM) touches barely a seventh of that
|
|
population. PCM and APCM show zero lines here because the
|
|
synthetic window (2016-2018) predates both — on real
|
|
current CCLFs those columns are the interesting ones. The
|
|
same query, pointed at real claims, quantifies the access
|
|
gap RFI parts (b)-(d) ask about.
|
|
"""),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
# ── 6. Section II.E cross-read ─────────────────────────────────────────
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(fr_md, mo):
|
|
mo.md(f"""
|
|
## 6. Section II.E Cross-Read — Pincites for RFI Part (d)
|
|
|
|
RFI part (d) explicitly ties to section II.E of the same rule
|
|
({fr_md("91 FR 43935")}-43946), where CMS reconsiders primary-care
|
|
valuation and the care-management code family. A close read of the
|
|
full section (#649) yields one load-bearing fact and a pincite
|
|
map:
|
|
|
|
> **Nothing in II.E is proposed.** The entire section is an RFI —
|
|
> every policy idea is "seeking comment," "considering," or
|
|
> "may consider in future rulemaking." The only operative
|
|
> care-management-adjacent text is the **G2211 changes proposed in
|
|
> section II.D** (cross-referenced at {fr_md("91 FR 43937")} ¶3).
|
|
> A part-(d) answer must therefore *answer II.E's questions*, not
|
|
> "support" nonexistent proposals — and can cite CMS's own
|
|
> concessions as the agency-record predicate.
|
|
|
|
**CMS's concessions on the record** (each a predicate for a
|
|
palliative-specific recommendation):
|
|
|
|
- *"uptake of the care management codes has been limited"* and
|
|
CMS "seek[s] feedback on whether a different payment structure
|
|
might be more appropriate" — {fr_md("91 FR 43938")} ¶5.
|
|
- APCM *"retains the fundamental structure of FFS billing and may
|
|
not provide sufficient incentives... to focus on proactive,
|
|
population-based non-visit care management activities"* —
|
|
{fr_md("91 FR 43941")} ¶11.
|
|
- APCM billing runs higher among ACO-assigned beneficiaries,
|
|
suggesting *"accountability and care management infrastructure"*
|
|
drives adoption — {fr_md("91 FR 43942")} ¶1.
|
|
|
|
**The part-(d) question cluster to answer directly:**
|
|
|
|
| Pincite | Gist | Status |
|
|
|---|---|---|
|
|
| {fr_md("91 FR 43936")} ¶5 | two-track (tech vs traditional) care-management code set | RFI question |
|
|
| {fr_md("91 FR 43937")} ¶4 | longitudinal-visit category; between-visit work in valuation | discussed (future rulemaking) |
|
|
| {fr_md("91 FR 43938")} ¶2 | which care-management services to bundle into longitudinal visits | RFI question |
|
|
| {fr_md("91 FR 43938")} ¶4 | 14-year unbundling history; APCM "interprofessional care teams" | discussed |
|
|
| {fr_md("91 FR 43938")} ¶6 | reconfigure the code family; supervision in tech-enabled models | RFI question |
|
|
| {fr_md("91 FR 43939")} ¶2 | open data invitation: advanced-primary-care resource costs | RFI question |
|
|
| {fr_md("91 FR 43939")} ¶6 | technology-enabled care-management codes / differentiation | RFI question |
|
|
| {fr_md("91 FR 43944")} ¶16 | REACH/PC Flex PCC bundle includes **advance care planning** | discussed |
|
|
| {fr_md("91 FR 43945")} ¶16 | risk stratification targeting enhanced care management | RFI question |
|
|
| {fr_md("91 FR 43946")} ¶2 | primary-care "global period" bundling AWV, E/M, APCM | discussed |
|
|
|
|
Vocabulary note for drafting: II.E never says "serious illness" —
|
|
its terms are *chronic conditions*, *complex condition*, and
|
|
*high-risk beneficiaries*; the P44 comment should mirror that
|
|
vocabulary when mapping palliative care onto II.E's constructs.
|
|
The Section 3 code family (PCM-1/2/3) answers II.E's
|
|
consolidation question directly: it is a care-management track
|
|
for the population II.E's own data shows the current family
|
|
fails to reach.
|
|
""")
|
|
return
|
|
|
|
|
|
# ── 7. Provenance ──────────────────────────────────────────────────────
|
|
|
|
|
|
@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()}, comments due
|
|
**{_close.isoformat()}** (`proposed_for(2027).comment_close`,
|
|
read live; today is {_today.isoformat()}). This notebook's
|
|
exhibits and the PRISMA evidence base feed the P44 comment to
|
|
docket CMS-2026-2377.
|
|
"""),
|
|
kind="success" if _open else "danger",
|
|
)
|
|
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')
|
|
ORDER BY ingested_at DESC
|
|
""")
|
|
mo.vstack(
|
|
[
|
|
mo.md("""
|
|
## 7. Provenance
|
|
|
|
Ingest-log rows for the lake tables Section 2 reads, plus
|
|
the evidence-base pipeline: identification —
|
|
`dev/scripts/search_pubmed_palliative_rfi.py` (E-utilities
|
|
database searches, uncapped, plus elink forward snowball
|
|
and grey-literature standards documents; run 2026-08-18);
|
|
screening — `stack prisma screen palliative-rfi` (stage 2,
|
|
2026-08-18) and `stack prisma eligible palliative-rfi
|
|
--max-chars 60000` (stage 3, 2026-08-20, qwen2.5:14b-ctx32k
|
|
on local GPU); flow — `prisma.flow` over live Zotero tags.
|
|
Section 4's comment record —
|
|
`dev/scripts/tag_palliative_comments.py` (lexical sweep
|
|
over the Mirrulations-completed corpus, 2026-08-19; stats
|
|
in `bib_meta.palliative_sweep`). Section 5 —
|
|
`input_layer.medical_claim` (synthetic Tuva CCLF) read
|
|
live from aco.duckdb. Section 6 — close read of
|
|
`data/fr_downloads/2026-14327.txt` lines 8278-9564 (#649);
|
|
jump links resolve via `bib.frlink` anchor maps.
|
|
`cms.primary_care_service_code` is the aco module's
|
|
attribution reference list (P39), not an FR ingest.
|
|
"""),
|
|
mo.ui.table(
|
|
provenance.to_pandas(),
|
|
label="Ingest Log — Lake Tables Used in Section 2",
|
|
),
|
|
]
|
|
)
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run()
|