Snapshot of the working project as of 2026-10-23 (local 2d4d4fc), one commit with no earlier history: the tracked files were de-identified first (synthetic claim ids and patient data only; see Context.md section 7). Credentials, run logs, screenshots, claim reports and client workbooks are gitignored and not included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
r"""
|
|
The Charge Summary grows claim by claim (operator, 2026-10-12): each claim's
|
|
row goes into the Excel summary and the results database as soon as that
|
|
claim is done, not once at the end of the run.
|
|
|
|
python tests\test_summary_per_claim.py
|
|
|
|
Drives the real `_process_claims` with the search, the form read and both
|
|
writers stubbed — no browser, no file, no database.
|
|
"""
|
|
|
|
import io
|
|
import sys
|
|
import pathlib
|
|
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 # noqa: E402
|
|
from ecw_bot.flows import export_medcor_ready as flow # noqa: E402
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
def _ub(fired=()):
|
|
return {"cpt": [{"code": "99213", "checked": True, "fee": "100.00"}],
|
|
"icd": [{"code": "Z00.00"}],
|
|
"progress_notes": {"opened": "modal", "text": "Office visit."},
|
|
"_conditions": {"fired": list(fired), "saved": False,
|
|
"failed": [], "review": [], "comments": {}}}
|
|
|
|
|
|
def _run(ids, found=None, forms=None):
|
|
"""
|
|
`_process_claims` over `ids`. `found` = ids the search finds (default
|
|
all), `forms` = claim id -> what the form read returns. Returns the
|
|
event list: ("search", id), ("excel", sorted outcome ids),
|
|
("db", outcomes, details).
|
|
"""
|
|
found = set(ids if found is None else found)
|
|
forms = forms or {}
|
|
events = []
|
|
|
|
def search(page, cid):
|
|
events.append(("search", cid))
|
|
return {"id": cid, "count": 1 if cid in found else 0,
|
|
"found": cid in found, "seconds": 1.0}
|
|
|
|
orig = (cfg.SHOW_EACH_CLAIM, cfg.SHOW_CLAIM_IDS, cfg.CLAIM_PROCESS_LIMIT,
|
|
flow.claim_results.search_claim, flow._read_claim_form,
|
|
flow.claim_filters.clear_claim_no, flow._write_charge_summary,
|
|
flow._save_results_db)
|
|
cfg.SHOW_EACH_CLAIM = False
|
|
cfg.SHOW_CLAIM_IDS = False
|
|
cfg.CLAIM_PROCESS_LIMIT = 0
|
|
flow.claim_results.search_claim = search
|
|
flow._read_claim_form = lambda page, cid, head: dict(
|
|
forms.get(cid) or _ub(), claim_no=cid)
|
|
flow.claim_filters.clear_claim_no = lambda page: None
|
|
flow._write_charge_summary = lambda info, outcomes: events.append(
|
|
("excel", sorted(outcomes)))
|
|
flow._save_results_db = lambda info, outcomes, details: events.append(
|
|
("db", dict(outcomes), dict(details)))
|
|
try:
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
flow._process_claims(None, {"ids": list(ids), "file": None})
|
|
finally:
|
|
(cfg.SHOW_EACH_CLAIM, cfg.SHOW_CLAIM_IDS, cfg.CLAIM_PROCESS_LIMIT,
|
|
flow.claim_results.search_claim, flow._read_claim_form,
|
|
flow.claim_filters.clear_claim_no, flow._write_charge_summary,
|
|
flow._save_results_db) = orig
|
|
return events
|
|
|
|
|
|
@case
|
|
def each_claim_is_written_to_the_excel_summary_before_the_next_starts():
|
|
events = _run(["111", "222"])
|
|
order = [e[:2] if e[0] != "db" else ("db",) for e in events]
|
|
assert order == [("search", "111"), ("excel", ["111"]), ("db",),
|
|
("search", "222"), ("excel", ["111", "222"]), ("db",)], \
|
|
order
|
|
|
|
|
|
@case
|
|
def each_claim_saves_only_its_own_row_to_the_database():
|
|
events = _run(["111", "222"],
|
|
forms={"222": _ub(["depression_dx_pointer"])})
|
|
db = [e for e in events if e[0] == "db"]
|
|
assert [sorted(e[1]) for e in db] == [["111"], ["222"]], db
|
|
assert db[0][1]["111"]["Comments"] == cs.CLEAN, db[0]
|
|
assert db[1][1]["222"]["Category"] == "Fail", db[1]
|
|
assert "ICD pointer issue" in db[1][1]["222"]["Comments"], db[1]
|
|
assert sorted(db[1][2]) == ["222"], db[1]
|
|
# The details are what the conditions reported, never the claim's own
|
|
# fields (review, 2026-10-12).
|
|
detail = db[1][2]["222"]
|
|
assert detail["fired"] == ["depression_dx_pointer"], detail
|
|
assert "cpt" not in detail and "progress_notes" not in detail, detail
|
|
|
|
|
|
@case
|
|
def a_claim_not_found_updates_the_excel_but_saves_nothing_to_the_database():
|
|
events = _run(["111", "222"], found=["222"])
|
|
assert ("excel", ["111"]) in events, events
|
|
db = [e for e in events if e[0] == "db"]
|
|
assert [sorted(e[1]) for e in db] == [["222"]], db
|
|
|
|
|
|
@case
|
|
def nothing_is_written_again_after_the_last_claim():
|
|
events = _run(["111"])
|
|
assert [e[0] for e in events] == ["search", "excel", "db"], events
|
|
|
|
|
|
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())
|