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,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())
|
||||
Reference in new issue
Block a user