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