"""Abstract base class every detector must implement.""" from __future__ import annotations import base64 from abc import ABC, abstractmethod from pathlib import Path from backend.core.schema import DetectionResult, Verdict class BaseDetector(ABC): """ Contract: - Each detector receives an image path. - Returns a DetectionResult with p_fake in [0, 1]. - Must never raise — catch all exceptions and return DetectionResult with error set. """ name: str = "base" @abstractmethod async def detect(self, image_path: str) -> DetectionResult: """Run detection. Must not raise.""" # ── Helpers ────────────────────────────────────────────────── @staticmethod def _to_base64(image_path: str) -> str: data = Path(image_path).read_bytes() return base64.b64encode(data).decode("utf-8") @staticmethod def _mime_type(image_path: str) -> str: ext = Path(image_path).suffix.lower() return { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp", ".gif": "image/gif", ".bmp": "image/bmp", }.get(ext, "image/jpeg") @staticmethod def _verdict_from_p_fake( p_fake: float, ai_threshold: float = 0.65, real_threshold: float = 0.35, ) -> Verdict: if p_fake >= ai_threshold: return Verdict.AI_GENERATED if p_fake <= real_threshold: return Verdict.REAL return Verdict.UNCERTAIN def _error_result(self, error: str) -> DetectionResult: return DetectionResult( detector=self.name, p_fake=0.5, verdict=Verdict.UNCERTAIN, confidence=0.0, error=error, )