Self-healing agent (supervisor, recovery, login fixes, operator mail) and Portal sage redesign

Snapshot of D:\Ecw Process a11c9ff (2026-10-30).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-30 10:58:35 +05:30
1 parent fafa5285eb
commit d500f750da
26 files changed
+1704 -357

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
r"""
flows/supervisor.py — the agent loop around the bot (operator, 2026-10-30):
run the day's queue to the end without a person, restarting on technical
failures and mailing what happened.
Rules (operator's choices that day):
* login failures (exit 1, 6): retry after 10, 20, then 30 minutes; the
3rd failure in a row stops the agent and mails it — the client's eCW
account must not be restricted;
* restricted (4), eCW screen changed (7), config (2): stop at once — no
retry can fix them;
* a stuck page (9), a stall (8), a claims error (5): restart with a fresh
Chrome and login for the claims still pending;
* the attempt guard said wait (3): wait, it is not a failure;
* nothing pending: done. A clean run that left the same claims pending
as before: stop, those are for a person.
python tests\test_supervisor.py
No bot, no browser, no mail, no database: all stand-ins.
"""
import sys
import pathlib
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "src"))
from ecw_bot.flows import supervisor as sv # noqa: E402
CASES = []
def case(fn):
CASES.append(fn)
return fn
def _decide(code, pending, state=None):
state = state if state is not None else sv.State()
return sv.next_action(code, pending, state), state
@case
def nothing_pending_is_done():
(action, _, _), _ = _decide(0, [])
assert action == "done"
@case
def login_failures_back_off_10_20_then_stop_on_the_third():
st = sv.State()
waits = []
for _ in range(2):
(action, wait, _), st = _decide(1, ["a"], st)
assert action == "retry"
waits.append(wait)
assert waits == [600, 1200], waits
(action, _, why), st = _decide(6, ["a"], st)
assert action == "stop" and "3 login" in why, why
@case
def a_good_login_resets_the_login_failures():
st = sv.State()
_decide(1, ["a"], st)
_decide(9, ["a"], st)
assert st.login_fails == 0
@case
def restricted_screen_changed_and_config_stop_at_once():
for code in (2, 4, 7):
(action, _, _), _ = _decide(code, ["a"])
assert action == "stop", code
@case
def a_stuck_page_restarts_soon():
(action, wait, _), _ = _decide(9, ["a", "b"])
assert action == "retry" and wait <= 120
@case
def the_guard_saying_wait_is_not_a_failure():
(action, wait, _), st = _decide(3, ["a"])
assert action == "retry" and wait >= 300 and st.login_fails == 0
@case
def a_clean_run_that_left_the_same_claims_stops():
st = sv.State()
(action, _, _), st = _decide(0, ["a", "b"], st)
assert action == "retry"
(action, _, why), st = _decide(0, ["a", "b"], st)
assert action == "stop" and "person" in why, why
@case
def pending_is_the_queue_minus_claims_done_since_the_agent_started():
queue = ["1", "2", "3", "4"]
rows = [("1", "Clean claims"), ("2", "Bot could not search the claim"),
("3", "Telehealth (POS 02 on T1017)")]
assert sv.pending(queue, rows) == ["2", "4"]
@case
def the_loop_restarts_mails_and_ends_when_nothing_is_left():
codes = [6, 9, 0]
left = [["1", "2"], ["2"], []]
runs, mails, slept = [], [], []
st = sv.run_loop(
run_bot=lambda resume: runs.append(resume) or codes.pop(0),
pending_now=lambda: left.pop(0),
mail=lambda kind, *a: mails.append(kind),
sleep=slept.append)
assert runs == [None, ["1", "2"], ["2"]], runs
assert slept == [600, 60], slept
assert mails == ["incident", "incident", "summary"], mails
assert st.outcome == "done"
def main() -> int:
failures = 0
for fn in CASES:
name = fn.__name__.replace("_", " ")
try:
fn()
print(f" ok {name}")
except AssertionError as exc:
print(f" FAIL {name}: {exc}")
failures += 1
except Exception as exc:
print(f" ERROR {name}: {type(exc).__name__}: {exc}")
failures += 1
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())