Files
stack/dev/scripts/generate_alr_table_models.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

585 lines
20 KiB
Python

"""Generate ALR SQLTable models from the ALR/ASR User's Guide PDF.
Parses the Assignment List Report (ALR) table specifications from
the ALRASRGuide.pdf to extract all 9 ALR table layouts and generates
a single Python module at ``src/aco/table/alr.py`` with one SQLTable
subclass per table.
Usage::
uv run python dev/scripts/generate_alr_table_models.py
Source: dev/ALRASRGuide.pdf
"""
from __future__ import annotations
import re
from pathlib import Path
import pdfplumber
def extract_alr_table_specs(pdf_path: str) -> dict:
"""Extract ALR table field specifications from PDF pages 8-26.
Returns dict mapping table_id -> {title, fields: list[dict]}
"""
pdfplumber.open(pdf_path)
# Manual field specifications based on PDF extraction
# Table 1-1 is on pages 8-15, others follow
tables = {}
# Table 1-1: Assigned Beneficiaries (PRIMARY TABLE)
tables["1-1"] = {
"title": "Assigned Beneficiaries",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "HICN",
"type": "str | None",
"desc": "Health Insurance Claim Number (blank after 1/1/2020)",
},
{"name": "First_Name", "type": "str", "desc": "Beneficiary first name"},
{"name": "Last_Name", "type": "str", "desc": "Beneficiary last name"},
{"name": "Sex", "type": "int", "desc": "0=unknown, 1=male, 2=female"},
{"name": "Birth_Date", "type": "date", "desc": "Date of birth"},
{
"name": "Death_Date",
"type": "date | None",
"desc": "Date of death if applicable",
},
{
"name": "County_Name",
"type": "str | None",
"desc": "County of residence (blank if excluded)",
},
{
"name": "State_Name",
"type": "str | None",
"desc": "State of residence (blank if excluded)",
},
{
"name": "State_County_CD",
"type": "str",
"desc": "SSA state (2) + county (3) code",
},
{
"name": "Voluntary_Alignment_Flag",
"type": "int",
"desc": "1=voluntarily aligned, 0=not",
},
{
"name": "Designated_Primary_Clinician_TIN",
"type": "str | None",
"desc": "TIN of primary clinician (if voluntary)",
},
{
"name": "Designated_Primary_Clinician_NPI",
"type": "str | None",
"desc": "NPI of primary clinician (if voluntary)",
},
{
"name": "Claims_Based_Assignment_Flag",
"type": "int",
"desc": "1=claims-based assigned, 0=not",
},
{
"name": "Claims_Based_Assignment_Step",
"type": "int",
"desc": "0=voluntary only, 1=Step 1, 2=Step 2",
},
{
"name": "Previously_Assigned_Beneficiary_Flag",
"type": "int",
"desc": "1=on previous list (quarterly only)",
},
{
"name": "Medicare_Part_D_Enrollment_Flag",
"type": "int",
"desc": "Number of months enrolled in Part D (0-12)",
},
{
"name": "Beneficiary_Excluded_One_Or_More_Reasons",
"type": "int",
"desc": "1=excluded for any reason",
},
{
"name": "Beneficiary_Death_Before_PY",
"type": "int",
"desc": "1=died before performance year",
},
{
"name": "Beneficiary_Excluded_Other_Reasons",
"type": "int",
"desc": "1=excluded for other reason",
},
{
"name": "Beneficiary_Part_A_or_B_Only",
"type": "int",
"desc": "1=at least 1 month Part A or B only",
},
{
"name": "Beneficiary_Medicare_Health_Plan",
"type": "int",
"desc": "1=at least 1 month in MA/PACE",
},
{
"name": "Beneficiary_Non_US_Resident",
"type": "int",
"desc": "1=non-US residence",
},
{
"name": "Beneficiary_Other_Shared_Savings",
"type": "int",
"desc": "1=in other shared savings initiative",
},
],
}
# Add 12 monthly enrollment flags
for i in range(1, 13):
tables["1-1"]["fields"].append(
{
"name": f"EnrollFlag{i}",
"type": "int",
"desc": f"Month {i} eligibility: 0=not eligible, 1=ESRD, 2=disabled, 3=aged/dual, 4=aged/non-dual",
}
)
# Add HCC metadata fields
tables["1-1"]["fields"].append(
{
"name": "CMS_HCC_Version",
"type": "str",
"desc": "HCC model version used (V22, V24, V28)",
}
)
# Add 90 HCC position fields
for i in range(1, 91):
tables["1-1"]["fields"].append(
{
"name": f"HCC_Position_{i:02d}",
"type": "int",
"desc": "HCC indicator (0=no, 1=yes) - maps to specific HCC via data dictionary",
}
)
# Add person years fields
tables["1-1"]["fields"].extend(
[
{
"name": "Dual_Person_Years",
"type": "float",
"desc": "Dual-eligible person years",
},
{
"name": "Total_Person_Years",
"type": "float",
"desc": "Total person years for beneficiary",
},
]
)
# Table 1-2: Services at TIN Level
tables["1-2"] = {
"title": "Assigned Beneficiaries and Number of Primary Care Services at ACO Participant TIN Level",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "HICN",
"type": "str | None",
"desc": "Health Insurance Claim Number",
},
{"name": "First_Name", "type": "str", "desc": "Beneficiary first name"},
{"name": "Last_Name", "type": "str", "desc": "Beneficiary last name"},
{"name": "Sex", "type": "int", "desc": "0=unknown, 1=male, 2=female"},
{"name": "Birth_Date", "type": "date", "desc": "Date of birth"},
{"name": "Death_Date", "type": "date | None", "desc": "Date of death"},
{
"name": "ACO_Participant_TIN",
"type": "str",
"desc": "ACO participant TIN",
},
{
"name": "Count_Primary_Care_Services",
"type": "int",
"desc": "Count of primary care services at TIN",
},
],
}
# Table 1-3: Services at CCN Level
tables["1-3"] = {
"title": "Assigned Beneficiaries and Number of Primary Care Services at ACO CCN Level",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "HICN",
"type": "str | None",
"desc": "Health Insurance Claim Number",
},
{"name": "First_Name", "type": "str", "desc": "Beneficiary first name"},
{"name": "Last_Name", "type": "str", "desc": "Beneficiary last name"},
{"name": "Sex", "type": "int", "desc": "0=unknown, 1=male, 2=female"},
{"name": "Birth_Date", "type": "date", "desc": "Date of birth"},
{"name": "Death_Date", "type": "date | None", "desc": "Date of death"},
{"name": "ACO_CCN", "type": "str", "desc": "CMS Certification Number"},
{
"name": "Count_Primary_Care_Services",
"type": "int",
"desc": "Count of primary care services at CCN",
},
],
}
# Table 1-4: Top TIN-NPI Combinations
tables["1-4"] = {
"title": "Top ACO Participant TIN-Individual NPI Combinations",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "HICN",
"type": "str | None",
"desc": "Health Insurance Claim Number",
},
{"name": "First_Name", "type": "str", "desc": "Beneficiary first name"},
{"name": "Last_Name", "type": "str", "desc": "Beneficiary last name"},
{"name": "Sex", "type": "int", "desc": "0=unknown, 1=male, 2=female"},
{"name": "Birth_Date", "type": "date", "desc": "Date of birth"},
{"name": "Death_Date", "type": "date | None", "desc": "Date of death"},
{
"name": "ACO_Participant_TIN",
"type": "str",
"desc": "ACO participant TIN",
},
{"name": "Individual_NPI", "type": "str", "desc": "Provider NPI"},
{
"name": "Count_Primary_Care_Services",
"type": "int",
"desc": "Count of services at TIN-NPI combination",
},
],
}
# Table 1-5: Beneficiary Turnover
tables["1-5"] = {
"title": "Beneficiary Turnover Analysis",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "HICN",
"type": "str | None",
"desc": "Health Insurance Claim Number",
},
{"name": "First_Name", "type": "str", "desc": "Beneficiary first name"},
{"name": "Last_Name", "type": "str", "desc": "Beneficiary last name"},
{"name": "Sex", "type": "int", "desc": "0=unknown, 1=male, 2=female"},
{"name": "Birth_Date", "type": "date", "desc": "Date of birth"},
{"name": "Death_Date", "type": "date | None", "desc": "Date of death"},
{
"name": "No_Plurality_Primary_Care",
"type": "int",
"desc": "1=did not receive plurality of services",
},
{
"name": "Part_A_or_B_Only",
"type": "int",
"desc": "1=at least 1 month Part A or B only",
},
{
"name": "Medicare_Health_Plan",
"type": "int",
"desc": "1=at least 1 month in MA plan",
},
{"name": "Non_US_Resident", "type": "int", "desc": "1=non-US residence"},
{
"name": "Other_Shared_Savings",
"type": "int",
"desc": "1=in other shared savings initiative",
},
{
"name": "No_Physician_Visit_Other",
"type": "int",
"desc": "1=no physician visit or other reason",
},
],
}
# Table 1-6: Assignable Beneficiaries
tables["1-6"] = {
"title": "Beneficiaries Assignable to ACO or Who Selected Primary Clinician",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "HICN",
"type": "str | None",
"desc": "Health Insurance Claim Number",
},
{"name": "First_Name", "type": "str", "desc": "Beneficiary first name"},
{"name": "Last_Name", "type": "str", "desc": "Beneficiary last name"},
{"name": "Sex", "type": "int", "desc": "0=unknown, 1=male, 2=female"},
{"name": "Birth_Date", "type": "date", "desc": "Date of birth"},
{"name": "Death_Date", "type": "date | None", "desc": "Date of death"},
{
"name": "Voluntary_Alignment_Selection_Only",
"type": "int",
"desc": "1=voluntary alignment selection only",
},
],
}
# Table 1-7: COVID-19 Diagnoses
tables["1-7"] = {
"title": "Assigned Beneficiaries with COVID-19 Diagnoses and Excluded Months",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "B97_29_Diagnosis",
"type": "int",
"desc": "1=B97.29 diagnosis (1/27/20-3/31/20)",
},
{
"name": "U07_1_Diagnosis",
"type": "int",
"desc": "1=U07.1 diagnosis (4/1/20-5/11/23)",
},
{
"name": "COVID19_Episode",
"type": "int",
"desc": "1=meets episode criteria",
},
{
"name": "Admission_DT",
"type": "date | None",
"desc": "Admission date for episode",
},
{
"name": "Discharge_DT",
"type": "date | None",
"desc": "Discharge date for episode",
},
],
}
# Add 12 monthly COVID episode flags
for i in range(1, 13):
tables["1-7"]["fields"].append(
{
"name": f"COVID19_MONTH{i:02d}",
"type": "int | None",
"desc": f"Month {i} episode flag: 0=no, 1=episode, None=missing",
}
)
# Table 1-8: CCN List
tables["1-8"] = {
"title": "List of CCNs Used in Beneficiary Assignment",
"fields": [
{
"name": "ACO_Participant_TIN",
"type": "str",
"desc": "ACO participant TIN",
},
{"name": "ACO_CCN", "type": "str", "desc": "CMS Certification Number"},
{
"name": "CCN_Type",
"type": "str",
"desc": "Expanded facility description",
},
{
"name": "Deactivated_Flag",
"type": "int",
"desc": "0=active, 1=deactivated",
},
{
"name": "Newly_Enrolled_Flag",
"type": "int",
"desc": "0=initial, 1=Q1, 2=Q2, 3=Q3, 4=Q4 new",
},
{
"name": "Reactivated_Flag",
"type": "int",
"desc": "0=not reactivated, 1-4=Q1-Q4 reactivated",
},
],
}
# Table 1-9: Underserved Populations
tables["1-9"] = {
"title": "List of Assigned Beneficiaries with Indicators of Underserved Populations",
"fields": [
{"name": "MBI", "type": "str", "desc": "Medicare Beneficiary Identifier"},
{
"name": "ADI_National_Percentile_Rank",
"type": "int | None",
"desc": "Area Deprivation Index rank 1-100 (higher=more deprived)",
},
{
"name": "LIS_Enrollment_Flag",
"type": "int",
"desc": "1=any month enrolled in Part D LIS",
},
{
"name": "Dual_Eligibility_Flag",
"type": "int",
"desc": "1=any month dually eligible",
},
{
"name": "Person_Years_LIS_or_Dual",
"type": "float",
"desc": "Months LIS/dual / 12",
},
{
"name": "Total_Person_Years",
"type": "float",
"desc": "Months eligible / 12",
},
],
}
return tables
def normalize_field_name(name: str) -> str:
"""Convert field names to Python snake_case.
ALR fields are already in snake_case or PascalCase_With_Underscores.
Just lowercase them.
"""
return name.lower()
def class_name(table_id: str) -> str:
"""Convert table ID to class name."""
# "1-1" -> "AlrAssignedBeneficiaries"
# "1-2" -> "AlrServicesAtTin"
names = {
"1-1": "AlrAssignedBeneficiaries",
"1-2": "AlrServicesAtTin",
"1-3": "AlrServicesAtCcn",
"1-4": "AlrTopTinNpiCombinations",
"1-5": "AlrBeneficiaryTurnover",
"1-6": "AlrAssignableBeneficiaries",
"1-7": "AlrCovid19Diagnoses",
"1-8": "AlrCcnList",
"1-9": "AlrUnderservedPopulations",
}
return names[table_id]
def table_name(table_id: str) -> str:
"""Convert table ID to database table name."""
# "1-1" -> "alr_assigned_beneficiaries"
cls = class_name(table_id)
# Convert PascalCase to snake_case
return re.sub(r"(?<!^)(?=[A-Z])", "_", cls).lower()
def generate_module(tables: dict) -> str:
"""Generate the alr.py module source code."""
total_fields = sum(len(t["fields"]) for t in tables.values())
lines = [
'"""ALR — Assignment List Report file layouts.',
"",
"Auto-generated from the ALR/ASR User's Guide:",
"dev/ALRASRGuide.pdf",
f"Version #16 (April 2024) — {total_fields} fields across {len(tables)} tables.",
"",
"Assignment List Reports are CSV files delivered to ACOs participating",
"in the Medicare Shared Savings Program. They contain beneficiary-",
"identifiable data on assigned beneficiaries.",
"",
"Tables::",
"",
]
for tid in sorted(tables.keys()):
info = tables[tid]
lines.append(f" Table {tid}: {info['title']} ({len(info['fields'])} fields)")
lines.extend(
[
'"""',
"",
"from __future__ import annotations",
"",
"from datetime import date",
"",
"from aco.table.base import SQLTable",
"",
]
)
# Generate classes in order
for tid in sorted(tables.keys()):
info = tables[tid]
cls = class_name(tid)
tbl = table_name(tid)
title = info["title"]
lines.append("")
lines.append(f"class {cls}(SQLTable):")
lines.append(f' """ALR Table {tid}: {title}')
lines.append("")
lines.append(f" {len(info['fields'])} fields, CSV format.")
lines.append("")
lines.append(" Source: ALRASRGuide.pdf")
lines.append(' """')
lines.append("")
lines.append(' __schema__ = "alr"')
lines.append(f' __tablename__ = "{tbl}"')
for field in info["fields"]:
py_name = normalize_field_name(field["name"])
py_type = field["type"]
desc = field["desc"]
# Escape docstring issues
desc = desc.replace('"""', '\'""')
if desc.startswith('"'):
desc = " " + desc
lines.append("")
lines.append(f" {py_name}: {py_type}")
lines.append(f' """{desc}"""')
lines.append("")
# Remove trailing blank lines
while lines and lines[-1] == "":
lines.pop()
return "\n".join(lines) + "\n"
def main():
"""Generate src/aco/table/alr.py from ALRASRGuide.pdf."""
_root = Path(__file__).resolve().parents[2]
pdf_path = _root / "dev" / "seeds" / "ALRASRGuide.pdf"
output_path = _root / "src" / "aco" / "table" / "alr.py"
if not pdf_path.exists():
print(f"Error: {pdf_path} not found")
return 1
tables = extract_alr_table_specs(str(pdf_path))
code = generate_module(tables)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(code)
print(f"Generated {output_path}")
print(
f" {len(tables)} tables, {sum(len(t['fields']) for t in tables.values())} fields"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())