Files
MapMil/centers/analytics/api/app/services/job_stale.py
T
gitrusprusandCursor 3f9dc6643b Add reusable LLM parser profiles with multi-event extract.
Support kind=llm profiles (instruction/schema), optional multi-event posts via #eN URLs, and recover stale running/queued parse jobs after worker crashes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 20:22:38 +03:00

37 lines
1.2 KiB
Python

"""Stale parse-job recovery helpers (running/queued left behind after worker crash)."""
from __future__ import annotations
import os
from datetime import datetime, timezone
from ..models import ParseJob
# Jobs stuck in running/queued longer than this are considered abandoned.
STALE_JOB_SECONDS = int(os.getenv("STALE_JOB_SECONDS", "900"))
def _aware(dt: datetime | None) -> datetime | None:
if dt is None:
return None
if dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc)
return dt
def job_anchor_time(job: ParseJob) -> datetime | None:
"""Best available timestamp for staleness (prefer last_run_at)."""
return _aware(job.last_run_at) or _aware(getattr(job, "created_at", None))
def is_stale_job(job: ParseJob, now: datetime | None = None, *, ttl: int | None = None) -> bool:
if job.status not in ("running", "queued"):
return False
now = now or datetime.now(timezone.utc)
anchor = job_anchor_time(job)
if anchor is None:
# No timestamp — treat long-lived running as stale immediately for recovery
return job.status == "running"
limit = ttl if ttl is not None else STALE_JOB_SECONDS
return (now - anchor).total_seconds() >= limit