"""Inference must refuse to guess a CRS. Detection QA rejects a tile without explicit CRS metadata, but the inference side silently assumed EPSG:4326. That produced geometry that renders as a plausible polygon in the wrong place, which is worse than a clear failure: "fail closed" is the stated rule for the runtime. """ from __future__ import annotations import pytest from app.core.errors import AppError from app.services.detection_georeferencing import ( pixel_bbox_to_epsg4326_polygon, pixel_points_to_epsg4326_polygon, ) from app.services.detection_service import DetectionService TILE_WITHOUT_CRS = { "bounds": [4.0, 51.0, 5.0, 52.0], "pixel_window": [0, 0, 100, 100], } def test_manifest_without_crs_is_rejected() -> None: with pytest.raises(AppError) as exc_info: DetectionService._require_manifest_crs({"tiles": [{"bounds": [0, 0, 1, 1]}]}) assert exc_info.value.code == "DETECTION_TILE_MANIFEST_INVALID" def test_manifest_crs_is_read_from_any_of_the_documented_keys() -> None: assert DetectionService._require_manifest_crs({"crs": "EPSG:31370"}) == "EPSG:31370" assert DetectionService._require_manifest_crs({"source_crs": "EPSG:31370"}) == "EPSG:31370" assert DetectionService._require_manifest_crs({"dataset_crs": "EPSG:3812"}) == "EPSG:3812" def test_bbox_georeferencing_requires_an_explicit_crs() -> None: with pytest.raises(AppError) as exc_info: pixel_bbox_to_epsg4326_polygon(bbox=[0.0, 0.0, 10.0, 10.0], tile=TILE_WITHOUT_CRS) assert exc_info.value.code == "DETECTION_TILE_CRS_REQUIRED" def test_mask_georeferencing_requires_an_explicit_crs() -> None: with pytest.raises(AppError) as exc_info: pixel_points_to_epsg4326_polygon( points=[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0]], tile=TILE_WITHOUT_CRS ) assert exc_info.value.code == "DETECTION_TILE_CRS_REQUIRED" def test_explicit_crs_on_the_tile_is_used() -> None: tile = {**TILE_WITHOUT_CRS, "crs": "EPSG:4326"} polygon = pixel_bbox_to_epsg4326_polygon(bbox=[0.0, 0.0, 50.0, 50.0], tile=tile) assert polygon.bounds == pytest.approx((4.0, 51.5, 4.5, 52.0)) def test_projected_bounds_are_reprojected_as_a_whole_rectangle() -> None: # Lambert 72 around Mol. All four corners must be transformed, otherwise a # rotated footprint is understated. bounds = DetectionService._bounds_to_epsg4326([200000.0, 200000.0, 201000.0, 201000.0], "EPSG:31370") assert bounds is not None min_x, min_y, max_x, max_y = bounds assert 4.0 < min_x < 6.0 assert 50.0 < min_y < 52.0 assert max_x > min_x and max_y > min_y