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
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.
256 lines
9.1 KiB
Python
256 lines
9.1 KiB
Python
"""Build geographic and setting analysis tables for skin substitutes.
|
||
|
||
Addresses #240 (geographic analysis) and #241 (setting analysis).
|
||
|
||
Usage:
|
||
uv run python dev/scripts/build_skin_subs_geo_setting.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
import duckdb
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
DUCKDB_PATH = ROOT / "data" / "aco.duckdb"
|
||
|
||
# MAC jurisdiction → states mapping (approximate, some states split)
|
||
MAC_JURISDICTIONS = {
|
||
"Novitas (JH/JL)": ["AR", "CO", "LA", "MS", "NM", "OK", "TX"],
|
||
"First Coast (JN)": ["FL"],
|
||
"Palmetto (JJ/JM)": ["AL", "GA", "NC", "SC", "TN", "VA", "WV"],
|
||
"CGS (J15)": ["KY", "OH"],
|
||
"WPS (J5/J8)": ["IA", "IN", "KS", "MI", "MO", "NE"],
|
||
"NGS (J6/JK)": ["CT", "IL", "MA", "ME", "MN", "NH", "NY", "RI", "VT", "WI"],
|
||
"Noridian (JE/JF)": [
|
||
"AK",
|
||
"AZ",
|
||
"CA",
|
||
"HI",
|
||
"ID",
|
||
"MT",
|
||
"ND",
|
||
"NV",
|
||
"OR",
|
||
"SD",
|
||
"UT",
|
||
"WA",
|
||
"WY",
|
||
],
|
||
}
|
||
|
||
# Invert: state → MAC
|
||
STATE_TO_MAC = {}
|
||
for mac, states in MAC_JURISDICTIONS.items():
|
||
for st in states:
|
||
STATE_TO_MAC[st] = mac
|
||
|
||
|
||
def build_geographic_summary(con: duckdb.DuckDBPyConnection) -> None:
|
||
"""#240 — State and MAC jurisdiction analysis."""
|
||
print("\n=== Geographic Analysis (#240) ===")
|
||
|
||
con.execute("DROP TABLE IF EXISTS skin_subs.geographic_summary")
|
||
con.execute("""
|
||
CREATE TABLE skin_subs.geographic_summary AS
|
||
SELECT
|
||
state,
|
||
count(*) AS claim_lines,
|
||
count(*) FILTER (WHERE claim_type = 'product') AS product_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_per_line,
|
||
round(sum(paid_amount) / nullif(count(DISTINCT person_id), 0), 2)
|
||
AS paid_per_bene,
|
||
round(sum(paid_amount) / nullif(count(DISTINCT rendering_npi), 0), 2)
|
||
AS paid_per_provider,
|
||
count(DISTINCT hcpcs_code) AS product_variety,
|
||
-- Setting mix
|
||
round(count(*) FILTER (WHERE place_of_service = '11') * 100.0
|
||
/ count(*), 1) AS pct_office,
|
||
round(count(*) FILTER (WHERE place_of_service = '22') * 100.0
|
||
/ count(*), 1) AS pct_hopd,
|
||
round(count(*) FILTER (WHERE place_of_service = '24') * 100.0
|
||
/ count(*), 1) AS pct_asc,
|
||
round(count(*) FILTER (WHERE place_of_service IN ('31','32')) * 100.0
|
||
/ count(*), 1) AS pct_snf,
|
||
-- Specialty mix
|
||
round(count(*) FILTER (WHERE provider_specialty = 'podiatry') * 100.0
|
||
/ count(*), 1) AS pct_podiatry,
|
||
round(count(*) FILTER (WHERE provider_specialty = 'dermatology') * 100.0
|
||
/ count(*), 1) AS pct_dermatology
|
||
FROM skin_subs.claims_synthetic
|
||
GROUP BY state
|
||
ORDER BY total_paid DESC
|
||
""")
|
||
|
||
count = con.execute("SELECT count(*) FROM skin_subs.geographic_summary").fetchone()[
|
||
0
|
||
]
|
||
print(f" States: {count}")
|
||
|
||
# Add MAC jurisdiction column
|
||
mac_cases = " ".join(
|
||
f"WHEN state = '{st}' THEN '{mac}'" for st, mac in STATE_TO_MAC.items()
|
||
)
|
||
con.execute(f"""
|
||
ALTER TABLE skin_subs.geographic_summary
|
||
ADD COLUMN mac_jurisdiction VARCHAR;
|
||
UPDATE skin_subs.geographic_summary
|
||
SET mac_jurisdiction = CASE {mac_cases} ELSE 'Other' END;
|
||
""")
|
||
|
||
# MAC-level rollup
|
||
con.execute("DROP TABLE IF EXISTS skin_subs.mac_summary")
|
||
con.execute("""
|
||
CREATE TABLE skin_subs.mac_summary AS
|
||
SELECT
|
||
mac_jurisdiction,
|
||
count(DISTINCT state) AS states,
|
||
sum(claim_lines) AS claim_lines,
|
||
sum(unique_benes) AS unique_benes,
|
||
sum(unique_providers) AS unique_providers,
|
||
round(sum(total_paid), 2) AS total_paid,
|
||
round(avg(paid_per_bene), 2) AS avg_paid_per_bene,
|
||
round(avg(pct_office), 1) AS avg_pct_office,
|
||
round(avg(pct_podiatry), 1) AS avg_pct_podiatry
|
||
FROM skin_subs.geographic_summary
|
||
GROUP BY mac_jurisdiction
|
||
ORDER BY total_paid DESC
|
||
""")
|
||
|
||
print("\n By MAC jurisdiction:")
|
||
for r in con.execute("SELECT * FROM skin_subs.mac_summary").fetchall():
|
||
print(
|
||
f" {r[0]:25s} states={r[1]:2d} lines={r[2]:5d} "
|
||
f"paid=${r[5]:>10,.2f} per_bene=${r[6]:>8,.2f} "
|
||
f"office={r[7]}% podiatry={r[8]}%"
|
||
)
|
||
|
||
print("\n Top 5 states by spend per beneficiary:")
|
||
for r in con.execute("""
|
||
SELECT state, mac_jurisdiction, unique_benes, total_paid,
|
||
paid_per_bene, pct_office, pct_podiatry
|
||
FROM skin_subs.geographic_summary
|
||
ORDER BY paid_per_bene DESC LIMIT 5
|
||
""").fetchall():
|
||
print(
|
||
f" {r[0]} {r[1]:25s} benes={r[2]:4d} "
|
||
f"per_bene=${r[4]:>8,.2f} office={r[5]}% podiatry={r[6]}%"
|
||
)
|
||
|
||
|
||
def build_setting_analysis(con: duckdb.DuckDBPyConnection) -> None:
|
||
"""#241 — Care setting analysis."""
|
||
print("\n=== Setting Analysis (#241) ===")
|
||
|
||
con.execute("DROP TABLE IF EXISTS skin_subs.setting_analysis")
|
||
con.execute("""
|
||
CREATE TABLE skin_subs.setting_analysis AS
|
||
WITH setting_product AS (
|
||
SELECT
|
||
place_of_service_description AS setting,
|
||
provider_specialty,
|
||
hcpcs_code,
|
||
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,
|
||
-- Provider concentration (HHI proxy)
|
||
round(sum(paid_amount) / nullif(
|
||
count(DISTINCT rendering_npi), 0), 2) AS paid_per_provider
|
||
FROM skin_subs.claims_synthetic
|
||
WHERE claim_type = 'product'
|
||
GROUP BY setting, provider_specialty, hcpcs_code
|
||
)
|
||
SELECT
|
||
setting,
|
||
provider_specialty,
|
||
sp.hcpcs_code,
|
||
h.product_name,
|
||
h.manufacturer,
|
||
h.category,
|
||
claim_lines,
|
||
unique_benes,
|
||
unique_providers,
|
||
total_paid,
|
||
avg_paid,
|
||
avg_units,
|
||
paid_per_bene,
|
||
paid_per_provider,
|
||
-- Flag: high per-provider concentration
|
||
CASE WHEN paid_per_provider > 10000 THEN true
|
||
ELSE false END AS high_concentration
|
||
FROM setting_product sp
|
||
LEFT JOIN skin_subs.hcpcs_universe h ON sp.hcpcs_code = h.hcpcs_code
|
||
ORDER BY total_paid DESC
|
||
""")
|
||
|
||
count = con.execute("SELECT count(*) FROM skin_subs.setting_analysis").fetchone()[0]
|
||
print(f" Setting×specialty×product combinations: {count}")
|
||
|
||
# Setting summary
|
||
print("\n By setting:")
|
||
for r in con.execute("""
|
||
SELECT setting, sum(claim_lines) as lines,
|
||
sum(unique_providers) as provs,
|
||
round(sum(total_paid), 2) as paid,
|
||
round(avg(avg_units), 1) as avg_units,
|
||
round(avg(paid_per_bene), 2) as per_bene
|
||
FROM skin_subs.setting_analysis
|
||
GROUP BY setting ORDER BY paid DESC
|
||
""").fetchall():
|
||
print(
|
||
f" {r[0]:15s} lines={r[1]:5d} provs={r[2]:4d} "
|
||
f"paid=${r[3]:>12,.2f} units={r[4]:4.1f} per_bene=${r[5]:>8,.2f}"
|
||
)
|
||
|
||
# Specialty×setting cross-tab
|
||
print("\n Specialty × setting (total paid):")
|
||
for r in con.execute("""
|
||
SELECT provider_specialty, setting,
|
||
round(sum(total_paid), 2) as paid,
|
||
sum(claim_lines) as lines
|
||
FROM skin_subs.setting_analysis
|
||
GROUP BY provider_specialty, setting
|
||
ORDER BY paid DESC LIMIT 10
|
||
""").fetchall():
|
||
print(f" {r[0]:20s} {r[1]:10s} lines={r[3]:5d} paid=${r[2]:>10,.2f}")
|
||
|
||
# High-concentration flag
|
||
high_conc = con.execute("""
|
||
SELECT count(DISTINCT provider_specialty || setting || hcpcs_code)
|
||
FROM skin_subs.setting_analysis WHERE high_concentration
|
||
""").fetchone()[0]
|
||
print(f"\n High-concentration combos (>$10K/provider): {high_conc}")
|
||
|
||
|
||
def main() -> None:
|
||
print("Building geographic and setting analysis ...")
|
||
con = duckdb.connect(str(DUCKDB_PATH))
|
||
|
||
build_geographic_summary(con)
|
||
build_setting_analysis(con)
|
||
|
||
# Updated table inventory
|
||
print("\n=== 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()
|