from __future__ import annotations import json from dataclasses import dataclass from datetime import datetime from typing import Any from database import ReviewDatabase @dataclass(frozen=True) class SQLiteJobRunRepository: database: ReviewDatabase def completed(self, job_id: str, idempotency_key: str) -> bool: with self.database.connect() as connection: row = connection.execute( """ SELECT 1 FROM job_runs WHERE job_id = ? AND idempotency_key = ? AND status = 'success' LIMIT 1 """, (job_id, idempotency_key), ).fetchone() return row is not None def start( self, job_id: str, idempotency_key: str, output_version: str, metadata: dict[str, Any] | None = None, ) -> int: now = datetime.now().astimezone().isoformat(timespec="seconds") with self.database.connect() as connection: row = connection.execute( """ SELECT COALESCE(MAX(attempt), 0) + 1 AS attempt FROM job_runs WHERE job_id = ? AND idempotency_key = ? """, (job_id, idempotency_key), ).fetchone() cursor = connection.execute( """ INSERT INTO job_runs (job_id, idempotency_key, status, attempt, started_at, output_version, metadata) VALUES (?, ?, 'running', ?, ?, ?, ?) """, ( job_id, idempotency_key, int(row["attempt"]), now, output_version, json.dumps(metadata or {}, ensure_ascii=False, separators=(",", ":")), ), ) connection.execute( """ DELETE FROM job_runs WHERE id < (SELECT COALESCE(MAX(id), 0) - 20000 FROM job_runs) AND status != 'running' """ ) return int(cursor.lastrowid) def finish( self, run_id: int, status: str, elapsed_ms: int, error_code: str = "", message: str = "", ) -> None: now = datetime.now().astimezone().isoformat(timespec="seconds") with self.database.connect() as connection: connection.execute( """ UPDATE job_runs SET status = ?, finished_at = ?, elapsed_ms = ?, error_code = ?, message = ? WHERE id = ? """, (status, now, elapsed_ms, error_code, message[:1000], int(run_id)), ) def recent(self, limit: int = 20) -> list[dict[str, Any]]: with self.database.connect() as connection: rows = connection.execute( """ SELECT id, job_id, idempotency_key, status, attempt, started_at, finished_at, elapsed_ms, error_code, message, output_version FROM job_runs ORDER BY id DESC LIMIT ? """, (max(1, min(100, int(limit))),), ).fetchall() return [dict(row) for row in rows]