fix: harden release checks for grouped routes
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 11:41:16 +02:00
parent 948e50b5e1
commit d7eadbc6ca
8 changed files with 116 additions and 9 deletions
+3
View File
@@ -14,6 +14,9 @@
- Kept precision, recall, F1 and mean IoU strictly based on candidate polygons versus persisted reference footprints; added a separately labelled reference-envelope comparison as diagnostic evidence only.
- Persisted raw/evaluated/excluded/clipped population counts and diagnostic matching evidence in the existing `quality_checks.findings_json` structure without changing migrations or canonical metric rows.
- Surfaced inference coverage and box-to-footprint diagnostics in Detection Lab and hardened the real-data workflow assertions, documentation and regression coverage.
- Live Tower/PostGIS validation on the persisted Mol-center run evaluated 304 of 374 GRB references, excluded 70 outside the inference tile, persisted six canonical Metric rows and exposed 14 possible box-to-footprint artifacts without inflating the strict scores.
- Updated the API contract audit to use the canonical OpenAPI path map after FastAPI 0.139 introduced grouped top-level routers; this keeps all 81 operations audited across clean framework installations.
- Kept Starlette on its supported pre-1.0 compatibility line until GeoIntel deliberately migrates its test client from `httpx` to `httpx2`; this removes the new framework deprecation warning without taking an unrelated test-stack upgrade.
## Sprint 183 Mol map source clarity and live AI validation (2026-07-14)
+1
View File
@@ -6,6 +6,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.112.0",
"starlette>=0.46.0,<1.0.0",
"uvicorn[standard]>=0.30.6",
"SQLAlchemy>=2.0.34",
"psycopg[binary]>=3.2.1",
+7
View File
@@ -1,6 +1,13 @@
from pathlib import Path
def test_backend_keeps_starlette_on_the_supported_pre_httpx2_line() -> None:
pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
content = pyproject.read_text(encoding="utf-8")
assert '"starlette>=0.46.0,<1.0.0"' in content
def test_readiness_gate_treats_deprecation_warnings_as_errors() -> None:
script = Path(__file__).resolve().parents[2] / "scripts" / "run_readiness_check.sh"
content = script.read_text(encoding="utf-8")
@@ -18,6 +18,9 @@ def test_api_contract_audit_checks_openapi_against_docs() -> None:
content = script.read_text(encoding="utf-8")
assert "create_app" in content
assert ".openapi()" in content
assert 'schema.get("paths", {})' in content
assert "for route in app.routes" not in content
assert "docs/API_CONTRACTS.md" in content
assert "Missing documented API route" in content
assert "Documented API route is not implemented" in content
@@ -8,6 +8,7 @@ from fastapi.testclient import TestClient
from geoalchemy2.shape import from_shape
from shapely.geometry import Polygon, box
from app.core.errors import AppError
from app.main import app
from app.db.session import get_db
from app.models import AnalysisRun, Dataset, Detection, Job, Metric, Project, QualityCheck, VectorFeature
@@ -276,6 +277,54 @@ def test_detection_qa_no_match_case_persists_zero_scores() -> None:
assert result["f1_score"] == 0.0
def test_configured_yolo_qa_requires_persisted_tile_manifest_provenance() -> None:
project_id = uuid4()
dataset_id = uuid4()
reference_dataset_id = uuid4()
analysis_run_id = uuid4()
detection = _detection(project_id, dataset_id, analysis_run_id)
reference_dataset = Dataset(
id=reference_dataset_id,
project_id=project_id,
name="reference.geojson",
dataset_type="vector",
source="manual",
dataset_role="reference",
)
reference_feature = VectorFeature(
id=uuid4(),
dataset_id=reference_dataset_id,
feature_class="building",
geometry=from_shape(box(4.0, 51.0, 4.1, 51.1), srid=4326),
)
db = FakeSession(
objects={
(AnalysisRun, analysis_run_id): AnalysisRun(
id=analysis_run_id,
project_id=project_id,
dataset_id=dataset_id,
analysis_type="detection",
status="success",
model_name="yolo-configured",
parameters_json={"model_id": "yolo-configured"},
),
(Dataset, reference_dataset_id): reference_dataset,
},
query_rows={Detection: [detection], VectorFeature: [reference_feature]},
)
with pytest.raises(AppError) as exc_info:
DetectionService.compare_detections_with_reference(
db=db,
analysis_run_id=analysis_run_id,
reference_dataset_id=reference_dataset_id,
iou_threshold=0.5,
)
assert exc_info.value.code == "DETECTION_QA_COVERAGE_UNAVAILABLE"
assert db.added == []
def _coverage_manifest(tmp_path, dataset_id, bounds=(-1.0, -1.0, 3.0, 3.0)):
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(
+45 -1
View File
@@ -7587,7 +7587,8 @@ Open:
## Local validation
- `bash scripts/run_readiness_check.sh` passed end to end.
- Full backend suite passed: `506` tests.
- Full backend suite passed: `508` tests after the final coverage, manifest
provenance and framework-contract regression additions.
- API contract audit passed with `81` implemented routes and the two documented
non-envelope endpoints.
- Alembic reports one head: `202606120900`.
@@ -7599,3 +7600,46 @@ Open:
- Deploy Sprint 184 to Tower, rerun QA for the persisted Mol-center analysis
run and verify both persisted coverage evidence and Detection Lab rendering
against live PostGIS before making any model-training decision.
## Tower and live Mol evidence
- Pushed commit `948e50b` and rebuilt the all-in-one runtime at
`http://192.168.10.150:1202`. PostGIS `3.6`, required schema/indexes,
database collation, Alembic head `202606120900`, frontend proxy, API proxy
and icon checks passed.
- Reused persisted analysis run `97c204cc-bc75-4139-aab5-25397dd0d87e`,
raster `e9a292e2-57d2-4a85-9df6-32580ba6ea98` and GRB reference dataset
`bfc83882-2acc-470d-baec-ee828b4428f8`; no duplicate inference or fake data
was produced.
- The persisted EPSG:31370 tile manifest contained one inference tile. All 36
detections were evaluated; one was clipped at the tile boundary. Of 374
persisted reference footprints, 304 intersected coverage, 70 were excluded
outside coverage and 34 were clipped at the boundary.
- Quality check `a25290ed-14ea-4c84-aff7-20672f568b1c` persisted 19 strict
footprint-IoU matches, 17 false positives and 285 false negatives. Its six
canonical Metric rows contain precision `0.527778`, recall `0.0625`, F1
`0.111765`, mean IoU `0.599302` and the two error counts.
- The diagnostic-only reference-envelope pass found 33 matches and identifies
14 possible box-to-footprint matching artifacts. Those values are persisted
only in findings and do not alter canonical Metric rows.
- Internal-browser validation reran the comparison from Detection Lab and
displayed `304 of 374 reference features evaluated`, coverage exclusions,
boundary clipping and the explicitly labelled diagnostic. The deployed page
produced no console warnings or errors for the live Tower URL.
- A later clean framework resolution exposed FastAPI `0.139.0`'s grouped
top-level router representation: `app.routes` contains 17 groups while the
canonical OpenAPI schema still contains all 81 operations. Updated the API
contract audit to read the OpenAPI path map and added a regression guard so
clean framework installations cannot produce a false 81-route drift report.
- Starlette `1.3.1` also deprecates the existing `httpx` TestClient path in
favor of `httpx2`. Kept Starlette on the still-supported
`>=0.46.0,<1.0.0` line until that test-client migration receives a dedicated
compatibility pass; the release gate consequently remains warning-free.
## Decision
- Sprint 184 is operationally complete. Coverage bias is now explicit and the
observed 14-match geometry gap confirms that model quality must not be judged
from envelope diagnostics. The next safe model step is an evidence-led
footprint-label/matching review, followed by a fresh bounded Mol multi-zone
benchmark before any activation or retraining decision.
@@ -943,7 +943,7 @@ export function DetectionLab({
</strong>
<p>
{detectionQaResult.coverage.applied
? `${detectionQaResult.coverage.reference_excluded_outside_count} outside coverage, ${detectionQaResult.coverage.reference_clipped_boundary_count} clipped at the boundary, ${detectionQaResult.coverage.tile_count} tiles.`
? `${detectionQaResult.coverage.reference_excluded_outside_count} outside coverage, ${detectionQaResult.coverage.reference_clipped_boundary_count} clipped at the boundary, ${detectionQaResult.coverage.tile_count} ${detectionQaResult.coverage.tile_count === 1 ? 'tile' : 'tiles'}.`
: 'This run uses the complete selected reference population.'}
</p>
</div>
+7 -7
View File
@@ -26,15 +26,15 @@ def _load_app_routes() -> set[tuple[str, str]]:
sys.path.insert(0, str(BACKEND))
from app.main import create_app
app = create_app()
schema = create_app().openapi()
routes: set[tuple[str, str]] = set()
for route in app.routes:
path = getattr(route, "path", None)
methods = getattr(route, "methods", set())
if not path or path in IGNORED_OPENAPI_PATHS:
for path, path_item in schema.get("paths", {}).items():
if path in IGNORED_OPENAPI_PATHS or not isinstance(path_item, dict):
continue
for method in sorted(methods - {"HEAD", "OPTIONS"}):
routes.add((method, path))
for method in path_item:
normalized_method = method.upper()
if normalized_method in {"GET", "POST", "PATCH", "DELETE"}:
routes.add((normalized_method, path))
return routes