GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
304 lines
14 KiB
Python
304 lines
14 KiB
Python
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.runtime_model_provenance_service import RuntimeModelProvenanceService
|
|
from app.services.yolo_adapter import YoloDetectionAdapter
|
|
from app.core.errors import AppError
|
|
|
|
|
|
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:
|
|
try:
|
|
RuntimeModelProvenanceService.validate_for_runtime(
|
|
model_path=model_path,
|
|
model_id=settings.yolo_seg_model_id,
|
|
task_type="segmentation",
|
|
expected_model_version=settings.yolo_seg_model_version,
|
|
allowed_frameworks=("ultralytics/pytorch", "ultralytics", "pytorch"),
|
|
)
|
|
except AppError as exc:
|
|
status = "contract_incomplete"
|
|
limitation = (
|
|
"Configured YOLO segmentation weights are not runnable until their immutable "
|
|
f"runtime provenance sidecar validates: {exc.message}"
|
|
)
|
|
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:
|
|
try:
|
|
RuntimeModelProvenanceService.validate_for_runtime(
|
|
model_path=model_path,
|
|
model_id=settings.sam_model_id,
|
|
task_type="segmentation",
|
|
expected_model_version=settings.sam_model_version,
|
|
allowed_frameworks=("ultralytics/sam", "sam", "ultralytics", "pytorch"),
|
|
)
|
|
except AppError as exc:
|
|
status = "contract_incomplete"
|
|
limitation = (
|
|
"Configured SAM weights are not runnable until their immutable runtime provenance "
|
|
f"sidecar validates: {exc.message}"
|
|
)
|
|
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:
|
|
try:
|
|
validate_runtime = getattr(yolo_adapter_class, "validate_runtime", None)
|
|
if validate_runtime is not None:
|
|
yolo_adapter_class(settings).validate_runtime()
|
|
except AppError as exc:
|
|
status = "accelerator_unavailable"
|
|
limitation = exc.message
|
|
else:
|
|
try:
|
|
RuntimeModelProvenanceService.validate_for_runtime(
|
|
model_path=model_path,
|
|
model_id=settings.yolo_model_id,
|
|
task_type="object_detection",
|
|
expected_model_version=settings.yolo_model_version,
|
|
allowed_frameworks=("ultralytics/pytorch", "ultralytics", "pytorch"),
|
|
)
|
|
except AppError as exc:
|
|
status = "contract_incomplete"
|
|
limitation = (
|
|
"Configured YOLO weights are not runnable until their immutable runtime provenance "
|
|
f"sidecar validates: {exc.message}"
|
|
)
|
|
else:
|
|
configured = True
|
|
status = "configured"
|
|
limitation = "Configured for local YOLO inference over an existing raster tile manifest within its validated area scope."
|
|
|
|
return DetectionModelCapability(
|
|
model_id=settings.yolo_model_id,
|
|
display_name=settings.yolo_model_display_name,
|
|
framework="ultralytics/pytorch",
|
|
task_type="object_detection",
|
|
supported_classes=[value.strip().lower() for value in settings.yolo_model_classes.split(",") if value.strip()],
|
|
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,
|
|
)
|