Files
stack/dev/scripts/generate_cms_express.py
kert 85ce5e719d
Some checks failed
CI / skinny-install (aco) (push) Successful in 45s
CI / skinny-install (api) (push) Successful in 29s
CI / skinny-install (bcda) (push) Successful in 25s
CI / skinny-install (bib) (push) Successful in 23s
CI / skinny-install (bls) (push) Successful in 20s
CI / skinny-install (ccw) (push) Successful in 36s
CI / skinny-install (cli) (push) Successful in 27s
CI / skinny-install (cms) (push) Successful in 24s
CI / skinny-install (conf) (push) Successful in 27s
CI / skinny-install (pfs) (push) Successful in 25s
CI / skinny-install (rex) (push) Successful in 25s
CI / lint-test (push) Successful in 6m2s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Failing after 6s
Infra CI / docs (push) Successful in 33s
Infra CI / api (push) Successful in 6s
Infra CI / mc (push) Successful in 7s
Deploy / build-scan-report (push) Has been cancelled
chore: clean sweep — lint, format, stale refs, generated artifacts
- Fix all 72 ruff lint errors (unused imports, unused variables, E402)
- Format all 14 unformatted dev/scripts files
- Move generated artifacts to assets/ (dag.html, pfs.html)
- Remove duplicate root coverage.svg (already in assets/icons/)
- Update .dockerignore for infra/ tree layout
- Update .gitignore: add .env.bak, mirrors/, htmlcov/
- Fix stale path refs in coverage_badge.py, woodpecker backend,
  test_network_isolation.sh, docs custom.css
- Add .gitkeep to empty dirs (infra/polaris, cloud/*/terraform)
- Delete 12 stale local branches, 10 stale remote branches
2026-03-24 17:33:55 -04:00

328 lines
10 KiB
Python

"""Generate CMS express modules from table model introspection.
Reads every ``src/cms/table/*.py`` model and emits one
``@nw.narwhalify`` function per table grouped into domain modules
under ``src/cms/express/``.
Each generated function calls ``auto_cast(df)`` which inspects
column names at runtime and casts string columns matching CMS
numeric naming conventions to Float64.
Usage::
uv run python dev/scripts/generate_cms_express.py
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
# ── Constants ────────────────────────────────────────────────────
SRC = Path("src")
TABLE_DIR = SRC / "cms" / "table"
EXPRESS_DIR = SRC / "cms" / "express"
# ── Domain grouping ──────────────────────────────────────────────
DOMAIN_RULES: list[tuple[str, list[str]]] = [
(
"aco",
[
"accountable_care_organization",
"aco_reach_",
"number_accountable_care",
"county_level_aggregate_expenditure",
"performance_year_financial",
"reach_acos",
"advance_investment_payment",
"pioneer_aco",
"value_modifier",
],
),
(
"enrollment",
[
"medicare_monthly_enrollment",
"program_statistics_medicare_total_enrollment",
"program_statistics_original_medicare_enrollment",
"program_statistics_medicare_advantage_other_health_plan_enrollment",
"program_statistics_medicare_part_d_enrollment",
"program_statistics_medicare_newly_enrolled",
"program_statistics_medicare_medicaid_dual_enrollment",
"program_statistics_medicare_deaths",
"program_statistics_medicare_premiums",
"medicare_fee_service_public_provider_enrollment",
],
),
(
"provider",
[
"medicare_physician_other_practitioners",
"medicare_part_d_prescribers",
"physician_supplier_procedure",
"order_referring",
"opt_out_affidavits",
"fiscal_intermediary_shared_system",
"pending_initial_logging_tracking",
"revalidation_",
"provider_services_file_",
"public_reporting_missing",
"managing_clinician_",
"quality_payment_program",
"restructured_betos",
"medicare_provider_supplier_taxonomy",
"medicare_clinical_laboratory",
"medicare_fee_service_comprehensive",
],
),
(
"facility",
[
"hospital_",
"skilled_nursing_facility_",
"home_health_agency_",
"hospice_",
"federally_qualified_health_center_",
"rural_health_clinic_",
"long_term_care_facility",
"facility_level_minimum_data_set",
"minimum_data_set_frequency",
"nursing_home_chain",
"payroll_based_journal",
"deficit_reduction_act",
"end_stage_renal_disease",
"medicare_dialysis_facilities",
"home_infusion_therapy",
"income_asset_ownership",
"opioid_treatment_program",
],
),
(
"drug_spending",
[
"medicare_part_b_spending",
"medicare_part_b_discarded",
"medicare_part_d_spending",
"medicare_quarterly_part_b",
"medicare_quarterly_part_d",
"medicaid_spending_by_drug",
"medicaid_opioid_",
"medicare_part_d_opioid",
"monthly_prescription_drug",
"quarterly_prescription_drug",
],
),
(
"utilization",
[
"medicare_inpatient_hospitals",
"medicare_outpatient_hospitals",
"medicare_durable_medical_equipment",
"medicare_post_acute_care",
"medicare_geographic_variation",
"medicare_advantage_geographic_variation",
"medicare_telehealth",
"medicare_covid_19",
"medicare_current_beneficiary_survey",
"specialty_",
"beneficiary_",
"post_acute_care",
"ambulatory_surgery_center",
],
),
(
"market",
[
"market_saturation",
"medicare_advantage_",
"medicare_plan_finder",
"prescription_drug_plan_",
"medicaid_managed_care",
"medicare_demonstrations",
"medicare_diabetes",
],
),
(
"innovation",
[
"innovation_center_",
"cpc_initiative_",
"comprehensive_care_joint_replacement",
"kidney_care_choices",
"ambulatory_specialty_model",
"strong_start_",
"agency_healthcare_research",
],
),
(
"program_statistics",
[
"program_statistics_medicare_inpatient",
"program_statistics_medicare_outpatient",
"program_statistics_medicare_home_health",
"program_statistics_medicare_hospice",
"program_statistics_medicare_skilled_nursing",
"program_statistics_medicare_physician",
"program_statistics_medicare_part_d",
"program_statistics_medicare_part_part_b",
"program_statistics_medicare_providers",
"program_statistics_medicare_advantage_inpatient",
"program_statistics_medicare_advantage_outpatient",
"program_statistics_medicare_advantage_physician",
"program_statistics_medicare_advantage_skilled",
],
),
]
def classify_domain(table_stem: str) -> str:
"""Assign a table to a domain module."""
for domain, prefixes in DOMAIN_RULES:
for prefix in prefixes:
if table_stem.startswith(prefix) or prefix in table_stem:
return domain
return "other"
# ── Table introspection ──────────────────────────────────────────
def parse_table_file(path: Path) -> dict | None:
"""Extract class name, tablename, and field count from a table module."""
content = path.read_text()
class_match = re.search(r"class (\w+)\(SQLTable\):", content)
if not class_match:
return None
class_name = class_match.group(1)
tn_match = re.search(r'__tablename__\s*=\s*"([^"]+)"', content)
tablename = tn_match.group(1) if tn_match else path.stem
field_count = len(re.findall(r" \w+: (?:str|date) \| None", content))
return {
"class_name": class_name,
"tablename": tablename,
"stem": path.stem,
"field_count": field_count,
}
# ── Code generation ──────────────────────────────────────────────
def generate_function(table: dict) -> str:
"""Generate a single @nw.narwhalify function for a table."""
tablename = table["tablename"]
param = f"cms__{tablename}"
field_count = table["field_count"]
lines = [
"@nw.narwhalify",
f"def {tablename}({param}: FrameT) -> FrameT:",
f' """Clean and type-cast cms.{tablename}.',
"",
f" {field_count} fields. Numeric columns auto-cast to Float64.",
' """',
f" return auto_cast({param})",
]
return "\n".join(lines)
def generate_module(domain: str, tables: list[dict]) -> str:
"""Generate a full express module for a domain group."""
lines = [
f'"""CMS express — {domain} domain.',
"",
f"Auto-generated narwhals functions for {len(tables)} CMS tables.",
"Each function takes the raw table and returns a typed frame",
"with numeric columns cast to Float64.",
'"""',
"",
"from __future__ import annotations",
"",
"import narwhals as nw",
"from narwhals.typing import FrameT",
"",
"from cms.express._helpers import auto_cast",
]
for tbl in sorted(tables, key=lambda t: t["tablename"]):
lines.append("")
lines.append("")
lines.append(generate_function(tbl))
lines.append("")
return "\n".join(lines)
def generate_init(domains: dict[str, list[dict]]) -> str:
"""Generate src/cms/express/__init__.py."""
lines = [
'"""CMS express layer — narwhals transformation functions.',
"",
"Auto-generated pure functions that clean, type-cast, and",
"standardize all 150 CMS public datasets.",
"",
"Each domain module provides one function per table:",
"",
]
for domain in sorted(domains):
count = len(domains[domain])
lines.append(f"- **{domain}**: {count} tables")
lines.append('"""')
lines.append("")
for domain in sorted(domains):
lines.append(f"from . import {domain} as {domain}")
lines.append("")
return "\n".join(lines)
# ── Main ─────────────────────────────────────────────────────────
def main() -> None:
tables = []
for f in sorted(TABLE_DIR.glob("*.py")):
if f.name == "__init__.py":
continue
info = parse_table_file(f)
if info and info["field_count"] >= 2:
tables.append(info)
print(f"Parsed {len(tables)} table models")
domains: dict[str, list[dict]] = {}
for tbl in tables:
domain = classify_domain(tbl["stem"])
domains.setdefault(domain, []).append(tbl)
for domain, tbls in sorted(domains.items()):
print(f" {domain}: {len(tbls)} tables")
EXPRESS_DIR.mkdir(parents=True, exist_ok=True)
for domain, tbls in domains.items():
module_path = EXPRESS_DIR / f"{domain}.py"
content = generate_module(domain, tbls)
module_path.write_text(content)
print(f" wrote {module_path} ({len(tbls)} functions)")
init_path = EXPRESS_DIR / "__init__.py"
content = generate_init(domains)
init_path.write_text(content)
print(f" wrote {init_path}")
total_fns = sum(len(v) for v in domains.values())
print(f"\nDone: {total_fns} functions in {len(domains)} modules")
if __name__ == "__main__":
sys.path.insert(0, str(SRC))
main()