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 "Paginering van gevonden objecten" in lab assert "Vorige resultatenpagina" in lab assert "Volgende resultatenpagina" in lab assert "Herkomst" in lab assert "Luchtbeeldtegel" in lab assert 'title={detection.source_tile_path ?? undefined}' not in lab assert "pagination-toolbar" in styles assert ".source-tile-cell" 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