feat: add governed nationwide AOI orchestration and CUDA enforcement
This commit is contained in:
@@ -15,7 +15,7 @@ from sqlalchemy import func
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.core.errors import AppError
|
||||
from app.core.request_context import get_request_id
|
||||
from app.models import AnalysisRun, Dataset, Detection, Job, Project, VectorFeature
|
||||
from app.models import AnalysisRun, Area, Dataset, Detection, Job, Project, VectorFeature
|
||||
from app.schemas.detection import DetectionListResponse, DetectionRead, DetectionRunListResponse, DetectionRunRead, DetectionRunResponse
|
||||
from app.services.detection_georeferencing import pixel_bbox_to_epsg4326_polygon
|
||||
from app.services.detection_qa_service import DetectionQaService
|
||||
@@ -90,6 +90,12 @@ class DetectionService:
|
||||
message="Configured YOLO inference requires an existing raster tile manifest path",
|
||||
status_code=400,
|
||||
)
|
||||
requested_classes = {DetectionService._canonical_class_name(value) for value in (class_filter or [])}
|
||||
unsupported_classes = sorted(requested_classes - set(model.supported_classes))
|
||||
if unsupported_classes:
|
||||
raise AppError(code="DETECTION_CLASS_NOT_VALIDATED", message="The selected model is not validated for one or more requested classes", details={"unsupported_classes": unsupported_classes, "supported_classes": model.supported_classes}, status_code=422)
|
||||
if model.model_id == resolved_settings.yolo_model_id and resolved_settings.yolo_enforce_validation_scope:
|
||||
DetectionService._validate_model_area_scope(db, dataset, resolved_settings)
|
||||
|
||||
run_parameters = {
|
||||
"model_id": model.model_id,
|
||||
@@ -214,6 +220,18 @@ class DetectionService:
|
||||
raise AppError(code="DETECTION_MODEL_UNAVAILABLE", message="Detection model is unavailable", status_code=503)
|
||||
|
||||
@staticmethod
|
||||
def _validate_model_area_scope(db, dataset: Dataset, settings: Settings) -> None:
|
||||
allowed_names = [value.strip().casefold() for value in settings.yolo_validated_area_names.split(",") if value.strip()]
|
||||
area = db.get(Area, dataset.area_id) if dataset.area_id else None
|
||||
area_name = area.name.strip() if area is not None else ""
|
||||
if not area_name or not any(token in area_name.casefold() for token in allowed_names):
|
||||
raise AppError(
|
||||
code="DETECTION_VALIDATION_SCOPE_UNAVAILABLE",
|
||||
message="Configured YOLO inference is not validated for this Dataset area.",
|
||||
details={"dataset_id": str(dataset.id), "dataset_area": area_name or None, "validated_area_names": allowed_names},
|
||||
status_code=422,
|
||||
)
|
||||
@staticmethod
|
||||
def _fail_run_after_exception(db, analysis_run: AnalysisRun, job: Job, exc: Exception, fallback_code: str) -> None:
|
||||
try:
|
||||
db.rollback()
|
||||
|
||||
Reference in New Issue
Block a user