Files
ecw-charge-validation/Context.md
T
ManikandanandClaude Opus 5.5 64efb68ea1 Portal sign-in with the Ecare office login
Ecare staff sign in with their office username and password, checked by
the Ecare sign-in service from the backend (AUTH_TOKEN_API_URL); a good
Ecare login still needs a Portal User. Service down = "try again
shortly", never a wrong password. /api/auth/login, me, logout; sign-in
tries limited per address. Users page: Ecare office login or Portal
password per user. Context.md: 27/10 runs, the login retry bug.
71 test files, 1239 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:55:50 +05:30

96 KiB
Raw Blame History

Project Context — eCW Charge Validation Bot (Salud)

Last updated: 2026-10-23. A one-file briefing for anyone (person or AI session) picking this project up. No credentials or patient data belong in this file.


1. What this is

A Python + Playwright bot that drives the eClinicalWorks (eCW) web UI for the client Salud and does the "Charge Validation" step of the team's Medcor Ready workflow:

  1. Logs into eCW (username/password from .env, 2FA approval link read from the mailbox automatically).
  2. Filters claims to status 108ME "Medcor Ready" and exports the Claim Report to Input\Claim Report_<date>.xlsx.
  3. Adds a payer Group Name → Output\Medcor Ready_<date>.xlsx, using the team's own template (Claim Status Dashboard pairs, see §5).
  4. For each claim: looks it up, opens the claim form (UB / HCFA / Dental screen), waits for it to finish loading, reads it, opens the Progress Note, runs the conditions (rules from the client's Charge Condition Document), shows one review box per claim, and closes the claim with Cancel (or Save, only if enabled and confirmed).
  5. Writes Output\Charge Summary_<date>.xlsx in the team's 24-column format (Charge Summary_TEST_<date>.xlsx for a TEST_CLAIM_IDS_FILE run), after every claim (since 2026-10-12): the sheet grows claim by claim, so a run that stops halfway keeps what it did. A same-day file is kept as __HHMMSS; if it is open in Excel, the new sheet is saved under a __HHMMSS name instead and the log and a box say which.
  6. With RESULTS_DB=1, saves each claim's summary row to SQL Server (Charge_Validation.dbo.CV_ChargeSummary) as soon as that claim is done; run_web.bat shows them on a local page (§5).

Repo: D:\Ecw Process, git branch master, no remote (never pushed).


2. How to run

python run.py                      # the bot (logs into LIVE eCW)
python tests/test_<name>.py        # one test file, prints "N/M passed"
run_web.bat                        # results portal, http://127.0.0.1:8000
start_ecw_chrome.bat               # Chrome the bot attaches to (BROWSER_CDP_URL)

Watching a run live (2026-10-22, 8af5c14): start_ecw_chrome.bat opens Google Chrome with its own profile D:\EcwChrome (CDP_PROFILE_DIR), debug port 9222 on 127.0.0.1, the eCW proxy (claude.ai / anthropic.com go direct, for the Claude extension) and the bot's usual Chrome flags. With BROWSER_CDP_URL=http://127.0.0.1:9222 in .env the bot attaches to that Chrome instead of launching Playwright's Chromium: it opens its own tab, clears eCW's cookies and site storage (ecwcloud.com only — claude.ai's sign-in stays) when it attaches and again when it closes, closes only its own tabs and leaves Chrome open. Only a loopback URL is accepted. The captcha incognito retry still launches its own Chromium. Blank BROWSER_CDP_URL = the old launch. Since 0f16560 (2026-10-22, operator: "Start on the Portal is all it takes"): no Chrome on the port at Start → the run opens Google Chrome itself (same profile, proxy and flags, through a short-lived Python so Chrome is outside the bot's process tree: a Portal Stop's taskkill /T never takes it) and closes it at the end (CDP_CLOSE_AFTER_RUN=1); a Chrome the run did not open is never closed. Before login the Chrome on the port must carry the eCW proxy (its command line, read with PowerShell, has --proxy-server=<PROXY_IP:PORT>), else exit 2 — added after a test opened a Chrome with no proxy on 9222 at 19:41 that day, a Portal run attached to it and eCW refused the login as "restricted" (exit 4, guard lockout 5 min; the tests no longer read the real .env's BROWSER_CDP_URL). .env on this PC has BROWSER_CDP_URL=http://127.0.0.1:9222 since 2026-10-22 (backup of the earlier .env in the session's temp folder). Live: 19:29 attached and reached the 2FA wait (stopped), 19:53 Portal run opened Chrome, logged in, 50/50 claims (39 Pass / 11 Fail / 0 Review), cleared the eCW cookies and closed Chrome — exit 0. The landing page after Home took ~2 min there (15 s in Chromium runs); watch it. Since 8fea168 (2026-10-23, operator): a run that opened Chrome itself shows a person's box "eCW Bot — Chrome open" after the proxy check and before the eCW login, so the Claude extension can be added; OK → login (no OK → on after MSGBOX_TIMEOUT_SECONDS, 180 s; shown on a Portal run too). Once the bot's tab is open, that Chrome's empty first tab (New Tab / about:blank) is closed, so the window shows eCW only. A Chrome already on the port (start_ecw_chrome.bat): no box, every tab kept. Live 2026-10-23 terminal runs: 16:27 — 20 of 50 claims (all Pass), stopped with Ctrl-C (KeyboardInterrupt in a claim box: no run_end, so the Runs page shows it as ended without a record); 17:00 — stopped with Ctrl-C at the proxy check, before any login; 17:07 — the box came up, OK, login in 279 s (box wait included), 2FA mail in 42 s, Chrome showed only eCW tabs, claims running. A full test suite ran beside the 16:27 and 17:07 runs without touching them (the tests' Chrome uses a free port, never 9222). 2026-10-26 17:40 terminal run: Chrome open box → OK, login 97 s (2FA mail 42 s), 50/50 read, 38 Pass / 12 Fail / 0 Review, no bot error, ~27 min. Against the 23/10 17:07 run, 49 claims gave the same comment; the one change is eCW data, not code: a patient's sliding fee was renewed (new period from 10/23/2026), so a claim with an earlier DOS is now Fail "Sliding fee NOT STARTED" (§6 Waiting 6). The #25 and Portal changes of 23–26/10 changed no other result. 2026-10-27 terminal runs: 16:40 — 2FA mail never came in 10 min (eCW timed the approval out), then attempt 2 crashed: the run closed the Chrome it opened and re-attached to 9222 while that Chrome was still closing (connect_over_cdp: Target page ... has been closed; §10). 16:53 (after a 120 s guard cooldown) — login 220 s, 2FA mail 42 s, 50/50, 38 Pass / 12 Fail / 0 Review (the 50-claim sheet; same as 26/10 17:40). 17:30 with Mecor Ready 2 - 10262026.xlsx (all claims, QUIET_BOXES, ECW_KEEP_OPEN=0) — login 98 s, 50/50, 30 Pass / 15 Fail / 5 Review, the same as that file on 26/10. No bot error in either.

Runs page (2026-10-22, Stage 1 of EEWA-31..33, 751a177): http://127.0.0.1:8000/runs (header link "Runs"). Every Run writes Logs\events_<run>.jsonl beside run_<run>.log (core/run_events.py): one JSON line per event, each with its time to the millisecond, flushed at once, never fatal. The page lists every Run (Started By, Run Source, mode, duration, result + exit code, eCW Login Attempts + total login time, Pass / Fail / Review, Claim Errors, Warnings; filters date / result) and a Run's detail: each eCW Login Attempt's steps with times and the 2FA mail wait, Run Errors with the traceback (the log never had one), Claim Errors, Not Reached claims, Warnings, failure screenshots (PNG only) and a timeline; refreshes every 5 s while Running. Runs before 2026-10-22 are not listed. Started By = the signed-in Portal User for a page run (ECW_STARTED_BY, Stage 2), the Windows user for a terminal run. Seen live 2026-10-22 16:24 (a terminal run of the 50-claim sheet): every login step and claim recorded; Log In → 2FA mail 46 s. Disk check before login: system drive under DISK_WARN_MB (2048) = Warning, under DISK_STOP_MB (500) = exit 2. Terms (Run Error / Claim Error / Warning / Fail / Not Reached / eCW Login Attempt / Portal User / Sign-in / Started By): GLOSSARY.md. Why files and not SQL: docs/adr/0001-run-events-in-files.md. Spec: docs/superpowers/specs/2026-10-22-run-tracking-and-portal-sign-in-design.md.

Portal Sign-in (2026-10-22, Stage 2 of EEWA-31..33, c230a8e): every page and /api needs a Sign-in. Setup, once: run tools\sql\create_tables.sql in SSMS (adds CV_PortalUser, CV_PortalSignIn and, for Stage 3, CV_ChargeSummary.started_by), then python tools\add_portal_user.py for the first Super Admin (password typed hidden), then restart run_web.bat. Roles since f99dc17 (2026-10-27, operator: "super admin means me"): Super Admin (everything; Users page adds / changes Admins and Viewers — never deletes; nobody changes a Super Admin from the Portal, only the PC tool makes one), Admin (everything: Start / Stop, Sign-ins page; adds / changes Viewers only), Viewer (Dashboard, Claim status, Runs; changes nothing, no Users or Sign-ins). Operator is gone (folded into Admin). The live table was moved 2026-10-27 by the operator in SSMS (the bot's SQL login cannot ALTER the table): the one user is Super Admin; the CHECK allows only the three roles. Ecare office sign-in (1da9e7c, 2026-10-27, operator: the office login API, "only who we want"): with AUTH_TOKEN_API_URL set (https://auth-wfm.ecareindia.com/api/login/token; public TLS cert, empty body = 401), a user added with sign-in "Ecare office login" (the Users page default) keeps no Portal password (pw_iterations 0): the backend posts their username + password there (15 s); 4xx = wrong password, 5xx / timeout / 404 / 405 = 503 "try again shortly", a good login with no Portal User = 403 no_access. The Ecare token is not kept; same session cookie. /api/auth/login / me / logout; 20 tries per address per 15 min = 429. "Portal password" users (the Super Admin's backup, clients) never reach Ecare. NOT live yet: AUTH_TOKEN_API_URL is in neither the PC's .env nor Dokploy, and no zip with it is deployed. Waiting: which fields the Ecare reply carries (name, emp id?) so the Add form can be username + role only and fill the name at first Sign-in — D:\ecw-portal-deploy\ecare-fields.ps1 (prints field names, never the token or password) or AutoPilot's backend/app/api/auth.py. Passwords PBKDF2-SHA256 (600k, own salt), 10+ characters; a user an Admin adds or resets must choose their own at the next Sign-in. 5 wrong passwords = locked 15 minutes; 30 minutes idle = signed out; sessions live in the server's memory (a restart signs everyone out). The header shows the user, role and Sign out. Started By goes to the bot as ECW_STARTED_BY (Runs page, bot_run.json, the Dashboard card "started by …"); Stopped By into the Run's stopped event. Code: web/users.py, web/app.py; tests test_portal_users.py, test_portal_signin.py (the other web tests sign in first). Set up on this PC 2026-10-22: the new part of create_tables.sql run in SSMS as an admin at 18:19 (the bot's Windows login MFILESERVER\EC10980 cannot create or alter tables — checked; it can insert / update the new ones through its database roles); first Admin ec10980 added with add_portal_user.py at 18:22 (PBKDF2 600k, nothing plain stored); first live Sign-in 18:24 (recorded in CV_PortalSignIn). To look at the tables in SSMS: tools/sql/view_portal_tables.sql (read-only; the user query leaves the hash and salt out).

Last-run banner and Started By in the results (2026-10-22, Stage 3, ecbbf3a): the Dashboard shows a red banner when the latest Run ended in a Run Error or without a record (killed / crashed) — when, Started By, step, exit code, message, claims Not Reached, a link to the Run; never for Done, Stopped or Running, and the next good Run clears it. dbo.CV_ChargeSummary.started_by holds the Run's Started By: the bot writes it only once the column exists (checked before each insert; until then the rows are saved without it), the Portal shows it as "Started By" in a claim's side panel, and a Portal reading a table without the column says "Results table needs an update — run create_tables.sql". The Excel Charge Summary is unchanged. Tests: test_run_alerts.py.

From the page (2026-10-14): .\run_web.bat → http://127.0.0.1:8000 (the Dashboard) → Start run (Super Admin / Admin; Admin / Operator 2026-10-22 to 10-27). The page starts python run.py itself, always VERIFY (SAVE_CORRECTIONS=0 forced), information boxes go to the log (QUIET_BOXES=1); CAPTCHA / 2FA boxes still come up on the PC. The card shows phase, progress, Pass/Fail/Review and a live log; Stop kills the bot and its Chrome. A terminal run is shown too but cannot be stopped from the page. The child reads .env afresh (an edit made after the page started is seen) but writes to the page's own results database.

All test files at once (there is no tests/run_all.py yet). Git Bash:

for t in tests/test_*.py; do echo "$t: $(python "$t" 2>&1 | grep -E '^[0-9]+/[0-9]+ passed' | tail -1)"; done

PowerShell:

Get-ChildItem tests\test_*.py | ForEach-Object { "$($_.Name): $((python $_.FullName 2>&1 | Select-String '^\d+/\d+ passed' | Select-Object -Last 1).Line)" }
  • Dependencies: playwright, pyotp only for the bot (locked-down machines; no pytest, no openpyxl in the bot). The results DB and page use pyodbc, fastapi, uvicorn, jinja2 (+ httpx for tests), already installed on this machine, with ODBC Driver 18 for SQL Server.
  • Tests run offline against fake HTML pages in tests/fixtures/ and fake DB connections; they never touch eCW or SQL Server (test_cdp_browser.py starts a local Chromium on a debug port). As of 2026-10-27: 71 test files, 1239 tests (full run 2026-10-27: 1239/1239) (earlier flaky, each failed once and passed on rerun: test_verify_no_live_writes, depression_dx_pointer, progress_note_wait — watch them) (about 5 minutes for all of them).

Run modes

Read by settings.py. A setting missing from .env takes the code default, so deleting a .env line does not switch anything off.

Setting Meaning In .env now Code default
TEST_CLAIM_IDS_FILE If set: skip the bulk export, process the ids in this sheet; summary goes to Charge Summary_TEST_<date>.xlsx; missing path = exit 2 before login, unreadable sheet = exit 5 Input\Medcor Ready Claim Sheet.xlsx (since 2026-10-06) blank (bulk export)
SAVE_CORRECTIONS 0 = apply fixes for review, close with Cancel. 1 = save after operator OK 0 (deliberate) 0 (since 2026-10-05)
APPLY_CORRECTIONS Run the conditions at all not set 1
CLAIM_PROCESS_LIMIT Max claims per run (0 = all) 20 1
CHECK_SLIDING_FEE / CHECK_HIGH_RISK Dental checks not set 1 / 1
WRITE_CHARGE_SUMMARY / SUMMARY_ASSIGNED_TO End-of-run summary sheet not set 1 / eCW Bot
SHOW_EACH_CLAIM / REVIEW_BEFORE_CLOSE Per-claim box after close / the one box shown with a corrected claim still open (replaces the per-claim box for that claim) 1 / not set 1 / 1
PROGRESS_NOTE_WAIT_MS How long to poll for the Progress Note after clicking Prog. Notes not set 20000
MSGBOX_TIMEOUT_SECONDS Message box timeout 180 180
RESULTS_DB / RESULTS_DB_CONN Also save the summary rows to SQL Server / ODBC connection string not set 0 / Windows login to MECARE331\MECARE331, DB Charge_Validation
DISK_WARN_MB / DISK_STOP_MB System drive free space before login: Warning / stop with exit 2 not set 2048 / 500
BROWSER_CDP_URL / CDP_PROFILE_DIR / CDP_CLOSE_AFTER_RUN Run in Google Chrome on this debug port (127.0.0.1 only; opened by the run when none is there) / its profile / close a Chrome the run opened http://127.0.0.1:9222 (since 2026-10-22) / not set / not set blank (launch Chromium) / D:\EcwChrome / 1
WATCHDOG_MINUTES No progress on a claim this long = frozen page → driver closed, exit 8 (never under MSGBOX_TIMEOUT_SECONDS + 2 min; 0 = off) not set 10

⚠️ There is no separate test environment. Same code, same live eCW for "test" and "live" runs; only the claim list and the save switch differ. Since 2026-10-05 (review item P1) SAVE_CORRECTIONS defaults to 0 in code and in .env.example, so a missing or blank line can never turn saving (the OK click) on; APPLY_CORRECTIONS stays 1 (it only fills the form in, closed with Cancel while saving is off, so it does not save, assuming Cancel discards the edits: unverified until P16's check on a test claim, since adding a CPT row or ticking a bill-to box may already save on the server). An unquoted # comment after whitespace is stripped (SAVE_CORRECTIONS=0 # note reads as 0), and a yes/no or number value the bot cannot read stops the run with exit 2 before login. Every run prints [MODE] lines (VERIFY / LIVE-WRITE, claim limit, test sheet), and with SAVE_CORRECTIONS=1 it asks for an OK before login; Enter on that box and on the per-claim save box means Cancel.


3. Code layout (src/ecw_bot/)

Path Role
flows/export_medcor_ready.py Main flow: login → filters → export → categorize → per-claim loop → per-claim box → summary (~1,280 lines)
pages/claim_form.py Claim form open/load-wait/read/close/save + all CPT-row edit helpers + telehealth detection; clear_screen() (before every claim) and close_duplicate_forms() (~2,420 lines)
pages/progress_note.py Find and read the Progress Note (iframe/tab), keyed by prisma-section
pages/sliding_fee.py Dental: Info → Patient Information → Sliding Fee Schedule date check
pages/dental_notes.py Dental: "high risk" in Billing Notes
pages/claim_filters.py, claim_results.py, claim_report.py, leftnav.py Claim lookup screen, search, export; claim_results.search_blocker() = Claim No + Lookup on screen, enabled, nothing over them, eCW spinner gone; wait_search_ready() gives it up to 20 s before the claim is a failed search
selectors/*.py All CSS selectors (mapped from live DOM; comments say where from)
conditions/*.py One module per rule; registry in conditions/__init__.py
conditions/base.py run_all: every detect() on the untouched claim first, then apply() in registry order; ConditionResult.review / declined(), optional needs_review() and summary_comment() hooks, and a report dict (fired / changed / failed / review / comments)
core/charge_summary.py Charge Summary sheet (24 columns); build() (the rows) and write() (Excel); review_reasons() and claim_outcome() → Pass / Fail / Review
core/results_db.py The same rows → SQL Server dbo.CV_ChargeSummary (typed, parameterized insert, never fatal)
core/run_lock.py The run record Logs\bot_run.json: one live run at a time, exit code, source (web / terminal)
core/payer_groups.py + payer_groups.json PAYER → Group Name (dashboard names, classify()); rule_group() translates them to the names the conditions decide on
core/spreadsheet.py Minimal xlsx read/write (no openpyxl); write_xlsx_keep_old() keeps a same-day file and survives one open in Excel
core/page_utils.py shot() with a FAILURE tag (is_failure_tag: failed / missing / not_ / no_ / unconfirmed ...) also saves the page's DOM + frames beside the png as <HHMMSS>_<tag>.html (SAVE_DOM_ON_FAILURE=1, at most SAVE_DOM_LIMIT=30 a run; 2026-10-21) — no more hand-copying DevTools into Dom.txt. visible() = instant check; appears() = real wait (Playwright 1.62's is_visible(timeout) never waits); click_topmost() clicks the copy on top when dialogs stack (never .first)
core/fallback.py + selectors/fallbacks.py Fallback selectors (2026-10-21): the run-critical selectors are CHAINS — primary first, then alternatives taken from the live DOM (ng-click / ng-model / type+value, plain CSS) — joined as a CSS list, so every call site falls back with no change: password field, Log In, Claim No, Lookup, UB Cancel (closeUBClaimScreen(), never the X or OK), Dental Cancel (closeDntl()), Prog. Notes (#UBClaimProgressNoteBtn, claimProgressNoteBtn<ts>). report() logs [SELECTOR] fallback: <name> — <primary> not found, matched <alt> once a run (pre-flight and login call it)
pages/preflight.py Pre-flight selector check (2026-10-21): the claims search page before the first claim, the claim form on the first claim of each screen type (UB / HCFA / Dental: Cancel, CPT tab, rows with code / fee / POS / M1). Missing → [PREFLIGHT] FAIL <names>, page shot + DOM saved, run stops with exit 7 (PREFLIGHT_STOP=0 only warns). No CPT row on a claim → its row checks wait for the next; 3 in a row = rows missing
core/watchdog.py Claim watchdog (2026-10-22): watching(limit) around the claim loop, beat(where) at each claim step and every message box, check() raises Stalled → exit 8; on a stall it closes this process's Playwright driver (node.exe) so the frozen call returns, and exits 8 itself if the main thread never comes back
core/run_events.py A Run's timed events → Logs\events_<run>.jsonl (start, emit, warning, claim_error, run_error with traceback, login_attempt_start/end, login_step); never raises, never writes an OTP code / password / username
core/browser.py launch(): Playwright Chromium with the eCW proxy, or (BROWSER_CDP_URL) attach to the operator's Chrome — clear_ecw_data() at attach and close, cdp_ready() checked before login
core/msgbox.py Timed Windows message boxes; default_cancel=True makes Enter mean Cancel
core/text.py first_line() shared by core / conditions / flows
auth/* Login, 2FA mailbox, captcha, cooldowns; login.wait_for_home() answers eCW's "password expires in N days" box with Cancel; password_entry.type_password() TYPES the password key by key (never fill(), 2026-10-20)
settings.py .env parsing and every setting

Outside src/: web/ (FastAPI results page: app.py, repo.py, templates/index.html; bot_runner.py starts / stops the bot as a child process; run_status.py reads the run log into the page's live status and per-claim list), tools/sql/create_tables.sql (results table), tools/import_payer_groups.py, run_web.bat, start_ecw_chrome.bat → tools/start_ecw_chrome.py (§2), web/users.py

  • tools/add_portal_user.py + templates/signin.html, password.html, users.html, signins.html (Portal Sign-in, §2), web/runs.py
  • templates/runs.html / run.html (the Runs page, §2).

Claim screens: UB (#UBClaimDiv, has a CPT/HCPCS tab), HCFA ("Claim" screen, data-fieldname fields, payer in #billingClaimTbl8), Dental (dentalClaim<ts>, #cancelDentalClaimButton).


4. Conditions (UB/HCFA claims)

Registry order (conditions/__init__.py) — order matters:

# Module Doc row Rule
1 zero_fee_uncheck common rule FIRST. Checked CPT line with Billed Fee 0 → uncheck
2 telehealth #21 Telehealth note → POS 2 + modifier on first CPT row: Medicaid/RAE = GT, Commercial = 95, audio-only (Commercial) = 93. Medicare excluded. Any other payer group (unmapped, blank, Program, Dentaquest) → declined → Review. A row already at POS 2 / right modifier is not a change
3 medicare_telehealth #22 Medicare telehealth → POS 2 + 95/93, add G2025; once G2025 confirmed, uncheck + zero other rows. G2025 not added (or its modifier did not take) → Review, other rows left alone
4 depression_dx_pointer #6 Z13.31 pointer only on G8510/G8431; removed elsewhere; duplicate G8510/G8431 → keep first priced one. Not on Medicare claims (#14 instead)
5 j2930_j2001_update #5 J2930 and J2001 → uncheck + fee 0 (no replacement code)
6 dummy_codes #8 DCONS / Dscprit (any case) → uncheck + fee 0. BHT3/BHT4 are NOT dummy codes (operator, 2026-10-06)
7 dental_dummy_codes #48 DPIMP or DFIMP (any case; DFIMP "FINAL IMPRESSION" is the code the live Dental grid carries, 2026-10-20). Dental payer (dashboard "Dental - Commercial" or "Dental - CHP+/DentaQuest") → uncheck + fee 0. Any other payer, unmapped or none read → nothing changed, claim Review with a Comments note naming the payer group ("DPIMP on a Medicaid payer claim — not changed ...") (operator, 2026-10-13). Separate from #8
8 dental_payer_check #13 A billed Dental code ("D" + 4 digits; DCONS / Dscprit / DPIMP / DFIMP are not; billed = lines.ends_billed) and no insurance row (primary, secondary ...) is DentaQuest, CHP+ DentaQuest or "Delta Dental …" (by name, case and spaces ignored; Aetna Dental PPO, Cigna Dental, MetLife are NOT allowed) → Claim Status set to Patient (PAT, claim_form.set_claim_status, selectors.claim_form.SEL_CLAIM_STATUS — never the #status claimRP field), no program check (operator, 2026-10-21, EEWA-27). Only where the run may write (writes_allowed: LIVE-WRITE, never the Dental screen) — otherwise Review "set it to Patient by hand". No payer read → Review, nothing changed. Comment names codes and payer(s)
9 medicaid_uncheck_cpts #43, #44 Medicaid payer (rule_group "Medicaid" only — not RAE, CHP+, unmapped or blank; RAE ruled out by the operator 2026-10-14) → 15851, 15852, 73521 uncheck + fee 0; no other line touched (operator, 2026-10-13)
10 medicare_billing_screening #14 Medicare payer (rule_group; Medicare Advantage plans are Commercial since 2026-10-21) → G8510/G8431 uncheck + fee 0; no other line touched. No other billed line (screening only) → also Review "provider clarification needed" (operator, 2026-10-07)
11 medicare_vaccine_a6 #19 Medicare payer (rule_group) with a billed G0008 or G0009 (lines.ends_billed) → condition code A6: UB form ON SCREEN only — A6 already in FL 18-28 → nothing; else typed into the first empty box (uBClaimCondtnCode1..11) and read back from the box and its Angular model; all 11 full → Review "add A6 by hand"; typed but not taken → changed + Review. HCFA / Dental → Review "check A6 by hand" — not to be mapped: Medicare is billed on UB (#2), so such a claim is on the wrong form (operator, 2026-10-21). #19 complete; live test still needs a Medicare UB claim with G0008 / G0009. Only those two codes trigger it — not #18's vaccine list; the bot does not add G0008/G0009 (operator, 2026-10-16)
12 medicare_wellness_history #27, #28, #29 Check only, never changes the claim (operator, 2026-10-20). Medicare payer with a billed G0402 (IPPE, once per lifetime), G0438 (first AWV, once in 3 years) or G0439 (subsequent AWV, once a year): reads the patient's earlier claims in Account Inquiry (claim form side menu → Patient Inquiry Detail → Show CPT Payments, every page; this claim left out; closed with the X, the tick undone). Billed inside the window, counted either way from this claim's service date (exactly 3 years / 12 months = outside) → Review, Comments "G0438 already billed on MM/DD/YYYY (claim N) — once in 3 years". Account Inquiry not read → Review "check … history by hand". pages/account_inquiry.py, selectors/account_inquiry.py. Since ac25f83 (2026-10-23) it also leaves its verdict on the claim's data (_wellness_history: read or not, the repeats found) for #25 — still nothing changed in eCW
13 medicaid_pregnancy_th #23 Medicaid payer (rule_group "Medicaid" only, NOT RAE) with a pregnancy-related ICD (chapter O, Z33, Z34, Z36, Z3A — Z32.00/.01/.02 pregnancy test never counts) → TH on the first billed E&M line (99202-99499; none → first billed line), in M1, overwriting what M1 held (comment says "replacing 25"; never M2-M4); TH already in any box → nothing; no M1 box → Review "add TH by hand". A Medicaid telehealth pregnancy visit: #21 writes TH instead of GT (RAE keeps GT). Dental → Review. 9b87b4d, not run live yet (2026-10-20, EEWA-30)
14 bh_h_code_modifier #12 H code (H + 4 digits). BH payer (name starts with "BH") with any modifier → M1–M4 cleared on that line only; telehealth note → not fired (GT stays); note not read → Review, nothing changed (operator, 2026-10-07). Any other payer → billed H code line unchecked + fee 0.00, telehealth or not, modifier or not; blank payer → not fired (operator, 2026-10-12). Comment worded from what was done
15 vaccine_admin_codes #18 Vaccine without admin code → add 90460(+90461) age ≤18, 90471(+90472) >18; units 1; all payers except Medicare. Vaccine = the client's 19-code list only (90633, 90648, 90651, 90656, 90657, 90660, 90680, 90687, 90698, 90707, 90710, 90713, 90716, 90732, 90734, 90744, 90746, 91321, 91322), by code; admin = 90460/90461/90471/90472 only (operator, 2026-10-13; no hover text, no 90476-90759 range). Unclassified payer / no payer / no age → Review; an admin code that could not be added → Review ("NOT added — add by hand")
16 injection_admin_code #20 Injection code from the client's 41-code list (J0561 ... J8499, as given — IUD, inhalation and oral J-codes included) billed without 96372 → add 96372 ×1, Units 1, all payers except Medicare. Only 96372 counts as admin billed — also when it is unchecked with a fee (priced_line_check checks it at the end, so no second 96372; review 2026-10-14). J2001 / J2930 never count (#5 unchecks them). Unclassified / no payer → Review; not added → Review; Dental → Review (operator, 2026-10-13)
17 medicare_pps_code #25 PPS Payer (exact eCW names: Medicare, Medicare-Novitas, HCFA Medicare, HCFA Medicare Novitas, Humana Medicare, Devoted MCR UB, Cigna Healthspring, Cigna Health Spring/ Bravo — the two Cigna plans count although Commercial elsewhere) on a UB claim: billed (lines.ends_billed) Qualifying Codes → the PPS Code on the client's Medicare G code crosswalk (CROSSWALK): G0466 new office, G0466H new home, G0467 established office, G0467NH nursing home, G0467H established home, G0468 IPPE / AWV (G0402 / G0438 / G0439 win over an E&M). Right PPS Code billed → Pass. Missing → added via the Fee Schedule picker, Units 1 (LIVE-WRITE; Fail "CPT Code Missing (PPS code G0467 added (99213))"); VERIFY → Review "G0467 not added in VERIFY — add it by hand (99213)". Review, nothing changed: codes only on both the G0466 and G0467 lists ("new or established?"); conflicting visit codes; a different PPS Code billed (also beside the right one); mental health 90791 … 90845 ("G0469 or G0470?" until the client rules, EEWA-37; either one billed → Pass); a variant the picker cannot find; HCFA with a Qualifying Code; G0468 when #27–#29 found a repeat, could not read the history, or never checks it (the Cigna plans, Commercial there — operator, 2026-10-23). Dental → skipped (operator). ac25f83
18 priced_line_check common rule LAST. Unchecked line with Billed Fee > 0 → check

Every claim runs every condition above — each one's detect() decides from the claim's codes and payer, so a claim gets every "All payers" condition plus its own payer's (operator's number-one rule, 2026-10-07 / 2026-10-12). Dental included since 2026-10-12 (spec docs/superpowers/specs/2026-10-07-dental-claims-run-all-conditions-design.md, "as built" section): a Dental claim runs its high risk / sliding fee checks, then the same read, Prog. Notes and run_all. The Dental grid has no POS and no modifier columns, so a condition whose fix needs them (telehealth, medicare_telehealth) or a new line (vaccine_admin_codes) declares DENTAL_UNSUPPORTED and goes to Review on a Dental claim instead of being applied. Dental claims are never saved, whatever SAVE_CORRECTIONS says (operator, until a live verify run). Any condition whose CPT/HCPCS tab could not be opened is declined (Review), not silently skipped; a condition that raises is reported as failed (Review). Neither kind of claim is ever offered for Save.

Telehealth detection (pages/claim_form.py)

  • Telehealth = "telehealth" or "telephone" in the note, or a TH token — except a TH inside a Modifiers: list. That is the obstetric modifier on in-person OB visits ("OB office visit … Modifiers: TH"), not telehealth (found 2026-10-05 in saved notes; before this fix those visits got POS 2 + GT).
  • Audio vs video, first match wins: explicit audio-only ("Mode of Treatment: Telephone", "audio only", "no video", "(audio)", "phone call only") → audio; explicit video ("audio and video", "video visit", "via video", "Mode of Treatment: Video") → video; any other "telephone" → audio; otherwise video (the document's default). "Audiology" or the consent line "phone and/or video" decide nothing. The box shows the phrase that matched.

Loading and reading waits

  • Claim click times out after 5 s (was 30 s), then waits up to 5 s for the form.
  • A claim form left open by the previous claim is cancelled before the next click; if it will not close, that claim goes to Review instead of being read on the wrong form.
  • HCFA and Dental: wait (20 s) for the claim number to match and the CPT rows to be filled and stable; not loaded → Review, conditions skipped. Wrong claim number → error.
  • Progress Note: polled up to PROGRESS_NOTE_WAIT_MS until its sections stop changing; one re-click if nothing appears; a note that never settles is read but marked partial (Review).

What the operator sees (one box per claim)

  • Corrected claim (SAVE off, REVIEW_BEFORE_CLOSE=1): one box, shown with the form still open behind it: search result, what changed, any "Needs review" lines, "nothing saved". The old before-apply box is gone (log line only).
  • Every other claim: the per-claim box after close (SHOW_EACH_CLAIM), with "Changed", "Needs review" and "Summary: — ".
  • Dental: the per-claim box carries the high-risk and sliding-fee results (no separate box, no "?" fields; nothing when SHOW_EACH_CLAIM=0).
  • Test runs prefix every box title with [TEST] . All box text is English.

Dental checks (read-only)

Run first on a Dental claim, before the conditions (above). Results show in the per-claim box and go into the same Charge Summary row as the condition comments (§5): HIGH RISK or a sliding fee EXPIRED / NOT_STARTED → Fail; sliding fee NONE is noted in Comments (Pass); Billing Notes unreadable or sliding fee UNKNOWN → Review.

  • #15 High risk — "high risk" (any case/spacing) in Billing Notes (#dentalClaimNotes) and at least 79 characters from where "HIGH RISK" starts to the end of the notes (dental_notes.high_risk_length, HIGH_RISK_MIN_CHARS; spaces and line breaks count, trailing blanks do not; text before HIGH RISK does not count — 2026-10-21, was the whole notes) → YES/NO. "High risk" in shorter notes → NO (not Review); the log gives the length. Verified live on 4 Dental test claims: 1 YES, 3 NO; the 79 rule verified live 2026-10-13 (the YES claim's notes: 307 characters).
  • #8 Sliding fee — only Date from/to vs claim DOS → ACTIVE / EXPIRED / NOT_STARTED / NONE / UNKNOWN. Closes with Close + Cancel, never Save. Verified live on the same 4 claims (all ACTIVE).

Removed / not built

  • #9 Medicaid "D" codes — built, then reverted on operator's instruction (commit 6c44165).
  • #1 Claim assigned to the patient → Claim Status Patient — built (1c6b0f9), reverted the same day (5354c84) on the operator's instruction.
  • #16 secondary Medicaid — not needed (operator, 2026-10-21).

5. Charge Summary sheet (core/charge_summary.py)

24 columns, exactly the team's format (incl. their spellings Recevied date, Assigned To ):

  • A–R from the downloaded report: 15 columns by the same header, plus S.No, Group Name (payer_groups), Pmts/Refunds = PMTS/ADJS − Adjustment. Report column CLAIMTYPE (U = UB, D = Dental, blank = HCFA) is dropped.
  • S–X only for claims this run read: dates = run date, Assigned To = setting, Comments per condition, Current Status, Category. Three categories since 2026-10-05:
    • Review, "Needs manual review": the bot could not check everything. Triggers: search raised; form unreadable or never loaded; conditions not run (APPLY_CORRECTIONS=0); progress note not read or partial; zero CPT or zero ICD rows read; a condition failed or declined; an approved save eCW did not confirm; Dental notes unreadable or sliding fee UNKNOWN. Comments = findings then reasons; "Corrections SAVED to eCW" leads when the claim was saved. Review wins over Fail.
    • Fail: a condition changed something (its comment comes from what actually happened, e.g. "G2025 NOT added — add by hand"), or Dental HIGH RISK / sliding EXPIRED / NOT_STARTED. "Ready to Submit" if saved, else "Corrections pending (not saved)".
    • Pass: "Clean claims (…)", Ready to Submit.
    • A claim the run never reached or did not find stays blank in S–X.
  • Same-day re-run keeps the old file as …__HHMMSS.xlsx. If the old file is open in Excel (rename refused), the old file is left alone and the NEW sheet is saved as …__HHMMSS.xlsx instead (D10, 2026-10-05); the [SUMMARY] log line names the file actually written, and a box tells the operator the new name. Same for Medcor Ready_<date>.xlsx (its box only with NOTIFY_UNMAPPED_PAYERS=1).
  • A TEST_CLAIM_IDS_FILE run writes Charge Summary_TEST_<date>.xlsx (D1).
  • Written after every claim (operator, 2026-10-12), not once at the end. The first claim's write keeps an earlier same-day file aside as above; every later claim rewrites this run's own file (charge_summary.write(into=...), no __HHMMSS copy per claim). Rows of claims not done yet stay blank in S–X until their claim is done. If the operator opens the in-progress file in Excel, the rest of the run goes to a new __HHMMSS name. Each box (new name, write error) shows once per run, not per claim. Test: tests/test_summary_per_claim.py.

Payer groups (since 2026-10-06)

  • src\ecw_bot\payer_groups.json holds the team's template: every distinct Payer Name → Payer Group Name pair from the Claim Status Dashboard (\\mecare2\Production\Salud-eCW RPA\Claim Status Dashboard Details 1001.xlsx), 267 payers, plus 5 RAE spellings the dashboard does not list. Payer and group names only, no patient data.
  • Refresh it when the dashboard changes, from PowerShell (Git Bash cannot reach the share): python tools\import_payer_groups.py (--dry-run lists what would change without writing).
  • Group Name columns (Medcor Ready_<date>.xlsx, Charge Summary) show the dashboard's names: Commercial Ins, Medicaid, RAE (Behavioral Health), Dental - CHP+/DentaQuest, Dental - Commercial, Medicare, CHP+, Self Pay, Patient...
  • Conditions decide on payer_groups.rule_group(): Commercial Ins → Commercial, RAE (…) → RAE payer, Dental - CHP+/DentaQuest → Dentaquest, Self Pay → Program, Payer Not In Group → unmapped; others pass through. CHP+, Patient and the Dental groups have no telehealth rule (Review); the vaccine rule now applies to them (document #18: all payers except Medicare).
  • One-off sheet from the 21-Sep report: Output\Charge Summary_09-21-2026.xlsx (1,145 claims; 301 with blank Group Name — unmapped payers). With the dashboard table the same report has 0 blank Group Names.

Results database + web page (since 2026-10-08)

The same Charge Summary rows also go to SQL Server, and a local page shows them. Spec: docs/superpowers/specs/2026-10-08-results-db-and-web-page-design.md. Portal look since a436489 (2026-10-26, operator: "konjam attractive, content add, grid correct, header footer align"): header, breadcrumb, page and footer share one 1180px column; header has a "CV" mark, tab-style links with the current page marked (aria-current), a user avatar; a footer at the bottom of every page (brand, PHI note, mode). Dashboard adds the pass rate on Checked today, a Last 7 days Pass / Fail / Review chart (a day links to its page), Today's Fail and Review reasons (comments grouped with counts; the extra day_results query runs only when today has claims) and three equal cards (Claim status, Runs, Find a claim). Motion is movement only, never opacity (a tab the browser holds in the background must not stay faded), and off under prefers-reduced-motion; still no inline style= attributes (a test). Checked in the Browser pane against a made-up-data preview (port 8765, a scratchpad script; no SQL, no real users), desktop and phone width. The running Portal shows it after run_web.bat is restarted. Sign-in page since 61338b4 (2026-10-26, operator): a card on a slow navy-to-blue moving gradient — navy brand panel ("Charge Validation" and three feature titles only, no description lines; floating circles, pulsing CV mark) and the form (icon fields, Show / Hide password, Caps Lock warning, idle / lockout rules read from web/users.py). Every page has an inline SVG tab icon (no /favicon.ico 404). Motion off under prefers-reduced-motion. Live on the Portal (restarted 2026-10-26, [portal] terminal tab).

  • Server MECARE331\MECARE331, database Charge_Validation, table dbo.CV_ChargeSummary (every table of this project starts with CV_, operator 2026-10-12): the format's 24 columns + run_at, run_mode (VERIFY / LIVE-WRITE, or TEST VERIFY / TEST LIVE-WRITE since 2026-10-12), details_json (conditions fired, before/after changes, review reasons, Dental results). Amounts → DECIMAL, dates → DATE, unconvertible → NULL.
  • core/results_db.py; the flow calls _save_results_db after each claim's Excel write, with that claim's row only (since 2026-10-12). Every row of one run shares one run_at (kept on the run's info dict), so the page shows it as one run. Only rows the run filled in (a Category) are saved; a claim not found saves nothing. RESULTS_DB=1 turns it on (default off); RESULTS_DB_CONN (default: Windows login, no password). A DB error is one [RESULTS-DB] log line; the run and the Excel are unaffected.
  • Setup (once): run tools/sql/create_tables.sql in SSMS; give the Windows login db_datareader + db_datawriter (commented lines at the end of that file). Done 2026-10-12 on MECARE331: table created, grants run for MFILESERVER\EC10980, renamed to CV_ChargeSummary with tools/sql/rename_to_cv_prefix.sql. Checked read-only from the bot's own connection: 28 columns, types / widths / indexes match, 0 rows, the web repo reads it.
  • Portal (2026-10-19, 2da2bdd): run_web.bat → http://127.0.0.1:8000 (local only, PHI). Operator: a second run hid the first (the old page opened on the newest run only), and the front page was three quarters claims table. Spec / plan: docs/superpowers/specs/2026-10-19-results-portal-redesign-design.md.
    • Dashboard /: Bot run card (below) with a Live claims panel (every claim of the running / last run: result, Checking, Waiting; Not finished / Not checked once over; a saved result opens its day page), today's tiles, Find a claim, a big Claim status button. No claims table.
    • Claim status /days: one card per day a run STARTED (all runs of a day together, last 90 days), newest first: claims checked, runs, Pass / Fail / Review, colour bar.
    • One day /day/YYYY-MM-DD: every row of that day's runs; tiles filter by Category; claim # search; short table (#, Claim #, Time, Payer, Result, Comments), 10 a page, no sideways scroll; a row opens every field + conditions JSON in a side panel (empty fields hidden); ?claim=<no> opens that claim.
    • Find a claim /claim?claim_no=: every saved check (newest first, 200 max), each linked to its day.
    • Navy & blue theme, breadcrumb, VERIFY / LIVE-WRITE pill (templates/base.html). Old /?run_at= links redirect to the run's day. JSON: /api/runs, /api/results. The same claim checked twice in a day shows twice; "Not found" claims have no saved row.
    • Fed by the bot's [CLAIMS] Queue: ..., [CLAIMS] Results run_at: <ISO> (the saved run's time, RESULTS_DB=1 only) and one [RESULT] claim n/N <id>: <Category> line per claim.
  • Bot run card (2026-10-14): Start run / Stop / live status — POST /api/bot/start, POST /api/bot/stop (Origin must be this page and header X-Run-Token must match the token in the page; else 403), GET /api/bot/status. Spec / plan: docs/superpowers/specs/2026-10-14-start-bot-from-web-page-design.md.

6. Rulings and decisions (operator, keep these)

  • 2026-09-29 Vaccine admin units: always 1 (document says count-based; client to confirm).
  • 2026-09-30 Common rule: zero fee checked → uncheck; priced unchecked → check (both halves, first/last).
  • 2026-09-30 A checked row billed at 0 does not count as billed (vaccine, depression agree with it).
  • 2026-09-30 Depression duplicates: keep the first priced G8510/G8431.
  • 2026-09-30 Sliding fee: Dental only; compare only the date range with DOS.
  • 2026-09-30 High risk / sliding fee: show in a message box for now; Excel later (now in the summary).
  • 2026-10-01 Condition #9 not needed — reverted.
  • Client: J2930 gets no replacement code (just uncheck + zero).
  • Client: Z13.31 is the only depression pointer (no Z13.32).
  • 2026-10-05 Review steps 1–6 approved and done (commit f5bf3f9). Rulings taken in that work:
    • SAVE_CORRECTIONS code default 0; APPLY_CORRECTIONS stays 1 (it only fills the form, closed with Cancel).
    • PHI/client files in the repo root are gitignored in place, not moved.
    • A TH in a Modifiers: list is the OB modifier, not telehealth.
    • Audio-only (93) needs explicit evidence; "telephone" still means audio; default is video.
    • A telehealth note on a payer with no modifier rule is Review, never a guessed modifier.
    • A claim with a failed / declined condition or an unread note is never offered Save.
  • 2026-10-06 Payer groups come from the team's Claim Status Dashboard (Payer Name → Payer Group Name), not a hand-made list.
  • 2026-10-06 Clean-claim comment: "Clean claims (claims that have no errors, placed in Pending with Errors)"; other claims keep their condition comments.
  • 2026-10-06 Dummy codes are DCONS and Dscprit only (uncheck + fee 0, any payer). BHT3/BHT4 are not dummy codes.
  • 2026-10-14 Results page can start the bot: VERIFY only (LIVE-WRITE stays terminal-only); information boxes off for a page run (log [BOX] lines), boxes needing a person stay on; one live run at a time (a second refuses before login, exit 2).
  • 2026-10-16 #19 Medicare: only G0008 / G0009 trigger condition code A6 (not #18's vaccine list; G0008/G0009 are not added); UB first empty Condition Code box; HCFA → Review by hand.
  • 2026-10-21 #19 (0f090e8): the HCFA condition codes need no mapping — Medicare claims are billed on UB (#2); a Medicare G0008/G0009 claim on HCFA stays Review (wrong form). #19 is complete.
  • 2026-10-16 VERIFY writes nothing to eCW (option C): a CPT line's Assign To Patient box is set on screen only (no click, so eCW's save does not run) and no CPT row is added — a code that would be added is Review "add by hand". LIVE-WRITE still clicks and adds.
  • 2026-10-16 #13 Dental codes only to DentaQuest / Delta Dental / CHP+ Dental (the document's three; not other Dental payers); otherwise Review + comment (check program, then Submit to Patient) — the bot does not change the Claim Status yet.
  • 2026-10-21 #1 (Claim Status → Patient for a claim whose every priced line is unticked) — built in 1c6b0f9, then reverted the same day (5354c84) on the operator's instruction: #1 is not needed. priced_line_check ticks every unticked priced line again, as before.
  • 2026-10-21 #16 (secondary Medicaid) — not needed (operator).
  • 2026-10-21 #13 (df74331): a secondary (any insurance row) DentaQuest / Delta Dental / CHP+ Dental counts; with none, the bot sets the Claim Status to Patient (PAT) itself, no program check (option A). Written only where the run may write (LIVE-WRITE, not the Dental screen); elsewhere Review "set it by hand". Claim Status DOM (HCFA / Dental ClaimData.ClaimStatus, UB selectedClaimStatusCodes) from the claim form DOMs the operator pasted.
  • 2026-10-19 Results portal: Dashboard (Bot run card, today's tiles, Find a claim, Claim status button — no claims table on the front page) → Claim status = date cards (all runs of a day together) → one day = short table + side panel; navy & blue theme; live claims in the Bot run card; search one claim over all dates.
  • 2026-10-15 Results page table: claim number and Category always on screen; a Claim status button lists every claim of the run; one vertical scroll only, 10 claims a page, column slider above the table.
  • 2026-10-07 Every claim runs every "All payers" condition plus the conditions for its own payer (Medicare, Medicaid, Dental...) — the same for all payers, Dental included.
  • 2026-10-07 #14 Medicare: G8510/G8431 unchecked + fee 0, no other code touched; the depression-pointer process (#6) does not run on Medicare; screening only → also Review (provider clarification).
  • 2026-10-12 #14 on a UB form with no Assign-To-Patient checkbox: zeroing the fee is enough (no Review); the comment says "fee zeroed", not "unchecked".
  • 2026-10-07 #12 H codes: payers whose name starts with "BH" only; every modifier on the H code line is removed; telehealth visits excluded (GT stays).
  • 2026-10-12 #12 H codes are billed to BH payers only: on any other payer (Medicare, Medicaid, Commercial ...) the H code line is unchecked and its fee zeroed — telehealth included.
  • 2026-10-12 #14 stays G8510 / G8431 only (not G8511 / G8432).
  • 2026-10-12 Every database table of this project starts with CV_ (results table: dbo.CV_ChargeSummary).
  • 2026-10-12 Number-one rule, restated: every claim = every "All payers" condition + its own payer's conditions; Dental claims too (now built). A fix the Dental screen cannot hold → Review; Dental claims are never saved for now.
  • 2026-10-12 Charge Summary (Excel + DB) is written after every claim, not at the end of the run; Comments keep the per-condition wording (Clean / depression pointer / invalid codes ...).
  • 2026-10-12 One claim's problem never costs the next claim. Before every claim the screen is cleared (extra tabs, note viewers, a leftover form by Cancel, stray panel) and Claim No + Lookup are verified on screen and uncovered. A claim that hits a problem or a page that will not clear → that claim is Review in the summary (with the reason) and the run moves to the next claim.
  • 2026-10-12 #15 High risk = "HIGH RISK" in the Billing Notes and the whole notes 79 characters or more (spaces included). "HIGH RISK" in shorter notes → not high risk (not Review).
  • 2026-10-21 #15 (da8acdb): the 79 characters are counted from "HIGH RISK" to the end of the Billing Notes (later lines included), not the whole notes; text before it (an earlier line) does not count.
  • 2026-10-13 #43 / #44: on a Medicaid claim 15851, 15852 and 73521 are unchecked and their fee set to 0 — one condition, medicaid_uncheck_cpts.
  • 2026-10-21 Selector breaks: no live "AI healer" (sending a page's DOM to an AI service mid-run sends PHI) until a HIPAA approval (BAA / zero data retention) exists; even then read / navigation steps only, never a write step. First: fallback selectors + automatic DOM/screenshot capture on a break + a pre-flight selector check, fixed offline in Claude Code.
  • 2026-10-21 UHC Community ("UHC Community Plan" / "... Do not use") is Medicare — payer_groups.json stays as it is: #20 adds no 96372 there, #14 unchecks G8510. The client's #20 example on that payer does not apply.
  • 2026-10-21 #22 (21038b1) Medicare Advantage plans (AARP Medicare Advantage, Aetna Medicare ADV) follow the dashboard: Commercial for every condition (payer_groups.RULE_OVERRIDES now empty). So telehealth gets #21 93/95, not #22 G2025; no #14 G8510/G8431, #19 A6 or #27-#29 history check; #18 vaccine admin and #20 96372 are added.
  • 2026-10-20 #23: Medicaid only (not RAE); Z32.00/Z32.01/Z32.02 (pregnancy test) never get TH; TH goes in M1; a Medicaid telehealth pregnancy visit gets TH, not GT.
  • 2026-10-21 #23 confirmed (8f5a4a4): pregnancy ICD = O*, Z33, Z34, Z36, Z3A (no progress-note keywords); TH in M1 of the first billed E&M line (none → first billed line); M1 in use → overwritten with TH, not moved to M2-M4. TH already in M2-M4 → left alone (bot's choice: no second TH).
  • 2026-10-23 #25 (operator, spec EEWA-34): PPS Payers by exact eCW name (list in §4 row 17); the two Cigna plans are an exception to the 2026-10-21 #22 Medicare Advantage = Commercial ruling, other "Medicare" group payers are not in #25. UB only; HCFA → Review; Dental → skipped. Billed lines only. New vs established decided by a code on one list only; both-lists codes alone → Review. Conflicting codes → Review. G0468 wins over an E&M. One PPS Code for the medical visit, one for mental health. Different PPS Code billed → Review, never overwritten. LIVE-WRITE adds Units 1 through the picker; VERIFY adds nothing. G0468 not added when #27–#29 found a repeat — nor when the history is not checked (the Cigna plans: #27–#29 never runs on them) → Review (operator, later the same day).
  • 2026-10-20 #27/#28/#29 (client, via the operator): G0402 / G0438 / G0439 come on Medicare claims only. The bot only CHECKS the history in Account Inquiry and writes the error in the Charge Summary Comments — never adds, changes or unchecks. G0402 billed before at any time; G0438 inside 3 years; G0439 inside 12 months → error (Review). Window from this claim's service date.
  • 2026-10-20 #48: DFIMP (the live Dental grid's "FINAL IMPRESSION" code) is the same dummy code as the document's DPIMP — both handled; notes and the box name the code found.
  • 2026-10-13 #48: DPIMP is a dummy code on Dental payers → uncheck + fee 0, in its own file dental_dummy_codes (#8 unchanged). DPIMP on any other payer → not changed, noted in the Comments (the claim is Review).
  • 2026-10-14 An HCFA claim form Cancel will not close before the next claim (or one still taking no clicks after the 10 s wait): Escape as the last resort (HCFA window only; UB / Dental left as they are), logged loudly — so one stuck form cannot send the rest of the run to Review.
  • 2026-10-13 #18 vaccines are the client's 19-code list only, admin codes 90460/90461/90471/90472 only; a vaccine code off the list is left alone (no note).
  • 2026-10-14 #43/#44 (medicaid_uncheck_cpts): Medicaid payers only — NOT the RAE (BH) payers, CHP+ or any other group. As built; no code change.
  • 2026-10-21 #44: only 73521 is unchecked on Medicaid, not every imaging code (the client examples' 76801 / 76805 are not added). As built; no code change.
  • 2026-10-13 #20 injection codes: the client's 41-code list as given; 96372 only; always Units 1; Medicare excluded.
  • 2026-10-14 #20 final (operator): ONLY the codes the operator gave — the 41 injection codes (IUD / implant, inhalation and oral J-codes included, as listed) and 96372 as the one admin code. No other code is added or counted; no client confirmation needed.
  • 2026-10-08 Results go to SQL Server MECARE331\MECARE331, DB Charge_Validation, in the Charge Summary format (incl. PATIENT, Account Number, Guarantor Name); FastAPI page for now, the full portal later.

Waiting on a ruling

  1. An admin code / G2025 that the Fee Schedule prices at 0.00 after being added (pinned in tests).
  2. medicare_telehealth with G2025 unconfirmed leaves other rows; priced_line_check may then check an unchecked priced row (pinned in tests).
  3. Dental HIGH RISK / sliding EXPIRED rows say "Corrections pending (not saved)" although nothing was corrected. Needs the operator's wording (e.g. "Submit via DentaQuest portal").
  4. Demo .env: which sheet TEST_CLAIM_IDS_FILE points at (now the Dental sheet), and a shorter MSGBOX_TIMEOUT_SECONDS?
  5. #25 mental health: G0469 or G0470 (new / established) — the client's ruling (EEWA-37). Until then Review.
  6. Sliding fee renewed: a claim whose DOS falls in the patient's previous sliding fee period, checked after a renewal — Pass on the old period, or Fail "NOT STARTED" against the current one (as built)? Seen 2026-10-26. Billing team / client to rule.

7. Working agreements

  • Never commit or push unless the user asks at that moment. No remote exists.
  • Never commit run.py (has an unrelated local change), Dom.txt, Charge Condition Document.xlsx, Charge Summary File Format.xlsx, ~$* lock files.
  • Jira posts (comments, tickets) carry no claim ids and no patient data.
  • Context.md is tracked in git, so keep it free of claim ids, patient data and credentials.
  • Every tracked file, not only Context.md: no real claim id, no patient or provider identifier. Code comments, selectors, fixtures and tests use synthetic values (claim ids 91xx000, 9000001, 8000014; the patient "Sample, Pat Q"). A scan on 2026-10-23 found 33 real claim ids in 52 tracked files and one captured patient's identifiers (name, DOB, account number, address, phone, subscriber number) plus two provider names in the UB / progress-note fixtures and their tests — all replaced (synthetic, not reversible; the tests unchanged otherwise). The local git history still holds them: never push this history. A remote (Gitea) gets a fresh single-commit snapshot of a scanned tree only (§10).
  • Input\, Output\, Logs\, Screenshots\ contain PHI — gitignored; never paste their contents.
  • Never print or type credentials from .env.
  • Keep SAVE_CORRECTIONS=0 until the client approves writes.
  • Live eCW inspection is read-only; close claims with Cancel, never OK.
  • User writes in Tanglish; answer in Tanglish.
  • New eCW screens are mapped from a DOM dump the user pastes into Dom.txt.

8. Git state

History: git log --oneline.

Last code commits: 1da9e7c (2026-10-27, Portal sign-in with the Ecare office login — §2), cc3a01a (2026-10-27, Portal container on India time — §10), 69c51d8 (2026-10-27, Dockerfile: libgssapi-krb5-2 for ODBC Driver 18 — §10), 70ba709 (2026-10-27, Portal server mode for Dokploy — §10), f99dc17 (2026-10-27, Portal roles Super Admin / Admin / Viewer — §2), 61338b4 (2026-10-26, Sign-in page redesign + tab icon — §5), a436489 (2026-10-26, Portal look: aligned header / footer, 7-day chart, today's reasons, pass rate — §5), 2d4d4fc (2026-10-23, tracked files de-identified — §7), 8fea168 (2026-10-23, "Chrome open" box before the login and the empty first tab closed, in a Chrome the run opened — §2), 1c7eb55 (2026-10-23, #25: G0468 → Review when the preventive visit history is not checked — the Cigna plans), ac25f83 (2026-10-23, #25 medicare_pps_code — PPS Code for PPS Payers; medicare_wellness_history leaves its verdict for it; EEWA-35/36, §4 row 17), 0f16560 (2026-10-22, claim watchdog — exit 8; Start opens Google Chrome itself and closes it after; proxy check on an attached Chrome; tests isolated from the real BROWSER_CDP_URL, §2), 37c214d (2026-10-22, the Dashboard says "the Portal is not reachable — is run_web.bat still running?" when a Start or a status poll cannot reach the server; a Start used to do nothing), 57d22fb (2026-10-22, auto_dialog answers every dialog — Cancel a confirm in VERIFY, §10), ecbbf3a (2026-10-22, last-run banner and CV_ChargeSummary.started_by — EEWA-33 Stage 3, §2), c230a8e (2026-10-22, Portal Sign-in, roles, Started By / Stopped By — EEWA-32 Stage 2, §2), 9a07f74 (2026-10-22, events file closed at the end of a Run; tests keep events files out of the repo), 751a177 (2026-10-22, Run events + Runs page — EEWA-31 Stage 1, §2), 8af5c14 (2026-10-22, attach to the operator's Chrome over CDP — BROWSER_CDP_URL, eCW cookies cleared at start and end, §2), 5354c84 (2026-10-21, #1 reverted — not needed), df74331 (2026-10-21, #13 Claim Status set to Patient, secondary insurance counts, EEWA-27), da8acdb (2026-10-21, #15 high risk 79 characters from "HIGH RISK" to the end), a6c01d6 (2026-10-21, #27-#29 Account Inquiry launcher from the real DOM), 8f5a4a4 (2026-10-21, #23 TH always in M1, overwriting), 21038b1 (2026-10-21, #22 Medicare Advantage = Commercial for every condition, RULE_OVERRIDES empty), c0e1a75 (2026-10-21, selector resilience — fallback selector chains, pre-flight check (exit 7), DOM saved on every failure shot), 9b87b4d (2026-10-20, #23 medicaid_pregnancy_th — TH on a Medicaid pregnancy visit, TH not GT on telehealth, EEWA-30), 686ed0c (2026-10-20, #27-#29 medicare_wellness_history — G0402/G0438/G0439 history check in Account Inquiry, EEWA-29), 1dffa10 (2026-10-20, eCW password typed key by key — fill() left Log In dead after the password change; #48 DFIMP), 2da2bdd (2026-10-19, results portal — Dashboard, Claim status by day, day page, Find a claim), 6005b7d (2026-10-19, code review fixes: lines.ends_billed for #13/#19, A6 read-back and visible UB form only, VERIFY add-by-hand wording, Dental never written, P16 docs), 6457100 (2026-10-16, #19 medicare_vaccine_a6 — A6 for G0008/G0009 on Medicare, UB), 57e2f5a (2026-10-16, VERIFY writes nothing to eCW — boxes set on screen only, no CPT row added), d74b7b3 (2026-10-16, #13 dental_payer_check — Review only), 11b1032 (2026-10-15, results page: one vertical scroll, 10 claims a page, column slider), 5e8c76a (2026-10-15, results page: claim columns pinned, claim side panel, Claim status button, [CLAIMS] Queue: line), 1d79ac3 (2026-10-15, page run reads .env afresh, page's results DB), f2d29e5..665ca7e (2026-10-14, start / stop the bot from the results page: run record, [RESULT] line, QUIET_BOXES, run status, bot runner, Start/Stop card; branch feature/start-bot-from-page fast-forwarded), a28ffac (2026-10-14, HCFA form Cancel will not close → Escape as the last resort in clear_screen), ec150bf (2026-10-14, review fixes: search page waits up to 20 s for eCW to finish loading; a claim window is never Escaped by the stray-panel check; no duplicate 96372), 719d185 (2026-10-13, #18 vaccine code list + #20 injection admin 96372), f8216a7 (2026-10-13, claim windows that take no clicks: Chrome flags + settle wait), c138c48 (2026-10-13, #48 DPIMP on Dental payers), b7d078b (2026-10-13, #43/#44 Medicaid 15851/15852/73521 unchecked + fee 0), 3d12518 (2026-10-12, #15 High risk needs 79-character Billing Notes), 77fdffd (2026-10-12, note viewers counted by clicks + leftover-form diagnostic), a9dcc63 (2026-10-12, stacked dialogs + clean search page before every claim), ebc4382 (2026-10-12, results table renamed CV_ChargeSummary), cef7666 (2026-10-12, Dental claims run every condition), 48a6940 (2026-10-12, low-severity review hardening), 449813e (2026-10-12, #12 H code off a BH payer unchecked + zeroed), a979eb8 (2026-10-12, review fixes for #12/#14), 27b8f39 (2026-10-12, summary written claim by claim), 8fbd20d (2026-10-08, results DB + web page) and 394d01e (2026-10-08, DCONS/Dscprit, #14 Medicare screening, #12 BH H codes). Uncommitted (check git status):

  • run.py: local change, never commit (§7).
  • tests/test_guard.py: the operator's local change, not ours.
  • src/ecw_bot/claim_file.py (one-line change) and untracked tools/sql/Create_CV_Table.rpt: present before 2026-10-22's CDP work, not part of it; ask the operator before committing.
  • Root Dom.txt, client .xlsx files and ~$ lock files no longer show in git status: they are gitignored, still in place.

9. Jira (project EEWA, parent EEWA-5 "Charge Validation")

Status lives in Jira; the open work is in §6 and §10.

Key Task Status (2026-10-16)
EEWA-8 Medicare Telehealth Done
EEWA-10 j2930_j2001_update Done
EEWA-11 dummy_codes Done (2026-10-12 comment corrects the old BHT3/BHT4 one: DCONS/Dscprit only)
EEWA-12 vaccine_admin_codes (2026-10-13 comment: client's 19 vaccine codes, 4 admin codes) In Progress
EEWA-13 sliding_fee Done
EEWA-14 dental_high_risk (incl. the 79-character rule) Done
EEWA-15 bh_h_code_modifier (#12 H codes) Done (closed 2026-10-20)
EEWA-16 medicare_billing_screening (#14; rulings pending) Done (closed 2026-10-20)
EEWA-17 Results database and web page Done (closed 2026-10-20)
EEWA-18 Claim-by-claim run stability (clean page, stacked dialogs, ghost form) Done (closed 2026-10-26: 50/50 runs 22/10 and 23/10)
EEWA-19 medicaid_uncheck_cpts (#43 15851/15852, #44 73521; Medicaid only, not RAE — ruled 2026-10-14) Done (closed 2026-10-20)
EEWA-20 dental_dummy_codes (#48 DPIMP on Dental payers) In Progress
EEWA-21 injection_admin_code (#20 add 96372; only the operator's 41 codes — ruled 2026-10-14) In Progress
EEWA-22 Log messages use first_line (review cleanup) To Do
EEWA-23 Run record: one place for its start/end time logic (review cleanup) To Do
EEWA-24 Run record: one Windows "running process" query (review cleanup) To Do
EEWA-25 Results page: live check of Stop on a VERIFY run To Do
EEWA-26 RunStatus type from runner to status to page (blocked by EEWA-23) To Do
EEWA-27 dental_payer_check (#13 Dental codes only to DentaQuest / Delta Dental / CHP+ Dental; Claim Status → Patient since 2026-10-21) To Do
EEWA-28 medicare_vaccine_a6 (#19 G0008/G0009 on Medicare → condition code A6; UB done, HCFA Review — no mapping needed, Medicare = UB) To Do
EEWA-30 medicaid_pregnancy_th (#23: TH modifier on a pregnancy visit on Medicaid) To Do
EEWA-29 medicare_wellness_history (#27/#28/#29: G0402 / G0438 / G0439 billed too often → Review) To Do
EEWA-31 run_events + Runs page (Stage 1) Done (closed 2026-10-26; built 751a177, seen on every live run since 2026-10-22)
EEWA-32 portal sign-in (Stage 2: Portal Users, Roles, Started By / Stopped By); blocked by EEWA-31 In Progress (built 2026-10-22, c230a8e; live Sign-in and a Portal Start with Started By 2026-10-22; roles Super Admin / Admin / Viewer f99dc17 2026-10-27; Viewer not yet tried)
EEWA-33 portal run alerts (Stage 3: Run Error banner, started_by column); blocked by EEWA-32 Done (closed 2026-10-26; built ecbbf3a; banner seen after the 19:42 run, started_by on the Portal runs' rows)
EEWA-34 medicare_pps_code (#25) — spec To Do (parent of the work below)
EEWA-35 #25 PPS Code check — Review when missing In Progress (built 2026-10-23, ac25f83; not run live)
EEWA-36 #25 add the PPS Code in LIVE-WRITE; blocked by EEWA-35 In Progress (built 2026-10-23, ac25f83; not run live)
EEWA-37 #25 mental health G0469 / G0470 — after the client's ruling To Do (waiting on the client)

EEWA-15 to EEWA-18 were created on 2026-10-12 for work already built and moved to In Progress on 2026-10-13; EEWA-19, EEWA-20 and EEWA-21 were created and moved to In Progress on 2026-10-13. EEWA-22..26 were created on 2026-10-15 from the code review's leftovers (/to-tickets), as Subtasks of EEWA-5 (EEWA-5 is a Task, not an epic: a Task under it is refused), label ready-for-agent. How skills use Jira: docs/agents/issue-tracker.md. EEWA-21 has the 2026-10-14 #20 ruling as a comment. New tickets are assigned to Manikandan. The older descriptions of EEWA-13/14 (2026-10-01) name test claim ids — new posts must not (§7).


10. Open items

  • Stability (review of 25 runs, 20–26/10, 2026-10-26). After the eCW login the run is steady: the last five full runs read 50/50 with no bot error and the same results run to run. The weak part is getting in: about half the runs never passed the login — "No second factor prompted / Login not confirmed" (four on 20/10, not since), a late 2FA mail (372 s once, one 9-minute timeout), the 19:42 restriction (fixed by the proxy check), guard waits, and stops by hand. Two runs stopped at "Opening landing page" (Chrome takes ~2 min there); the watchdog came after them and has not fired live yet. Suggested, not ticketed: a landing-page timeout with one retry, a 2FA retry, a week of daily runs to measure login times on the Runs page.

  • Login retry bug (seen 2026-10-27 16:50, not fixed). After a 2FA timeout the run closes the Chrome it opened and attempt 2 attaches to 9222 at once, while that Chrome is still closing → connect_over_cdp: Target page, context or browser has been closed, run over. Fix: wait until 9222 stops answering, then open a fresh Chrome (test-first).

  • Gitea: one overall snapshot update at the end of a day, when the operator asks (from the clone D:\ecw-charge-validation; operator pushes; 2026-10-26: ba15a57 → 120fd68).

  • Portal for the client — Gitea + Dokploy (asked 2026-10-23; LIVE on Dokploy 2026-10-27, cc3a01a, Ecare network only). URL http://10.200.0.15:8085 (Dokploy project Ecw_ChargeValidation → production → app Portal; port 8085 → 8000). The operator signed in there as Super Admin; Dashboard, Users, Sign-ins work. How it got there:

    • Source = Drop (zip upload), not Git: Dokploy cannot clone from Gitea (server certificate verification failed, Traefik's self-signed cert); Gitea has no internal http port and no SSH; the operator cannot open Dokploy Settings → Git Providers; the OTS-Team Gitea account does not see automation-tools. So: git archive --format=zip HEAD Dockerfile requirements-portal.txt src web → D:\ecw-portal-deploy\ portal-<hash>.zip → app → General → Provider → Drop → select → the Deploy under the drop box (the top Deploy / Reload rebuilds the last upload). Long term: ask the Dokploy admin for a Gitea provider.
    • DB: the SQL Server is on the operator's PC (MECARE331 = 10.50.2.131), instance TCP port 58468 (dynamic — a SQL restart may move it; set a static port later). SQL login cv_portal (made in SSMS 2026-10-27: db_datareader + INSERT/UPDATE CV_PortalUser, INSERT CV_PortalSignIn); its password only in Dokploy's env (RESULTS_DB_CONN), never here or in chat. The Portal works only while that PC is on.
    • Fixes found live: 69c51d8 the image needs libgssapi-krb5-2 by name (autoremove took it; ODBC Driver 18 "Can't open lib ... file not found"); cc3a01a TZ=Asia/Kolkata + tzdata (the server is UTC: a 15:45 IST Sign-in read 10:15; three such rows stay in CV_PortalSignIn from 2026-10-27 ~15:45). Errors seen: SQLSTATE 01000 = driver not loading; 28000 = login missing / wrong password.
    • Sessions are in memory: every Deploy / Reload signs everyone out.
    • For the client: needs a public HTTPS URL (or VPN) from IT, and the DB off a desktop PC. Dockerfile (python 3.13-slim + ODBC Driver 18, copies only src/ + web/, requirements-portal.txt); env in Dokploy, never in Git: RESULTS_DB_CONN (a SQL login; a Linux container cannot use Trusted_Connection or, likely, the named instance — host,port), PORTAL_URLS (hosts / origins; all-https = Secure cookie), PORTAL_SERVER=1 (set in the image: NoRunner, no Start / Stop). The Runs page there is empty (events files stay on the bot PC). Earlier notes: Gitea giteanew.ecareindia.com (10.200.0.15, internal; its HTTPS certificate is not in Git Bash's curl bundle) and Dokploy (http://10.200.0.15:3000, same server). The operator joins Dokploy and creates a Private Gitea repo themselves; nothing is pushed until they say so, and only a single-commit snapshot of a scanned tree (§7). The Portal cannot simply run in a Linux container as it is: Start / Stop starts run.py (Windows, Chrome, eCW proxy, message boxes; never committed), the Runs page reads Logs\events_*.jsonl (PHI, on the bot's PC), the DB connection is Trusted_Connection (needs a SQL login from a Linux container, and a route to MECARE331), and host / allowed origins are 127.0.0.1 with no Secure cookie. Plan: the container is the results Portal (Viewer for the client); the bot stays on the Windows PC; Runs data to SQL later. Needs Ecare IT + Salud approval (PHI on the page), a SQL login, the repo URL. Also open: the "Chrome open" box waits up to 3 min on an unattended Portal run — off-by-default setting suggested, not decided.
  • #25 medicare_pps_code (2026-10-23, ac25f83, EEWA-35/36) — not run live. Built test-first on the fixture UB claim (the fixture's Fee Schedule prices for G0466 / G0467 / G0467NH / G0468 are made up; G0467H left out to test "the picker cannot find it"). Needs test claims from the client: Medicare UB 99213 without G0467; Devoted MCR UB 99203 without G0466; a home visit 99347; a nursing-home visit 99309; an AWV G0439; a mental-health 90834; a Cigna Healthspring claim; one already carrying the right PPS Code. Watch on the first live VERIFY run: how many claims go to Review for it (every HCFA claim of a PPS Payer with a Qualifying Code — "HCFA Medicare" is one of the names), whether the live picker finds the H / NH variants, and the fees eCW puts on them. Open: §6 "Waiting" 5. The code review (2026-10-23) left two judgement calls as they are: the hand-over through data["_wellness_history"] (documented in conditions/base.py) and the use of vaccine_admin_codes' private _facts / _add_admin_code (as injection_admin_code does).

  • Run tracking (EEWA-31..33, 2026-10-22). All three stages built and tested offline; the database part set up the same day (tables, column, first Admin ec10980 — §2). Runs that day, as the events files show them:

    • 16:24 (terminal, 50-claim test sheet): login in one attempt (Log In → 2FA mail 46 s), 17 claims checked, then Ended without a record — the process was gone while the per-claim box after claim 17 was up (window closed, most likely); the record has no end and no exit code.
    • 17:56 (terminal, VERIFY with information boxes off): the 2FA mail never came; Stopped after 8.5 minutes still waiting (no claims). Same as attempt 1 on 2026-10-21 — a mail that does not come is eCW / mailbox side, not the bot.
    • 18:11 (terminal): login OK in 117 s (mail 53 s), then claim 1 hung 28 minutes after its UB form opened and the pre-flight passed — the next step is reading the form (claim_form.extract); no error, no dialog, and the open click had already timed out (5 s) though the form was there. Stopped from here at 18:43. The same claim took 18 s in the next run, so it looks like a frozen page, not the claim — see the item below.
    • 18:47 (Portal, started by ec10980) — Done, exit 0: login in one attempt (107 s, mail 43 s), 50/50 claims: 39 Pass / 11 Fail / 0 Review, no Claim Error, Warning or Run Error, ~18 s a claim (information boxes off), 17 minutes in all. Same split as the 2026-10-21 live run. Started By was ec10980 in the events, in bot_run.json and on all 50 CV_ChargeSummary rows. Sign-in worked live at 18:24 (a first Start clicked after run_web.bat had been closed did nothing — fixed in 37c214d). Still to check live: a Viewer without Start, and the Dashboard banner after a failed run. All decisions are in the spec.
  • A page that freezes mid-claim — watchdog added 2026-10-22 (0f16560), not yet seen firing live. The 18:11 run sat 28 minutes after claim 1's form opened (Playwright's evaluate has no timeout). Now no progress for WATCHDOG_MINUTES (10) closes the driver; the claim becomes a Claim Error (Review), the run ends with exit 8 at step stalled, the rest Not Reached, the Dashboard banner shows it. The cause of the freeze itself is still unknown (not seen again in the 18:47 or 19:53 runs).

  • Found while mapping the login (2026-10-22), not changed: auth/mailbox.py prints a mailed 2FA code into the run log ([OTP] Code ...; the page tail hides it, the file does not); a login page with no username box raises Restricted (exit 4) though it is more likely the proxy; OK on the final captcha box counts as logged in without checking the home page.

  • auto_dialog — fixed 2026-10-22 (57d22fb). It called dialog.message(), a property in Playwright Python, so the TypeError was swallowed and no dialog was ever answered (a real Chromium tab crashed on an unanswered confirm). Now: alert → OK; confirm / prompt → Cancel in VERIFY (live eCW is read-only; a confirm's OK may be a save), OK only in LIVE-WRITE; beforeunload dismissed. Each is logged ([DIALOG] confirm: '...' -> Cancel) and is a Warning on the Runs page. No dialog has appeared in any live log yet; if a VERIFY confirm ever needs OK (say, a form that will not close without it), the Warning shows which one — revisit the rule then. Tests: test_auto_dialog.py.

  • CDP Chrome — live 2026-10-22, still to check: the Claude extension in that Chrome (added and signed in with start_ecw_chrome.bat; claude.ai bypasses the proxy, not yet tried), and why the landing page after Home took ~2 min in Chrome (15 s in Chromium). eCW loaded through the proxy with no certificate error; cookies were cleared and Chrome closed at the end (§2).

  • LIVE-WRITE ISSUE — fixed in code 2026-10-16 (57e2f5a), live check pending. Found 2026-10-15, confirmed by the operator in the eCW audit log 2026-10-16: clicking a CPT line's "Assign To Patient" box runs eCW's updateBillToIns(), which saves on the server at once — Cancel does NOT undo it (settings.py P16). Adding a CPT row (picker OK, addClaimFSCPTCode('save')) saves too. Typed edits (POS, modifiers, fees) are not kept. Earlier runs changed boxes on 9 claims (2026-09-17..10-15, mostly G8510 checked by the old depression re-include step); the list is in the logs (no claim ids here) and the operator restores them by hand from the audit log. Fix (option C): in VERIFY claim_form._set_bill_box sets the box by JS (no click event) and add_cpt_row adds nothing; vaccine admin (every code it would add) / 96372 / G2025 become Review "add by hand"; #22 still unchecks / zeroes the other rows on screen (review 2026-10-19). writes_allowed(page) is False on a Dental claim in both modes. LIVE-WRITE unchanged — its box clicks still save at once even if the operator then declines the save box (out of scope for now). Tests: tests/test_verify_no_live_writes.py; the click/add mechanics tests run as LIVE-WRITE. Page VERIFY run 2026-10-16 19:08 on the 50-claim sheet (after 57e2f5a + 6457100): 50/50, Pass 39 / Fail 11 / Review 0, exit 0 — claim for claim the same as 2026-10-15's run; Fails are 9 telehealth (typed POS/M1), 1 depression pointer (typed), 1 Dental HIGH RISK (read only). No box change, no row add, no A6 in the log, so nothing was written — and the fix was not exercised. Left: the operator adds claims to the sheet that exercise it (a checked line at 0.00, a non-Medicare vaccine without admin code, a Medicare UB claim with G0008/G0009), one VERIFY run, then their audit logs show no new change.

  • #19 Medicare A6 (2026-10-16, 6457100, EEWA-28). UB done: A6 typed into the first empty Condition Code box. Left: the HCFA screen's condition codes (screenshot + DOM from the operator) — until then an HCFA Medicare claim with G0008/G0009 is Review "check A6 by hand"; and one live check that typing A6 is not saved by itself (audit log), as the DOM suggests (no blur / change hook).

  • Code review of 2026-10-19 (1d79ac3..HEAD, Standards + Spec, 66 findings, 59 confirmed / plausible): fixed in 6005b7d and 2da2bdd (the portal replaced the page findings). Left: (1) #13 secondary insurance — done 2026-10-21 (any row counts); (2) #13 tests on the UB / HCFA fixtures and a run_all-level VERIFY "zero clicks" test; (3) live checks: a box set on screen in VERIFY survives the next tab re-click (Angular redraw), a Dental fee blur is not kept on Cancel, typing A6 is not saved by itself; (4) payer lookup still copied across conditions (only #13 / #19 use lines.primary_payer); (5) Jira EEWA-27 / EEWA-28 still To Do and their summaries do not follow docs/agents/issue-tracker.md's format.

  • Results page, live (2026-10-14/15): three page-started VERIFY runs of the 7-claim sheet, each 7/7 read, Pass 4 / Fail 3 / Review 0, exit 0; card, chips and table matched. A 50-claim run (operator, terminal) gave Pass 38 / Fail 12; page runs of the 50 (2026-10-15 and 10-16) gave Pass 39 / Fail 11. The 0.5 s "search page not ready" wait saved a claim live. Left: Stop on a real run (EEWA-25). A terminal run waits at "Press Enter to close", and the page shows it as running until then.

  • Test claim sheet (Input\Medcor Ready Claim Sheet.xlsx, 2026-10-15): 50 claims (43 added by the operator's list; extra notes in column R). .env CLAIM_PROCESS_LIMIT=50 (operator) takes all 50.

  • Code review of 2026-10-14 (session changes 2026-10-12..13, 14 findings). Fixed in ec150bf: (1) the search page check waited 0 s — a claim of run 6 went to Review under eCW's loading spinner ("covered by html.jelly-page"); now wait_search_ready() polls up to 20 s, the route spinner counts as not ready, and a click falling through to the page is logged "not taking clicks yet (eCW still loading)"; (2)+(3) clear_screen() and open_claim() called dismiss_stray_modal() without sparing the claim window, so Escape could drop an HCFA form (left over, or auto-opened by eCW after the Lookup) without closeClaimScreen() — now spared, Cancel only; (4) #20: an unchecked 96372 with a fee + bot-added 96372 = two billed — now counted as billed. Left open (not fixed): close_duplicate_forms counts a Cancel it could not click; a ghost (inert) window next to a live one fails the claim after 2×10 s; #18 adds 90471 on a COVID vaccine already billed with 90480 (ask the client); DPIMP already unchecked at 0.00 on a non-Dental payer still goes to Review; progress_note.close() says "closed" when it clicked nothing; short "HIGH RISK" notes show a bare "NO" in the Dental box; payer lookup copied in 4 conditions; uncheck-and-zero apply/describe copied in 5. From the fix's own review: a form clear_screen could not Cancel stayed on screen and every later claim went to Review — operator ruled (2026-10-14) Escape as the LAST RESORT, HCFA window only, logged loudly ("LAST RESORT … closeClaimScreen() does NOT run"; check that claim in eCW by hand) — a28ffac. Not fixed: open_claim's retry after a failed click still Escapes (older path); a stray panel UNDER an HCFA window would make Escape close the form (never seen). #20: an UNCHECKED priced injection line that priced_line_check checks at the end gets no 96372 — not ruled.

  • Code review of 2026-10-08/09: no high-severity bug. The 4 medium findings are fixed in a979eb8 (2026-10-12): #14 "screening only" is judged on the live grid after the zeroing; #12/#14 decline with CPT_GRID_NOT_READ on an empty live grid; #14's comment says "unchecked" / "fee zeroed" from what was done (UB with no checkbox: fee 0 is enough, operator); #12 records a fee eCW re-prices after the modifier change (waits up to 2 s per H-code row, like Units). The low findings are fixed in 48a6940: PHI kept out of details_json, text cut to column width, SQLSTATE-only DB errors, 30 s query timeout, run_mode TEST VERIFY / TEST LIVE-WRITE, web Host check + no-store headers + clear error kinds, #12 partial note = unread, J-code detect ignores case, test gaps. Not done (need a live look): payer from the insurance row with sequence "P" rather than the first row; whether eCW's M1–M4 inputs react to key events. Rulings needed: Z13.31 retag on Medicare; WELLCARE MEDICARE / PERENNIAL ADVANTAGE CO (and unmapped payers with G8510) as Medicare for #14.

  • Results DB live setup: done (2026-10-12). RESULTS_DB=1 in .env; the SAVE-off test run wrote one row per claim to dbo.CV_ChargeSummary (7 claims, Dental included). Left: open the page with .\run_web.bat (PowerShell needs the .\) and check the rows there. .env.example does not document RESULTS_DB / RESULTS_DB_CONN yet.

  • Dental claims — live verify (built 2026-10-12, cef7666): the Dental grid selectors and extract() come from one Dom.txt capture. First live run SAVE-off on a few Dental claims (one with DCONS or Dscprit); watch how many go to Review for "Progress note not read" (Prog. Notes disabled on Dental?). Dental save stays off until that run is clean.

  • Not built: "Secondary insurance removal" (expected on a test claim), the "Special programs" half of #8, and the rest of the document rows.

  • Stacked dialogs — verified live (a9dcc63). Second SAVE-off test run 2026-10-12 (run_20261012_171538.log): 7 found, 7 read (first run: 3), 0 search failed, 0 Review; 4 Pass, 3 Fail. Every claim logged Search page ready; eCW opened claim 6 twice again and the top copy was Cancelled. The "2 progress note viewers" line on every claim was a miscount (the viewer has two Close buttons) — fixed in 77fdffd.

  • Ghost claim form after HCFA / Dental Cancel — root cause found and fixed in f8216a7 (2026-10-13), not yet seen live. Test run 2026-10-13 (run_20261013_163322.log: 7 found, 7 read, 0 Review; 4 Pass, 3 Fail) with the 77fdffd diagnostic showed two cases, both only after an HCFA or Dental claim:

    1. The HCFA window closed while the bot's review box was in front of Chrome stayed at opacity 0.222, pointer-events: none: Chrome had paused the fade-out of a window it thought hidden. The next claim clicked its Cancel four times (~45 s). Fix: Chrome is launched with --disable-backgrounding-occluded-windows, --disable-renderer-backgrounding, --disable-background-timer-throttling (core/browser.py).
    2. eCW opens the next claim's own form by itself after the Lookup (window ids are creation timestamps: made during that claim's search, before the bot's click). The bot Cancels it and clicks again — correct, 7/7 read; the log message now says so. Likely the cause of the "claim opened twice" seen before. Safety net: a window with pointer-events: none is never clicked; it gets up to 10 s to settle (gone / clickable → Cancel / still not → refused as before). click_topmost and count_shown skip such elements. Next run: there should be no 4×Cancel timeouts; look for finished on its own / takes no clicks lines.
  • Three test files used to write screenshots into the operator's Screenshots\ folder (*_search_not_ready_<claim id>.png, some *_progress_note.png of 2026-10-12/13 outside run times); fixed in f8216a7. The files already written are fake test pages, not deleted.

  • eCW password changed 2026-10-20 by the operator (the bot never changes a password). Afterwards every login hung: Log In went grey, no 2FA page. A network log showed the click sent NO request when the password was set with fill(); typed key by key it was sent at once and eCW went to its 2FA page. Fixed in password_entry.type_password() (login and captcha). A person's own logged-in eCW session is not the cause (ruled out by logging out). 2FA mail can still be slow: attempt 1 that day got no mail in 9 min, attempt 2 got it in 42 s.

  • Ideas offered, not started: expected-vs-bot accuracy report from the test sheet's Comments column.

  • Selector breaks (2026-10-21, c0e1a75): pre-flight check, DOM saved on every failure shot and fallback selector chains — verified live 2026-10-21 (run below): [PREFLIGHT] Search page OK (5 selectors), ub / hcfa / dental claim form OK, no [SELECTOR] fallback: line (every primary still matches), and the 2FA timeout saved its page as <HHMMSS>_2fa_timed_out.html. A [SELECTOR] fallback: line later means a primary needs updating. AI healer (Gemini on Vertex AI, which the operator's org already uses under its agreement) waits for the auth details (API key or service account + project/region) and the model name; read / navigation steps only, DOM masked before it is sent.

  • #23 medicaid_pregnancy_th (2026-10-20, 9b87b4d, EEWA-30) — not run live yet. Files: conditions/medicaid_pregnancy_th.py, claim_form.put_row_m1(), the TH-for-GT switch in telehealth.apply, registry after medicare_wellness_history, tests/test_conditions_medicaid_pregnancy_th.py (14). Rulings confirmed 2026-10-21 (§6). Needs Medicaid pregnancy test claims (in person and telehealth, one with only Z32.0x).

  • #27-#29 medicare_wellness_history (2026-10-20, EEWA-29) — not run live yet. Built from Dom.txt's Patient Inquiry Detail capture; the side-panel launcher is a[title="Account Inquiry"][ng-click^= "vm.loadSmartPanel("], the visible copy clicked (from the claim form DOM the operator pasted; 2026-10-21, was a text match before). Flow confirmed by the operator 2026-10-21: G0402 / G0438 / G0439 on the claim → Account Inquiry → Show CPT Payments → an earlier one inside lifetime / 3 years / 1 year → comment only, nothing changed. Account Inquiry lists CPT lines only under a posted payment / adjustment: a line billed but not yet adjudicated is not seen. Needs a live VERIFY run on Medicare claims carrying G0402 / G0438 / G0439, one with an earlier repeat and one without; watch that the modal closes and the next claim is not covered by it.

  • Live VERIFY run 2026-10-21 (Input\Medcor Ready Claim Sheet.xlsx, 50 claims, code at c8958cc): 50 found, 50 forms read, 39 Pass / 11 Fail / 0 Review, no error, every form closed with Cancel; Charge Summary TEST sheet + dbo.CV_ChargeSummary written. Conditions that fired: depression_dx_pointer, telehealth, vaccine_admin_codes. #15 new rule seen live: the HIGH RISK Dental claim counts 173 characters from "HIGH RISK" (307 for the whole notes before) → YES; the other Dental claims NO. #13 / #23 / #27-#29 did not fire — the sheet has no such claim; they still need claims of their own (D code on another payer, Medicaid pregnancy, Medicare G0402 / G0438 / G0439).

    • Login took 735 s: attempt 1's 2FA mail never came (9 min timeout), attempt 2's came in 42 s. Two earlier tries that day died with "Target page, context or browser has been closed" (after the 2FA confirm, and before the password box) while C: had 0-14 MB free; with ~2 GB freed the run went through. Keep C: with free space (the runs set TEMP / TMP to D:\claude_tmp; Chromium still writes some files under the user profile on C:).
  • Client test claims run 2026-10-20 (14 claims, VERIFY, one box per claim; sheet Input\Condition Test Claims.xlsx, run with TEST_CLAIM_IDS_FILE set for that run only — .env unchanged): 12 Pass, 1 Fail, 1 Review. Every claim was handled correctly for the rules as they stand, but almost none exercises a fix — the examples are already corrected:

    • #12: BH payer, H0002 with no modifier → nothing to do.
    • #18 (4): every vaccine already has its admin code billed (one claim's vaccines 90677 / 90715 are not on the 19-code list anyway).
    • #20 (3): one has 96372 billed; one is UHC Community = Medicare (ruled 2026-10-21: correct, #20 does not apply) — #14 unchecked G8510 instead (Fail); one was not read (below).
    • #43/#44 (3): 15851 already unchecked; two carry only 76801 / 76805 (ruling 0b).
    • #48 (3): DFIMP (now handled) and DCONS, all already unchecked at 0.00. Still to ask the client for: a BH-payer H code WITH a modifier and an H code on a non-BH payer (#12); a listed vaccine with NO admin code, one patient ≤18 and one >18 (#18); a listed injection with no 96372 on a non-Medicare payer (#20); a Medicaid claim with 15851/15852/73521 still checked (#43/#44); DPIMP/DFIMP still checked with a fee (#48).
  • The claim after a held-open review box was not read: a claim modal still covered the search page ("Claim No is covered by div#claimModal…"), 20 s wait, Review. The next claim started clean. Watch for it; re-run that claim alone.

  • eCW password expiry box (history). On 2026-10-05 eCW started showing "This password expires in 14 days. Do you want to change the password now?" after the 2FA approval. Both runs that day failed with "Login not confirmed": the box hid the home screen, and the bot did not know its Cancel button. Fixed in b787e2d: the box is found by its text and only its own Cancel is clicked (never OK). An already-expired password stops the run with that reason. The fix has not run live yet, because the real box's DOM was never captured. A person must change the password in eCW before it expires and update ECW_PASSWORD in .env. The bot never sets a password.

  • Two payers seen in downloaded reports are not on the dashboard yet: Banner Aetna Insurance, HealthSpring Claims Dept (blank Group Name).

  • Team's Comments dropdown list (the format's dropdown points at a broken network file).

  • "Click reported failure, but the claim form is already open": now ~5–10 s per hit (was ~30 s); the log line records the error and elapsed time, so the root cause can be studied.

  • Vaccine add-admin-code step: not yet tested live.

  • Sliding fee: no live EXPIRED or NONE case seen yet (all verified claims were ACTIVE).

  • None of the 2026-10-05 fixes has run against live eCW yet. Do a short live test run (SAVE off) before the demo.

  • Live checks waiting for the operator's OK: P16 (add a row / tick a box on a test claim, Cancel, reopen: did anything persist?) and L3 (UB checkbox selector in DevTools).

  • A form that stays stuck on screen can cost up to ~2 min per claim while the bot tries to cancel it (that claim then goes to Review).

11. Demo / production review (2026-10-01, 56 verified findings → 38 items)

Full report: docs/2026-10-01-demo-production-review.md (file:line references are as of 2026-10-01).

Status (2026-10-06): recommended steps 1–6 are done in commit f5bf3f9: P12, P1, D1, D10, D9, D3, D4, D5, D6, D7, D8, P8, D11, D2, with St1 and P2 in a lighter form (the outcome report plus "tainted claims are never offered Save"). Each step was implemented, reviewed and fixed, then a final review of the whole change ran with 5 lenses (19 of its 21 surviving findings fixed). Still open from steps 1–6: P16 and L3 (live checks) and the demo .env choice (§6). Steps 7–10 below are not started.

Top items as found (the ones marked ✅ are done):

Before the demo

  • ✅ D1 Test mode on → summary mostly blank; name test summaries _TEST.
  • ✅ D2 Summary says Clean/Pass even when a check crashed, declined, or the note was not read → add a "Review" category.
  • ✅ D3 Telehealth reports POS 2→2 on already-correct claims → correct claims marked Fail.
  • ✅ D4 Audio-only detection matches "audio and video", "Audiology" → 93 instead of 95.
  • ✅ D5 Telehealth writes 93 for unmapped/blank payers.
  • ✅ D6 Failed G2025 add still reported as success.
  • ✅ D7 HCFA/Dental read before Angular fills the form (0 CPT → Pass).
  • ✅ D8 Progress note fixed 3 s sleep misses slow notes.
  • ✅ D9 Dental per-claim box shows "?" fields and Tamil words.
  • ✅ D10 Same-day summary lost if the earlier file is open in Excel.
  • ✅ D11 30 s click timeout (known).

Before live production

  • ✅ P1 Save/Apply default True; .env inline comments parse wrong; no mode banner; Enter = Save.
  • ◐ P2 Partial apply() failure leaves edits that a later Save writes.
  • P3/P4 No recovery after a stuck form/expired session; summary lost on crash; no resume.
  • P5 Save/close success checked by a UB-only element (wrong on HCFA/Dental).
  • P7 Cancel / Don't Save clicked by numbered ids.
  • ✅ P12 PHI files in repo root not gitignored.
  • P16 Prove SAVE_CORRECTIONS=0 is truly read-only (does adding a row/checkbox save server-side?).
  • Also: TLS checks off, PHI retention, auto-accepted confirm(), stale README.

Logic: L1 depression can strip a line's only pointer; L2 telehealth first row may be an excluded code; L3 UB checkbox selector unconfirmed; L4 slow sliding modal reads as NONE; L5 high-risk negations.

Structure: ◐ one outcome model per claim (changed/declined/failed — light version done); run journal + resume; RUN_MODE attended/unattended; production approval model (with client); split claim_form.py; tests/run_all.py.

Performance: ~43–45 s/claim before the fixes (click fallback) → ~13–15 s expected after them (click timeout fix done; not yet measured live); ~540-claim run 4.5–14 h today → 2.3–4.8 h. No run beyond 17 claims yet (soak test needed).

Recommended order: PHI gitignore + save-off default + Cancel-discards check → demo setup → on-screen polish → telehealth correctness → read completeness → truthful summary → production write safety → run resilience → operating model → hardening/docs.