210 lines
6.6 KiB
Python
210 lines
6.6 KiB
Python
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from django.core.management import CommandError
|
|
|
|
from apps.jobs.models import GeocodeLocationLookup
|
|
from apps.jobs.services.geocoding import (
|
|
CsvGeocodeProvider,
|
|
GeoPoint,
|
|
LocationMatch,
|
|
LocationMatchResult,
|
|
import_csv_geodata,
|
|
parse_belgian_location_query,
|
|
resolve_location,
|
|
validate_csv_geodata,
|
|
)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_parse_belgian_location_query():
|
|
postal, municipality = parse_belgian_location_query("Gent, 9000")
|
|
assert postal == "9000"
|
|
assert municipality == "gent"
|
|
|
|
postal, municipality = parse_belgian_location_query("1050 Ixelles")
|
|
assert postal == "1050"
|
|
assert municipality == "ixelles"
|
|
|
|
postal, municipality = parse_belgian_location_query("Brussel")
|
|
assert postal is None
|
|
assert municipality == "brussel"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_geocode_provider_resolves_postal_and_municipality_exactly():
|
|
GeocodeLocationLookup.objects.create(
|
|
source_name="fixture",
|
|
source_version="2026-01-01",
|
|
source_license_name="",
|
|
source_license_url="",
|
|
source_metadata={},
|
|
query_kind="postal",
|
|
query_value="9000",
|
|
postal_code="9000",
|
|
municipality="Gent",
|
|
region="Vlaams-Brabant",
|
|
latitude=Decimal("51.0500"),
|
|
longitude=Decimal("3.7300"),
|
|
confidence=Decimal("0.90"),
|
|
)
|
|
GeocodeLocationLookup.objects.create(
|
|
source_name="fixture",
|
|
source_version="2026-01-01",
|
|
source_license_name="",
|
|
source_license_url="",
|
|
source_metadata={},
|
|
query_kind="municipality",
|
|
query_value="gent",
|
|
postal_code="9000",
|
|
municipality="Gent",
|
|
region="Vlaams-Brabant",
|
|
latitude=Decimal("51.0500"),
|
|
longitude=Decimal("3.7300"),
|
|
confidence=Decimal("0.90"),
|
|
)
|
|
|
|
provider = CsvGeocodeProvider(source_name="fixture", source_version="2026-01-01")
|
|
result = resolve_location("9000 Gent", provider)
|
|
assert result.ambiguous is False
|
|
assert result.location is not None
|
|
assert result.location.postal_code == "9000"
|
|
assert result.location.municipality == "Gent"
|
|
assert result.location.point == GeoPoint(latitude=51.05, longitude=3.73)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_geocode_provider_marks_ambiguous_as_unknown():
|
|
GeocodeLocationLookup.objects.create(
|
|
source_name="fixture",
|
|
source_version="2026-01-01",
|
|
source_license_name="",
|
|
source_license_url="",
|
|
source_metadata={},
|
|
query_kind="municipality",
|
|
query_value="brussel",
|
|
postal_code="1000",
|
|
municipality="Brussel",
|
|
region="Brussels Hoofdstedelijk Gewest",
|
|
latitude=Decimal("50.8500"),
|
|
longitude=Decimal("4.3500"),
|
|
confidence=Decimal("0.85"),
|
|
)
|
|
GeocodeLocationLookup.objects.create(
|
|
source_name="fixture",
|
|
source_version="2026-01-01",
|
|
source_license_name="",
|
|
source_license_url="",
|
|
source_metadata={},
|
|
query_kind="municipality",
|
|
query_value="brussel",
|
|
postal_code="1000-200",
|
|
municipality="Brussel",
|
|
region="Brussels Hoofdstedelijk Gewest",
|
|
latitude=Decimal("50.8400"),
|
|
longitude=Decimal("4.3400"),
|
|
confidence=Decimal("0.85"),
|
|
)
|
|
|
|
provider = CsvGeocodeProvider(source_name="fixture", source_version="2026-01-01")
|
|
result = resolve_location("Brussel", provider)
|
|
assert result.ambiguous is True
|
|
assert result.location is None
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_validate_csv_geodata_flags_duplicates_and_invalid_coordinates(tmp_path: Path):
|
|
source = tmp_path / "geodata.csv"
|
|
source.write_text(
|
|
"postal_code,municipality,region,latitude,longitude\n"
|
|
"9000,Gent,Vlaams-Brabant,51.05,3.73\n"
|
|
"9000,Gent,Vlaams-Brabant,51.05,3.74\n"
|
|
)
|
|
with pytest.raises(CommandError, match="dubbel record"):
|
|
validate_csv_geodata(source)
|
|
|
|
source.write_text(
|
|
"postal_code,municipality,region,latitude,longitude\n9000,Gent,Vlaams-Brabant,99,3.73\n"
|
|
)
|
|
with pytest.raises(CommandError, match="latitude buiten bereik"):
|
|
validate_csv_geodata(source)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_import_csv_geodata_is_atomic_without_replace(tmp_path: Path):
|
|
source = tmp_path / "geodata.csv"
|
|
source.write_text(
|
|
"postal_code,municipality,region,latitude,longitude\n"
|
|
"9000,Gent,Vlaams-Brabant,51.05,3.73\n"
|
|
"1040,Brussel,Brussels Hoofdstedelijk Gewest,50.85,4.35\n"
|
|
)
|
|
GeocodeLocationLookup.objects.create(
|
|
source_name="fixture",
|
|
source_version="manual",
|
|
source_license_name="",
|
|
source_license_url="",
|
|
source_metadata={},
|
|
query_kind="municipality",
|
|
query_value="gent",
|
|
postal_code="9000",
|
|
municipality="Gent",
|
|
region="Vlaams-Brabant",
|
|
latitude=Decimal("51.05"),
|
|
longitude=Decimal("3.73"),
|
|
confidence=Decimal("1.00"),
|
|
)
|
|
GeocodeLocationLookup.objects.create(
|
|
source_name="fixture",
|
|
source_version="manual",
|
|
source_license_name="",
|
|
source_license_url="",
|
|
source_metadata={},
|
|
query_kind="postal",
|
|
query_value="9000",
|
|
postal_code="9000",
|
|
municipality="Gent",
|
|
region="Vlaams-Brabant",
|
|
latitude=Decimal("51.05"),
|
|
longitude=Decimal("3.73"),
|
|
confidence=Decimal("1.00"),
|
|
)
|
|
|
|
with pytest.raises(CommandError, match="overschrijven zonder --replace"):
|
|
import_csv_geodata(source, source_name="fixture", source_version="manual")
|
|
assert GeocodeLocationLookup.objects.count() == 2
|
|
|
|
|
|
def test_resolve_location_keeps_ambiguous_unknown() -> None:
|
|
class StubProvider:
|
|
def resolve(self, query: str):
|
|
return [
|
|
LocationMatch(
|
|
postal_code="1000",
|
|
municipality="Brussel",
|
|
region="Brussels",
|
|
point=None,
|
|
confidence=0.85,
|
|
source="csv",
|
|
source_version="manual",
|
|
metadata={},
|
|
),
|
|
LocationMatch(
|
|
postal_code="1001",
|
|
municipality="Brussel",
|
|
region="Brussels",
|
|
point=None,
|
|
confidence=0.85,
|
|
source="csv",
|
|
source_version="manual",
|
|
metadata={},
|
|
),
|
|
]
|
|
|
|
result = resolve_location("Brussel", StubProvider())
|
|
assert result.ambiguous is True
|
|
assert isinstance(result, LocationMatchResult)
|
|
assert result.location is None
|