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>
307 lines
11 KiB
Python
307 lines
11 KiB
Python
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())
|