from __future__ import annotations from datetime import datetime, timezone from app.modules.documents.models import ERPAgentJob, ERPWorkstationAgent def _utcnow(): return datetime.now(timezone.utc) def _iso(value): return value.isoformat() if value else None def _elapsed_seconds(started): if not started: return 0 try: now = _utcnow() value = started if value.tzinfo is None: value = value.replace(tzinfo=timezone.utc) return max(0, int((now - value).total_seconds())) except Exception: return 0 def historical_run_status(db, run): job = db.get(ERPAgentJob, run.agent_job_id) if run and run.agent_job_id else None workstation = db.get(ERPWorkstationAgent, run.workstation_agent_id) if run and run.workstation_agent_id else None job_status = str(job.status if job else run.status or "queued").lower() run_status = str(run.status or job_status).lower() if run_status == "completed": stage = "completed" label = "Completed" terminal = True ok = True elif run_status == "failed": stage = "failed" label = "Failed" terminal = True ok = False elif job_status == "claimed": stage = "processing" label = "Processing on workstation" terminal = False ok = None elif job_status == "succeeded": stage = "ingesting" label = "Result received; updating ERP evidence" terminal = False ok = None elif job_status == "failed": stage = "failed" label = "Failed" terminal = True ok = False elif job_status == "cancelled": stage = "failed" label = "Cancelled" terminal = True ok = False else: stage = "queued" label = "Queued for workstation" terminal = False ok = None return { "run_id": int(run.id), "job_id": int(job.id) if job else None, "status": run_status, "job_status": job_status, "stage": stage, "label": label, "terminal": terminal, "ok": ok, "workstation": ( str(getattr(workstation, "machine_name", "") or "").strip() or str(getattr(workstation, "agent_instance_id", "") or "").strip() or "ERP Local Agent" ) if workstation else "ERP Local Agent", "company_name": str(run.company_name or run.tally_guid or ""), "date_from": str(run.date_from or ""), "date_to": str(run.date_to or ""), "evidence_rows": int(run.evidence_rows or 0), "attempts": int(job.attempts or 0) if job else 0, "max_attempts": int(job.max_attempts or 0) if job else 0, "error": str(run.error_message or (job.last_error if job else "") or ""), "created_at": _iso(run.created_at_utc), "claimed_at": _iso(job.claimed_at_utc) if job else None, "completed_at": _iso(run.completed_at_utc or (job.completed_at_utc if job else None)), "elapsed_seconds": _elapsed_seconds(run.created_at_utc), }