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,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.providers import base, fixture, grb, manual, osm, registry
|
||||
|
||||
__all__ = ["base", "fixture", "grb", "manual", "osm", "registry"]
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
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", "water", "parcels"],
|
||||
supported_geometry_types=["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
|
||||
supported_query_modes=["bbox", "persisted_area"],
|
||||
fetch_signature="POST /api/v1/projects/{project_id}/datasets/grb/acquire",
|
||||
limitation_message=(
|
||||
"Alleen expliciet begrensde selecties tot 20 km per zijde worden opgehaald. "
|
||||
"Volledige providerdownloads en onbeperkte queries zijn niet toegestaan."
|
||||
),
|
||||
attribution="Bron: Grootschalig Referentie Bestand Vlaanderen, Digitaal Vlaanderen",
|
||||
license_note="Hergebruik volgens de open-datavoorwaarden en bronvermelding van Digitaal Vlaanderen.",
|
||||
configured=True,
|
||||
)
|
||||
|
||||
def fetch(self, project_id: str, area_id: str | None, layers: list[str]) -> dict:
|
||||
del project_id, area_id, layers
|
||||
return {
|
||||
"provider": self.provider_name,
|
||||
"status": "bounded_request_required",
|
||||
"message": (
|
||||
"Use POST /api/v1/projects/{project_id}/datasets/grb/acquire with an EPSG:4326 "
|
||||
"bounding box and one governed product key."
|
||||
),
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,153 @@
|
||||
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 == "grb":
|
||||
return ProviderImportResult(
|
||||
provider_name="grb",
|
||||
status="bounded_request_required",
|
||||
message=(
|
||||
"Use the governed project GRB acquisition endpoint with an EPSG:4326 bounding box "
|
||||
"and one supported layer."
|
||||
),
|
||||
requested_layers=layers,
|
||||
dataset_role=mapping.dataset_role,
|
||||
source_name=mapping.source_name,
|
||||
)
|
||||
if provider.provider_name == "osm":
|
||||
return ProviderImportResult(
|
||||
provider_name=provider.provider_name,
|
||||
status="not_configured",
|
||||
message=f"No live {provider.display_name} import is configured.",
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user