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,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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user