from __future__ import annotations from pathlib import Path from typing import Type from app.core.config import Settings, get_settings from app.schemas.detection import DetectionModelCapability from app.services.segmentation_adapter import ( SamSegmentationAdapter, YoloSegmentationAdapter, ) from app.services.yolo_adapter import YoloDetectionAdapter class ModelRegistryService: @staticmethod def list_model_capabilities( settings: Settings | None = None, yolo_adapter_class: Type[YoloDetectionAdapter] = YoloDetectionAdapter, task_type: str = "object_detection", yolo_seg_adapter_class: Type[YoloSegmentationAdapter] = YoloSegmentationAdapter, sam_adapter_class: Type[SamSegmentationAdapter] = SamSegmentationAdapter, ) -> list[DetectionModelCapability]: resolved_settings = settings or get_settings() if task_type == "segmentation": return ModelRegistryService.list_segmentation_model_capabilities( settings=resolved_settings, yolo_seg_adapter_class=yolo_seg_adapter_class, sam_adapter_class=sam_adapter_class, ) if task_type != "object_detection": return [] return [ DetectionModelCapability( model_id="yolo-placeholder", display_name="YOLO detector placeholder", framework="ultralytics/pytorch", task_type="object_detection", supported_classes=["building", "road", "water", "landuse"], configured=False, status="not_configured", limitation_message="YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed.", version=None, ), ModelRegistryService._configured_yolo_capability( resolved_settings, yolo_adapter_class ), DetectionModelCapability( model_id="manual-fixture-detector", display_name="Manual fixture detector", framework="fixture", task_type="object_detection", supported_classes=["building"], configured=True, status="configured", limitation_message="Fixture detector is for explicit tests/demo fixtures only and is not production inference.", version="fixture-v1", ), ] @staticmethod def get_model_capability( model_id: str, settings: Settings | None = None, yolo_adapter_class: Type[YoloDetectionAdapter] = YoloDetectionAdapter, task_type: str = "object_detection", yolo_seg_adapter_class: Type[YoloSegmentationAdapter] = YoloSegmentationAdapter, sam_adapter_class: Type[SamSegmentationAdapter] = SamSegmentationAdapter, ) -> DetectionModelCapability | None: normalized = model_id.strip() for model in ModelRegistryService.list_model_capabilities( settings=settings, yolo_adapter_class=yolo_adapter_class, task_type=task_type, yolo_seg_adapter_class=yolo_seg_adapter_class, sam_adapter_class=sam_adapter_class, ): if model.model_id == normalized: return model return None @staticmethod def list_segmentation_model_capabilities( settings: Settings | None = None, yolo_seg_adapter_class: Type[YoloSegmentationAdapter] = YoloSegmentationAdapter, sam_adapter_class: Type[SamSegmentationAdapter] = SamSegmentationAdapter, ) -> list[DetectionModelCapability]: resolved_settings = settings or get_settings() return [ DetectionModelCapability( model_id="segmentation-placeholder", display_name="Segmentation placeholder", framework="placeholder", task_type="segmentation", supported_classes=["building", "vegetation", "water", "landuse"], configured=False, status="not_configured", limitation_message="Segmentation inference is not configured for this placeholder; no model is downloaded or executed.", version=None, ), DetectionModelCapability( model_id="fixture-segmenter", display_name="Fixture segmenter", framework="fixture", task_type="segmentation", supported_classes=["building", "vegetation", "water", "landuse"], configured=True, status="configured", limitation_message="Fixture segmenter is for explicit tests/demo fixtures only and is not production inference.", version="fixture-v1", ), ModelRegistryService._configured_yolo_seg_capability( resolved_settings, yolo_seg_adapter_class ), ModelRegistryService._configured_sam_capability( resolved_settings, sam_adapter_class ), ] @staticmethod def _configured_yolo_seg_capability( settings: Settings, adapter_class: Type[YoloSegmentationAdapter] = YoloSegmentationAdapter, ) -> DetectionModelCapability: configured = False status = "not_configured" limitation = ( "YOLO segmentation is disabled. Set YOLO_SEG_ENABLED=true and YOLO_SEG_MODEL_PATH to a local " "segmentation model file to enable inference. GeoIntel never downloads model weights automatically." ) model_path = ( Path(settings.yolo_seg_model_path).expanduser() if settings.yolo_seg_model_path else None ) if settings.yolo_seg_enabled: if not adapter_class.dependencies_available(): status = "dependency_unavailable" limitation = "Segmentation dependencies are not installed. Install backend optional extras with geointel-backend[ai]." elif model_path is None: limitation = "YOLO_SEG_MODEL_PATH is not set. GeoIntel will not download segmentation model weights automatically." elif not model_path.exists() or not model_path.is_file(): limitation = "YOLO_SEG_MODEL_PATH does not point to an existing local model file. GeoIntel will not download segmentation model weights automatically." else: configured = True status = "configured" limitation = "Configured for local YOLO segmentation inference over an existing raster tile manifest." return DetectionModelCapability( model_id=settings.yolo_seg_model_id, display_name=settings.yolo_seg_model_display_name, framework="ultralytics/pytorch", task_type="segmentation", supported_classes=["building", "vegetation", "water", "landuse"], configured=configured, status=status, limitation_message=limitation, version=settings.yolo_seg_model_version, ) @staticmethod def _configured_sam_capability( settings: Settings, adapter_class: Type[SamSegmentationAdapter] = SamSegmentationAdapter, ) -> DetectionModelCapability: configured = False status = "not_configured" limitation = ( "SAM is disabled. Set SAM_ENABLED=true and SAM_MODEL_PATH to a local SAM-compatible model file to " "enable class-agnostic segmentation. GeoIntel never downloads model weights automatically." ) model_path = ( Path(settings.sam_model_path).expanduser() if settings.sam_model_path else None ) if settings.sam_enabled: if not adapter_class.dependencies_available(): status = "dependency_unavailable" limitation = "Segmentation dependencies are not installed. Install backend optional extras with geointel-backend[ai]." elif model_path is None: limitation = "SAM_MODEL_PATH is not set. GeoIntel will not download segmentation model weights automatically." elif not model_path.exists() or not model_path.is_file(): limitation = "SAM_MODEL_PATH does not point to an existing local model file. GeoIntel will not download segmentation model weights automatically." else: configured = True status = "configured" limitation = "Configured for local class-agnostic SAM segmentation over an existing raster tile manifest." return DetectionModelCapability( model_id=settings.sam_model_id, display_name=settings.sam_model_display_name, framework="ultralytics/sam", task_type="segmentation", supported_classes=["segment"], configured=configured, status=status, limitation_message=limitation, version=settings.sam_model_version, ) @staticmethod def _configured_yolo_capability( settings: Settings, yolo_adapter_class: Type[YoloDetectionAdapter], ) -> DetectionModelCapability: configured = False status = "not_configured" limitation = "YOLO is disabled. Set YOLO_ENABLED=true and YOLO_MODEL_PATH to a local model file to enable inference." model_path = ( Path(settings.yolo_model_path).expanduser() if settings.yolo_model_path else None ) if settings.yolo_enabled: if not yolo_adapter_class.dependencies_available(): status = "dependency_unavailable" limitation = "YOLO dependencies are not installed. Install backend optional extras with geointel-backend[ai]." elif model_path is None: limitation = "YOLO_MODEL_PATH is not set. GeoIntel will not download model weights automatically." elif not model_path.exists() or not model_path.is_file(): limitation = "YOLO_MODEL_PATH does not point to an existing local model file. GeoIntel will not download model weights automatically." else: configured = True status = "configured" limitation = "Configured for local YOLO inference over an existing raster tile manifest." return DetectionModelCapability( model_id=settings.yolo_model_id, display_name=settings.yolo_model_display_name, framework="ultralytics/pytorch", task_type="object_detection", supported_classes=["building", "road", "water", "landuse"], configured=configured, status=status, limitation_message=limitation, version=settings.yolo_model_version, training_scope=( "Operator-managed local weights; the runtime has no nationally governed training-corpus evidence." ), validation_scope="Mol and the Kempen operator evidence; no Belgian national validation matrix is bound.", validated_regions=["flanders_mol_kempen"], nationally_validated=False, operator_review_required=True, )