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>
270 lines
10 KiB
Python
270 lines
10 KiB
Python
r"""
|
|
conditions/dental_payer_check.py — document #13: Dental codes are billed
|
|
only to DentaQuest, Delta Dental or CHP+ Dental — on any insurance row,
|
|
secondary included. None of them on a claim with a billed Dental code: the
|
|
Claim Status is set to Patient (PAT) where the run may write, else Review
|
|
"set it by hand"; no payer read: Review (operator, 2026-10-16 / 2026-10-21).
|
|
|
|
python tests\test_conditions_dental_payer_check.py
|
|
|
|
Uses tests/fixtures/dental_claim.html (D0120 row 0, DCONS row 1) for the
|
|
run_all case.
|
|
"""
|
|
|
|
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" / "dental_claim.html"
|
|
|
|
from ecw_bot.conditions import dental_payer_check as cond # noqa: E402
|
|
from ecw_bot.conditions import CONDITIONS # noqa: E402
|
|
from ecw_bot.conditions.base import run_all # noqa: E402
|
|
from ecw_bot.core import charge_summary as cs # noqa: E402
|
|
from ecw_bot.pages import claim_form # noqa: E402
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
def _row(code, billed=True, fee="85.00"):
|
|
return {"code": code, "billed": billed, "billed_fee": fee, "units": "1"}
|
|
|
|
|
|
def _claim(payer, *rows):
|
|
return {"insurance": [{"Name": payer}] if payer is not None else [],
|
|
"cpt": list(rows)}
|
|
|
|
|
|
# ── recognition ──────────────────────────────────────────────────────────────
|
|
|
|
@case
|
|
def a_dental_code_is_d_and_four_digits():
|
|
for code in ("D0120", "D1110", " d0274 ", "D9999"):
|
|
assert cond.is_dental_code(code), code
|
|
for code in ("DCONS", "Dscprit", "DPIMP", "D012", "D01200", "99213",
|
|
"G8510", "", None):
|
|
assert not cond.is_dental_code(code), code
|
|
|
|
|
|
@case
|
|
def the_three_payers_are_allowed_and_no_other():
|
|
for payer in ("DentaQuest", "CHP+ DENTAQUEST", "Delta Dental",
|
|
"Delta Dental of Colorado", "DELTA DENTAL PPO",
|
|
" delta dental of oregon "):
|
|
assert cond.is_allowed_payer(payer), payer
|
|
for payer in ("Aetna Dental PPO", "Dental Cigna", "MetLife Dental Claims",
|
|
"Medicaid", "Anthem Blue Cross Blue Shield",
|
|
"Dental Slide D", "", None):
|
|
assert not cond.is_allowed_payer(payer), payer
|
|
|
|
|
|
# ── needs_review (no browser) ────────────────────────────────────────────────
|
|
|
|
@case
|
|
def allowed_payers_need_nothing():
|
|
for payer in ("DentaQuest", "CHP+ DENTAQUEST", "Delta Dental of Colorado"):
|
|
data = _claim(payer, _row("D0120"), _row("D1110"))
|
|
assert cond.detect("C1", data) is False
|
|
assert cond.needs_review("C1", data) is None, payer
|
|
|
|
|
|
@case
|
|
def an_allowed_secondary_payer_is_enough():
|
|
"""Operator 2026-10-21: secondary insurance counts."""
|
|
data = _claim("Medicaid", _row("D0120"))
|
|
data["insurance"].append({"Name": "Delta Dental of Colorado"})
|
|
assert cond.detect("C1", data) is False
|
|
assert cond.needs_review("C1", data) is None
|
|
|
|
|
|
@case
|
|
def another_payer_is_detected():
|
|
for payer in ("Aetna Dental PPO", "Medicaid", "Cigna HealthCare Of Colorado"):
|
|
data = _claim(payer, _row("D0120"), _row("DCONS"), _row("D1110"))
|
|
assert cond.detect("C1", data) is True, payer
|
|
assert cond.needs_review("C1", data) is None, payer
|
|
|
|
|
|
@case
|
|
def no_payer_read_is_review_not_a_status_change():
|
|
for data in (_claim("", _row("D0120")), _claim(None, _row("D0120"))):
|
|
assert cond.detect("C1", data) is False
|
|
why = cond.needs_review("C1", data)
|
|
assert why and "no payer read" in why and "D0120" in why, why
|
|
|
|
|
|
@case
|
|
def only_billed_dental_lines_count():
|
|
# Unchecked at 0.00, or checked at 0.00 (zero_fee_uncheck unchecks it):
|
|
# nothing billed, nothing to do.
|
|
for row in (_row("D0120", billed=False, fee="0.00"),
|
|
_row("D0120", billed=True, fee="0.00")):
|
|
assert cond.detect("C1", _claim("Medicaid", row)) is False, row
|
|
# Dummy codes only: not dental codes.
|
|
data = _claim("Medicaid", _row("DCONS"), _row("DPIMP"), _row("99213"))
|
|
assert cond.detect("C1", data) is False
|
|
|
|
|
|
@case
|
|
def it_is_registered():
|
|
assert "dental_payer_check" in [m.NAME for m in CONDITIONS]
|
|
assert cs.COMMENTS.get("dental_payer_check")
|
|
|
|
|
|
# ── run_all on the Dental screen (browser) ──────────────────────────────────
|
|
|
|
def _dental_read(page, payer):
|
|
data = claim_form.extract(page)
|
|
data["screen"] = "dental"
|
|
data["insurance"] = [{"Name": payer}]
|
|
data["progress_notes"] = {"opened": "tab", "text": "Routine dental visit."}
|
|
return data
|
|
|
|
|
|
def _outcome(page, payer):
|
|
page.goto(FIXTURE.as_uri())
|
|
data = _dental_read(page, payer)
|
|
report = {}
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
results = run_all(page, "C1", data, report=report)
|
|
data["_conditions"] = {"fired": [r.condition_name for r in results],
|
|
"saved": False, "failed": report["failed"],
|
|
"review": report["review"],
|
|
"comments": report["comments"]}
|
|
return cs.claim_outcome(True, data), report
|
|
|
|
|
|
@case
|
|
def a_dental_claim_to_aetna_dental_is_review_set_by_hand(page):
|
|
# The Dental screen is never written (claims not saved): Review.
|
|
outcome, report = _outcome(page, "Aetna Dental PPO")
|
|
notes = [r["reason"] for r in report["review"]
|
|
if r["name"] == "dental_payer_check"]
|
|
assert len(notes) == 1 and "D0120" in notes[0], report["review"]
|
|
assert "set it to Patient by hand" in notes[0], notes
|
|
assert outcome["Category"] == "Review", outcome
|
|
assert "Patient" in outcome["Comments"], outcome
|
|
|
|
|
|
# ── the Claim Status dropdown (browser) ─────────────────────────────────────
|
|
|
|
_STATUS = """
|
|
<select id="status" ng-model="claimRP.claimRPStatus">
|
|
<option value="A">Active</option><option value="PAT">Not this one</option>
|
|
</select>
|
|
<select id="claimStatusSel1" ng-model="ClaimData.ClaimStatus"
|
|
ng-change="ClaimStautsChange('ChangeEvent')">
|
|
<option value=""></option>
|
|
<option value="string:CMC">Ready to Submit (Electronic)</option>
|
|
<option value="string:PAT">Patient</option>
|
|
<option value="string:108ME" selected>Medcor Ready</option>
|
|
</select>
|
|
"""
|
|
|
|
|
|
def _write(page, html, data, save=True):
|
|
from ecw_bot import settings as _cfg
|
|
page.set_content(html)
|
|
orig = _cfg.SAVE_CORRECTIONS
|
|
_cfg.SAVE_CORRECTIONS = save
|
|
try:
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
return cond.apply(page, "C1", data)
|
|
finally:
|
|
_cfg.SAVE_CORRECTIONS = orig
|
|
|
|
|
|
def _value(page, selector):
|
|
return page.locator(selector).input_value()
|
|
|
|
|
|
@case
|
|
def live_write_sets_the_claim_status_to_patient(page):
|
|
data = _claim("Aetna Dental PPO", _row("D0120"))
|
|
result = _write(page, _STATUS, data)
|
|
assert result.changed is True and result.review == "", result
|
|
assert _value(page, "#claimStatusSel1") == "string:PAT"
|
|
assert _value(page, "#status") == "A", "the other status field untouched"
|
|
c = result.changes[0]
|
|
assert (c["before"], c["after"], c["label"]) == ("108ME", "PAT", "Patient")
|
|
assert cond.summary_comment(result) == (
|
|
"Dental code(s) D0120 billed to Aetna Dental PPO — not DentaQuest / "
|
|
"Delta Dental / CHP+ Dental - Claim Status set to Patient")
|
|
assert "108ME -> PAT (Patient)" in cond.describe(result)
|
|
|
|
|
|
@case
|
|
def the_ub_dropdown_with_plain_values_works_too(page):
|
|
html = ('<select id="claimStatusSel2" ng-model="selectedClaimStatusCodes">'
|
|
'<option value="108ME" selected>Medcor Ready</option>'
|
|
'<option value="PAT">Patient</option></select>')
|
|
result = _write(page, html, _claim("Medicaid", _row("D1110")))
|
|
assert result.changed and _value(page, "#claimStatusSel2") == "PAT"
|
|
|
|
|
|
@case
|
|
def verify_changes_nothing_and_says_set_it_by_hand(page):
|
|
result = _write(page, _STATUS, _claim("Medicaid", _row("D0120")),
|
|
save=False)
|
|
assert result.changed is False
|
|
assert "set it to Patient by hand" in result.review, result.review
|
|
assert _value(page, "#claimStatusSel1") == "string:108ME"
|
|
|
|
|
|
@case
|
|
def no_patient_option_or_no_dropdown_is_review(page):
|
|
html = ('<select ng-model="ClaimData.ClaimStatus">'
|
|
'<option value="108ME" selected>Medcor Ready</option></select>')
|
|
result = _write(page, html, _claim("Medicaid", _row("D0120")))
|
|
assert result.changed is False and "set it by hand" in result.review
|
|
result = _write(page, "<p>no form</p>", _claim("Medicaid", _row("D0120")))
|
|
assert result.changed is False and "no Claim Status box" in result.review
|
|
|
|
|
|
@case
|
|
def a_dental_claim_to_dentaquest_gets_no_payer_review(page):
|
|
_, report = _outcome(page, "DentaQuest")
|
|
assert not [r for r in report["review"]
|
|
if r["name"] == "dental_payer_check"], report["review"]
|
|
|
|
|
|
def main() -> int:
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
failures = 0
|
|
with sync_playwright() as pw:
|
|
browser = pw.chromium.launch(headless=True)
|
|
page = browser.new_page()
|
|
for fn in CASES:
|
|
name = fn.__name__.replace("_", " ")
|
|
try:
|
|
if len(inspect.signature(fn).parameters) == 0:
|
|
fn()
|
|
else:
|
|
fn(page)
|
|
print(f" ok {name}")
|
|
except AssertionError as exc:
|
|
print(f" FAIL {name}: {exc}")
|
|
failures += 1
|
|
except Exception as exc:
|
|
print(f" ERROR {name}: {type(exc).__name__}: {exc}")
|
|
failures += 1
|
|
browser.close()
|
|
|
|
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
|
|
return 1 if failures else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|