Make YOLO preflight runnable in backend container
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-17 02:02:17 +02:00
parent 3f1a686bef
commit 382dcadec4
8 changed files with 89 additions and 52 deletions
+1
View File
@@ -10,6 +10,7 @@
## Sprint 25 YOLO compatibility smoke hardening (2026-06-17) ## Sprint 25 YOLO compatibility smoke hardening (2026-06-17)
- Added an explicit `--check-model-load` mode to `scripts/yolo_preflight.py`. - Added an explicit `--check-model-load` mode to `scripts/yolo_preflight.py`.
- Added the same YOLO preflight entrypoint under `backend/scripts/` so it can run inside the backend Docker container.
- The smoke loads only a configured local model file through the YOLO adapter, runs no inference and does not download weights. - The smoke loads only a configured local model file through the YOLO adapter, runs no inference and does not download weights.
- The CLI rejects `--check-model-load` together with `--assume-dependencies` to avoid false-positive AI readiness. - The CLI rejects `--check-model-load` together with `--assume-dependencies` to avoid false-positive AI readiness.
- Added tests for successful mocked model-load smoke, load failure reporting and CLI guard behavior. - Added tests for successful mocked model-load smoke, load failure reporting and CLI guard behavior.
+6
View File
@@ -224,6 +224,12 @@ Optional local model compatibility smoke:
python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json
``` ```
In Docker, run the same smoke through the backend container:
```bash
docker compose exec -T backend python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json
```
The smoke loads only the supplied local model file, does not run inference and The smoke loads only the supplied local model file, does not run inference and
does not download weights. does not download weights.
+59
View File
@@ -0,0 +1,59 @@
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
BACKEND_ROOT = Path(__file__).resolve().parents[1]
if str(BACKEND_ROOT) not in sys.path:
sys.path.insert(0, str(BACKEND_ROOT))
from app.core.config import Settings # noqa: E402
from app.services.yolo_preflight_service import YoloPreflightService # noqa: E402
def main() -> int:
parser = argparse.ArgumentParser(description="Run local YOLO configuration preflight without running inference.")
parser.add_argument("--model-path", help="Existing local YOLO model path.")
parser.add_argument("--tile-manifest-path", help="Existing raster tile manifest path.")
parser.add_argument("--enabled", action="store_true", help="Treat YOLO as enabled for this preflight.")
parser.add_argument("--max-tiles", type=int, default=100, help="Maximum tile count allowed by preflight.")
parser.add_argument(
"--assume-dependencies",
action="store_true",
help="Skip checking installed ultralytics/torch packages; useful for validating local paths on non-AI machines.",
)
parser.add_argument(
"--check-model-load",
action="store_true",
help="Explicitly load the configured local model file to verify Ultralytics compatibility; no inference is run.",
)
parser.add_argument("--json", action="store_true", help="Print JSON output only.")
args = parser.parse_args()
if args.check_model_load and args.assume_dependencies:
parser.error("--check-model-load cannot be combined with --assume-dependencies")
settings = Settings(
yolo_enabled=args.enabled or bool(args.model_path),
yolo_model_path=args.model_path,
yolo_max_tiles=args.max_tiles,
)
payload = YoloPreflightService.run(
settings=settings,
tile_manifest_path=args.tile_manifest_path,
assume_dependencies=args.assume_dependencies,
check_model_load=args.check_model_load,
)
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
else:
print("GeoIntel YOLO preflight")
print(json.dumps(payload, indent=2, sort_keys=True))
return 0 if payload["status"] in {"ready", "not_configured", "dependency_unavailable"} else 1
if __name__ == "__main__":
raise SystemExit(main())
+1
View File
@@ -35,6 +35,7 @@ def test_readiness_gate_compiles_yolo_preflight_script() -> None:
content = script.read_text(encoding="utf-8") content = script.read_text(encoding="utf-8")
assert "-m py_compile scripts/yolo_preflight.py" in content assert "-m py_compile scripts/yolo_preflight.py" in content
assert "-m py_compile backend/scripts/yolo_preflight.py" in content
def test_demo_export_workflow_script_verifies_export_endpoints() -> None: def test_demo_export_workflow_script_verifies_export_endpoints() -> None:
+3 -3
View File
@@ -1,12 +1,12 @@
## Sprint 25 YOLO compatibility smoke hardening (2026-06-17) ## Sprint 25 YOLO compatibility smoke hardening (2026-06-17)
Changed: Changed:
- Added explicit `--check-model-load` support to `scripts/yolo_preflight.py` and `YoloPreflightService`. - Added explicit `--check-model-load` support to `scripts/yolo_preflight.py`, `backend/scripts/yolo_preflight.py` and `YoloPreflightService`.
- The model-load smoke requires real optional AI dependencies, loads only an existing local model file, runs no inference and does not download weights. - The model-load smoke requires real optional AI dependencies, loads only an existing local model file, runs no inference and does not download weights.
- The CLI rejects `--check-model-load` with `--assume-dependencies` to avoid false-positive AI readiness. - The CLI rejects `--check-model-load` with `--assume-dependencies` to avoid false-positive AI readiness.
- Added regression tests for mocked successful load, load failure reporting and CLI guard behavior. - Added regression tests for mocked successful load, load failure reporting and CLI guard behavior.
- Added Python compile validation for `scripts/yolo_preflight.py` to the readiness gate. - Added Python compile validation for both YOLO preflight entrypoints to the readiness gate.
- Updated AI pipeline, scripts, backend, TODO and changelog docs. - Updated AI pipeline, scripts, backend, TODO and changelog docs, including Docker runtime usage.
Validation: Validation:
- `python -m py_compile scripts/yolo_preflight.py` passed. - `python -m py_compile scripts/yolo_preflight.py` passed.
+6
View File
@@ -29,6 +29,12 @@ Verify a configured local YOLO model without running inference:
python scripts/yolo_preflight.py --model-path /absolute/path/to/model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json python scripts/yolo_preflight.py --model-path /absolute/path/to/model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json
``` ```
Against the Docker runtime:
```bash
docker compose exec -T backend python scripts/yolo_preflight.py --model-path /absolute/path/to/model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json
```
The model-load smoke is opt-in, requires real optional AI dependencies, refuses The model-load smoke is opt-in, requires real optional AI dependencies, refuses
`--assume-dependencies`, loads only the supplied local file and does not download `--assume-dependencies`, loads only the supplied local file and does not download
weights or run prediction. weights or run prediction.
+1
View File
@@ -38,6 +38,7 @@ ${PYTHON_BIN} scripts/validate_m14_launch_assets.py
${PYTHON_BIN} -m py_compile scripts/gis_import_smoke.py ${PYTHON_BIN} -m py_compile scripts/gis_import_smoke.py
${PYTHON_BIN} -m py_compile scripts/seed_demo_workflow.py ${PYTHON_BIN} -m py_compile scripts/seed_demo_workflow.py
${PYTHON_BIN} -m py_compile scripts/yolo_preflight.py ${PYTHON_BIN} -m py_compile scripts/yolo_preflight.py
${PYTHON_BIN} -m py_compile backend/scripts/yolo_preflight.py
${PYTHON_BIN} -m py_compile scripts/cleanup_demo_artifacts.py ${PYTHON_BIN} -m py_compile scripts/cleanup_demo_artifacts.py
${PYTHON_BIN} -m py_compile backend/scripts/cleanup_demo_artifacts.py ${PYTHON_BIN} -m py_compile backend/scripts/cleanup_demo_artifacts.py
${PYTHON_BIN} -m compileall backend/app ${PYTHON_BIN} -m compileall backend/app
+12 -49
View File
@@ -1,59 +1,22 @@
from __future__ import annotations from __future__ import annotations
import argparse import importlib.util
import json
import sys
from pathlib import Path from pathlib import Path
from types import ModuleType
ROOT = Path(__file__).resolve().parents[1] def load_backend_script() -> ModuleType:
BACKEND_ROOT = ROOT / "backend" script = Path(__file__).resolve().parents[1] / "backend" / "scripts" / "yolo_preflight.py"
if str(BACKEND_ROOT) not in sys.path: spec = importlib.util.spec_from_file_location("backend_yolo_preflight", script)
sys.path.insert(0, str(BACKEND_ROOT)) if spec is None or spec.loader is None:
raise RuntimeError(f"Could not load YOLO preflight implementation from {script}")
from app.core.config import Settings # noqa: E402 module = importlib.util.module_from_spec(spec)
from app.services.yolo_preflight_service import YoloPreflightService # noqa: E402 spec.loader.exec_module(module)
return module
def main() -> int: _impl = load_backend_script()
parser = argparse.ArgumentParser(description="Run local YOLO configuration preflight without loading a model.") main = _impl.main
parser.add_argument("--model-path", help="Existing local YOLO model path.")
parser.add_argument("--tile-manifest-path", help="Existing raster tile manifest path.")
parser.add_argument("--enabled", action="store_true", help="Treat YOLO as enabled for this preflight.")
parser.add_argument("--max-tiles", type=int, default=100, help="Maximum tile count allowed by preflight.")
parser.add_argument(
"--assume-dependencies",
action="store_true",
help="Skip checking installed ultralytics/torch packages; useful for validating local paths on non-AI machines.",
)
parser.add_argument(
"--check-model-load",
action="store_true",
help="Explicitly load the configured local model file to verify Ultralytics compatibility; no inference is run.",
)
parser.add_argument("--json", action="store_true", help="Print JSON output only.")
args = parser.parse_args()
if args.check_model_load and args.assume_dependencies:
parser.error("--check-model-load cannot be combined with --assume-dependencies")
settings = Settings(
yolo_enabled=args.enabled or bool(args.model_path),
yolo_model_path=args.model_path,
yolo_max_tiles=args.max_tiles,
)
payload = YoloPreflightService.run(
settings=settings,
tile_manifest_path=args.tile_manifest_path,
assume_dependencies=args.assume_dependencies,
check_model_load=args.check_model_load,
)
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
else:
print("GeoIntel YOLO preflight")
print(json.dumps(payload, indent=2, sort_keys=True))
return 0 if payload["status"] in {"ready", "not_configured", "dependency_unavailable"} else 1
if __name__ == "__main__": if __name__ == "__main__":