Files
ecw-charge-validation/Context.md
T
2026-10-30 10:58:35 +05:30

1382 lines
109 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
python run.py # the bot (logs into LIVE eCW)
python agent.py # the agent: the whole day's queue, self-healing (below)
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)
```
**The agent (2026-10-30, operator: "we are an agent, not a bot — observe,
fix, continue, and mail me what happened").** `python agent.py`
(`flows/supervisor.py`) runs the bot as a child process, pass after pass,
until every claim in the day's Medcor Ready queue has a result. Pass 1
downloads the live queue; a later pass takes only the claims still
pending — the queue file minus every `CV_ChargeSummary` row since the
agent started, a "Bot could not ..." Review not counting — through
`RESUME_CLAIM_IDS_FILE` (live mode, live Charge Summary, no TEST label,
no second export). After each pass, by exit code (`next_action`):
login failure (1, 6) → retry after 10 then 20 min, the 3rd in a row stops
the agent (the client's account must not be restricted); restricted (4),
screen changed (7), config (2) → stop at once; stuck page (9), stall (8),
claims error (5) → fresh Chrome + login in 1 min; guard wait (3) → wait;
a clean pass leaving the same claims pending → stop ("left for a
person"); 15 passes a day at most. VERIFY only: refuses to start with
`SAVE_CORRECTIONS=1`; forces `QUIET_BOXES=1`, `SHOW_EACH_CLAIM=0`,
`CLAIM_PROCESS_LIMIT=0`. Every failure and the end are mailed
(`core/notify.py`, from the 2FA mailbox's account over SMTP — `SMTP_*`
default to `IMAP_*`, port 465; **`NOTIFY_EMAIL_TO` blank = no mail, and it
is blank on this PC until the operator gives the address**). A mail
carries no claim number (6+ digit runs masked) and only login / error
page PNGs (`notify._EMAILABLE`) — never a claim screen or page HTML.
Inside a pass, `flows/recovery.py`: a claim the bot could not search or
read → login page on screen = session over (exit 9); else Escape, close
Patient Information, clear the screen, reopen the claim search if still
blocked, and try the claim once more; 2 claims in a row still failing
after recovery → exit 9 (live 2026-10-29: a Dental claim's sliding fee
check left Patient Information open and 18 claims went "Bot could not
search" one after another). Login: the security image page's Continue is
clicked by the bot (`login.continue_security_image`, a person only when no
Continue/OK is found; the page is saved as `security_image_page` the first
time); a 2FA link approved but not picked up in `OTP_PICKUP_SECONDS` (60)
→ Resend OTP + approve the new mail, `OTP_PICKUP_RESENDS` (2) times
(live 2026-10-29 night: three logins lost to mail 6 to 8.5 min late with
Resend still on screen). The "Chrome open" and security image boxes are
quiet with `QUIET_BOXES=1` (a `[BOX]` log line); CAPTCHA and manual 2FA
boxes still show — they need a person. **None of this has run live yet.**
**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`; fixed in `a89f689`,
§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.
**Client call (2026-10-27, MoM mailed to the team):** open conditions
discussed with Madan for clarification; the 50-claim results file is in
the shared production folder for Madan to review and give feedback; Madan
has a Portal login (daily runs, Pass / Fail claims, comments; changes
nothing). eCW data is the client's: we never update anything in eCW, not
even to undo the bot's own earlier box changes (operator, 2026-10-28).
**2026-10-28 runs:** 17:59 login failed (a late security image page, the
password box stayed disabled; fixed `d1aa6da`); 18:00 / 18:03 / 18:04 runs
started from VS Code's debugger (F5) — a Stop / restart there kills the run
with no log line (18:04 died at claim 29 of 50, 28 saved). **19:28 — first
run on the LIVE Medcor Ready queue** (operator: "move the download part
live"; `.env` `TEST_CLAIM_IDS_FILE` blanked, the old line kept as a
comment; `SHOW_EACH_CLAIM=0`, `QUIET_BOXES=1`, `MSGBOX_TIMEOUT_SECONDS=10`;
backup of the earlier `.env` in the session's temp folder): report
downloaded, 216 claims in the queue, the first 50 (`CLAIM_PROCESS_LIMIT`)
read: **24 Pass / 25 Fail / 1 Review**, exit 0, ~32 min; login 753 s (the
security image page waited for a person; 2FA mail 48 s). lab_codes,
vaccine_z23_pointer and t1017_non_billable fired on live claims. Still
VERIFY: nothing saved. Unattended runs still need the security image
Continue clicked by a person (offered: the bot clicks it — not decided).
**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).
**Delete run (`4cc27a5`, 2026-10-28, Super Admin only):** a day's page lists that
day's runs (time, claims, started by) with a Delete run button, shown to the
Super Admin only; `POST /admin/runs/delete` checks the role and the page
token (Admin / Viewer = 403). A confirm box first; it deletes that run's
`CV_ChargeSummary` rows (`WHERE run_at = ?`), never eCW, never the `Logs\`
files, and records "run <date time> · run deleted · by" in the Sign-in
history. A SQL login without DELETE gets "No permission to delete runs"
(SQLSTATE 42000) — the Dokploy Portal's `cv_portal` needs
`GRANT DELETE ON dbo.CV_ChargeSummary TO cv_portal` (§10).
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:
```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:
```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-28: 75 test
files, **1286 tests** (full run 2026-10-28 with Z23 and T1017: 1286/1286) (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 |
| `flows/supervisor.py` + root `agent.py` | The agent (2026-10-30, §2): passes of the bot as a child process until nothing is pending; `next_action(code, pending, state)` is the whole retry policy, `pending(queue, rows)` the resume list |
| `flows/recovery.py` | A stuck screen inside a pass (§2): `recover()` (logout check, Escape + Patient Information close + clear, reopen search) and `run_claim()` (one retry per claim, 2 in a row → `PageStuck`, exit 9) |
| `core/notify.py` | The agent's mail (§2): `incident()` / `summary()`; claim numbers masked, login / error PNGs only; never fatal |
| `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 | `lab_codes` | #3, #4 | **Lab code** = 5 digits starting with 8 (36416 is not one, never touched). By the **primary payer** (EEWA-38, operator 2026-10-28): **Medicare payers** (#25's exact eCW names, `medicare_pps_code.is_pps_payer`) → every lab line unchecked + fee 0; **Cigna Salud Employee** → any lab code; **every other payer**, blank included → only the 13 in-house codes (82948, 81025, 85018, 82270, 82274, 87806, 85610, 87880, 80305, 81001, 81003, 87210, 87636), any other lab line unchecked + fee 0. Code only, not the fee. Not on Dental claims. Comment: "Lab code not billed to Medicare (…)" / "Lab code not in the in-house list (…)" |
| 11 | `t1017_non_billable` | #46 | **T1017** by the primary payer (EEWA-40, operator 2026-10-28): a **Medicare payer** (#25's exact eCW names) or **Medicaid** (dashboard "Medicaid" group only, as #43/#44 — not RAE, not CHP+) → unchecked + fee 0, comment "T1017 not billable (Medicaid) - unchecked, fee 0"; **any other payer**, blank or unmapped included → nothing changed, **Review** "T1017 on <payer> - check by hand". Not on Dental claims |
| 12 | `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) |
| 13 | `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) |
| 14 | `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 |
| 15 | `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) |
| 16 | `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 |
| 17 | `vaccine_z23_pointer` | #45 | A line with one of the client's 19 vaccine codes (#18's list, not the admin codes) must carry **Z23**'s ICD pointer (its number in the ICD list) in ICD1–4 (EEWA-39, operator 2026-10-28). Missing → the first empty slot, the line's other pointers kept; already there → nothing; no other line touched. No Z23 in the ICD list → Review "Z23 not on the claim — add it by hand"; four slots full → Review "no free pointer slot". All payers, Medicare too; not Dental. Comment "Z23 pointer added to the vaccine line (…)" |
| 18 | `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") |
| 19 | `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) |
| 20 | `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` |
| 21 | `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: <Category> — <Current Status>".
- 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.
- **Payer: sheet vs eCW (`97d4d74`, 2026-10-28, operator):** every condition goes by
eCW's payer (the claim form's first insurance row), never the report's
PAYER column. When the two differ (case and spacing aside, neither blank)
the Comments end with "Payer: sheet says X, eCW says Y - eCW used" — a
note, the Category is not changed. The PAYER / Group Name columns stay
the report's.
- 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`.
- **Sage redesign (2026-10-30, the operator-approved mockup, live on
Dokploy that day from `portal-20261030-ui.zip`).** `base.html`: the navy
header became a sage side menu (`<aside class="side">`: brand, mode pill,
links with CSS-mask icons, the user's name over their role, Sign out);
breadcrumb and footer in the page column; Outfit + Inter from Google
Fonts; the old CSS variable names kept with sage / lime / Pass green /
Fail orange / Review blue values, so every page follows. Dashboard:
"Good morning|afternoon|evening, <first name>" + claim search, 4 tiles
with %, the lime **Agent run** card (the old Bot run card, same ids and
script) beside **Results by day**, **Top reasons today** beside **Runs
today** (`app._runs_today`: today's rows grouped by `run_at`, newest
first — no extra query). Sign in: sage panel + "Welcome back" card.
Not built: Queue / Pending tiles (the Portal cannot see the bot PC's
queue file — the agent would have to write the queue size to the DB).
The mockup canvas: claude.ai artifact "Charge Validation Portal Mockups"
(private to the operator).
---
## 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.
- 2026-10-30 The bot becomes an agent: technical failures (login, session, stuck screen, browser) are the agent's to recover from without a person; condition or process mistakes stay the operator's to point out. Mail the operator what happened, what the agent did and whether it worked.
- 2026-10-30 Agent mail: from the bot mailbox over SMTP; screenshots of login / error pages only, never a claim screen; no claim numbers.
- 2026-10-30 The security image page's Continue is clicked by the bot.
- 2026-10-30 Login failures: at most 3 in a row (10 / 20 min apart), then stop and mail — protect the client's eCW account.
- 2026-10-30 Bulk runs show no message boxes; the full day's queue (no `CLAIM_PROCESS_LIMIT`) must finish the same day.
- 2026-10-30 Portal look: the approved mockup — sage side menu, lime highlight, Outfit + Inter, Dashboard greeting / tiles / Agent run / Results by day / Top reasons / Runs today, two-panel Sign in (no Ecare/Portal toggle: the back end has none).
### 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: `78409f0` (2026-10-30, Portal sage redesign — §5;
deployed to Dokploy from the working tree before this commit, same
files), `24c9ce4` (2026-10-30, the self-healing agent: supervisor,
recovery, security image Continue, Resend pickup, operator mail — §2,
§10), `0c2fca8` (2026-10-28, vaccine_z23_pointer #45 /
t1017_non_billable #46 — §4), `d1aa6da` (2026-10-28, login waits for a late
security image page — §10), `97d4d74` (2026-10-28, Charge Summary notes a payer the
sheet and eCW disagree on — §5), `aaab1ff` (2026-10-28, lab_codes #3/#4,
EEWA-38 — §4), `4cc27a5` (2026-10-28, Portal Delete run, Super Admin only
— §2), `a89f689` (2026-10-27, login retry after a 2FA timeout
waits for the closed Chrome and opens a new one — §10), `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) | Done (closed 2026-10-28 by the operator) |
| 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 | Done (closed 2026-10-28 by the operator; 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 | Done (closed 2026-10-28 by the operator; 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-38 | lab_codes (#3, #4: Medicare payers no lab code, Cigna Salud any, others the 13 in-house) | In Progress (built 2026-10-28, `aaab1ff`; fired on live claims 2026-10-28 19:28) |
| EEWA-39 | vaccine_z23_pointer (#45: Z23 pointer on every vaccine line) | In Progress (built 2026-10-28, `0c2fca8`; fired on live claims 2026-10-28 19:28) |
| EEWA-40 | t1017_non_billable (#46: T1017 non-billable on Medicare / Medicaid, Review elsewhere) | In Progress (built 2026-10-28, `0c2fca8`; fired on a live claim 2026-10-28 19:28) |
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
- **Agent (2026-10-30), not yet run live.** To do: `NOTIFY_EMAIL_TO` in
`.env` (the operator's address) and one test mail; a first `python
agent.py` to see the security image Continue click on the real page
(its HTML is saved as `security_image_page` — tighten the selector from
it) and a late-mail Resend pickup. Not built: the agent's status in the
Portal (it runs on the bot PC; the Dokploy Portal sees only the
database — a status / queue-size row would carry it), Queue / Pending
tiles.
- **29/10 live queue (690 claims).** 208 got a result in two runs (17:18
126, 19:29 82); the rest were not reached: Ctrl-C would not stop a
run blocked in a Playwright call (the tab was closed instead), a stuck
Patient Information dialog cost 18 claims (now `recovery.py`), and three
logins that night failed on 2FA mail 6 to 8.5 min late (now the Resend
pickup). 482 claims of that day's queue were never checked.
- **Tests:** `test_cdp_browser.py` 2 of 11 fail on this PC since
2026-10-29 (the Chrome it opens has a command line PowerShell cannot
read — the same `[BROWSER] Could not read the command line` line live
runs print); environment, not code — look at it before trusting the
proxy check. `test_msgbox.py` now pins `QUIET_BOXES` off (this PC's
`.env` has it on).
- **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, fixed in `a89f689`, not yet
seen live).** After a 2FA timeout the run closed the Chrome it opened and
attempt 2 attached to 9222 at once, while that Chrome was still closing →
`connect_over_cdp: Target page, context or browser has been closed`, run
over. Two causes: `Browser.close` only starts Chrome's shutdown, and the
"no Chrome → open it" step ran once, before the login loop. Now the
attached browser's `close()` waits (up to 30 s) until the port stops
answering, and `launch()` opens a new Chrome when this run opened one and
closed it (same extra args; the eCW proxy comes from `chrome_command`).
A Chrome the run did not open is still never opened or closed. The
"Chrome open" box is not shown again on the retry. Test:
`test_cdp_browser.py` (close → port quiet → launch again → close).
- **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. **Domain
(2026-10-29): `https://ecw.app.ecareindia.com`** (DNS → 10.200.0.15, so
Ecare network / VPN only; Let's Encrypt cert to 2027-01-27). Its 502
was the domain's Container Port 3000 (must be **8000**); then 400
"Invalid host header" until `PORTAL_URLS=http://10.200.0.15:8085,
https://ecw.app.ecareindia.com` was saved AND the app reloaded (the
operator's Reload had not run — the container still said "Up 44
hours"; check that on the Logs tab). 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`; Delete run also needs `GRANT DELETE ON
dbo.CV_ChargeSummary TO cv_portal` — not run yet, and no zip with
Delete run deployed yet); 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). Putting
them back is the client's call, not ours (operator, 2026-10-28): we do
not update anything in eCW, not even to undo the bot's own change.
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.