Split dev/ flat directory into dev/scripts/ (29 .py files) and dev/seeds/ (PDFs, Excel, ZIPs, NDJSON, grafana, PY2023). Update all Path(__file__) references to use parents[2] for project root and dev/seeds/ for seed data. Fix path references in src/ too. Add PY2024 CMS quality measure value sets (HWR, UAMCC, ACR) to the REGISTRY and load into DuckDB — enables 2024->2025->2026 diffs. Add HWR tables to notebook DIFF_SPECS now that two years exist.
717 lines
29 KiB
Python
717 lines
29 KiB
Python
"""Annotate CCLF express functions and pipe steps with IP documentation.
|
|
|
|
Reads the CCLF Information Packet PDF and injects relevant passages
|
|
from the IP directly into the docstrings of each function in
|
|
``src/aco/express/cclf.py`` and each step comment block in
|
|
``src/aco/pipe/cclf.py``.
|
|
|
|
The goal is traceability: every transformation has a citation back to
|
|
the authoritative CMS document explaining *why* the logic exists.
|
|
|
|
Usage::
|
|
|
|
python dev/scripts/generate_cclf_docs.py
|
|
|
|
Source: https://www.cms.gov/files/document/cclf-information-packet.pdf
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
import pdfplumber
|
|
|
|
# ── PDF extraction ───────────────────────────────────────────────
|
|
|
|
_ROOT = Path(__file__).resolve().parents[2]
|
|
_SEEDS = _ROOT / "dev" / "seeds"
|
|
|
|
PDF_PATH = _SEEDS / "cclf-information-packet.pdf"
|
|
EXPRESS_PATH = _ROOT / "src" / "aco" / "express" / "cclf.py"
|
|
PIPE_PATH = _ROOT / "src" / "aco" / "pipe" / "cclf.py"
|
|
|
|
|
|
def _extract_pages(pdf_path: Path) -> dict[int, str]:
|
|
"""Extract text from every page of the PDF, keyed by 1-based page."""
|
|
pdf = pdfplumber.open(str(pdf_path))
|
|
pages = {}
|
|
for i, page in enumerate(pdf.pages):
|
|
text = page.extract_text() or ""
|
|
pages[i + 1] = text
|
|
return pages
|
|
|
|
|
|
def _extract_section(pages: dict[int, str], start_page: int, end_page: int) -> str:
|
|
"""Join page text for a range of pages."""
|
|
parts = []
|
|
for p in range(start_page, end_page + 1):
|
|
if p in pages:
|
|
parts.append(pages[p])
|
|
return "\n".join(parts)
|
|
|
|
|
|
def _extract_field_descriptions(
|
|
pages: dict[int, str],
|
|
) -> dict[str, dict[str, str]]:
|
|
"""Extract field-level descriptions from Appendix B tables.
|
|
|
|
Returns {cclf_id: {field_label: description}}.
|
|
"""
|
|
pdf = pdfplumber.open(str(PDF_PATH))
|
|
result: dict[str, dict[str, str]] = {}
|
|
current_file: str | None = None
|
|
|
|
for i in range(len(pdf.pages)):
|
|
text = pdf.pages[i].extract_text() or ""
|
|
for m in re.finditer(r"Table \d+: (.+?)\(CCLF(\w+)\)", text):
|
|
current_file = f"CCLF{m.group(2)}"
|
|
if current_file not in result:
|
|
result[current_file] = {}
|
|
|
|
tables = pdf.pages[i].extract_tables()
|
|
for table in tables:
|
|
if not table or not table[0]:
|
|
continue
|
|
header = table[0]
|
|
if not any("Element" in str(h) for h in header):
|
|
continue
|
|
for row in table[1:]:
|
|
if not row or not row[0]:
|
|
continue
|
|
elem = str(row[0]).strip()
|
|
if not elem.isdigit():
|
|
continue
|
|
label = str(row[1] or "").replace("\n", " ").strip()
|
|
label = label.replace(" ", "") # Fix PDF artifacts
|
|
desc = (
|
|
str(row[7] or "").replace("\n", " ").strip() if len(row) > 7 else ""
|
|
)
|
|
if current_file and label:
|
|
result.setdefault(current_file, {})[label.lower()] = desc
|
|
|
|
return result
|
|
|
|
|
|
# ── Documentation passages ───────────────────────────────────────
|
|
# Each entry maps a function name to the IP passage that justifies it.
|
|
# These are extracted from the PDF sections and lightly edited for
|
|
# clarity in a docstring context.
|
|
|
|
|
|
def _build_ip_passages(pages: dict[int, str]) -> dict[str, str]:
|
|
"""Build function→IP passage mapping from PDF content.
|
|
|
|
Passages are extracted once and associated with each function
|
|
based on which CCLF IP section governs that function's logic.
|
|
"""
|
|
passages: dict[str, str] = {}
|
|
|
|
# ── stg_beneficiary_xref (CCLF9) ────────────────────
|
|
passages["stg_beneficiary_xref"] = _clean("""
|
|
CCLF IP Section 3.1 "Matching MBIs" (p.14):
|
|
|
|
"A beneficiary's MBI is unique to that beneficiary but may change
|
|
over time. CCLFs 1 through 9, A, and B, as they are provided each
|
|
month, contain the beneficiary's most current MBI. When records from
|
|
multiple months are being combined, the Beneficiary XREF File (CCLF9)
|
|
can be used each month to cross reference records associated with the
|
|
current MBI to records associated with previous MBIs."
|
|
|
|
CCLF IP Section 5.1.1 "Creation of the Most Recent MBI field" (p.19):
|
|
|
|
"The beneficiary XREF file (CCLF9) contains a complete history of
|
|
MBIs ever used to identify a beneficiary, for beneficiaries who have
|
|
had a change in their MBI over time. The XREF file (CCLF9) provides
|
|
a crosswalk between all older MBIs and the most recent MBI."
|
|
|
|
CCLF9 fields used: CRNT_NUM (current MBI), PRVS_NUM (previous MBI),
|
|
PRVS_ID_EFCTV_DT (effective date of previous ID — used to pick latest).
|
|
""")
|
|
|
|
# ── stg_institutional_header (CCLF1 staging) ────────
|
|
passages["stg_institutional_header"] = _clean("""
|
|
CCLF IP Section 2.2.1 "Part A Claims Header File" (p.8-9):
|
|
|
|
"The Part A Claims Header File (CCLF1) contains header-level claim
|
|
information for institutional services covered by Part A and some
|
|
Part B. A single claim can be associated with multiple services.
|
|
The file includes provider CCN/OSCAR, beneficiary MBI, claim type,
|
|
service dates, bill type codes, DRG, payment amounts, and provider
|
|
NPIs."
|
|
|
|
MBI resolution: BENE_MBI_ID is joined to CCLF9._stg_beneficiary_xref
|
|
on PRVS_NUM to resolve to CURRENT_BENE_MBI_ID via COALESCE.
|
|
|
|
CCLF IP Section 5.3.1 "Calculating Total Part A and B Expenditures" (p.23):
|
|
|
|
"Identify the canceled claims in the Part A Header file. These claims
|
|
are identified by CLM_ADJSMT_TYPE_CD=1. Change the sign of the
|
|
variable CLM_PMT_AMT for each of these cancellation claims (i.e.,
|
|
multiply the CLM_PMT_AMT by -1)."
|
|
|
|
Fields negated on cancellation: CLM_PMT_AMT, CLM_MDCR_INSTNL_TOT_CHRG_AMT.
|
|
""")
|
|
|
|
# ── int_institutional_header_adr ─────────────────────
|
|
passages["int_institutional_header_adr"] = _clean("""
|
|
CCLF IP Section 4.4 "Debit/Credit Method" (p.19):
|
|
|
|
"The Debit/Credit method gives a full account/history of claims
|
|
processed over time. The variable CLM_ADJSMT_TYPE_CD identifies:
|
|
0 = Original Claim
|
|
1 = Cancellation Claim
|
|
2 = Adjustment Claim (adjustment to an original claim)"
|
|
|
|
CCLF IP Section 5.1.2 "Natural Key for Part A" (p.20):
|
|
|
|
Part A natural key: CLM_BLG_PRVDR_OSCAR_NUM, CLM_FROM_DT,
|
|
CLM_THRU_DT, Most Recent MBI (MR_MBI).
|
|
|
|
CCLF IP Section 5.2.1 "Related Claims in Part A Header File" (p.21-22):
|
|
|
|
"This group of claims consists of records that are all related to one
|
|
another — they all involve a claim for services provided to a
|
|
beneficiary by a single Part A provider during the same time period.
|
|
Sorting by CLM_EFCTV_DT and then by CLM_ADJSMT_TYPE_CD, yields
|
|
the final action claim as the last record."
|
|
|
|
"To identify 'final action' claims, match each cancellation claim with
|
|
an original or adjustment claim. Remove all matched pairs, yielding
|
|
only final action claim(s)."
|
|
|
|
ADR logic: rank by CUR_CLM_UNIQ_ID descending within the natural key
|
|
partition (PRVDR_OSCAR_NUM, CLM_FROM_DT, CLM_THRU_DT,
|
|
CURRENT_BENE_MBI_ID), keep rank=1 where CLM_ADJSMT_TYPE_CD != '1'.
|
|
""")
|
|
|
|
# ── stg_revenue_center (CCLF2) ──────────────────────
|
|
passages["stg_revenue_center"] = _clean("""
|
|
CCLF IP Section 2.2.2 "Part A Claim Revenue Center Detail File" (p.9):
|
|
|
|
"The Part A Claims Revenue Center Detail File (CCLF2) contains
|
|
line-item level detail for each claim from the Part A Claims Header
|
|
File. This file contains HCPCS code for each service, the date the
|
|
service was received, and for outpatient claims the payment amount
|
|
and allowed charge amount for individual services."
|
|
|
|
CCLF IP Section 3.5 "Part A Header vs Revenue Center Expenditures" (p.16):
|
|
|
|
"Both the Part A Header file (CCLF1) and the Part A Revenue Center
|
|
file (CCLF2) contain a payment field (CLM_PMT_AMT and
|
|
CLM_LINE_CVRD_PD_AMT respectively). The revenue center payment
|
|
amounts should only be relied on if they sum to the header level
|
|
payment amount."
|
|
|
|
Joined to CCLF1 header on CUR_CLM_UNIQ_ID + CURRENT_BENE_MBI_ID.
|
|
""")
|
|
|
|
# ── int_diagnosis_pivot (CCLF4) ─────────────────────
|
|
passages["int_diagnosis_pivot"] = _clean("""
|
|
CCLF IP Section 2.2.4 "Part A Diagnosis Code File" (p.9):
|
|
|
|
"The Part A Diagnosis Code File (CCLF4) contains the diagnosis code
|
|
for the principal diagnosis as well as all secondary diagnoses that
|
|
correspond with a given claim."
|
|
|
|
CCLF4 fields:
|
|
- CUR_CLM_UNIQ_ID: links back to CCLF1 claim header
|
|
- CLM_VAL_SQNC_NUM: sequence 1-25 identifying diagnosis position
|
|
- CLM_DGNS_CD: ICD-9/10 diagnosis code
|
|
- CLM_POA_IND: Present on Admission indicator
|
|
- DGNS_PRCDR_ICD_IND: '0'=ICD-10, '9'=ICD-9, 'U'=unknown
|
|
|
|
Pivots from long format (one row per diagnosis per claim) to wide
|
|
format (25 diagnosis_code_N and 25 diagnosis_poa_N columns per claim).
|
|
""")
|
|
|
|
# ── int_procedure_pivot (CCLF3) ─────────────────────
|
|
passages["int_procedure_pivot"] = _clean("""
|
|
CCLF IP Section 2.2.3 "Part A Procedure Code File" (p.9):
|
|
|
|
"The Part A Procedure Code File (CCLF3) contains detailed information
|
|
regarding claims from the Part A Claims Header File, such as the type
|
|
of surgical procedure performed and the date it was performed."
|
|
|
|
"The ICD Version Indicator is a single character denotation of whether
|
|
the code derived from ICD-9 (9) or ICD-10 (0). 'U' indicates unknown."
|
|
|
|
CCLF3 fields:
|
|
- CUR_CLM_UNIQ_ID: links back to CCLF1 claim header
|
|
- CLM_VAL_SQNC_NUM: sequence 1-25 identifying procedure position
|
|
- CLM_PRCDR_CD: ICD-9/10 procedure code
|
|
- CLM_PRCDR_PRFRM_DT: date procedure was performed
|
|
- DGNS_PRCDR_ICD_IND: '0'=ICD-10, '9'=ICD-9, 'U'=unknown
|
|
|
|
Pivots from long format to wide (25 procedure_code_N + procedure_date_N).
|
|
""")
|
|
|
|
# ── int_institutional_medical_claim ──────────────────
|
|
passages["int_institutional_medical_claim"] = _clean("""
|
|
CCLF IP Section 2.2 "Part A Claims Data" (p.8):
|
|
|
|
"Part A claim data files contain claims submitted by facilities such
|
|
as hospitals, SNFs, HHAs, rehabilitation facilities, and dialysis
|
|
facilities. These files are referred to as institutional or facility
|
|
files."
|
|
|
|
Joins four CCLF sources into the input_layer.medical_claim schema:
|
|
|
|
CCLF1 (header): claim_id, dates, bill type, DRG, payment, providers
|
|
CCLF2 (lines): line number, revenue center, HCPCS, modifiers, units
|
|
CCLF4 (dx pivot): diagnosis_code_1..25, diagnosis_poa_1..25
|
|
CCLF3 (px pivot): procedure_code_1..25, procedure_date_1..25
|
|
|
|
Key transformations:
|
|
- bill_type_code = CLM_BILL_FAC_TYPE_CD || CLM_BILL_CLSFCTN_CD
|
|
|| CLM_BILL_FREQ_CD (3-digit concatenation, IP Table 14 elements
|
|
8-9 and 29)
|
|
- drg_code = RIGHT(DGNS_DRG_CD, 3) — extract 3-digit MS-DRG from
|
|
5-character field (IP Table 14 element 17)
|
|
- diagnosis_code_type: DGNS_PRCDR_ICD_IND '0'->'icd-10-cm',
|
|
'9'->'icd-9-cm' (IP Table 14 element 30)
|
|
- procedure_code_type: same indicator mapped to 'icd-10-pcs'/'icd-9-pcs'
|
|
- claim_type = 'institutional' (all CCLF1 claims)
|
|
""")
|
|
|
|
# ── stg_physician_claim (CCLF5) ─────────────────────
|
|
passages["stg_physician_claim"] = _clean("""
|
|
CCLF IP Section 2.2.6 "Part B Physician File" (p.10):
|
|
|
|
"The Part B Physician File (CCLF5) consists of claim-line records but
|
|
includes both claim-level and line-level information. At the claim
|
|
level the file contains header level diagnosis code, disposition code,
|
|
and type of claim. At the line level the file contains provider
|
|
specialty, date of service, HCPCS code, HCPCS modifier code, payment
|
|
amount, allowed charge amount, line-level diagnosis code, units of
|
|
service, primary payer, provider TIN, and rendering NPI number."
|
|
|
|
CCLF IP Section 5.3.1 (p.23):
|
|
|
|
"Identify all the canceled records (line items) in the Part B
|
|
Physician file. The canceled line items are identified by
|
|
CLM_ADJSMT_TYPE_CD=1. Change the sign of CLM_LINE_CVRD_PD_AMT
|
|
and CLM_LINE_ALOWD_CHRG_AMT for each of these cancellation claims."
|
|
|
|
Fields negated on cancellation: CLM_LINE_CVRD_PD_AMT, CLM_LINE_ALOWD_CHRG_AMT.
|
|
""")
|
|
|
|
# ── int_physician_claim_adr ─────────────────────────
|
|
passages["int_physician_claim_adr"] = _clean("""
|
|
CCLF IP Section 5.1.2 "Natural Key for Part B Physician/DME" (p.20):
|
|
|
|
Part B natural key: CLM_CNTL_NUM, Most Recent MBI (MR_MBI).
|
|
|
|
CCLF IP Section 5.2.2 "Related Claims in the Part B Physician File" (p.22):
|
|
|
|
"In the Part B Physician File, you will find original claims,
|
|
cancellation claims, and adjustment claims."
|
|
|
|
CCLF IP Section 3.2 "Dropping Denied Claims" (p.15):
|
|
|
|
"For Part B Physician/DME claims, some individual line-items can be
|
|
denied, whereas other line-items are not denied. Part B claims need
|
|
to be dropped depending upon the value of CLM_CARR_PMT_DNL_CD, and
|
|
Part B line-items need to be dropped depending upon the value of
|
|
CLM_PRCSG_IND_CD."
|
|
|
|
ADR logic: rank by CUR_CLM_UNIQ_ID descending within the natural key
|
|
(RNDRG_PRVDR_NPI_NUM, CLM_FROM_DT, CLM_THRU_DT, CURRENT_BENE_MBI_ID),
|
|
keep rank=1 where CLM_ADJSMT_TYPE_CD != '1'.
|
|
""")
|
|
|
|
# ── int_physician_medical_claim ─────────────────────
|
|
passages["int_physician_medical_claim"] = _clean("""
|
|
CCLF IP Section 2.2.6 "Part B Physician File" (p.10):
|
|
|
|
CCLF5 contains both claim-level and line-level data in a single
|
|
file. Unlike Part A (which has separate header/line/dx/px files),
|
|
Part B has up to 12 inline diagnosis codes (CLM_DGNS_1_CD through
|
|
CLM_DGNS_12_CD) directly in each claim-line row.
|
|
|
|
Key field mappings from CCLF5 (IP Table 18):
|
|
- CUR_CLM_UNIQ_ID -> claim_id
|
|
- CLM_LINE_NUM -> claim_line_number
|
|
- CLM_POS_CD -> place_of_service_code
|
|
- CLM_LINE_HCPCS_CD -> hcpcs_code
|
|
- HCPCS_1_MDFR_CD..HCPCS_5_MDFR_CD -> hcpcs_modifier_1..5
|
|
- RNDRG_PRVDR_NPI_NUM -> rendering_npi
|
|
- CLM_RNDRG_PRVDR_TAX_NUM -> rendering_tin
|
|
- CLM_LINE_CVRD_PD_AMT -> paid_amount
|
|
- CLM_LINE_ALOWD_CHRG_AMT -> allowed_amount
|
|
- CLM_DGNS_1_CD..CLM_DGNS_12_CD -> diagnosis_code_1..12 (13-25 null)
|
|
- DGNS_PRCDR_ICD_IND -> diagnosis_code_type
|
|
- claim_type = 'professional' (all Part B physician claims)
|
|
""")
|
|
|
|
# ── stg_dme_claim (CCLF6) ──────────────────────────
|
|
passages["stg_dme_claim"] = _clean("""
|
|
CCLF IP Section 2.2.7 "Part B DME File" (p.10):
|
|
|
|
"The Part B DME File (CCLF6) consists of claim-line records but
|
|
includes both claim-level and line-level information. Claim-level
|
|
information includes date of service, disposition code, and type of
|
|
claim submitted (DMEPOS versus non-DMEPOS). Line-level information
|
|
includes date of service, HCPCS code, payment amount, allowed charge
|
|
amount, ordering NPI number, and 'paid to' NPI number."
|
|
|
|
Cancellation adjustment applied to CLM_LINE_CVRD_PD_AMT and
|
|
CLM_LINE_ALOWD_CHRG_AMT per IP Section 5.3.1.
|
|
""")
|
|
|
|
# ── int_dme_claim_adr ──────────────────────────────
|
|
passages["int_dme_claim_adr"] = _clean("""
|
|
CCLF IP Section 5.2.3 "Related Claims in the Part B DME File" (p.23):
|
|
|
|
"In the Part B DME File, you will find original claims, cancellation
|
|
claims, and adjustment claims. A variety of related claims are found
|
|
including:
|
|
1. An original claim with no other related claims.
|
|
2. A set of related claims consisting of an original claim and an
|
|
adjustment claim.
|
|
3. A set of related claims consisting of two original claims and
|
|
one cancellation claim.
|
|
4. A set of related claims consisting of three original claims
|
|
and two cancellation claims."
|
|
|
|
ADR logic mirrors Part B physician: rank within natural key partition,
|
|
keep latest non-cancelled version.
|
|
""")
|
|
|
|
# ── int_dme_medical_claim ──────────────────────────
|
|
passages["int_dme_medical_claim"] = _clean("""
|
|
CCLF IP Section 2.2.7 "Part B DME File" (p.10):
|
|
|
|
Key field mappings from CCLF6 (IP Table 19):
|
|
- CUR_CLM_UNIQ_ID -> claim_id
|
|
- CLM_LINE_NUM -> claim_line_number
|
|
- CLM_POS_CD -> place_of_service_code
|
|
- CLM_LINE_HCPCS_CD -> hcpcs_code
|
|
- CLM_LINE_CVRD_PD_AMT -> paid_amount
|
|
- CLM_LINE_ALOWD_CHRG_AMT -> allowed_amount
|
|
- ORDRG_PRVDR_NPI_NUM -> rendering_npi (ordering provider)
|
|
- CLM_BLG_PRVDR_NPI_NUM -> billing_npi
|
|
- claim_type = 'professional' (DME is classified as professional)
|
|
|
|
Note: CCLF6 does not contain diagnosis codes or procedure codes;
|
|
all 25 dx/px columns are null. Diagnosis information for DME claims
|
|
would need to be obtained through other sources.
|
|
""")
|
|
|
|
# ── medical_claim (union) ──────────────────────────
|
|
passages["medical_claim"] = _clean("""
|
|
CCLF IP Section 2.2 "Part A Claims Data" + Section 2.2.6-2.2.7 (p.8-10):
|
|
|
|
The three claim types are combined into a single medical_claim table:
|
|
|
|
- Institutional (CCLF1+2+3+4): Hospital, SNF, HHA, rehab, dialysis.
|
|
Includes Part A and some Part B services billed on institutional
|
|
claim forms. claim_type = 'institutional'.
|
|
|
|
- Professional/Physician (CCLF5): Part B services from physicians,
|
|
NPPs, and other individual practitioners. claim_type = 'professional'.
|
|
|
|
- Professional/DME (CCLF6): Durable Medical Equipment, Prosthetics,
|
|
Orthotics, and Supplies. claim_type = 'professional'.
|
|
|
|
CCLF IP Section 5.4 "Part A vs Part B Claims" (p.27):
|
|
|
|
"The Part A claims files will include Medicare provider payments for
|
|
some services covered under both Part A and Part B. To distinguish,
|
|
use the Claim Facility Type Code and Classification Code."
|
|
""")
|
|
|
|
# ── stg_pharmacy_claim (CCLF7) ─────────────────────
|
|
passages["stg_pharmacy_claim"] = _clean("""
|
|
CCLF IP Section 2.3 "Part D Claims Data" (p.10):
|
|
|
|
"The Part D File (CCLF7) contains prescription drug information at
|
|
the beneficiary level. Data elements include the NDC, quantity
|
|
dispensed, days supplied, prescribing provider ID, service provider
|
|
ID, and patient payment amount."
|
|
|
|
MBI resolution via CCLF9 crosswalk applied to BENE_MBI_ID.
|
|
""")
|
|
|
|
# ── int_pharmacy_claim_adr ─────────────────────────
|
|
passages["int_pharmacy_claim_adr"] = _clean("""
|
|
CCLF IP Section 3.3 "Part D Data Limitations" (p.15):
|
|
|
|
"The Part D claims contained in the CCLF are 'final action' claims,
|
|
unlike the other claims-related files which are debit/credit claims.
|
|
Part D cancellation claims are always submitted with a $0 payment
|
|
amount. As you create a claims record over time by combining many
|
|
monthly CCLF data feeds for Part D claims, you will need to identify
|
|
for any given set of Related Claims the most recent claim and
|
|
delete/ignore all of the previous related claims for that event."
|
|
|
|
CCLF IP Section 5.2.4 "Related Claims in the Part D File" (p.23):
|
|
|
|
"In the Part D File, you will find original claims, cancellation
|
|
claims, and adjustment claims."
|
|
|
|
ADR logic: rank by CLM_ADJSMT_TYPE_CD descending (adjustment=2 >
|
|
cancellation=1 > original=0) within CUR_CLM_UNIQ_ID partition,
|
|
keep rank=1 where type != '1'.
|
|
""")
|
|
|
|
# ── pharmacy_claim ─────────────────────────────────
|
|
passages["pharmacy_claim"] = _clean("""
|
|
CCLF IP Section 2.3 "Part D Claims Data" (p.10):
|
|
|
|
Key field mappings from CCLF7 (IP Table 20):
|
|
- CUR_CLM_UNIQ_ID -> claim_id
|
|
- CLM_LINE_NDC_CD -> ndc_code ("A universal unique product identifier
|
|
for human drugs")
|
|
- CLM_LINE_FROM_DT -> dispensing_date, paid_date
|
|
- CLM_LINE_SRVC_UNIT_QTY -> quantity
|
|
- CLM_LINE_DAYS_SUPLY_QTY -> days_supply
|
|
- CLM_LINE_RX_FILL_NUM -> refills
|
|
- CLM_LINE_BENE_PMT_AMT -> paid_amount, copayment_amount ("The dollar
|
|
amount paid by the beneficiary not reimbursed by a third party")
|
|
|
|
NPI qualification (IP Table 20 elements 6-7, 13):
|
|
- PRVDR_SRVC_ID_QLFYR_CD = '01' means NPI; '06' = UPIN, '07' = NCPDP
|
|
- CLM_SRVC_PRVDR_GNRC_ID_NUM -> dispensing_provider_npi (when = '01')
|
|
- PRVDR_PRSBNG_ID_QLFYR_CD = '01' means NPI
|
|
- CLM_PRSBNG_PRVDR_GNRC_ID_NUM -> prescribing_provider_npi (when = '01')
|
|
""")
|
|
|
|
# ── stg_beneficiary_demographics (CCLF8) ──────────
|
|
passages["stg_beneficiary_demographics"] = _clean("""
|
|
CCLF IP Section 2.4.1 "Beneficiary Demographics File" (p.10-12):
|
|
|
|
"This file contains the beneficiary's current MBI, first/middle/last
|
|
name, ZIP code, date of birth, sex, race, age, Medicare Status Code,
|
|
dual eligibility status, hospice begin/end dates, and date of death
|
|
if a decedent."
|
|
|
|
CCLF IP Section 3.1 (p.14):
|
|
|
|
MBI resolution via CCLF9 applied before deduplication to ensure a
|
|
single row per resolved beneficiary identity.
|
|
|
|
Dedup: unique on CURRENT_BENE_MBI_ID, keeping first occurrence.
|
|
""")
|
|
|
|
# ── eligibility ────────────────────────────────────
|
|
passages["eligibility"] = _clean("""
|
|
CCLF IP Section 2.4.1 "Beneficiary Demographics File" (p.10):
|
|
|
|
Key field mappings from CCLF8 (IP Table 21):
|
|
- BENE_MBI_ID (resolved) -> person_id, member_id
|
|
- BENE_SEX_CD -> gender: '0'=Unknown, '1'=Male, '2'=Female
|
|
- BENE_RACE_CD -> race: '0'=Unknown, '1'=White, '2'=Black,
|
|
'3'=Other, '4'=Asian, '5'=Hispanic, '6'=North American Native
|
|
- BENE_DOB -> birth_date
|
|
- BENE_DEATH_DT -> death_date; death_flag = 1 when not null
|
|
- BENE_RNG_BGN_DT -> enrollment_start_date ("Date beneficiary
|
|
enrolled in Hospice" — used as enrollment proxy)
|
|
- BENE_RNG_END_DT -> enrollment_end_date
|
|
- BENE_ORGNL_ENTLMT_RSN_CD -> original_reason_entitlement_code:
|
|
'0'=OASI, '1'=DIB, '2'=ESRD, '3'=DIB+ESRD
|
|
- BENE_DUAL_STUS_CD -> dual_status_code
|
|
- BENE_MDCR_STUS_CD -> medicare_status_code
|
|
- BENE_1ST_NAME, BENE_MIDL_NAME, BENE_LAST_NAME -> name fields
|
|
- BENE_LINE_1_ADR -> address
|
|
- GEO_ZIP_PLC_NAME -> city
|
|
- GEO_USPS_STATE_CD -> state
|
|
- GEO_ZIP5_CD -> zip_code
|
|
""")
|
|
|
|
return passages
|
|
|
|
|
|
def _clean(text: str) -> str:
|
|
"""Dedent and strip a passage."""
|
|
return textwrap.dedent(text).strip()
|
|
|
|
|
|
# ── Docstring injection ─────────────────────────────────────────
|
|
|
|
|
|
def _inject_express_docs(source: str, passages: dict[str, str]) -> str:
|
|
"""Inject IP passages into express function docstrings.
|
|
|
|
For each function, finds its docstring and appends the relevant
|
|
IP passage in a ``CCLF IP Reference`` section.
|
|
"""
|
|
lines = source.split("\n")
|
|
output: list[str] = []
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
output.append(line)
|
|
|
|
# Detect function definition
|
|
fn_match = re.match(r"^def (\w+)\(", line)
|
|
if fn_match:
|
|
fn_name = fn_match.group(1)
|
|
if fn_name in passages:
|
|
# Scan forward to find the closing triple-quote of
|
|
# the existing docstring
|
|
i += 1
|
|
in_docstring = False
|
|
docstring_indent = ""
|
|
triple_count = 0
|
|
while i < len(lines):
|
|
cur = lines[i]
|
|
output.append(cur)
|
|
|
|
# Count triple quotes on this line
|
|
tq = cur.count('"""')
|
|
triple_count += tq
|
|
|
|
if tq >= 1 and not in_docstring:
|
|
in_docstring = True
|
|
# Detect indentation from the opening line
|
|
m = re.match(r"^(\s+)", cur)
|
|
if m:
|
|
docstring_indent = m.group(1)
|
|
|
|
# Closing triple-quote: second occurrence
|
|
if triple_count >= 2:
|
|
# Found the closing """. Insert IP passage
|
|
# just before it.
|
|
closing_line = output.pop()
|
|
# Build the passage block
|
|
passage = passages[fn_name]
|
|
passage_lines = _format_passage(passage, docstring_indent)
|
|
output.extend(passage_lines)
|
|
output.append(closing_line)
|
|
break
|
|
|
|
i += 1
|
|
i += 1
|
|
|
|
return "\n".join(output)
|
|
|
|
|
|
def _format_passage(passage: str, indent: str) -> list[str]:
|
|
"""Format an IP passage for insertion into a docstring."""
|
|
result = [f"{indent}"] # blank line separator
|
|
result.append(f"{indent}CCLF IP Reference")
|
|
result.append(f"{indent}~~~~~~~~~~~~~~~~~~")
|
|
for line in passage.split("\n"):
|
|
if line.strip():
|
|
result.append(f"{indent}{line}")
|
|
else:
|
|
result.append(f"{indent}")
|
|
return result
|
|
|
|
|
|
def _inject_pipe_docs(source: str, passages: dict[str, str]) -> str:
|
|
"""Inject IP summary into pipe module docstring.
|
|
|
|
Adds a field-mapping reference table to the module docstring.
|
|
"""
|
|
# Build a summary section for the pipe module docstring
|
|
summary_lines = [
|
|
"",
|
|
"CCLF IP Reference for Each Step",
|
|
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
|
|
"",
|
|
"Each step below implements logic described in the CCLF",
|
|
"Information Packet (Version 41.0, 07/16/2025).",
|
|
"See ``src/aco/express/cclf.py`` for full IP citations",
|
|
"embedded in each function's docstring.",
|
|
"",
|
|
"Key IP sections governing this pipeline:",
|
|
"",
|
|
"- Section 2.2: Part A Claims Data (CCLF1-4, institutional)",
|
|
"- Section 2.2.6: Part B Physician File (CCLF5, professional)",
|
|
"- Section 2.2.7: Part B DME File (CCLF6, professional/DME)",
|
|
"- Section 2.3: Part D Claims Data (CCLF7, pharmacy)",
|
|
"- Section 2.4.1: Beneficiary Demographics (CCLF8, eligibility)",
|
|
"- Section 3.1: Matching MBIs (CCLF9 crosswalk logic)",
|
|
"- Section 3.2: Dropping Denied Claims (Part B filtering)",
|
|
"- Section 3.3: Part D Data Limitations (final-action claims)",
|
|
"- Section 3.5: Header vs Revenue Center Expenditures",
|
|
"- Section 4.4: Debit/Credit Method & Adjustment Type Codes",
|
|
"- Section 5.1: Natural Key & MBI XREF Creation",
|
|
"- Section 5.2: Related Claims Resolution (ADR by file type)",
|
|
"- Section 5.3: Expenditure Calculation (sign-change rules)",
|
|
]
|
|
|
|
# Find the closing """ of the module docstring and insert before it
|
|
lines = source.split("\n")
|
|
output: list[str] = []
|
|
triple_count = 0
|
|
inserted = False
|
|
|
|
for line in lines:
|
|
tq = line.count('"""')
|
|
triple_count += tq
|
|
|
|
# Second triple-quote closes the module docstring
|
|
if triple_count == 2 and not inserted:
|
|
# Insert summary before closing """
|
|
for sl in summary_lines:
|
|
output.append(sl)
|
|
output.append("")
|
|
inserted = True
|
|
|
|
output.append(line)
|
|
|
|
return "\n".join(output)
|
|
|
|
|
|
# ── Main ─────────────────────────────────────────────────────────
|
|
|
|
|
|
def main() -> None:
|
|
print(f"Reading {PDF_PATH.name}...")
|
|
pages = _extract_pages(PDF_PATH)
|
|
print(f" {len(pages)} pages extracted")
|
|
|
|
print("Building IP passages for each function...")
|
|
passages = _build_ip_passages(pages)
|
|
print(f" {len(passages)} functions annotated")
|
|
|
|
# ── Annotate express module ──────────────────────────
|
|
print(f"\nAnnotating {EXPRESS_PATH.name}...")
|
|
express_src = EXPRESS_PATH.read_text()
|
|
|
|
# Check which functions already have IP references
|
|
already = set()
|
|
for fn_name in passages:
|
|
pattern = rf"def {fn_name}\(.*?CCLF IP Reference"
|
|
if re.search(pattern, express_src, re.DOTALL):
|
|
already.add(fn_name)
|
|
|
|
if already:
|
|
print(
|
|
f" Skipping {len(already)} already-annotated: {', '.join(sorted(already))}"
|
|
)
|
|
passages_to_apply = {k: v for k, v in passages.items() if k not in already}
|
|
else:
|
|
passages_to_apply = passages
|
|
|
|
if passages_to_apply:
|
|
new_express = _inject_express_docs(express_src, passages_to_apply)
|
|
EXPRESS_PATH.write_text(new_express)
|
|
print(
|
|
f" Wrote {len(new_express.splitlines())} lines "
|
|
f"({len(passages_to_apply)} functions annotated)"
|
|
)
|
|
else:
|
|
print(" All functions already annotated, no changes.")
|
|
|
|
# ── Annotate pipe module ─────────────────────────────
|
|
print(f"\nAnnotating {PIPE_PATH.name}...")
|
|
pipe_src = PIPE_PATH.read_text()
|
|
|
|
if "CCLF IP Reference" in pipe_src:
|
|
print(" Already annotated, no changes.")
|
|
else:
|
|
new_pipe = _inject_pipe_docs(pipe_src, passages)
|
|
PIPE_PATH.write_text(new_pipe)
|
|
print(f" Wrote {len(new_pipe.splitlines())} lines")
|
|
|
|
print("\nDone.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|