Initial GeoIntel V1 foundation
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-16 23:36:32 +02:00
commit 6ea3586a3e
605 changed files with 45284 additions and 0 deletions
View File
+5
View File
@@ -0,0 +1,5 @@
from __future__ import annotations
from app.providers import base, fixture, grb, manual, osm, registry
__all__ = ["base", "fixture", "grb", "manual", "osm", "registry"]
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class ProviderCapability:
provider_name: str
display_name: str
authority_level: str
supported_layers: list[str]
supported_geometry_types: list[str]
supported_query_modes: list[str]
fetch_signature: str
configured: bool
status: str
limitation_message: str
attribution: str
license_note: str
not_configured_reason: str | None = None
def to_dict(self) -> dict[str, Any]:
return {
"provider_name": self.provider_name,
"display_name": self.display_name,
"authority_level": self.authority_level,
"supported_layers": self.supported_layers,
"supported_geometry_types": self.supported_geometry_types,
"supported_query_modes": self.supported_query_modes,
"fetch_signature": self.fetch_signature,
"configured": self.configured,
"status": self.status,
"limitation_message": self.limitation_message,
"attribution": self.attribution,
"license_note": self.license_note,
"not_configured_reason": self.not_configured_reason,
}
class BaseReferenceProvider:
def __init__(
self,
provider_name: str,
display_name: str,
authority_level: str,
supported_layers: list[str],
supported_geometry_types: list[str],
supported_query_modes: list[str],
fetch_signature: str,
limitation_message: str,
attribution: str,
license_note: str,
configured: bool = False,
) -> None:
self.provider_name = provider_name
self.display_name = display_name
self.authority_level = authority_level
self.supported_layers = supported_layers
self.supported_geometry_types = supported_geometry_types
self.supported_query_modes = supported_query_modes
self.fetch_signature = fetch_signature
self.limitation_message = limitation_message
self.attribution = attribution
self.license_note = license_note
self._configured = configured
@property
def capability(self) -> ProviderCapability:
return ProviderCapability(
provider_name=self.provider_name,
display_name=self.display_name,
authority_level=self.authority_level,
supported_layers=self.supported_layers,
supported_geometry_types=self.supported_geometry_types,
supported_query_modes=self.supported_query_modes,
fetch_signature=self.fetch_signature,
configured=self.is_configured,
status="configured" if self.is_configured else "not_configured",
limitation_message=self.limitation_message,
attribution=self.attribution,
license_note=self.license_note,
not_configured_reason=None if self.is_configured else "Provider integration is not configured yet",
)
@property
def is_configured(self) -> bool:
return self._configured
def fetch(self, project_id: str, area_id: str | None, layers: list[str]) -> dict[str, Any]:
del project_id, area_id, layers
return {
"provider": self.provider_name,
"status": "not_configured",
"message": "Provider integration is not configured yet",
}
+20
View File
@@ -0,0 +1,20 @@
from __future__ import annotations
from app.providers.base import BaseReferenceProvider
class FixtureProvider(BaseReferenceProvider):
def __init__(self) -> None:
super().__init__(
provider_name="fixture",
display_name="Fixture data",
authority_level="fixture",
supported_layers=["buildings", "roads", "water", "landuse", "custom"],
supported_geometry_types=["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"],
supported_query_modes=["fixture"],
fetch_signature="tests/fixtures and demo fixture upload flow",
limitation_message="Fixture provider represents local test/demo fixtures only.",
attribution="GeoIntel local fixtures",
license_note="Fixtures are for local development and tests; do not present them as official data.",
configured=True,
)
+20
View File
@@ -0,0 +1,20 @@
from __future__ import annotations
from app.providers.base import BaseReferenceProvider
class GRBProvider(BaseReferenceProvider):
def __init__(self) -> None:
super().__init__(
provider_name="grb",
display_name="GRB",
authority_level="authoritative",
supported_layers=["buildings", "roads", "parcels"],
supported_geometry_types=["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
supported_query_modes=["area"],
fetch_signature="POST /api/v1/external/grb/fetch",
limitation_message="GRB live WFS/download integration is not configured in Sprint 7B.",
attribution="Digitaal Vlaanderen - Basiskaart Vlaanderen (GRB)",
license_note="Use must follow Digitaal Vlaanderen open data and attribution terms.",
configured=False,
)
+20
View File
@@ -0,0 +1,20 @@
from __future__ import annotations
from app.providers.base import BaseReferenceProvider
class ManualProvider(BaseReferenceProvider):
def __init__(self) -> None:
super().__init__(
provider_name="manual",
display_name="Manual upload",
authority_level="manual",
supported_layers=["buildings", "roads", "water", "landuse", "custom"],
supported_geometry_types=["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"],
supported_query_modes=["upload"],
fetch_signature="POST /api/v1/projects/{project_id}/datasets/upload",
limitation_message="Manual provider data is supplied through the existing dataset upload flow.",
attribution="User supplied",
license_note="License and attribution must be supplied by the uploader in source metadata.",
configured=True,
)
+20
View File
@@ -0,0 +1,20 @@
from __future__ import annotations
from app.providers.base import BaseReferenceProvider
class OSMProvider(BaseReferenceProvider):
def __init__(self) -> None:
super().__init__(
provider_name="osm",
display_name="OpenStreetMap",
authority_level="contextual",
supported_layers=["buildings", "roads", "water", "landuse"],
supported_geometry_types=["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
supported_query_modes=["area"],
fetch_signature="POST /api/v1/external/osm/fetch",
limitation_message="OSM live Overpass/download integration is not configured in Sprint 7B.",
attribution="OpenStreetMap contributors",
license_note="OpenStreetMap data is available under ODbL; attribution is required.",
configured=False,
)
+141
View File
@@ -0,0 +1,141 @@
from __future__ import annotations
from pydantic import BaseModel
from app.core.errors import AppError
from app.providers.base import ProviderCapability
from app.providers.fixture import FixtureProvider
from app.providers.grb import GRBProvider
from app.providers.manual import ManualProvider
from app.providers.osm import OSMProvider
class ProviderDatasetMapping(BaseModel):
provider_name: str
dataset_role: str
source_name: str
reference_required: bool
write_path: str = "DatasetService"
class ProviderImportResult(BaseModel):
provider_name: str
status: str
message: str
requested_layers: list[str]
dataset_id: str | None = None
dataset_role: str | None = None
source_name: str | None = None
class ExternalProviderRegistry:
def __init__(self) -> None:
self.providers = {
"grb": GRBProvider(),
"osm": OSMProvider(),
"manual": ManualProvider(),
"fixture": FixtureProvider(),
}
def list_capabilities(self) -> list[ProviderCapability]:
return [provider.capability for provider in self.providers.values()]
def get(self, provider_name: str):
normalized = provider_name.strip().lower()
if normalized not in self.providers:
raise AppError(code="PROVIDER_NOT_FOUND", message="Provider not found", status_code=404)
return self.providers[normalized]
def fetch(self, provider_name: str, project_id: str, area_id: str | None, layers: list[str]) -> dict:
provider = self.get(provider_name)
return provider.fetch(project_id=project_id, area_id=area_id, layers=layers)
def dataset_mapping(self, provider_name: str, requested_dataset_role: str | None = None) -> ProviderDatasetMapping:
provider = self.get(provider_name)
if provider.provider_name == "osm":
dataset_role = "reference" if requested_dataset_role == "reference" else "source"
return ProviderDatasetMapping(
provider_name="osm",
dataset_role=dataset_role,
source_name="osm",
reference_required=requested_dataset_role == "reference",
)
return ProviderDatasetMapping(
provider_name=provider.provider_name,
dataset_role="reference",
source_name=provider.provider_name,
reference_required=True,
)
def import_contract(
self,
provider_name: str,
project_id: str,
area_id: str | None,
layers: list[str],
requested_dataset_role: str | None = None,
) -> ProviderImportResult:
del project_id, area_id
provider = self.get(provider_name)
mapping = self.dataset_mapping(provider.provider_name, requested_dataset_role=requested_dataset_role)
if provider.provider_name in {"grb", "osm"}:
return ProviderImportResult(
provider_name=provider.provider_name,
status="not_configured",
message=f"No live {provider.display_name} import is configured in Sprint 7B.",
requested_layers=layers,
dataset_role=mapping.dataset_role,
source_name=mapping.source_name,
)
if provider.provider_name == "manual":
return ProviderImportResult(
provider_name="manual",
status="upload_flow_required",
message="Manual provider data must use the existing dataset upload/reference flow.",
requested_layers=layers,
dataset_role=mapping.dataset_role,
source_name=mapping.source_name,
)
return ProviderImportResult(
provider_name="fixture",
status="fixture_flow_required",
message="Fixture provider data must use checked-in demo/test fixture flows.",
requested_layers=layers,
dataset_role=mapping.dataset_role,
source_name=mapping.source_name,
)
_registry = ExternalProviderRegistry()
def list_provider_capabilities() -> list[ProviderCapability]:
return _registry.list_capabilities()
def get_provider(provider_name: str):
return _registry.get(provider_name)
def fetch_provider_data(provider_name: str, project_id: str, area_id: str | None, layers: list[str]) -> dict:
return _registry.fetch(provider_name, project_id, area_id, layers)
def get_provider_dataset_mapping(provider_name: str, requested_dataset_role: str | None = None) -> ProviderDatasetMapping:
return _registry.dataset_mapping(provider_name, requested_dataset_role=requested_dataset_role)
def import_provider_dataset(
provider_name: str,
project_id: str,
area_id: str | None,
layers: list[str],
requested_dataset_role: str | None = None,
) -> ProviderImportResult:
return _registry.import_contract(
provider_name=provider_name,
project_id=project_id,
area_id=area_id,
layers=layers,
requested_dataset_role=requested_dataset_role,
)