835 lines
36 KiB
Python
835 lines
36 KiB
Python
r"""
|
|
conditions/zero_fee_uncheck.py — the operator's common rule for every
|
|
claim (2026-09-30): a CPT line that is checked but billed at zero gets
|
|
unchecked. Only that direction here — an unchecked line is never touched by
|
|
this condition; the reverse half (unchecked with a billed fee -> checked)
|
|
is conditions/priced_line_check.py's, registered last, and tested in
|
|
test_conditions_priced_line_check.py.
|
|
|
|
python tests\test_conditions_zero_fee_uncheck.py
|
|
|
|
Pure-data cases first (the shared zero-fee predicate, then detect()), then
|
|
page cases against BOTH claim screens: hcfa_claim.html, whose own rows carry
|
|
the live-verified "Assign To Patient" checkbox (`ng-click="updateBillToIns(
|
|
$index)"`), and ub_claim.html, whose own rows carry a plain checkbox with no
|
|
ng-click at all — so on the UB form a UB-style row is injected with the
|
|
checkbox vaccine_claim.html's rows use (`id="claimCPTBillToInsChk..."
|
|
ng-click="updateBillToIns(n)"`), and the fixture's own rows stand in for a
|
|
row whose checkbox state is unknown.
|
|
|
|
Rows are injected with page.evaluate, the same way
|
|
test_conditions_depression_dx_pointer.py's `_inject_duplicate_target_row`
|
|
adds its row, rather than growing either shared fixture and its row-count
|
|
assumptions (`CPT/HCPCS (3)`, `len(cpt) == 3`, ...).
|
|
|
|
The live evidence this rule was built against: of 39 saved claim snapshots,
|
|
exactly one checked zero-fee line — 85018 (Hemoglobin) at 0.00 — which is
|
|
the code the injected rows below use.
|
|
|
|
The last two cases are where this rule meets another condition's own rule
|
|
— depression's choice of which G8510 to keep, and vaccine_admin_codes'
|
|
checked-admin test on vaccine_claim.html. Both used to be PINNED as known
|
|
gaps; the operator ruled on both on 2026-09-30, and they now assert the
|
|
ruled outcome: the priced G8510 is the one kept, and a checked zero-fee
|
|
admin line no longer counts as "admin already billed".
|
|
"""
|
|
|
|
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" / "hcfa_claim.html"
|
|
UB_FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "ub_claim.html"
|
|
VACCINE_FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "vaccine_claim.html"
|
|
|
|
from ecw_bot import conditions # noqa: E402
|
|
from ecw_bot.conditions import zero_fee_uncheck as zfu # noqa: E402
|
|
from ecw_bot.conditions import (bh_h_code_modifier, # noqa: E402
|
|
dental_dummy_codes,
|
|
dental_payer_check,
|
|
depression_dx_pointer, dummy_codes,
|
|
injection_admin_code,
|
|
medicare_pps_code,
|
|
j2930_j2001_update, lab_codes, medicaid_uncheck_cpts,
|
|
t1017_non_billable,
|
|
medicare_billing_screening,
|
|
medicare_telehealth,
|
|
medicare_vaccine_a6, medicare_wellness_history,
|
|
medicaid_pregnancy_th,
|
|
priced_line_check, telehealth,
|
|
vaccine_admin_codes, vaccine_z23_pointer)
|
|
from ecw_bot.conditions.base import ( # noqa: E402
|
|
CPT_TAB_NOT_CONFIRMED, ConditionResult, run_all)
|
|
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
|
|
|
|
|
|
_HEMOGLOBIN = "85018"
|
|
|
|
|
|
def _cpt(code, billed=True, fee="0.00"):
|
|
"""One `extract()["cpt"]` row, cut down to what detect() reads."""
|
|
return {"code": code, "billed": billed, "billed_fee": fee}
|
|
|
|
|
|
def _inject_row(page, code, row_id, *, screen="hcfa", checked=True,
|
|
fee="0.00", has_checkbox=True, has_fee=True):
|
|
"""
|
|
Adds one real CPT row (a `CPTROW_` id) above the grid's trailing "*"
|
|
entry row. `screen` picks the row's own markup:
|
|
|
|
* "hcfa" — hcfa_claim.html's shape: `data-fieldname` fields.
|
|
* "ub" — the UB form's `uBClaim*<n>` id prefixes, with the
|
|
"Assign To Patient" checkbox vaccine_claim.html's rows carry
|
|
(ub_claim.html's own rows have none that SEL_CPT_ASSIGN_TO_PATIENT
|
|
matches).
|
|
|
|
`has_checkbox` / `has_fee` drop the checkbox or the Billed Fee input
|
|
entirely — the rows whose state is unknown, which must be left alone.
|
|
"""
|
|
page.evaluate(
|
|
"""(a) => {
|
|
const n = a.rowId.replace('CPTROW_', '');
|
|
const tbody = document.querySelector(a.screen === 'ub'
|
|
? '#claimCPTCodesTable9999999999999 tbody'
|
|
: '#claimCPTCodesTable8888888888888 tbody');
|
|
const entryRow = tbody.querySelector('tr:last-child');
|
|
const esc = s => String(s).replace(/&/g, '&').replace(/"/g, '"');
|
|
const box = !a.hasCheckbox ? '' : (a.screen === 'ub'
|
|
? '<input type="checkbox" id="claimCPTBillToInsChk9999999999999' + n + '"'
|
|
+ ' ng-click="updateBillToIns(' + n + ')"'
|
|
: '<input type="checkbox" ng-click="updateBillToIns(' + n + ')"')
|
|
+ (a.checked ? ' checked' : '') + '>';
|
|
const codeInput = a.screen === 'ub'
|
|
? '<input id="uBClaimCPTCode' + n + '" type="text" value="' + esc(a.code) + '" readonly>'
|
|
: '<input data-fieldname="claimCPTCode" type="text" value="' + esc(a.code) + '" readonly>';
|
|
const feeInput = !a.hasFee ? '' : (a.screen === 'ub'
|
|
? '<input type="text" id="uBClaimBillFee' + n + '" value="' + esc(a.fee) + '">'
|
|
: '<input data-fieldname="ClaimCPTBilledFee" type="text" value="' + esc(a.fee) + '">');
|
|
const tr = document.createElement('tr');
|
|
tr.id = a.rowId;
|
|
tr.setAttribute('ng-repeat', 'cpt in CPTCodes');
|
|
tr.innerHTML = '<td>' + box + '</td><td>' + codeInput + '</td>'
|
|
+ '<td>' + feeInput + '</td>';
|
|
tbody.insertBefore(tr, entryRow);
|
|
}""", {"code": code, "rowId": row_id, "screen": screen,
|
|
"checked": checked, "fee": fee,
|
|
"hasCheckbox": has_checkbox, "hasFee": has_fee})
|
|
|
|
|
|
def _set_row(page, row_id, *, checked=None, fee=None):
|
|
"""Sets one existing row's checkbox and/or Billed Fee in place."""
|
|
page.evaluate(
|
|
"""(a) => {
|
|
const tr = document.getElementById(a.rowId);
|
|
if (a.checked !== null)
|
|
tr.querySelector('[ng-click*="updateBillToIns("]').checked = a.checked;
|
|
if (a.fee !== null)
|
|
tr.querySelector('[id^="uBClaimBillFee"], [data-fieldname="ClaimCPTBilledFee"]')
|
|
.value = a.fee;
|
|
}""", {"rowId": row_id, "checked": checked, "fee": fee})
|
|
|
|
|
|
def _count_checkbox_clicks(page, row_id):
|
|
"""
|
|
Counts clicks on one row's "Assign To Patient" checkbox, read back as
|
|
`window.clicksOn[row_id]` — the same helper
|
|
test_conditions_depression_dx_pointer.py uses. On the live screen that
|
|
click is what fires eCW's own `updateBillToIns()`, so a row this rule
|
|
must leave alone should never receive one at all.
|
|
"""
|
|
page.evaluate(
|
|
"""(rowId) => {
|
|
window.clicksOn = window.clicksOn || {};
|
|
window.clicksOn[rowId] = 0;
|
|
document.querySelector('#' + rowId + ' [ng-click*="updateBillToIns("]')
|
|
.addEventListener('click', () => { window.clicksOn[rowId] += 1; });
|
|
}""", row_id)
|
|
|
|
|
|
def _watch_fee_writes(page):
|
|
"""
|
|
Counts `input`/`change` events on every Billed Fee input on the grid as
|
|
`window.feeWrites` — Playwright's fill() fires both, so a zero here
|
|
proves the fee was never written, not merely left with the same value.
|
|
Call after every row has been injected.
|
|
"""
|
|
page.evaluate(
|
|
"""(s) => {
|
|
window.feeWrites = 0;
|
|
document.querySelectorAll('tr[id^="CPTROW_"]').forEach(tr => {
|
|
tr.querySelectorAll(s).forEach(el => {
|
|
el.addEventListener('input', () => { window.feeWrites += 1; });
|
|
el.addEventListener('change', () => { window.feeWrites += 1; });
|
|
});
|
|
});
|
|
}""", sel.SEL_CPT_BILLED_FEE)
|
|
|
|
|
|
def _row_state(page, index):
|
|
"""(checked or None, fee or None) for the index-th real CPT row."""
|
|
row = page.locator(sel.SEL_CPT_ROWS).nth(index)
|
|
box = row.locator(sel.SEL_CPT_ASSIGN_TO_PATIENT).first
|
|
fee = row.locator(sel.SEL_CPT_BILLED_FEE).first
|
|
return (box.is_checked() if box.count() else None,
|
|
fee.input_value() if fee.count() else None)
|
|
|
|
|
|
# =============================================================================
|
|
# The shared zero-fee predicate
|
|
# =============================================================================
|
|
|
|
@case
|
|
def is_zero_fee_reads_blank_and_every_zero_spelling_as_zero():
|
|
for value in ("", " ", None, "0", "0.00", "0.0", ".00", " 0.00 ",
|
|
"$0.00", "0,000.00"):
|
|
assert claim_form.is_zero_fee(value) is True, repr(value)
|
|
|
|
|
|
@case
|
|
def is_zero_fee_reads_a_priced_or_non_numeric_fee_as_not_zero():
|
|
for value in ("65.00", "0.01", "$1,200.00", "1,200.00", "N/A", "abc",
|
|
"--", "0.00 USD"):
|
|
assert claim_form.is_zero_fee(value) is False, repr(value)
|
|
|
|
|
|
# =============================================================================
|
|
# detect()
|
|
# =============================================================================
|
|
|
|
@case
|
|
def detect_true_for_a_checked_line_billed_at_0_00():
|
|
# The one live case: 85018 (Hemoglobin), checked, at 0.00.
|
|
data = {"cpt": [_cpt("99213", fee="210.00"), _cpt(_HEMOGLOBIN, fee="0.00")]}
|
|
assert zfu.detect("C1", data) is True
|
|
|
|
|
|
@case
|
|
def detect_true_for_a_checked_line_with_a_blank_fee():
|
|
assert zfu.detect("C1", {"cpt": [_cpt(_HEMOGLOBIN, fee="")]}) is True
|
|
|
|
|
|
@case
|
|
def detect_true_for_a_checked_line_billed_at_plain_0():
|
|
assert zfu.detect("C1", {"cpt": [_cpt(_HEMOGLOBIN, fee="0")]}) is True
|
|
|
|
|
|
@case
|
|
def detect_false_for_an_unchecked_line_at_0_00():
|
|
# Already excluded — this condition never touches an unchecked line; the
|
|
# reverse half (unchecked AND priced -> checked) is priced_line_check's,
|
|
# and 0.00 is not priced anyway.
|
|
data = {"cpt": [_cpt("99213", fee="210.00"),
|
|
_cpt("G8510", billed=False, fee="0.00")]}
|
|
assert zfu.detect("C1", data) is False
|
|
|
|
|
|
@case
|
|
def detect_false_when_the_checkbox_state_is_unknown():
|
|
# billed None: extract() found no "Assign To Patient" checkbox on the row.
|
|
assert zfu.detect("C1", {"cpt": [_cpt(_HEMOGLOBIN, billed=None, fee="0.00")]}) is False
|
|
assert zfu.detect("C1", {"cpt": [{"code": _HEMOGLOBIN, "billed_fee": "0.00"}]}) is False
|
|
|
|
|
|
@case
|
|
def detect_false_for_a_checked_line_whose_fee_was_never_read():
|
|
# billed_fee None: extract() found no Billed Fee input on the row.
|
|
# apply() (uncheck_zero_fee_cpt_rows) leaves such a row alone, so
|
|
# detect() must not fire on it either — unknown is not zero (review
|
|
# finding, 2026-09-30).
|
|
assert zfu.detect("C1", {"cpt": [_cpt(_HEMOGLOBIN, fee=None)]}) is False
|
|
|
|
|
|
@case
|
|
def detect_false_for_a_checked_priced_line():
|
|
data = {"cpt": [_cpt("99213", fee="210.00"), _cpt("J7298", fee="1200.00")]}
|
|
assert zfu.detect("C1", data) is False
|
|
|
|
|
|
@case
|
|
def detect_false_for_a_non_numeric_fee():
|
|
assert zfu.detect("C1", {"cpt": [_cpt(_HEMOGLOBIN, fee="N/A")]}) is False
|
|
|
|
|
|
@case
|
|
def detect_false_with_no_cpt_lines():
|
|
assert zfu.detect("C1", {"cpt": []}) is False
|
|
assert zfu.detect("C1", {}) is False
|
|
assert zfu.detect("C1", None) is False
|
|
|
|
|
|
# =============================================================================
|
|
# apply() — the professional/HCFA screen
|
|
# =============================================================================
|
|
|
|
@case
|
|
def apply_unchecks_a_checked_zero_fee_line_on_the_hcfa_screen(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, fee="0.00")
|
|
_count_checkbox_clicks(page, "CPTROW_3")
|
|
_watch_fee_writes(page)
|
|
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert isinstance(result, ConditionResult)
|
|
assert result.condition_name == "zero_fee_uncheck"
|
|
assert result.changed is True
|
|
assert result.changes == [{"row": 3, "code": _HEMOGLOBIN, "fee": "0.00"}], result.changes
|
|
|
|
assert _row_state(page, 3) == (False, "0.00")
|
|
assert page.evaluate("window.clicksOn['CPTROW_3']") == 1
|
|
# Priced lines keep their checkbox and fee; G8510 (unchecked, 65.00) too.
|
|
assert _row_state(page, 0) == (True, "210.00")
|
|
assert _row_state(page, 1) == (True, "1200.00")
|
|
assert _row_state(page, 2) == (False, "65.00")
|
|
assert page.evaluate("window.feeWrites") == 0, "the Billed Fee was written"
|
|
|
|
# A second pass has nothing left to uncheck.
|
|
assert zfu.apply(page, "9103000", {}).changed is False
|
|
|
|
|
|
@case
|
|
def apply_leaves_priced_and_unchecked_lines_alone(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, "G8510", "CPTROW_3", checked=False, fee="0.00")
|
|
_count_checkbox_clicks(page, "CPTROW_3")
|
|
_count_checkbox_clicks(page, "CPTROW_0")
|
|
_watch_fee_writes(page)
|
|
|
|
result = zfu.apply(page, "9131000", {})
|
|
assert result.changed is False, result.changes
|
|
assert result.changes == [], result.changes
|
|
|
|
assert _row_state(page, 0) == (True, "210.00")
|
|
assert _row_state(page, 1) == (True, "1200.00")
|
|
assert _row_state(page, 2) == (False, "65.00")
|
|
assert _row_state(page, 3) == (False, "0.00")
|
|
assert page.evaluate("window.clicksOn['CPTROW_3']") == 0, \
|
|
"an unchecked line must never be clicked — updateBillToIns() would fire"
|
|
assert page.evaluate("window.clicksOn['CPTROW_0']") == 0
|
|
assert page.evaluate("window.feeWrites") == 0
|
|
|
|
|
|
@case
|
|
def apply_unchecks_every_zero_spelling_and_never_writes_the_fee(page):
|
|
"""
|
|
Blank, "0", ".00" and "0.00" are all zero — each line is unchecked, and
|
|
its fee is reported and left exactly as read: never normalised to 0.00,
|
|
never filled, never blurred.
|
|
"""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, "85018", "CPTROW_3", fee="")
|
|
_inject_row(page, "85025", "CPTROW_4", fee="0")
|
|
_inject_row(page, "36415", "CPTROW_5", fee=".00")
|
|
_inject_row(page, "81002", "CPTROW_6", fee="0.00")
|
|
_watch_fee_writes(page)
|
|
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert result.changes == [
|
|
{"row": 3, "code": "85018", "fee": ""},
|
|
{"row": 4, "code": "85025", "fee": "0"},
|
|
{"row": 5, "code": "36415", "fee": ".00"},
|
|
{"row": 6, "code": "81002", "fee": "0.00"},
|
|
], result.changes
|
|
assert [_row_state(page, i) for i in (3, 4, 5, 6)] == [
|
|
(False, ""), (False, "0"), (False, ".00"), (False, "0.00")]
|
|
assert page.evaluate("window.feeWrites") == 0, "the Billed Fee was written"
|
|
|
|
|
|
@case
|
|
def apply_leaves_a_non_numeric_fee_alone(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, fee="N/A")
|
|
_count_checkbox_clicks(page, "CPTROW_3")
|
|
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert result.changed is False, result.changes
|
|
assert _row_state(page, 3) == (True, "N/A")
|
|
assert page.evaluate("window.clicksOn['CPTROW_3']") == 0
|
|
|
|
|
|
@case
|
|
def apply_leaves_a_checked_row_with_no_fee_input_alone(page):
|
|
# No Billed Fee input at all: the fee is unknown, not zero.
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, has_fee=False)
|
|
_count_checkbox_clicks(page, "CPTROW_3")
|
|
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert result.changed is False, result.changes
|
|
assert _row_state(page, 3) == (True, None)
|
|
assert page.evaluate("window.clicksOn['CPTROW_3']") == 0
|
|
|
|
|
|
@case
|
|
def apply_leaves_a_zero_fee_row_with_no_checkbox_alone(page):
|
|
# No "Assign To Patient" checkbox: whether it is billed is unknown.
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", fee="0.00", has_checkbox=False)
|
|
_watch_fee_writes(page)
|
|
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert result.changed is False, result.changes
|
|
assert _row_state(page, 3) == (None, "0.00")
|
|
assert page.evaluate("window.feeWrites") == 0
|
|
|
|
|
|
# =============================================================================
|
|
# apply() — the UB Claim form
|
|
# =============================================================================
|
|
|
|
@case
|
|
def apply_unchecks_a_checked_zero_fee_line_on_the_ub_form(page):
|
|
page.goto(UB_FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_2", screen="ub", checked=True, fee="0.00")
|
|
_inject_row(page, "90791", "CPTROW_3", screen="ub", checked=True, fee="150.00")
|
|
_inject_row(page, "G8510", "CPTROW_4", screen="ub", checked=False, fee="0.00")
|
|
_count_checkbox_clicks(page, "CPTROW_2")
|
|
_count_checkbox_clicks(page, "CPTROW_3")
|
|
_count_checkbox_clicks(page, "CPTROW_4")
|
|
_watch_fee_writes(page)
|
|
|
|
result = zfu.apply(page, "9104000", {})
|
|
assert result.changes == [{"row": 2, "code": _HEMOGLOBIN, "fee": "0.00"}], result.changes
|
|
|
|
assert _row_state(page, 2) == (False, "0.00")
|
|
assert _row_state(page, 3) == (True, "150.00")
|
|
assert _row_state(page, 4) == (False, "0.00")
|
|
assert page.evaluate("window.clicksOn['CPTROW_2']") == 1
|
|
assert page.evaluate("window.clicksOn['CPTROW_3']") == 0
|
|
assert page.evaluate("window.clicksOn['CPTROW_4']") == 0
|
|
assert page.evaluate("window.feeWrites") == 0, "the Billed Fee was written"
|
|
|
|
|
|
@case
|
|
def apply_leaves_ub_rows_without_the_assign_to_patient_checkbox_alone(page):
|
|
"""
|
|
ub_claim.html's own rows carry a plain checkbox with no
|
|
`updateBillToIns(` ng-click — not the live-verified "Assign To Patient"
|
|
box — so their billed state is unknown, and a 0.00 fee on one of them
|
|
must not be acted on.
|
|
"""
|
|
page.goto(UB_FIXTURE.as_uri())
|
|
page.evaluate("() => { document.getElementById('uBClaimBillFee0').value = '0.00'; }")
|
|
_watch_fee_writes(page)
|
|
|
|
result = zfu.apply(page, "9104000", {})
|
|
assert result.changed is False, result.changes
|
|
assert page.locator("#claimCPTBillToInsChk0").is_checked() is True
|
|
assert page.locator("#uBClaimBillFee0").input_value() == "0.00"
|
|
assert page.evaluate("window.feeWrites") == 0
|
|
|
|
|
|
# =============================================================================
|
|
# apply() — no change, and a tab that cannot be confirmed
|
|
# =============================================================================
|
|
|
|
@case
|
|
def apply_logs_why_when_there_is_nothing_to_uncheck(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert result == ConditionResult.none("zero_fee_uncheck"), result
|
|
logged = out.getvalue()
|
|
assert "[COND zero_fee_uncheck 9103000]" in logged, logged
|
|
assert "nothing to uncheck" in logged, logged
|
|
|
|
# Only on that path — not when a line was actually unchecked.
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, fee="0.00")
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
result = zfu.apply(page, "9103000", {})
|
|
assert result.changed is True, result.changes
|
|
assert "nothing to uncheck" not in out.getvalue(), out.getvalue()
|
|
|
|
|
|
@case
|
|
def apply_leaves_the_claim_as_read_when_the_cpt_tab_cannot_be_confirmed(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, fee="0.00")
|
|
|
|
def tab_that_raises(page_):
|
|
raise RuntimeError("No CPT/HCPCS tab matched (simulated)")
|
|
|
|
real = claim_form.open_cpt_hcpcs_tab
|
|
claim_form.open_cpt_hcpcs_tab = tab_that_raises
|
|
out = io.StringIO()
|
|
try:
|
|
with contextlib.redirect_stdout(out):
|
|
result = zfu.apply(page, "9103000", {})
|
|
finally:
|
|
claim_form.open_cpt_hcpcs_tab = real
|
|
assert result.changed is False, result.changes
|
|
assert _row_state(page, 3) == (True, "0.00"), "nothing may be touched"
|
|
assert "leaving the claim as read" in out.getvalue(), out.getvalue()
|
|
# Declined, not none(): never checked is not clean (review item D2).
|
|
assert result.review == CPT_TAB_NOT_CONFIRMED, result
|
|
|
|
|
|
# =============================================================================
|
|
# describe()
|
|
# =============================================================================
|
|
|
|
@case
|
|
def describe_names_each_unchecked_line_and_its_fee(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, fee="0.00")
|
|
result = zfu.apply(page, "9103000", {})
|
|
text = zfu.describe(result)
|
|
lines = text.splitlines()
|
|
assert lines[0] == "Checked CPT line(s) with a zero billed fee unchecked:", text
|
|
assert lines[1:] == [" Row 4 (85018): checked with billed fee 0.00 -> unchecked"], text
|
|
|
|
|
|
@case
|
|
def describe_says_blank_rather_than_printing_nothing():
|
|
result = ConditionResult(condition_name="zero_fee_uncheck", changed=True,
|
|
changes=[{"row": 0, "code": _HEMOGLOBIN, "fee": ""}])
|
|
text = zfu.describe(result)
|
|
assert " Row 1 (85018): checked with billed fee (blank) -> unchecked" in text, text
|
|
|
|
|
|
# =============================================================================
|
|
# The registry
|
|
# =============================================================================
|
|
|
|
@case
|
|
def registered_first_in_conditions():
|
|
"""
|
|
First, so it runs on the untouched claim and every specific condition
|
|
runs after it on a consistent grid and has the last word — and the
|
|
specific conditions keep their own order behind it. The one entry
|
|
after them is this rule's reverse half, priced_line_check, registered
|
|
LAST (2026-09-30) so it only ever sees a line an exclusion condition
|
|
has already zeroed — see test_conditions_priced_line_check.py.
|
|
"""
|
|
assert conditions.CONDITIONS[0] is zfu, conditions.CONDITIONS
|
|
assert conditions.CONDITIONS[1:] == [
|
|
telehealth, medicare_telehealth, depression_dx_pointer,
|
|
j2930_j2001_update, dummy_codes, dental_dummy_codes,
|
|
dental_payer_check, medicaid_uncheck_cpts, lab_codes,
|
|
t1017_non_billable,
|
|
medicare_billing_screening, medicare_vaccine_a6,
|
|
medicare_wellness_history, medicaid_pregnancy_th,
|
|
bh_h_code_modifier, vaccine_z23_pointer, vaccine_admin_codes,
|
|
injection_admin_code,
|
|
medicare_pps_code,
|
|
priced_line_check,
|
|
], conditions.CONDITIONS
|
|
assert conditions.BY_NAME["zero_fee_uncheck"] is zfu
|
|
assert zfu.NAME == "zero_fee_uncheck"
|
|
|
|
|
|
# =============================================================================
|
|
# run_all() against the REAL registry
|
|
# =============================================================================
|
|
|
|
@case
|
|
def run_all_fires_zero_fee_uncheck_alone_on_a_plain_zero_fee_line(page):
|
|
"""
|
|
Against the REAL registry, not fakes — proves this condition is wired
|
|
into conditions.CONDITIONS, and that a plain zero-fee line sets off
|
|
nothing else. `icd` is left out of the data on purpose: the fixture
|
|
carries Z13.31, which would fire depression_dx_pointer on its own
|
|
account (the next case covers the two together). And the fixture's own
|
|
G8510 is checked first: as served it sits unchecked at 65.00, which
|
|
priced_line_check (the reverse half, 2026-09-30) would check on its
|
|
own account too.
|
|
"""
|
|
page.goto(FIXTURE.as_uri())
|
|
_set_row(page, "CPTROW_2", checked=True)
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, fee="0.00")
|
|
data = {"cpt": claim_form.extract(page)["cpt"]}
|
|
results = run_all(page, "9103000", data)
|
|
assert [r.condition_name for r in results] == ["zero_fee_uncheck"], results
|
|
assert results[0].changes == [{"row": 3, "code": _HEMOGLOBIN, "fee": "0.00"}]
|
|
|
|
|
|
@case
|
|
def run_all_unchecks_a_zero_fee_g8510_and_depression_does_not_re_check_it(page):
|
|
"""
|
|
The toggle this rule would otherwise start — the same check/uncheck
|
|
shape fixed for claim 9131000: the first G8510 is checked at 0.00 and
|
|
the claim carries Z13.31, so both zero_fee_uncheck and
|
|
depression_dx_pointer fire. zero_fee_uncheck runs first and unchecks
|
|
it; depression's own "re-include the first G8510" step must then leave
|
|
it unchecked, because a zero-fee line is never billed. Before, it
|
|
checked it straight back: two contradictory changes reported for one
|
|
row, and updateBillToIns() fired twice on a line that must stay out.
|
|
"""
|
|
page.goto(FIXTURE.as_uri())
|
|
_set_row(page, "CPTROW_2", checked=True, fee="0.00")
|
|
_count_checkbox_clicks(page, "CPTROW_2")
|
|
data = claim_form.extract(page)
|
|
assert zfu.detect("9103000", data) is True
|
|
assert depression_dx_pointer.detect("9103000", data) is True
|
|
|
|
results = run_all(page, "9103000", data)
|
|
names = [r.condition_name for r in results]
|
|
assert names == ["zero_fee_uncheck", "depression_dx_pointer"], names
|
|
assert results[0].changes == [{"row": 2, "code": "G8510", "fee": "0.00"}], results[0].changes
|
|
|
|
depression_checks = [c for c in results[1].changes
|
|
if "before" not in c and "unchecked" not in c and "fee" not in c]
|
|
assert depression_checks == [], ("depression re-checked the zero-fee G8510",
|
|
results[1].changes)
|
|
# Its pointer is still retagged — only the check is withheld.
|
|
retagged = {c["row"] for c in results[1].changes if "before" in c}
|
|
assert retagged == {0, 2}, results[1].changes
|
|
|
|
assert _row_state(page, 2) == (False, "0.00")
|
|
assert page.evaluate("window.clicksOn['CPTROW_2']") == 1, \
|
|
"the G8510 checkbox was clicked more than once — a check/uncheck toggle"
|
|
|
|
|
|
# =============================================================================
|
|
# run_all() — where the rule meets another condition's own rule. Both were
|
|
# PINNED as known gaps until the operator ruled on them (2026-09-30); each
|
|
# now asserts the ruled outcome. See zero_fee_uncheck.py's module docstring.
|
|
# =============================================================================
|
|
|
|
def _inject_full_g8510_row(page, row_id, *, checked, fee):
|
|
"""
|
|
A second G8510 on hcfa_claim.html, after the fixture's own (CPTROW_2),
|
|
carrying all four ICD pointer fields — depression_dx_pointer's retag
|
|
step reads every one, the same as
|
|
test_conditions_depression_dx_pointer.py's `_inject_duplicate_target_row`.
|
|
"""
|
|
page.evaluate(
|
|
"""(a) => {
|
|
const tbody = document.querySelector(
|
|
'#claimCPTCodesTable8888888888888 tbody');
|
|
const entryRow = tbody.querySelector('tr:last-child');
|
|
const n = a.rowId.replace('CPTROW_', '');
|
|
const tr = document.createElement('tr');
|
|
tr.id = a.rowId;
|
|
tr.innerHTML =
|
|
'<td><input data-fieldname="claimCPTCode" value="G8510" readonly></td>'
|
|
+ '<td><input type="checkbox" ng-click="updateBillToIns(' + n + ')"'
|
|
+ (a.checked ? ' checked' : '') + '></td>'
|
|
+ '<td><input data-fieldname="ClaimCPTICD1" value="1"></td>'
|
|
+ '<td><input data-fieldname="ClaimCPTICD2" value=""></td>'
|
|
+ '<td><input data-fieldname="ClaimCPTICD3" value=""></td>'
|
|
+ '<td><input data-fieldname="ClaimCPTICD4" value=""></td>'
|
|
+ '<td><input data-fieldname="ClaimCPTBilledFee" value="' + a.fee + '"></td>';
|
|
tbody.insertBefore(tr, entryRow);
|
|
}""", {"rowId": row_id, "checked": checked, "fee": fee})
|
|
|
|
|
|
@case
|
|
def run_all_keeps_the_priced_g8510_when_the_first_copy_is_zero_fee(page):
|
|
"""
|
|
Operator ruling (2026-09-30), flipped from a PINNED gap. The first
|
|
G8510 is checked at 0.00 and a later G8510 checked at 65.00 — a shape
|
|
none of the 39 snapshots shows. zero_fee_uncheck unchecks the 0.00
|
|
copy. depression then keeps the first G8510 whose fee is NOT zero —
|
|
the 65.00 one — so its re-include step has nothing to check (it is
|
|
already checked) and its duplicate step has nothing to do on the 0.00
|
|
copy (already unchecked, already 0.00). The priced G8510 stays billed
|
|
with its fee; before the ruling it was zeroed, and no G8510 was left
|
|
billed at all.
|
|
|
|
Without zero_fee_uncheck in the registry, depression reaches the same
|
|
end state on its own: the 0.00 copy is now the duplicate, and its
|
|
duplicate step unchecks it.
|
|
"""
|
|
def claim():
|
|
page.goto(FIXTURE.as_uri())
|
|
_set_row(page, "CPTROW_2", checked=True, fee="0.00")
|
|
_inject_full_g8510_row(page, "CPTROW_3", checked=True, fee="65.00")
|
|
_count_checkbox_clicks(page, "CPTROW_2")
|
|
_count_checkbox_clicks(page, "CPTROW_3")
|
|
return claim_form.extract(page)
|
|
|
|
results = run_all(page, "9103000", claim())
|
|
names = [r.condition_name for r in results]
|
|
assert names == ["zero_fee_uncheck", "depression_dx_pointer"], names
|
|
assert results[0].changes == [{"row": 2, "code": "G8510", "fee": "0.00"}], \
|
|
results[0].changes
|
|
not_retags = [c for c in results[1].changes if "before" not in c]
|
|
assert not_retags == [], ("depression checked, unchecked or zeroed a "
|
|
"G8510", results[1].changes)
|
|
assert {c["row"] for c in results[1].changes} == {0, 2, 3}, results[1].changes
|
|
assert (_row_state(page, 2), _row_state(page, 3)) == \
|
|
((False, "0.00"), (True, "65.00")), "the priced G8510 stays billed"
|
|
assert page.evaluate("window.clicksOn") == {"CPTROW_2": 1, "CPTROW_3": 0}
|
|
|
|
# Without the rule: depression's own duplicate step unchecks the 0.00
|
|
# copy, the priced one untouched.
|
|
results = run_all(page, "9103000", claim(),
|
|
conditions=[c for c in conditions.CONDITIONS
|
|
if c is not zfu])
|
|
assert [r.condition_name for r in results] == ["depression_dx_pointer"], results
|
|
assert [c for c in results[0].changes if "before" not in c] == \
|
|
[{"row": 2, "code": "G8510", "unchecked": True}], results[0].changes
|
|
assert (_row_state(page, 2), _row_state(page, 3)) == \
|
|
((False, "0.00"), (True, "65.00"))
|
|
assert page.evaluate("window.clicksOn") == {"CPTROW_2": 1, "CPTROW_3": 0}
|
|
|
|
|
|
_TDAP = "TETANUS DIPHTHERIA TOXOIDS AND ACELLULAR PERTUSSIS VACC"
|
|
_ADMIN_1ST = "IMMUNIZATION ADMIN SINGLE OR COMBINATION"
|
|
|
|
|
|
@case
|
|
def run_all_adds_the_admin_code_when_the_only_admin_line_is_checked_at_zero(page):
|
|
"""
|
|
Operator ruling (2026-09-30), flipped from a PINNED known gap. `run_all`
|
|
runs every detect() on the claim as read, where the 90471 is CHECKED at
|
|
0.00. It used to count as "admin already billed", so vaccine_admin_codes
|
|
never fired; zero_fee_uncheck then unchecked that 90471 and the claim
|
|
left with the vaccine billed and no admin code — document #18's own
|
|
case. Now a checked zero-fee admin line does not count (zero_fee_uncheck,
|
|
first, will uncheck it before vaccine_admin_codes' apply() runs): both
|
|
fire, the 0.00 line ends unchecked, and a new 90471 is added.
|
|
|
|
The same claim with the 90471 ALREADY unchecked (live claim 9132000's
|
|
shape) reaches the same grid, with vaccine_admin_codes alone.
|
|
"""
|
|
def claim(admin_checked):
|
|
page.goto(VACCINE_FIXTURE.as_uri())
|
|
page.evaluate("rows => window.seedCptRows(rows)", [
|
|
{"code": "99396", "title": "PREVENTIVE VISIT EST AGE 40-64",
|
|
"fee": "250.00"},
|
|
{"code": "90734", "title": _TDAP, "fee": "60.00", "icd1": "2"},
|
|
{"code": "90471", "title": _ADMIN_1ST, "fee": "0.00",
|
|
"checked": admin_checked},
|
|
])
|
|
return claim_form.extract(page)
|
|
|
|
def grid():
|
|
return [(r["code"], r["billed"], r["billed_fee"])
|
|
for r in claim_form.extract(page)["cpt"]]
|
|
|
|
fixed = [("99396", True, "250.00"), ("90734", True, "60.00"),
|
|
("90471", False, "0.00"), ("90471", True, "30.00")]
|
|
|
|
data = claim(admin_checked=True)
|
|
assert vaccine_admin_codes.detect("9113000", data) is True
|
|
results = run_all(page, "9113000", data)
|
|
assert [r.condition_name for r in results] == \
|
|
["zero_fee_uncheck", "vaccine_admin_codes"], results
|
|
assert results[0].changes == [{"row": 2, "code": "90471", "fee": "0.00"}], \
|
|
results[0].changes
|
|
added = [c for c in results[1].changes if c.get("added")]
|
|
assert [c["code"] for c in added] == ["90471"], results[1].changes
|
|
assert grid() == fixed, grid()
|
|
|
|
results = run_all(page, "9113000", claim(admin_checked=False))
|
|
assert [r.condition_name for r in results] == ["vaccine_admin_codes"], results
|
|
assert grid() == fixed, grid()
|
|
|
|
|
|
@case
|
|
def run_all_does_not_fire_on_a_checked_row_with_no_fee_input(page):
|
|
# The page-level shape: extract() reports the row's fee as None, not
|
|
# "" — so detect() and apply() agree and the log never claims a
|
|
# zero-fee line that apply() then cannot find.
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, _HEMOGLOBIN, "CPTROW_3", checked=True, has_fee=False)
|
|
data = claim_form.extract(page)
|
|
row = [r for r in data["cpt"] if r["code"] == _HEMOGLOBIN]
|
|
assert [(r["billed"], r["billed_fee"]) for r in row] == [(True, None)], row
|
|
assert zfu.detect("9103000", data) is False
|
|
|
|
|
|
# =============================================================================
|
|
# PINNED, NEEDS AN OPERATOR RULING — medicare_telehealth on a claim whose
|
|
# G2025 is checked at 0.00. Today's outcome, not the settled one (review
|
|
# finding, 2026-09-30).
|
|
# =============================================================================
|
|
|
|
_MEDICARE_DATA = {
|
|
"progress_notes": {"text": "Session Setting (PoS): TeleHealth"},
|
|
"insurance": [{"Name": "Humana Medicare"}],
|
|
}
|
|
|
|
|
|
@case
|
|
def pinned_needs_ruling_a_g2025_checked_at_zero_leaves_no_line_billed(page):
|
|
"""
|
|
PINNED, NEEDS A RULING. The claim a G2025 added at 0.00 leaves for the
|
|
next run (the Fee Schedule priced it at nothing). zero_fee_uncheck,
|
|
first, unchecks the 0.00 G2025 — the common rule. medicare_telehealth
|
|
then asks `cpt_code_present("G2025")`, which ignores the checkbox,
|
|
counts G2025 as confirmed, and unchecks and zeroes every OTHER line:
|
|
the claim ends with NO billed line, which that step's own gate says it
|
|
must never do. Requiring a checked, non-zero G2025 there changes
|
|
document #22's behaviour — beyond rulings A/B/C.
|
|
"""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_row(page, "G2025", "CPTROW_3", checked=True, fee="0.00")
|
|
data = dict(_MEDICARE_DATA, cpt=claim_form.extract(page)["cpt"])
|
|
results = run_all(page, "9103000", data)
|
|
assert [r.condition_name for r in results] == ["zero_fee_uncheck", "medicare_telehealth"], results
|
|
assert results[0].changes == [{"row": 3, "code": "G2025", "fee": "0.00"}]
|
|
billed = [r["code"] for r in claim_form.extract(page)["cpt"] if r["billed"]]
|
|
assert billed == [], ("today: no line left billed", billed)
|
|
|
|
|
|
def _run_cases() -> int:
|
|
for fixture in (FIXTURE, UB_FIXTURE, VACCINE_FIXTURE):
|
|
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())
|