Initial import: eCW Charge Validation bot and results Portal

Snapshot of the working project as of 2026-10-23 (local 2d4d4fc), one
commit with no earlier history: the tracked files were de-identified
first (synthetic claim ids and patient data only; see Context.md
section 7). Credentials, run logs, screenshots, claim reports and client
workbooks are gitignored and not included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-23 18:38:17 +05:30
commit ba15a574ab
205 files changed
+56772

No files matched your search

+121
View File
@@ -0,0 +1,121 @@
"""
Read-only access to the results table (`dbo.CV_ChargeSummary`, written by
src/ecw_bot/core/results_db.py). SELECT statements only, always
parameterized; one short connection per call.
"""
from datetime import date, datetime, timedelta
from ecw_bot.core import results_db
# started_by since 2026-10-22 (EEWA-33): a table without it is reported as
# "needs tools/sql/create_tables.sql" (web/app.py, SQLSTATE 42S22).
_SELECT_COLUMNS = ", ".join(["id", "run_at", "run_mode", "started_by"]
+ [c for _, c in results_db.COLUMNS]
+ ["details_json"])
_RUN_COUNTS = (
"run_at, MAX(run_mode) AS run_mode, COUNT(*) AS claims, "
"SUM(CASE WHEN category = 'Pass' THEN 1 ELSE 0 END) AS pass_count, "
"SUM(CASE WHEN category = 'Fail' THEN 1 ELSE 0 END) AS fail_count, "
"SUM(CASE WHEN category = 'Review' THEN 1 ELSE 0 END) AS review_count")
# The same counts per day a run STARTED: every row of a run shares its
# run_at, so a run never splits over two days (portal, 2026-10-19).
_DAY = "CAST(run_at AS DATE)"
_DAY_COUNTS = (
f"{_DAY} AS day, COUNT(*) AS claims, COUNT(DISTINCT run_at) AS runs, "
"SUM(CASE WHEN category = 'Pass' THEN 1 ELSE 0 END) AS pass_count, "
"SUM(CASE WHEN category = 'Fail' THEN 1 ELSE 0 END) AS fail_count, "
"SUM(CASE WHEN category = 'Review' THEN 1 ELSE 0 END) AS review_count")
def _coerce(row: dict) -> dict:
"""
run_at as a datetime whatever the driver: ODBC Driver 17/18 return
DATETIME2 as datetime, the legacy {SQL Server} driver as text
("2026-10-08 15:10:00.0000000"), which the page cannot compare or
format (review, 2026-10-12).
"""
value = row.get("run_at")
if isinstance(value, str):
try:
row["run_at"] = datetime.fromisoformat(value.strip()[:26])
except ValueError:
pass
# The same for a day (DATE), which that driver returns as "2026-10-16".
value = row.get("day")
if isinstance(value, str):
try:
row["day"] = date.fromisoformat(value.strip()[:10])
except ValueError:
pass
elif isinstance(value, datetime):
row["day"] = value.date()
return row
class SqlResultsRepo:
def __init__(self, conn_str: str, connect=None):
self.conn_str = conn_str
self._connect = connect
def _query(self, sql: str, params=()) -> list:
connect = self._connect
if connect is None:
import pyodbc
connect = pyodbc.connect
conn = connect(self.conn_str, timeout=10)
try:
cur = conn.cursor().execute(sql, list(params))
cols = [d[0] for d in cur.description]
return [_coerce(dict(zip(cols, row))) for row in cur.fetchall()]
finally:
conn.close()
def runs(self) -> list:
"""Every run, newest first, with its claim count per Category."""
return self._query(
f"SELECT TOP 200 {_RUN_COUNTS} FROM {results_db.TABLE} "
f"GROUP BY run_at ORDER BY run_at DESC")
def results(self, run_at, category=None, claim_no=None) -> list:
"""One run's rows, in S.No order, optionally filtered."""
sql = (f"SELECT {_SELECT_COLUMNS} FROM {results_db.TABLE} "
f"WHERE run_at = ?")
params = [run_at]
if category:
sql += " AND category = ?"
params.append(category)
if claim_no:
sql += " AND claim_no LIKE ?"
params.append(f"%{claim_no}%")
sql += " ORDER BY s_no, id"
return self._query(sql, params)
def days(self, limit: int = 90) -> list:
"""The last `limit` days with a run, newest first: claims checked
(every row — a claim checked twice counts twice), runs, and the
count per Category."""
return self._query(
f"SELECT TOP {int(limit)} {_DAY_COUNTS} FROM {results_db.TABLE} "
f"GROUP BY {_DAY} ORDER BY {_DAY} DESC")
def day_results(self, day) -> list:
"""Every row of every run that started on `day`, in run order then
S.No. The page counts and filters them itself: its tiles need the
whole day whatever filter is on."""
start = datetime(day.year, day.month, day.day)
return self._query(
f"SELECT {_SELECT_COLUMNS} FROM {results_db.TABLE} "
f"WHERE run_at >= ? AND run_at < ? ORDER BY run_at, s_no, id",
[start, start + timedelta(days=1)])
def claim_history(self, claim_no) -> list:
"""Every saved check of the claims matching `claim_no`, newest
first (at most 200)."""
return self._query(
f"SELECT TOP 200 {_SELECT_COLUMNS} FROM {results_db.TABLE} "
f"WHERE claim_no LIKE ? ORDER BY run_at DESC, s_no, id",
[f"%{claim_no}%"])