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>
1367 lines
56 KiB
Python
1367 lines
56 KiB
Python
r"""
|
|
conditions/vaccine_admin_codes.py — Charge Condition Document row 18:
|
|
vaccine codes billed without an immunization-admin code get the admin
|
|
code(s) added — 90460/90461 for age 18 and below, 90471/90472 above 18 —
|
|
with the "each additional" code's Units = vaccine count - 1.
|
|
|
|
python tests\test_conditions_vaccine_admin_codes.py
|
|
|
|
Pure-data cases first (detection, payer gate, age), then page cases
|
|
against fixtures/vaccine_claim.html, whose Fee Schedule picker is the same
|
|
simulation hcfa_claim.html uses for G2025 — `add_cpt_row()` is exercised for
|
|
real (click Add, search, pick the exact match, OK), never stubbed.
|
|
|
|
The three "no change" data cases at the end mirror live claims the client
|
|
had already fixed by hand (9131000, 9132000, 9133000): the condition must
|
|
leave every one of them alone.
|
|
|
|
Every checked vaccine or admin row a page case seeds carries its own fee.
|
|
The fixture's `seedCptRows` prices a row with no `fee` at 0.00, and since
|
|
the operator's ruling of 2026-09-30 a CHECKED line billed at zero is not
|
|
billed at all — zero_fee_uncheck unchecks it first — so it no longer counts
|
|
as a vaccine, or as "admin already billed". A case that meant a billed line
|
|
says so with a price; the cases about a zero-fee line say "0.00".
|
|
"""
|
|
|
|
import contextlib
|
|
import io
|
|
import sys
|
|
import pathlib
|
|
import inspect
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
|
|
FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "vaccine_claim.html"
|
|
UB_FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "ub_claim.html"
|
|
HCFA_FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "hcfa_claim.html"
|
|
|
|
from ecw_bot import conditions # noqa: E402
|
|
from ecw_bot.conditions import vaccine_admin_codes as vac # noqa: E402
|
|
from ecw_bot.conditions.base import ConditionResult, run_all # noqa: E402
|
|
from ecw_bot.pages import claim_form # noqa: E402
|
|
from ecw_bot.selectors import claim_form as sel # noqa: E402
|
|
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
# =============================================================================
|
|
# Data helpers
|
|
# =============================================================================
|
|
|
|
def _row(code, desc="", billed=True, sdos="07/07/2026", units="1"):
|
|
return {"code": code, "description": desc, "billed": billed,
|
|
"sdos": sdos, "units": units}
|
|
|
|
|
|
def _data(cpt, payer="Medicaid", dob="01/02/1976", age_sex=" 50Y, M",
|
|
claim_date="07/07/2026"):
|
|
return {
|
|
"cpt": cpt,
|
|
"insurance": [{"Name": payer}] if payer is not None else [],
|
|
"patient": {"dob": dob, "age_sex": age_sex},
|
|
"header": {"claim_date": claim_date},
|
|
}
|
|
|
|
|
|
def _age(data):
|
|
return vac.patient_age(data, vac.checked_vaccine_rows(data.get("cpt")))[0]
|
|
|
|
|
|
_TDAP = "TETANUS DIPHTHERIA TOXOIDS AND ACELLULAR PERTUSSIS VACC"
|
|
_ADMIN_1ST = "IMMUNIZATION ADMIN SINGLE OR COMBINATION"
|
|
_ADMIN_ADD = "IMMUNIZATION ADMIN, EACH ADD"
|
|
|
|
|
|
# =============================================================================
|
|
# Vaccine / admin row recognition
|
|
# =============================================================================
|
|
|
|
_CLIENT_VACCINES = ("90633", "90648", "90651", "90656", "90657", "90660",
|
|
"90680", "90687", "90698", "90707", "90710", "90713",
|
|
"90716", "90732", "90734", "90744", "90746", "91321",
|
|
"91322")
|
|
|
|
|
|
@case
|
|
def every_code_on_the_client_vaccine_list_is_a_vaccine():
|
|
"""Operator, 2026-10-13: the 19 codes of the client's list, by code."""
|
|
assert vac.VACCINE_CODES == frozenset(_CLIENT_VACCINES)
|
|
for code in _CLIENT_VACCINES:
|
|
assert vac.is_vaccine_row(_row(code)), code
|
|
|
|
|
|
@case
|
|
def a_listed_code_is_a_vaccine_whatever_its_hover_text():
|
|
assert vac.is_vaccine_row(_row("90732", "PCV20"))
|
|
assert vac.is_vaccine_row(_row("90746", ""))
|
|
|
|
|
|
@case
|
|
def a_code_off_the_list_is_not_a_vaccine_whatever_its_hover_text():
|
|
# The earlier rule took "VACC" in the hover text or 90476-90759; both
|
|
# are gone (operator, 2026-10-13) — the list is the whole rule.
|
|
for code, hover in (("90715", _TDAP), ("90677", "PCV20"),
|
|
("90750", "Shingrix"), ("90686", "FLU VACC"),
|
|
("90476", ""), ("90759", ""),
|
|
("Q2037", "FLU VACC FLUVIRIN 3 YRS & >"),
|
|
("99213", "Office Visit, Est Pt., Level 3")):
|
|
assert not vac.is_vaccine_row(_row(code, hover)), code
|
|
|
|
|
|
@case
|
|
def only_the_four_admin_codes_are_admin_rows():
|
|
for code in ("90460", "90461", "90471", "90472"):
|
|
assert vac.is_admin_row(_row(code)), code
|
|
assert not vac.is_vaccine_row(_row(code)), code
|
|
# Other administration codes, and "ADMIN" in the hover text, are not
|
|
# this condition's admin codes (operator, 2026-10-13).
|
|
for code, hover in (("90473", ""), ("90474", ""), ("90480", ""),
|
|
("G0008", ""), ("G0009", ""), ("G0010", ""),
|
|
("0002A", "IMMUNIZATION ADMIN COVID 2ND")):
|
|
assert not vac.is_admin_row(_row(code, hover)), code
|
|
|
|
|
|
@case
|
|
def a_covid_vaccine_with_90480_billed_still_gets_its_admin_code():
|
|
"""90480 is not one of the four admin codes, so it is not "admin
|
|
already billed": 91321 on an adult gets 90471 added."""
|
|
cpt = [_row("99396"), _row("91321"), _row("90480")]
|
|
assert [r["code"] for r in vac.checked_vaccine_rows(cpt)] == ["91321"]
|
|
assert vac.checked_admin_present(cpt) is False
|
|
assert vac.detect("C1", _data(cpt)) is True
|
|
|
|
|
|
@case
|
|
def flu_plus_covid_count_as_two_vaccines():
|
|
data = _data([_row("90656"), _row("91322"), _row("90480")])
|
|
assert vac._plan("C1", data, data["cpt"])["vaccines"] == ["90656", "91322"]
|
|
|
|
|
|
@case
|
|
def vaccine_count_is_rows_not_units():
|
|
# Ruling 4: one vaccine row at Units 3 is ONE vaccine -> 90471 x1 only.
|
|
data = _data([_row("99396"), _row("90734", _TDAP, units="3")])
|
|
assert vac._plan("C1", data, data["cpt"])["vaccines"] == ["90734"]
|
|
data = _data([_row("90734", _TDAP, units="2"),
|
|
_row("90732", "PCV20", units="1")])
|
|
assert len(vac._plan("C1", data, data["cpt"])["vaccines"]) == 2
|
|
|
|
|
|
@case
|
|
def unchecked_vaccine_is_not_counted():
|
|
cpt = [_row("99396"), _row("90734", _TDAP, billed=False)]
|
|
assert vac.checked_vaccine_rows(cpt) == []
|
|
assert vac.detect("C1", _data(cpt)) is False
|
|
|
|
|
|
@case
|
|
def unknown_checkbox_state_counts_as_checked():
|
|
# billed=None: the row has no "Assign To Patient" box at all; an absent
|
|
# key is a snapshot from before extract() read it. Neither may silently
|
|
# drop a vaccine.
|
|
none_row = _row("90734", _TDAP, billed=None)
|
|
missing = {"code": "90732", "description": "PCV20"}
|
|
assert vac.checked_vaccine_rows([none_row, missing]) == [none_row, missing]
|
|
|
|
|
|
# =============================================================================
|
|
# A checked line billed at zero — zero_fee_uncheck unchecks it first
|
|
# (operator ruling, 2026-09-30)
|
|
# =============================================================================
|
|
|
|
def _fee(row, fee):
|
|
"""`row` with a Billed Fee as extract() reads it."""
|
|
return dict(row, billed_fee=fee)
|
|
|
|
|
|
@case
|
|
def a_checked_zero_fee_admin_line_is_not_admin_already_billed():
|
|
# zero_fee_uncheck, registered first, unchecks it before this
|
|
# condition's apply() runs — counting it would leave the vaccine billed
|
|
# with no admin code (document #18's own case).
|
|
for fee in ("0.00", "", "0", "$0.00"):
|
|
cpt = [_fee(_row("90734", _TDAP), "60.00"),
|
|
_fee(_row("90471", _ADMIN_1ST), fee)]
|
|
assert vac.checked_admin_present(cpt) is False, fee
|
|
assert vac.detect("C1", _data(cpt)) is True, fee
|
|
plan = vac._plan("C1", _data(cpt), cpt)
|
|
assert (plan["first"], plan["vaccines"]) == ("90471", ["90734"]), (fee, plan)
|
|
|
|
|
|
@case
|
|
def a_checked_priced_admin_line_is_still_admin_already_billed():
|
|
for fee in ("30.00", "$30.00", "N/A"): # N/A: does not parse, not zero
|
|
cpt = [_fee(_row("90734", _TDAP), "60.00"),
|
|
_fee(_row("90471", _ADMIN_1ST), fee)]
|
|
assert vac.checked_admin_present(cpt) is True, fee
|
|
assert vac.detect("C1", _data(cpt)) is False, fee
|
|
|
|
|
|
@case
|
|
def a_zero_fee_admin_line_with_an_unknown_checkbox_still_counts():
|
|
# billed None keeps today's behaviour: zero_fee_uncheck never touches a
|
|
# row with no "Assign To Patient" checkbox, so it stays "billed".
|
|
cpt = [_fee(_row("90734", _TDAP), "60.00"),
|
|
_fee(_row("90471", _ADMIN_1ST, billed=None), "0.00")]
|
|
assert vac.checked_admin_present(cpt) is True
|
|
assert vac.detect("C1", _data(cpt)) is False
|
|
|
|
|
|
@case
|
|
def an_admin_line_whose_fee_was_never_read_still_counts():
|
|
# No billed_fee key at all (a snapshot from before extract() read fees,
|
|
# or a hand-built row): the fee is unknown, not zero — today's behaviour.
|
|
cpt = [_row("90734", _TDAP), _row("90471", _ADMIN_1ST)]
|
|
assert "billed_fee" not in cpt[1]
|
|
assert vac.checked_admin_present(cpt) is True
|
|
assert vac.detect("C1", _data(cpt)) is False
|
|
|
|
|
|
@case
|
|
def a_checked_zero_fee_vaccine_is_not_counted():
|
|
# The same ruling for a vaccine row: zero_fee_uncheck unchecks it, so it
|
|
# is not billed — one vaccine left, so 90471 alone, no 90472.
|
|
tdap = _fee(_row("90734", _TDAP), "0.00")
|
|
pcv = _fee(_row("90732", "PCV20"), "290.00")
|
|
assert vac.checked_vaccine_rows([tdap, pcv]) == [pcv]
|
|
plan = vac._plan("C1", _data([tdap, pcv]), [tdap, pcv])
|
|
assert plan["vaccines"] == ["90732"], plan
|
|
assert plan["first"] == "90471", plan
|
|
|
|
|
|
@case
|
|
def a_claim_whose_only_vaccine_is_checked_at_zero_fires_nothing():
|
|
cpt = [_fee(_row("99396"), "250.00"), _fee(_row("90734", _TDAP), "0.00")]
|
|
assert vac.checked_vaccine_rows(cpt) == []
|
|
assert vac.detect("C1", _data(cpt)) is False
|
|
|
|
|
|
@case
|
|
def a_zero_fee_vaccine_with_an_unknown_checkbox_still_counts():
|
|
none_row = _fee(_row("90734", _TDAP, billed=None), "0.00")
|
|
assert vac.checked_vaccine_rows([none_row]) == [none_row]
|
|
assert vac.detect("C1", _data([none_row])) is True
|
|
|
|
|
|
@case
|
|
def a_checked_admin_line_with_no_fee_input_still_counts():
|
|
# billed_fee None: extract() found NO Billed Fee input on the row
|
|
# (review finding, 2026-09-30). zero_fee_uncheck's apply() skips such a
|
|
# row (`uncheck_zero_fee_cpt_rows`: no fee input -> left alone), so it is
|
|
# still billed when this condition's apply() runs — it must count.
|
|
cpt = [_fee(_row("90734", _TDAP), "60.00"),
|
|
_fee(_row("90471", _ADMIN_1ST), None)]
|
|
assert vac.unchecked_by_zero_fee_rule(cpt[1]) is False
|
|
assert vac.checked_admin_present(cpt) is True
|
|
assert vac.detect("C1", _data(cpt)) is False
|
|
|
|
|
|
@case
|
|
def a_checked_vaccine_with_no_fee_input_still_counts():
|
|
tdap = _fee(_row("90734", _TDAP), None)
|
|
pcv = _fee(_row("90732", "PCV20"), "290.00")
|
|
assert vac.checked_vaccine_rows([tdap, pcv]) == [tdap, pcv]
|
|
plan = vac._plan("C1", _data([tdap, pcv]), [tdap, pcv])
|
|
assert plan["vaccines"] == ["90734", "90732"], plan
|
|
|
|
|
|
# =============================================================================
|
|
# detect()
|
|
# =============================================================================
|
|
|
|
@case
|
|
def detect_true_for_checked_vaccines_without_admin():
|
|
cpt = [_row("99396"), _row("90732", "PCV20"), _row("90734", _TDAP)]
|
|
assert vac.detect("C1", _data(cpt)) is True
|
|
|
|
|
|
@case
|
|
def detect_false_when_a_checked_admin_code_is_present():
|
|
cpt = [_row("90734", _TDAP), _row("90471", _ADMIN_1ST)]
|
|
assert vac.detect("C1", _data(cpt)) is False
|
|
|
|
|
|
@case
|
|
def an_admin_by_hover_text_does_not_count_as_admin_billed():
|
|
# By code only since 2026-10-13: 0002A is not one of the four.
|
|
cpt = [_row("90734", _TDAP), _row("0002A", "IMMUNIZATION ADMIN COVID 2ND")]
|
|
assert vac.detect("C1", _data(cpt)) is True
|
|
|
|
|
|
@case
|
|
def detect_still_true_when_the_only_admin_code_is_unchecked():
|
|
# Live claim 9132000's unchecked 90460 at 0.00 is not "present".
|
|
cpt = [_row("90656", "FLU VACC IIV3 NO PRSV 0.5ML IM"),
|
|
_row("90460", "IMMUNIZATION ADMIN", billed=False)]
|
|
assert vac.detect("C1", _data(cpt)) is True
|
|
|
|
|
|
@case
|
|
def detect_false_with_no_vaccine_at_all():
|
|
assert vac.detect("C1", _data([_row("99213")])) is False
|
|
assert vac.detect("C1", {}) is False
|
|
assert vac.detect("C1", None) is False
|
|
|
|
|
|
@case
|
|
def detect_false_for_medicare():
|
|
cpt = [_row("90734", _TDAP)]
|
|
assert vac.detect("C1", _data(cpt, payer="Humana Medicare")) is False
|
|
|
|
|
|
@case
|
|
def detect_false_for_an_unmapped_payer_or_no_payer():
|
|
cpt = [_row("90734", _TDAP)]
|
|
assert vac.detect("C1", _data(cpt, payer="Some New Carrier Inc")) is False
|
|
assert vac.detect("C1", _data(cpt, payer=None)) is False
|
|
|
|
|
|
@case
|
|
def detect_true_for_every_known_non_medicare_group():
|
|
# The PAYER GATE only, on data shaped like a UB-form read. Commercial
|
|
# ("United Health Care") claims open on the professional/HCFA screen —
|
|
# end to end in hcfa_screen_vaccine_claim_fires_on_the_payer_from_the_
|
|
# insurances_grid below — and Dental claims never reach run_all at all
|
|
# (is_dental_claim_form).
|
|
cpt = [_row("90734", _TDAP)]
|
|
for payer in ("Medicaid", "BH COLORADO ACCESS", "United Health Care",
|
|
"DentaQuest", "OUTREACH PROGRAM"):
|
|
assert vac.detect("C1", _data(cpt, payer=payer)) is True, payer
|
|
|
|
|
|
@case
|
|
def detect_false_when_no_age_can_be_determined():
|
|
cpt = [_row("90734", _TDAP)]
|
|
assert vac.detect("C1", _data(cpt, dob="", age_sex="")) is False
|
|
|
|
|
|
# =============================================================================
|
|
# needs_review() — "did not fire" that is not "clean" (review item D2)
|
|
# =============================================================================
|
|
|
|
@case
|
|
def needs_review_names_an_unmapped_payer():
|
|
cpt = [_row("90734", _TDAP)]
|
|
why = vac.needs_review("C1", _data(cpt, payer="Some New Carrier Inc"))
|
|
assert why and "Some New Carrier Inc" in why and "90734" in why, why
|
|
assert "add the admin code by hand" in why, why
|
|
|
|
|
|
@case
|
|
def needs_review_names_a_claim_with_no_payer_name_read():
|
|
why = vac.needs_review("C1", _data([_row("90734", _TDAP)], payer=None))
|
|
assert why and "no primary payer name was read" in why, why
|
|
|
|
|
|
@case
|
|
def needs_review_names_a_claim_with_no_usable_age():
|
|
why = vac.needs_review("C1", _data([_row("90734", _TDAP)], dob="",
|
|
age_sex=""))
|
|
assert why and "no usable DOB or age" in why, why
|
|
|
|
|
|
@case
|
|
def needs_review_is_silent_for_medicare():
|
|
"""Document #19 is Medicare's own rule: not covered, not unfinished."""
|
|
cpt = [_row("90734", _TDAP)]
|
|
why = vac.needs_review("C1", _data(cpt, payer="Humana Medicare"))
|
|
assert why is None, why
|
|
|
|
|
|
@case
|
|
def needs_review_is_silent_when_an_admin_code_is_already_billed():
|
|
# Even with a payer it could not classify: nothing is missing.
|
|
cpt = [_row("90734", _TDAP), _row("90471", _ADMIN_1ST)]
|
|
why = vac.needs_review("C1", _data(cpt, payer="Some New Carrier Inc"))
|
|
assert why is None, why
|
|
|
|
|
|
@case
|
|
def needs_review_is_silent_with_no_vaccine_or_when_detect_fires():
|
|
assert vac.needs_review("C1", _data([_row("99213", "OFFICE VISIT")],
|
|
payer="Some New Carrier Inc")) is None
|
|
assert vac.needs_review("C1", {}) is None
|
|
assert vac.needs_review("C1", _data([_row("90734", _TDAP)])) is None
|
|
|
|
|
|
@case
|
|
def needs_review_does_not_log_the_decision_a_second_time():
|
|
data = _data([_row("90734", _TDAP)], payer="Some New Carrier Inc")
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
vac.detect("C1", data)
|
|
vac.needs_review("C1", data)
|
|
logged = out.getvalue()
|
|
assert logged.count("leaving the claim as read") == 1, logged
|
|
|
|
|
|
@case
|
|
def run_all_reports_the_unmapped_payer_as_review():
|
|
report = {}
|
|
data = _data([_row("90734", _TDAP)], payer="Some New Carrier Inc")
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
results = run_all(None, "C1", data, conditions=[vac], report=report)
|
|
assert results == [], results
|
|
assert report["fired"] == [] and report["failed"] == [], report
|
|
assert [r["name"] for r in report["review"]] == [vac.NAME], report
|
|
|
|
|
|
# =============================================================================
|
|
# Age
|
|
# =============================================================================
|
|
|
|
@case
|
|
def age_is_whole_years_on_the_first_vaccine_rows_date_of_service():
|
|
cpt = [_row("99396", sdos="01/01/2020"),
|
|
_row("90734", _TDAP, sdos="07/07/2026")]
|
|
assert _age(_data(cpt, dob="01/02/1976")) == 50
|
|
|
|
|
|
@case
|
|
def eighteenth_birthday_on_the_dos_is_still_18_pediatric():
|
|
cpt = [_row("90734", _TDAP, sdos="03/15/2026")]
|
|
assert _age(_data(cpt, dob="03/15/2008")) == 18
|
|
assert vac.admin_codes_for_age(18) == ("90460", "90461")
|
|
|
|
|
|
@case
|
|
def nineteenth_birthday_on_the_dos_is_19_adult():
|
|
cpt = [_row("90734", _TDAP, sdos="03/15/2026")]
|
|
assert _age(_data(cpt, dob="03/15/2007")) == 19
|
|
assert vac.admin_codes_for_age(19) == ("90471", "90472")
|
|
|
|
|
|
@case
|
|
def day_before_the_nineteenth_birthday_is_still_18():
|
|
cpt = [_row("90734", _TDAP, sdos="03/14/2026")]
|
|
assert _age(_data(cpt, dob="03/15/2007")) == 18
|
|
|
|
|
|
@case
|
|
def age_falls_back_to_the_claim_date_when_the_row_has_no_sdos():
|
|
cpt = [_row("90734", _TDAP, sdos="")]
|
|
assert _age(_data(cpt, dob="03/15/2007", claim_date="03/15/2026")) == 19
|
|
|
|
|
|
@case
|
|
def age_falls_back_to_age_sex_when_dob_is_unusable():
|
|
cpt = [_row("90734", _TDAP)]
|
|
assert _age(_data(cpt, dob="", age_sex=" 50Y, M")) == 50
|
|
assert _age(_data(cpt, dob="not a date", age_sex="7Y, F")) == 7
|
|
# a DOB after the date of service is not a usable DOB either
|
|
assert _age(_data(cpt, dob="01/01/2030", age_sex=" 50Y, M")) == 50
|
|
|
|
|
|
@case
|
|
def months_or_days_only_age_sex_is_zero_years():
|
|
cpt = [_row("90734", _TDAP)]
|
|
assert _age(_data(cpt, dob="", age_sex=" 12M 12D, F")) == 0
|
|
assert _age(_data(cpt, dob="", age_sex="3D, M")) == 0
|
|
assert _age(_data(cpt, dob="", age_sex="")) is None
|
|
|
|
|
|
# =============================================================================
|
|
# Live samples the client already fixed by hand — must stay untouched
|
|
# =============================================================================
|
|
|
|
@case
|
|
def live_9131000_adult_with_admin_already_billed_no_change():
|
|
# The live vaccines (90677, 90750, 90715) are off the client's list
|
|
# since 2026-10-13; listed codes stand in so the admin rule is tested.
|
|
cpt = [_row("99396"), _row("90732", "PCV20"), _row("90746", "Shingrix"),
|
|
_row("90734", _TDAP),
|
|
_row("90471", _ADMIN_1ST, units="1"),
|
|
_row("90472", _ADMIN_ADD, units="2")]
|
|
assert vac.detect("9131000", _data(cpt, age_sex=" 50Y, F")) is False
|
|
|
|
|
|
@case
|
|
def live_9132000_adult_with_checked_90471_and_unchecked_90460_no_change():
|
|
# 90673 is off the client's vaccine list (2026-10-13): no vaccine at
|
|
# all now, as well as an admin code already billed.
|
|
cpt = [_row("99397"), _row("90673", "FLU VACC RIV3 NO PRESERV"),
|
|
_row("90471", _ADMIN_1ST),
|
|
_row("90460", "IMMUNIZATION ADMIN THROUGH 18 YRS", billed=False)]
|
|
assert vac.detect("9132000",
|
|
_data(cpt, dob="02/10/1968", age_sex=" 58Y, F")) is False
|
|
|
|
|
|
@case
|
|
def live_9133000_infant_with_pediatric_admin_already_billed_no_change():
|
|
cpt = [_row("99392"), _row("90732", "PCV20"),
|
|
_row("90716", "VARICELLA VIRUS VACCINE LIVE FOR SUBCUTANEOUS USE"),
|
|
_row("90707", "MMR VIRUS VACCINE LIVE SUBCUTANEOUS"),
|
|
_row("90633", "HEPA VACCINE PED/ADOL-2 DOSE IM"),
|
|
_row("90460", "IMMUNIZATION ADMIN THROUGH 18 YRS"),
|
|
_row("90461", "IMMUNIZATION ADMIN EACH ADD", units="3")]
|
|
assert vac.detect("9133000",
|
|
_data(cpt, dob="07/07/2025", age_sex=" 12M, M")) is False
|
|
|
|
|
|
# =============================================================================
|
|
# Registry
|
|
# =============================================================================
|
|
|
|
@case
|
|
def registered_last_of_the_specific_conditions():
|
|
"""
|
|
After every other specific condition, so it re-reads a grid they have
|
|
already changed. After it come only injection_admin_code (#20, which
|
|
adds a line the same way, 2026-10-13), medicare_pps_code (#25, which
|
|
adds one too, 2026-10-23) and priced_line_check — the common rule's
|
|
reverse half, registered LAST on 2026-09-30 so it only ever sees lines
|
|
the exclusion conditions have already zeroed.
|
|
"""
|
|
from ecw_bot.conditions import (injection_admin_code, medicare_pps_code,
|
|
priced_line_check)
|
|
assert conditions.CONDITIONS[-4] is vac, conditions.CONDITIONS
|
|
assert conditions.CONDITIONS[-3] is injection_admin_code, conditions.CONDITIONS
|
|
assert conditions.CONDITIONS[-2] is medicare_pps_code, conditions.CONDITIONS
|
|
assert conditions.CONDITIONS[-1] is priced_line_check, conditions.CONDITIONS
|
|
assert conditions.BY_NAME["vaccine_admin_codes"] is vac
|
|
assert vac.NAME == "vaccine_admin_codes"
|
|
|
|
|
|
# =============================================================================
|
|
# Page helpers
|
|
# =============================================================================
|
|
|
|
def _fresh(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
|
|
|
|
def _seed(page, rows, dob=None, age_sex=None):
|
|
page.evaluate("rows => window.seedCptRows(rows)", rows)
|
|
if dob is not None or age_sex is not None:
|
|
page.evaluate("([d, a]) => window.setPatient(d, a)",
|
|
[dob or "", age_sex or ""])
|
|
|
|
|
|
def _live(page):
|
|
return claim_form.extract(page)
|
|
|
|
|
|
def _rows_for(page, code):
|
|
return [r for r in _live(page)["cpt"] if r["code"] == code]
|
|
|
|
|
|
_ADULT_ONE = [
|
|
{"code": "99396", "title": "PREVENTIVE VISIT EST AGE 40-64", "fee": "250.00"},
|
|
{"code": "90734", "title": _TDAP, "fee": "60.00", "icd1": "2"},
|
|
]
|
|
|
|
_CHILD_FOUR = [
|
|
{"code": "99392", "title": "PREV VISIT EST AGE 1-4", "fee": "180.00"},
|
|
{"code": "90732", "title": "PCV20", "fee": "290.00", "icd1": "2"},
|
|
{"code": "90716", "title": "VARICELLA VIRUS VACCINE LIVE FOR SUBCUTANEOUS USE",
|
|
"fee": "180.00", "icd1": "2"},
|
|
{"code": "90707", "title": "MMR VIRUS VACCINE LIVE SUBCUTANEOUS",
|
|
"fee": "95.00", "icd1": "2"},
|
|
{"code": "90633", "title": "HEPA VACCINE PED/ADOL-2 DOSE IM",
|
|
"fee": "70.00", "icd1": "2"},
|
|
]
|
|
|
|
|
|
# =============================================================================
|
|
# Extractor: the additive `billed` field
|
|
# =============================================================================
|
|
|
|
@case
|
|
def extract_reports_billed_from_the_assign_to_patient_checkbox(page):
|
|
_fresh(page)
|
|
_seed(page, [{"code": "90734", "title": _TDAP},
|
|
{"code": "90460", "title": "IMMUNIZATION ADMIN", "checked": False},
|
|
{"code": "90732", "title": "PCV20", "noCheckbox": True}])
|
|
cpt = _live(page)["cpt"]
|
|
assert [r["billed"] for r in cpt] == [True, False, None], cpt
|
|
assert cpt[0]["description"] == _TDAP, cpt[0]
|
|
|
|
|
|
@case
|
|
def extract_billed_is_none_on_a_ub_form_without_the_checkbox(page):
|
|
# ub_claim.html's rows carry no updateBillToIns( checkbox — unknown,
|
|
# which the condition treats as checked, never as "not billed".
|
|
page.goto(UB_FIXTURE.as_uri())
|
|
cpt = _live(page)["cpt"]
|
|
assert cpt and all(r["billed"] is None for r in cpt), cpt
|
|
assert cpt[0]["code"] == "H0023", "existing fields must be unchanged"
|
|
|
|
|
|
@case
|
|
def extract_reports_billed_fee_none_for_a_row_with_no_fee_input(page):
|
|
# None ("never read"), not "" — "" is a blank fee that WAS read, which
|
|
# is_zero_fee() calls zero. Told apart, zero_fee_uncheck.detect() and
|
|
# this condition's pre-read agree with uncheck_zero_fee_cpt_rows(),
|
|
# which leaves a row with no fee input alone (review finding 2026-09-30).
|
|
_fresh(page)
|
|
_seed(page, [{"code": "90734", "title": _TDAP, "fee": "60.00"},
|
|
{"code": "90471", "title": _ADMIN_1ST, "noFee": True}])
|
|
cpt = _live(page)["cpt"]
|
|
assert [r["billed_fee"] for r in cpt] == ["60.00", None], cpt
|
|
|
|
|
|
@case
|
|
def run_all_counts_a_checked_admin_line_with_no_fee_input_as_billed(page):
|
|
"""
|
|
The claim-level shape of the finding: a checked 90471 with no Billed
|
|
Fee input. zero_fee_uncheck never unchecks it (no fee input -> left
|
|
alone), so it IS the admin code billed — no second 90471 is added.
|
|
Before the fix extract() read its fee as "" (zero): vaccine_admin_codes
|
|
fired and added a 90471 on top, two admin lines billed.
|
|
"""
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE + [{"code": "90471", "title": _ADMIN_1ST,
|
|
"noFee": True}])
|
|
data = _live(page)
|
|
assert vac.detect("9113000", data) is False
|
|
results = run_all(page, "9113000", data)
|
|
assert [r.condition_name for r in results] == [], results
|
|
assert [r["billed"] for r in _rows_for(page, "90471")] == [True]
|
|
|
|
|
|
@case
|
|
def run_all_counts_a_checked_vaccine_with_no_fee_input(page):
|
|
# The mirror: a checked vaccine with no fee input stays billed, so it is
|
|
# counted — two vaccines, 90471 AND 90472, not 90471 alone.
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE + [{"code": "90716", "title": _CHILD_FOUR[2]["title"],
|
|
"noFee": True, "icd1": "2"}])
|
|
results = run_all(page, "9113000", _live(page))
|
|
assert [r.condition_name for r in results] == ["vaccine_admin_codes"], results
|
|
added = [c["code"] for c in results[0].changes if c.get("added")]
|
|
assert added == ["90471", "90472"], results[0].changes
|
|
|
|
|
|
# =============================================================================
|
|
# PINNED, NEEDS AN OPERATOR RULING — an admin code the Fee Schedule prices
|
|
# at 0.00. Today's outcome, not the settled one (review finding 2026-09-30).
|
|
# =============================================================================
|
|
|
|
@case
|
|
def pinned_needs_ruling_an_admin_code_added_at_zero_adds_again_next_run(page):
|
|
"""
|
|
PINNED, NEEDS A RULING. Run 1: the picker prices the added 90471 at
|
|
0.00 — it lands checked at 0.00 and stays checked (zero_fee_uncheck ran
|
|
FIRST, before it existed; priced_line_check only ever checks), and
|
|
describe() reports it with no warning. Run 2 on the same claim, still
|
|
in the queue: that 90471 is a checked zero-fee admin line, which ruling
|
|
B says does not count, so zero_fee_uncheck unchecks it and
|
|
vaccine_admin_codes adds ANOTHER 90471 at 0.00 — one more per run.
|
|
Whether a line added at 0.00 should be flagged, failed, or left is
|
|
beyond rulings A/B/C.
|
|
"""
|
|
_fresh(page)
|
|
page.evaluate("([c, f]) => window.setFeeScheduleFee(c, f)", ["90471", "0.00"])
|
|
_seed(page, _ADULT_ONE)
|
|
|
|
def grid():
|
|
return [(r["code"], r["billed"], r["billed_fee"])
|
|
for r in _live(page)["cpt"]]
|
|
|
|
results = run_all(page, "9113000", _live(page))
|
|
assert [r.condition_name for r in results] == ["vaccine_admin_codes"], results
|
|
assert "CHECK" not in vac.describe(results[0]).upper(), vac.describe(results[0])
|
|
assert grid()[-1] == ("90471", True, "0.00"), grid()
|
|
|
|
results = run_all(page, "9113000", _live(page))
|
|
assert [r.condition_name for r in results] == ["zero_fee_uncheck", "vaccine_admin_codes"], results
|
|
assert grid()[2:] == [("90471", False, "0.00"), ("90471", True, "0.00")], ("today: a second 90471 added", grid())
|
|
|
|
|
|
@case
|
|
def default_fixture_is_what_detect_fires_on(page):
|
|
_fresh(page)
|
|
data = _live(page)
|
|
assert data["insurance"][0]["Name"] == "Medicaid", data["insurance"]
|
|
assert data["patient"]["dob"] == "01/02/1976", data["patient"]
|
|
assert vac.detect("9113000", data) is True
|
|
|
|
|
|
# =============================================================================
|
|
# apply()
|
|
# =============================================================================
|
|
|
|
@case
|
|
def apply_adult_one_vaccine_adds_90471_x1_only(page):
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE)
|
|
data = _live(page)
|
|
|
|
result = vac.apply(page, "9113000", data)
|
|
assert isinstance(result, ConditionResult)
|
|
assert result.condition_name == "vaccine_admin_codes"
|
|
assert result.changed is True, result
|
|
assert [c["code"] for c in result.changes] == ["90471"], result.changes
|
|
assert result.changes[0]["added"] is True
|
|
assert result.changes[0]["units"] == "1", result.changes
|
|
|
|
added = _rows_for(page, "90471")
|
|
assert len(added) == 1 and added[0]["units"] == "1", added
|
|
assert added[0]["billed_fee"] == "30.00", added
|
|
assert _rows_for(page, "90472") == [], "one vaccine needs no 90472"
|
|
assert _rows_for(page, "90460") == [], "an adult never gets 90460"
|
|
|
|
|
|
@case
|
|
def apply_adult_three_vaccines_adds_90471_x1_and_90472_x1(page):
|
|
"""
|
|
The operator's simplified rule (2026-09-29): more than one vaccine ->
|
|
the two admin codes, each x1 — NOT 90472 x (vaccines - 1).
|
|
"""
|
|
_fresh(page) # the default grid: 90732, 90746, 90734 on a 50-year-old
|
|
data = _live(page)
|
|
|
|
result = vac.apply(page, "9113000", data)
|
|
assert result.changed is True
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert set(by_code) == {"90471", "90472"}, result.changes
|
|
assert by_code["90471"]["units"] == "1", by_code
|
|
assert by_code["90472"]["units"] == "1", by_code
|
|
assert by_code["90472"]["billed_fee"] == "20.00", by_code
|
|
# Both landed: nothing for a human (control for unit D review finding 3).
|
|
assert result.review == "", result.review
|
|
|
|
row = _rows_for(page, "90472")
|
|
assert len(row) == 1 and row[0]["units"] == "1", row
|
|
assert row[0]["billed_fee"] == "20.00", row
|
|
|
|
|
|
@case
|
|
def apply_adult_two_vaccines_leaves_both_lines_as_ecw_priced_them(page):
|
|
# Both codes want Units 1 and the picker already gave 1 — so neither
|
|
# line's Units is touched; each is left exactly as eCW priced it.
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE + [{"code": "90732", "title": "PCV20", "icd1": "2",
|
|
"fee": "290.00"}])
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert set(by_code) == {"90471", "90472"}, result.changes
|
|
assert by_code["90472"]["units"] == "1", by_code
|
|
assert by_code["90472"]["billed_fee"] == "20.00", by_code
|
|
for i in (3, 4):
|
|
units = page.locator(sel.SEL_CPT_ROWS).nth(i).locator(sel.SEL_CPT_UNITS).first
|
|
assert units.input_value() == "1"
|
|
assert units.get_attribute("data-blurred") is None, \
|
|
"a line eCW already gave 1 unit must be left as priced"
|
|
|
|
|
|
@case
|
|
def apply_child_four_vaccines_adds_90460_x1_and_90461_x1(page):
|
|
_fresh(page)
|
|
_seed(page, _CHILD_FOUR, dob="07/01/2025", age_sex=" 12M 6D, F")
|
|
data = _live(page)
|
|
|
|
result = vac.apply(page, "9133000", data)
|
|
assert result.changed is True
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert set(by_code) == {"90460", "90461"}, result.changes
|
|
assert by_code["90460"]["units"] == "1", by_code
|
|
assert by_code["90461"]["units"] == "1", by_code
|
|
assert by_code["90461"]["billed_fee"] == "15.00", by_code
|
|
assert _rows_for(page, "90471") == [] and _rows_for(page, "90472") == []
|
|
|
|
|
|
@case
|
|
def apply_does_not_hand_fill_pos_or_icd_pointers(page):
|
|
# eCW fills POS / dates / ICD pointers on the new line itself (the
|
|
# fixture's picker fakes that by copying the first line). Whatever it
|
|
# put there must survive apply() untouched.
|
|
_fresh(page)
|
|
_seed(page, [{"code": "99396", "title": "PREVENTIVE VISIT", "pos": "22",
|
|
"icd1": "1", "icd2": "2"},
|
|
{"code": "90734", "title": _TDAP, "pos": "22", "icd1": "2",
|
|
"fee": "60.00"}])
|
|
vac.apply(page, "9113000", _live(page))
|
|
added = _rows_for(page, "90471")[0]
|
|
assert added["pos"] == "22" and added["icd1"] == "1" \
|
|
and added["icd2"] == "2", added
|
|
|
|
|
|
@case
|
|
def apply_still_adds_when_only_an_unchecked_admin_is_present(page):
|
|
_fresh(page)
|
|
_seed(page, [{"code": "99397", "title": "PREVENTIVE VISIT EST 65+"},
|
|
{"code": "90656", "title": "FLU VACC IIV3 NO PRSV 0.5ML IM",
|
|
"fee": "65.00"},
|
|
{"code": "90460", "title": "IMMUNIZATION ADMIN", "checked": False,
|
|
"fee": "0.00"}])
|
|
result = vac.apply(page, "9132000", _live(page))
|
|
assert result.changed is True
|
|
assert [c["code"] for c in result.changes] == ["90471"], result.changes
|
|
assert len(_rows_for(page, "90460")) == 1, "the unchecked 90460 is left alone"
|
|
|
|
|
|
@case
|
|
def apply_child_with_an_unchecked_90460_adds_a_new_90460_row(page):
|
|
_fresh(page)
|
|
_seed(page, [{"code": "99392", "title": "PREV VISIT EST AGE 1-4"},
|
|
{"code": "90732", "title": "PCV20", "fee": "290.00"},
|
|
{"code": "90460", "title": "IMMUNIZATION ADMIN", "checked": False,
|
|
"fee": "0.00"}],
|
|
dob="07/01/2020", age_sex=" 6Y, M")
|
|
result = vac.apply(page, "C1", _live(page))
|
|
assert result.changed is True
|
|
rows = _rows_for(page, "90460")
|
|
assert [r["billed"] for r in rows] == [False, True], rows
|
|
|
|
|
|
@case
|
|
def apply_add_that_leaves_no_new_row_counts_as_a_failure(page):
|
|
# add_cpt_row() verifies only that SOME 90460 row exists afterward —
|
|
# true here from the unchecked one alone. The new-row count is what
|
|
# tells a real add from a picker that silently did nothing.
|
|
_fresh(page)
|
|
_seed(page, [{"code": "90732", "title": "PCV20", "fee": "290.00"},
|
|
{"code": "90460", "title": "IMMUNIZATION ADMIN", "checked": False}],
|
|
dob="07/01/2020", age_sex=" 6Y, M")
|
|
page.evaluate("() => { window.feeScheduleOkAddsNothing = true; }")
|
|
result = vac.apply(page, "C1", _live(page))
|
|
assert result.changed is False, result
|
|
assert result.changes == []
|
|
assert len(_rows_for(page, "90460")) == 1
|
|
|
|
|
|
@case
|
|
def apply_first_code_add_failure_adds_nothing(page):
|
|
_fresh(page)
|
|
page.evaluate("() => window.removeFromFeeSchedule('90471')")
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
assert result.changed is False, result
|
|
assert result.changes == []
|
|
# Declined, not none(): the claim still lacks its admin code (D2).
|
|
assert result == ConditionResult.declined(
|
|
vac.NAME, "90471 could not be added — add it by hand"), result
|
|
assert _rows_for(page, "90471") == []
|
|
assert _rows_for(page, "90472") == [], \
|
|
"90472 must not be attempted once 90471 could not be added"
|
|
assert not page.locator(sel.SEL_FEE_SCHEDULE_CPT_SEARCH).first.is_visible(), \
|
|
"the picker must be closed, not left blocking the claim form"
|
|
|
|
|
|
@case
|
|
def apply_additional_code_failure_keeps_the_first_and_reports_it(page):
|
|
_fresh(page)
|
|
page.evaluate("() => window.removeFromFeeSchedule('90472')")
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
assert result.changed is True
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert by_code["90471"]["added"] is True, by_code
|
|
assert by_code["90472"]["added"] is False, by_code
|
|
# The first code landed, the second did not: the claim still lacks an
|
|
# admin code, so it carries a review reason (review of unit D,
|
|
# 2026-10-05, finding 3 — this asserted review == "" before, which made
|
|
# the summary a Fail row reading "vaccine admin code added"). The
|
|
# removed fee-schedule entry means the picker added nothing.
|
|
assert result.review == "90472 could not be added — add it by hand", \
|
|
result.review
|
|
assert len(_rows_for(page, "90471")) == 1
|
|
text = vac.describe(result)
|
|
assert "90472" in text and "FAILED" in text, text
|
|
|
|
|
|
@case
|
|
def apply_decides_from_the_live_grid_not_the_pre_read_data(page):
|
|
# The pre-read data says "vaccines, no admin" — but by the time apply()
|
|
# runs, an admin code is on the live grid (e.g. added by hand or by an
|
|
# earlier condition). Nothing may be added on top of it.
|
|
_fresh(page)
|
|
data = _live(page)
|
|
_seed(page, [{"code": "90734", "title": _TDAP, "fee": "60.00"},
|
|
{"code": "90471", "title": _ADMIN_1ST, "fee": "30.00"}])
|
|
result = vac.apply(page, "9113000", data)
|
|
assert result.changed is False, result
|
|
assert len(_rows_for(page, "90471")) == 1
|
|
|
|
|
|
@case
|
|
def apply_live_plan_skips_a_checked_zero_fee_admin_line_the_same_as_detect(page):
|
|
"""
|
|
detect() and apply()'s live-grid plan share one rule (2026-09-30): the
|
|
claim as read has its only 90471 checked at 0.00, detect() fires on
|
|
it, and apply() — called straight here, zero_fee_uncheck never run —
|
|
reads the live grid the same way and adds 90471, rather than calling
|
|
the 0.00 line "admin already billed". In a real run zero_fee_uncheck
|
|
has unchecked that line first (test_conditions_zero_fee_uncheck.py).
|
|
"""
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE + [{"code": "90471", "title": _ADMIN_1ST,
|
|
"fee": "0.00"}])
|
|
data = _live(page)
|
|
assert vac.detect("9113000", data) is True
|
|
result = vac.apply(page, "9113000", data)
|
|
assert result.changed is True, result
|
|
assert [c["code"] for c in result.changes if c["added"]] == ["90471"], \
|
|
result.changes
|
|
assert [(r["billed"], r["billed_fee"]) for r in _rows_for(page, "90471")] \
|
|
== [(True, "0.00"), (True, "30.00")]
|
|
|
|
|
|
@case
|
|
def apply_live_plan_does_not_count_a_checked_zero_fee_vaccine(page):
|
|
# Two vaccine lines, one of them checked at 0.00: one billed vaccine,
|
|
# so 90471 alone — detect()'s plan and apply()'s agree.
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE + [{"code": "90732", "title": "PCV20",
|
|
"fee": "0.00", "icd1": "2"}])
|
|
data = _live(page)
|
|
assert vac._plan("9113000", data, data["cpt"])["vaccines"] == ["90734"]
|
|
result = vac.apply(page, "9113000", data)
|
|
assert [c["code"] for c in result.changes] == ["90471"], result.changes
|
|
assert _rows_for(page, "90472") == [], "one billed vaccine needs no 90472"
|
|
|
|
|
|
@case
|
|
def apply_leaves_a_medicare_claim_alone(page):
|
|
_fresh(page)
|
|
data = _live(page)
|
|
data["insurance"] = [{"Name": "Humana Medicare"}]
|
|
result = vac.apply(page, "9113000", data)
|
|
assert result.changed is False
|
|
assert _rows_for(page, "90471") == []
|
|
|
|
|
|
@case
|
|
def describe_names_vaccines_age_codes_and_units(page):
|
|
_fresh(page)
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
text = vac.describe(result)
|
|
for bit in ("90732", "90746", "90734", "90471", "90472"):
|
|
assert bit in text, (bit, text)
|
|
# The age line itself — a bare "50" is already inside "90746".
|
|
assert "Patient age used: 50 (" in text and "above 18" in text, text
|
|
assert "Added 90471 x1" in text and "Added 90472 x1" in text, text
|
|
|
|
|
|
@case
|
|
def describe_names_the_pediatric_band_for_a_child(page):
|
|
_fresh(page)
|
|
_seed(page, _CHILD_FOUR, dob="07/01/2025", age_sex=" 12M 6D, F")
|
|
text = vac.describe(vac.apply(page, "9133000", _live(page)))
|
|
assert "Patient age used: 1 (" in text and "18 and below" in text, text
|
|
assert "Added 90460 x1" in text and "Added 90461 x1" in text, text
|
|
|
|
|
|
# --- vaccine count: rows, not units (ruling 4) --------------------------------
|
|
|
|
@case
|
|
def apply_counts_vaccine_rows_not_their_units(page):
|
|
_fresh(page)
|
|
_seed(page, [{"code": "99396", "title": "PREVENTIVE VISIT EST AGE 40-64"},
|
|
{"code": "90734", "title": _TDAP, "units": "2", "fee": "120.00"},
|
|
{"code": "90732", "title": "PCV20", "units": "1",
|
|
"fee": "290.00"}])
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert set(by_code) == {"90471", "90472"}, result.changes
|
|
assert by_code["90472"]["units"] == "1", \
|
|
"2 vaccine ROWS -> 90472 x1, whatever units the vaccines carry"
|
|
|
|
|
|
# --- the new line is the one Units is written to ------------------------------
|
|
|
|
_CHILD_THREE_WITH_OLD_90461 = [
|
|
{"code": "99392", "title": "PREV VISIT EST AGE 1-4"},
|
|
{"code": "90732", "title": "PCV20", "fee": "290.00"},
|
|
{"code": "90716", "title": "VARICELLA VIRUS VACCINE LIVE FOR SUBCUTANEOUS USE",
|
|
"fee": "180.00"},
|
|
{"code": "90707", "title": "MMR VIRUS VACCINE LIVE SUBCUTANEOUS", "fee": "95.00"},
|
|
{"code": "90461", "title": "IMMUNIZATION ADMIN EACH ADD", "checked": False,
|
|
"fee": "0.00"},
|
|
]
|
|
|
|
|
|
def _assert_new_and_old_90461(rows, new_first):
|
|
assert len(rows) == 2, rows
|
|
new, old = (rows[0], rows[1]) if new_first else (rows[1], rows[0])
|
|
assert new["billed"] is True and new["units"] == "1" \
|
|
and new["billed_fee"] == "15.00", ("the NEW line gets Units 1", rows)
|
|
assert old["billed"] is False and old["units"] == "1" \
|
|
and old["billed_fee"] == "0.00", ("the old unchecked line is untouched", rows)
|
|
|
|
|
|
@case
|
|
def apply_sets_units_on_the_new_90461_not_an_old_unchecked_one(page):
|
|
# The 9132000 shape, pediatric: a leftover UNCHECKED 90461 at 0.00 sits
|
|
# above where the new one lands.
|
|
# The picker hands back Units 0, so the Units write actually runs —
|
|
# it must land on the NEW line, not the old one.
|
|
_fresh(page)
|
|
_seed(page, _CHILD_THREE_WITH_OLD_90461, dob="07/01/2020", age_sex=" 6Y, M")
|
|
page.evaluate("() => { window.feeScheduleNewLineUnits = '0'; }")
|
|
result = vac.apply(page, "C1", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert by_code["90461"]["units"] == "1", by_code
|
|
assert by_code["90461"]["error"] is None, by_code
|
|
_assert_new_and_old_90461(_rows_for(page, "90461"), new_first=False)
|
|
|
|
|
|
@case
|
|
def apply_sets_units_on_the_new_line_even_when_it_lands_above_an_old_one(page):
|
|
# A grid that inserts the new 90461 ABOVE the old one: "the last row
|
|
# with this code" would be the old unchecked line — the diff finds the
|
|
# real new one.
|
|
_fresh(page)
|
|
_seed(page, _CHILD_THREE_WITH_OLD_90461, dob="07/01/2020", age_sex=" 6Y, M")
|
|
page.evaluate("() => { window.feeScheduleInsertBeforeCode = '90461'; "
|
|
"window.feeScheduleNewLineUnits = '0'; }")
|
|
result = vac.apply(page, "C1", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert by_code["90461"]["units"] == "1", by_code
|
|
assert by_code["90461"]["error"] is None, by_code
|
|
_assert_new_and_old_90461(_rows_for(page, "90461"), new_first=True)
|
|
|
|
|
|
@case
|
|
def apply_reports_an_extra_line_the_picker_added_alongside(page):
|
|
# The second picker open keeps the first pick, so OK adds 90471 again
|
|
# with 90472: the 90472 add itself is fine, the stray line is reported.
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE + [{"code": "90732", "title": "PCV20", "icd1": "2",
|
|
"fee": "290.00"}])
|
|
page.evaluate("() => { window.feeScheduleOkAlsoAdds = '90471'; }")
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert by_code["90472"]["added"] is True, by_code
|
|
unexpected = " ".join(by_code["90472"]["unexpected"])
|
|
assert "90471" in unexpected, by_code
|
|
text = vac.describe(result)
|
|
assert "CHECK BY HAND" in text and "90471" in text, text
|
|
|
|
|
|
# --- a failed first add that still changed the grid ---------------------------
|
|
|
|
@case
|
|
def apply_first_add_that_rechecks_an_old_line_is_reported_not_hidden(page):
|
|
# OK re-checks and re-prices the claim's existing UNCHECKED 90460
|
|
# instead of appending one: nothing was "added", but the grid is not
|
|
# what it was — a result of "nothing changed" would let another
|
|
# condition's Save write it unreviewed.
|
|
_fresh(page)
|
|
_seed(page, [{"code": "99392", "title": "PREV VISIT EST AGE 1-4"},
|
|
{"code": "90732", "title": "PCV20", "fee": "290.00"},
|
|
{"code": "90716", "title": "VARICELLA VIRUS VACCINE LIVE",
|
|
"fee": "180.00"},
|
|
{"code": "90460", "title": "IMMUNIZATION ADMIN", "checked": False,
|
|
"fee": "0.00"}],
|
|
dob="07/01/2020", age_sex=" 6Y, M")
|
|
page.evaluate("() => { window.feeScheduleOkRechecksExisting = true; }")
|
|
result = vac.apply(page, "C1", _live(page))
|
|
assert result.changed is True, result
|
|
assert [c["code"] for c in result.changes] == ["90460"], result.changes
|
|
change = result.changes[0]
|
|
assert change["added"] is False and change["grid_changed"] is True, change
|
|
# And flagged: a failed add that moved the grid is never "clean" (D2).
|
|
assert result.review == ("90460 could not be added cleanly — check the "
|
|
"CPT grid by hand"), result.review
|
|
assert _rows_for(page, "90461") == [], "90461 must not be attempted"
|
|
text = vac.describe(result)
|
|
assert "FAILED to add 90460" in text and "CHECK BY HAND" in text, text
|
|
# both states of the line, so the operator sees exactly what moved
|
|
assert "90460 x1 (fee 0.00, unchecked)" in text, text
|
|
assert "90460 x1 (fee 25.00, checked)" in text, text
|
|
|
|
|
|
# --- nothing raised mid-add may lose a line already on the form ---------------
|
|
|
|
@contextlib.contextmanager
|
|
def _patched(module, name, replacement):
|
|
real = getattr(module, name)
|
|
setattr(module, name, replacement)
|
|
try:
|
|
yield real
|
|
finally:
|
|
setattr(module, name, real)
|
|
|
|
|
|
@case
|
|
def apply_second_add_raising_keeps_the_first_and_reports_both(page):
|
|
_fresh(page)
|
|
real_add = claim_form.add_cpt_row
|
|
|
|
def add_that_times_out_on_90472(page_, code):
|
|
if code != "90472":
|
|
return real_add(page_, code)
|
|
# The picker is open when the timeout hits, as it would be live.
|
|
page_.locator(sel.SEL_CPT_ADD_BTN).first.click()
|
|
raise TimeoutError("Locator.click: Timeout 10000ms exceeded (simulated)")
|
|
|
|
with _patched(claim_form, "add_cpt_row", add_that_times_out_on_90472):
|
|
results = run_all(page, "9113000", _live(page))
|
|
assert [r.condition_name for r in results] == ["vaccine_admin_codes"], \
|
|
"a raise inside the second add must not drop the whole result"
|
|
by_code = {c["code"]: c for c in results[0].changes}
|
|
assert by_code["90471"]["added"] is True, by_code
|
|
assert by_code["90472"]["added"] is False, by_code
|
|
assert "raised" in by_code["90472"]["error"], by_code
|
|
# Unit D review, finding 3: the missing 90472 makes it a Review row.
|
|
assert results[0].review.startswith("90472 could not be added"), \
|
|
results[0].review
|
|
assert len(_rows_for(page, "90471")) == 1
|
|
assert not page.locator(sel.SEL_FEE_SCHEDULE_CPT_SEARCH).first.is_visible(), \
|
|
"the picker the failed add left open must be closed"
|
|
text = vac.describe(results[0])
|
|
assert "Added 90471 x1" in text and "FAILED to add 90472" in text, text
|
|
|
|
|
|
@case
|
|
def apply_units_write_raising_still_reports_the_line_it_added(page):
|
|
_fresh(page)
|
|
page.evaluate("() => { window.feeScheduleNewLineUnits = '0'; }")
|
|
|
|
def units_that_time_out(*args, **kwargs):
|
|
raise TimeoutError("Locator.fill: Timeout 10000ms exceeded (simulated)")
|
|
|
|
with _patched(claim_form, "set_cpt_units", units_that_time_out):
|
|
results = run_all(page, "9113000", _live(page))
|
|
assert [r.condition_name for r in results] == ["vaccine_admin_codes"], results
|
|
by_code = {c["code"]: c for c in results[0].changes}
|
|
assert by_code["90472"]["added"] is True, by_code
|
|
assert by_code["90472"]["units"] == "0", \
|
|
"the report shows what the line really reads — still 0 units"
|
|
assert "raised" in by_code["90472"]["error"], by_code
|
|
text = vac.describe(results[0])
|
|
assert "Added 90472 x0" in text and "CHECK" in text, text
|
|
|
|
|
|
# --- the Fee Schedule picker is never left open --------------------------------
|
|
|
|
@case
|
|
def apply_ok_that_keeps_the_picker_open_closes_it_and_adds_nothing(page):
|
|
# Slow (~15s): add_cpt_row() waits out its own 10s for the picker to
|
|
# hide before deciding OK did nothing.
|
|
_fresh(page)
|
|
_seed(page, _ADULT_ONE)
|
|
page.evaluate("() => { window.feeScheduleOkKeepsOpen = true; }")
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
assert result.changed is False, result
|
|
assert _rows_for(page, "90471") == []
|
|
assert not page.locator(sel.SEL_FEE_SCHEDULE_CPT_SEARCH).first.is_visible(), \
|
|
"a picker left up would block the claim form's own Cancel"
|
|
|
|
|
|
@case
|
|
def add_cpt_row_closes_the_picker_when_a_step_raises(page):
|
|
# Slow (~10s): a disabled OK makes Playwright's click time out, with
|
|
# the picker open — it must be closed before the error goes on up.
|
|
_fresh(page)
|
|
page.evaluate("() => { document.getElementById('claimFeeSchOkBtn')"
|
|
".disabled = true; }")
|
|
try:
|
|
claim_form.add_cpt_row(page, "90471")
|
|
except Exception:
|
|
pass
|
|
else:
|
|
raise AssertionError("the OK click timeout must still be raised")
|
|
assert not page.locator(sel.SEL_FEE_SCHEDULE_CPT_SEARCH).first.is_visible()
|
|
|
|
|
|
# --- the fee read back is the re-priced one -----------------------------------
|
|
|
|
@case
|
|
def apply_waits_for_an_async_units_reprice(page):
|
|
_fresh(page)
|
|
page.evaluate("() => { window.unitsRepriceDelayMs = 600; "
|
|
"window.feeScheduleNewLineUnits = '0'; }")
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert by_code["90472"]["billed_fee"] == "20.00", \
|
|
("the 0.00 fee read straight after the blur is stale", by_code)
|
|
assert by_code["90472"]["error"] is None, by_code
|
|
|
|
|
|
@case
|
|
def apply_flags_a_fee_that_never_reprices(page):
|
|
_fresh(page)
|
|
page.evaluate("() => { window.unitsNoReprice = true; "
|
|
"window.feeScheduleNewLineUnits = '0'; }")
|
|
result = vac.apply(page, "9113000", _live(page))
|
|
by_code = {c["code"]: c for c in result.changes}
|
|
assert by_code["90472"]["units"] == "1", by_code
|
|
assert "re-priced" in (by_code["90472"]["error"] or ""), by_code
|
|
assert "CHECK" in vac.describe(result)
|
|
|
|
|
|
# --- the professional/HCFA screen ---------------------------------------------
|
|
|
|
def _hcfa_with_one_vaccine(page):
|
|
page.goto(HCFA_FIXTURE.as_uri())
|
|
page.evaluate("""(title) => {
|
|
const row = document.getElementById('CPTROW_1');
|
|
row.querySelector('[data-fieldname="claimCPTCode"]').value = '90734';
|
|
row.querySelector('[data-fieldname="claimCPTCode"]').closest('td')
|
|
.setAttribute('title', title);
|
|
// The live screen's DOB bind (MM/DD/YYYY, read on 9109000 and
|
|
// 9119000) is not part of the fixture; a made-up adult DOB.
|
|
const dob = document.createElement('span');
|
|
dob.setAttribute('ng-bind', 'PatientInfo.dob');
|
|
dob.textContent = '01/15/1980';
|
|
document.body.appendChild(dob);
|
|
}""", _TDAP)
|
|
|
|
|
|
@case
|
|
def hcfa_screen_vaccine_claim_fires_on_the_payer_from_the_insurances_grid(page):
|
|
# hcfa_claim.html is a United Health Care (Commercial) claim — covered
|
|
# by document #18. This used to be pinned as a gap: extract() read no
|
|
# insurance on this screen, so the payer gate always said no.
|
|
_hcfa_with_one_vaccine(page)
|
|
data = _live(page)
|
|
assert [r["code"] for r in vac.checked_vaccine_rows(data["cpt"])] == ["90734"]
|
|
assert data["insurance"][0]["Name"] == "United Health Care", data["insurance"]
|
|
assert vac.detect("9103000", data) is True
|
|
|
|
|
|
@case
|
|
def hcfa_screen_with_no_payer_row_still_blames_the_read(page):
|
|
_hcfa_with_one_vaccine(page)
|
|
page.evaluate("() => document.querySelector("
|
|
"'#billingClaimTbl8 tbody').innerHTML = ''")
|
|
data = _live(page)
|
|
assert data["insurance"] == [], data["insurance"]
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
assert vac.detect("9103000", data) is False
|
|
assert "no primary payer name was read" in out.getvalue(), \
|
|
("the log must blame the read, not payer_groups.json", out.getvalue())
|
|
|
|
|
|
@case
|
|
def run_all_fires_only_this_condition_on_the_fixture(page):
|
|
_fresh(page)
|
|
data = _live(page)
|
|
results = run_all(page, "9113000", data)
|
|
assert [r.condition_name for r in results] == ["vaccine_admin_codes"], results
|
|
|
|
|
|
# =============================================================================
|
|
# What the Charge Summary says (final review, 2026-10-05)
|
|
# =============================================================================
|
|
|
|
def _change(code, added):
|
|
return {"code": code, "added": added, "units": 1, "wanted_units": 1,
|
|
"billed_fee": "10.00", "error": "" if added else "not found",
|
|
"grid_changed": None if added else True}
|
|
|
|
|
|
@case
|
|
def summary_comment_says_which_admin_code_was_added_and_which_was_not():
|
|
"""
|
|
The fixed "( vaccine admin code added )" read "added" next to the
|
|
review reason saying the code could NOT be added.
|
|
"""
|
|
sc = vac.summary_comment
|
|
mk = lambda *c: ConditionResult("vaccine_admin_codes", True, list(c))
|
|
assert sc(mk(_change("90471", True), _change("90472", True))) == \
|
|
"CPT Code Missing (vaccine admin code 90471 added; 90472 added)"
|
|
assert sc(mk(_change("90471", False))) == \
|
|
"CPT Code Missing (vaccine admin code 90471 NOT added — add by hand)"
|
|
assert sc(mk(_change("90471", True), _change("90472", False))) == (
|
|
"CPT Code Missing (vaccine admin code 90471 added; 90472 NOT added "
|
|
"— add by hand)")
|
|
|
|
|
|
@case
|
|
def the_charge_summary_row_no_longer_says_added_for_a_failed_add():
|
|
"""End to end through charge_summary.claim_outcome, both cases."""
|
|
from ecw_bot.core import charge_summary as cs
|
|
for changes, review in (
|
|
([_change("90471", False)],
|
|
"90471 could not be added cleanly — check the CPT grid by hand"),
|
|
([_change("90471", True), _change("90472", False)],
|
|
"90472 could not be added cleanly — check the CPT grid by hand")):
|
|
result = ConditionResult("vaccine_admin_codes", True, changes,
|
|
review=review)
|
|
data = {"cpt": [{"code": "90686"}], "icd": [{"code": "Z23"}],
|
|
"progress_notes": {"opened": "frame", "sections": {}},
|
|
"_conditions": {
|
|
"fired": ["vaccine_admin_codes"], "saved": False,
|
|
"failed": [],
|
|
"review": [{"name": "vaccine_admin_codes",
|
|
"reason": review}],
|
|
"comments": {"vaccine_admin_codes":
|
|
vac.summary_comment(result)}}}
|
|
o = cs.claim_outcome(True, data)
|
|
assert o["Category"] == "Review", o
|
|
assert "vaccine admin code added" not in o["Comments"], o
|
|
assert "NOT added — add by hand" in o["Comments"], o
|
|
|
|
|
|
@case
|
|
def a_live_grid_that_reads_empty_after_a_full_first_read_is_declined():
|
|
"""
|
|
Final review, 2026-10-05: detect() found billed vaccines in the first
|
|
read, but the live re-read came back empty — declined, not "Nothing to
|
|
add" (none(), which the summary reads as Pass).
|
|
"""
|
|
from ecw_bot.conditions.base import CPT_GRID_NOT_READ
|
|
orig = (claim_form.open_cpt_hcpcs_tab, claim_form.extract)
|
|
claim_form.open_cpt_hcpcs_tab = lambda page: True
|
|
claim_form.extract = lambda page: {"cpt": []}
|
|
try:
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
result = vac.apply(None, "9131000",
|
|
{"cpt": [_row("90686", "Flu vaccine")]})
|
|
finally:
|
|
claim_form.open_cpt_hcpcs_tab, claim_form.extract = orig
|
|
assert result == ConditionResult.declined("vaccine_admin_codes",
|
|
CPT_GRID_NOT_READ), result
|
|
|
|
|
|
def _run_cases() -> int:
|
|
if not FIXTURE.exists():
|
|
print(f" FAIL fixture missing: {FIXTURE}")
|
|
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)
|
|
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:
|
|
from ecw_bot import settings as _cfg
|
|
# 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.
|
|
orig = _cfg.SAVE_CORRECTIONS
|
|
_cfg.SAVE_CORRECTIONS = True
|
|
try:
|
|
return _run_cases()
|
|
finally:
|
|
_cfg.SAVE_CORRECTIONS = orig
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|