r""" Progress Note extraction, against a real browser. python tests\test_progress_note.py Local headless chromium against `tests\fixtures\progress_note_host.html`. Never touches eCW: no proxy, no login, no 2FA mail, no attempt against the guard. The fixture puts the note in an **iframe** with Close on the parent page, because that is how it actually arrives — the note is a whole printable document with its own ``. That split is the trap being tested: * reading the modal's `innerText` finds nothing, since iframe content is not part of it — which an earlier version of `open_progress_notes` did; * looking for Close inside the note finds nothing either, since it lives on the parent. The rest is about eCW's own labels. Every section carries `prisma-section="..."`, so the note comes back as a dict keyed by those rather than as prose — but `"Footer"` repeats four times, and a naive dict build drops the signature block. That is covered. """ import sys import pathlib ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) HOST = pathlib.Path(__file__).resolve().parent / "fixtures" / "progress_note_host.html" from ecw_bot.pages import progress_note # noqa: E402 from ecw_bot.selectors import claim_form as sel # noqa: E402 CHECKS = [] def check(fn): CHECKS.append(fn) return fn @check def found_in_the_iframe_not_the_modal(page, note, where): """The note is a separate document; the frame walk is what finds it.""" assert where == "frame", where # And the trap: the hosting modal's own innerText holds none of the note. modal_text = page.evaluate( """() => { const m = document.querySelector('.modal'); return m ? (m.innerText || '') : ''; }""") assert "R69" not in modal_text, ( "the modal's innerText contains the note, so this fixture no longer " "reproduces the iframe case it exists for") @check def patient_header_and_dos(page, note, where): hdr = note["patient_header"] assert "SAMPLE, Pat Q" in hdr, hdr assert "900123" in hdr, hdr assert note["dos"] == "06/29/2026", note["dos"] @check def sections_are_keyed_by_ecw_labels(page, note, where): got = note["sections"] for want in ("Header", "Past Medical History", "Surgical History", "Family History", "Social History", "Hospitalization/Major Diagnostic Procedure", "Reason for Appointment", "History of Present Illness", "Assessments", "Labs", "Procedure Codes", "Care Plan Details", "Footer", "Active Problem List"): assert want in got, f"{want!r} missing; got {sorted(got)}" assert "Generalized Anxiety Disorder" in got["Past Medical History"], \ got["Past Medical History"] assert "BH Outreach" in got["Reason for Appointment"], \ got["Reason for Appointment"] # Present but empty on this note, exactly as eCW served it. assert got["Active Problem List"] == "", repr(got["Active Problem List"]) @check def repeated_footer_sections_are_joined_not_overwritten(page, note, where): """ "Footer" appears four times. Overwriting on each would keep only the last — the facility block — and silently lose the signature and the sign-off. """ footer = note["sections"]["Footer"] assert "Electronically signed by Provider" in footer, footer assert "Sign off status" in footer, footer assert "Brighton Womens Clinic" in footer, footer @check def assessments_are_parsed_with_their_codes(page, note, where): a = note["assessments"] assert len(a) == 2, a assert a[0]["n"] == "1" and a[0]["code"] == "R69", a[0] assert a[0]["primary"] is True, a[0] assert "Illness, unspecified" in a[0]["text"], a[0] assert a[1]["code"] == "F41.1", a[1] assert a[1]["primary"] is False, a[1] @check def procedure_codes_carry_their_modifiers(page, note, where): """ The pair charge entry actually needs: the note's CPT and modifier are the same H0023 / GT the UB Claim form shows. """ pr = note["procedure_codes"] assert len(pr) == 2, pr assert pr[0]["code"] == "H0023", pr[0] assert pr[0]["modifiers"] == ["GT"], pr[0] assert "Behavioral Health Outreach Service" in pr[0]["description"], pr[0] # The description must not swallow the "Modifiers: ..." tail. assert "Modifiers" not in pr[0]["description"], pr[0] assert pr[1]["code"] == "90791" and pr[1]["modifiers"] == [], pr[1] @check def signoff_is_extracted(page, note, where): assert "Morgan Lee Taylor" in note["signed_by"], note["signed_by"] assert "07/13/2026" in note["signed_at"], note["signed_at"] assert note["signoff_status"] == "Completed", note["signoff_status"] @check def chart_locked_needs_textcontent(page, note, where): """ `#chartLockedId` is `display:none`. innerText returns "" for it, so a reader using innerText alone reports the chart as unlocked — the opposite of the truth, on a field that decides whether a claim should be touched. """ assert note["chart_locked"] is True, note["chart_locked"] @check def summary_names_the_facts_that_matter(page, note, where): text = progress_note.summary(note) for want in ("06/29/2026", "R69", "H0023", "GT", "Completed", "Morgan Lee Taylor"): assert want in text, f"{want!r} missing from summary:\n{text}" @check def close_clicks_the_parent_not_the_note(page, note, where): """Close lives on the host page; the ng-click typo is eCW's own.""" assert "closeProgressNoteViwer" in sel.SEL_PN_CLOSE, sel.SEL_PN_CLOSE assert progress_note.close(page) is True assert page.locator(".modal").first.is_visible() is False def main() -> int: if not HOST.exists(): print(f" FAIL fixture missing: {HOST}") return 1 try: from playwright.sync_api import sync_playwright except ImportError: print(" SKIP playwright is not installed") return 0 failures = 0 with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) # local only, no proxy page = browser.new_page() page.goto(HOST.as_uri()) page.wait_for_timeout(500) # let the iframe load reader, where = progress_note.find(page) if reader is None: print(" FAIL the note document was not found at all") browser.close() return 1 note = progress_note.extract(reader) for fn in CHECKS: name = fn.__name__.replace("_", " ") try: fn(page, note, where) 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 browser.close() print(f"\n{len(CHECKS) - failures}/{len(CHECKS)} passed") return 1 if failures else 0 if __name__ == "__main__": sys.exit(main())