r""" core/charge_summary.py — the 24-column Charge Summary sheet written at the end of every claims run. python tests\test_charge_summary.py No browser, no eCW: a fictional report is written to a temp folder. Review items D1 and D10 (2026-10-05): a TEST_CLAIM_IDS_FILE run writes Charge Summary_TEST_.xlsx, and a same-day file held open (as Excel holds it) no longer costs the run its sheet — it lands under a `__HHMMSS` name instead. Review items D2/St1 (unit D, 2026-10-05): a third Category, "Review" / "Needs manual review", for every claim the bot did not really check. The Pass/Fail cases below now pass a fully-read UB claim (`_ub()`: CPT rows, a progress note that was read, conditions that ran) — before, a bare `{"_conditions": {"fired": []}}` was enough for Pass, and that bare dict (no note, no rows) is exactly what now reads Review. """ import io import re import sys import pathlib import tempfile import contextlib ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) from ecw_bot import settings as cfg # noqa: E402 from ecw_bot.core import charge_summary as cs, spreadsheet # noqa: E402 from ecw_bot.flows import export_medcor_ready as flow # noqa: E402 CASES = [] REPORT_HEADER = ["", "CLAIMTYPE", "CLAIMS#", "SERVICE DATE", "PVDR", "PATIENT", "PAYER", "STATUS", "CHARGES", "PMTS/ADJS", "Adjustment", "WithHeld", "Balance", "Visit Type", "Account Number", "Provider Name", "Guarantor Name"] def _row(claim, payer="Medicaid", pmts="35.00", adj="5.00", ctype="U"): return ["", ctype, claim, "09/01/2026", "AB", "Test, Patient", payer, "Medcor Ready", "275.00", pmts, adj, "0.00", "240.00", "Acute Care Walk In", "123456", "Doe, Jane", "Test, Patient"] REPORT = [REPORT_HEADER, _row("1000001"), _row("1000002", payer="DentaQuest", ctype="D"), _row("1000001"), _row("1000003", payer="Unmapped Payer X")] MAPPING = {"Medicaid": "Medicaid", "DentaQuest": "Dentaquest"} def case(fn): CASES.append(fn) return fn def _build(outcomes=None): return cs.build_rows(REPORT, 0, 2, outcomes or {}, "10/01/2026", "eCW Bot", MAPPING) def _col(name): return cs.HEADERS.index(name) def _ub(fired=(), saved=False, **extra): """A UB/HCFA claim the bot fully read: CPT rows, a progress note it actually read, and the conditions run with `fired` changing something. `extra` overrides or adds keys (`_conditions` included).""" data = {"claim_no": "1", "cpt": [{"code": "99213", "checked": True, "fee": "100.00"}], "progress_notes": {"opened": "modal", "text": "Office visit."}, "_conditions": {"fired": list(fired), "saved": saved, "failed": [], "review": [], "comments": {}}} data.update(extra) return data # ── claim_outcome ──────────────────────────────────────────────────────────── @case def not_found_or_not_read_leaves_the_tracking_columns_blank(): assert cs.claim_outcome(False, {}) == {} assert cs.claim_outcome(True, {}) == {} @case def clean_comment_is_the_teams_exact_wording(): # Operator, 2026-10-06 — copied from the team's sheet, character for # character ("Pending with Errors", not the earlier "Medcor Ready"). assert cs.CLEAN == ("Clean claims (claims that have no errors, placed " "in Pending with Errors)"), cs.CLEAN @case def nothing_fired_is_a_clean_pass(): o = cs.claim_outcome(True, _ub()) assert o == {"Comments": cs.CLEAN, "Current Status": "Ready to Submit", "Category": "Pass"}, o @case def a_fired_condition_is_a_fail_with_its_comment(): o = cs.claim_outcome(True, _ub(["depression_dx_pointer", "dummy_codes"])) assert o["Category"] == "Fail", o assert "ICD pointer issue" in o["Comments"], o assert "DCONS/Dscprit" in o["Comments"], o assert o["Current Status"] == "Corrections pending (not saved)", o @case def a_saved_correction_reads_ready_to_submit(): o = cs.claim_outcome(True, _ub(["telehealth"], saved=True)) assert o["Current Status"] == "Ready to Submit", o @case def dental_high_risk_and_expired_sliding_fee_are_findings(): o = cs.claim_outcome(True, { "high_risk": {"high_risk": True}, "sliding_fee": {"verdict": "expired", "reason": "DOS after range"}}) assert o["Category"] == "Fail", o assert cs.HIGH_RISK in o["Comments"], o assert "Sliding fee EXPIRED" in o["Comments"], o @case def dental_clean_with_active_sliding_fee_is_a_pass(): o = cs.claim_outcome(True, {"high_risk": {"high_risk": False}, "sliding_fee": {"verdict": "active"}}) assert o["Category"] == "Pass" and o["Comments"] == cs.CLEAN, o @case def dental_sliding_fee_none_is_noted_but_still_a_pass(): o = cs.claim_outcome(True, {"high_risk": {"high_risk": False}, "sliding_fee": {"verdict": "none", "reason": "no dates"}}) assert o["Category"] == "Pass", o assert "Sliding fee NONE" in o["Comments"], o # ── claim_outcome: Review (review items D2/St1, unit D) ───────────────────── REVIEW = {"Current Status": "Needs manual review", "Category": "Review"} def _review(data): o = cs.claim_outcome(True, data) assert {k: o.get(k) for k in REVIEW} == REVIEW, o return o["Comments"] @case def a_form_that_could_not_be_read_is_review_not_blank(): c = _review({"claim_no": "1", "_error": "Timeout 30000ms exceeded."}) assert c == "Bot could not read the claim (Timeout 30000ms exceeded.)", c @case def a_form_that_never_finished_loading_is_review_with_its_reason(): c = _review(_ub(_not_evaluated="HCFA form: no CPT row after 20s", _conditions=None, cpt=[])) assert c.startswith("HCFA form: no CPT row after 20s"), c # The flow skipped the conditions ON PURPOSE — not APPLY_CORRECTIONS=0. assert "APPLY_CORRECTIONS" not in c, c @case def apply_corrections_off_is_review(): data = _ub() del data["_conditions"] assert _review(data) == "Conditions not run (APPLY_CORRECTIONS=0)" @case def a_progress_note_never_read_is_review_in_every_state(): for note, state in ((None, "not opened"), ({"opened": "nothing"}, "nothing"), ({"opened": "absent"}, "absent"), ({"opened": "disabled"}, "disabled")): data = _ub() if note is None: del data["progress_notes"] else: data["progress_notes"] = note c = _review(data) assert c == (f"Progress note not read ({state}) — telehealth not " f"checked"), (note, c) @case def a_progress_note_that_raised_is_review_with_its_first_line(): c = _review(_ub(progress_notes={"opened": "iframe", "error": "Target closed\nCall log: ..."})) assert c == ("Progress note not read (error: Target closed) — telehealth " "not checked"), c @case def zero_cpt_rows_read_is_review(): assert _review(_ub(cpt=[])) == "No CPT rows were read" @case def a_condition_that_raised_is_review(): c = _review(_ub(_conditions={"fired": [], "saved": False, "comments": {}, "review": [], "failed": [{"name": "telehealth", "stage": "apply", "error": "Timeout"}]})) assert c == "telehealth failed: Timeout", c @case def a_condition_review_reason_is_review(): c = _review(_ub(_conditions={ "fired": [], "saved": False, "comments": {}, "failed": [], "review": [{"name": "dummy_codes", "reason": "CPT/HCPCS tab could not be opened — not " "checked"}]})) assert c == ("dummy_codes: CPT/HCPCS tab could not be opened — not " "checked"), c @case def dental_unreadable_billing_notes_is_review(): c = _review({"claim_no": "1", "high_risk": {"high_risk": None, "match": ""}}) assert c == "High risk: Billing Notes could not be read", c @case def dental_sliding_fee_unknown_is_review_not_a_pass(): c = _review({"high_risk": {"high_risk": False}, "sliding_fee": {"verdict": "unknown", "reason": "no Patient Info"}}) assert c == "Sliding fee UNKNOWN (no Patient Info)", c @case def a_dental_claim_is_not_asked_for_conditions_notes_or_cpt_rows(): """Dental never runs the conditions or opens a note — by design.""" o = cs.claim_outcome(True, {"claim_no": "1", "high_risk": {"high_risk": False}}) assert o["Category"] == "Pass" and o["Comments"] == cs.CLEAN, o @case def a_dental_claim_that_ran_the_conditions_carries_both_in_one_row(): """ Operator, 2026-10-12: a Dental claim runs the conditions too — its row has the condition comments AND the Dental findings, and the same claim-level checks as UB/HCFA (it did read a note and CPT rows). """ data = _ub(["dummy_codes"], screen="dental", high_risk={"high_risk": True, "match": ""}, sliding_fee={"verdict": "active"}) o = cs.claim_outcome(True, data) assert o["Category"] == "Fail", o assert "DCONS/Dscprit" in o["Comments"], o assert cs.HIGH_RISK in o["Comments"], o assert o["Current Status"] == "Corrections pending (not saved)", o assert cs.is_dental(data) @case def a_dental_read_is_asked_for_its_note_and_rows_like_any_claim(): data = _ub(screen="dental", high_risk={"high_risk": False, "match": ""}, progress_notes={"opened": "disabled"}) c = _review(data) assert "Progress note not read (disabled)" in c, c no_cond = {k: v for k, v in data.items() if k != "_conditions"} no_cond["progress_notes"] = {"opened": "modal", "text": "x"} assert "Conditions not run" in _review(no_cond), _review(no_cond) @case def a_dental_claim_that_never_finished_loading_is_review(): c = _review({"high_risk": {"high_risk": True}, "_not_evaluated": "Dental form: no service line after 20s"}) # The finding first, then the reason. assert c == (f"{cs.HIGH_RISK}; Dental form: no service line after 20s"), c @case def review_wins_over_fail_and_carries_both(): """A fired condition on a claim whose note was never read.""" c = _review(_ub(["dummy_codes"], progress_notes={"opened": "disabled"})) assert c == (f"{cs.COMMENTS['dummy_codes']}; Progress note not read " f"(disabled) — telehealth not checked"), c @case def every_reason_is_listed_in_order(): data = _ub(["telehealth"], cpt=[], progress_notes={"opened": "nothing"}) data["_conditions"]["failed"] = [{"name": "j2930_j2001_update", "stage": "detect", "error": "boom"}] data["_conditions"]["review"] = [{"name": "vaccine_admin_codes", "reason": "payer unmapped"}] c = _review(data) assert c.split("; ") == [ cs.COMMENTS["telehealth"], "Progress note not read (nothing) — telehealth not checked", "No CPT rows were read", "j2930_j2001_update failed: boom", "vaccine_admin_codes: payer unmapped"], c @case def a_fail_uses_the_conditions_own_comment_when_it_has_one(): data = _ub(["telehealth", "dummy_codes"]) data["_conditions"]["comments"] = {"telehealth": "Telehealth (POS 02 set)"} o = cs.claim_outcome(True, data) assert o["Category"] == "Fail", o assert o["Comments"] == ("Telehealth (POS 02 set); " + cs.COMMENTS["dummy_codes"]), o @case def a_partial_note_with_no_sections_is_review(): """ Unit D review, finding 1 (2026-10-05): the header arrived and nothing under it — no Procedure Codes, so telehealth saw nothing (the D8 false Clean). This test pinned Pass before; it is Review now. """ c = _review(_ub(progress_notes={"opened": "modal", "text": "x", "partial": True, "partial_sections": 0})) assert c == "Progress note partial (0 sections) — telehealth not checked", c @case def a_partial_note_with_some_sections_is_review_too(): """Sections never settled: the Procedure Codes may still be missing.""" c = _review(_ub(progress_notes={"opened": "modal", "text": "x", "partial": True, "partial_sections": 3})) assert c == ("Progress note may be partial (3 section(s)) — telehealth " "may not be fully checked"), c @case def a_partial_note_with_an_unknown_section_count_has_no_question_mark(): """ Unit F review, finding 4 (2026-10-05): these reasons are also the on-screen "Needs review" lines, and D9 keeps "?" off the client's box. """ c = _review(_ub(progress_notes={"opened": "modal", "text": "x", "partial": True})) assert c == ("Progress note may be partial (section count unknown) — " "telehealth may not be fully checked"), c assert "?" not in c, c @case def a_partial_note_keeps_the_fired_conditions_comment_first(): c = _review(_ub(["telehealth"], progress_notes={"opened": "tab", "text": "x", "partial": True, "partial_sections": 0})) assert c == (f"{cs.COMMENTS['telehealth']}; Progress note partial " f"(0 sections) — telehealth not checked"), c @case def first_line_is_the_one_shared_helper(): """ Unit D review, finding 4: core/text.first_line replaced three copies (conditions/base, core/charge_summary, the flow's inline one). """ from ecw_bot.core.text import first_line from ecw_bot.conditions import base assert first_line(TimeoutError("Timeout 30000ms\n call log:\n x")) == \ "Timeout 30000ms" assert first_line(ValueError()) == "ValueError" # empty message assert first_line(" boom \nmore") == "boom" assert first_line("") == "" assert base.first_line is first_line and cs.first_line is first_line assert flow.first_line is first_line # Final review, 2026-10-05: the three private copies that were left # (claim_form, spreadsheet, vaccine_admin_codes) are gone too. Two of # them returned "" for a message that starts with a newline. from ecw_bot.pages import claim_form, progress_note from ecw_bot.core import spreadsheet from ecw_bot.conditions import vaccine_admin_codes for mod in (claim_form, progress_note, spreadsheet, vaccine_admin_codes): assert mod.first_line is first_line, mod.__name__ assert not hasattr(mod, "_first_line"), mod.__name__ assert first_line(RuntimeError("\nreal first line\nmore")) == \ "real first line" # ── Final review, 2026-10-05 ───────────────────────────────────────────────── @case def a_saved_claim_that_is_review_says_it_was_saved(): """ "Needs manual review" alone hid a save that had happened: the reviewer could treat the claim as untouched and redo or undo live edits. """ o = cs.claim_outcome(True, _ub(["zero_fee_uncheck"], saved=True, progress_notes={"opened": "disabled"})) assert o["Category"] == "Review", o assert o["Current Status"] == "Needs manual review", o assert o["Comments"].startswith(f"{cs.SAVED_NOTE}; " f"{cs.COMMENTS['zero_fee_uncheck']}"), o unsaved = cs.claim_outcome(True, _ub(["zero_fee_uncheck"], progress_notes={"opened": "disabled"})) assert cs.SAVED_NOTE not in unsaved["Comments"], unsaved @case def a_search_that_raised_is_review_not_a_blank_row(): data = {"claim_no": "1", "_search_error": "Lookup never became visible"} o = cs.claim_outcome(False, data) assert o == {"Comments": "Bot could not search the claim (Lookup never " "became visible)", "Current Status": "Needs manual review", "Category": "Review"}, o # A claim really not in the queue stays blank. assert cs.claim_outcome(False, {}) == {} @case def an_error_after_the_conditions_ran_is_worded_by_stage(): data = {"claim_no": "1", "_error": "describe blew up", "_conditions": {"fired": ["dummy_codes"], "saved": False, "failed": [], "review": [], "comments": {}}} o = cs.claim_outcome(True, data) assert o["Category"] == "Review", o assert o["Comments"] == (f"{cs.COMMENTS['dummy_codes']}; Bot failed after " f"the conditions ran (describe blew up)"), o plain = cs.claim_outcome(True, {"claim_no": "1", "_error": "x"}) assert plain["Comments"] == "Bot could not read the claim (x)", plain @case def cpt_rows_with_no_icd_rows_is_review(): """depression_dx_pointer reads the ICD list; an empty one is unchecked.""" o = cs.claim_outcome(True, _ub(icd=[])) assert o["Category"] == "Review", o assert o["Comments"] == "No ICD rows were read", o assert cs.claim_outcome(True, _ub(icd=[{"code": "R69"}]))["Category"] \ == "Pass" # Zero CPT rows already says it; no second ICD line on top. reasons = cs.review_reasons(_ub(cpt=[], icd=[])) assert reasons == ["No CPT rows were read"], reasons @case def a_note_skipped_on_a_form_that_never_loaded_is_named(): reasons = cs.review_reasons(_ub(progress_notes={"opened": "skipped"})) assert reasons == ["Progress note not read (skipped) — telehealth not " "checked"], reasons @case def the_telehealth_fallback_comments_claim_nothing_that_may_not_have_happened(): """ Used only when a summary_comment hook raises: they must not say "G2025 added" (it can fail), "93 or 95" (Medicaid is GT) or "unchecked". """ for name in ("telehealth", "medicare_telehealth"): text = cs.COMMENTS[name] for false_claim in ("G2025 added", "93 or 95", "unchecked"): assert false_claim not in text, (name, text) assert "see the log" in text, text @case def a_settled_note_is_still_a_pass(): """Control: `partial` absent (the note settled) — Pass as before.""" o = cs.claim_outcome(True, _ub(progress_notes={"opened": "modal", "text": "x"})) assert o["Category"] == "Pass", o # ── build_rows ─────────────────────────────────────────────────────────────── @case def header_is_the_formats_24_columns_in_order(): rows = _build() assert rows[0] == cs.HEADERS and len(rows[0]) == 24 assert "CLAIMTYPE" not in rows[0] @case def one_row_per_claim_numbered_in_report_order(): rows = _build() assert [r[_col("CLAIMS#")] for r in rows[1:]] == ["1000001", "1000002", "1000003"] assert [r[_col("S.No")] for r in rows[1:]] == [1, 2, 3] @case def report_columns_are_copied_and_group_and_refunds_are_derived(): r = _build()[1] assert r[_col("PAYER")] == "Medicaid" and r[_col("CHARGES")] == "275.00" assert r[_col("Guarantor Name")] == "Test, Patient" assert r[_col("Group Name")] == "Medicaid" assert r[_col("Pmts/Refunds")] == "30.00", r # 35.00 - 5.00 @case def unmapped_payer_gets_a_blank_group(): assert _build()[3][_col("Group Name")] == "" @case def tracking_columns_fill_only_for_claims_the_run_read(): outcomes = {"1000001": cs.claim_outcome(True, _ub())} rows = _build(outcomes) read, unread = rows[1], rows[2] assert read[_col("Category")] == "Pass" assert read[_col("Completed On")] == "10/01/2026" assert read[_col("Recevied date")] == "10/01/2026" assert read[_col("Assigned To ")] == "eCW Bot" for h in ("Recevied date", "Assigned To ", "Completed On", "Comments", "Current Status", "Category"): assert unread[_col(h)] == "", (h, unread) @case def a_claim_id_only_test_sheet_still_builds(): rows = cs.build_rows([["Claim No"], ["9000002"], ["9000001"]], 0, 0, {}, "10/01/2026", "eCW Bot", MAPPING) assert [r[_col("CLAIMS#")] for r in rows[1:]] == ["9000002", "9000001"] assert rows[1][_col("PAYER")] == "" and rows[1][_col("Pmts/Refunds")] == "" # ── write ──────────────────────────────────────────────────────────────────── @case def write_saves_a_readable_sheet_and_keeps_a_same_day_file_aside(): with tempfile.TemporaryDirectory() as tmp: tmp = pathlib.Path(tmp) report = tmp / "report.xlsx" spreadsheet.write_xlsx(report, REPORT) out = cs.write(report, {}, tmp, "10-01-2026", "eCW Bot") assert out.name == "Charge Summary_10-01-2026.xlsx" back = spreadsheet.read_rows(out) # read_rows strips cells, so the format's "Assigned To " reads back # without its trailing space. assert back[0] == [h.strip() for h in cs.HEADERS], back[0] assert len(back) == 4, back cs.write(report, {}, tmp, "10-01-2026", "eCW Bot") kept = list(tmp.glob("Charge Summary_10-01-2026__*.xlsx")) assert len(kept) == 1, kept @case def file_name_is_live_or_TEST(): assert cs.file_name("10-01-2026") == "Charge Summary_10-01-2026.xlsx" assert cs.file_name("10-01-2026", test_mode=True) == \ "Charge Summary_TEST_10-01-2026.xlsx" @case def test_mode_writes_the_TEST_file_and_leaves_the_live_one_alone(): """D1: a test run must never push the live same-day summary aside.""" with tempfile.TemporaryDirectory() as tmp: tmp = pathlib.Path(tmp) report = tmp / "report.xlsx" spreadsheet.write_xlsx(report, REPORT) live = cs.write(report, {}, tmp, "10-01-2026", "eCW Bot") live_bytes = live.read_bytes() out = cs.write(report, {}, tmp, "10-01-2026", "eCW Bot", test_mode=True) assert out == tmp / "Charge Summary_TEST_10-01-2026.xlsx", out assert len(spreadsheet.read_rows(out)) == 4 assert live.read_bytes() == live_bytes, "the live summary changed" assert not list(tmp.glob("Charge Summary_10-01-2026__*.xlsx")), \ "the live summary was pushed aside by a test run" @case def a_same_day_summary_open_in_excel_does_not_lose_this_runs_sheet(): """ D10: the earlier summary is held open (on Windows a Python handle refuses the rename with WinError 32, as Excel does). The new sheet goes to a `__HHMMSS` name, the open file is untouched, nothing raises, and the path returned is the new file — the one the flow logs. """ with tempfile.TemporaryDirectory() as tmp: tmp = pathlib.Path(tmp) report = tmp / "report.xlsx" spreadsheet.write_xlsx(report, REPORT) first = cs.write(report, {}, tmp, "10-01-2026", "eCW Bot") first_bytes = first.read_bytes() outcomes = {"1000001": cs.claim_outcome(True, _ub())} with open(first, "rb"): out = cs.write(report, outcomes, tmp, "10-01-2026", "eCW Bot") assert out != first, "wrote over the open file's name" assert re.fullmatch(r"Charge Summary_10-01-2026__\d{6}\.xlsx", out.name), out.name assert first.read_bytes() == first_bytes, "the open file was changed" back = spreadsheet.read_rows(out) assert len(back) == 4, back assert back[1][_col("Category")] == "Pass", back[1] FALLBACK_NAME = "Charge Summary_10-01-2026__101010.xlsx" def _flow_write(test_file, returns=FALLBACK_NAME): """ _write_charge_summary with charge_summary.write replaced by a recorder that returns `returns`, and msgbox.info_box by one that records instead of popping a real Windows box. Returns (args, kwargs it was called with, printed text, [(title, body)] of boxes shown). """ calls = [] shown = [] def _record(*args, **kwargs): calls.append((args, kwargs)) # By default, as if the usual name was open in Excel: the log must # name THIS, and the operator must be told. return pathlib.Path(returns) orig = (cs.write, cfg.WRITE_CHARGE_SUMMARY, cfg.TEST_CLAIM_IDS_FILE, flow.msgbox.info_box) printed = io.StringIO() try: cs.write = _record cfg.WRITE_CHARGE_SUMMARY = True cfg.TEST_CLAIM_IDS_FILE = test_file flow.msgbox.info_box = lambda t, b: shown.append((t, b)) with contextlib.redirect_stdout(printed): flow._write_charge_summary({"file": pathlib.Path("r.xlsx")}, {"1": {"Category": "Pass"}}) finally: (cs.write, cfg.WRITE_CHARGE_SUMMARY, cfg.TEST_CLAIM_IDS_FILE, flow.msgbox.info_box) = orig assert len(calls) == 1, calls return calls[0][0], calls[0][1], printed.getvalue(), shown @case def flow_passes_test_mode_when_the_ids_came_from_a_test_sheet(): _, kwargs, _, _ = _flow_write("C:/somewhere/Standard Claims.xlsx") assert kwargs.get("test_mode") is True, kwargs @case def flow_passes_test_mode_off_for_a_live_run(): _, kwargs, _, _ = _flow_write("") assert kwargs.get("test_mode") is False, kwargs @case def flow_logs_the_file_actually_written(): _, _, printed, _ = _flow_write("") line = "[SUMMARY] 1 claim(s) filled in -> " + FALLBACK_NAME assert line in printed, printed @case def flow_shows_a_box_when_the_summary_went_to_another_name(): """ Fix round 1 (review item D10, 2026-10-05): the person with the usual file open in Excel is at the desk, so the fallback name goes in a box, not only the log — else they present the stale sheet they have open. """ _, _, _, shown = _flow_write("") assert len(shown) == 1, shown title, body = shown[0] assert title == "eCW Bot — Charge summary saved under another name", title usual = cs.file_name(cfg.TODAY) assert f"could not be saved as {usual}" in body, body assert FALLBACK_NAME in body, body # Fix round 2: the box must not promise the earlier file was untouched — # a failed move-back can leave a half-file under the usual name. assert "left as it was" not in body, body @case def flow_shows_no_box_when_the_summary_has_its_usual_name(): _, _, _, shown = _flow_write("", returns=cs.file_name(cfg.TODAY)) assert shown == [], shown @case def flow_shows_no_box_for_a_test_run_under_the_TEST_name(): _, _, _, shown = _flow_write("C:/somewhere/Standard Claims.xlsx", returns=cs.file_name(cfg.TODAY, True)) assert shown == [], shown @case def flow_write_never_raises_on_a_bad_report(): shown = [] old = flow.msgbox.info_box flow.msgbox.info_box = lambda t, b: shown.append(t) try: flow._write_charge_summary({"file": pathlib.Path("no_such_file.xlsx")}, {}) finally: flow.msgbox.info_box = old assert shown, "a failed write should show a box, not raise" # ── per-claim writes (2026-10-12: the summary grows claim by claim) ───────── @case def the_report_is_read_once_while_it_is_unchanged(): """Built after every claim, twice (Excel + DB): one read, not 2 x N.""" with tempfile.TemporaryDirectory() as tmp: report = pathlib.Path(tmp) / "report.xlsx" spreadsheet.write_xlsx(report, REPORT) reads = [] real = spreadsheet.read_rows spreadsheet.read_rows = lambda p: reads.append(p) or real(p) try: first = cs.build(report, {}, "eCW Bot") cs.build(report, {"1000001": cs.claim_outcome(True, _ub())}, "eCW Bot") assert len(reads) == 1, reads spreadsheet.write_xlsx(report, REPORT[:2]) again = cs.build(report, {}, "eCW Bot") finally: spreadsheet.read_rows = real assert len(reads) == 2, "a changed report must be read again" assert len(first) == 4 and len(again) == 2, (first, again) @case def write_into_rewrites_this_runs_own_file_with_no_backup(): """ After the first claim, the sheet goes back into the file that claim wrote — no `__HHMMSS` copy per claim; only the first write keeps an earlier same-day file aside. """ with tempfile.TemporaryDirectory() as tmp: tmp = pathlib.Path(tmp) report = tmp / "report.xlsx" spreadsheet.write_xlsx(report, REPORT) first = cs.write(report, {"1000001": cs.claim_outcome(True, _ub())}, tmp, "10-01-2026", "eCW Bot") out = cs.write(report, {"1000001": cs.claim_outcome(True, _ub()), "1000002": cs.claim_outcome(True, _ub())}, tmp, "10-01-2026", "eCW Bot", into=first) assert out == first, out assert not list(tmp.glob("Charge Summary_10-01-2026__*.xlsx")), \ "a backup was made for this run's own file" back = spreadsheet.read_rows(out) assert back[1][_col("Category")] == "Pass", back[1] assert back[2][_col("Category")] == "Pass", back[2] @case def write_into_a_locked_file_goes_to_a_new_name(): """The operator opened the in-progress summary in Excel mid-run.""" with tempfile.TemporaryDirectory() as tmp: tmp = pathlib.Path(tmp) report = tmp / "report.xlsx" spreadsheet.write_xlsx(report, REPORT) first = cs.write(report, {}, tmp, "10-01-2026", "eCW Bot") first_bytes = first.read_bytes() real = spreadsheet.write_xlsx def locked(path, rows): if pathlib.Path(path) == first: raise PermissionError(13, "Permission denied") return real(path, rows) spreadsheet.write_xlsx = locked try: with contextlib.redirect_stdout(io.StringIO()) as printed: out = cs.write(report, {"1000001": cs.claim_outcome(True, _ub())}, tmp, "10-01-2026", "eCW Bot", into=first) finally: spreadsheet.write_xlsx = real assert re.fullmatch(r"Charge Summary_10-01-2026__\d{6}\.xlsx", out.name), out.name assert first.read_bytes() == first_bytes, "the locked file changed" assert spreadsheet.read_rows(out)[1][_col("Category")] == "Pass" assert "open or locked" in printed.getvalue(), printed.getvalue() def _flow_write_twice(first_returns, second_returns): """_write_charge_summary called for two claims of one run.""" calls, shown = [], [] returns = [pathlib.Path(first_returns), pathlib.Path(second_returns)] def _record(*args, **kwargs): calls.append(kwargs) return returns[len(calls) - 1] orig = (cs.write, cfg.WRITE_CHARGE_SUMMARY, cfg.TEST_CLAIM_IDS_FILE, flow.msgbox.info_box) info = {"file": pathlib.Path("r.xlsx")} try: cs.write = _record cfg.WRITE_CHARGE_SUMMARY = True cfg.TEST_CLAIM_IDS_FILE = "" flow.msgbox.info_box = lambda t, b: shown.append((t, b)) with contextlib.redirect_stdout(io.StringIO()): flow._write_charge_summary(info, {"1": {"Category": "Pass"}}) flow._write_charge_summary(info, {"1": {"Category": "Pass"}, "2": {"Category": "Fail"}}) finally: (cs.write, cfg.WRITE_CHARGE_SUMMARY, cfg.TEST_CLAIM_IDS_FILE, flow.msgbox.info_box) = orig return calls, shown @case def flow_second_claim_writes_into_the_file_the_first_one_wrote(): usual = cs.file_name(cfg.TODAY) calls, shown = _flow_write_twice(usual, usual) assert calls[0].get("into") is None, calls assert calls[1].get("into") == pathlib.Path(usual), calls assert shown == [], shown @case def flow_shows_the_another_name_box_once_not_per_claim(): calls, shown = _flow_write_twice(FALLBACK_NAME, FALLBACK_NAME) assert calls[1].get("into") == pathlib.Path(FALLBACK_NAME), calls assert len(shown) == 1, shown @case def flow_shows_the_box_again_when_the_run_moves_to_a_new_name(): """Mid-run, the operator opened this run's file: the sheet moves on.""" usual = cs.file_name(cfg.TODAY) _, shown = _flow_write_twice(usual, FALLBACK_NAME) assert len(shown) == 1 and FALLBACK_NAME in shown[0][1], shown @case def flow_write_error_box_shows_once_per_run(): shown = [] old = flow.msgbox.info_box flow.msgbox.info_box = lambda t, b: shown.append(t) info = {"file": pathlib.Path("no_such_file.xlsx")} try: with contextlib.redirect_stdout(io.StringIO()): flow._write_charge_summary(info, {}) flow._write_charge_summary(info, {}) finally: flow.msgbox.info_box = old assert len(shown) == 1, shown def main() -> int: failures = 0 for fn in CASES: name = fn.__name__.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())