""" Claim-id extraction from the exported report. python tests\\test_claim_file.py Built xlsx files, not fixtures checked into the repo: the real report is patient data and must not be committed, but the *shape* of it can be reproduced exactly. Each case below is written as a genuine xlsx — zip, worksheet XML, inline `t="str"` cells — matching what eCW actually produced on 2026-09-07: CLAIMS# ... The cases that matter, and why each one is here: * the real header is **`CLAIMS#`**, not `CLAIM #` as the on-screen grid calls it — reading the column name off the screen would have found nothing; * the claim id is in column **C** with a blank column A before it, so anything that reads positionally from the left is wrong; * a **sparse row** (eCW omitting an empty cell entirely) must not shift the columns after it — the one failure mode of parsing cells in order; * shared strings, which this build does not use but another may; * an HTML table served with an .xls name, which other eCW builds send. """ import io import sys import zipfile import pathlib import tempfile sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "src")) from ecw_bot import claim_file # noqa: E402 from ecw_bot.core import spreadsheet # noqa: E402 TMP = pathlib.Path(tempfile.mkdtemp()) NS = 'xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"' def xlsx(rows, shared=None, name="t.xlsx") -> pathlib.Path: """ Write a real xlsx. `rows` is a list of {column letter: cell} dicts. A cell is either a plain string (inline `t="str"`, what eCW sends) or an int, meaning an index into `shared` (`t="s"`). """ body = [] for r, cells in enumerate(rows, start=1): parts = [] for letter, value in cells.items(): if isinstance(value, int): parts.append(f'{value}') else: parts.append(f'{value}') body.append(f'{"".join(parts)}') sheet = (f'' f'{"".join(body)}') path = TMP / name with zipfile.ZipFile(path, "w") as z: z.writestr("xl/worksheets/sheet1.xml", sheet) if shared is not None: items = "".join(f"{s}" for s in shared) z.writestr("xl/sharedStrings.xml", f'{items}') return path def html(name="t.xls") -> pathlib.Path: path = TMP / name path.write_text( "" "" "" "" "
CLAIMTYPECLAIMS#PATIENT
U9104000Sample, Pat, Q
U9101000Example, Robin
", encoding="utf-8") return path # Column A is blank in eCW's report; the claim id is in C. REAL = [ {"A": "", "B": "CLAIMTYPE", "C": "CLAIMS#", "D": "SERVICE DATE"}, {"A": "", "B": "U", "C": "9104000", "D": "06/29/2026"}, {"A": "", "B": "U", "C": "9101000", "D": "07/01/2026"}, {"A": "", "B": "U", "C": "9105000", "D": "07/02/2026"}, ] def case_real_shape(): info = claim_file.claim_ids(xlsx(REAL, name="real.xlsx")) assert info["header"] == "CLAIMS#", info["header"] assert info["column"] == 3, info["column"] assert info["ids"] == ["9104000", "9101000", "9105000"], info["ids"] def case_sparse_row(): """eCW omitting an empty cell must not shift the columns after it.""" rows = list(REAL) + [{"B": "U", "C": "9102000"}] # no A at all info = claim_file.claim_ids(xlsx(rows, name="sparse.xlsx")) assert info["ids"][-1] == "9102000", info["ids"][-1] assert info["column"] == 3, info["column"] def case_duplicates_and_blanks(): rows = list(REAL) + [ {"A": "", "B": "U", "C": "9104000"}, # duplicate of row 2 {"A": "", "B": "U", "C": ""}, # blank claim id ] info = claim_file.claim_ids(xlsx(rows, name="dupes.xlsx")) assert info["duplicates"] == ["9104000"], info["duplicates"] assert info["blank_rows"] == 1, info["blank_rows"] assert len(info["ids"]) == 3, info["ids"] # Reported, not folded into the total — a count alone hides both. text = claim_file.summary(info) assert "1 duplicate(s) skipped" in text, text assert "1 blank row(s) skipped" in text, text def case_shared_strings(): """This build inlines every string, but another may use the table.""" shared = ["", "CLAIMTYPE", "CLAIMS#", "U"] rows = [{"A": 0, "B": 1, "C": 2}, {"A": 0, "B": 3, "C": "9104000"}, {"A": 0, "B": 3, "C": "9101000"}] info = claim_file.claim_ids(xlsx(rows, shared=shared, name="shared.xlsx")) assert info["header"] == "CLAIMS#", info["header"] assert info["ids"] == ["9104000", "9101000"], info["ids"] def case_html_export(): """Other eCW builds serve an HTML table with an .xls name.""" info = claim_file.claim_ids(html()) assert info["header"] == "CLAIMS#", info["header"] assert info["ids"] == ["9104000", "9101000"], info["ids"] def case_no_header_falls_back_to_shape(): rows = [{"A": "", "B": "U", "C": str(9104000 + i)} for i in range(8)] info = claim_file.claim_ids(xlsx(rows, name="noheader.xlsx")) assert len(info["ids"]) == 8, info["ids"] assert info["column"] == 3, info["column"] def case_no_claim_column_is_an_error(): rows = [{"A": "PATIENT", "B": "PAYER"}, {"A": "Sample, Pat", "B": "Medicaid"}] try: claim_file.claim_ids(xlsx(rows, name="nocol.xlsx")) except ValueError as exc: assert "No claim-id column" in str(exc), exc return raise AssertionError("expected a ValueError naming the missing column") def case_summary_does_not_dump_everything(): """541 ids in a MessageBox would be a wall of digits. Ends only.""" rows = [{"A": "", "C": "CLAIMS#"}] + \ [{"A": "", "C": str(5500000 + i)} for i in range(541)] info = claim_file.claim_ids(xlsx(rows, name="big.xlsx")) assert len(info["ids"]) == 541 text = claim_file.summary(info) assert text.count("55000") < 30, "summary is dumping the whole list" assert "541" in text, text assert info["ids"][0] in text and info["ids"][-1] in text, text CASES = [v for k, v in sorted(globals().items()) if k.startswith("case_")] def main() -> int: failures = 0 for fn in CASES: name = fn.__name__[5:].replace("_", " ") try: fn() print(f" ok {name}") except AssertionError as exc: print(f" FAIL {name}: {exc}") failures += 1 except Exception as exc: print(f" ERROR {name}: {type(exc).__name__}: {exc}") failures += 1 print(f"\n{len(CASES) - failures}/{len(CASES)} passed") return 1 if failures else 0 if __name__ == "__main__": sys.exit(main())