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,306 @@
|
||||
r"""
|
||||
conditions/injection_admin_code.py — document #20: an injection code
|
||||
billed without 96372 gets 96372 added, Units 1, on every payer except
|
||||
Medicare (operator, 2026-10-13: the client's 41-code list as given; 96372
|
||||
is the only admin code; always Units 1).
|
||||
|
||||
python tests\test_conditions_injection_admin_code.py
|
||||
|
||||
Uses tests/fixtures/vaccine_claim.html — its Fee Schedule picker lists
|
||||
96372 — reshaped per case with `seedCptRows`.
|
||||
"""
|
||||
|
||||
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" / "vaccine_claim.html"
|
||||
|
||||
from ecw_bot.conditions import injection_admin_code as inj # noqa: E402
|
||||
from ecw_bot.conditions import CONDITIONS # noqa: E402
|
||||
from ecw_bot.conditions.base import ConditionResult, run_all # noqa: E402
|
||||
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()
|
||||
|
||||
|
||||
def _row(code, billed=True, fee="40.00"):
|
||||
return {"code": code, "billed": billed, "billed_fee": fee, "units": "1"}
|
||||
|
||||
|
||||
def _data(cpt, payer="Medicaid"):
|
||||
return {"cpt": cpt,
|
||||
"insurance": [{"Name": payer}] if payer is not None else []}
|
||||
|
||||
|
||||
_CLIENT_LIST = (
|
||||
"J0561 J0696 J1000 J1010 J1020 J1030 J1050 J1055 J1100 J1200 J1815 "
|
||||
"J1885 J2001 J2003 J2004 J2315 J2360 J2405 J2550 J2788 J2919 J2920 "
|
||||
"J2930 J3030 J3140 J3301 J3420 J3490 J7296 J7297 J7298 J7300 J7301 "
|
||||
"J7307 J7512 J7609 J7620 J7621 J7633 J7644 J8499").split()
|
||||
|
||||
|
||||
# ── recognition (no browser) ─────────────────────────────────────────────────
|
||||
|
||||
@case
|
||||
def the_list_is_the_clients_41_codes():
|
||||
assert len(_CLIENT_LIST) == 41
|
||||
assert inj.INJECTION_CODES == frozenset(_CLIENT_LIST)
|
||||
|
||||
|
||||
@case
|
||||
def every_listed_code_but_j2001_and_j2930_is_an_injection_row():
|
||||
for code in _CLIENT_LIST:
|
||||
expected = code not in ("J2001", "J2930")
|
||||
assert inj.is_injection_row(_row(code)) is expected, code
|
||||
assert inj.is_injection_row(_row(" j1885 ")) is True, "case and spaces"
|
||||
for code in ("96372", "90734", "99213", "J9999"):
|
||||
assert inj.is_injection_row(_row(code)) is False, code
|
||||
|
||||
|
||||
@case
|
||||
def only_a_96372_the_claim_will_bill_counts_as_admin_billed():
|
||||
assert inj.admin_present([_row("96372")]) is True
|
||||
assert inj.admin_present([_row("96372", fee="0.00")]) is False, \
|
||||
"a checked 96372 at 0.00 is unchecked by zero_fee_uncheck first"
|
||||
assert inj.admin_present([_row("96372", billed=False, fee="0.00")]) \
|
||||
is False, "unchecked at 0.00 bills nothing"
|
||||
for other in ("96373", "96374", "90471"):
|
||||
assert inj.admin_present([_row(other)]) is False, other
|
||||
|
||||
|
||||
@case
|
||||
def an_unchecked_96372_with_a_fee_counts_it_is_checked_at_the_end():
|
||||
"""Review, 2026-10-14: priced_line_check (last) checks it, so a second
|
||||
96372 added here would bill 96372 twice."""
|
||||
row = _row("96372", billed=False, fee="25.00")
|
||||
assert inj.admin_present([row]) is True
|
||||
data = _data([_row("J1885"), row])
|
||||
(fired, out) = _quiet(inj.detect, "C1", data)
|
||||
assert fired is False, out
|
||||
assert "priced_line_check checks it" in out, out
|
||||
assert inj.needs_review("C1", data) is None
|
||||
# No payer read: still nothing to add, and no Review asking for one by
|
||||
# hand — the 96372 is there.
|
||||
assert inj.needs_review("C1", _data([_row("J1885"), row], payer="")) \
|
||||
is None
|
||||
|
||||
|
||||
# ── detect / needs_review ───────────────────────────────────────────────────
|
||||
|
||||
@case
|
||||
def detect_an_injection_without_96372():
|
||||
assert inj.detect("C1", _data([_row("99213"), _row("J1885")])) is True
|
||||
|
||||
|
||||
@case
|
||||
def detect_nothing_when_96372_is_billed():
|
||||
data = _data([_row("J1885"), _row("96372")])
|
||||
(fired, _) = _quiet(inj.detect, "C1", data)
|
||||
assert fired is False
|
||||
assert inj.needs_review("C1", data) is None
|
||||
|
||||
|
||||
@case
|
||||
def an_unchecked_or_zero_fee_injection_is_not_billed():
|
||||
assert inj.detect("C1", _data([_row("J1885", billed=False)])) is False
|
||||
assert inj.detect("C1", _data([_row("J1885", fee="0.00")])) is False
|
||||
|
||||
|
||||
@case
|
||||
def j2001_or_j2930_alone_never_needs_96372():
|
||||
for code in ("J2001", "J2930"):
|
||||
data = _data([_row(code)])
|
||||
assert inj.detect("C1", data) is False, code
|
||||
assert inj.needs_review("C1", data) is None, code
|
||||
|
||||
|
||||
@case
|
||||
def medicare_is_left_alone_and_not_review():
|
||||
data = _data([_row("J1885")], payer="Medicare")
|
||||
(fired, _) = _quiet(inj.detect, "C1", data)
|
||||
assert fired is False
|
||||
assert inj.needs_review("C1", data) is None
|
||||
|
||||
|
||||
@case
|
||||
def an_unmapped_or_unread_payer_is_review():
|
||||
for payer, words in (("NO SUCH PAYER XYZ", "cannot classify"),
|
||||
("", "no primary payer name")):
|
||||
data = _data([_row("J1885")], payer=payer)
|
||||
(fired, _) = _quiet(inj.detect, "C1", data)
|
||||
assert fired is False, payer
|
||||
why = inj.needs_review("C1", data)
|
||||
assert why and words in why and "96372" in why, why
|
||||
|
||||
|
||||
# ── apply (browser) ─────────────────────────────────────────────────────────
|
||||
|
||||
def _fresh(page, rows):
|
||||
page.goto(FIXTURE.as_uri())
|
||||
page.evaluate("rows => window.seedCptRows(rows)", rows)
|
||||
|
||||
|
||||
def _rows_for(page, code):
|
||||
return [r for r in claim_form.extract(page)["cpt"] if r["code"] == code]
|
||||
|
||||
|
||||
_TWO_INJECTIONS = [
|
||||
{"code": "99213", "title": "OFFICE VISIT EST LEVEL 3", "fee": "120.00"},
|
||||
{"code": "J1885", "title": "Toradol (30mg) ketorolac", "fee": "15.00"},
|
||||
{"code": "J3420", "title": "INJ VIT B-12 CYNOCOBLMN TO 1000 MCG",
|
||||
"fee": "10.00"},
|
||||
]
|
||||
|
||||
|
||||
@case
|
||||
def apply_adds_96372_once_with_units_1_for_two_injections(page):
|
||||
_fresh(page, _TWO_INJECTIONS)
|
||||
data = claim_form.extract(page)
|
||||
result, out = _quiet(inj.apply, page, "C1", data)
|
||||
assert isinstance(result, ConditionResult)
|
||||
assert result.changed is True and result.review == "", (result, out)
|
||||
added = _rows_for(page, "96372")
|
||||
assert len(added) == 1, added
|
||||
assert added[0]["units"] == "1", added
|
||||
c = result.changes[0]
|
||||
assert c["code"] == "96372" and c["added"] is True, c
|
||||
assert c["injections"] == ["J1885", "J3420"], c
|
||||
assert "96372 added" in inj.summary_comment(result)
|
||||
text = inj.describe(result)
|
||||
assert "J1885, J3420" in text and "Added 96372" in text, text
|
||||
|
||||
|
||||
@case
|
||||
def apply_adds_nothing_when_96372_is_already_billed(page):
|
||||
_fresh(page, _TWO_INJECTIONS + [
|
||||
{"code": "96372", "title": "THER/PROPH/DIAG INJ SC/IM",
|
||||
"fee": "25.00"}])
|
||||
result, _ = _quiet(inj.apply, page, "C1", claim_form.extract(page))
|
||||
assert result.changed is False
|
||||
assert len(_rows_for(page, "96372")) == 1
|
||||
|
||||
|
||||
@case
|
||||
def a_96372_the_picker_cannot_find_is_review_add_by_hand(page):
|
||||
_fresh(page, _TWO_INJECTIONS)
|
||||
page.evaluate("() => window.removeFromFeeSchedule('96372')")
|
||||
result, _ = _quiet(inj.apply, page, "C1", claim_form.extract(page))
|
||||
assert result.changed is False, result
|
||||
assert "add it by hand" in result.review, result
|
||||
assert _rows_for(page, "96372") == []
|
||||
|
||||
|
||||
@case
|
||||
def run_all_adds_96372_and_the_summary_says_so(page):
|
||||
_fresh(page, _TWO_INJECTIONS)
|
||||
data = claim_form.extract(page)
|
||||
data["progress_notes"] = {"opened": "modal", "text": "Office visit."}
|
||||
report = {}
|
||||
results, _ = _quiet(run_all, page, "C1", data, report=report)
|
||||
assert "injection_admin_code" in [r.condition_name for r in results]
|
||||
assert report["review"] == [] and report["failed"] == [], report
|
||||
data["_conditions"] = {"fired": [r.condition_name for r in results],
|
||||
"saved": False, "failed": [], "review": [],
|
||||
"comments": report["comments"]}
|
||||
outcome = cs.claim_outcome(True, data)
|
||||
assert outcome["Category"] == "Fail", outcome
|
||||
assert "injection admin code 96372 added" in outcome["Comments"], outcome
|
||||
|
||||
|
||||
@case
|
||||
def run_all_bills_96372_once_when_it_sat_unchecked_with_a_fee(page):
|
||||
"""The duplicate the review found: J1885 checked, 96372 unchecked at
|
||||
25.00. Nothing is added, and priced_line_check checks the 96372 — one
|
||||
96372 billed, not two."""
|
||||
_fresh(page, [
|
||||
{"code": "99213", "title": "OFFICE VISIT EST LEVEL 3", "fee": "120.00"},
|
||||
{"code": "J1885", "title": "Toradol (30mg) ketorolac", "fee": "15.00"},
|
||||
{"code": "96372", "title": "THER/PROPH/DIAG INJ SC/IM",
|
||||
"fee": "25.00", "checked": False}])
|
||||
data = claim_form.extract(page)
|
||||
data["progress_notes"] = {"opened": "modal", "text": "Office visit."}
|
||||
report = {}
|
||||
results, out = _quiet(run_all, page, "C1", data, report=report)
|
||||
changed = [r.condition_name for r in results if r.changed]
|
||||
assert "injection_admin_code" not in changed, out
|
||||
assert "priced_line_check" in changed, out
|
||||
admin = _rows_for(page, "96372")
|
||||
assert len(admin) == 1, admin
|
||||
assert admin[0]["billed"] is True, admin
|
||||
assert report["review"] == [], report
|
||||
|
||||
|
||||
@case
|
||||
def it_is_registered_after_j2930_and_before_priced_line_check():
|
||||
names = [m.NAME for m in CONDITIONS]
|
||||
i = names.index("injection_admin_code")
|
||||
assert names.index("j2930_j2001_update") < i < names.index(
|
||||
"priced_line_check"), names
|
||||
assert inj.DENTAL_UNSUPPORTED
|
||||
|
||||
|
||||
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, _cfg.SAVE_CORRECTIONS)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
_cfg.SCREENSHOTS = pathlib.Path(tmp)
|
||||
# These cases exercise eCW's own click and Add flow, as a LIVE-WRITE
|
||||
# run does. VERIFY clicks no box and adds no row (eCW saves both at
|
||||
# once, 2026-10-16): that is tests/test_verify_no_live_writes.py.
|
||||
_cfg.SAVE_CORRECTIONS = True
|
||||
try:
|
||||
return _run_cases()
|
||||
finally:
|
||||
_cfg.SCREENSHOTS, _cfg.SAVE_CORRECTIONS = orig
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user