Harden detection result review
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-13 13:31:43 +02:00
parent 6fe1bbd946
commit 4455e242c6
12 changed files with 795 additions and 22 deletions
@@ -0,0 +1,222 @@
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def _polygon(x: float, y: float, size: float) -> dict:
return {
"type": "Polygon",
"coordinates": [
[
[x, y],
[x + size, y],
[x + size, y + size],
[x, y + size],
[x, y],
]
],
}
def _evidence_feature(
role: str,
feature_id: str,
geometry: dict,
*,
tile_index: int = 1,
confidence: float | None = None,
) -> dict:
properties = {
"qa_evidence_role": role,
"candidate_feature_id": feature_id,
"feature_class": "building",
"tile_index": tile_index,
"analysis_run_id": "run-1",
"quality_check_id": "quality-1",
"calibration_threshold": 0.15,
"calibration_model_asset_id": "model-a",
}
if confidence is not None:
properties["candidate_confidence"] = confidence
return {
"type": "Feature",
"id": f"{role}:{feature_id}",
"properties": properties,
"geometry": geometry,
}
def _write_portfolio(
tmp_path: Path,
features: list[dict],
*,
declared_false_positives: int,
) -> Path:
evidence_dir = tmp_path / "samples" / "geel" / "evidence"
evidence_dir.mkdir(parents=True)
evidence_path = evidence_dir / "calibration_evidence.geojson"
evidence_path.write_text(
json.dumps({"type": "FeatureCollection", "features": features}),
encoding="utf-8",
)
portfolio_path = tmp_path / "calibration_evidence_portfolio.json"
portfolio_path.write_text(
json.dumps(
{
"model_asset_id": "model-a",
"model_sha256": "abc123",
"samples": [
{
"sample_slug": "geel",
"role_counts": {
"false_positive": declared_false_positives,
"match_candidate": 1,
},
"evidence_geojson_path": str(evidence_path),
}
],
}
),
encoding="utf-8",
)
return portfolio_path
def test_detection_results_table_uses_bounded_local_pagination() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
)
styles = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(
encoding="utf-8"
)
assert "DETECTION_PAGE_SIZE_OPTIONS" in lab
assert "visibleDetectionItems" in lab
assert "detectionItems.slice" in lab
assert "Detection result pagination" in lab
assert "Previous detection results page" in lab
assert "Next detection results page" in lab
assert "pagination-toolbar" in styles
assert "detectionItems.map((detection)" not in lab
def test_false_positive_audit_builds_reviewable_persisted_evidence(tmp_path: Path) -> None:
script = ROOT / "scripts" / "audit_detection_false_positive_evidence.py"
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(
encoding="utf-8"
)
assert script.exists()
assert "py_compile scripts/audit_detection_false_positive_evidence.py" in readiness
assert "COPY scripts/audit_detection_false_positive_evidence.py" in dockerfile
features = [
_evidence_feature(
"false_positive",
"fp-small",
_polygon(5.0, 51.2, 0.0001),
tile_index=4,
confidence=0.27,
),
_evidence_feature(
"false_positive",
"fp-large",
_polygon(5.001, 51.2, 0.0003),
tile_index=4,
confidence=0.81,
),
_evidence_feature(
"match_candidate",
"matched",
_polygon(5.002, 51.2, 0.0002),
tile_index=7,
),
]
portfolio_path = _write_portfolio(
tmp_path / "portfolio",
features,
declared_false_positives=2,
)
output_dir = tmp_path / "audit"
result = subprocess.run(
[
sys.executable,
str(script),
"--portfolio",
str(portfolio_path),
"--output-dir",
str(output_dir),
],
cwd=ROOT,
check=True,
capture_output=True,
text=True,
)
report = json.loads(
(output_dir / "detection_false_positive_audit.json").read_text(encoding="utf-8")
)
assert report["model_asset_id"] == "model-a"
assert report["model_sha256"] == "abc123"
assert report["false_positive_count"] == 2
assert report["candidate_count"] == 3
assert report["false_positive_rate"] == 2 / 3
assert report["confidence_coverage_count"] == 2
assert report["confidence"]["median"] == 0.54
assert report["source_tile_counts"] == {"geel:4": 2}
assert sum(bucket["count"] for bucket in report["area_buckets"].values()) == 2
assert report["samples"][0]["false_positive_area_m2"]["median"] > 0
assert report["recommendations"]
geojson = json.loads(
(output_dir / "false_positives.geojson").read_text(encoding="utf-8")
)
assert geojson["type"] == "FeatureCollection"
assert len(geojson["features"]) == 2
assert all(
feature["properties"]["qa_evidence_role"] == "false_positive"
for feature in geojson["features"]
)
assert all(feature["properties"]["sample_slug"] == "geel" for feature in geojson["features"])
assert all(feature["properties"]["area_m2"] > 0 for feature in geojson["features"])
assert "False-positive audit JSON" in result.stdout
assert (output_dir / "detection_false_positive_audit.md").is_file()
def test_false_positive_audit_rejects_declared_role_count_drift(tmp_path: Path) -> None:
script = ROOT / "scripts" / "audit_detection_false_positive_evidence.py"
portfolio_path = _write_portfolio(
tmp_path / "portfolio",
[
_evidence_feature(
"false_positive",
"fp-one",
_polygon(5.0, 51.2, 0.0001),
)
],
declared_false_positives=2,
)
result = subprocess.run(
[
sys.executable,
str(script),
"--portfolio",
str(portfolio_path),
"--output-dir",
str(tmp_path / "audit"),
],
cwd=ROOT,
check=False,
capture_output=True,
text=True,
)
assert result.returncode != 0
assert "declares 2 false positives but evidence contains 1" in result.stderr