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

This commit is contained in:
Jens
2026-08-31 21:56:53 +02:00
commit faeb58ef6d
1386 changed files with 263203 additions and 0 deletions
View File
+357
View File
@@ -0,0 +1,357 @@
from __future__ import annotations
from .common import (
ApiErrorEnvelope,
ApiErrorItem,
Envelope,
GeoJsonFeature,
GeoJsonFeatureCollection,
ItemList,
PaginationEnvelope,
)
from .coverage import (
CoverageBBox,
CoverageCatalogResponse,
CoverageResolutionItem,
CoverageResolveRequest,
CoverageResolveResponse,
CoverageSourceContract,
)
from .project import ProjectCreate, ProjectDeleteResult, ProjectList, ProjectRead, ProjectUpdate
from .area import AreaCreate, AreaList, AreaRead, AreaUpdate
from .analysis import ChangeDetectionRequest, ChangeDetectionSummary
from .dataset import DatasetCreateResponse, DatasetList
from .source_freshness import (
SourceFreshnessItem,
SourceFreshnessReport,
SourceFreshnessSummary,
SourceIntegritySummary,
)
from .source_catalog import (
SourceCatalogProbeItem,
SourceCatalogProbeReport,
SourceCatalogProbeSummary,
)
from .source_registry import (
DatasetProvenanceRead,
DatasetLineageEdgeRead,
DatasetQuarantineRead,
SourceRegistryDetailRead,
SourceRegistryRead,
SourceSnapshotRead,
)
from .grb_refresh import GrbRefreshLayerPlan, GrbRefreshPlan, GrbRefreshPlanSummary
from .grb import GrbAcquireRequest, GrbAcquisitionResult, GrbProductRead
from .official_vector import (
OfficialVectorAcquireRequest,
OfficialVectorAcquisitionResult,
OfficialVectorProductRead,
)
from .detection import (
DetectionListResponse,
DetectionModelCapability,
DetectionModelsResponse,
DetectionQaRequest,
DetectionRead,
DetectionRunListResponse,
DetectionRunRead,
DetectionRunRequest,
DetectionComparisonRequest,
DetectionComparisonResponse,
DetectionRunResponse,
ModelAssetListResponse,
ModelAssetRead,
YoloPreflightResponse,
)
from .detection_review import DetectionReviewList, DetectionReviewRead, DetectionReviewSummary, DetectionReviewUpsert
from .segmentation import (
SegmentationListResponse,
SegmentationModelCapability,
SegmentationModelsResponse,
SegmentationQaRequest,
SegmentationRead,
SegmentationRunListResponse,
SegmentationRunRead,
SegmentationRunRequest,
SegmentationRunResponse,
)
from .health import HealthResponse, SystemCapabilities
from .job import JobCreate, JobList, JobRead, JobStatus
from .orthophoto import OrthophotoAcquireRequest, OrthophotoAcquisitionResult, OrthophotoProductRead
from .dhmv import (
DhmvAcquireRequest,
DhmvAcquisitionResult,
DhmvProductRead,
TerrainMetric,
TerrainPartitionSelectionRequest,
TerrainSelectionRequest,
TerrainSelectionResponse,
TerrainSelectionSummary,
)
from .spw_terrain import (
SpwTerrainAcquireRequest,
SpwTerrainAcquisitionResult,
SpwTerrainProductRead,
)
from .flood_hazard import (
FloodHazardAcquireRequest,
FloodHazardAcquisitionResult,
FloodHazardMetric,
FloodHazardPartitionSelectionRequest,
FloodHazardProductRead,
FloodHazardSelectionRequest,
FloodHazardSelectionResponse,
FloodHazardSelectionSummary,
)
from .bathymetry import (
BathymetryPartitionFinalizeRequest,
BathymetryPartitionFinalizationResult,
BathymetryProfileAcquireRequest,
BathymetryProfileAcquisitionResult,
BathymetryRasterMetric,
BathymetryRasterSelectionRequest,
BathymetryRasterSelectionResponse,
BathymetryRasterSelectionSummary,
BathymetrySourceProbeRead,
BathymetrySourceRead,
MdkBathymetryAcquireRequest,
MdkBathymetryAcquisitionResult,
)
from .thematic_raster import (
ThematicRasterAcquireRequest,
ThematicRasterAcquisitionResult,
ThematicRasterMetric,
ThematicRasterProductRead,
ThematicRasterSelectionRequest,
ThematicRasterSelectionResponse,
ThematicRasterSelectionSummary,
)
from .external import (
ExternalFetchRequest,
ExternalFetchResponse,
ProviderCapabilitiesResponse,
ProviderCapabilityResponse,
ProviderImportRequest,
ProviderImportResponse,
ProviderLayersResponse,
ProviderStatusResponse,
)
from .export import (
ExportContentResponse,
ExportCreateResponse,
ExportListResponse,
ExportRead,
GeoJsonExportRequest,
MetadataExportRequest,
ReportExportRequest,
)
from .qa import (
AnalysisQaResponse,
QaProviderComparisonRequest,
QaProviderComparisonResult,
QualityEvidenceResponse,
)
from .operations import (
RasterClipRequest,
RasterIndexBaseRequest,
RasterMetadataResponse,
RasterNdviRequest,
RasterNdwiRequest,
RasterNdbiRequest,
RasterOperationResult,
RasterPreviewResponse,
RasterReprojectRequest,
RasterReprojectResponse,
RasterStatsResponse,
RasterTileManifest,
RasterTileManifestTile,
RasterTileRequest,
RasterTileResponse,
VectorBBoxResponse,
VectorBufferRequest,
VectorClipRequest,
VectorIntersectRequest,
VectorOperationRequest,
VectorOperationResult,
VectorSelectionBBox,
VectorSelectionDeriveRequest,
VectorSelectionRequest,
VectorSelectionResponse,
VectorSelectionMetric,
VectorSelectionSummary,
VectorStatsRequest,
VectorStatsResponse,
)
__all__ = [
"Envelope",
"ItemList",
"GeoJsonFeature",
"GeoJsonFeatureCollection",
"ApiErrorEnvelope",
"ApiErrorItem",
"PaginationEnvelope",
"CoverageBBox",
"CoverageCatalogResponse",
"CoverageResolutionItem",
"CoverageResolveRequest",
"CoverageResolveResponse",
"CoverageSourceContract",
"ProjectCreate",
"ProjectRead",
"ProjectUpdate",
"ProjectList",
"ProjectDeleteResult",
"AreaCreate",
"AreaRead",
"AreaUpdate",
"AreaList",
"ChangeDetectionRequest",
"ChangeDetectionSummary",
"DatasetCreateResponse",
"DatasetList",
"SourceFreshnessItem",
"SourceFreshnessReport",
"SourceFreshnessSummary",
"SourceIntegritySummary",
"SourceCatalogProbeItem",
"SourceCatalogProbeReport",
"SourceCatalogProbeSummary",
"SourceRegistryRead",
"SourceRegistryDetailRead",
"SourceSnapshotRead",
"DatasetLineageEdgeRead",
"DatasetQuarantineRead",
"DatasetProvenanceRead",
"GrbRefreshLayerPlan",
"GrbRefreshPlan",
"GrbRefreshPlanSummary",
"GrbAcquireRequest",
"GrbAcquisitionResult",
"GrbProductRead",
"OfficialVectorAcquireRequest",
"OfficialVectorAcquisitionResult",
"OfficialVectorProductRead",
"DetectionListResponse",
"DetectionModelCapability",
"DetectionModelsResponse",
"DetectionQaRequest",
"DetectionRead",
"DetectionRunListResponse",
"DetectionRunRead",
"DetectionRunRequest",
"DetectionComparisonRequest",
"DetectionComparisonResponse",
"DetectionRunResponse",
"ModelAssetListResponse",
"ModelAssetRead",
"YoloPreflightResponse",
"DetectionReviewList",
"DetectionReviewRead",
"DetectionReviewSummary",
"DetectionReviewUpsert",
"SegmentationListResponse",
"SegmentationModelCapability",
"SegmentationModelsResponse",
"SegmentationQaRequest",
"SegmentationRead",
"SegmentationRunListResponse",
"SegmentationRunRead",
"SegmentationRunRequest",
"SegmentationRunResponse",
"HealthResponse",
"SystemCapabilities",
"JobCreate",
"JobList",
"JobRead",
"JobStatus",
"OrthophotoAcquireRequest",
"OrthophotoAcquisitionResult",
"OrthophotoProductRead",
"DhmvAcquireRequest",
"DhmvAcquisitionResult",
"DhmvProductRead",
"SpwTerrainAcquireRequest",
"SpwTerrainAcquisitionResult",
"SpwTerrainProductRead",
"TerrainMetric",
"TerrainPartitionSelectionRequest",
"TerrainSelectionRequest",
"TerrainSelectionResponse",
"TerrainSelectionSummary",
"FloodHazardAcquireRequest",
"FloodHazardAcquisitionResult",
"FloodHazardMetric",
"FloodHazardPartitionSelectionRequest",
"FloodHazardProductRead",
"FloodHazardSelectionRequest",
"FloodHazardSelectionResponse",
"FloodHazardSelectionSummary",
"BathymetryProfileAcquireRequest",
"BathymetryProfileAcquisitionResult",
"BathymetryRasterMetric",
"BathymetryRasterSelectionRequest",
"BathymetryRasterSelectionResponse",
"BathymetryRasterSelectionSummary",
"BathymetryPartitionFinalizeRequest",
"BathymetryPartitionFinalizationResult",
"BathymetrySourceProbeRead",
"BathymetrySourceRead",
"MdkBathymetryAcquireRequest",
"MdkBathymetryAcquisitionResult",
"ThematicRasterAcquireRequest",
"ThematicRasterAcquisitionResult",
"ThematicRasterMetric",
"ThematicRasterProductRead",
"ThematicRasterSelectionRequest",
"ThematicRasterSelectionResponse",
"ThematicRasterSelectionSummary",
"VectorBBoxResponse",
"VectorClipRequest",
"VectorBufferRequest",
"VectorIntersectRequest",
"VectorOperationRequest",
"VectorOperationResult",
"VectorSelectionBBox",
"VectorSelectionDeriveRequest",
"VectorSelectionRequest",
"VectorSelectionResponse",
"VectorSelectionMetric",
"VectorSelectionSummary",
"RasterClipRequest",
"RasterStatsResponse",
"RasterReprojectRequest",
"RasterReprojectResponse",
"RasterTileRequest",
"RasterMetadataResponse",
"RasterOperationResult",
"RasterPreviewResponse",
"RasterTileManifestTile",
"RasterTileManifest",
"RasterTileResponse",
"RasterIndexBaseRequest",
"RasterNdviRequest",
"RasterNdwiRequest",
"RasterNdbiRequest",
"VectorStatsRequest",
"VectorStatsResponse",
"ExternalFetchRequest",
"ExternalFetchResponse",
"ProviderCapabilitiesResponse",
"ProviderCapabilityResponse",
"ProviderImportRequest",
"ProviderImportResponse",
"ProviderLayersResponse",
"ProviderStatusResponse",
"GeoJsonExportRequest",
"MetadataExportRequest",
"ReportExportRequest",
"ExportRead",
"ExportCreateResponse",
"ExportListResponse",
"ExportContentResponse",
"QaProviderComparisonRequest",
"QaProviderComparisonResult",
"AnalysisQaResponse",
"QualityEvidenceResponse",
]
+46
View File
@@ -0,0 +1,46 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
from app.schemas.operations import VectorSelectionBBox
class ChangeDetectionRequest(BaseModel):
source_dataset_id: UUID
target_dataset_id: UUID
iou_threshold: float = Field(default=0.8, ge=0.0, le=1.0)
# Below this the two footprints are separate objects rather than one that
# was redrawn; between the two thresholds the change class is "modified".
modified_threshold: float = Field(default=0.3, ge=0.0, le=1.0)
include_unchanged: bool = True
# Without a selection the comparison covers both datasets in full, which is
# rarely the question and never a response a map can draw.
bbox: VectorSelectionBBox | None = None
area_id: UUID | None = None
preview_limit: int = Field(default=2_000, ge=1, le=20_000)
class ChangeDetectionSummary(BaseModel):
source_dataset_id: UUID
target_dataset_id: UUID
source_feature_count: int
target_feature_count: int
added_count: int
removed_count: int
# A footprint that was redrawn rather than demolished and rebuilt. Without
# this class it appeared as one removal plus one addition.
modified_count: int = 0
unchanged_count: int
iou_threshold: float
modified_iou_threshold: float | None = None
selection_area_id: UUID | None = None
# Counts describe the whole selection; the GeoJSON is capped so a regional
# comparison does not return both datasets in one response.
preview_limit: int | None = None
preview_truncated: bool = False
warnings: list[str] = Field(default_factory=list)
generated_at: datetime
geojson: dict
+75
View File
@@ -0,0 +1,75 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
from app.schemas.operations import VectorSelectionBBox
class AoiOperationCreate(BaseModel):
area_id: UUID | None = None
bbox: VectorSelectionBBox | None = None
operation_type: str = Field(min_length=1, max_length=128)
provider_key: str = Field(min_length=1, max_length=120)
product_key: str = Field(min_length=1, max_length=120)
coverage_zone: str | None = Field(default=None, max_length=64)
max_partition_side_m: float | None = Field(default=None, gt=0, le=60_000)
max_attempts: int = Field(default=3, ge=1, le=10)
parameters_json: dict = Field(default_factory=dict)
class AoiPartitionRead(BaseModel):
id: UUID
partition_key: str
provider_key: str
product_key: str
ordinal: int
status: str
attempt_count: int
max_attempts: int
checkpoint_json: dict | None = None
result_json: dict | None = None
error_message: str | None = None
model_config = {"from_attributes": True}
class AoiOperationRead(BaseModel):
id: UUID
project_id: UUID
area_id: UUID | None = None
parent_job_id: UUID | None = None
operation_type: str
status: str
request_json: dict
plan_json: dict
result_json: dict | None = None
error_message: str | None = None
progress: float
partition_counts: dict[str, int]
partitions: list[AoiPartitionRead] = Field(default_factory=list)
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
class AoiOperationList(BaseModel):
items: list[AoiOperationRead]
total: int
class AoiPartitionCheckpoint(BaseModel):
checkpoint_json: dict = Field(default_factory=dict)
class AoiPartitionComplete(BaseModel):
result_json: dict = Field(default_factory=dict)
skipped: bool = False
class AoiPartitionFail(BaseModel):
error_message: str = Field(min_length=1, max_length=4000)
retryable: bool = True
details: dict = Field(default_factory=dict)
+55
View File
@@ -0,0 +1,55 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel
class AreaCreate(BaseModel):
name: str
geometry: dict
crs: str | None = "EPSG:4326"
class AreaUpdate(BaseModel):
name: str | None = None
geometry: dict | None = None
crs: str | None = None
class AreaRead(BaseModel):
id: UUID
project_id: UUID
name: str
original_crs: str | None
area_m2: float | None
created_at: datetime | None = None
geometry_type: str | None = None
geometry: dict | None = None
model_config = {"from_attributes": True}
class AreaListItem(AreaRead):
pass
class AreaList(BaseModel):
items: list[AreaRead]
total: int
limit: int
offset: int
class MunicipalitySearchItem(BaseModel):
niscode: str
name: str
name_nl: str | None = None
name_fr: str | None = None
name_de: str | None = None
class MunicipalitySearchList(BaseModel):
items: list[MunicipalitySearchItem]
total: int
+93
View File
@@ -0,0 +1,93 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, Field
from app.schemas.operations import VectorSelectionBBox
class AssistantChatMessage(BaseModel):
role: Literal["user", "assistant"]
content: str = Field(min_length=1, max_length=4_000)
class AssistantQueryRequest(BaseModel):
question: str = Field(min_length=2, max_length=2_000)
model: str | None = Field(default=None, max_length=255)
bbox: VectorSelectionBBox | None = None
area_id: UUID | None = None
history: list[AssistantChatMessage] = Field(default_factory=list, max_length=8)
class AssistantModelRead(BaseModel):
name: str
size_bytes: int | None = None
parameter_size: str | None = None
quantization_level: str | None = None
capabilities: list[str] = Field(default_factory=list)
class AssistantModelList(BaseModel):
items: list[AssistantModelRead]
total: int
default_model: str | None = None
class AssistantStatus(BaseModel):
enabled: bool
reachable: bool
status: str
base_url: str
default_model: str | None = None
model_count: int = 0
limitation_message: str
class AssistantContextMetric(BaseModel):
theme: str
label: str
value: float
unit: str
source: str
dataset_id: UUID
observed_at: datetime | None = None
is_estimate: bool = False
class AssistantTemporalSeries(BaseModel):
temporal_series_key: str
label: str
source: str
first_year: int
last_year: int
observation_count: int
class AssistantEstimateDisclosure(BaseModel):
"""A value in the answer that the source itself calls an estimate.
Derived from metric metadata rather than from the generated sentences, so
the disclosure is present whatever wording the model chose.
"""
theme: str
label: str
unit: str
source: str
dataset_id: UUID
reason: str
class AssistantQueryResponse(BaseModel):
answer: str
model: str
scope_label: str
context_metrics: list[AssistantContextMetric]
temporal_series: list[AssistantTemporalSeries]
estimate_disclosures: list[AssistantEstimateDisclosure] = Field(default_factory=list)
source_dataset_ids: list[UUID]
warnings: list[str]
generated_at: datetime
+29
View File
@@ -0,0 +1,29 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, Field
from app.schemas.common import Envelope
class AuthLoginRequest(BaseModel):
username: str = Field(min_length=1, max_length=128)
password: str = Field(min_length=1, max_length=1024)
class AuthSession(BaseModel):
authentication_required: bool
authenticated: bool
username: str | None = None
expires_at: datetime | None = None
role: Literal["operator", "guest"] | None = None
guest_access_enabled: bool = False
authentik_enabled: bool = False
guest_project_id: UUID | None = None
class AuthSessionEnvelope(Envelope[AuthSession]):
pass
+173
View File
@@ -0,0 +1,173 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, Field, field_validator
from .operations import VectorSelectionBBox
class BathymetryProfileAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
force_refresh: bool = False
class BathymetrySourceRead(BaseModel):
key: str
display_name: str
owner: str
authority_level: Literal["authoritative", "contextual"]
geographic_coverage: str
data_kind: str
query_modes: list[str]
vertical_reference: str
horizontal_crs: str
native_resolution: str | None = None
integration_status: Literal["operational", "probe_only", "available_not_integrated", "catalog_only"]
acquisition_supported: bool
configured: bool
service_url: str | None = None
catalog_url: str
attribution: str
license_note: str
limitation_message: str
class BathymetryProfileAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
profile_count: int = Field(ge=0)
document_count: int = Field(ge=0)
structured_depth_count: int = Field(ge=0)
structured_width_count: int = Field(ge=0)
watercourse_count: int = Field(ge=0)
bbox_epsg4326: list[float]
clipped_to_area_id: UUID | None = None
measurement_date_min: str | None = None
measurement_date_max: str | None = None
attribution: str
limitation_message: str
class BathymetryPartitionFinalizeRequest(BaseModel):
partition_scope_key: str = Field(min_length=1, max_length=120, pattern=r"^[a-z0-9][a-z0-9_-]*$")
expected_area_ids: list[UUID] = Field(min_length=1, max_length=500)
dataset_ids: list[UUID] = Field(default_factory=list, max_length=500)
no_profile_area_ids: list[UUID] = Field(default_factory=list, max_length=500)
manifest_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
observed_at: datetime
@field_validator("expected_area_ids", "dataset_ids", "no_profile_area_ids")
@classmethod
def require_unique_ids(cls, value: list[UUID]) -> list[UUID]:
if len(value) != len(set(value)):
raise ValueError("Partition identifiers must be unique")
return value
class BathymetryPartitionFinalizationResult(BaseModel):
partition_scope_key: str
regional_partitions_complete: bool
partition_count: int = Field(ge=1)
data_partition_count: int = Field(ge=0)
no_profile_partition_count: int = Field(ge=0)
profile_count: int = Field(ge=0)
document_count: int = Field(ge=0)
structured_depth_count: int = Field(ge=0)
measurement_date_min: str | None = None
measurement_date_max: str | None = None
dataset_ids: list[UUID]
manifest_sha256: str
observed_at: datetime
limitation_message: str
class BathymetrySourceProbeRead(BaseModel):
source_key: str
status: Literal[
"disabled",
"invalid_configuration",
"tls_error",
"endpoint_unavailable",
"invalid_capabilities",
"reachable",
]
configured_url: str
capabilities_url: str | None = None
tls_verified: bool
capabilities_reachable: bool
acquisition_supported: bool = False
wcs_version: str | None = None
coverage_identifiers: list[str] = Field(default_factory=list)
advertised_formats: list[str] = Field(default_factory=list)
advertised_crs: list[str] = Field(default_factory=list)
response_sha256: str | None = None
checked_at: datetime
message: str
limitation_message: str
class MdkBathymetryAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
force_refresh: bool = False
class MdkBathymetryAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
coverage_id: str
bbox_epsg4326: list[float]
vertical_reference: str
resolution_m: float = Field(gt=0)
attribution: str
limitation_message: str
class BathymetryRasterSelectionRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
class BathymetryRasterMetric(BaseModel):
metric_key: str
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
is_estimate: bool = False
class BathymetryRasterSelectionSummary(BaseModel):
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
primary_metric_key: str
metrics: list[BathymetryRasterMetric]
class BathymetryRasterSelectionResponse(BaseModel):
dataset_id: UUID
product_key: str
selection_bbox: VectorSelectionBBox
selection_area_id: UUID | None = None
selected_cell_count: int = Field(ge=1)
valid_cell_count: int = Field(ge=1)
coverage_ratio: float = Field(ge=0, le=1)
# Set when the drawn selection is smaller than one source cell and the
# analysis was widened to the cells it touches, so the value covers more
# ground than was requested.
cell_selection_warning: str | None = None
resolution_m: float = Field(gt=0)
vertical_reference: str
survey_period: str
summary: BathymetryRasterSelectionSummary
unsupported_metrics: list[str]
limitation_message: str
generated_at: str
+54
View File
@@ -0,0 +1,54 @@
from __future__ import annotations
from typing import Any, Generic, Literal, TypeVar
from pydantic import BaseModel, Field
DataT = TypeVar("DataT")
class Envelope(BaseModel, Generic[DataT]):
data: DataT
class ItemList(BaseModel, Generic[DataT]):
items: list[DataT]
total: int
class PaginatedEnvelope(ItemList[DataT], Generic[DataT]):
limit: int
offset: int
class PaginationEnvelope(BaseModel):
items: list
total: int
limit: int = Field(default=50)
offset: int = Field(default=0)
class ApiErrorItem(BaseModel):
code: str
message: str
details: dict = Field(default_factory=dict)
class ApiErrorEnvelope(BaseModel):
error: str
message: str
details: dict | list = Field(default_factory=dict)
request_id: str | None = None
class GeoJsonFeature(BaseModel):
type: Literal["Feature"]
id: str | int | None = None
geometry: dict[str, Any] | None
properties: dict[str, Any] = Field(default_factory=dict)
class GeoJsonFeatureCollection(BaseModel):
type: Literal["FeatureCollection"]
features: list[GeoJsonFeature]
+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, Field, model_validator
CoverageStatus = Literal["operational", "partial", "not_configured", "unsupported"]
CoverageAuthority = Literal["authoritative", "official_context", "contextual"]
CoverageAcquisitionMode = Literal[
"operator_archive",
"operator_wfs",
"bounded_api",
"bounded_raster",
"catalog_only",
]
class CoverageBBox(BaseModel):
minx: float = Field(ge=-180, le=180)
miny: float = Field(ge=-90, le=90)
maxx: float = Field(ge=-180, le=180)
maxy: float = Field(ge=-90, le=90)
@model_validator(mode="after")
def validate_extent(self) -> "CoverageBBox":
if self.maxx <= self.minx or self.maxy <= self.miny:
raise ValueError("bbox max values must be greater than min values")
return self
class CoverageSourceContract(BaseModel):
source_name: str
display_name: str
authority_level: CoverageAuthority
coverage_zones: list[str]
themes: list[str]
native_layers: list[str]
supported_geometry_types: list[str]
acquisition_mode: CoverageAcquisitionMode
integration_status: CoverageStatus
source_url: str
attribution: str
license_note: str
limitation_message: str
class CoverageCatalogResponse(BaseModel):
themes: list[str]
zones: list[str]
statuses: list[CoverageStatus]
sources: list[CoverageSourceContract]
class CoverageResolveRequest(BaseModel):
project_id: UUID
bbox: CoverageBBox
themes: list[str] = Field(default_factory=list, max_length=32)
class CoverageResolutionItem(BaseModel):
zone: str
theme: str
status: CoverageStatus
source_names: list[str]
materialized_dataset_ids: list[UUID]
evidence: list["CoverageEvidenceItem"] = Field(default_factory=list)
limitation_message: str
class CoverageEvidenceItem(BaseModel):
dataset_id: UUID
source_name: str
authority_level: CoverageAuthority
source_version: str | None = None
observed_at: str | None = None
published_at: str | None = None
crs: str | None = None
resolution: dict | None = None
coverage_bbox_epsg4326: list[float] | None = None
attribution: str | None = None
license_note: str | None = None
checksum_sha256: str | None = None
class CoverageResolveResponse(BaseModel):
project_id: UUID
bbox: CoverageBBox
requested_themes: list[str]
intersected_zones: list[str]
outside_supported_scope: bool
items: list[CoverageResolutionItem]
warnings: list[str]
+132
View File
@@ -0,0 +1,132 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel
class DatasetStorageResponse(BaseModel):
original_filename: str | None = None
stored_filename: str | None = None
content_type: str | None = None
size_bytes: int | None = None
checksum_sha256: str | None = None
class DatasetVectorSummary(BaseModel):
feature_count: int | None = None
geometry_types: list[str] | None = None
bounds_json: dict | None = None
approximate_area_m2: float | None = None
crs: str | None = None
feature_geometry_count: int | None = None
invalid_features: int | None = None
crs_assumed: bool | None = None
class DatasetCreateResponse(BaseModel):
id: UUID
name: str
dataset_type: str
source: str
dataset_role: str = "source"
source_name: str | None = None
reference_layer_name: str | None = None
source_metadata: dict | None = None
provenance_metadata: dict | None = None
ingest_key: str | None = None
source_registry_id: UUID | None = None
source_snapshot_id: UUID | None = None
data_contract_key: str | None = None
data_contract_version: str | None = None
validation_status: str | None = None
validation_report_json: dict | None = None
provenance_status: str | None = None
lineage_status: str | None = None
quarantine_status: str | None = None
imported_at: datetime | None = None
temporal_series_key: str | None = None
observed_at: datetime | None = None
valid_from: datetime | None = None
valid_to: datetime | None = None
temporal_granularity: str | None = None
source_version: str | None = None
project_id: UUID
area_id: UUID | None = None
storage_path: str | None = None
original_filename: str | None = None
stored_filename: str | None = None
content_type: str | None = None
size_bytes: int | None = None
checksum_sha256: str | None = None
crs: str | None = None
bounds_json: dict | None = None
metadata_json: dict | None = None
vector_summary: DatasetVectorSummary | None = None
status: str
derived_from_dataset_id: UUID | None = None
created_at: datetime | None = None
feature_count: int | None = None
model_config = {"from_attributes": True}
class DatasetList(BaseModel):
items: list[DatasetCreateResponse]
total: int
limit: int
offset: int
class DatasetMetadataRefresh(BaseModel):
feature_count: int | None = None
geometry_types: list[str] | None = None
bounds_json: dict | None = None
crs: str | None = None
class DatasetTemporalUpdate(BaseModel):
temporal_series_key: str
observed_at: datetime
valid_from: datetime | None = None
valid_to: datetime | None = None
temporal_granularity: str = "snapshot"
source_version: str | None = None
class DatasetVersionRead(BaseModel):
id: UUID
dataset_id: UUID
version: int
storage_path: str | None = None
source_version: str | None = None
observed_at: datetime | None = None
valid_from: datetime | None = None
valid_to: datetime | None = None
checksum_sha256: str | None = None
source_metadata: dict | None = None
provenance_metadata: dict | None = None
ingest_key: str | None = None
source_registry_id: UUID | None = None
source_snapshot_id: UUID | None = None
data_contract_key: str | None = None
data_contract_version: str | None = None
validation_status: str | None = None
validation_report_json: dict | None = None
provenance_status: str | None = None
lineage_status: str | None = None
created_at: datetime | None = None
model_config = {"from_attributes": True}
class ExportRequest(BaseModel):
dataset_id: UUID
name: str | None = None
class ExportRead(BaseModel):
export_id: UUID
path: str
status: str
+18
View File
@@ -0,0 +1,18 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel
class DemoWorkflowResponse(BaseModel):
project_id: UUID
area_id: UUID
reference_dataset_id: UUID
candidate_dataset_id: UUID
raster_dataset_id: UUID | None = None
quality_check_id: UUID
metric_count: int
status: str
message: str
created: bool
+220
View File
@@ -0,0 +1,220 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field
class DetectionModelCapability(BaseModel):
model_config = ConfigDict(protected_namespaces=())
model_id: str
display_name: str
framework: str
task_type: str
supported_classes: list[str]
configured: bool
status: str
limitation_message: str
version: str | None = None
training_scope: str | None = None
validation_scope: str | None = None
validated_regions: list[str] = Field(default_factory=list)
nationally_validated: bool = False
operator_review_required: bool = True
class DetectionModelsResponse(BaseModel):
models: list[DetectionModelCapability]
class ModelAssetRead(BaseModel):
model_config = ConfigDict(protected_namespaces=())
model_asset_id: str
filename: str
display_name: str
model_path: str
suffix: str
framework: str
task_type: str
size_bytes: int
sha256: str
active: bool
runtime_available: bool
runtime_status: str
governed_validation_status: str
promotion_status: str
status: str
limitation_message: str
will_download_models: bool = False
class ModelAssetListResponse(BaseModel):
model_config = ConfigDict(protected_namespaces=())
items: list[ModelAssetRead]
total: int
model_directory: str
class DetectionRunRequest(BaseModel):
model_config = ConfigDict(protected_namespaces=())
project_id: UUID
dataset_id: UUID
model_id: str
model_asset_id: str | None = None
confidence_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
class_filter: list[str] | None = None
tile_manifest_path: str | None = None
parameters_json: dict = Field(default_factory=dict)
class DetectionQaRequest(BaseModel):
reference_dataset_id: UUID
iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
class_name: str | None = None
min_confidence: float | None = Field(default=None, ge=0.0, le=1.0)
# Confidence cuts to report alongside the run's own operating point. They
# are read off the one matching pass, so a sweep costs no extra inference.
calibration_thresholds: list[float] = Field(default_factory=list, max_length=32)
class DetectionComparisonRequest(BaseModel):
"""Place several runs side by side against one reference."""
analysis_run_ids: list[UUID] = Field(min_length=2, max_length=12)
reference_dataset_id: UUID
iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
class DetectionComparisonResponse(BaseModel):
reference_dataset_id: UUID
iou_threshold: float
# Whether these runs answer the same question at all, and why not if they
# do not. Numbers from incomparable runs are reported but never ranked as
# if they were alternatives.
comparability: dict
ranking_metric: str
rows: list[dict]
class DetectionRunResponse(BaseModel):
model_config = ConfigDict(protected_namespaces=())
analysis_run_id: UUID
job_id: UUID
project_id: UUID
dataset_id: UUID
model_id: str
status: str
detection_count: int
error_code: str | None = None
message: str
class DetectionRunRead(BaseModel):
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
id: UUID
project_id: UUID
dataset_id: UUID | None = None
job_id: UUID | None = None
analysis_type: str
status: str
model_name: str | None = None
model_version: str | None = None
parameters_json: dict
result_json: dict | None = None
error_message: str | None = None
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
class DetectionRunListResponse(BaseModel):
items: list[DetectionRunRead]
# ``total`` counts every run; ``items`` is the most recent page of them.
total: int
limit: int | None = None
offset: int = 0
truncated: bool = False
class DetectionRead(BaseModel):
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
id: UUID
project_id: UUID
dataset_id: UUID | None = None
analysis_run_id: UUID | None = None
job_id: UUID | None = None
model_name: str
model_version: str | None = None
class_name: str
confidence: float
bbox_json: dict | None = None
source_tile_path: str | None = None
properties_json: dict | None = None
created_at: datetime | None = None
class DetectionListResponse(BaseModel):
items: list[DetectionRead]
# ``total`` is the complete population; ``items`` is one page of it.
total: int
limit: int | None = None
offset: int = 0
truncated: bool = False
class YoloPreflightChecks(BaseModel):
model_config = ConfigDict(protected_namespaces=())
enabled: bool
dependencies_available: bool | None = None
accelerator_ready: bool | None = None
model_path_set: bool | None = None
model_file_exists: bool | None = None
model_provenance_manifest_path: str | None = None
model_provenance_valid: bool | None = None
model_load_requested: bool
model_load_ok: bool | None = None
manifest_path_set: bool | None = None
manifest_valid: bool | None = None
tile_paths_exist: bool | None = None
tile_limit_ok: bool | None = None
class YoloRuntimeDetails(BaseModel):
model_config = ConfigDict(protected_namespaces=())
dependencies_assumed: bool
model_directory: str | None = None
yolo_config_dir: str | None = None
torch_version: str | None = None
ultralytics_version: str | None = None
cuda_available: bool | None = None
configured_device: str
cuda_required: bool
class YoloPreflightResponse(BaseModel):
model_config = ConfigDict(protected_namespaces=())
model_id: str
model_asset_id: str | None = None
model_path: str | None = None
tile_manifest_path: str | None = None
status: str
message: str
checks: YoloPreflightChecks
tile_count: int
max_tiles: int
will_download_models: bool
will_run_inference: bool
runtime: YoloRuntimeDetails
error_code: str | None = None
details: dict | None = None
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, Field
DetectionEvidenceRole = Literal["false_positive", "false_negative"]
DetectionReviewDecision = Literal[
"confirmed_model_false_positive",
"confirmed_model_false_negative",
"reference_gap_or_change",
"qa_alignment_mismatch",
"imagery_obscured_or_uncertain",
"uncertain",
"unreviewed",
]
class DetectionReviewUpsert(BaseModel):
evidence_role: DetectionEvidenceRole
evidence_feature_id: str = Field(min_length=1, max_length=255)
decision: DetectionReviewDecision
notes: str | None = Field(default=None, max_length=2000)
reviewed_by: str = Field(default="operator", min_length=1, max_length=120)
class DetectionReviewRead(BaseModel):
id: UUID | None = None
project_id: UUID
quality_check_id: UUID
analysis_run_id: UUID | None = None
evidence_role: DetectionEvidenceRole
evidence_feature_id: str
detection_id: UUID | None = None
reference_feature_id: UUID | None = None
decision: DetectionReviewDecision = "unreviewed"
notes: str | None = None
reviewed_by: str | None = None
confidence: float | None = None
class_name: str | None = None
source_tile_path: str | None = None
created_at: datetime | None = None
updated_at: datetime | None = None
class DetectionReviewSummary(BaseModel):
total: int
reviewed: int
remaining: int
false_positive_total: int
false_negative_total: int
decision_counts: dict[str, int]
# The score with the operator's verdicts applied, next to the raw one. A
# finding adjudicated as a reference gap is not the model's error, and an
# interval covers what the unreviewed remainder could still turn out to be.
reviewed_metrics: dict | None = None
class DetectionReviewList(BaseModel):
items: list[DetectionReviewRead]
total: int
limit: int
offset: int
summary: DetectionReviewSummary
+102
View File
@@ -0,0 +1,102 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field
from .operations import VectorSelectionBBox
class DhmvAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str = "dtm_1m"
resolution_m: float | None = Field(default=None, ge=1.0, le=10.0)
force_refresh: bool = False
class DhmvProductRead(BaseModel):
key: str
display_name: str
surface_model: str
coverage_id: str
native_resolution_m: float
source_crs: str
vertical_reference: str
acquisition_period: str
catalog_url: str
attribution: str
limitation_message: str
class DhmvAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
surface_model: str
coverage_id: str
native_resolution_m: float
resolution_m: float
width: int
height: int
valid_pixel_count: int
nodata_value: float
bbox_epsg4326: list[float]
bbox_epsg31370: list[float]
vertical_reference: str
acquisition_period: str
attribution: str
limitation_message: str
class TerrainSelectionRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
class TerrainPartitionSelectionRequest(TerrainSelectionRequest):
product_key: str = "dtm_1m"
dataset_ids: list[UUID] | None = Field(default=None, min_length=1, max_length=4096)
class TerrainMetric(BaseModel):
metric_key: str
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
derived: bool = True
class TerrainSelectionSummary(BaseModel):
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
primary_metric_key: str
metrics: list[TerrainMetric]
class TerrainSelectionResponse(BaseModel):
dataset_id: UUID
dataset_ids: list[UUID] = Field(default_factory=list)
partition_count: int = Field(default=1, ge=1)
product_key: str
surface_model: str
selection_bbox: VectorSelectionBBox
selection_area_id: UUID | None = None
sample_count: int
slope_sample_count: int
coverage_ratio: float
# Set when the drawn selection is smaller than one source cell and the
# analysis was widened to the cells it touches, so the value covers more
# ground than was requested.
cell_selection_warning: str | None = None
resolution_m: float
vertical_reference: str
summary: TerrainSelectionSummary
unsupported_metrics: list[str]
limitation_message: str
generated_at: str
+111
View File
@@ -0,0 +1,111 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, model_validator
from app.schemas.operations import VectorSelectionBBox
ExportKind = Literal["dataset", "detection_run", "segmentation_run", "vector_selection"]
DetectionExportIntendedUse = Literal["review", "operational"]
MapResultMode = Literal["current", "evolution"]
class GeoJsonExportRequest(BaseModel):
dataset_id: UUID | None = None
analysis_run_id: UUID | None = None
area_id: UUID | None = None
export_kind: ExportKind = "dataset"
name: str | None = None
bbox: VectorSelectionBBox | None = None
limit: int = 250
intended_use: DetectionExportIntendedUse = "review"
@model_validator(mode="after")
def validate_target(self) -> "GeoJsonExportRequest":
if self.export_kind == "dataset" and self.dataset_id is None:
raise ValueError("dataset_id is required for dataset GeoJSON exports")
if self.export_kind == "vector_selection":
if self.dataset_id is None:
raise ValueError("dataset_id is required for vector selection GeoJSON exports")
if self.bbox is None:
raise ValueError("bbox is required for vector selection GeoJSON exports")
if self.export_kind in {"detection_run", "segmentation_run"} and self.analysis_run_id is None:
raise ValueError("analysis_run_id is required for run GeoJSON exports")
if self.intended_use == "operational" and self.export_kind != "detection_run":
raise ValueError("operational intended_use is supported only for detection run exports")
return self
class MetadataExportRequest(BaseModel):
project_id: UUID
name: str | None = None
class ReportExportRequest(BaseModel):
project_id: UUID
name: str | None = None
class MapResultExportRequest(BaseModel):
project_id: UUID
mode: MapResultMode
bbox: VectorSelectionBBox
dataset_id: UUID | None = None
earlier_dataset_id: UUID | None = None
later_dataset_id: UUID | None = None
area_id: UUID | None = None
partitioned: bool = False
product_key: str | None = None
partition_scope_key: str | None = None
theme_id: str | None = None
name: str | None = None
@model_validator(mode="after")
def validate_map_target(self) -> "MapResultExportRequest":
if self.mode == "current" and self.dataset_id is None:
raise ValueError("dataset_id is required for current map-result exports")
if self.mode == "evolution" and (
self.earlier_dataset_id is None or self.later_dataset_id is None
):
raise ValueError("earlier_dataset_id and later_dataset_id are required for evolution exports")
if self.partitioned and not self.product_key and not self.partition_scope_key:
raise ValueError("product_key or partition_scope_key is required for partitioned exports")
return self
class ExportRead(BaseModel):
id: UUID
project_id: UUID
analysis_run_id: UUID | None = None
export_type: str
storage_path: str
metadata_json: dict | None = None
created_at: datetime | None = None
status: str = "ready"
model_config = {"from_attributes": True}
class ExportCreateResponse(BaseModel):
export_id: UUID
path: str
status: str
export_type: str
metadata_json: dict | None = None
class ExportListResponse(BaseModel):
items: list[ExportRead]
total: int
limit: int
offset: int
class ExportContentResponse(BaseModel):
export_id: UUID
export_type: str
content: dict
+68
View File
@@ -0,0 +1,68 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel
class ProviderCapabilityResponse(BaseModel):
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
class ProviderCapabilitiesResponse(BaseModel):
providers: list[ProviderCapabilityResponse]
class ProviderLayersResponse(BaseModel):
provider_name: str
layers: list[str]
class ProviderStatusResponse(BaseModel):
provider_name: str
configured: bool
status: str
limitation_message: str
class ExternalFetchRequest(BaseModel):
project_id: UUID
area_id: UUID | None = None
layers: list[str] = []
class ExternalFetchResponse(BaseModel):
provider: str
status: str
message: str
requested_layers: list[str]
project_id: UUID
area_id: UUID | None = None
class ProviderImportRequest(BaseModel):
project_id: str
area_id: str | None = None
layers: list[str] = []
dataset_role: str | None = None
class ProviderImportResponse(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
+111
View File
@@ -0,0 +1,111 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field
from .operations import VectorSelectionBBox
class FloodHazardAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str = "pluviaal_current_t100"
resolution_m: float | None = Field(default=None, ge=2.0, le=20.0)
force_refresh: bool = False
class FloodHazardProductRead(BaseModel):
key: str
display_name: str
mechanism: str
climate_context: str
probability_class: str
return_period_years: int
coverage_id: str
native_resolution_m: float
source_crs: str
source_value_unit: str
normalized_value_unit: str
published_on: str
catalog_url: str
attribution: str
limitation_message: str
class FloodHazardAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
mechanism: str
climate_context: str
probability_class: str
return_period_years: int
coverage_id: str
resolution_m: float
width: int
height: int
inundated_pixel_count: int
bbox_epsg4326: list[float]
bbox_epsg31370: list[float]
attribution: str
limitation_message: str
class FloodHazardSelectionRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
class FloodHazardPartitionSelectionRequest(FloodHazardSelectionRequest):
product_key: str = "pluviaal_current_t100"
dataset_ids: list[UUID] | None = Field(default=None, min_length=1, max_length=4096)
class FloodHazardMetric(BaseModel):
metric_key: str
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
derived: bool = True
class FloodHazardSelectionSummary(BaseModel):
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
primary_metric_key: str
metrics: list[FloodHazardMetric]
class FloodHazardSelectionResponse(BaseModel):
dataset_id: UUID
dataset_ids: list[UUID] = Field(default_factory=list)
partition_count: int = Field(default=1, ge=1)
product_key: str
mechanism: str
climate_context: str
probability_class: str
return_period_years: int
selection_bbox: VectorSelectionBBox
selection_area_id: UUID | None = None
# Three populations kept apart: cells drawn, cells the model covers, and
# cells with a positive modelled depth. ``inundated_fraction`` is a share
# of the modelled cells, and is null when nothing was modelled — absence
# of a model is not evidence of zero risk.
selected_cell_count: int
valid_cell_count: int = 0
no_data_cell_count: int = 0
data_coverage_ratio: float = 1.0
inundated_cell_count: int
inundated_fraction: float | None = None
coverage_warning: str | None = None
resolution_m: float
summary: FloodHazardSelectionSummary
unsupported_metrics: list[str]
limitation_message: str
generated_at: str
+45
View File
@@ -0,0 +1,45 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel
from .operations import VectorSelectionBBox
class GrbAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str = "buildings"
force_refresh: bool = False
class GrbProductRead(BaseModel):
key: str
display_name: str
reference_layer_name: str
collections: list[str]
geometry_types: list[str]
source_crs: str
authority_level: str
catalog_url: str
attribution: str
license_note: str
limitation_message: str
class GrbAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
reference_layer_name: str
collections: list[str]
feature_count: int
candidate_feature_count: int
page_count: int
bbox_epsg4326: list[float]
source_version: str
attribution: str
limitation_message: str
+63
View File
@@ -0,0 +1,63 @@
from __future__ import annotations
from datetime import date, datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel
GrbRefreshLayerStatus = Literal[
"current",
"update_available",
"not_loaded",
"review_required",
"remote_unavailable",
]
class GrbRefreshLayerPlan(BaseModel):
theme: Literal["buildings", "roads", "water", "parcels"]
display_name: str
collections: list[str]
temporal_series_key: str
status: GrbRefreshLayerStatus
local_dataset_id: UUID | None = None
local_source_version: str | None = None
local_observed_at: datetime | None = None
local_imported_at: datetime | None = None
local_feature_count: int | None = None
local_size_bytes: int | None = None
retained_after_refresh: bool = True
action_message: str
class GrbRefreshPlanSummary(BaseModel):
layer_count: int
current_count: int
update_available_count: int
not_loaded_count: int
review_required_count: int
remote_unavailable_count: int
new_dataset_count_if_applied: int
retained_dataset_count: int
current_feature_count: int
current_size_bytes: int
class GrbRefreshPlan(BaseModel):
project_id: UUID
scope: str
generated_at: datetime
remote_status: str
remote_version: str | None = None
remote_edition_date: date | None = None
catalog_checked_at: datetime | None = None
summary: GrbRefreshPlanSummary
layers: list[GrbRefreshLayerPlan]
execution_mode: Literal["operator_stage_then_apply"] = "operator_stage_then_apply"
staging_required: bool = True
automatic_import: bool = False
destructive_replacement: bool = False
message: str
limitations: list[str]
+50
View File
@@ -0,0 +1,50 @@
from __future__ import annotations
from pydantic import BaseModel, Field
class ProviderCapability(BaseModel):
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
class HealthResponse(BaseModel):
status: str
service: str
version: str
build_sha: str | None = None
build_time: str | None = None
database: str | None = None
postgis: str | None = None
migration: str | None = None
storage: str | None = None
checks: dict[str, str] = Field(default_factory=dict)
class SystemCapabilities(BaseModel):
postgis: bool
rasterio: bool
geopandas: bool
yolo: bool | str
yolo_status: str
sam: bool | str
grb: str
sentinel: str
version: str
build_sha: str | None = None
providers: list[ProviderCapability] = Field(default_factory=list)
class SystemCapabilitiesEnvelope(BaseModel):
data: SystemCapabilities
+51
View File
@@ -0,0 +1,51 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
class JobCreate(BaseModel):
job_type: str
project_id: UUID
dataset_id: UUID | None = None
input_dataset_id: UUID | None = None
output_dataset_id: UUID | None = None
parameters_json: dict = Field(default_factory=dict)
class JobRead(BaseModel):
id: UUID
job_type: str
status: str
project_id: UUID
dataset_id: UUID | None = None
input_dataset_id: UUID | None = None
output_dataset_id: UUID | None = None
parameters_json: dict
result_json: dict | None = None
error_message: str | None = None
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
model_config = {"from_attributes": True}
class JobStatus(BaseModel):
id: UUID
status: str
error_message: str | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
result_json: dict | None = None
model_config = {"from_attributes": True}
class JobList(BaseModel):
items: list[JobRead]
total: int
limit: int
offset: int
+55
View File
@@ -0,0 +1,55 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel
from .operations import VectorSelectionBBox
class OfficialVectorAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str
force_refresh: bool = False
class OfficialVectorProductRead(BaseModel):
key: str
display_name: str
theme: str
provider: str
source_name: str
reference_layer_name: str
service_type: str
collection: str
geometry_types: list[str]
source_crs: str
source_version: str
observation_label: str
authority_level: str
catalog_url: str
attribution: str
license_note: str
limitation_message: str
coverage_zones: list[str]
class OfficialVectorAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
product_key: str
display_name: str
theme: str
provider: str
source_name: str
reference_layer_name: str
service_type: str
collection: str
feature_count: int
candidate_feature_count: int
page_count: int
bbox_epsg4326: list[float]
source_version: str
attribution: str
limitation_message: str
+264
View File
@@ -0,0 +1,264 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field, field_validator
class VectorOperationResult(BaseModel):
feature_count: int
geometry_type_summary: dict[str, int]
bounds_json: dict | None = None
crs: str | None = None
source_dataset_id: str
class VectorOperationRequest(BaseModel):
output_name: str | None = None
class VectorClipRequest(VectorOperationRequest):
area_id: str
class VectorBufferRequest(VectorOperationRequest):
distance_m: float
dissolve: bool = False
class VectorIntersectRequest(VectorOperationRequest):
other_dataset_id: str
class VectorStatsRequest(BaseModel):
pass
class RasterReadyResponse(BaseModel):
dataset_id: str
ready: bool
message: str | None = None
class RasterOperationResult(BaseModel):
dataset_id: str
ready: bool
metadata: dict | None = None
output_dataset_id: str | None = None
operation: str | None = None
class RasterMetadataResponse(BaseModel):
dataset_id: str
driver: str | None = None
width: int | None = None
height: int | None = None
band_count: int | None = None
crs: str | None = None
bounds: list[float] | None = None
resolution: list[float] | None = None
dtype: list[str] | None = None
nodata: list[float] | float | None = None
transform: list[float] | None = None
size_bytes: int | None = None
checksum_sha256: str | None = None
path: str | None = None
class RasterPreviewResponse(BaseModel):
dataset_id: str
ready: bool
preview: dict
metadata: dict | None = None
class RasterBandStats(BaseModel):
band_index: int
dtype: str | None = None
min: float | None = None
max: float | None = None
mean: float | None = None
std: float | None = None
nodata_count: int
nodata_ratio: float
valid_pixel_count: int
histogram: list[int] | None = None
histogram_bins: list[float] | None = None
class RasterStatsResponse(BaseModel):
dataset_id: str
source_dataset_id: str | None = None
bands: list[RasterBandStats]
generated_at: str | None = None
metadata: dict | None = None
class RasterReprojectRequest(BaseModel):
target_crs: str | None = "EPSG:31370"
resampling: str = "nearest"
output_name: str | None = None
class RasterClipRequest(BaseModel):
area_id: str
output_name: str | None = None
class RasterTileRequest(BaseModel):
tile_size: int = 512
overlap: int = 64
output_name: str | None = None
class RasterIndexBaseRequest(BaseModel):
output_name: str | None = None
class RasterNdviRequest(RasterIndexBaseRequest):
nir_band: int
red_band: int
class RasterNdwiRequest(RasterIndexBaseRequest):
green_band: int
nir_band: int
class RasterNdbiRequest(RasterIndexBaseRequest):
swir_band: int
nir_band: int
class RasterTileManifestTile(BaseModel):
path: str
pixel_window: list[int]
bounds: list[float]
transform: list[float]
index: int
class RasterTileManifest(BaseModel):
tile_set_id: str
source_dataset_id: str
source_raster_id: str
bounds: list[float]
tile_size: int
overlap: int
parameters: dict[str, str | int | float | bool | None]
created_at: str
tile_paths: list[str]
count: int
tiles: list[RasterTileManifestTile]
ai_inference: bool = False
tile_server: str | None = None
class RasterTileResponse(BaseModel):
dataset_id: str
ready: bool
operation: str
tile_set_id: str
tile_size: int
overlap: int
manifest_path: str
count: int
manifest: RasterTileManifest
class RasterReprojectResponse(BaseModel):
dataset_id: str
ready: bool
operation: str
output_dataset_id: str
source_dataset_id: str
target_dataset_id: str | None = None
class RasterOperationUnavailable(BaseModel):
code: str
message: str
class VectorBBoxResponse(BaseModel):
dataset_id: str
bounds_json: dict | None
feature_count: int
crs: str | None = None
class VectorStatsResponse(BaseModel):
dataset_id: str
feature_count: int
geometry_type_summary: dict[str, int]
bounds_json: dict | None
crs: str | None = None
class VectorSelectionBBox(BaseModel):
min_x: float
min_y: float
max_x: float
max_y: float
crs: str = "EPSG:4326"
@field_validator("crs")
@classmethod
def validate_crs(cls, value: str) -> str:
if value.upper() != "EPSG:4326":
raise ValueError("Only EPSG:4326 bbox selection is supported")
return "EPSG:4326"
class VectorSelectionRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
limit: int = Field(default=100, ge=1, le=1000)
class VectorSelectionDeriveRequest(VectorSelectionRequest):
output_name: str | None = None
class VectorSelectionMetric(BaseModel):
metric_key: str
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
is_estimate: bool = False
warning: str | None = None
class VectorSelectionSummary(BaseModel):
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
primary_metric_key: str | None = None
# ``feature_count`` counts whole features that touch the selection, while
# area and length metrics clip to it. These fields say how far the two
# populations diverge, so the numbers on one panel can be read together.
feature_count: int
fully_covered_feature_count: int | None = None
partially_covered_feature_count: int | None = None
selection_edge_warning: str | None = None
is_estimate: bool = False
warning: str | None = None
metrics: list[VectorSelectionMetric] = Field(default_factory=list)
class VectorSelectionResponse(BaseModel):
selection_bbox: VectorSelectionBBox
selection_area_id: UUID | None = None
feature_count: int
total_feature_count: int | None = None
limit: int
truncated: bool
geojson: dict
summary: VectorSelectionSummary | None = None
partition_count: int | None = None
available_partition_count: int | None = None
partition_scope_key: str | None = None
source_name: str | None = None
dataset_ids: list[UUID] | None = None
+50
View File
@@ -0,0 +1,50 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field
from .operations import VectorSelectionBBox
class OrthophotoAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str = "most_recent"
force_refresh: bool = False
resolution_m: float | None = Field(default=None, ge=0.1, le=2.0)
class OrthophotoProductRead(BaseModel):
key: str
display_name: str
observation_label: str
temporal_granularity: str
native_resolution_m: float
supports_detection: bool
color_mode: str
catalog_url: str
limitation_message: str
provider: str
coverage_zone: str
attribution: str
license_note: str
class OrthophotoAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
observation_label: str
temporal_granularity: str
supports_detection: bool
layer: str
width: int
height: int
resolution_m: float
bbox_epsg4326: list[float]
bbox_epsg31370: list[float]
attribution: str
limitation_message: str
+47
View File
@@ -0,0 +1,47 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel
class ProjectCreate(BaseModel):
name: str
description: str | None = None
region: str | None = "Belgium and Belgian North Sea"
class ProjectUpdate(BaseModel):
name: str | None = None
description: str | None = None
region: str | None = None
status: Literal["active", "archived"] | None = None
class ProjectRead(BaseModel):
id: UUID
name: str
description: str | None = None
region: str
status: str
created_at: datetime | None = None
updated_at: datetime | None = None
model_config = {"from_attributes": True}
class ProjectListItem(ProjectRead):
pass
class ProjectList(BaseModel):
items: list[ProjectRead]
total: int
limit: int
offset: int
class ProjectDeleteResult(BaseModel):
deleted: bool
+128
View File
@@ -0,0 +1,128 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from uuid import UUID
from pydantic import BaseModel, Field
from app.schemas.common import GeoJsonFeatureCollection
class QaProviderComparisonRequest(BaseModel):
candidate_dataset_id: UUID
reference_dataset_id: UUID
iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
area_id: UUID | None = None
class QaProviderComparisonResult(BaseModel):
status: str
warnings: list[str] = Field(default_factory=list)
# Counts of the population that was actually matched, so that
# ``matches + false_positives == candidate_feature_count`` holds even when
# an area filter or an unparseable geometry removed features. The ``_raw``
# fields keep the untouched dataset totals visible next to them.
candidate_feature_count: int
reference_feature_count: int
candidate_feature_count_raw: int | None = None
reference_feature_count_raw: int | None = None
matches: int
false_positives: int
false_negatives: int
precision: float | None
recall: float | None
f1_score: float | None
mean_iou: float | None
iou_threshold: float
unsupported_geometry: bool = False
unsupported_geometries: list[str] = Field(default_factory=list)
match_evidence: list[dict] = Field(default_factory=list)
false_positive_evidence: list[dict] = Field(default_factory=list)
false_negative_evidence: list[dict] = Field(default_factory=list)
generated_at: datetime
class MetricRead(BaseModel):
id: UUID
quality_check_id: UUID | None = None
analysis_run_id: UUID | None = None
metric_key: str
metric_value: float | None = None
metric_unit: str | None = None
label: str | None = None
metadata_json: dict | None = None
created_at: datetime | None = None
model_config = {"from_attributes": True}
class QualityCheckRead(BaseModel):
id: UUID
project_id: UUID
job_id: UUID | None = None
analysis_run_id: UUID | None = None
candidate_dataset_id: UUID | None = None
reference_dataset_id: UUID
check_type: str
status: str
score: float | None = None
parameters_json: dict | None = None
findings_json: dict | None = None
created_at: datetime | None = None
completed_at: datetime | None = None
metrics: list[MetricRead] = Field(default_factory=list)
model_config = {"from_attributes": True}
class QualityCheckList(BaseModel):
items: list[QualityCheckRead]
total: int
limit: int
offset: int
class QualityEvidenceResponse(BaseModel):
quality_check_id: UUID
project_id: UUID
candidate_dataset_id: UUID | None = None
reference_dataset_id: UUID
analysis_run_id: UUID | None = None
# The overlay is capped so a regional check stays reviewable; the counts in
# the quality check itself are always complete.
feature_count: int
total_feature_count: int | None = None
role_counts: dict[str, int] = Field(default_factory=dict)
truncated: bool = False
limit: int | None = None
warnings: list[str] = Field(default_factory=list)
geojson: GeoJsonFeatureCollection
class AnalysisQaResponse(BaseModel):
status: str
quality_check_id: UUID
analysis_run_id: UUID
reference_dataset_id: UUID
candidate_feature_count: int
reference_feature_count: int
candidate_feature_count_raw: int | None = None
reference_feature_count_raw: int | None = None
matches: int
false_positives: int
false_negatives: int
precision: float | None = None
recall: float | None = None
f1_score: float | None = None
mean_iou: float | None = None
iou_threshold: float
warnings: list[str] = Field(default_factory=list)
coverage: dict[str, Any] | None = None
temporal_compatibility: dict[str, Any] | None = None
box_to_footprint_diagnostics: dict[str, Any] | None = None
precision_recall_curve: dict[str, Any] | None = None
calibration_sweep: list[dict[str, Any]] = Field(default_factory=list)
match_evidence: list[dict[str, Any]] = Field(default_factory=list)
false_positive_evidence: list[dict[str, Any]] = Field(default_factory=list)
false_negative_evidence: list[dict[str, Any]] = Field(default_factory=list)
+110
View File
@@ -0,0 +1,110 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field
from app.schemas.detection import DetectionModelCapability
SegmentationModelCapability = DetectionModelCapability
class SegmentationModelsResponse(BaseModel):
models: list[SegmentationModelCapability]
class SegmentationRunRequest(BaseModel):
model_config = ConfigDict(protected_namespaces=())
project_id: UUID
dataset_id: UUID
model_id: str
confidence_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
class_filter: list[str] | None = None
tile_manifest_path: str | None = None
parameters_json: dict = Field(default_factory=dict)
class SegmentationQaRequest(BaseModel):
reference_dataset_id: UUID
iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
class_name: str | None = None
min_confidence: float | None = Field(default=None, ge=0.0, le=1.0)
# Read off the one matching pass, exactly as for detection.
calibration_thresholds: list[float] = Field(default_factory=list, max_length=32)
class SegmentationRunResponse(BaseModel):
model_config = ConfigDict(protected_namespaces=())
analysis_run_id: UUID
job_id: UUID
project_id: UUID
dataset_id: UUID
model_id: str
status: str
segmentation_count: int
error_code: str | None = None
message: str
class SegmentationRunRead(BaseModel):
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
id: UUID
project_id: UUID
dataset_id: UUID | None = None
job_id: UUID | None = None
analysis_type: str
status: str
model_name: str | None = None
model_version: str | None = None
parameters_json: dict
result_json: dict | None = None
error_message: str | None = None
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
class SegmentationRunListResponse(BaseModel):
items: list[SegmentationRunRead]
# ``total`` counts every run; ``items`` is the most recent page of them.
total: int
limit: int | None = None
offset: int = 0
truncated: bool = False
class SegmentationRead(BaseModel):
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
id: UUID
project_id: UUID
dataset_id: UUID | None = None
analysis_run_id: UUID | None = None
job_id: UUID | None = None
model_name: str
model_version: str | None = None
class_name: str
confidence: float | None = None
bbox_json: dict | None = None
area_m2: float | None = None
mask_path: str | None = None
source_tile_path: str | None = None
tile_index: int | None = None
properties_json: dict | None = None
provenance_json: dict | None = None
created_at: datetime | None = None
class SegmentationListResponse(BaseModel):
items: list[SegmentationRead]
# ``total`` describes the complete filtered population; ``items`` is one
# stable confidence-ranked page of it.
total: int
limit: int | None = None
offset: int = 0
truncated: bool = False
@@ -0,0 +1,14 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field
from .operations import VectorSelectionBBox
class VectorPartitionSelectionRequest(BaseModel):
dataset_ids: list[UUID] = Field(min_length=1, max_length=4096)
bbox: VectorSelectionBBox
area_id: UUID | None = None
limit: int = Field(default=1000, ge=1, le=1000)
+56
View File
@@ -0,0 +1,56 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel
SourceCatalogProbeStatus = Literal["available", "degraded", "unavailable", "disabled"]
SourceCatalogComparisonStatus = Literal["same", "different", "not_comparable", "no_local_data", "unavailable"]
class SourceCatalogProbeItem(BaseModel):
source_name: str
display_name: str
service_type: Literal["WFS", "WMS", "HTML", "DCAT"]
endpoint_url: str
status: SourceCatalogProbeStatus
reachable: bool
checked_at: datetime
cached: bool = False
expected_layers: list[str]
matched_layers: list[str]
missing_layers: list[str]
advertised_layer_count: int
metadata_url: str | None = None
metadata_identifier: str | None = None
remote_title: str | None = None
remote_version: str | None = None
remote_modified_at: datetime | None = None
remote_published_at: datetime | None = None
local_source_version: str | None = None
comparison_status: SourceCatalogComparisonStatus
capabilities_sha256: str | None = None
capabilities_etag: str | None = None
capabilities_last_modified_at: datetime | None = None
message: str
error_code: str | None = None
class SourceCatalogProbeSummary(BaseModel):
provider_count: int
available_count: int
degraded_count: int
unavailable_count: int
disabled_count: int
different_version_count: int
class SourceCatalogProbeReport(BaseModel):
project_id: UUID
generated_at: datetime
summary: SourceCatalogProbeSummary
items: list[SourceCatalogProbeItem]
limitations: list[str]
+66
View File
@@ -0,0 +1,66 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel
SourceFreshnessStatus = Literal["current", "due", "review_required", "local"]
SourceRefreshPolicy = Literal["rolling_snapshot", "annual_release", "edition", "scenario", "archive", "local"]
class SourceIntegritySummary(BaseModel):
missing_version_count: int = 0
checksum_mismatch_count: int = 0
missing_storage_file_count: int = 0
size_mismatch_count: int = 0
@property
def issue_count(self) -> int:
return (
self.missing_version_count
+ self.checksum_mismatch_count
+ self.missing_storage_file_count
+ self.size_mismatch_count
)
class SourceFreshnessItem(BaseModel):
source_name: str
display_name: str
dataset_count: int
ready_count: int
version_count: int
latest_imported_at: datetime | None = None
latest_observed_at: datetime | None = None
latest_source_version: str | None = None
refresh_policy: SourceRefreshPolicy
review_interval_days: int | None = None
next_review_at: datetime | None = None
status: SourceFreshnessStatus
historical_series: bool
auto_refresh_supported: bool = False
reason: str
recommended_action: str
integrity: SourceIntegritySummary
class SourceFreshnessSummary(BaseModel):
source_count: int
dataset_count: int
current_count: int
due_count: int
review_required_count: int
local_count: int
sources_with_integrity_issues: int
integrity_issue_count: int
class SourceFreshnessReport(BaseModel):
project_id: UUID
generated_at: datetime
summary: SourceFreshnessSummary
items: list[SourceFreshnessItem]
limitations: list[str]
+120
View File
@@ -0,0 +1,120 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
class SourceRegistryRead(BaseModel):
"""Read-only, server-owned source-authority definition."""
id: UUID
source_key: str
display_name: str
classification: str
authority_name: str
authority_scope_json: dict
provider_adapter_key: str | None = None
source_url: str | None = None
license_name: str
license_url: str | None = None
usage_restrictions: str
default_crs: str
default_units: str
spatial_resolution_json: dict
temporal_coverage_json: dict
geographic_coverage_json: dict
expected_geometry_types_json: list
expected_attributes_json: dict
usage_policy_json: dict
freshness_status: str
ingest_status: str
known_limitations_json: list
registry_metadata_json: dict
created_at: datetime | None = None
updated_at: datetime | None = None
snapshot_count: int = 0
model_config = {"from_attributes": True}
class SourceSnapshotRead(BaseModel):
"""Immutable version/snapshot evidence attached to an imported dataset."""
id: UUID
source_registry_id: UUID
snapshot_key: str
source_version: str | None = None
snapshot_at: datetime | None = None
fetched_at: datetime | None = None
source_url: str | None = None
checksum_sha256: str | None = None
crs: str | None = None
units: str | None = None
spatial_resolution_json: dict
temporal_coverage_json: dict
geographic_coverage_json: dict
observed_schema_json: dict
freshness_status: str
ingest_status: str
known_limitations_json: list
snapshot_metadata_json: dict
created_at: datetime | None = None
model_config = {"from_attributes": True}
class SourceRegistryDetailRead(BaseModel):
source: SourceRegistryRead
snapshots: list[SourceSnapshotRead]
class DatasetLineageEdgeRead(BaseModel):
id: UUID
parent_dataset_id: UUID
child_dataset_id: UUID
parent_dataset_version_id: UUID | None = None
child_dataset_version_id: UUID | None = None
relation_type: str
transformation_name: str
transformation_version: str | None = None
parameters_json: dict | None = None
input_checksum_sha256: str | None = None
output_checksum_sha256: str | None = None
created_at: datetime | None = None
model_config = {"from_attributes": True}
class DatasetQuarantineRead(BaseModel):
id: UUID
dataset_id: UUID | None = None
dataset_version_id: UUID | None = None
source_snapshot_id: UUID | None = None
stage: str
reason_code: str
details_json: dict | None = None
artifact_path: str | None = None
artifact_checksum_sha256: str | None = None
status: str
created_at: datetime | None = None
resolved_at: datetime | None = None
resolved_by: str | None = None
model_config = {"from_attributes": True}
class DatasetProvenanceRead(BaseModel):
dataset_id: UUID
source: SourceRegistryRead | None = None
snapshot: SourceSnapshotRead | None = None
data_contract_key: str | None = None
data_contract_version: str | None = None
validation_status: str | None = None
validation_report_json: dict | None = None
provenance_status: str | None = None
lineage_status: str | None = None
quarantine_status: str | None = None
lineage: list[DatasetLineageEdgeRead] = Field(default_factory=list)
quarantines: list[DatasetQuarantineRead] = Field(default_factory=list)
+55
View File
@@ -0,0 +1,55 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field
from .operations import VectorSelectionBBox
class SpwTerrainAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str = "spw_mnt_1m_2021_2022"
resolution_m: float | None = Field(default=None, ge=1.0, le=10.0)
force_refresh: bool = False
class SpwTerrainProductRead(BaseModel):
key: str
display_name: str
surface_model: str
source_filename: str
native_resolution_m: float
analysis_resolution_m: float
source_crs: str
vertical_reference: str
acquisition_period: str
catalog_url: str
attribution: str
license_note: str
limitation_message: str
coverage_zones: list[str]
configured: bool
status: str
class SpwTerrainAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
surface_model: str
native_resolution_m: float
resolution_m: float
width: int
height: int
valid_pixel_count: int
nodata_value: float
bbox_epsg4326: list[float]
bbox_epsg3812: list[float]
vertical_reference: str
acquisition_period: str
attribution: str
limitation_message: str
+91
View File
@@ -0,0 +1,91 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
from app.schemas.operations import VectorSelectionBBox
class TemporalComparisonRequest(BaseModel):
earlier_dataset_id: UUID
later_dataset_id: UUID
bbox: VectorSelectionBBox
area_id: UUID | None = None
preview_limit: int = Field(default=500, ge=1, le=1000)
class TemporalDatasetRef(BaseModel):
id: UUID
name: str
observed_at: datetime
source_version: str | None = None
class TemporalMetricComparison(BaseModel):
metric_key: str = "primary"
label: str
unit: str
aggregation_method: str
earlier_value: float
later_value: float
absolute_change: float
percent_change: float | None = None
is_estimate: bool = False
warning: str | None = None
class TemporalObservationMetric(BaseModel):
metric_key: str
label: str
value: float
unit: str
aggregation_method: str
is_estimate: bool = False
class TemporalObservation(BaseModel):
dataset: TemporalDatasetRef
metrics: list[TemporalObservationMetric]
class TemporalObjectChanges(BaseModel):
available: bool
added_count: int | None = None
removed_count: int | None = None
modified_count: int | None = None
unchanged_count: int | None = None
class TemporalComparisonResponse(BaseModel):
temporal_series_key: str
earlier: TemporalDatasetRef
later: TemporalDatasetRef
selection_bbox: VectorSelectionBBox
selection_area_id: UUID | None = None
metric: TemporalMetricComparison
metrics: list[TemporalMetricComparison] = Field(default_factory=list)
timeline: list[TemporalObservation] = Field(default_factory=list)
object_changes: TemporalObjectChanges
geojson: dict
warnings: list[str]
generated_at: datetime
class TemporalSeriesDataset(BaseModel):
id: UUID
name: str
observed_at: datetime
source_version: str | None = None
feature_count: int | None = None
class TemporalSeriesRead(BaseModel):
temporal_series_key: str
source_name: str | None = None
reference_layer_name: str | None = None
dataset_count: int
first_observed_at: datetime
last_observed_at: datetime
datasets: list[TemporalSeriesDataset]
+125
View File
@@ -0,0 +1,125 @@
from __future__ import annotations
from uuid import UUID
from pydantic import BaseModel, Field
from .operations import VectorSelectionBBox
class ThematicRasterAcquireRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
product_key: str
force_refresh: bool = False
class ThematicRasterProductRead(BaseModel):
key: str
display_name: str
theme: str
metric_kind: str
coverage_id: str
native_resolution_m: float
source_crs: str
source_value_unit: str
observation_year: int
source_version: str
catalog_url: str
attribution: str
license_note: str
legend_min_label: str
legend_max_label: str
included_source_values: list[int]
limitation_message: str
analysis_resolution_m: float | None = None
coverage_zones: list[str] = Field(default_factory=list)
configured: bool = True
status: str = "configured"
class WalousAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
theme: str
metric_kind: str
resolution_m: float
width: int
height: int
valid_pixel_count: int
bbox_epsg4326: list[float]
bbox_epsg3812: list[float]
observation_year: int
source_value_unit: str
attribution: str
limitation_message: str
class ThematicRasterAcquisitionResult(BaseModel):
output_dataset_id: UUID
reused: bool
provider: str
product_key: str
display_name: str
theme: str
metric_kind: str
coverage_id: str
resolution_m: float
width: int
height: int
valid_pixel_count: int
bbox_epsg4326: list[float]
bbox_epsg31370: list[float]
observation_year: int
source_value_unit: str
attribution: str
limitation_message: str
class ThematicRasterSelectionRequest(BaseModel):
bbox: VectorSelectionBBox
area_id: UUID | None = None
class ThematicRasterMetric(BaseModel):
metric_key: str
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
derived: bool = True
is_estimate: bool = True
class ThematicRasterSelectionSummary(BaseModel):
metric_label: str
metric_value: float
metric_unit: str
aggregation_method: str
primary_metric_key: str
metrics: list[ThematicRasterMetric]
class ThematicRasterSelectionResponse(BaseModel):
dataset_id: UUID
product_key: str
theme: str
metric_kind: str
selection_bbox: VectorSelectionBBox
selection_area_id: UUID | None = None
selected_cell_count: int
valid_cell_count: int
coverage_ratio: float
# Set when the drawn selection is smaller than one source cell and the
# analysis was widened to the cells it touches, so the value covers more
# ground than was requested.
cell_selection_warning: str | None = None
resolution_m: float
observation_year: int
summary: ThematicRasterSelectionSummary
unsupported_metrics: list[str]
limitation_message: str
generated_at: str