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

+153
View File
@@ -0,0 +1,153 @@
"""
Network preflight — proves the two halves of the login can run at the same time.
Manually, this account is a deadlock. The eCW portal only answers through the
corporate proxy, and the proxy refuses the webmail host outright, so a human
has to flip the system proxy on to log in, off to read the approval mail, and
on again to confirm it.
The bot sidesteps that by never touching the webmail *website*:
eCW portal + approval link → Playwright, launched with an explicit proxy
the approval mail → imaplib, a raw TCP socket to port 993
Two different network paths, so both work concurrently and nothing gets
toggled. The one rule that keeps it true: the SYSTEM proxy must stay OFF.
Playwright is handed the proxy per-browser; imaplib ignores WinINET and the
HTTP(S)_PROXY environment variables because it never speaks HTTP.
Run:
python "D:\\Ecw Process\\src\\check_net.py"
"""
# Run standalone from the project root: python tools\check_net.py
# The package lives under src\ , which is not on the path for a loose script.
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 box
# characters and arrows below — check_imap.py died on its own output with
# UnicodeEncodeError. See runlog._force_utf8_console.
for _s in (_sys.stdout, _sys.stderr):
try:
_s.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
import re
import ssl
import sys
import time
import socket
import urllib.request as urlreq
from urllib.parse import urlsplit
from ecw_bot import settings as cfg
TIMEOUT = 25
def _host_of(url: str) -> str:
return urlsplit(url).hostname or url
def _try_http(url: str, proxy: str) -> tuple:
"""(ok, detail). proxy="" forces a direct connection."""
handler = urlreq.ProxyHandler({"http": proxy, "https": proxy} if proxy else {})
opener = urlreq.build_opener(handler)
start = time.time()
try:
resp = opener.open(url, timeout=TIMEOUT)
return True, f"HTTP {resp.status} ({time.time() - start:.1f}s)"
except Exception as exc:
return False, f"{type(exc).__name__}: {str(exc)[:80]} ({time.time() - start:.1f}s)"
def _try_tcp(host: str, port: int) -> tuple:
start = time.time()
sock = socket.socket()
sock.settimeout(TIMEOUT)
try:
sock.connect((host, port))
return True, f"connected ({time.time() - start:.1f}s)"
except Exception as exc:
return False, f"{type(exc).__name__}: {exc}"
finally:
sock.close()
def _row(label: str, ok: bool, detail: str) -> None:
print(f" {label:<22} {'OK ' if ok else 'FAIL'} {detail}")
def _system_proxy_enabled():
"""True/False, or None when it cannot be read (non-Windows)."""
try:
import winreg
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
with key:
return bool(winreg.QueryValueEx(key, "ProxyEnable")[0])
except Exception:
return None
def main() -> int:
proxy = cfg.proxy_settings()
if not proxy:
print("No proxy configured in .env — set PROXY_IP / PROXY_PORT")
return 2
proxy_url = proxy["server"]
problems = []
print(f"proxy : {proxy_url}")
print(f"portal : {_host_of(cfg.ECW_URL)}")
print(f"mailbox : {cfg.IMAP_HOST}:{cfg.IMAP_PORT}\n")
# ── the proxy itself ──────────────────────────────────────────────────────
m = re.match(r"https?://([^:/]+):(\d+)", proxy_url)
print("proxy reachability")
if m:
ok, detail = _try_tcp(m.group(1), int(m.group(2)))
_row("TCP to proxy", ok, detail)
if not ok:
problems.append("the proxy itself is unreachable — check VPN / network")
print()
# ── eCW: must need the proxy ──────────────────────────────────────────────
print("eCW portal (browser goes through the proxy)")
direct_ok, direct_detail = _try_http(cfg.ECW_URL, "")
proxy_ok, proxy_detail = _try_http(cfg.ECW_URL, proxy_url)
_row("without proxy", direct_ok, direct_detail)
_row("through proxy", proxy_ok, proxy_detail)
if not proxy_ok:
problems.append("eCW is unreachable through the proxy — the bot cannot log in")
print()
# ── mailbox: must NOT need the proxy ──────────────────────────────────────
print("mailbox (imaplib — raw socket, never proxied)")
imap_ok, imap_detail = _try_tcp(cfg.IMAP_HOST, cfg.IMAP_PORT)
_row(f"TCP :{cfg.IMAP_PORT}", imap_ok, imap_detail)
if imap_ok:
start = time.time()
try:
conn = (imap_ssl(cfg) if cfg.IMAP_SSL else None)
conn.login(cfg.IMAP_USERNAME, cfg.IMAP_PASSWORD)
conn.logout()
_row("IMAP login", True, f"{cfg.IMAP_USERNAME} ({time.time() - start:.1f}s)")
except Exception as exc:
_row("IMAP login", False, f"{type(exc).__name__}: {str(exc)[:80]}")
problems.append("IMAP login failed — run check_imap.py")
else:
problems.append("port 993 is blocked — the bot cannot read the approval mail")
print()
# ── the rule that makes it work ───────────────────────────────────────────
print("system proxy (must stay OFF)")
sysproxy = _system_proxy_enabled()
if sysproxy is None:
_row("ProxyEnable", True, "not readable (non-Windows?) — skipped")
elif sysproxy:
_row("ProxyEnable", False, "1 — system proxy is ON")
problems.append(
"the system proxy is ON. Turn it OFF: the bot proxies only its own "
"browser, and a system-wide proxy is what blocks the webmail host")
else:
_row("ProxyEnable", True, "0 — off, correct")
print()
if problems:
print("PROBLEMS")
for p in problems:
print(f" - {p}")
return 1
print("All good — the browser can reach eCW through the proxy while imaplib")
print("reads the mailbox directly. No proxy toggling needed.")
return 0
def imap_ssl(c):
import imaplib
return imaplib.IMAP4_SSL(c.IMAP_HOST, c.IMAP_PORT,
ssl_context=ssl.create_default_context())
if __name__ == "__main__":
sys.exit(main())