Files
geointel/docs/superpowers/plans/2026-07-06-model-reference-catalog.md
T

11 KiB

Model And Reference Catalog Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a read-only local model asset catalog and make official reference providers visually distinct from AI model selection.

Architecture: Model files remain runtime assets in the mounted model directory; persisted domain state remains in jobs, analysis runs, detections, datasets and vector features. Detection runs keep model_id="yolo-configured" and optionally include a validated model_asset_id resolved by a dedicated backend service.

Tech Stack: FastAPI, Pydantic, SQLAlchemy, React, TypeScript, Vite, MapLibre, existing GeoIntel API envelope helpers.


File Structure

  • Create backend/app/services/model_asset_catalog_service.py: read-only scanner and resolver for local model assets.
  • Modify backend/app/core/config.py: add YOLO_MODELS_DIR setting with safe default /app/models.
  • Modify backend/app/schemas/detection.py: add model asset response schemas and optional request fields.
  • Modify backend/app/api/routes/detection.py: add model asset endpoint and pass selected asset IDs to preflight/detection.
  • Modify backend/app/services/detection_service.py: resolve selected model asset into run-local YOLO settings and persist selection in parameters.
  • Modify backend/app/services/yolo_preflight_service.py: accept selected asset ID and preflight against that asset path.
  • Add backend/tests/test_model_asset_catalog.py: unit tests for scanning, resolving and envelope behavior.
  • Add or extend backend/tests/test_sprint8b_yolo_foundation.py: detection run parameter persistence for selected assets.
  • Modify frontend/src/types.ts: add model asset types and optional request fields.
  • Modify frontend/src/services/api/detection.ts: add model asset endpoint and preflight query param.
  • Modify frontend/src/hooks/useDetectionWorkflow.ts: load/select model assets and send selected asset ID.
  • Modify frontend/src/components/detection/DetectionLab.tsx: add local model asset picker and clearer model state.
  • Modify frontend/src/components/providers/ProviderPanel.tsx: sharpen reference-provider catalog copy.
  • Update docs listed in the design document.

Task 1: Backend Catalog Tests

Files:

  • Create: backend/tests/test_model_asset_catalog.py

  • Step 1: Write catalog unit tests

from pathlib import Path

from app.core.config import Settings
from app.services.model_asset_catalog_service import ModelAssetCatalogService


def test_model_asset_catalog_lists_supported_files(tmp_path: Path) -> None:
    model_file = tmp_path / "building-detector.pt"
    model_file.write_bytes(b"local model")
    ignored_file = tmp_path / "notes.txt"
    ignored_file.write_text("ignore me", encoding="utf-8")
    settings = Settings(yolo_models_dir=str(tmp_path), yolo_model_path=str(model_file), yolo_enabled=True)

    response = ModelAssetCatalogService.list_assets(settings=settings)

    assert response.total == 1
    asset = response.items[0]
    assert asset.filename == "building-detector.pt"
    assert asset.active is True
    assert asset.model_path == str(model_file)
    assert asset.size_bytes == len(b"local model")
    assert len(asset.sha256) == 64
    assert asset.will_download_models is False


def test_model_asset_catalog_resolves_known_asset(tmp_path: Path) -> None:
    model_file = tmp_path / "building-detector.pt"
    model_file.write_bytes(b"local model")
    settings = Settings(yolo_models_dir=str(tmp_path), yolo_enabled=True)

    asset = ModelAssetCatalogService.resolve_asset("building-detector-pt", settings=settings)

    assert asset.filename == "building-detector.pt"
    assert asset.model_path == str(model_file)


def test_model_asset_catalog_rejects_unknown_asset(tmp_path: Path) -> None:
    settings = Settings(yolo_models_dir=str(tmp_path), yolo_enabled=True)

    try:
        ModelAssetCatalogService.resolve_asset("missing-model", settings=settings)
    except Exception as exc:
        assert getattr(exc, "code", "") == "DETECTION_MODEL_ASSET_NOT_FOUND"
    else:
        raise AssertionError("unknown model asset should fail")
  • Step 2: Run the tests to verify they fail before implementation

Run:

cd backend
python -m pytest tests/test_model_asset_catalog.py -q

Expected: import failure for model_asset_catalog_service.

Task 2: Backend Catalog Implementation

Files:

  • Create: backend/app/services/model_asset_catalog_service.py

  • Modify: backend/app/core/config.py

  • Modify: backend/app/schemas/detection.py

  • Step 1: Add settings and schemas

Add yolo_models_dir to settings:

yolo_models_dir: str = Field(default="/app/models", validation_alias="YOLO_MODELS_DIR")

Add schemas:

class ModelAssetRead(BaseModel):
    model_asset_id: str
    filename: str
    display_name: str
    model_path: str
    suffix: str
    framework: str
    task_type: str
    size_bytes: int
    sha256: str
    active: bool
    status: str
    limitation_message: str
    will_download_models: bool = False


class ModelAssetListResponse(BaseModel):
    items: list[ModelAssetRead]
    total: int
    model_directory: str
  • Step 2: Implement the scanner and resolver

Implement a service that:

  • resolves YOLO_MODELS_DIR;

  • scans .pt, .onnx, .engine;

  • uses deterministic slug IDs such as building-detector-pt;

  • computes SHA-256;

  • marks active file by comparing resolved paths;

  • raises AppError(code="DETECTION_MODEL_ASSET_NOT_FOUND", status_code=404) for unknown IDs.

  • Step 3: Run focused tests

Run:

cd backend
python -m pytest tests/test_model_asset_catalog.py -q

Expected: pass.

Task 3: API And Service Wiring

Files:

  • Modify: backend/app/api/routes/detection.py

  • Modify: backend/app/services/detection_service.py

  • Modify: backend/app/services/yolo_preflight_service.py

  • Modify: backend/app/schemas/detection.py

  • Test: backend/tests/test_model_asset_catalog.py

  • Step 1: Add endpoint test

Add a FastAPI test that calls GET /api/v1/detection/model-assets and asserts:

assert "data" in response.json()
assert response.json()["data"]["total"] == 1
  • Step 2: Add optional request fields

Extend DetectionRunRequest with:

model_asset_id: str | None = None

Extend preflight query handling with model_asset_id: str | None = None.

  • Step 3: Wire endpoint

Add route:

@router.get("/model-assets", response_model=dict)
def list_detection_model_assets() -> dict:
    return envelope(ModelAssetCatalogService.list_assets().model_dump())
  • Step 4: Resolve selected asset in detection service

When model_asset_id is present and model_id equals the configured YOLO model ID:

  • resolve the asset through ModelAssetCatalogService;

  • copy settings with yolo_model_path=asset.model_path;

  • persist model_asset_id, model_asset_path, and model_asset_sha256 in run parameters.

  • Step 5: Resolve selected asset in preflight

When model_asset_id is present:

  • resolve the asset;

  • preflight against that asset path;

  • include model_asset_id in the response.

  • Step 6: Run focused tests

Run:

cd backend
python -m pytest tests/test_model_asset_catalog.py tests/test_sprint8b_yolo_foundation.py -q

Expected: pass.

Task 4: Frontend Model Asset Selection

Files:

  • Modify: frontend/src/types.ts

  • Modify: frontend/src/services/api/detection.ts

  • Modify: frontend/src/hooks/useDetectionWorkflow.ts

  • Modify: frontend/src/components/detection/DetectionLab.tsx

  • Step 1: Add frontend types and API call

Add:

export interface ModelAssetRead {
  model_asset_id: string
  filename: string
  display_name: string
  model_path: string
  suffix: string
  framework: string
  task_type: string
  size_bytes: number
  sha256: string
  active: boolean
  status: string
  limitation_message: string
  will_download_models: boolean
}

export interface ModelAssetListResponse {
  items: ModelAssetRead[]
  total: number
  model_directory: string
}

Add model_asset_id?: string | null to DetectionRunRequest and preflight params.

  • Step 2: Add hook state

Add:

const [modelAssets, setModelAssets] = useState<ModelAssetRead[]>([])
const [selectedModelAssetId, setSelectedModelAssetId] = useState('')
const [modelAssetError, setModelAssetError] = useState<string | null>(null)

Load assets alongside detection models and default to the active asset when one exists.

  • Step 3: Send selected asset

Send model_asset_id: selectedModelAssetId || null in:

  • getYoloPreflight;

  • run.

  • Step 4: Add UI picker

In DetectionLab, render a local model asset picker when selectedDetectionModelId === "yolo-configured".

Show:

  • filename;

  • active badge;

  • size in MB;

  • checksum prefix;

  • no-download warning.

  • Step 5: Typecheck

Run:

cd frontend
npm run typecheck

Expected: pass.

Task 5: Provider Catalog Clarity

Files:

  • Modify: frontend/src/components/providers/ProviderPanel.tsx

  • Step 1: Update copy and grouping

Keep existing provider cards but add a concise heading that states:

  • GRB/OSM are source capabilities, not model choices;

  • manual upload is the currently configured reference data path;

  • fixture is test/demo only.

  • Step 2: Build

Run:

cd frontend
npm run build

Expected: pass.

Task 6: Documentation

Files:

  • Modify: docs/API_CONTRACTS.md

  • Modify: docs/AI_PIPELINES.md

  • Modify: backend/README.md

  • Modify: frontend/README.md

  • Modify: docs/CODEX_EXECUTION_LOG.md

  • Modify: CHANGELOG.md

  • Modify: docs/TODO.md

  • Step 1: Document backend API

Document:

  • GET /api/v1/detection/model-assets;

  • optional model_asset_id on preflight and run;

  • no model downloads;

  • YOLO_MODELS_DIR.

  • Step 2: Document UI behavior

Document:

  • model assets are local runtime files;
  • GRB/OSM are reference data providers;
  • manual upload remains the configured reference path.

Task 7: Full Validation

Files: none unless validation reveals a bug.

  • Step 1: Run backend compile
python -m compileall backend/app
  • Step 2: Run backend tests
cd backend
python -m pytest
  • Step 3: Run readiness
bash scripts/run_readiness_check.sh
  • Step 4: Run frontend typecheck and build
cd frontend
npm run typecheck
npm run build
  • Step 5: Run migration checks
cd backend
python -m alembic heads
python -m alembic upgrade head --sql
bash ../scripts/live_migration_smoke.sh
  • Step 6: Runtime smoke after deploy

Call:

curl http://192.168.10.150:1202/api/v1/detection/model-assets
curl "http://192.168.10.150:1202/api/v1/detection/yolo/preflight?model_asset_id=<asset>&tile_manifest_path=<manifest>"

Expected: canonical envelopes and no model downloads.