from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timezone from sqlalchemy.orm import Session from app.models import AnalysisRun, Job @dataclass(frozen=True) class ReconciliationResult: interrupted_jobs: int interrupted_analysis_runs: int class RuntimeReconciliationService: ERROR_MESSAGE = ( "PROCESS_INTERRUPTED: the GeoIntel process restarted before this work " "reached a terminal state" ) @staticmethod def reconcile( db: Session, *, finished_at: datetime | None = None, ) -> ReconciliationResult: resolved_finished_at = finished_at or datetime.now(timezone.utc) interrupted_jobs = ( db.query(Job) .filter(Job.status == "running") .update( { Job.status: "failed", Job.finished_at: resolved_finished_at, Job.error_message: RuntimeReconciliationService.ERROR_MESSAGE, }, synchronize_session=False, ) ) interrupted_analysis_runs = ( db.query(AnalysisRun) .filter(AnalysisRun.status == "running") .update( { AnalysisRun.status: "failed", AnalysisRun.finished_at: resolved_finished_at, AnalysisRun.error_message: RuntimeReconciliationService.ERROR_MESSAGE, }, synchronize_session=False, ) ) db.commit() return ReconciliationResult( interrupted_jobs=interrupted_jobs, interrupted_analysis_runs=interrupted_analysis_runs, )