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:
commit
ba15a574ab
205 files changed
+56772
No files matched your search
+121
@@ -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}%"])
|
||||
Reference in new issue
Block a user