Spaces:
Running
Running
| """ | |
| Download the CMS ICD-10-CM code list and save it as data/raw/icd10_codes.csv. | |
| CMS releases a new file each fiscal year. This script targets FY2025. | |
| Run from the repo root: | |
| python scripts/fetch_icd10.py | |
| """ | |
| import io | |
| import zipfile | |
| from pathlib import Path | |
| import pandas as pd | |
| import requests | |
| URL = ( | |
| "https://www.cms.gov/files/zip/2025-code-descriptions-tabular-order.zip" | |
| ) | |
| # Tab-delimited file inside the ZIP; columns are positional, not headered | |
| INNER_FILE = "icd10cm_codes_2025.txt" | |
| OUT = Path("data/raw/icd10_codes.csv") | |
| def main() -> None: | |
| print(f"Downloading {URL} …") | |
| resp = requests.get(URL, timeout=60) | |
| resp.raise_for_status() | |
| with zipfile.ZipFile(io.BytesIO(resp.content)) as zf: | |
| names = zf.namelist() | |
| print("Files in ZIP:", names) | |
| target = next((n for n in names if n.endswith(INNER_FILE)), None) | |
| if target is None: | |
| raise FileNotFoundError(f"{INNER_FILE!r} not found in ZIP — available: {names}") | |
| raw = zf.read(target).decode("utf-8", errors="replace") | |
| # Format: fixed-width — code (up to 7 chars, no dot) padded with spaces, | |
| # then one or more spaces, then the long description. No header row. | |
| # e.g. "A000 Cholera due to Vibrio cholerae 01, biovar cholerae" | |
| records = [] | |
| for line in raw.splitlines(): | |
| line = line.rstrip() | |
| if not line: | |
| continue | |
| parts = line.split(None, 1) # split on first whitespace | |
| if len(parts) == 2: | |
| code_raw, desc = parts | |
| # Insert decimal point per ICD-10 convention (after 3rd char) | |
| code = code_raw[:3] + ("." + code_raw[3:] if len(code_raw) > 3 else "") | |
| records.append({"code": code, "description": desc.strip()}) | |
| df = pd.DataFrame(records) | |
| OUT.parent.mkdir(parents=True, exist_ok=True) | |
| df.to_csv(OUT, index=False) | |
| print(f"Saved {len(df):,} codes to {OUT}") | |
| if __name__ == "__main__": | |
| main() | |