Poll for run completion every 5 minutes instead of checking once
All checks were successful
Deploy / deploy (push) Successful in 28s
All checks were successful
Deploy / deploy (push) Successful in 28s
The one-shot VERIFY_DELAY_MINUTES check was too rigid: MassPrice's processing time varies widely in practice (observed ~1 to over 20 minutes, and its queue can back up further under load — confirmed live, a run still sitting in "Queued" 15+ minutes in), so a single check window either misses slow-but-fine completions or has to be set needlessly long. Replaced with a poll loop: scheduler.py reschedules another one-off check every VERIFY_POLL_MINUTES (default 5) until automation's check_completion() reports "complete", up to VERIFY_MAX_ATTEMPTS (default 12, ~1 hour) before giving up and marking "not_confirmed" for good. verify_run() is split into a pure check_completion() (no storage side effects) so scheduler.py's loop owns the retry/give-up decision; automation.py just answers "is it done yet". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H6TgffxTvYhqxykAbVQg2v
This commit is contained in:
parent
8b58dadc5a
commit
213837ff97
6 changed files with 151 additions and 90 deletions
|
|
@ -189,7 +189,8 @@ def run_rule(rule_id, rule_name=None):
|
|||
rule id. Logs the outcome to run_history.json and returns a dict:
|
||||
{"status": "sent"|"failure", "message": ..., "entry_id": ...,
|
||||
"watermark_activity_id": ...}. On "sent", the caller (scheduler.py)
|
||||
schedules a delayed verify_run() call using entry_id/watermark_activity_id
|
||||
starts a poll loop (check_completion(), every VERIFY_POLL_MINUTES,
|
||||
up to VERIFY_MAX_ATTEMPTS times) using entry_id/watermark_activity_id
|
||||
to confirm the run actually completed on MassPrice's side."""
|
||||
status = "failure"
|
||||
message = ""
|
||||
|
|
@ -257,7 +258,7 @@ def run_rule(rule_id, rule_name=None):
|
|||
entry_id = _log_history(rule_id, rule_name, status, message)
|
||||
will_verify = status == "sent" and bool(rule_name)
|
||||
if will_verify:
|
||||
_set_verification_pending(entry_id)
|
||||
set_verification_pending(entry_id)
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
|
|
@ -268,12 +269,11 @@ def run_rule(rule_id, rule_name=None):
|
|||
}
|
||||
|
||||
|
||||
def verify_run(entry_id, rule_name, watermark_id):
|
||||
"""Delayed follow-up: check whether the run logged as entry_id
|
||||
actually completed on MassPrice's side by now. Updates that
|
||||
run_history.json entry's "verification" field in place."""
|
||||
verification = "not_confirmed"
|
||||
verify_message = ""
|
||||
def check_completion(rule_name, watermark_id):
|
||||
"""One-shot check: has a new "Complete" entry appeared for rule_name
|
||||
past watermark_id yet? Returns ("complete", "") or ("not_yet", msg).
|
||||
Pure check — no storage side effects; the caller (scheduler.py's
|
||||
poll loop) decides what that means for the stored history entry."""
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True, args=LAUNCH_ARGS)
|
||||
try:
|
||||
|
|
@ -284,21 +284,18 @@ def verify_run(entry_id, rule_name, watermark_id):
|
|||
|
||||
activity_id, status_text = _find_latest_activity(page, rule_name)
|
||||
if activity_id is None:
|
||||
verify_message = "No matching activity found on MassPrice"
|
||||
elif watermark_id is not None and activity_id <= watermark_id:
|
||||
verify_message = "No new completion appeared within the allotted time"
|
||||
elif status_text and "complete" in status_text.lower():
|
||||
verification = "complete"
|
||||
else:
|
||||
verify_message = f"Latest activity shows \"{status_text}\", not Complete"
|
||||
return "not_yet", "No matching activity found on MassPrice"
|
||||
if watermark_id is not None and activity_id <= watermark_id:
|
||||
return "not_yet", "No new completion has appeared yet"
|
||||
if status_text and "complete" in status_text.lower():
|
||||
return "complete", ""
|
||||
return "not_yet", f'Latest activity shows "{status_text}", not Complete yet'
|
||||
except Exception as exc:
|
||||
verify_message = str(exc)
|
||||
logger.exception("verify_run failed for entry %s", entry_id)
|
||||
logger.exception("check_completion failed for %s", rule_name)
|
||||
return "not_yet", str(exc)
|
||||
finally:
|
||||
browser.close()
|
||||
|
||||
_update_verification(entry_id, verification, verify_message)
|
||||
|
||||
|
||||
def _log_history(rule_id, rule_name, status, message):
|
||||
entry_id = uuid.uuid4().hex
|
||||
|
|
@ -312,11 +309,11 @@ def _log_history(rule_id, rule_name, status, message):
|
|||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"status": status,
|
||||
"message": message,
|
||||
# "pending" only when a verify_run() has actually been
|
||||
# scheduled for this entry (status == "sent" and rule_name
|
||||
# was known) — set explicitly by run_rule() below, not here,
|
||||
# since _log_history() itself doesn't know whether scheduling
|
||||
# succeeded.
|
||||
# "pending" only when a completion poll loop has actually
|
||||
# been started for this entry (status == "sent" and
|
||||
# rule_name was known) — set explicitly by run_rule() below,
|
||||
# not here, since _log_history() itself doesn't know whether
|
||||
# scheduling succeeded.
|
||||
"verification": None,
|
||||
"verify_message": None,
|
||||
},
|
||||
|
|
@ -325,29 +322,44 @@ def _log_history(rule_id, rule_name, status, message):
|
|||
return entry_id
|
||||
|
||||
|
||||
def _set_verification_pending(entry_id):
|
||||
history = storage.load("run_history.json", [])
|
||||
def _find_history_entry(history, entry_id):
|
||||
for entry in history:
|
||||
if entry.get("id") == entry_id:
|
||||
entry["verification"] = "pending"
|
||||
break
|
||||
else:
|
||||
return entry
|
||||
return None
|
||||
|
||||
|
||||
def set_verification_pending(entry_id):
|
||||
history = storage.load("run_history.json", [])
|
||||
entry = _find_history_entry(history, entry_id)
|
||||
if entry is None:
|
||||
return
|
||||
entry["verification"] = "pending"
|
||||
storage.save("run_history.json", history)
|
||||
|
||||
|
||||
def _update_verification(entry_id, verification, verify_message):
|
||||
def set_verification_message(entry_id, message):
|
||||
"""Record the latest poll's note without changing the terminal
|
||||
state — used between retries, while still "pending"."""
|
||||
history = storage.load("run_history.json", [])
|
||||
for entry in history:
|
||||
if entry.get("id") == entry_id:
|
||||
entry["verification"] = verification
|
||||
entry["verify_message"] = verify_message
|
||||
entry["verified_at"] = datetime.now(timezone.utc).isoformat()
|
||||
break
|
||||
else:
|
||||
entry = _find_history_entry(history, entry_id)
|
||||
if entry is None:
|
||||
# Entry aged out of the 200-cap between being logged and this
|
||||
# delayed check running — nothing to update, not an error.
|
||||
return
|
||||
entry["verify_message"] = message
|
||||
storage.save("run_history.json", history)
|
||||
|
||||
|
||||
def update_verification(entry_id, verification, verify_message):
|
||||
"""Set a terminal verification result ("complete" or "not_confirmed")."""
|
||||
history = storage.load("run_history.json", [])
|
||||
entry = _find_history_entry(history, entry_id)
|
||||
if entry is None:
|
||||
return
|
||||
entry["verification"] = verification
|
||||
entry["verify_message"] = verify_message
|
||||
entry["verified_at"] = datetime.now(timezone.utc).isoformat()
|
||||
storage.save("run_history.json", history)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue