Files
stack/dev/scripts/build_skin_subs_analytics.py
kert 16f3b43974
Some checks failed
CI / skinny-install (aco) (push) Successful in 1m12s
CI / skinny-install (api) (push) Successful in 30s
CI / skinny-install (bcda) (push) Successful in 36s
CI / skinny-install (bib) (push) Successful in 35s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 41s
CI / skinny-install (cms) (push) Successful in 37s
CI / skinny-install (conf) (push) Successful in 38s
CI / skinny-install (opps) (push) Successful in 33s
CI / skinny-install (perf) (push) Successful in 38s
CI / skinny-install (pfs) (push) Successful in 38s
CI / skinny-install (rex) (push) Successful in 34s
Deploy / build-scan-report (push) Failing after 46s
Infra CI / notebooks (push) Failing after 25s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Failing after 16s
CI / lint-test (push) Failing after 11m2s
Infra CI / mc (push) Successful in 21s
Infra CI / api (push) Successful in 29s
Package Supply Chain / pkg-supply-chain (push) Failing after 41s
feat: full session — mail servers, comment pipeline, PRISMA fetch, email ingest
Mail: Maddy on DO (corwins.media+Resend, fhirworx.io+Postmark),
touchless/stateless/idempotent. Gitea SMTP via env_file. CMS inbox
at cmsupdates@mail.fhirworx.io with IMAP→bib poller.

Bib: regulations.gov v4 client, Federal Register discovery, 164K
comment backfill (running), IMAP email ingest, Zotero sync routing.

PRISMA: altcha PoW solver, CrossRef DOI resolution, 83/129 PDFs.
Zotero: schema parity, ops module, CLI, fail-fast guard.
CI: docs.Dockerfile COPY glob fix (tracks #341).
Infra: Gitea+marimo fhirworx themes, IOM/OIG modules.
2026-04-16 09:04:38 -04:00

405 lines
15 KiB
Python

"""Build skin substitutes analytical tables in DuckDB.
Creates derived tables for ASP pricing trajectories (#239),
manufacturer/product market segmentation (#238), and utilization
metrics from synthetic claims (#234).
Usage:
uv run python dev/scripts/build_skin_subs_analytics.py
"""
from __future__ import annotations
from pathlib import Path
import duckdb
ROOT = Path(__file__).resolve().parents[2]
DUCKDB_PATH = ROOT / "data" / "aco.duckdb"
FLAT_RATE_2026 = 127.28 # CMS Jan 2026 reclassification: $/cm²
def build_asp_trajectories(con: duckdb.DuckDBPyConnection) -> None:
"""#239 — Product-level ASP pricing time series with analytics."""
print("\n=== ASP Pricing Trajectories (#239) ===")
con.execute("DROP TABLE IF EXISTS skin_subs.asp_trajectories")
con.execute(f"""
CREATE TABLE skin_subs.asp_trajectories AS
WITH quarterly AS (
SELECT
a.quarter,
a.hcpcs_code,
a.short_description,
h.product_name,
h.manufacturer,
h.category,
a.asp_per_unit,
a.payment_limit,
-- Quarter as sortable integer for window functions
(CAST(split_part(a.quarter, '-Q', 1) AS INTEGER) * 4
+ CAST(split_part(a.quarter, '-Q', 2) AS INTEGER)) AS q_ord
FROM skin_subs.asp_quarterly a
LEFT JOIN skin_subs.hcpcs_universe h
ON a.hcpcs_code = h.hcpcs_code
),
with_lag AS (
SELECT *,
LAG(asp_per_unit) OVER (
PARTITION BY hcpcs_code ORDER BY q_ord
) AS prev_asp,
FIRST_VALUE(asp_per_unit) OVER (
PARTITION BY hcpcs_code ORDER BY q_ord
) AS first_asp,
FIRST_VALUE(quarter) OVER (
PARTITION BY hcpcs_code ORDER BY q_ord
) AS entry_quarter,
COUNT(*) OVER (
PARTITION BY hcpcs_code
) AS total_quarters
FROM quarterly
)
SELECT
quarter,
hcpcs_code,
short_description,
product_name,
manufacturer,
category,
asp_per_unit,
payment_limit,
entry_quarter,
total_quarters,
-- Quarter-over-quarter change
CASE WHEN prev_asp > 0
THEN round((asp_per_unit - prev_asp) / prev_asp * 100, 2)
ELSE NULL END AS qoq_pct_change,
-- Cumulative change from entry
CASE WHEN first_asp > 0
THEN round((asp_per_unit - first_asp) / first_asp * 100, 2)
ELSE NULL END AS cumulative_pct_change,
-- Jan 2026 flat-rate impact: difference from $127.28/cm²
round(payment_limit - {FLAT_RATE_2026}, 2) AS flat_rate_delta,
-- Flag: would this product lose or gain under flat rate?
CASE WHEN payment_limit > {FLAT_RATE_2026} THEN 'loses'
WHEN payment_limit < {FLAT_RATE_2026} THEN 'gains'
ELSE 'neutral' END AS flat_rate_impact,
-- Anomaly flag: >50% QoQ spike
CASE WHEN prev_asp > 0
AND abs(asp_per_unit - prev_asp) / prev_asp > 0.5
THEN true ELSE false END AS anomalous_spike
FROM with_lag
ORDER BY hcpcs_code, quarter
""")
count = con.execute("SELECT count(*) FROM skin_subs.asp_trajectories").fetchone()[0]
print(f" Rows: {count}")
# Summary stats
for label, q in [
(
"Products with anomalous spikes",
"SELECT count(DISTINCT hcpcs_code) FROM skin_subs.asp_trajectories WHERE anomalous_spike",
),
(
"Products that lose under flat rate (latest quarter)",
"""SELECT count(DISTINCT hcpcs_code) FROM skin_subs.asp_trajectories
WHERE flat_rate_impact = 'loses'
AND quarter = (SELECT max(quarter) FROM skin_subs.asp_trajectories)""",
),
(
"Products that gain under flat rate (latest quarter)",
"""SELECT count(DISTINCT hcpcs_code) FROM skin_subs.asp_trajectories
WHERE flat_rate_impact = 'gains'
AND quarter = (SELECT max(quarter) FROM skin_subs.asp_trajectories)""",
),
]:
val = con.execute(q).fetchone()[0]
print(f" {label}: {val}")
# Top losers under flat rate
print("\n Top 10 losers under Jan 2026 flat rate (latest quarter):")
for r in con.execute("""
SELECT hcpcs_code, product_name, manufacturer,
round(payment_limit, 2) as pay, round(flat_rate_delta, 2) as delta
FROM skin_subs.asp_trajectories
WHERE quarter = (SELECT max(quarter) FROM skin_subs.asp_trajectories)
ORDER BY flat_rate_delta DESC LIMIT 10
""").fetchall():
print(
f" {r[0]} {(r[1] or ''):30s} {(r[2] or ''):20s} pay=${r[3]:>8} delta=${r[4]:>+8}"
)
def build_market_segmentation(con: duckdb.DuckDBPyConnection) -> None:
"""#238 — Manufacturer/product market segmentation."""
print("\n=== Market Segmentation (#238) ===")
# Products enriched with market data
con.execute("DROP TABLE IF EXISTS skin_subs.products_enriched")
con.execute(f"""
CREATE TABLE skin_subs.products_enriched AS
WITH latest_asp AS (
SELECT DISTINCT ON (hcpcs_code)
hcpcs_code, asp_per_unit, payment_limit, quarter AS latest_quarter
FROM skin_subs.asp_quarterly
ORDER BY hcpcs_code, quarter DESC
),
asp_stats AS (
SELECT
hcpcs_code,
count(DISTINCT quarter) AS quarters_on_market,
min(quarter) AS first_quarter,
max(quarter) AS last_quarter,
round(avg(asp_per_unit), 2) AS avg_asp,
round(min(asp_per_unit), 2) AS min_asp,
round(max(asp_per_unit), 2) AS max_asp,
round(max(asp_per_unit) - min(asp_per_unit), 2) AS asp_range
FROM skin_subs.asp_quarterly
GROUP BY hcpcs_code
),
claims_stats AS (
SELECT
hcpcs_code,
count(*) AS claim_lines,
round(sum(paid_amount), 2) AS total_paid,
round(avg(paid_amount), 2) AS avg_paid_per_line,
count(DISTINCT rendering_npi) AS unique_providers,
count(DISTINCT person_id) AS unique_benes
FROM skin_subs.claims_synthetic
WHERE claim_type = 'product'
GROUP BY hcpcs_code
)
SELECT
h.hcpcs_code,
h.product_name,
h.manufacturer,
h.category,
h.fda_pathway,
h.effective_date,
h.termination_date,
h.status,
-- ASP pricing
la.asp_per_unit AS latest_asp,
la.payment_limit AS latest_payment_limit,
la.latest_quarter,
s.quarters_on_market,
s.first_quarter,
s.last_quarter,
s.avg_asp,
s.min_asp,
s.max_asp,
s.asp_range,
-- Flat rate impact
CASE WHEN la.payment_limit IS NOT NULL
THEN round(la.payment_limit - {FLAT_RATE_2026}, 2)
ELSE NULL END AS flat_rate_delta,
-- Claims utilization
cs.claim_lines,
cs.total_paid,
cs.avg_paid_per_line,
cs.unique_providers,
cs.unique_benes
FROM skin_subs.hcpcs_universe h
LEFT JOIN latest_asp la ON h.hcpcs_code = la.hcpcs_code
LEFT JOIN asp_stats s ON h.hcpcs_code = s.hcpcs_code
LEFT JOIN claims_stats cs ON h.hcpcs_code = cs.hcpcs_code
ORDER BY cs.total_paid DESC NULLS LAST
""")
count = con.execute("SELECT count(*) FROM skin_subs.products_enriched").fetchone()[
0
]
print(f" Products: {count}")
# Manufacturer summary
con.execute("DROP TABLE IF EXISTS skin_subs.manufacturers")
con.execute("""
CREATE TABLE skin_subs.manufacturers AS
SELECT
manufacturer,
count(*) AS product_count,
count(*) FILTER (WHERE status = 'Active') AS active_products,
count(*) FILTER (WHERE claim_lines IS NOT NULL) AS products_with_claims,
round(sum(total_paid), 2) AS total_revenue,
round(avg(latest_asp), 2) AS avg_asp,
string_agg(DISTINCT category, '; ' ORDER BY category) AS categories,
min(first_quarter) AS earliest_entry,
max(latest_quarter) AS latest_data
FROM skin_subs.products_enriched
WHERE manufacturer IS NOT NULL AND manufacturer != ''
GROUP BY manufacturer
ORDER BY total_revenue DESC NULLS LAST
""")
print("\n Top manufacturers by revenue (synthetic claims):")
for r in con.execute("""
SELECT manufacturer, product_count, active_products,
total_revenue, avg_asp
FROM skin_subs.manufacturers
WHERE total_revenue IS NOT NULL
ORDER BY total_revenue DESC LIMIT 10
""").fetchall():
rev = f"${r[3]:,.0f}" if r[3] else "n/a"
print(
f" {(r[0] or ''):30s} prods={r[1]:3d} active={r[2]:3d} rev={rev:>12} avg_asp=${r[4] or 0:>8}"
)
print("\n Products by category:")
for r in con.execute("""
SELECT category, count(*) as n,
count(*) FILTER (WHERE claim_lines IS NOT NULL) as with_claims,
round(avg(latest_asp), 2) as avg_asp
FROM skin_subs.products_enriched
WHERE category IS NOT NULL
GROUP BY category ORDER BY n DESC
""").fetchall():
print(
f" {(r[0] or ''):40s} n={r[1]:3d} claims={r[2]:3d} avg_asp=${r[3] or 0:>8}"
)
def build_utilization_metrics(con: duckdb.DuckDBPyConnection) -> None:
"""#234 — Utilization metrics from synthetic claims."""
print("\n=== Utilization Metrics (#234) ===")
# Provider-level aggregation
con.execute("DROP TABLE IF EXISTS skin_subs.providers")
con.execute("""
CREATE TABLE skin_subs.providers AS
SELECT
rendering_npi,
provider_specialty,
state,
count(*) AS total_claim_lines,
count(*) FILTER (WHERE claim_type = 'product') AS product_lines,
count(*) FILTER (WHERE claim_type = 'application') AS application_lines,
count(DISTINCT person_id) AS unique_patients,
count(DISTINCT service_date) AS service_days,
round(sum(paid_amount), 2) AS total_paid,
round(avg(paid_amount), 2) AS avg_paid_per_line,
round(sum(paid_amount) / nullif(count(DISTINCT person_id), 0), 2)
AS paid_per_patient,
count(DISTINCT hcpcs_code) AS unique_products,
min(service_date) AS first_service,
max(service_date) AS last_service
FROM skin_subs.claims_synthetic
GROUP BY rendering_npi, provider_specialty, state
ORDER BY total_paid DESC
""")
# Beneficiary-level aggregation
con.execute("DROP TABLE IF EXISTS skin_subs.beneficiaries")
con.execute("""
CREATE TABLE skin_subs.beneficiaries AS
SELECT
person_id,
patient_age,
patient_gender,
state,
count(*) AS total_claim_lines,
count(DISTINCT rendering_npi) AS unique_providers,
count(DISTINCT service_date) AS service_dates,
count(DISTINCT hcpcs_code) AS unique_products,
round(sum(paid_amount), 2) AS total_paid,
min(service_date) AS first_service,
max(service_date) AS last_service,
-- Days between first and last service
julian(max(service_date)) - julian(min(service_date))
AS treatment_span_days
FROM skin_subs.claims_synthetic
GROUP BY person_id, patient_age, patient_gender, state
ORDER BY total_paid DESC
""")
# Utilization summary by dimensions
con.execute("DROP TABLE IF EXISTS skin_subs.utilization_summary")
con.execute("""
CREATE TABLE skin_subs.utilization_summary AS
SELECT
provider_specialty,
place_of_service_description AS setting,
state,
count(*) AS claim_lines,
count(DISTINCT person_id) AS unique_benes,
count(DISTINCT rendering_npi) AS unique_providers,
round(sum(paid_amount), 2) AS total_paid,
round(avg(paid_amount), 2) AS avg_paid,
round(avg(units), 1) AS avg_units,
round(sum(paid_amount) / nullif(count(DISTINCT person_id), 0), 2)
AS paid_per_bene
FROM skin_subs.claims_synthetic
WHERE claim_type = 'product'
GROUP BY provider_specialty, place_of_service_description, state
ORDER BY total_paid DESC
""")
# Print summaries
prov_count = con.execute("SELECT count(*) FROM skin_subs.providers").fetchone()[0]
bene_count = con.execute("SELECT count(*) FROM skin_subs.beneficiaries").fetchone()[
0
]
util_count = con.execute(
"SELECT count(*) FROM skin_subs.utilization_summary"
).fetchone()[0]
print(f" Providers: {prov_count}")
print(f" Beneficiaries: {bene_count}")
print(f" Utilization summary rows: {util_count}")
print("\n Top providers by total paid:")
for r in con.execute("""
SELECT rendering_npi, provider_specialty, state,
unique_patients, total_paid, paid_per_patient
FROM skin_subs.providers ORDER BY total_paid DESC LIMIT 10
""").fetchall():
print(
f" NPI {r[0]} {r[1]:15s} {r[2]} pts={r[3]:3d} paid=${r[4]:>10,.2f} per_pt=${r[5]:>8,.2f}"
)
print("\n Utilization by setting:")
for r in con.execute("""
SELECT setting, sum(claim_lines) as lines, sum(total_paid) as paid,
round(avg(paid_per_bene), 2) as avg_per_bene
FROM skin_subs.utilization_summary
GROUP BY setting ORDER BY paid DESC
""").fetchall():
print(
f" {r[0]:15s} lines={r[1]:5d} paid=${r[2]:>12,.2f} per_bene=${r[3]:>8,.2f}"
)
print("\n Utilization by specialty:")
for r in con.execute("""
SELECT provider_specialty, sum(claim_lines) as lines,
sum(total_paid) as paid, sum(unique_benes) as benes
FROM skin_subs.utilization_summary
GROUP BY provider_specialty ORDER BY paid DESC
""").fetchall():
print(f" {r[0]:20s} lines={r[1]:5d} paid=${r[2]:>12,.2f} benes={r[3]:5d}")
def main() -> None:
print("Building skin substitutes analytical tables ...")
con = duckdb.connect(str(DUCKDB_PATH))
con.execute("CREATE SCHEMA IF NOT EXISTS skin_subs")
build_asp_trajectories(con)
build_market_segmentation(con)
build_utilization_metrics(con)
# Final table inventory
print("\n=== All skin_subs tables ===")
for r in con.execute("""
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'skin_subs' ORDER BY table_name
""").fetchall():
cnt = con.execute(f"SELECT count(*) FROM skin_subs.{r[0]}").fetchone()[0]
print(f" skin_subs.{r[0]:30s}: {cnt:>6} rows")
con.close()
print("\nDone.")
if __name__ == "__main__":
main()