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
- 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
585 lines
20 KiB
Python
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())
|