Portal Delete run (Super Admin), lab codes #3/#4, Z23 pointer #45, T1017 #46, payer note, login security-image wait
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a8af44e673
commit
fafa5285eb
21 files changed
+1509
-63
No files matched your search
+45
-15
@@ -12,6 +12,7 @@ spreadsheet, one client instead of a loop, and the second factor is a mailed
|
||||
approval link rather than an authenticator code — see `twofactor.py`.
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from playwright.sync_api import Page, TimeoutError as PWTimeout
|
||||
@@ -30,6 +31,38 @@ from .restricted import Restricted, assert_not_restricted
|
||||
from .twofactor import handle_2fa
|
||||
|
||||
|
||||
SECURITY_IMAGE = "text=VERIFY YOUR SECURITY IMAGE"
|
||||
|
||||
|
||||
def wait_after_username(page: Page, timeout_ms: int, image: bool = True) -> str:
|
||||
"""
|
||||
After the username's Next: "image" once eCW's security image page shows
|
||||
(only while `image`), "password" once the password box is visible AND
|
||||
enabled — it is on the page from the start, disabled until the username
|
||||
step is done. Neither within `timeout_ms` → a screenshot (and the DOM)
|
||||
and RuntimeError. Polls: `Locator.is_visible()` never waits, so a
|
||||
single look missed a security image page that came late (2026-10-28).
|
||||
"""
|
||||
pw = page.locator(sel.SEL_PASSWORD).first
|
||||
img = page.locator(SECURITY_IMAGE).first
|
||||
end = time.monotonic() + timeout_ms / 1000
|
||||
while True:
|
||||
try:
|
||||
if image and img.is_visible():
|
||||
return "image"
|
||||
if pw.is_visible() and pw.is_enabled(timeout=1_000):
|
||||
return "password"
|
||||
except Exception:
|
||||
pass # navigating; look again
|
||||
if time.monotonic() > end:
|
||||
break
|
||||
page.wait_for_timeout(250)
|
||||
shot(page, "username_step_disabled")
|
||||
raise RuntimeError(
|
||||
f"Username step did not finish in {timeout_ms // 1000}s: the password "
|
||||
f"box stayed disabled (no security image page seen either)")
|
||||
|
||||
|
||||
def _assert_login_page(page: Page) -> None:
|
||||
"""
|
||||
Confirm the real login form came back before typing any credentials.
|
||||
@@ -90,21 +123,18 @@ def _do_login(page: Page, context, client_name: str):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Some logins interpose a security-image confirmation page.
|
||||
try:
|
||||
if page.locator("text=VERIFY YOUR SECURITY IMAGE").first.is_visible(timeout=3_000):
|
||||
print("[LOGIN] Security image page")
|
||||
msgbox.info_box(
|
||||
f"eCW Bot — Security Image ({client_name})",
|
||||
"The 'VERIFY YOUR SECURITY IMAGE' page came up.\n"
|
||||
"Click Continue/OK in the browser.\n\n"
|
||||
"Click OK once the password page appears.",
|
||||
needs_person=True,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
page.wait_for_selector(sel.SEL_PASSWORD, timeout=cfg.LOGIN_TIMEOUT)
|
||||
# Some logins interpose a security-image confirmation page — sometimes
|
||||
# seconds after Next (2026-10-28 17:59: missed by a one-look check).
|
||||
if wait_after_username(page, cfg.LOGIN_TIMEOUT) == "image":
|
||||
print("[LOGIN] Security image page")
|
||||
msgbox.info_box(
|
||||
f"eCW Bot — Security Image ({client_name})",
|
||||
"The 'VERIFY YOUR SECURITY IMAGE' page came up.\n"
|
||||
"Click Continue/OK in the browser.\n\n"
|
||||
"Click OK once the password page appears.",
|
||||
needs_person=True,
|
||||
)
|
||||
wait_after_username(page, cfg.LOGIN_TIMEOUT, image=False)
|
||||
# A renamed password field or Log In button still works through its
|
||||
# fallback (selectors/login.py); this says so in the log.
|
||||
fallback.report(page, ["Password field", "Log In button"],
|
||||
|
||||
@@ -44,6 +44,7 @@ from . import depression_dx_pointer
|
||||
from . import dummy_codes
|
||||
from . import injection_admin_code
|
||||
from . import j2930_j2001_update
|
||||
from . import lab_codes
|
||||
from . import medicaid_pregnancy_th
|
||||
from . import medicaid_uncheck_cpts
|
||||
from . import medicare_billing_screening
|
||||
@@ -52,18 +53,23 @@ from . import medicare_telehealth
|
||||
from . import medicare_vaccine_a6
|
||||
from . import medicare_wellness_history
|
||||
from . import priced_line_check
|
||||
from . import t1017_non_billable
|
||||
from . import telehealth
|
||||
from . import vaccine_admin_codes
|
||||
from . import vaccine_z23_pointer
|
||||
from . import zero_fee_uncheck
|
||||
from .base import run_all
|
||||
|
||||
CONDITIONS = [zero_fee_uncheck,
|
||||
telehealth, medicare_telehealth, depression_dx_pointer,
|
||||
j2930_j2001_update, dummy_codes, dental_dummy_codes,
|
||||
dental_payer_check, medicaid_uncheck_cpts, medicare_billing_screening,
|
||||
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_admin_codes, injection_admin_code,
|
||||
bh_h_code_modifier, vaccine_z23_pointer, vaccine_admin_codes,
|
||||
injection_admin_code,
|
||||
medicare_pps_code,
|
||||
priced_line_check]
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
r"""
|
||||
Condition (document #3, #4; EEWA-38, operator 2026-10-28): which lab codes
|
||||
a payer takes.
|
||||
|
||||
A **lab code** is a CPT of 5 digits starting with 8 (80000-89999) — the
|
||||
operator's rule, "8 series". 36416 (the finger stick billed beside some of
|
||||
them) is not one and is never touched.
|
||||
|
||||
The claim's primary payer decides:
|
||||
|
||||
* **Medicare payers** (#3: Medicare, Devoted MCR, Humana MCR, Novitas,
|
||||
Cigna Health Springs) — no lab code at all. The same exact eCW names
|
||||
as #25's PPS Payers (`medicare_pps_code.is_pps_payer`).
|
||||
* **Cigna Salud Employee** (#4: "Cigna Salud", the Salud employees'
|
||||
plan) — any lab code.
|
||||
* **Every other payer**, a blank one included (#4: "ALL except Cigna
|
||||
Salud") — only the 13 in-house codes, `IN_HOUSE`.
|
||||
|
||||
A lab code the payer does not take is unchecked and its Billed Fee zeroed,
|
||||
the treatment `j2930_j2001_update.py` gives its codes. The code only: the
|
||||
fees in the document's list are not checked. Not on Dental claims.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from ..pages import claim_form
|
||||
from . import lines
|
||||
from .base import CPT_TAB_NOT_CONFIRMED, ConditionResult
|
||||
from .medicare_pps_code import is_pps_payer
|
||||
|
||||
NAME = "lab_codes"
|
||||
|
||||
IN_HOUSE = frozenset((
|
||||
"82948", # Glucose blood stick
|
||||
"81025", # HCG, urine
|
||||
"85018", # Hemoglobin
|
||||
"82270", # Stool
|
||||
"82274", # Insure
|
||||
"87806", # HIV
|
||||
"85610", # INR
|
||||
"87880", # Strep group A
|
||||
"80305", # Drug screen
|
||||
"81001", # Urine dip
|
||||
"81003", # Urine dip w/o microscopy
|
||||
"87210", # Wet prep
|
||||
"87636", # COVID / Flu A/B
|
||||
))
|
||||
|
||||
CIGNA_SALUD = "cigna salud employee"
|
||||
|
||||
_LAB = re.compile(r"8\d{4}")
|
||||
|
||||
|
||||
def is_lab_code(code) -> bool:
|
||||
return bool(_LAB.fullmatch(str(code or "").strip()))
|
||||
|
||||
|
||||
def _rule(data: dict) -> str:
|
||||
""""medicare" (no lab code), "salud" (any) or "in_house" (the 13)."""
|
||||
if is_pps_payer(data):
|
||||
return "medicare"
|
||||
if lines.primary_payer(data).lower() == CIGNA_SALUD:
|
||||
return "salud"
|
||||
return "in_house"
|
||||
|
||||
|
||||
def _not_taken(code, rule: str) -> bool:
|
||||
code = str(code or "").strip()
|
||||
if not is_lab_code(code) or rule == "salud":
|
||||
return False
|
||||
return rule == "medicare" or code not in IN_HOUSE
|
||||
|
||||
|
||||
def detect(claim_id: str, data: dict) -> bool:
|
||||
if (data or {}).get("screen") == "dental":
|
||||
return False
|
||||
rule = _rule(data)
|
||||
return any(_not_taken(row.get("code"), rule)
|
||||
for row in (data or {}).get("cpt") or [])
|
||||
|
||||
|
||||
def apply(page, claim_id: str, data: dict) -> ConditionResult:
|
||||
"""
|
||||
Re-clicks CPT/HCPCS for the same reason `medicaid_uncheck_cpts.py`'s
|
||||
`apply()` does — eCW can leave the form on a different tab after the
|
||||
Prog. Notes viewer cycle.
|
||||
"""
|
||||
try:
|
||||
claim_form.open_cpt_hcpcs_tab(page)
|
||||
except Exception as exc:
|
||||
print(f"[COND {NAME} {claim_id}] Could not confirm the CPT/HCPCS "
|
||||
f"tab before fixing: {exc} — leaving the claim as read")
|
||||
return ConditionResult.declined(NAME, CPT_TAB_NOT_CONFIRMED)
|
||||
|
||||
rule = _rule(data)
|
||||
changes = claim_form.zero_and_uncheck_cpt_rows_matching(
|
||||
page, lambda code: _not_taken(code, rule))
|
||||
if not changes:
|
||||
return ConditionResult.none(NAME)
|
||||
for c in changes:
|
||||
c["rule"] = rule
|
||||
return ConditionResult(condition_name=NAME, changed=True, changes=changes)
|
||||
|
||||
|
||||
def _codes(result: ConditionResult) -> str:
|
||||
return "/".join(dict.fromkeys(c["code"] for c in result.changes))
|
||||
|
||||
|
||||
def summary_comment(result: ConditionResult) -> str:
|
||||
if any(c.get("rule") == "medicare" for c in result.changes):
|
||||
return (f"Lab code not billed to Medicare ({_codes(result)} - "
|
||||
f"unchecked, fee 0)")
|
||||
return (f"Lab code not in the in-house list ({_codes(result)} - "
|
||||
f"unchecked, fee 0)")
|
||||
|
||||
|
||||
def describe(result: ConditionResult) -> str:
|
||||
out = ["Lab codes the payer does not take — excluded:"]
|
||||
for c in result.changes:
|
||||
bits = []
|
||||
if c.get("unchecked"):
|
||||
bits.append("checkbox unchecked")
|
||||
if "fee" in c:
|
||||
bits.append(f"fee {c['fee']['before']} -> {c['fee']['after']}")
|
||||
out.append(f" Row {c['row'] + 1} ({c['code']}): {', '.join(bits)}")
|
||||
return "\n".join(out)
|
||||
@@ -0,0 +1,98 @@
|
||||
r"""
|
||||
Condition (document #46; EEWA-40, operator 2026-10-28): T1017 is a
|
||||
non-billable appointment on Medicare and Medicaid, and a human's call on
|
||||
any other payer.
|
||||
|
||||
Per the client's Charge Condition Document, row 46 (T1017, payer
|
||||
Medicaid):
|
||||
"If T1017 is received and it is not covered by Medicare, then please
|
||||
change this to a non-billable appt"
|
||||
|
||||
The operator's reading, by the claim's primary payer:
|
||||
|
||||
* a **Medicare payer** (#25's exact eCW names,
|
||||
`medicare_pps_code.is_pps_payer`) or **Medicaid** (the dashboard's
|
||||
"Medicaid" group only, `medicaid_uncheck_cpts.is_medicaid_claim` — not
|
||||
RAE, not CHP+) → the T1017 line is unchecked and its Billed Fee zeroed,
|
||||
the treatment `j2930_j2001_update.py` gives its codes;
|
||||
* **any other payer**, blank or unmapped included → nothing changed,
|
||||
Review: "T1017 on <payer> - check by hand".
|
||||
|
||||
Not on Dental claims.
|
||||
"""
|
||||
|
||||
from ..pages import claim_form
|
||||
from . import lines
|
||||
from .base import CPT_TAB_NOT_CONFIRMED, ConditionResult
|
||||
from .medicaid_uncheck_cpts import is_medicaid_claim
|
||||
from .medicare_pps_code import is_pps_payer
|
||||
|
||||
NAME = "t1017_non_billable"
|
||||
|
||||
T1017 = "T1017"
|
||||
|
||||
|
||||
def _payer_rule(data: dict) -> str:
|
||||
""""Medicare", "Medicaid" or "" (any other payer)."""
|
||||
if is_pps_payer(data):
|
||||
return "Medicare"
|
||||
if is_medicaid_claim(data):
|
||||
return "Medicaid"
|
||||
return ""
|
||||
|
||||
|
||||
def non_billable_for(data: dict) -> bool:
|
||||
return bool(_payer_rule(data))
|
||||
|
||||
|
||||
def detect(claim_id: str, data: dict) -> bool:
|
||||
if (data or {}).get("screen") == "dental":
|
||||
return False
|
||||
return any((row.get("code") or "").strip().upper() == T1017
|
||||
for row in (data or {}).get("cpt") or [])
|
||||
|
||||
|
||||
def apply(page, claim_id: str, data: dict) -> ConditionResult:
|
||||
"""
|
||||
Re-clicks CPT/HCPCS for the same reason `medicaid_uncheck_cpts.py`'s
|
||||
`apply()` does — eCW can leave the form on a different tab after the
|
||||
Prog. Notes viewer cycle.
|
||||
"""
|
||||
try:
|
||||
claim_form.open_cpt_hcpcs_tab(page)
|
||||
except Exception as exc:
|
||||
print(f"[COND {NAME} {claim_id}] Could not confirm the CPT/HCPCS "
|
||||
f"tab before fixing: {exc} — leaving the claim as read")
|
||||
return ConditionResult.declined(NAME, CPT_TAB_NOT_CONFIRMED)
|
||||
|
||||
rule = _payer_rule(data)
|
||||
if not rule:
|
||||
payer = lines.primary_payer(data) or "no payer"
|
||||
reason = f"{T1017} on {payer} - check by hand"
|
||||
print(f"[COND {NAME} {claim_id}] {reason}")
|
||||
return ConditionResult.declined(NAME, reason)
|
||||
|
||||
changes = claim_form.zero_and_uncheck_cpt_rows(page, {T1017})
|
||||
if not changes:
|
||||
return ConditionResult.none(NAME)
|
||||
for c in changes:
|
||||
c["rule"] = rule
|
||||
return ConditionResult(condition_name=NAME, changed=True, changes=changes)
|
||||
|
||||
|
||||
def summary_comment(result: ConditionResult) -> str:
|
||||
rule = next((c["rule"] for c in result.changes if c.get("rule")),
|
||||
"Medicare / Medicaid")
|
||||
return f"{T1017} not billable ({rule}) - unchecked, fee 0"
|
||||
|
||||
|
||||
def describe(result: ConditionResult) -> str:
|
||||
out = [f"{T1017} made non-billable:"]
|
||||
for c in result.changes:
|
||||
bits = []
|
||||
if c.get("unchecked"):
|
||||
bits.append("checkbox unchecked")
|
||||
if "fee" in c:
|
||||
bits.append(f"fee {c['fee']['before']} -> {c['fee']['after']}")
|
||||
out.append(f" Row {c['row'] + 1} ({c['code']}): {', '.join(bits)}")
|
||||
return "\n".join(out)
|
||||
@@ -0,0 +1,103 @@
|
||||
r"""
|
||||
Condition (document #45; EEWA-39, operator 2026-10-28): every vaccine line
|
||||
carries the Z23 diagnosis pointer.
|
||||
|
||||
Per the client's Charge Condition Document, row 45 (Vaccines codes):
|
||||
"Make sure you are assigning the proper diagnosis code (Z23) for ALL
|
||||
VACCINES"
|
||||
|
||||
The same mechanism as `depression_dx_pointer.py`: a CPT line points at a
|
||||
diagnosis by its number in the ICD list (ICD1-4 hold "1", "2", ...). On a
|
||||
claim with a vaccine code, Z23's number goes on every vaccine line that
|
||||
lacks it, in the first empty slot (ICD1 → ICD4); the line's other
|
||||
pointers stay (operator: "add in ICD1, ICD2"). Unlike #6, nothing is
|
||||
removed and no other line is touched.
|
||||
|
||||
* **Vaccine** = the client's 19 vaccine product codes of #18
|
||||
(`vaccine_admin_codes.VACCINE_CODES`), not the admin codes.
|
||||
* No Z23 in the ICD list → Review "add it by hand" (the bot has no flow
|
||||
for adding an ICD row). A line whose four slots are full → Review.
|
||||
* All payers, Medicare included. Not on Dental claims.
|
||||
"""
|
||||
|
||||
from ..pages import claim_form
|
||||
from .base import CPT_TAB_NOT_CONFIRMED, ConditionResult
|
||||
from .vaccine_admin_codes import VACCINE_CODES
|
||||
|
||||
NAME = "vaccine_z23_pointer"
|
||||
|
||||
Z23 = "Z23"
|
||||
NO_Z23 = "Z23 not on the claim — add it by hand"
|
||||
|
||||
|
||||
def z23_pointer(data: dict):
|
||||
"""Z23's number in the ICD list ("2"), or None. Z23 with or without a
|
||||
dot; Z23.0 is the same code written long, Z23.5 is not Z23."""
|
||||
for row in (data or {}).get("icd") or []:
|
||||
code = (row.get("Code") or "").strip().upper().rstrip(".")
|
||||
if code in (Z23, Z23 + ".0"):
|
||||
return (row.get("#") or "").strip() or None
|
||||
return None
|
||||
|
||||
|
||||
def _vaccine_codes(data: dict) -> list:
|
||||
return [(row.get("code") or "").strip()
|
||||
for row in (data or {}).get("cpt") or []
|
||||
if (row.get("code") or "").strip() in VACCINE_CODES]
|
||||
|
||||
|
||||
def detect(claim_id: str, data: dict) -> bool:
|
||||
if (data or {}).get("screen") == "dental":
|
||||
return False
|
||||
return bool(_vaccine_codes(data))
|
||||
|
||||
|
||||
def apply(page, claim_id: str, data: dict) -> ConditionResult:
|
||||
"""
|
||||
Re-clicks CPT/HCPCS for the same reason `depression_dx_pointer.py`'s
|
||||
`apply()` does — eCW can leave the form on a different tab after the
|
||||
Prog. Notes viewer cycle.
|
||||
"""
|
||||
try:
|
||||
claim_form.open_cpt_hcpcs_tab(page)
|
||||
except Exception as exc:
|
||||
print(f"[COND {NAME} {claim_id}] Could not confirm the CPT/HCPCS "
|
||||
f"tab before fixing: {exc} — leaving the claim as read")
|
||||
return ConditionResult.declined(NAME, CPT_TAB_NOT_CONFIRMED)
|
||||
pointer = z23_pointer(data)
|
||||
if pointer is None:
|
||||
print(f"[COND {NAME} {claim_id}] {NO_Z23} "
|
||||
f"({'/'.join(dict.fromkeys(_vaccine_codes(data)))})")
|
||||
return ConditionResult.declined(NAME, NO_Z23)
|
||||
|
||||
changes, full = claim_form.add_cpt_icd_pointer(
|
||||
page, pointer=pointer, target_codes=VACCINE_CODES)
|
||||
review = (f"Z23 pointer: no free pointer slot on "
|
||||
f"{'/'.join(dict.fromkeys(full))} — add it by hand"
|
||||
if full else "")
|
||||
if not changes:
|
||||
if not review:
|
||||
print(f"[COND {NAME} {claim_id}] Z23 already on every vaccine "
|
||||
f"line — nothing to change")
|
||||
return ConditionResult.none(NAME)
|
||||
return ConditionResult.declined(NAME, review)
|
||||
return ConditionResult(condition_name=NAME, changed=True,
|
||||
changes=changes, review=review)
|
||||
|
||||
|
||||
def _codes(result: ConditionResult) -> str:
|
||||
return "/".join(dict.fromkeys(c["code"] for c in result.changes))
|
||||
|
||||
|
||||
def summary_comment(result: ConditionResult) -> str:
|
||||
return f"Z23 pointer added to the vaccine line ({_codes(result)})"
|
||||
|
||||
|
||||
def describe(result: ConditionResult) -> str:
|
||||
out = ["Z23 ICD pointer on the vaccine lines:"]
|
||||
for c in result.changes:
|
||||
out.append(f" Row {c['row'] + 1} ({c['code']}): ICD "
|
||||
f"{c['before']['icd']} -> {c['after']['icd']}")
|
||||
if result.review:
|
||||
out.append(f" {result.review}")
|
||||
return "\n".join(out)
|
||||
@@ -91,6 +91,10 @@ COMMENTS = {
|
||||
"CHP+ Dental - Claim Status to Patient"),
|
||||
"medicaid_uncheck_cpts": (
|
||||
"CPT not billable to Medicaid (15851/15852/73521 - unchecked, fee 0)"),
|
||||
# Fallback only: the condition words its own comment (codes, payer rule).
|
||||
"lab_codes": "Lab code the payer does not take - unchecked, fee 0",
|
||||
# Fallback only: the condition words its own comment (which payer).
|
||||
"t1017_non_billable": "T1017 not billable (Medicare / Medicaid) - unchecked, fee 0",
|
||||
"medicare_billing_screening": (
|
||||
"Screening codes not billed to Medicare (G8510/G8431)"),
|
||||
# Fallback only: the condition words its own comment (which codes).
|
||||
@@ -104,6 +108,8 @@ COMMENTS = {
|
||||
"medicaid_pregnancy_th": (
|
||||
"TH modifier added (pregnancy visit on Medicaid)"),
|
||||
"vaccine_admin_codes": "CPT Code Missing ( vaccine admin code added )",
|
||||
# Fallback only: the condition words its own comment (which codes).
|
||||
"vaccine_z23_pointer": "Z23 pointer added to the vaccine line",
|
||||
# Fallback only: the condition words its own comment (96372 added or not).
|
||||
"injection_admin_code": "CPT Code Missing (injection admin code 96372)",
|
||||
# Fallback only: the condition words its own comment by the path it
|
||||
@@ -256,6 +262,24 @@ def review_reasons(data: dict) -> list:
|
||||
return reasons
|
||||
|
||||
|
||||
def _ecw_payer(data: dict) -> str:
|
||||
"""The payer every condition goes by: the claim form's first insurance
|
||||
row (`conditions.lines.primary_payer`, not imported — it brings the
|
||||
page code with it)."""
|
||||
rows = (data or {}).get("insurance") or []
|
||||
return str(rows[0].get("Name") or "").strip() if rows else ""
|
||||
|
||||
|
||||
def payer_note(sheet: str, ecw: str) -> str:
|
||||
"""A note when the report's PAYER and eCW's differ (operator,
|
||||
2026-10-28): the rules went by eCW's, and the reader should know. Not
|
||||
when either is blank, nor for case or spacing alone."""
|
||||
norm = lambda s: " ".join(str(s or "").split()).lower()
|
||||
if not norm(sheet) or not norm(ecw) or norm(sheet) == norm(ecw):
|
||||
return ""
|
||||
return f"Payer: sheet says {sheet.strip()}, eCW says {ecw.strip()} - eCW used"
|
||||
|
||||
|
||||
def claim_outcome(found: bool, data: dict) -> dict:
|
||||
"""
|
||||
Columns V-X for one claim, from what `_read_claim_form` returned.
|
||||
@@ -318,15 +342,20 @@ def claim_outcome(found: bool, data: dict) -> dict:
|
||||
# A save that happened is never hidden behind "Needs manual review"
|
||||
# (final review, 2026-10-05): it leads the Comments.
|
||||
lead = [SAVED_NOTE] if saved else []
|
||||
return {"Comments": "; ".join(lead + comments + reasons),
|
||||
"Current Status": "Needs manual review", "Category": "Review"}
|
||||
if not failed:
|
||||
return {"Comments": "; ".join([CLEAN] + comments),
|
||||
"Current Status": "Ready to Submit", "Category": "Pass"}
|
||||
return {"Comments": "; ".join(comments),
|
||||
"Current Status": ("Ready to Submit" if saved
|
||||
else "Corrections pending (not saved)"),
|
||||
"Category": "Fail"}
|
||||
out = {"Comments": "; ".join(lead + comments + reasons),
|
||||
"Current Status": "Needs manual review", "Category": "Review"}
|
||||
elif not failed:
|
||||
out = {"Comments": "; ".join([CLEAN] + comments),
|
||||
"Current Status": "Ready to Submit", "Category": "Pass"}
|
||||
else:
|
||||
out = {"Comments": "; ".join(comments),
|
||||
"Current Status": ("Ready to Submit" if saved
|
||||
else "Corrections pending (not saved)"),
|
||||
"Category": "Fail"}
|
||||
# Not a column: build_rows compares it with the report's PAYER.
|
||||
if _ecw_payer(data):
|
||||
out["_ecw_payer"] = _ecw_payer(data)
|
||||
return out
|
||||
|
||||
|
||||
def _money(text):
|
||||
@@ -371,6 +400,10 @@ def build_rows(report_rows: list, header_row: int, claim_col: int,
|
||||
done = outcomes.get(claim) or {}
|
||||
if done:
|
||||
vals.update(done)
|
||||
note = payer_note(vals["PAYER"], done.get("_ecw_payer"))
|
||||
if note:
|
||||
vals["Comments"] = "; ".join(
|
||||
c for c in (vals.get("Comments"), note) if c)
|
||||
vals["Recevied date"] = run_date
|
||||
vals["Completed On"] = run_date
|
||||
vals["Assigned To "] = assigned_to
|
||||
|
||||
@@ -2005,6 +2005,45 @@ def retag_cpt_icd_pointer(page: Page, *, target_pointer: str,
|
||||
return changes
|
||||
|
||||
|
||||
def add_cpt_icd_pointer(page: Page, *, pointer: str, target_codes) -> tuple:
|
||||
"""
|
||||
Make every row whose code is in `target_codes` carry the ICD pointer
|
||||
`pointer` among ICD1-4 — added in the first empty slot, the row's other
|
||||
pointers kept (document #45: Z23 on every vaccine line). A row that
|
||||
already carries it, and every other row, is left untouched; so is a row
|
||||
whose four slots are full.
|
||||
|
||||
Returns `(changes, full)`: `changes` in `retag_cpt_icd_pointer()`'s
|
||||
shape, `full` the codes of the rows that had no free slot.
|
||||
"""
|
||||
changes, full = [], []
|
||||
wanted = {c.upper() for c in target_codes}
|
||||
rows = page.locator(sel.SEL_CPT_ROWS)
|
||||
for i in range(rows.count()):
|
||||
row = rows.nth(i)
|
||||
code_input = row.locator(sel.SEL_CPT_CODE).first
|
||||
if code_input.count() == 0:
|
||||
continue
|
||||
code = (code_input.input_value(timeout=5_000) or "").strip()
|
||||
if code.upper() not in wanted:
|
||||
continue
|
||||
icd_inputs = [row.locator(s).first for s in sel.SEL_CPT_ICD]
|
||||
before = [(inp.input_value(timeout=5_000) or "").strip()
|
||||
for inp in icd_inputs]
|
||||
if pointer in before:
|
||||
continue
|
||||
if "" not in before:
|
||||
full.append(code)
|
||||
continue
|
||||
slot = before.index("")
|
||||
icd_inputs[slot].fill(pointer, timeout=10_000)
|
||||
after = list(before)
|
||||
after[slot] = pointer
|
||||
changes.append({"row": i, "code": code,
|
||||
"before": {"icd": before}, "after": {"icd": after}})
|
||||
return changes, full
|
||||
|
||||
|
||||
def kept_cpt_row_per_code(rows) -> dict:
|
||||
"""
|
||||
Which row is KEPT for each code when the same code sits on more than
|
||||
|
||||
Reference in new issue
Block a user