data: product susceptibility scoring for kickback/fraud risk (refs #260)
Some checks failed
CI / skinny-install (ccw) (push) Successful in 30s
CI / skinny-install (cli) (push) Successful in 33s
CI / skinny-install (aco) (push) Successful in 43s
CI / lint-test (push) Failing after 1m28s
CI / skinny-install (api) (push) Successful in 33s
CI / skinny-install (bcda) (push) Successful in 29s
CI / skinny-install (bib) (push) Successful in 30s
CI / skinny-install (bls) (push) Successful in 26s
CI / skinny-install (cms) (push) Successful in 28s
CI / skinny-install (conf) (push) Successful in 26s
CI / skinny-install (perf) (push) Successful in 26s
CI / skinny-install (pfs) (push) Successful in 30s
CI / skinny-install (rex) (push) Successful in 23s
Infra CI / notebooks (push) Successful in 6s
Infra CI / zotero (push) Successful in 7s
Infra CI / docs (push) Successful in 45s
Infra CI / api (push) Successful in 15s
Infra CI / mc (push) Successful in 9s
Deploy / build-scan-report (push) Successful in 4m29s
Some checks failed
CI / skinny-install (ccw) (push) Successful in 30s
CI / skinny-install (cli) (push) Successful in 33s
CI / skinny-install (aco) (push) Successful in 43s
CI / lint-test (push) Failing after 1m28s
CI / skinny-install (api) (push) Successful in 33s
CI / skinny-install (bcda) (push) Successful in 29s
CI / skinny-install (bib) (push) Successful in 30s
CI / skinny-install (bls) (push) Successful in 26s
CI / skinny-install (cms) (push) Successful in 28s
CI / skinny-install (conf) (push) Successful in 26s
CI / skinny-install (perf) (push) Successful in 26s
CI / skinny-install (pfs) (push) Successful in 30s
CI / skinny-install (rex) (push) Successful in 23s
Infra CI / notebooks (push) Successful in 6s
Infra CI / zotero (push) Successful in 7s
Infra CI / docs (push) Successful in 45s
Infra CI / api (push) Successful in 15s
Infra CI / mc (push) Successful in 9s
Deploy / build-scan-report (push) Successful in 4m29s
Composite susceptibility score per product (0-1) based on: - Price (30%): ASP magnitude (kickback headroom), flat-rate delta - Evidence (25%): RCT count, industry funding, price-per-RCT ratio - Distribution (25%): office/SNF setting mix, high-risk provider usage - Price volatility (10%): anomalous QoQ spikes - FDA pathway (10%): HCT/P > 510(k) > PMA risk weighting Results: 286 products scored - 30 critical (avg ASP $3,211, avg score 0.626) - 63 high (avg ASP $1,204, avg score 0.537) - 186 moderate, 7 low Key finding: all 30 critical products are amniotic membrane with zero RCTs and ASP >$2,000/cm². XWRAP Dual: $5,560/cm², 0 RCTs, highest susceptibility — same product class as DOJ fraud targets. 19 tables in skin_subs schema.
This commit is contained in:
247
dev/scripts/build_product_susceptibility.py
Normal file
247
dev/scripts/build_product_susceptibility.py
Normal file
@@ -0,0 +1,247 @@
|
||||
"""Systematic product susceptibility scoring for kickback/fraud risk.
|
||||
|
||||
Scores each skin substitute product on structural factors that make
|
||||
it more or less susceptible to kickback arrangements and fraudulent
|
||||
billing schemes.
|
||||
|
||||
Addresses #260.
|
||||
|
||||
Usage:
|
||||
uv run python dev/scripts/build_product_susceptibility.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 = 127.28
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("Building product susceptibility scores ...")
|
||||
con = duckdb.connect(str(DUCKDB_PATH))
|
||||
|
||||
con.execute("DROP TABLE IF EXISTS skin_subs.product_susceptibility")
|
||||
con.execute(f"""
|
||||
CREATE TABLE skin_subs.product_susceptibility AS
|
||||
WITH product_base AS (
|
||||
SELECT
|
||||
p.hcpcs_code,
|
||||
p.product_name,
|
||||
p.manufacturer,
|
||||
p.category,
|
||||
p.fda_pathway,
|
||||
p.status,
|
||||
p.latest_asp,
|
||||
p.latest_payment_limit,
|
||||
p.quarters_on_market,
|
||||
p.first_quarter,
|
||||
p.avg_asp,
|
||||
p.max_asp,
|
||||
p.asp_range,
|
||||
p.flat_rate_delta,
|
||||
p.claim_lines,
|
||||
p.total_paid,
|
||||
p.unique_providers,
|
||||
p.unique_benes
|
||||
FROM skin_subs.products_enriched p
|
||||
),
|
||||
-- Evidence quality: count of studies mentioning this product
|
||||
evidence AS (
|
||||
SELECT
|
||||
products AS product_key,
|
||||
count(*) AS study_count,
|
||||
count(*) FILTER (WHERE pub_type = 'rct') AS rct_count,
|
||||
count(*) FILTER (WHERE pub_type = 'meta-analysis') AS meta_count,
|
||||
count(*) FILTER (WHERE is_industry_linked) AS industry_funded,
|
||||
count(*) FILTER (WHERE stance = 'skeptical') AS skeptical_studies
|
||||
FROM skin_subs.study_characteristics
|
||||
WHERE products != ''
|
||||
GROUP BY products
|
||||
),
|
||||
-- Anomalous price trajectory
|
||||
price_anomaly AS (
|
||||
SELECT
|
||||
hcpcs_code,
|
||||
count(*) FILTER (WHERE anomalous_spike) AS spike_count,
|
||||
max(abs(qoq_pct_change)) AS max_qoq_change
|
||||
FROM skin_subs.asp_trajectories
|
||||
GROUP BY hcpcs_code
|
||||
),
|
||||
-- Provider concentration: how many providers use this product
|
||||
provider_profile AS (
|
||||
SELECT
|
||||
hcpcs_code,
|
||||
count(DISTINCT rendering_npi) AS provider_count,
|
||||
-- Is it used mostly by high-risk providers?
|
||||
count(DISTINCT rendering_npi) FILTER (
|
||||
WHERE rendering_npi IN (
|
||||
SELECT rendering_npi FROM skin_subs.anomaly_scores
|
||||
WHERE risk_tier IN ('critical', 'high')
|
||||
)
|
||||
) AS high_risk_provider_count,
|
||||
-- Setting mix
|
||||
count(*) FILTER (WHERE place_of_service = '11') * 100.0
|
||||
/ nullif(count(*), 0) AS pct_office,
|
||||
count(*) FILTER (WHERE place_of_service IN ('31','32')) * 100.0
|
||||
/ nullif(count(*), 0) AS pct_snf
|
||||
FROM skin_subs.claims_synthetic
|
||||
WHERE claim_type = 'product'
|
||||
GROUP BY hcpcs_code
|
||||
)
|
||||
SELECT
|
||||
pb.*,
|
||||
|
||||
-- === PRICE-BASED SUSCEPTIBILITY ===
|
||||
-- Higher ASP = larger kickback headroom (ASP+6% markup)
|
||||
CASE WHEN pb.latest_asp IS NOT NULL
|
||||
THEN round(pb.latest_asp * 0.06, 2)
|
||||
ELSE 0 END AS kickback_headroom_per_cm2,
|
||||
|
||||
-- Flat rate delta: products losing most had most to protect
|
||||
COALESCE(pb.flat_rate_delta, 0) AS reclassification_loss,
|
||||
|
||||
-- Price volatility score (0-1): normalized spike count + range
|
||||
round(COALESCE(pa.spike_count, 0) * 0.3
|
||||
+ LEAST(COALESCE(pa.max_qoq_change, 0) / 100.0, 1.0) * 0.7,
|
||||
3) AS price_volatility_score,
|
||||
|
||||
-- === EVIDENCE-BASED SUSCEPTIBILITY ===
|
||||
-- Low evidence = higher susceptibility
|
||||
COALESCE(ev.study_count, 0) AS study_count,
|
||||
COALESCE(ev.rct_count, 0) AS rct_count,
|
||||
COALESCE(ev.industry_funded, 0) AS industry_funded_studies,
|
||||
-- Price-to-evidence ratio: high price with weak evidence
|
||||
CASE WHEN COALESCE(ev.rct_count, 0) > 0
|
||||
THEN round(COALESCE(pb.latest_asp, 0) / ev.rct_count, 2)
|
||||
ELSE COALESCE(pb.latest_asp, 0)
|
||||
END AS price_per_rct,
|
||||
|
||||
-- === DISTRIBUTION-BASED SUSCEPTIBILITY ===
|
||||
COALESCE(pp.provider_count, 0) AS active_providers,
|
||||
COALESCE(pp.high_risk_provider_count, 0) AS high_risk_providers,
|
||||
round(COALESCE(pp.pct_office, 0), 1) AS pct_office_setting,
|
||||
round(COALESCE(pp.pct_snf, 0), 1) AS pct_snf_setting,
|
||||
|
||||
-- === REGULATORY-BASED SUSCEPTIBILITY ===
|
||||
-- FDA pathway risk: 510(k) < PMA < HCT/P (less evidence required)
|
||||
CASE pb.fda_pathway
|
||||
WHEN 'PMA' THEN 0.3
|
||||
WHEN '510(k)' THEN 0.5
|
||||
WHEN 'HCT/P' THEN 0.8
|
||||
ELSE 0.7 END AS fda_pathway_risk,
|
||||
|
||||
-- === COMPOSITE SUSCEPTIBILITY SCORE ===
|
||||
round(
|
||||
-- Price component (30%): normalized ASP
|
||||
LEAST(COALESCE(pb.latest_asp, 0) / 1000.0, 1.0) * 0.15
|
||||
+ LEAST(COALESCE(pb.flat_rate_delta, 0) / 3000.0, 1.0) * 0.15
|
||||
|
||||
-- Evidence component (25%): inverse evidence quality
|
||||
+ CASE WHEN COALESCE(ev.rct_count, 0) = 0 THEN 0.25
|
||||
WHEN ev.rct_count <= 2 THEN 0.15
|
||||
ELSE 0.05 END
|
||||
|
||||
-- Distribution component (25%): office/SNF + high-risk providers
|
||||
+ LEAST(COALESCE(pp.pct_office, 0) / 100.0, 1.0) * 0.10
|
||||
+ LEAST(COALESCE(pp.pct_snf, 0) / 100.0, 1.0) * 0.05
|
||||
+ CASE WHEN COALESCE(pp.high_risk_provider_count, 0) > 0
|
||||
THEN 0.10 ELSE 0.0 END
|
||||
|
||||
-- Price volatility (10%)
|
||||
+ COALESCE(pa.spike_count, 0) * 0.02
|
||||
|
||||
-- FDA pathway (10%)
|
||||
+ CASE pb.fda_pathway
|
||||
WHEN 'PMA' THEN 0.03
|
||||
WHEN '510(k)' THEN 0.05
|
||||
WHEN 'HCT/P' THEN 0.08
|
||||
ELSE 0.07 END
|
||||
, 3) AS susceptibility_score,
|
||||
|
||||
-- Risk tier
|
||||
'placeholder' AS susceptibility_tier
|
||||
|
||||
FROM product_base pb
|
||||
LEFT JOIN evidence ev ON pb.product_name = ev.product_key
|
||||
LEFT JOIN price_anomaly pa ON pb.hcpcs_code = pa.hcpcs_code
|
||||
LEFT JOIN provider_profile pp ON pb.hcpcs_code = pp.hcpcs_code
|
||||
ORDER BY susceptibility_score DESC NULLS LAST
|
||||
""")
|
||||
|
||||
# Update tiers based on score
|
||||
con.execute("""
|
||||
UPDATE skin_subs.product_susceptibility
|
||||
SET susceptibility_tier = CASE
|
||||
WHEN susceptibility_score >= 0.60 THEN 'critical'
|
||||
WHEN susceptibility_score >= 0.45 THEN 'high'
|
||||
WHEN susceptibility_score >= 0.30 THEN 'moderate'
|
||||
ELSE 'low' END
|
||||
""")
|
||||
|
||||
count = con.execute(
|
||||
"SELECT count(*) FROM skin_subs.product_susceptibility"
|
||||
).fetchone()[0]
|
||||
print(f" Products scored: {count}")
|
||||
|
||||
# Tier distribution
|
||||
print("\n Susceptibility tier distribution:")
|
||||
for r in con.execute("""
|
||||
SELECT susceptibility_tier, count(*) as n,
|
||||
round(avg(susceptibility_score), 3) as avg_score,
|
||||
round(avg(latest_asp), 2) as avg_asp
|
||||
FROM skin_subs.product_susceptibility
|
||||
GROUP BY susceptibility_tier ORDER BY avg_score DESC
|
||||
""").fetchall():
|
||||
print(f" {r[0]:10s} n={r[1]:3d} avg_score={r[2]:6.3f} avg_asp=${r[3] or 0:>8}")
|
||||
|
||||
# Top 20 most susceptible
|
||||
print("\n Top 20 most susceptible products:")
|
||||
for r in con.execute("""
|
||||
SELECT hcpcs_code, product_name, manufacturer, category,
|
||||
susceptibility_score, susceptibility_tier,
|
||||
latest_asp, rct_count, active_providers, pct_office_setting
|
||||
FROM skin_subs.product_susceptibility
|
||||
WHERE susceptibility_score IS NOT NULL
|
||||
ORDER BY susceptibility_score DESC LIMIT 20
|
||||
""").fetchall():
|
||||
print(f" {r[0]} {(r[1] or ''):25s} {(r[2] or ''):20s} "
|
||||
f"score={r[4]:5.3f} ({r[5]}) asp=${r[6] or 0:>8} "
|
||||
f"rcts={r[7]:2d} provs={r[8]:3d} office={r[9] or 0}%")
|
||||
|
||||
# Cross-reference: do susceptible products appear in enforcement?
|
||||
print("\n Susceptibility by category:")
|
||||
for r in con.execute("""
|
||||
SELECT category, count(*) as n,
|
||||
round(avg(susceptibility_score), 3) as avg_score,
|
||||
round(avg(latest_asp), 2) as avg_asp,
|
||||
sum(rct_count) as total_rcts
|
||||
FROM skin_subs.product_susceptibility
|
||||
WHERE category IS NOT NULL
|
||||
GROUP BY category
|
||||
HAVING count(*) >= 2
|
||||
ORDER BY avg_score DESC
|
||||
""").fetchall():
|
||||
print(f" {(r[0] or ''):40s} n={r[1]:3d} score={r[2]:6.3f} "
|
||||
f"asp=${r[3] or 0:>8} rcts={r[4]:3d}")
|
||||
|
||||
# Final table count
|
||||
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()
|
||||
Reference in New Issue
Block a user