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, )