from __future__ import annotations from uuid import uuid4 import pytest from pyproj import Transformer from shapely.geometry import box from app.core.errors import AppError from app.services.detection_qa_service import DetectionQaService from app.services.qa_service import QaService def test_tile_coverage_transforms_projected_manifest_bounds_to_epsg4326() -> None: dataset_id = uuid4() to_lambert = Transformer.from_crs("EPSG:4326", "EPSG:31370", always_xy=True) left, bottom = to_lambert.transform(5.11, 51.18) right, top = to_lambert.transform(5.13, 51.20) manifest = { "source_dataset_id": str(dataset_id), "crs": "EPSG:31370", "tiles": [{"bounds": [left, bottom, right, top], "crs": "EPSG:31370"}], } coverage = DetectionQaService.build_tile_coverage( manifest, manifest_path="/app/storage/tiles/manifest.json", expected_dataset_id=dataset_id, ) min_x, min_y, max_x, max_y = coverage.geometry.bounds assert min_x == pytest.approx(5.11, abs=0.001) assert min_y == pytest.approx(51.18, abs=0.001) assert max_x == pytest.approx(5.13, abs=0.001) assert max_y == pytest.approx(51.20, abs=0.001) assert coverage.tile_count == 1 def test_tile_coverage_rejects_manifest_for_different_dataset() -> None: manifest = { "source_dataset_id": str(uuid4()), "crs": "EPSG:4326", "tiles": [{"bounds": [5.0, 51.0, 5.1, 51.1]}], } with pytest.raises(AppError) as exc_info: DetectionQaService.build_tile_coverage( manifest, manifest_path="/app/storage/tiles/manifest.json", expected_dataset_id=uuid4(), ) assert exc_info.value.code == "DETECTION_QA_COVERAGE_MISMATCH" def test_coverage_filter_reports_outside_and_boundary_clipped_population() -> None: dataset_id = uuid4() coverage = DetectionQaService.build_tile_coverage( { "source_dataset_id": str(dataset_id), "crs": "EPSG:4326", "tiles": [{"bounds": [0.0, 0.0, 1.0, 1.0]}], }, manifest_path="/app/storage/tiles/manifest.json", expected_dataset_id=dataset_id, ) population = DetectionQaService.filter_population( [ ({"id": "inside"}, box(0.1, 0.1, 0.2, 0.2)), ({"id": "crossing"}, box(0.8, 0.8, 1.2, 1.2)), ({"id": "outside"}, box(2.0, 2.0, 3.0, 3.0)), ], coverage, ) assert population.raw_count == 3 assert population.evaluated_count == 2 assert population.excluded_outside_count == 1 assert population.clipped_boundary_count == 1 assert population.geometries[1][1].bounds == pytest.approx((0.8, 0.8, 1.0, 1.0)) def test_coverage_filter_preserves_prefiltered_database_population_count() -> None: dataset_id = uuid4() coverage = DetectionQaService.build_tile_coverage( { "source_dataset_id": str(dataset_id), "crs": "EPSG:4326", "tiles": [{"bounds": [0.0, 0.0, 1.0, 1.0]}], }, manifest_path="/app/storage/tiles/manifest.json", expected_dataset_id=dataset_id, ) population = DetectionQaService.filter_population( [ ({"id": "inside"}, box(0.1, 0.1, 0.2, 0.2)), ({"id": "crossing"}, box(0.8, 0.8, 1.2, 1.2)), ], coverage, raw_count=3, ) assert population.raw_count == 3 assert population.evaluated_count == 2 assert population.excluded_outside_count == 1 assert population.clipped_boundary_count == 1 def test_iou_matching_keeps_exact_results_with_many_spatially_disjoint_references() -> None: references = [({"id": f"outside-{index}"}, box(index + 10, 10, index + 10.5, 10.5)) for index in range(100)] references.append(({"id": "match"}, box(0.0, 0.0, 1.0, 1.0))) evidence = QaService._match_io_u_evidence( [({"id": "candidate"}, box(0.0, 0.0, 1.0, 1.0))], references, 0.5, ) assert evidence.matches == 1 assert evidence.false_positives == 0 assert evidence.false_negatives == 100 assert evidence.match_evidence[0]["reference_feature_id"] == "match"