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>
460 lines
17 KiB
Python
460 lines
17 KiB
Python
r"""
|
|
conditions/bh_h_code_modifier.py — document #12: on a claim whose payer's
|
|
name starts with "BH", an H code line (H0001-H2037) carries no modifier;
|
|
telehealth visits are excluded (their GT stays).
|
|
|
|
python tests\test_conditions_bh_h_code_modifier.py
|
|
|
|
Uses hcfa_claim.html with an injected H code row, whose M1-M4 inputs carry
|
|
the same data-fieldnames as the fixture's own rows.
|
|
"""
|
|
|
|
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" / "hcfa_claim.html"
|
|
UB_FIXTURE = FIXTURE.with_name("ub_claim.html")
|
|
|
|
from ecw_bot.conditions import bh_h_code_modifier as bh # noqa: E402
|
|
from ecw_bot.conditions.base import 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 = []
|
|
|
|
BH = [{"Name": "BH COLORADO ACCESS"}]
|
|
MEDICAID = [{"Name": "APM2 Medicaid"}]
|
|
OFFICE_NOTE = {"opened": "tab", "text": "Office visit. Session Setting: Clinic."}
|
|
TELE_NOTE = {"opened": "tab", "text": "Session Setting (PoS): TeleHealth"}
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
def _inject_h_row(page, code="H0023", mods=("GT", "", "", ""),
|
|
row_id="CPTROW_3"):
|
|
page.evaluate(
|
|
"""(a) => {
|
|
const tbody = document.querySelector(
|
|
'#claimCPTCodesTable8888888888888 tbody');
|
|
const last = tbody.querySelector('tr:last-child');
|
|
const tr = document.createElement('tr');
|
|
tr.id = a.rowId;
|
|
let html = '<td><input type="checkbox" ng-click="updateBillToIns(3)" checked></td>'
|
|
+ '<td><input data-fieldname="claimCPTCode" value="' + a.code + '" readonly></td>'
|
|
+ '<td><input data-fieldname="ClaimCPTPOS" type="text" value="11"></td>';
|
|
a.mods.forEach((m, i) => {
|
|
html += '<td><input data-fieldname="ClaimCPTMOD' + (i + 1)
|
|
+ '" type="text" value="' + m + '"></td>';
|
|
});
|
|
html += '<td><input data-fieldname="ClaimCPTBilledFee" value="150.00"></td>';
|
|
tr.innerHTML = html;
|
|
tbody.insertBefore(tr, last);
|
|
tr.querySelectorAll('[data-fieldname^="ClaimCPTMOD"]').forEach(el =>
|
|
el.addEventListener('blur', () => { el.dataset.blurred = '1'; }));
|
|
}""", {"code": code, "mods": list(mods), "rowId": row_id})
|
|
|
|
|
|
def _mods(page, index):
|
|
row = page.locator(sel.SEL_CPT_ROWS).nth(index)
|
|
return [row.locator(s).first.input_value() for s in sel.SEL_CPT_MOD]
|
|
|
|
|
|
def _data(page, ins=BH, note=OFFICE_NOTE):
|
|
data = claim_form.extract(page)
|
|
data["insurance"] = ins
|
|
# The fixture's Z13.31 would fire depression_dx_pointer in run_all, and
|
|
# the injected row has no ICD inputs for it to read.
|
|
data["icd"] = []
|
|
if note is not None:
|
|
data["progress_notes"] = note
|
|
return data
|
|
|
|
|
|
# ── detect ───────────────────────────────────────────────────────────────────
|
|
|
|
def _cpt(code="H0023", mod1="GT", mod2=""):
|
|
return [{"code": "99213", "mod1": "25"},
|
|
{"code": code, "mod1": mod1, "mod2": mod2}]
|
|
|
|
|
|
@case
|
|
def detect_true_for_a_bh_payer_h_code_with_a_modifier():
|
|
for payer in ("BH COLORADO ACCESS", "BH ROCKY MTN HEALTH 2022",
|
|
"BH ROCKY MOUNTAIN HEALTH PLANS", " bh rock mtn ub "):
|
|
data = {"insurance": [{"Name": payer}], "cpt": _cpt(),
|
|
"progress_notes": OFFICE_NOTE}
|
|
assert bh.detect("C1", data) is True, payer
|
|
|
|
|
|
@case
|
|
def detect_true_when_only_a_later_modifier_is_set():
|
|
data = {"insurance": BH, "cpt": _cpt(mod1="", mod2="HO"),
|
|
"progress_notes": OFFICE_NOTE}
|
|
assert bh.detect("C1", data) is True
|
|
|
|
|
|
def _billed_h(billed=True, fee="150.00", mod1="GT"):
|
|
return [{"code": "99213", "billed": True, "billed_fee": "210.00"},
|
|
{"code": "H0023", "mod1": mod1, "billed": billed,
|
|
"billed_fee": fee}]
|
|
|
|
|
|
@case
|
|
def detect_true_for_a_billed_h_code_on_a_non_bh_payer():
|
|
"""
|
|
Operator, 2026-10-12: an H code is billed to BH payers only; on any
|
|
other payer it is unchecked and zeroed — telehealth or not, modifier
|
|
or not.
|
|
"""
|
|
for payer in ("Carelon Behavioral Health, Inc", "APM2 Medicaid",
|
|
"HCFA Medicare", "Rocky Mountain Health Prime"):
|
|
for note in (OFFICE_NOTE, TELE_NOTE):
|
|
for mod1 in ("GT", ""):
|
|
data = {"insurance": [{"Name": payer}],
|
|
"cpt": _billed_h(mod1=mod1), "progress_notes": note}
|
|
assert bh.detect("C1", data) is True, (payer, note, mod1)
|
|
|
|
|
|
@case
|
|
def detect_true_for_a_non_bh_ub_h_line_with_a_fee_and_no_checkbox():
|
|
data = {"insurance": MEDICAID, "cpt": _billed_h(billed=None)}
|
|
assert bh.detect("C1", data) is True
|
|
|
|
|
|
@case
|
|
def detect_false_for_a_non_bh_h_code_already_unchecked_and_zeroed():
|
|
for billed, fee in ((False, "150.00"), (None, "0.00"), (True, "0.00")):
|
|
data = {"insurance": MEDICAID, "cpt": _billed_h(billed, fee)}
|
|
assert bh.detect("C1", data) is False, (billed, fee)
|
|
|
|
|
|
@case
|
|
def detect_false_when_the_payer_was_not_read():
|
|
for ins in ([{"Name": ""}], []):
|
|
data = {"insurance": ins, "cpt": _billed_h()}
|
|
assert bh.detect("C1", data) is False, ins
|
|
|
|
|
|
@case
|
|
def detect_false_when_the_h_code_has_no_modifier():
|
|
data = {"insurance": BH, "cpt": _cpt(mod1=""),
|
|
"progress_notes": OFFICE_NOTE}
|
|
assert bh.detect("C1", data) is False
|
|
|
|
|
|
@case
|
|
def detect_false_for_codes_that_only_start_with_h():
|
|
"""Only H0001-H2037 style codes: 'H' plus four digits."""
|
|
for code in ("HCG", "H00231", "H002"):
|
|
data = {"insurance": BH, "cpt": _cpt(code=code),
|
|
"progress_notes": OFFICE_NOTE}
|
|
assert bh.detect("C1", data) is False, code
|
|
|
|
|
|
@case
|
|
def detect_false_on_a_telehealth_visit():
|
|
data = {"insurance": BH, "cpt": _cpt(), "progress_notes": TELE_NOTE}
|
|
assert bh.detect("C1", data) is False
|
|
|
|
|
|
@case
|
|
def detect_true_when_the_note_was_not_read():
|
|
"""Unknown telehealth: it fires, and apply() sends it to Review."""
|
|
for note in (None, {"opened": "disabled"}, {"opened": "absent"},
|
|
{"opened": "tab", "error": "boom"}):
|
|
data = {"insurance": BH, "cpt": _cpt()}
|
|
if note is not None:
|
|
data["progress_notes"] = note
|
|
assert bh.detect("C1", data) is True, note
|
|
|
|
|
|
# ── apply ────────────────────────────────────────────────────────────────────
|
|
|
|
@case
|
|
def apply_clears_every_modifier_on_the_h_code_row_only(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("GT", "HO", "", ""))
|
|
other_before = [_mods(page, i) for i in (0, 1, 2)]
|
|
|
|
result = bh.apply(page, "C1", _data(page))
|
|
assert result.changed is True, result
|
|
assert result.review == "", result.review
|
|
assert result.changes == [{"row": 3, "code": "H0023",
|
|
"before": ["GT", "HO", "", ""],
|
|
"after": ["", "", "", ""], "ok": True}], \
|
|
result.changes
|
|
assert _mods(page, 3) == ["", "", "", ""]
|
|
assert [_mods(page, i) for i in (0, 1, 2)] == other_before, \
|
|
"non-H-code rows must be left exactly as they were"
|
|
row = page.locator(sel.SEL_CPT_ROWS).nth(3)
|
|
assert row.locator(sel.SEL_CPT_MOD1).first.get_attribute("data-blurred") == "1", \
|
|
"M1 must be blurred so eCW's updateCPTMod commits the change"
|
|
|
|
|
|
@case
|
|
def an_empty_live_grid_declines_instead_of_passing(page):
|
|
"""Review finding 2 (2026-10-12): the grid did not re-render."""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
data = _data(page)
|
|
page.evaluate("""() => document.querySelectorAll('tr[id^="CPTROW_"]')
|
|
.forEach(tr => tr.remove())""")
|
|
result = bh.apply(page, "C1", data)
|
|
assert result.changed is False
|
|
assert result.review == bh.CPT_GRID_NOT_READ, result.review
|
|
|
|
|
|
@case
|
|
def a_fee_eCW_reprices_after_the_modifier_change_is_recorded(page):
|
|
"""Review finding 4: eCW re-prices the line on the modifier's blur."""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
page.evaluate("""() => {
|
|
const tr = document.getElementById('CPTROW_3');
|
|
const fee = tr.querySelector('[data-fieldname="ClaimCPTBilledFee"]');
|
|
tr.querySelector('[data-fieldname="ClaimCPTMOD1"]').addEventListener(
|
|
'blur', () => setTimeout(() => { fee.value = '120.00'; }, 300));
|
|
}""")
|
|
result = bh.apply(page, "C1", _data(page))
|
|
assert result.changes[0]["fee"] == {"before": "150.00",
|
|
"after": "120.00"}, result.changes
|
|
text = bh.describe(result)
|
|
assert "150.00" in text and "120.00" in text, text
|
|
|
|
|
|
@case
|
|
def a_partial_note_counts_as_not_read_on_a_bh_payer(page):
|
|
"""
|
|
Review, 2026-10-12: a note whose sections never settled may be missing
|
|
the telehealth line, so GT must not be removed on its word.
|
|
"""
|
|
partial = dict(OFFICE_NOTE, partial=True, partial_sections=0)
|
|
assert bh.detect("C1", {"insurance": BH, "cpt": _cpt(),
|
|
"progress_notes": partial}) is True
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
result = bh.apply(page, "C1", _data(page, note=partial))
|
|
assert result.changed is False
|
|
assert result.review == bh.NOTE_NOT_READ, result.review
|
|
assert _mods(page, 3)[0] == "GT"
|
|
|
|
|
|
@case
|
|
def a_modifier_eCW_puts_back_after_the_blur_is_not_removed(page):
|
|
"""
|
|
The read-back is judged after eCW has had time to react, not straight
|
|
after the blur: here the modifier comes back 300 ms later.
|
|
"""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
page.evaluate("""() => {
|
|
const m1 = document.getElementById('CPTROW_3')
|
|
.querySelector('[data-fieldname="ClaimCPTMOD1"]');
|
|
m1.addEventListener('blur', () =>
|
|
setTimeout(() => { m1.value = 'GT'; }, 300));
|
|
}""")
|
|
result = bh.apply(page, "C1", _data(page))
|
|
assert result.changes[0]["ok"] is False, result.changes
|
|
assert result.review == bh.NOT_REMOVED, result.review
|
|
assert "STILL SET" in bh.describe(result)
|
|
assert "NOT removed" in bh.summary_comment(result), \
|
|
bh.summary_comment(result)
|
|
|
|
|
|
@case
|
|
def apply_on_the_ub_form_clears_and_blurs_every_set_modifier(page):
|
|
"""The UB screen's own ids (uBClaimModN), M2 seeded as well as M1."""
|
|
page.goto(UB_FIXTURE.as_uri())
|
|
page.evaluate("""() => {
|
|
const tr = document.querySelectorAll('tr[id^="CPTROW_"]')[0];
|
|
const m2 = tr.querySelector('[id^="uBClaimMod2"]');
|
|
m2.value = 'HO';
|
|
for (const el of tr.querySelectorAll('[id^="uBClaimMod"]'))
|
|
el.addEventListener('blur', () => { el.dataset.blurred = '1'; });
|
|
}""")
|
|
data = _data(page)
|
|
assert data["cpt"][0]["code"] == "H0023"
|
|
result = bh.apply(page, "C1", data)
|
|
assert result.changes and result.changes[0]["before"][:2] == ["GT", "HO"], \
|
|
result.changes
|
|
assert result.changes[0]["ok"] is True
|
|
assert _mods(page, 0) == ["", "", "", ""]
|
|
tr = page.locator(sel.SEL_CPT_ROWS).nth(0)
|
|
for n in (1, 2):
|
|
assert tr.locator(f'[id^="uBClaimMod{n}"]').first \
|
|
.get_attribute("data-blurred") == "1", f"M{n} not blurred"
|
|
|
|
|
|
@case
|
|
def apply_sends_an_unread_note_to_review_and_changes_nothing(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("GT", "", "", ""))
|
|
result = bh.apply(page, "C1", _data(page, note={"opened": "disabled"}))
|
|
assert result.changed is False
|
|
assert result.review == bh.NOTE_NOT_READ, result.review
|
|
assert _mods(page, 3) == ["GT", "", "", ""]
|
|
|
|
|
|
@case
|
|
def apply_is_a_no_op_when_the_h_code_has_no_modifier(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("", "", "", ""))
|
|
result = bh.apply(page, "C1", _data(page))
|
|
assert result.changed is False and result.review == "", result
|
|
|
|
|
|
@case
|
|
def describe_names_the_code_and_the_removed_modifiers(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("GT", "HO", "", ""))
|
|
text = bh.describe(bh.apply(page, "C1", _data(page)))
|
|
assert "H0023" in text and "GT" in text and "HO" in text, text
|
|
|
|
|
|
@case
|
|
def run_all_on_a_bh_office_visit_fires_this_condition(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("GT", "", "", ""))
|
|
report = {}
|
|
results = run_all(page, "C1", _data(page), report=report)
|
|
assert "bh_h_code_modifier" in report["fired"], report
|
|
assert "bh_h_code_modifier" in [r.condition_name for r in results], results
|
|
assert _mods(page, 3) == ["", "", "", ""]
|
|
|
|
|
|
def _h_row_state(page, index=3):
|
|
row = page.locator(sel.SEL_CPT_ROWS).nth(index)
|
|
return (row.locator(sel.SEL_CPT_ASSIGN_TO_PATIENT).first.is_checked(),
|
|
row.locator(sel.SEL_CPT_BILLED_FEE).first.input_value())
|
|
|
|
|
|
@case
|
|
def apply_on_a_non_bh_payer_unchecks_and_zeroes_the_h_code_only(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("GT", "", "", ""))
|
|
others = [_h_row_state(page, i) for i in (0, 1, 2)]
|
|
result = bh.apply(page, "C1", _data(page, ins=MEDICAID, note=TELE_NOTE))
|
|
assert result.changed is True and result.review == "", result
|
|
assert result.changes == [{"row": 3, "code": "H0023", "unchecked": True,
|
|
"fee": {"before": "150.00",
|
|
"after": "0.00"}}], result.changes
|
|
assert _h_row_state(page) == (False, "0.00")
|
|
assert _mods(page, 3) == ["GT", "", "", ""], "modifier is not this path's"
|
|
assert [_h_row_state(page, i) for i in (0, 1, 2)] == others
|
|
|
|
|
|
@case
|
|
def apply_on_a_non_bh_payer_with_an_unread_note_still_unchecks(page):
|
|
"""Telehealth does not matter off a BH payer, so the note does not."""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
result = bh.apply(page, "C1",
|
|
_data(page, ins=MEDICAID, note={"opened": "disabled"}))
|
|
assert result.changed is True, result
|
|
assert _h_row_state(page) == (False, "0.00")
|
|
|
|
|
|
@case
|
|
def apply_on_a_non_bh_payer_with_an_empty_live_grid_declines(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
data = _data(page, ins=MEDICAID)
|
|
page.evaluate("""() => document.querySelectorAll('tr[id^="CPTROW_"]')
|
|
.forEach(tr => tr.remove())""")
|
|
result = bh.apply(page, "C1", data)
|
|
assert result.changed is False
|
|
assert result.review == bh.CPT_GRID_NOT_READ, result.review
|
|
|
|
|
|
@case
|
|
def summary_comment_and_describe_follow_the_path_taken(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
off = bh.apply(page, "C1", _data(page, ins=MEDICAID))
|
|
assert bh.summary_comment(off) == ("H code not billed to a non-BH payer "
|
|
"(unchecked and fee zeroed)"), \
|
|
bh.summary_comment(off)
|
|
# UB, no Assign-To-Patient checkbox: only the fee moved (option A).
|
|
fee_only = bh.ConditionResult(
|
|
condition_name=bh.NAME, changed=True,
|
|
changes=[{"row": 0, "code": "H0023",
|
|
"fee": {"before": "74.00", "after": "0.00"}}])
|
|
assert bh.summary_comment(fee_only) == ("H code not billed to a non-BH "
|
|
"payer (fee zeroed)"), \
|
|
bh.summary_comment(fee_only)
|
|
text = bh.describe(off)
|
|
assert "H0023" in text and "150.00" in text and "non-BH" in text, text
|
|
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
on = bh.apply(page, "C1", _data(page))
|
|
assert bh.summary_comment(on) == bh.MODIFIER_COMMENT, \
|
|
bh.summary_comment(on)
|
|
|
|
|
|
@case
|
|
def run_all_on_a_non_bh_payer_leaves_the_h_code_unchecked_at_zero(page):
|
|
"""priced_line_check (last) must not re-check it: its fee is 0.00."""
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page)
|
|
report = {}
|
|
run_all(page, "C1", _data(page, ins=MEDICAID), report=report)
|
|
assert "bh_h_code_modifier" in report["fired"], report
|
|
assert _h_row_state(page) == (False, "0.00")
|
|
|
|
|
|
@case
|
|
def run_all_on_a_bh_telehealth_visit_keeps_gt(page):
|
|
page.goto(FIXTURE.as_uri())
|
|
_inject_h_row(page, mods=("GT", "", "", ""))
|
|
report = {}
|
|
run_all(page, "C1", _data(page, note=TELE_NOTE), report=report)
|
|
assert "bh_h_code_modifier" not in report["fired"], report
|
|
assert _mods(page, 3)[0] == "GT"
|
|
|
|
|
|
def main() -> 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
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|