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>
331 lines
12 KiB
Python
331 lines
12 KiB
Python
r"""
|
|
conditions/medicare_vaccine_a6.py — document #19: a Medicare claim billing
|
|
G0008 or G0009 carries condition code A6 (operator, 2026-10-16: those two
|
|
codes only, not #18's vaccine list; the bot does not add G0008 / G0009).
|
|
|
|
python tests\test_conditions_medicare_vaccine_a6.py
|
|
|
|
Uses tests/fixtures/ub_claim.html — its Condition Codes boxes (FL 18-28)
|
|
are uBClaimCondtnCode1..11 as on the real form, set per case with
|
|
setConditionCodes.
|
|
"""
|
|
|
|
import io
|
|
import sys
|
|
import pathlib
|
|
import inspect
|
|
import contextlib
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
|
|
FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "ub_claim.html"
|
|
HCFA_FIXTURE = (pathlib.Path(__file__).resolve().parent / "fixtures"
|
|
/ "hcfa_claim.html")
|
|
|
|
from ecw_bot.conditions import medicare_vaccine_a6 as cond # noqa: E402
|
|
from ecw_bot.conditions import CONDITIONS # noqa: E402
|
|
from ecw_bot.conditions.base import (CPT_TAB_NOT_CONFIRMED, # noqa: E402
|
|
ConditionResult, run_all)
|
|
from ecw_bot.core import charge_summary as cs # noqa: E402
|
|
from ecw_bot.pages import claim_form # noqa: E402
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
def _quiet(fn, *a, **k):
|
|
with contextlib.redirect_stdout(io.StringIO()) as out:
|
|
result = fn(*a, **k)
|
|
return result, out.getvalue()
|
|
|
|
|
|
MEDICARE = "HCFA Medicare"
|
|
|
|
|
|
def _row(code, billed=True, fee="40.00"):
|
|
return {"code": code, "billed": billed, "billed_fee": fee, "units": "1"}
|
|
|
|
|
|
def _data(cpt, payer=MEDICARE, screen="ub", codes=None):
|
|
return {"screen": screen,
|
|
"insurance": [{"Name": payer}] if payer is not None else [],
|
|
"cpt": cpt,
|
|
"condition_codes": ([""] * 11 if codes is None else codes)
|
|
if screen == "ub" else None}
|
|
|
|
|
|
# ── detect / needs_review (no browser) ───────────────────────────────────────
|
|
|
|
@case
|
|
def g0008_or_g0009_on_medicare_without_a6_fires():
|
|
for code in ("G0008", "G0009", " g0009 "):
|
|
assert cond.detect("C1", _data([_row("99213"), _row(code)])), code
|
|
|
|
|
|
@case
|
|
def a6_already_there_does_not_fire():
|
|
codes = ["", "", "a6"] + [""] * 8
|
|
data = _data([_row("G0008")], codes=codes)
|
|
assert cond.detect("C1", data) is False
|
|
assert cond.needs_review("C1", data) is None
|
|
|
|
|
|
@case
|
|
def not_medicare_or_no_trigger_does_not_fire():
|
|
for data in (_data([_row("G0008")], payer="BH COLORADO ACCESS"),
|
|
_data([_row("G0008")], payer=""),
|
|
_data([_row("90656"), _row("90732"), _row("G0010")]),
|
|
_data([_row("G0008", billed=False, fee="0.00")]),
|
|
_data([_row("G0009", fee="0.00")]),
|
|
_data([_row("G0009", billed=None, fee="0.00")])):
|
|
assert cond.detect("C1", data) is False, data
|
|
assert cond.needs_review("C1", data) is None, data
|
|
|
|
|
|
@case
|
|
def an_unchecked_priced_trigger_counts_priced_line_check_checks_it():
|
|
"""Review, 2026-10-19: priced_line_check, last, checks an unchecked
|
|
G0008 with a fee — so it is billed, and needs A6 like a checked one."""
|
|
data = _data([_row("99213"), _row("G0008", billed=False, fee="40.00")])
|
|
assert cond.detect("C1", data) is True
|
|
assert cond.billed_trigger_codes(data["cpt"]) == ["G0008"]
|
|
|
|
|
|
@case
|
|
def a_trigger_billed_twice_is_named_once():
|
|
data = _data([_row("G0009"), _row("G0009")], screen="hcfa")
|
|
assert cond.needs_review("C1", data).startswith("G0009 on Medicare")
|
|
|
|
|
|
@case
|
|
def hcfa_or_dental_medicare_claim_is_review_by_hand():
|
|
for screen in ("hcfa", "dental"):
|
|
data = _data([_row("G0009"), _row("G0008")], screen=screen)
|
|
assert cond.detect("C1", data) is False, screen
|
|
why = cond.needs_review("C1", data)
|
|
assert why and "A6 by hand" in why and "G0009, G0008" in why, why
|
|
|
|
|
|
@case
|
|
def a_ub_read_without_condition_codes_is_review():
|
|
data = _data([_row("G0008")])
|
|
data["condition_codes"] = None
|
|
assert cond.detect("C1", data) is False
|
|
assert "A6 by hand" in cond.needs_review("C1", data)
|
|
|
|
|
|
# ── apply (browser) ──────────────────────────────────────────────────────────
|
|
|
|
def _fresh(page, codes=None):
|
|
page.goto(FIXTURE.as_uri())
|
|
if codes is not None:
|
|
page.evaluate("c => window.setConditionCodes(c)", codes)
|
|
|
|
|
|
def _codes(page):
|
|
return claim_form.extract(page)["condition_codes"]
|
|
|
|
|
|
@case
|
|
def extract_reads_the_eleven_boxes(page):
|
|
_fresh(page, ["09", "", "40"])
|
|
assert _codes(page) == ["09", "", "40"] + [""] * 8
|
|
|
|
|
|
@case
|
|
def apply_types_a6_into_the_first_empty_box(page):
|
|
_fresh(page, ["09", "", "40"])
|
|
result, out = _quiet(cond.apply, page, "C1", _data([_row("G0008")]))
|
|
assert result.changed is True and result.review == "", (result, out)
|
|
assert _codes(page) == ["09", "A6", "40"] + [""] * 8
|
|
c = result.changes[0]
|
|
assert c["box"] == 2 and c["fl"] == 19, c
|
|
assert cond.summary_comment(result) == \
|
|
"Condition code A6 added (G0008 on Medicare)"
|
|
assert "FL 19" in cond.describe(result)
|
|
|
|
|
|
@case
|
|
def a_hidden_ub_form_left_in_the_dom_is_not_read(page):
|
|
"""eCW leaves closed forms in the DOM: a hidden UB form must not answer
|
|
for the claim on screen (review, 2026-10-19)."""
|
|
_fresh(page, ["A6"])
|
|
page.evaluate("() => { document.getElementById("
|
|
"'UBClaimDiv9999999999999').style.display = 'none'; }")
|
|
assert _codes(page) is None
|
|
data = _data([_row("G0008")])
|
|
data["condition_codes"] = None
|
|
assert "A6 by hand" in cond.needs_review("C1", data)
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0008")]))
|
|
assert result.changed is False
|
|
assert "Condition Codes boxes not found" in result.review, result
|
|
|
|
|
|
@case
|
|
def a_box_marked_invalid_is_changed_and_review(page):
|
|
"""Typed, but eCW's directive marks it invalid: the box IS touched, so
|
|
the result is changed (the review box shows it) AND Review."""
|
|
_fresh(page)
|
|
page.evaluate("() => { window.conditionCodeInvalid = true; }")
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0008")]))
|
|
assert result.changed is True, result
|
|
assert "marked it invalid" in result.review, result
|
|
assert "add A6 by hand" in result.review
|
|
assert result.changes[0]["box"] == 1
|
|
assert "NOT added" in cond.summary_comment(result)
|
|
assert "read back 'A6'" in cond.describe(result)
|
|
|
|
|
|
@case
|
|
def a_box_whose_angular_model_did_not_take_is_review(page):
|
|
"""The box shows A6 but its model (what OK saves) does not hold it."""
|
|
_fresh(page)
|
|
page.evaluate("""() => { window.angular = { element: () => ({
|
|
scope: () => ({ searchRecord: '' }),
|
|
isolateScope: () => ({ searchRecord: '' }) }) }; }""")
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0009")]))
|
|
assert result.changed is True and "model holds ''" in result.review, result
|
|
|
|
|
|
@case
|
|
def a_box_whose_angular_model_took_is_ok(page):
|
|
_fresh(page)
|
|
page.evaluate("""() => { window.angular = { element: el => ({
|
|
scope: () => ({ searchRecord: el.value }) }) }; }""")
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0009")]))
|
|
assert result.changed is True and result.review == "", result
|
|
|
|
|
|
@case
|
|
def apply_leaves_a6_already_there(page):
|
|
_fresh(page, ["09", "", "A6"])
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0009")]))
|
|
assert result == ConditionResult.none(cond.NAME), result
|
|
assert _codes(page) == ["09", "", "A6"] + [""] * 8
|
|
|
|
|
|
@case
|
|
def all_boxes_full_is_review_and_nothing_overwritten(page):
|
|
full = [f"{n:02d}" for n in range(1, 12)]
|
|
_fresh(page, full)
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0008")]))
|
|
assert result.changed is False
|
|
assert "all 11 Condition Code boxes are full" in result.review, result
|
|
assert "add A6 by hand" in result.review
|
|
assert _codes(page) == full
|
|
|
|
|
|
@case
|
|
def apply_declines_when_the_cpt_tab_cannot_open(page):
|
|
real = claim_form.open_cpt_hcpcs_tab
|
|
|
|
def raises(_page):
|
|
raise RuntimeError("simulated")
|
|
claim_form.open_cpt_hcpcs_tab = raises
|
|
try:
|
|
result, _ = _quiet(cond.apply, page, "C1", _data([_row("G0008")]))
|
|
finally:
|
|
claim_form.open_cpt_hcpcs_tab = real
|
|
assert result == ConditionResult.declined(cond.NAME, CPT_TAB_NOT_CONFIRMED)
|
|
|
|
|
|
@case
|
|
def run_all_adds_a6_and_the_summary_says_so(page):
|
|
_fresh(page)
|
|
page.evaluate("""() => {
|
|
document.getElementById('uBClaimCPTCode0').value = 'G0008';
|
|
document.querySelector('#UBClaimTbl3 [ng-bind="insurance.InsName"]')
|
|
.textContent = 'HCFA Medicare';
|
|
}""")
|
|
data = claim_form.extract(page)
|
|
assert data["screen"] == "ub" and data["condition_codes"] == [""] * 11
|
|
data["progress_notes"] = {"opened": "modal", "text": "Office visit."}
|
|
report = {}
|
|
results, out = _quiet(run_all, page, "C1", data, report=report)
|
|
assert "medicare_vaccine_a6" in [r.condition_name for r in results], out
|
|
assert _codes(page)[0] == "A6"
|
|
data["_conditions"] = {"fired": [r.condition_name for r in results],
|
|
"saved": False, "failed": report["failed"],
|
|
"review": report["review"],
|
|
"comments": report["comments"]}
|
|
outcome = cs.claim_outcome(True, data)
|
|
assert "Condition code A6 added (G0008 on Medicare)" in \
|
|
outcome["Comments"], outcome
|
|
|
|
|
|
@case
|
|
def hcfa_claim_read_through_run_all_is_review(page):
|
|
"""End to end on the HCFA fixture: no Condition Codes are read there,
|
|
so a Medicare claim billing G0008 is Review, nothing typed."""
|
|
page.goto(HCFA_FIXTURE.as_uri())
|
|
page.evaluate("""() => {
|
|
document.getElementById('claimCPTCode0').value = 'G0008';
|
|
document.querySelectorAll('[ng-bind="insurance.InsName"]')
|
|
.forEach(e => { e.textContent = 'HCFA Medicare'; });
|
|
}""")
|
|
data = claim_form.extract(page)
|
|
assert data["condition_codes"] is None, data["screen"]
|
|
data["progress_notes"] = {"opened": "modal", "text": "Office visit."}
|
|
report = {}
|
|
results, out = _quiet(run_all, page, "C1", data, report=report)
|
|
assert "medicare_vaccine_a6" not in [r.condition_name for r in results]
|
|
reasons = " | ".join(str(r) for r in report["review"])
|
|
assert "check condition code A6 by hand" in reasons, report["review"]
|
|
|
|
|
|
@case
|
|
def it_is_registered_between_the_common_rule_halves():
|
|
names = [m.NAME for m in CONDITIONS]
|
|
i = names.index("medicare_vaccine_a6")
|
|
assert names.index("zero_fee_uncheck") < i < names.index(
|
|
"priced_line_check"), names
|
|
|
|
|
|
def _run_cases() -> int:
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
failures = 0
|
|
with sync_playwright() as pw:
|
|
browser = pw.chromium.launch(headless=True)
|
|
page = browser.new_page()
|
|
for fn in CASES:
|
|
name = fn.__name__.replace("_", " ")
|
|
try:
|
|
if len(inspect.signature(fn).parameters) == 0:
|
|
fn()
|
|
else:
|
|
fn(page)
|
|
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(CASES) - failures}/{len(CASES)} passed")
|
|
return 1 if failures else 0
|
|
|
|
|
|
def main() -> int:
|
|
"""shot() writes into the operator's Screenshots\\ folder; not here."""
|
|
import tempfile
|
|
from ecw_bot import settings as _cfg
|
|
orig = _cfg.SCREENSHOTS
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
_cfg.SCREENSHOTS = pathlib.Path(tmp)
|
|
try:
|
|
return _run_cases()
|
|
finally:
|
|
_cfg.SCREENSHOTS = orig
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|