"""Report domain models — the unified output of a face-intelligence job.""" from __future__ import annotations import uuid from datetime import datetime, timezone from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field class Evidence(BaseModel): """Preserved raw provider output — never discarded. Every result from every provider is preserved as Evidence, regardless of success. This is the foundation of evidence-first design. """ provider: str capability: str timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) raw: Any = None # verbatim provider response normalized: dict = Field(default_factory=dict) elapsed_ms: float = 0.0 success: bool = True error: Optional[str] = None error_type: Optional[str] = None metadata: dict = Field(default_factory=dict) retry_count: int = 0 limitations: List[str] = Field(default_factory=list) class ConfidenceScore(BaseModel): """Explainable confidence — decomposed into weighted sub-scores.""" overall: float # 0.0 – 1.0 components: Dict[str, float] = Field(default_factory=dict) explanation: str = "" method: str = "weighted_average" class FaceDetection(BaseModel): """One detected face, with cross-provider consensus.""" box: Dict[str, int] # {x, y, w, h} confidence: ConfidenceScore landmarks: Optional[Dict[str, List[int]]] = None detected_by: List[str] = Field(default_factory=list) embedding: Optional[List[float]] = None embedding_provider: Optional[str] = None class FaceMatch(BaseModel): """A recognition match against the reference gallery.""" query_face_index: int best_match: Optional[str] = None confidence: ConfidenceScore distances: Dict[str, float] = Field(default_factory=dict) class ImageAnalysisResult(BaseModel): """Output of an image-analysis provider (quality, properties, features).""" provider: str quality_score: Optional[float] = None brightness: Optional[float] = None contrast: Optional[float] = None sharpness: Optional[float] = None noise_level: Optional[float] = None width: Optional[int] = None height: Optional[int] = None channels: Optional[int] = None color_profile: Optional[str] = None dominant_colors: List[str] = Field(default_factory=list) aspects: dict = Field(default_factory=dict) confidence: Optional[ConfidenceScore] = None class MetadataResult(BaseModel): """Output of a metadata-extraction provider (EXIF, XMP, IPTC).""" provider: str format: Optional[str] = None exif: dict = Field(default_factory=dict) xmp: dict = Field(default_factory=dict) iptc: dict = Field(default_factory=dict) gps: Optional[dict] = None camera_make: Optional[str] = None camera_model: Optional[str] = None software: Optional[str] = None capture_time: Optional[str] = None confidence: Optional[ConfidenceScore] = None class ForensicsResult(BaseModel): """Output of a forensics provider (integrity, duplicates, manipulation).""" provider: str integrity_score: Optional[float] = None is_duplicate: Optional[bool] = None duplicate_of: Optional[str] = None similarity_score: Optional[float] = None manipulation_indicators: List[str] = Field(default_factory=list) ela_score: Optional[float] = None # error-level analysis noise_inconsistency: Optional[float] = None details: dict = Field(default_factory=dict) confidence: Optional[ConfidenceScore] = None class OCRResult(BaseModel): """Output of an OCR provider.""" provider: str text_blocks: List[dict] = Field(default_factory=list) full_text: str = "" language: Optional[str] = None confidence: Optional[ConfidenceScore] = None class ObjectDetectionResult(BaseModel): """Output of an object-detection provider.""" provider: str objects: List[dict] = Field(default_factory=list) model: Optional[str] = None confidence: Optional[ConfidenceScore] = None class SceneResult(BaseModel): """Output of a scene-classification provider.""" provider: str labels: List[dict] = Field(default_factory=list) confidence: Optional[ConfidenceScore] = None class NSFWResult(BaseModel): """Output of an NSFW-detection provider.""" provider: str is_nsfw: bool = False labels: List[str] = Field(default_factory=list) confidence: Optional[ConfidenceScore] = None class AIDetectionResult(BaseModel): """Output of an AI-generated-image-detection provider.""" provider: str is_ai_generated: bool = False confidence: Optional[ConfidenceScore] = None class EmbeddingResult(BaseModel): """Output of an embedding provider.""" provider: str embedding: List[float] = Field(default_factory=list) model: Optional[str] = None dimensions: int = 0 # --------------------------------------------------------------------------- # # OSINT models (Phase 10-15) # --------------------------------------------------------------------------- # class OSINTMatch(BaseModel): """One merged reverse-image-search match with source classification.""" image_url: str = "" source_page: str = "" title: str = "" snippet: str = "" thumbnail: str = "" source_type: str = "unknown" platform: Optional[str] = None root_domain: str = "" is_cdn: bool = False confidence: float = 0.0 first_seen: Optional[str] = None found_by: List[str] = Field(default_factory=list) class OSINTResult(BaseModel): """Output of the OSINT reverse-search orchestrator.""" providers_invoked: List[str] = Field(default_factory=list) providers_succeeded: List[str] = Field(default_factory=list) providers_failed: List[str] = Field(default_factory=list) total_matches: int = 0 matches: List[OSINTMatch] = Field(default_factory=list) source_type_breakdown: dict = Field(default_factory=dict) elapsed_ms: float = 0.0 class FaceQualityMetrics(BaseModel): """Quality metrics for a single detected face.""" quality_score: float = 0.0 blur_score: float = 0.0 is_blurry: bool = False face_size: int = 0 # width * height of bbox face_size_label: str = "" # "small" | "medium" | "large" pose_yaw: float = 0.0 # degrees; 0 = frontal pose_pitch: float = 0.0 pose_roll: float = 0.0 pose_label: str = "" # "frontal" | "profile" | "extreme" orientation: str = "" # "upright" | "tilted" | "rotated" is_best_face: bool = False class FaceCluster(BaseModel): """A cluster of faces that appear to be the same person.""" cluster_id: int face_indices: List[int] = Field(default_factory=list) representative_index: int = 0 num_faces: int = 0 avg_similarity: float = 0.0 class FaceIntelligenceResult(BaseModel): """Output of the face intelligence service.""" total_faces: int = 0 best_face_index: Optional[int] = None quality_metrics: List[FaceQualityMetrics] = Field(default_factory=list) clusters: List[FaceCluster] = Field(default_factory=list) duplicate_face_indices: List[int] = Field(default_factory=list) elapsed_ms: float = 0.0 class ForensicMetadataReport(BaseModel): """Forensic metadata intelligence — expanded EXIF/XMP/IPTC/ICC.""" provider: str = "forensic_metadata" format: Optional[str] = None exif: dict = Field(default_factory=dict) xmp: dict = Field(default_factory=dict) iptc: dict = Field(default_factory=dict) icc_profile: Optional[dict] = None gps: Optional[dict] = None camera_make: Optional[str] = None camera_model: Optional[str] = None lens_model: Optional[str] = None software: Optional[str] = None capture_time: Optional[str] = None capture_time_iso: Optional[str] = None timezone_estimate: Optional[str] = None editing_history: List[str] = Field(default_factory=list) thumbnail_extracted: bool = False embedded_preview: bool = False camera_fingerprint: Optional[str] = None # hash of camera-specific noise pattern compression_analysis: Optional[dict] = None confidence: Optional[ConfidenceScore] = None class DetectedObjectIntelligence(BaseModel): """One detected object with intelligence metadata.""" class_label: str confidence: float = 0.0 box: dict = Field(default_factory=dict) crop_base64: Optional[str] = None is_vehicle: bool = False is_screen: bool = False is_document: bool = False is_phone: bool = False is_laptop: bool = False is_watch: bool = False is_logo: bool = False is_text_region: bool = False is_license_plate: bool = False is_qr_code: bool = False is_barcode: bool = False searchable_metadata: dict = Field(default_factory=dict) class ObjectIntelligenceResult(BaseModel): """Output of the object intelligence service.""" total_objects: int = 0 objects: List[DetectedObjectIntelligence] = Field(default_factory=list) vehicles: List[DetectedObjectIntelligence] = Field(default_factory=list) license_plates: List[DetectedObjectIntelligence] = Field(default_factory=list) qr_codes: List[DetectedObjectIntelligence] = Field(default_factory=list) barcodes: List[DetectedObjectIntelligence] = Field(default_factory=list) documents: List[DetectedObjectIntelligence] = Field(default_factory=list) screens: List[DetectedObjectIntelligence] = Field(default_factory=list) phones: List[DetectedObjectIntelligence] = Field(default_factory=list) laptops: List[DetectedObjectIntelligence] = Field(default_factory=list) watches: List[DetectedObjectIntelligence] = Field(default_factory=list) logos: List[DetectedObjectIntelligence] = Field(default_factory=list) text_regions: List[DetectedObjectIntelligence] = Field(default_factory=list) elapsed_ms: float = 0.0 class LocationEvidence(BaseModel): """One piece of evidence for a location estimate.""" source: str = "" # "gps" | "ocr" | "language" | "scene" | "license_plate" | "logo" value: str = "" confidence: float = 0.0 details: dict = Field(default_factory=dict) class LocationEstimate(BaseModel): """Output of the location intelligence service.""" candidate_countries: List[dict] = Field(default_factory=list) # [{"country": "France", "confidence": 0.8}] candidate_cities: List[dict] = Field(default_factory=list) gps: Optional[dict] = None evidence: List[LocationEvidence] = Field(default_factory=list) conflicting_evidence: List[str] = Field(default_factory=list) overall_confidence: float = 0.0 elapsed_ms: float = 0.0 class CorrelationNode(BaseModel): """A node in the correlation graph.""" id: str node_type: str # "face" | "object" | "location" | "metadata" | "image" | "embedding" label: str = "" properties: dict = Field(default_factory=dict) class CorrelationEdge(BaseModel): """An edge in the correlation graph — a deterministic relationship.""" source: str target: str edge_type: str # "same_face" | "same_object" | "same_location" | "same_camera" | "same_hash" | "same_embedding" | "same_metadata" | "same_timestamp" confidence: float = 1.0 evidence: str = "" class CorrelationGraph(BaseModel): """Output of the correlation engine.""" nodes: List[CorrelationNode] = Field(default_factory=list) edges: List[CorrelationEdge] = Field(default_factory=list) num_nodes: int = 0 num_edges: int = 0 elapsed_ms: float = 0.0 class ConflictReport(BaseModel): """Cross-provider disagreement.""" kind: str # e.g. "face_count_mismatch" providers: List[str] description: str severity: str = "info" # info | warning | error class ReportMetadata(BaseModel): """Job/report metadata.""" job_id: str = Field(default_factory=lambda: str(uuid.uuid4())) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) image_hash: Optional[str] = None total_elapsed_ms: float = 0.0 providers_invoked: List[str] = Field(default_factory=list) providers_succeeded: List[str] = Field(default_factory=list) providers_failed: List[str] = Field(default_factory=list) limitations: List[str] = Field(default_factory=list) class UnifiedFaceReport(BaseModel): """The final unified report consumed by API + UI.""" metadata: ReportMetadata detections: List[FaceDetection] = Field(default_factory=list) matches: List[FaceMatch] = Field(default_factory=list) scraped_images: List[dict] = Field(default_factory=list) reverse_matches: List[dict] = Field(default_factory=list) image_analyses: List[ImageAnalysisResult] = Field(default_factory=list) metadata_extractions: List[MetadataResult] = Field(default_factory=list) forensics: List[ForensicsResult] = Field(default_factory=list) ocr_results: List[OCRResult] = Field(default_factory=list) object_detections: List[ObjectDetectionResult] = Field(default_factory=list) scene_results: List[SceneResult] = Field(default_factory=list) nsfw_results: List[NSFWResult] = Field(default_factory=list) ai_detection_results: List[AIDetectionResult] = Field(default_factory=list) embedding_results: List[EmbeddingResult] = Field(default_factory=list) # OSINT (Phase 10-15) osint: Optional[OSINTResult] = None face_intelligence: Optional[FaceIntelligenceResult] = None forensic_metadata: Optional[ForensicMetadataReport] = None object_intelligence: Optional[ObjectIntelligenceResult] = None location_estimate: Optional[LocationEstimate] = None correlation_graph: Optional[CorrelationGraph] = None evidence: List[Evidence] = Field(default_factory=list) conflicts: List[ConflictReport] = Field(default_factory=list) overall_confidence: Optional[ConfidenceScore] = None