Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
"""The object count and the area metric must describe the same selection.
|
||||
|
||||
``intersection_area`` clips a feature to the drawn rectangle, but the object
|
||||
count treated any feature that merely touches the rectangle as wholly inside.
|
||||
For a rectangle across a built-up area that overstates the count at every
|
||||
edge, and the two headline numbers on the same panel then describe different
|
||||
populations: "1.000 gebouwen" next to the clipped area of rather fewer.
|
||||
|
||||
The count now reports how many features lie entirely inside and how many are
|
||||
cut by the selection edge, and is marked as an estimate when any are.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.services.vector_feature_service import VectorFeatureService
|
||||
|
||||
|
||||
def test_a_count_without_partial_features_is_exact() -> None:
|
||||
disclosure = VectorFeatureService.count_disclosure(
|
||||
total_feature_count=120,
|
||||
fully_covered_feature_count=120,
|
||||
)
|
||||
|
||||
assert disclosure["partially_covered_feature_count"] == 0
|
||||
assert disclosure["is_estimate"] is False
|
||||
assert disclosure["warning"] is None
|
||||
|
||||
|
||||
def test_features_cut_by_the_selection_edge_are_reported() -> None:
|
||||
disclosure = VectorFeatureService.count_disclosure(
|
||||
total_feature_count=120,
|
||||
fully_covered_feature_count=98,
|
||||
)
|
||||
|
||||
assert disclosure["partially_covered_feature_count"] == 22
|
||||
assert disclosure["is_estimate"] is True
|
||||
assert "22" in disclosure["warning"]
|
||||
assert "rand" in disclosure["warning"]
|
||||
|
||||
|
||||
def test_a_selection_of_only_partial_features_is_still_coherent() -> None:
|
||||
disclosure = VectorFeatureService.count_disclosure(
|
||||
total_feature_count=3,
|
||||
fully_covered_feature_count=0,
|
||||
)
|
||||
|
||||
assert disclosure["partially_covered_feature_count"] == 3
|
||||
assert disclosure["is_estimate"] is True
|
||||
|
||||
|
||||
def test_an_empty_selection_makes_no_claim() -> None:
|
||||
disclosure = VectorFeatureService.count_disclosure(
|
||||
total_feature_count=0,
|
||||
fully_covered_feature_count=0,
|
||||
)
|
||||
|
||||
assert disclosure["partially_covered_feature_count"] == 0
|
||||
assert disclosure["is_estimate"] is False
|
||||
assert disclosure["warning"] is None
|
||||
|
||||
|
||||
def test_a_preclipped_full_area_selection_has_no_edge_effect() -> None:
|
||||
"""Selecting the whole work area cuts nothing; the count is exact."""
|
||||
|
||||
disclosure = VectorFeatureService.count_disclosure(
|
||||
total_feature_count=500,
|
||||
fully_covered_feature_count=None,
|
||||
)
|
||||
|
||||
assert disclosure["partially_covered_feature_count"] is None
|
||||
assert disclosure["is_estimate"] is False
|
||||
assert disclosure["warning"] is None
|
||||
|
||||
|
||||
def test_an_inconsistent_covered_count_never_produces_a_negative() -> None:
|
||||
disclosure = VectorFeatureService.count_disclosure(
|
||||
total_feature_count=10,
|
||||
fully_covered_feature_count=14,
|
||||
)
|
||||
|
||||
assert disclosure["partially_covered_feature_count"] == 0
|
||||
assert disclosure["is_estimate"] is False
|
||||
|
||||
|
||||
class _ScalarQuery:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def filter(self, *args): # noqa: ANN002, ARG002
|
||||
return self
|
||||
|
||||
def scalar(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class _SequenceSession:
|
||||
"""Answers the summary's scalar queries in order: covered count, then metrics."""
|
||||
|
||||
def __init__(self, values):
|
||||
self.values = iter(values)
|
||||
|
||||
def query(self, *args): # noqa: ANN002, ARG002
|
||||
return _ScalarQuery(next(self.values))
|
||||
|
||||
|
||||
def _buildings_dataset():
|
||||
from uuid import uuid4
|
||||
|
||||
from app.models import Dataset
|
||||
|
||||
return Dataset(
|
||||
id=uuid4(),
|
||||
project_id=uuid4(),
|
||||
name="grb-buildings.geojson",
|
||||
dataset_type="vector",
|
||||
dataset_role="reference",
|
||||
source_name="grb",
|
||||
reference_layer_name="buildings",
|
||||
source_metadata={"theme": "buildings"},
|
||||
)
|
||||
|
||||
|
||||
BBOX = {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"}
|
||||
|
||||
|
||||
def test_summary_reports_the_edge_cut_next_to_the_object_count() -> None:
|
||||
summary = VectorFeatureService.summarize_features_by_bbox(
|
||||
_SequenceSession([88, 125_000.0]),
|
||||
dataset=_buildings_dataset(),
|
||||
bbox=BBOX,
|
||||
total_feature_count=100,
|
||||
)
|
||||
|
||||
assert summary["feature_count"] == 100
|
||||
assert summary["fully_covered_feature_count"] == 88
|
||||
assert summary["partially_covered_feature_count"] == 12
|
||||
assert "12 van de 100" in summary["selection_edge_warning"]
|
||||
|
||||
count_metric = next(
|
||||
item for item in summary["metrics"] if item["aggregation_method"] == "feature_count"
|
||||
)
|
||||
assert count_metric["is_estimate"] is True
|
||||
assert "doorgesneden" in count_metric["warning"]
|
||||
|
||||
# The clipped area metric is exact and must not inherit the count's caveat.
|
||||
area_metric = next(
|
||||
item for item in summary["metrics"] if item["aggregation_method"] == "intersection_area"
|
||||
)
|
||||
assert area_metric["is_estimate"] is False
|
||||
|
||||
|
||||
def test_summary_stays_exact_when_the_selection_cuts_nothing() -> None:
|
||||
summary = VectorFeatureService.summarize_features_by_bbox(
|
||||
_SequenceSession([100, 125_000.0]),
|
||||
dataset=_buildings_dataset(),
|
||||
bbox=BBOX,
|
||||
total_feature_count=100,
|
||||
)
|
||||
|
||||
assert summary["partially_covered_feature_count"] == 0
|
||||
assert summary["selection_edge_warning"] is None
|
||||
Reference in New Issue
Block a user