Initial import: eCW Charge Validation bot and results Portal
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>
This commit is contained in:
commit
ba15a574ab
205 files changed
+56772
No files matched your search
@@ -0,0 +1,330 @@
|
||||
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())
|
||||
Reference in new issue
Block a user