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
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
Is the bot allowed to run right now, and if not, when?
|
||||
|
||||
python tools\\check_guard.py
|
||||
|
||||
Exists because "SystemExit: 3" kept coming up and the only way to find out why
|
||||
was to start a whole run — which then refused, told you nothing about the clock,
|
||||
and left you guessing whether to try again in one minute or twenty. This reads
|
||||
`Logs\\attempts.json` and prints the answer. It starts no browser, touches eCW
|
||||
not at all, and **records no attempt**, so running it is free.
|
||||
|
||||
Exit code mirrors the bot's: 0 = a run would proceed, 3 = it would be refused.
|
||||
"""
|
||||
|
||||
import sys as _sys
|
||||
from pathlib import Path as _Path
|
||||
_sys.path.insert(0, str(_Path(__file__).resolve().parent.parent / "src"))
|
||||
|
||||
# Windows hands these scripts a cp1252 console, which cannot encode the arrows
|
||||
# below. See runlog._force_utf8_console.
|
||||
for _s in (_sys.stdout, _sys.stderr):
|
||||
try:
|
||||
_s.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
from ecw_bot import settings as cfg
|
||||
from ecw_bot.core import guard
|
||||
|
||||
|
||||
def _local(dt):
|
||||
return dt.astimezone().strftime("%H:%M:%S") if dt else "-"
|
||||
|
||||
|
||||
def _ago(dt, now):
|
||||
if not dt:
|
||||
return ""
|
||||
secs = (now - dt).total_seconds()
|
||||
return f"{secs/60:.0f} min ago" if secs >= 90 else f"{secs:.0f}s ago"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
state = guard._read()
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
print(f"Now : {_local(now)}")
|
||||
print(f"State file : {guard.STATE_PATH}")
|
||||
print()
|
||||
|
||||
last = guard._parse(state.get("last_attempt"))
|
||||
success = guard._parse(state.get("last_success"))
|
||||
locked = guard._parse(state.get("locked_until"))
|
||||
window = now - timedelta(hours=1)
|
||||
recent = sorted(d for d in (guard._parse(x) for x in state.get("recent", []))
|
||||
if d and d > window)
|
||||
|
||||
print(f"Last attempt : {_local(last)} {_ago(last, now)}")
|
||||
print(f"Last success : {_local(success)} {_ago(success, now)}")
|
||||
print()
|
||||
|
||||
# Rule 1 — the 5 minute cooldown between attempts. Waited out, not refused.
|
||||
print(f"Cooldown ({cfg.LOGIN_COOLDOWN_SECONDS}s between attempts)")
|
||||
if last:
|
||||
left = cfg.LOGIN_COOLDOWN_SECONDS - (now - last).total_seconds()
|
||||
if left > 0:
|
||||
waits = left <= cfg.COOLDOWN_MAX_WAIT_SECONDS
|
||||
print(f" {left:.0f}s left"
|
||||
+ (" → the bot WAITS this out and then runs"
|
||||
if waits else " → too long to wait, it would exit 3"))
|
||||
else:
|
||||
print(" clear")
|
||||
else:
|
||||
print(" clear (no attempt recorded yet)")
|
||||
print()
|
||||
|
||||
# Rule 2 — the rolling hour. This one refuses; it is the real protection.
|
||||
print(f"Hourly limit ({len(recent)} of {cfg.LOGIN_MAX_PER_HOUR} in the "
|
||||
f"last hour)")
|
||||
for d in recent:
|
||||
print(f" {_local(d)} leaves the window at "
|
||||
f"{_local(d + timedelta(hours=1))}")
|
||||
if len(recent) >= cfg.LOGIN_MAX_PER_HOUR:
|
||||
clears = min(recent) + timedelta(hours=1)
|
||||
print(f" FULL. Clears at {_local(clears)} — "
|
||||
f"{(clears - now).total_seconds()/60:.0f} min from now.")
|
||||
else:
|
||||
print(f" room for {cfg.LOGIN_MAX_PER_HOUR - len(recent)} more")
|
||||
print()
|
||||
|
||||
# Rule 3 — eCW showed its restriction page.
|
||||
print("Restriction lockout")
|
||||
if locked and now < locked:
|
||||
print(f" eCW refused us at "
|
||||
f"{state.get('restricted_at', '?')[:19]} UTC. "
|
||||
f"Locked until {_local(locked)}.")
|
||||
print(" Usual cause is the browser losing the proxy, not the account "
|
||||
"— check tools\\check_net.py first.")
|
||||
else:
|
||||
print(" none")
|
||||
print()
|
||||
|
||||
refusal = guard.check()
|
||||
if refusal:
|
||||
print(f"VERDICT: a run would be REFUSED (exit 3)")
|
||||
print(f" {refusal}")
|
||||
return 3
|
||||
print("VERDICT: clear — a run would proceed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_sys.exit(main())
|
||||
Reference in new issue
Block a user