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 / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 56s
Infra CI / zotero (push) Successful in 13s
Infra CI / docs (push) Successful in 1m14s
Infra CI / api (push) Successful in 50s
Infra CI / mc (push) Successful in 19s
Deploy / report (push) Successful in 13s
CI / test (push) Successful in 14m27s
Harden / build-scan-report (push) Successful in 26m15s
Renovate / renovate (push) Successful in 15s
Notebooks Integration / notebooks-integration (push) Successful in 7m16s
Zotero Sync / zotero-sync (push) Successful in 53s
Package Supply Chain / pkg-supply-chain (push) Successful in 58s
The M5 close-out missed half the issue's scope: #514 says 'OPPS/PFS reference data' and I cut over only OPPS, leaving PFS — the largest reference domain, 23.5M rows across 8 tables — entirely on the monolith, including pfs.* queries in the very notebook whose OPPS query was migrated. This completes PFS the same way: - publish_opps_to_lake.py → publish_reference_to_lake.py with a schema registry (opps: 3 tables, pfs: 8); host-side docker-exec wrapper extracted to dev/scripts/_lake.py, shared by the ingests. - PFS published to the lake and read-back verified: carrier_locality 21,863,770 rows in 10.1s, plus rvu/gpci/clinical_labor/medical_ equipment/medical_supply/physician_work_time/zip_carrier_locality. - New dev/scripts/ingest_pfs.py wraps pfs.pipe.load_all (previously ad-hoc, no entrypoint) with the standard plumbing: duckdb_batch preflight, replica refresh, lake publish. - 5 notebooks migrated: pfs_calcs, pfs_reconciliation, skin_sub_budget_neutrality read the lake as their primary connection; skin_sub_pricing and skin_sub_cost_sharing switch their pure-pfs cells to the lake. The one cross-source join (pfs × skin_subs) stays on the monolith mirror, annotated. All 5 headless-verified in prod: zero cell errors. - pfs_calcs leaves the pre-commit host-run safe list (the lake catalog is compose-internal); the nightly integration covers it in-container.
609 lines
20 KiB
Python
609 lines
20 KiB
Python
import marimo
|
||
|
||
__generated_with = "0.21.1"
|
||
app = marimo.App(width="medium")
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _():
|
||
import marimo as mo
|
||
|
||
return (mo,)
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
# PFS Budget Neutrality Impact of Skin Substitute Codes
|
||
|
||
The Physician Fee Schedule is **budget neutral** — when CMS raises
|
||
RVUs for any service, it must lower them elsewhere (or reduce the
|
||
conversion factor) so aggregate spending stays flat. This notebook
|
||
quantifies how skin substitute application code revaluations
|
||
redistribute payment away from other services.
|
||
|
||
**Mechanism:** CMS publishes ~11,000 HCPCS codes with Work, PE, and
|
||
MP RVUs. When the sum of (RVU × frequency) grows, the conversion
|
||
factor or a budget neutrality adjustor (BNA) scales down to
|
||
compensate. Every code shares the compression proportionally.
|
||
|
||
**What this means:** A PE RVU increase on 15271–15278 is not free
|
||
money — it is a tax on every other service in the fee schedule.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _():
|
||
import altair as alt
|
||
import polars as pl
|
||
|
||
from conf import connect
|
||
from pfs.rules import RULES
|
||
|
||
# PFS reference data lives in the DuckLake lakehouse (M5, #514);
|
||
# queries are unchanged — the lake is the default database.
|
||
con = connect.ducklake()
|
||
|
||
def q(sql):
|
||
return con.execute(sql).pl()
|
||
|
||
SKIN_CODES = "('15271','15272','15273','15274','15275','15276','15277','15278')"
|
||
|
||
return RULES, SKIN_CODES, alt, con, pl, q
|
||
|
||
|
||
# ── 1. Skin sub RVU growth vs. total pool ────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 1. Skin Substitute Share of the RVU Pool
|
||
|
||
The unweighted RVU pool (sum of all base-mod RVUs across ~11k codes)
|
||
grows over time as CMS adds codes and revalues services. The skin
|
||
sub application codes (15271–15278) are a small but growing share.
|
||
|
||
> **Note:** Budget neutrality operates on *frequency-weighted* RVUs
|
||
> (RVU × utilization), not unweighted sums. Without CMS utilization
|
||
> data, this analysis uses unweighted RVUs as a structural proxy.
|
||
> The actual impact is amplified by the explosive volume growth in
|
||
> skin substitute claims documented by OIG.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(SKIN_CODES, alt, mo, q):
|
||
pool_share = q(f"""
|
||
WITH pool AS (
|
||
SELECT year,
|
||
sum(work_rvu + non_fac_pe_rvu + mp_rvu) as total_rvu,
|
||
sum(CASE WHEN hcpcs IN {SKIN_CODES}
|
||
THEN work_rvu + non_fac_pe_rvu + mp_rvu ELSE 0 END) as skin_rvu,
|
||
sum(CASE WHEN hcpcs NOT IN {SKIN_CODES}
|
||
THEN work_rvu + non_fac_pe_rvu + mp_rvu ELSE 0 END) as other_rvu
|
||
FROM pfs.rvu
|
||
WHERE mod IS NULL OR mod = ''
|
||
GROUP BY year
|
||
)
|
||
SELECT year,
|
||
round(skin_rvu, 2) as skin_rvu,
|
||
round(total_rvu, 1) as total_rvu,
|
||
round(100.0 * skin_rvu / total_rvu, 4) as skin_pct,
|
||
round(skin_rvu - LAG(skin_rvu) OVER (ORDER BY year), 2) as skin_delta,
|
||
round(total_rvu - LAG(total_rvu) OVER (ORDER BY year), 1) as pool_delta
|
||
FROM pool ORDER BY year
|
||
""")
|
||
|
||
share_chart = (
|
||
alt.Chart(pool_share.to_pandas())
|
||
.mark_bar()
|
||
.encode(
|
||
x=alt.X("year:O", title="Year"),
|
||
y=alt.Y("skin_rvu:Q", title="Skin Sub Total NF RVUs (8 codes)"),
|
||
tooltip=["year", "skin_rvu", "total_rvu", "skin_pct", "skin_delta"],
|
||
)
|
||
.properties(
|
||
title="Skin Sub Application Codes — Total NF RVUs by Year",
|
||
width=700,
|
||
height=300,
|
||
)
|
||
)
|
||
|
||
mo.vstack([share_chart, pool_share])
|
||
return (pool_share,)
|
||
|
||
|
||
# ── 2. PE RVU revaluation trajectory ─────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 2. Practice Expense RVU Revaluation
|
||
|
||
PE is the largest RVU component for skin sub application codes and
|
||
is where the budget neutrality tax bites hardest. When CMS increases
|
||
PE RVUs for these codes (e.g., to reflect updated clinical labor
|
||
rates or supply costs), **all other codes' PE RVUs must absorb a
|
||
compensating reduction** via the BNA.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(alt, q):
|
||
pe_trajectory = q("""
|
||
SELECT year, hcpcs,
|
||
CASE hcpcs
|
||
WHEN '15271' THEN '15271 trunk <100cm²'
|
||
WHEN '15272' THEN '15272 trunk add-on'
|
||
WHEN '15275' THEN '15275 face <100cm²'
|
||
WHEN '15276' THEN '15276 face add-on'
|
||
END as label,
|
||
non_fac_pe_rvu,
|
||
work_rvu,
|
||
mp_rvu,
|
||
non_fac_pe_rvu + work_rvu + mp_rvu as total_nf_rvu
|
||
FROM pfs.rvu
|
||
WHERE hcpcs IN ('15271','15272','15275','15276')
|
||
AND (mod IS NULL OR mod = '')
|
||
ORDER BY year, hcpcs
|
||
""")
|
||
|
||
pe_chart = (
|
||
alt.Chart(pe_trajectory.to_pandas())
|
||
.mark_line(point=True)
|
||
.encode(
|
||
x=alt.X("year:O", title="Year"),
|
||
y=alt.Y("non_fac_pe_rvu:Q", title="Non-Facility PE RVU"),
|
||
color=alt.Color("label:N", title="Code"),
|
||
tooltip=[
|
||
"year",
|
||
"hcpcs",
|
||
"label",
|
||
"non_fac_pe_rvu",
|
||
"work_rvu",
|
||
"total_nf_rvu",
|
||
],
|
||
)
|
||
.properties(title="Practice Expense RVU Trajectory", width=700, height=350)
|
||
)
|
||
pe_chart
|
||
return
|
||
|
||
|
||
# ── 3. Implied budget neutrality tax ─────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 3. Implied Budget Neutrality Tax
|
||
|
||
When skin sub PE RVUs increase by Δ, **every other code's effective
|
||
payment decreases** proportionally. The "tax rate" is:
|
||
|
||
```
|
||
tax_rate = skin_sub_PE_delta / total_pool_PE
|
||
```
|
||
|
||
This table shows the year-over-year PE RVU increase for the 8 skin
|
||
sub codes, the total PE pool, and the implied compression on all
|
||
other codes.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(SKIN_CODES, q):
|
||
bn_tax = q(f"""
|
||
WITH yearly AS (
|
||
SELECT year,
|
||
sum(CASE WHEN hcpcs IN {SKIN_CODES} THEN non_fac_pe_rvu ELSE 0 END) as skin_pe,
|
||
sum(non_fac_pe_rvu) as total_pe,
|
||
sum(CASE WHEN hcpcs NOT IN {SKIN_CODES} THEN non_fac_pe_rvu ELSE 0 END) as other_pe
|
||
FROM pfs.rvu
|
||
WHERE mod IS NULL OR mod = ''
|
||
GROUP BY year
|
||
)
|
||
SELECT year,
|
||
round(skin_pe, 2) as skin_pe_rvu,
|
||
round(total_pe, 1) as total_pe_pool,
|
||
round(skin_pe - LAG(skin_pe) OVER (ORDER BY year), 2) as skin_pe_delta,
|
||
round(total_pe - LAG(total_pe) OVER (ORDER BY year), 1) as pool_pe_delta,
|
||
-- If skin PE grew and pool grew less, the difference is absorbed by others
|
||
round(CASE WHEN LAG(skin_pe) OVER (ORDER BY year) IS NOT NULL
|
||
THEN (skin_pe - LAG(skin_pe) OVER (ORDER BY year))
|
||
/ NULLIF(LAG(total_pe) OVER (ORDER BY year), 0) * 100
|
||
END, 4) as implied_tax_pct,
|
||
-- Dollar impact: tax_pct × average CF
|
||
round(CASE WHEN LAG(skin_pe) OVER (ORDER BY year) IS NOT NULL
|
||
THEN (skin_pe - LAG(skin_pe) OVER (ORDER BY year))
|
||
/ NULLIF(LAG(total_pe) OVER (ORDER BY year), 0) * 100
|
||
END, 4) as pct_compression
|
||
FROM yearly ORDER BY year
|
||
""")
|
||
bn_tax
|
||
return (bn_tax,)
|
||
|
||
|
||
# ── 4. Which services bear the burden? ────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 4. Which Services Bear the Burden?
|
||
|
||
Budget neutrality compression is proportional to each service
|
||
category's share of the PE pool. Categories with large PE shares
|
||
(surgery, cardiology) absorb more dollars even though the per-code
|
||
reduction is tiny.
|
||
|
||
The table shows: if the skin sub PE increase in the most recent year
|
||
were fully offset by compressing other categories, how much does
|
||
each category lose?
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(SKIN_CODES, alt, mo, q):
|
||
# Get the latest year's skin sub PE delta
|
||
skin_pe_delta = q(f"""
|
||
WITH yearly AS (
|
||
SELECT year, sum(non_fac_pe_rvu) as skin_pe
|
||
FROM pfs.rvu
|
||
WHERE hcpcs IN {SKIN_CODES} AND (mod IS NULL OR mod = '')
|
||
GROUP BY year
|
||
)
|
||
SELECT year, skin_pe,
|
||
skin_pe - LAG(skin_pe) OVER (ORDER BY year) as delta
|
||
FROM yearly ORDER BY year DESC LIMIT 1
|
||
""")
|
||
|
||
delta_val = skin_pe_delta.select("delta").item()
|
||
delta_year = skin_pe_delta.select("year").item()
|
||
|
||
category_impact = q(f"""
|
||
SELECT
|
||
CASE
|
||
WHEN hcpcs BETWEEN '99201' AND '99499' THEN 'E/M'
|
||
WHEN hcpcs BETWEEN '10000' AND '19999' THEN 'Integumentary'
|
||
WHEN hcpcs BETWEEN '20000' AND '29999' THEN 'Musculoskeletal'
|
||
WHEN hcpcs BETWEEN '30000' AND '39999' THEN 'Resp/Cardiovascular'
|
||
WHEN hcpcs BETWEEN '40000' AND '49999' THEN 'Digestive'
|
||
WHEN hcpcs BETWEEN '50000' AND '59999' THEN 'Urinary/Genital'
|
||
WHEN hcpcs BETWEEN '60000' AND '69999' THEN 'Endocrine/Nervous'
|
||
WHEN hcpcs BETWEEN '70000' AND '79999' THEN 'Radiology'
|
||
WHEN hcpcs BETWEEN '80000' AND '89999' THEN 'Path/Lab'
|
||
WHEN hcpcs BETWEEN '90000' AND '99199' THEN 'Medicine'
|
||
WHEN hcpcs LIKE 'G%' THEN 'G-codes'
|
||
ELSE 'Other'
|
||
END as category,
|
||
count(*) as codes,
|
||
round(sum(non_fac_pe_rvu), 1) as category_pe,
|
||
round(100.0 * sum(non_fac_pe_rvu) /
|
||
NULLIF((SELECT sum(non_fac_pe_rvu) FROM pfs.rvu
|
||
WHERE year={delta_year} AND (mod IS NULL OR mod = '')
|
||
AND hcpcs NOT IN {SKIN_CODES}), 0), 2) as pe_share_pct,
|
||
-- Implied PE reduction absorbed by this category
|
||
round({delta_val} * sum(non_fac_pe_rvu) /
|
||
NULLIF((SELECT sum(non_fac_pe_rvu) FROM pfs.rvu
|
||
WHERE year={delta_year} AND (mod IS NULL OR mod = '')
|
||
AND hcpcs NOT IN {SKIN_CODES}), 0), 4) as implied_pe_loss
|
||
FROM pfs.rvu
|
||
WHERE year = {delta_year}
|
||
AND (mod IS NULL OR mod = '')
|
||
AND hcpcs NOT IN {SKIN_CODES}
|
||
GROUP BY category
|
||
ORDER BY category_pe DESC
|
||
""")
|
||
|
||
impact_chart = (
|
||
alt.Chart(category_impact.to_pandas())
|
||
.mark_bar()
|
||
.encode(
|
||
x=alt.X(
|
||
"implied_pe_loss:Q",
|
||
title=f"Implied PE RVU Loss (from {delta_val:+.2f} skin sub PE delta)",
|
||
),
|
||
y=alt.Y("category:N", title="", sort="-x"),
|
||
color=alt.Color(
|
||
"pe_share_pct:Q",
|
||
title="PE Pool Share %",
|
||
scale=alt.Scale(scheme="reds"),
|
||
),
|
||
tooltip=[
|
||
"category",
|
||
"codes",
|
||
"category_pe",
|
||
"pe_share_pct",
|
||
"implied_pe_loss",
|
||
],
|
||
)
|
||
.properties(
|
||
title=f"Budget Neutrality Burden by Service Category (CY{delta_year})",
|
||
width=700,
|
||
height=350,
|
||
)
|
||
)
|
||
|
||
mo.vstack(
|
||
[
|
||
mo.md(f"""
|
||
**CY{delta_year}:** Skin sub application codes gained **{delta_val:+.2f} PE RVUs**.
|
||
Under budget neutrality, this is redistributed across ~{category_impact.select("codes").sum().item():,} other codes
|
||
proportional to their PE share.
|
||
"""),
|
||
impact_chart,
|
||
category_impact,
|
||
]
|
||
)
|
||
return
|
||
|
||
|
||
# ── 5. Conversion factor erosion ─────────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 5. Conversion Factor Erosion
|
||
|
||
The conversion factor has declined from $36.09 (2020) to $32.35
|
||
(2025–2026). While this is driven primarily by MACRA spending
|
||
targets, **RVU pool growth contributes to the pressure.** When total
|
||
unweighted RVUs grow faster than allowed spending, the CF must
|
||
decline to maintain budget neutrality.
|
||
|
||
This chart overlays the CF trajectory with the total RVU pool
|
||
growth to show the inverse relationship.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(RULES, alt, pl, q):
|
||
cf_data = pl.DataFrame(
|
||
{
|
||
"year": list(RULES.keys()),
|
||
"conversion_factor": [r.conversion_factor for r in RULES.values()],
|
||
}
|
||
)
|
||
|
||
pool_growth = q("""
|
||
SELECT year, round(sum(work_rvu + non_fac_pe_rvu + mp_rvu), 0) as total_rvu
|
||
FROM pfs.rvu WHERE mod IS NULL OR mod = ''
|
||
GROUP BY year ORDER BY year
|
||
""")
|
||
|
||
combined = cf_data.join(pool_growth, on="year", how="inner")
|
||
|
||
cf_line = (
|
||
alt.Chart(combined.to_pandas())
|
||
.mark_line(point=True, color="#1f77b4")
|
||
.encode(
|
||
x=alt.X("year:O", title="Year"),
|
||
y=alt.Y(
|
||
"conversion_factor:Q",
|
||
title="Conversion Factor ($)",
|
||
scale=alt.Scale(zero=False),
|
||
),
|
||
tooltip=["year", "conversion_factor", "total_rvu"],
|
||
)
|
||
)
|
||
|
||
rvu_line = (
|
||
alt.Chart(combined.to_pandas())
|
||
.mark_line(point=True, color="#d62728", strokeDash=[4, 4])
|
||
.encode(
|
||
x=alt.X("year:O"),
|
||
y=alt.Y(
|
||
"total_rvu:Q",
|
||
title="Total Unweighted RVU Pool",
|
||
scale=alt.Scale(zero=False),
|
||
),
|
||
)
|
||
)
|
||
|
||
cf_chart = (
|
||
alt.layer(cf_line, rvu_line)
|
||
.resolve_scale(y="independent")
|
||
.properties(
|
||
title="Conversion Factor vs. RVU Pool Growth", width=700, height=350
|
||
)
|
||
)
|
||
cf_chart
|
||
return
|
||
|
||
|
||
# ── 6. Per-code dollar impact ─────────────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 6. Dollar Impact on Common Services
|
||
|
||
How much does a typical office visit, imaging study, or surgical
|
||
procedure lose when skin sub PE RVUs increase? This table shows
|
||
the implied payment reduction for commonly billed codes.
|
||
|
||
The calculation: if skin sub PE grows by Δ and the total PE pool
|
||
is P, then each other code's PE is effectively reduced by
|
||
`code_PE × (Δ / P)`, and payment drops by that × CF.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(RULES, SKIN_CODES, mo, q):
|
||
|
||
latest_year = max(RULES.keys())
|
||
cf = RULES[latest_year].conversion_factor
|
||
|
||
# Latest skin PE delta
|
||
skin_delta = q(f"""
|
||
WITH yearly AS (
|
||
SELECT year, sum(non_fac_pe_rvu) as skin_pe
|
||
FROM pfs.rvu WHERE hcpcs IN {SKIN_CODES} AND (mod IS NULL OR mod = '')
|
||
GROUP BY year
|
||
)
|
||
SELECT skin_pe - LAG(skin_pe) OVER (ORDER BY year) as delta
|
||
FROM yearly ORDER BY year DESC LIMIT 1
|
||
""").item()
|
||
|
||
total_pe = q(f"""
|
||
SELECT sum(non_fac_pe_rvu) FROM pfs.rvu
|
||
WHERE year = {latest_year} AND (mod IS NULL OR mod = '')
|
||
AND hcpcs NOT IN {SKIN_CODES}
|
||
""").item()
|
||
|
||
tax_rate = skin_delta / total_pe if total_pe else 0
|
||
|
||
common_codes = q(f"""
|
||
SELECT hcpcs, description,
|
||
non_fac_pe_rvu,
|
||
work_rvu,
|
||
mp_rvu,
|
||
non_fac_pe_rvu + work_rvu + mp_rvu as total_rvu,
|
||
round(non_fac_pe_rvu * {tax_rate}, 6) as pe_rvu_loss,
|
||
round(non_fac_pe_rvu * {tax_rate} * {cf}, 4) as dollar_loss
|
||
FROM pfs.rvu
|
||
WHERE year = {latest_year}
|
||
AND (mod IS NULL OR mod = '')
|
||
AND hcpcs IN ('99213','99214','99215',
|
||
'99203','99204','99205',
|
||
'27447','27130',
|
||
'43239','45380',
|
||
'93000','93306',
|
||
'77067','74177',
|
||
'36415','85025',
|
||
'90834','90837',
|
||
'17000','11102')
|
||
ORDER BY dollar_loss
|
||
""")
|
||
|
||
mo.vstack(
|
||
[
|
||
mo.md(f"""
|
||
**CY{latest_year}** parameters:
|
||
- Skin sub PE delta: **{skin_delta:+.2f} RVUs**
|
||
- Other-code PE pool: **{total_pe:,.1f} RVUs**
|
||
- Implied tax rate: **{tax_rate * 100:.4f}%** of each code's PE
|
||
- CF: **${cf}**
|
||
"""),
|
||
common_codes,
|
||
]
|
||
)
|
||
return
|
||
|
||
|
||
# ── 7. Cumulative tax since 2015 ──────────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 7. Cumulative Structural Tax Since 2015
|
||
|
||
The skin sub application codes' total PE RVUs have grown from
|
||
16.45 (2015) to 22.05 (2026). Under budget neutrality, this
|
||
5.60 RVU increase must come from somewhere.
|
||
|
||
This chart shows the cumulative PE RVU "withdrawn" from the
|
||
rest of the fee schedule by the growth in skin sub PE.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(SKIN_CODES, alt, q):
|
||
cumulative = q(f"""
|
||
WITH yearly AS (
|
||
SELECT year,
|
||
sum(CASE WHEN hcpcs IN {SKIN_CODES} THEN non_fac_pe_rvu ELSE 0 END) as skin_pe,
|
||
sum(non_fac_pe_rvu) as total_pe
|
||
FROM pfs.rvu WHERE mod IS NULL OR mod = ''
|
||
GROUP BY year
|
||
)
|
||
SELECT year,
|
||
round(skin_pe, 2) as skin_pe,
|
||
round(skin_pe - FIRST_VALUE(skin_pe) OVER (ORDER BY year), 2) as cumulative_pe_growth,
|
||
round(total_pe, 1) as total_pe
|
||
FROM yearly ORDER BY year
|
||
""")
|
||
|
||
cum_chart = (
|
||
alt.Chart(cumulative.to_pandas())
|
||
.mark_area(opacity=0.3, color="#d62728")
|
||
.encode(
|
||
x=alt.X("year:O", title="Year"),
|
||
y=alt.Y(
|
||
"cumulative_pe_growth:Q",
|
||
title="Cumulative Skin Sub PE Growth (RVUs above 2015 baseline)",
|
||
),
|
||
tooltip=["year", "skin_pe", "cumulative_pe_growth", "total_pe"],
|
||
)
|
||
) + (
|
||
alt.Chart(cumulative.to_pandas())
|
||
.mark_line(point=True, color="#d62728")
|
||
.encode(
|
||
x="year:O",
|
||
y="cumulative_pe_growth:Q",
|
||
)
|
||
)
|
||
|
||
cum_chart_final = cum_chart.properties(
|
||
title="Cumulative PE RVU Growth — Skin Sub Application Codes vs. 2015 Baseline",
|
||
width=700,
|
||
height=300,
|
||
)
|
||
cum_chart_final
|
||
return
|
||
|
||
|
||
# ── 8. Key findings ──────────────────────────────────────────────────
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md("""
|
||
## 8. Key Findings
|
||
|
||
1. **Structural tax is real but small per-code.** The unweighted RVU
|
||
share of skin sub application codes is ~0.1% of the pool.
|
||
Per-code compression is fractions of a cent.
|
||
|
||
2. **Volume is the amplifier.** The structural RVU analysis
|
||
understates the true impact because it ignores utilization.
|
||
OIG documented explosive volume growth in skin sub claims — when
|
||
frequency-weighted, these 8 codes consume a much larger share of
|
||
aggregate spending than their unweighted RVUs suggest.
|
||
|
||
3. **PE is the battleground.** Work RVUs for 15271–15278 have been
|
||
stable (13.46 total for 10 years). PE RVUs grew from 16.45 to
|
||
22.05 (+34%). The CY2022 clinical labor rate update was a major
|
||
driver.
|
||
|
||
4. **CY2026 reclassification shifts the tax.** Moving skin subs
|
||
from ASP + 6% (OPPS) to flat $127.28/cm² doesn't directly
|
||
affect the PFS budget neutrality pool — but it does change
|
||
the volume incentives that drive utilization of application
|
||
codes 15271–15278.
|
||
|
||
5. **The real cost is in the product, not the application.** For a
|
||
25cm² wound, the application fee (~$140) is dwarfed by the
|
||
product cost (often >$5,000). Budget neutrality only governs
|
||
the application fee; the product cost is outside the PFS.
|
||
""")
|
||
return
|
||
|
||
|
||
if __name__ == "__main__":
|
||
app.run()
|