Skip to content

Commit be29b06

Browse files
committed
scripts: round-2 peer-review fixes (year derivation + render cap)
- parse_bills_to_tracker: bill_id and encounter_id year tokens are now derived from the row's own statement_date / DOS instead of the current calendar year, so a 2025-dated statement processed in 2026 still gets numbered under 2025. Added a small _year_of() helper to centralize the parse. - classify_rename_medical_bills: render_pdf_pages now stops iterating after MAX_PAGES, matching the fix already applied to classify_eobs. Previously the full PDF was rasterized in memory and then sliced, which was wasteful on multi-page itemizations. - scripts/README.md: documents that --help / CLI overrides cover input/output paths but the Azure OpenAI .env path is intentionally hardcoded to the workspace-wide location per AGENTS.md §4.
1 parent 239d99b commit be29b06

3 files changed

Lines changed: 29 additions & 16 deletions

File tree

scripts/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Skips rows whose `status` is settled or closed. Returns exit code 1 if any bill
5151

5252
The scripts below are for a single-workstation pipeline that takes a folder full of scanned medical-bill PDFs and turns it into a clean per-provider folder layout plus a tracker CSV the LLM workflow can consume. They are deliberately not part of the kit's instruction-only contract: they call Azure OpenAI for vision OCR, expect a specific local folder layout, and read API credentials from a workstation `.env`. Use them, ignore them, or rewrite them to fit your own setup.
5353

54-
All four accept `--help` and CLI overrides for every default path.
54+
All four accept `--help` and CLI overrides for every input/output path. The Azure OpenAI `.env` location is the one exception, intentionally hardcoded to `C:/Code/projects/ai-toolkit/.env` per the workspace-wide AGENTS.md §4 pre-push peer review pattern — edit the constant at the top of each script if your workstation keeps the credentials somewhere else.
5555

5656
### classify_rename_medical_bills.py
5757

scripts/classify_rename_medical_bills.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,17 @@ def make_client():
142142
)
143143

144144

145-
def render_pdf_pages(pdf_path: Path) -> list[bytes]:
145+
def render_pdf_pages(pdf_path: Path, max_pages: int = MAX_PAGES) -> list[bytes]:
146+
"""Render up to max_pages of the PDF as JPEG bytes; stop iterating
147+
after the cap to avoid paying the render cost on pages we will not
148+
send to the model."""
146149
doc = fitz.open(str(pdf_path))
147150
pages: list[bytes] = []
148151
zoom = RENDER_DPI / 72.0
149152
mat = fitz.Matrix(zoom, zoom)
150-
for page in doc:
153+
for i, page in enumerate(doc):
154+
if i >= max_pages:
155+
break
151156
pix = page.get_pixmap(matrix=mat)
152157
pages.append(pix.tobytes("jpeg", jpg_quality=JPEG_QUALITY))
153158
doc.close()
@@ -275,7 +280,7 @@ def process_file(client, deployment, path: Path,
275280

276281
if suffix == ".pdf":
277282
try:
278-
images = render_pdf_pages(path)
283+
images = render_pdf_pages(path, max_pages=MAX_PAGES)
279284
except Exception as exc:
280285
print(f" [skip] render failed: {exc}", flush=True)
281286
return
@@ -285,11 +290,6 @@ def process_file(client, deployment, path: Path,
285290
print(f" [skip] unsupported type: {suffix}", flush=True)
286291
return
287292

288-
if len(images) > MAX_PAGES:
289-
print(f" [warn] {len(images)} pages, capping at {MAX_PAGES}",
290-
flush=True)
291-
images = images[:MAX_PAGES]
292-
293293
try:
294294
result = call_vision(client, deployment, images)
295295
except Exception as exc:

scripts/parse_bills_to_tracker.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,13 @@ def normalize_account(acct) -> str:
296296
return re.sub(r"\s+", "", str(acct))
297297

298298

299+
def _year_of(iso_date: str | None) -> int | None:
300+
if not iso_date or len(iso_date) < 4 or not iso_date[:4].isdigit():
301+
return None
302+
y = int(iso_date[:4])
303+
return y if 1900 <= y <= 2100 else None
304+
305+
299306
def _merge_into(existing: dict, statement_date: str | None,
300307
current_balance: float | None) -> None:
301308
"""Update an existing bill row with a newer statement's data."""
@@ -358,13 +365,18 @@ def make_bill_record(extracted: dict, source_file: Path,
358365
_merge_into(existing, statement_date, current_balance)
359366
return None, "merged"
360367

361-
# New bill
362-
year = datetime.date.today().year
363-
bill_seq[year] = bill_seq.get(year, 0) + 1
364-
bill_id = f"B-{year}-{bill_seq[year]:03d}"
368+
# New bill. Derive the bill_id year from the row's own statement_date
369+
# (or DOS start, then last_statement_date) rather than today, so that
370+
# late-arriving 2025 statements processed in 2026 are still numbered
371+
# under 2025 instead of jumping years.
372+
bill_year = _year_of(statement_date) or _year_of(dos_start) \
373+
or datetime.date.today().year
374+
bill_seq[bill_year] = bill_seq.get(bill_year, 0) + 1
375+
bill_id = f"B-{bill_year}-{bill_seq[bill_year]:03d}"
365376

366377
# Encounter assignment: share encounter_id with any existing bill that
367-
# has the same date_of_service_start
378+
# has the same date_of_service_start. Year of the encounter id is the
379+
# year of the DOS itself.
368380
encounter_id = ""
369381
if dos_start:
370382
for existing in known_bills:
@@ -373,8 +385,9 @@ def make_bill_record(extracted: dict, source_file: Path,
373385
encounter_id = existing["encounter_id"]
374386
break
375387
if not encounter_id and dos_start:
376-
encounter_seq[year] = encounter_seq.get(year, 0) + 1
377-
encounter_id = f"E-{year}-{encounter_seq[year]:03d}"
388+
enc_year = _year_of(dos_start) or bill_year
389+
encounter_seq[enc_year] = encounter_seq.get(enc_year, 0) + 1
390+
encounter_id = f"E-{enc_year}-{encounter_seq[enc_year]:03d}"
378391

379392
findings = extracted.get("findings") or []
380393
if not isinstance(findings, list):

0 commit comments

Comments
 (0)