1561 lines
47 KiB
Markdown
1561 lines
47 KiB
Markdown
# API Contracts v1
|
|
|
|
This document freezes the first API shape. Codex may add implementation details but must not rename these routes without updating this file and the frontend API client.
|
|
|
|
## API principles
|
|
|
|
- Base path: `/api/v1`.
|
|
- JSON by default.
|
|
- GeoJSON accepted for geometries where possible.
|
|
- Long processing tasks return a job or analysis run record instead of blocking.
|
|
- Error responses use the shared `ApiError` schema.
|
|
|
|
## Shared schemas
|
|
|
|
### ApiError
|
|
|
|
```json
|
|
{
|
|
"error": "string",
|
|
"message": "human readable message",
|
|
"details": {},
|
|
"request_id": "optional string"
|
|
}
|
|
```
|
|
|
|
### GeoJsonGeometry
|
|
|
|
Any valid GeoJSON geometry object. V1 primarily expects `Polygon` and `MultiPolygon` for areas.
|
|
|
|
### BoundingBox
|
|
|
|
```json
|
|
{
|
|
"min_x": 0.0,
|
|
"min_y": 0.0,
|
|
"max_x": 0.0,
|
|
"max_y": 0.0,
|
|
"crs": "EPSG:4326"
|
|
}
|
|
```
|
|
|
|
## Health
|
|
|
|
### GET `/health`
|
|
|
|
Returns service status.
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "geointel-backend",
|
|
"version": "0.1.0"
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/system/capabilities`
|
|
|
|
Returns enabled feature flags and tool availability.
|
|
|
|
```json
|
|
{
|
|
"postgis": true,
|
|
"rasterio": true,
|
|
"geopandas": true,
|
|
"yolo": false,
|
|
"sam": false,
|
|
"grb": "planned",
|
|
"sentinel": "planned",
|
|
"providers": [
|
|
{
|
|
"provider_name": "grb",
|
|
"display_name": "GRB",
|
|
"authority_level": "authoritative",
|
|
"supported_layers": ["buildings", "roads", "parcels"],
|
|
"supported_geometry_types": ["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
|
|
"supported_query_modes": ["area"],
|
|
"fetch_signature": "POST /api/v1/external/grb/fetch",
|
|
"configured": false,
|
|
"status": "not_configured",
|
|
"limitation_message": "GRB live WFS/download integration is not configured in Sprint 7B.",
|
|
"attribution": "Digitaal Vlaanderen - Basiskaart Vlaanderen (GRB)",
|
|
"license_note": "Use must follow Digitaal Vlaanderen open data and attribution terms.",
|
|
"not_configured_reason": "Provider integration is not configured yet"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Projects
|
|
|
|
### GET `/api/v1/projects`
|
|
|
|
Returns all projects.
|
|
|
|
### POST `/api/v1/projects`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"name": "Geel building detection demo",
|
|
"description": "Detect buildings and validate against GRB",
|
|
"region": "Kempen"
|
|
}
|
|
```
|
|
|
|
Response: `ProjectRead`.
|
|
|
|
### GET `/api/v1/projects/{project_id}`
|
|
|
|
Returns one project with summary counts.
|
|
|
|
### PATCH `/api/v1/projects/{project_id}`
|
|
|
|
Updates name/description/region.
|
|
|
|
### DELETE `/api/v1/projects/{project_id}`
|
|
|
|
Soft-delete in V1 preferred. Hard-delete only if storage cleanup is also implemented.
|
|
|
|
## Areas
|
|
|
|
### GET `/api/v1/projects/{project_id}/areas`
|
|
|
|
Returns areas for a project. Area responses include persisted AOI geometry as
|
|
GeoJSON so the frontend can display the selected area in the map workbench.
|
|
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"project_id": "uuid",
|
|
"name": "Geel Centrum AOI",
|
|
"original_crs": "EPSG:4326",
|
|
"area_m2": 1234.5,
|
|
"created_at": "timestamp",
|
|
"geometry_type": null,
|
|
"geometry": {
|
|
"type": "MultiPolygon",
|
|
"coordinates": []
|
|
}
|
|
}
|
|
```
|
|
|
|
### POST `/api/v1/projects/{project_id}/areas`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"name": "Geel Centrum AOI",
|
|
"geometry": {"type": "Polygon", "coordinates": []},
|
|
"crs": "EPSG:4326"
|
|
}
|
|
```
|
|
|
|
Backend responsibilities:
|
|
|
|
- Validate geometry.
|
|
- Repair trivial polygon issues if safe.
|
|
- Store geometry in PostGIS.
|
|
- Calculate area in square meters using projected CRS.
|
|
- Store bbox.
|
|
|
|
### GET `/api/v1/projects/{project_id}/areas/{area_id}`
|
|
|
|
Returns one project area. The payload uses the same `AreaRead` shape as the
|
|
area list endpoint and includes persisted GeoJSON geometry for map display.
|
|
|
|
### PATCH `/api/v1/projects/{project_id}/areas/{area_id}`
|
|
|
|
Updates the area name and/or geometry. Geometry updates follow the same
|
|
validation, repair and metric-calculation rules as area creation.
|
|
|
|
## Datasets
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/upload`
|
|
|
|
Multipart upload.
|
|
|
|
Fields:
|
|
|
|
- `file`: dataset file.
|
|
- `dataset_type`: `vector`, `geojson` (legacy), `raster`.
|
|
- `source`: free text, e.g. `user_upload`, `grb`, `osm`.
|
|
- `dataset_role`: `source`, `derived`, or `reference` (default `source`).
|
|
- `source_name`: optional source identity, e.g. `manual`, `grb`, `osm`; reference uploads default to `manual` when omitted.
|
|
- `reference_layer_name`: optional reference layer label, e.g. `buildings`; only retained for reference datasets.
|
|
- `area_id`: optional.
|
|
|
|
Response: `DatasetRead` with extracted metadata if supported.
|
|
|
|
Vector uploads remain stored as original files and are also persisted into `vector_features` as queryable PostGIS state.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/orthophoto/acquire`
|
|
|
|
Explicitly acquire a bounded most-recent winter orthophoto selection from the
|
|
official Digitaal Vlaanderen `OMWRGBMRVL` WMS `Ortho` layer.
|
|
|
|
```json
|
|
{
|
|
"bbox": {"min_x": 5.10, "min_y": 51.17, "max_x": 5.11, "max_y": 51.18, "crs": "EPSG:4326"},
|
|
"area_id": "optional-project-area-uuid",
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The canonical envelope contains a synchronous Job. Its `output_dataset_id`
|
|
identifies the raster Dataset; `result_json` contains provider, layer, pixel
|
|
dimensions, EPSG:4326/EPSG:31370 bounds, sampling resolution, attribution,
|
|
cache reuse and limitation text.
|
|
|
|
Safety contract:
|
|
|
|
- every side must measure between 128 m and 1,024 m in EPSG:31370;
|
|
- an optional `area_id` must belong to the project and cover at least 99% of
|
|
the rectangle;
|
|
- defaults are 1 m/pixel, a 32 MiB response limit and 24-hour exact-request
|
|
reuse;
|
|
- WMS bytes are georeferenced to EPSG:31370 and persisted only through
|
|
`DatasetService`; no fetch runs on startup;
|
|
- this is the latest mosaic available at request time, not a historical
|
|
observation date for every pixel.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets`
|
|
|
|
List datasets.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}`
|
|
|
|
Return metadata.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/metadata/refresh`
|
|
|
|
Re-extract metadata.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/inspect`
|
|
|
|
Return a wrapped vector inspection payload with metadata, storage summary and feature summary.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/summary`
|
|
|
|
Return vector summary data only.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/metadata`
|
|
|
|
Return raster metadata profile for supported raster uploads.
|
|
|
|
If raster processing is unavailable:
|
|
|
|
```text
|
|
code: RASTER_PROCESSING_UNAVAILABLE
|
|
message: Raster processing unavailable. Install rasterio and GDAL-compatible drivers to enable raster metadata extraction.
|
|
```
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/inspect`
|
|
|
|
Return raster inspect wrapper payload.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/stats`
|
|
|
|
Return raster band statistics payload.
|
|
|
|
If raster processing dependencies are unavailable:
|
|
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE`
|
|
- message: dependency-specific unavailable message.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/preview`
|
|
|
|
Preview readiness for raster layers.
|
|
|
|
If preview dependencies are unavailable:
|
|
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE`
|
|
- message: `Raster preview unavailable...`
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/clip`
|
|
|
|
Clip raster by selected area. Returns a `202`-style accepted job payload through the job wrapper (`jobs` create/read flow).
|
|
|
|
If raster processing dependencies are unavailable:
|
|
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE`
|
|
- message: `Raster processing unavailable. Install rasterio and GDAL-compatible drivers to enable raster processing operations.`
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/reproject`
|
|
|
|
Reproject raster dataset to another CRS.
|
|
|
|
Input:
|
|
|
|
- `target_crs` (default: `EPSG:31370`)
|
|
- `resampling` (`nearest`, `bilinear`, `cubic`; default `nearest`)
|
|
- `output_name`
|
|
|
|
Returns a job payload with derived dataset id in `result.output_dataset_id`.
|
|
|
|
Failure modes:
|
|
|
|
- code: `INVALID_PARAMETERS` for bad CRS or resampling
|
|
- code: `INVALID_DATASET_CRS` when source raster CRS is missing
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio is unavailable
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/indices/ndvi`
|
|
|
|
Compute NDVI from raster band pairs.
|
|
|
|
Input:
|
|
|
|
- `nir_band` (positive integer, 1-based)
|
|
- `red_band` (positive integer, 1-based)
|
|
- `output_name` (optional)
|
|
|
|
Returns a job payload with derived dataset id in `result.output_dataset_id`.
|
|
|
|
Failure modes:
|
|
|
|
- code: `INVALID_PARAMETERS` for non-positive/non-integer band indices
|
|
- code: `INVALID_PARAMETERS` for band index outside source band count
|
|
- code: `INVALID_DATASET_TYPE` when source is not raster
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio or numpy is unavailable
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/indices/ndwi`
|
|
|
|
Compute NDWI from raster band pairs.
|
|
|
|
Input:
|
|
|
|
- `nir_band` (positive integer, 1-based)
|
|
- `green_band` (positive integer, 1-based)
|
|
- `output_name` (optional)
|
|
|
|
Returns a job payload with derived dataset id in `result.output_dataset_id`.
|
|
|
|
Failure modes:
|
|
|
|
- code: `INVALID_PARAMETERS` for non-positive/non-integer band indices
|
|
- code: `INVALID_PARAMETERS` for band index outside source band count
|
|
- code: `INVALID_DATASET_TYPE` when source is not raster
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio or numpy is unavailable
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/indices/ndbi`
|
|
|
|
Compute NDBI from raster band pairs.
|
|
|
|
Input:
|
|
|
|
- `nir_band` (positive integer, 1-based)
|
|
- `swir_band` (positive integer, 1-based)
|
|
- `output_name` (optional)
|
|
|
|
Returns a job payload with derived dataset id in `result.output_dataset_id`.
|
|
|
|
Failure modes:
|
|
|
|
- code: `INVALID_PARAMETERS` for non-positive/non-integer band indices
|
|
- code: `INVALID_PARAMETERS` for band index outside source band count
|
|
- code: `INVALID_DATASET_TYPE` when source is not raster
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio or numpy is unavailable
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/tile`
|
|
|
|
Generate raster tiles and a manifest for downstream processing. Returns a job payload with `tile_set_id` and manifest metadata.
|
|
|
|
If raster processing dependencies are unavailable:
|
|
|
|
- code: `RASTER_PROCESSING_UNAVAILABLE`
|
|
- message: `Raster processing unavailable. Install rasterio and GDAL-compatible drivers to enable raster processing operations.`
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/clip`
|
|
|
|
Clip vector dataset to selected area.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/buffer`
|
|
|
|
Apply buffer distance to vector features.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/intersect`
|
|
|
|
Intersect source vector dataset with another vector dataset.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/stats`
|
|
|
|
Return vector stats (feature counts and geometry summary).
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/bbox`
|
|
|
|
Return vector bounds and feature count.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select`
|
|
|
|
Read-only spatial selection over persisted `vector_features`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"area_id": "optional persisted area UUID",
|
|
"limit": 250
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"selection_bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"selection_area_id": "present when area_id was requested",
|
|
"feature_count": 2,
|
|
"total_feature_count": 2,
|
|
"limit": 250,
|
|
"truncated": false,
|
|
"geojson": {
|
|
"type": "FeatureCollection",
|
|
"features": []
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
- Only vector/GeoJSON datasets are supported.
|
|
- Coordinates are EPSG:4326 longitude/latitude.
|
|
- `area_id` is optional and must belong to the route project. When present, the bbox remains the bounded preview extent but PostGIS filtering and configured aggregations use the persisted Area geometry exactly. This prevents a municipal or regional full-work-area query from counting objects in the surrounding bbox corners.
|
|
- Results are generated from persisted PostGIS `vector_features`, not from client-side map data.
|
|
- `feature_count` is the number of GeoJSON features returned in the bounded preview. `total_feature_count` is the exact number of persisted rows intersecting the requested bbox or persisted Area geometry.
|
|
- The response is capped by `limit` and returns `truncated=true` when `total_feature_count` exceeds the returned preview.
|
|
- `limit` is bounded to `1..1000`. Municipality-scale clients must page spatially by viewport instead of requesting an unbounded municipality FeatureCollection.
|
|
- The Map workspace uses this existing endpoint for vector datasets above 5,000 features. It starts delivery at zoom level 14, debounces `moveend` requests and explicitly reports `truncated=true` as a request to zoom further in. This is a client delivery policy, not a second API or persistence path.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select/derive`
|
|
|
|
Persists a bbox selection as a new derived vector dataset and indexes the
|
|
selected output into `vector_features`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"limit": 250,
|
|
"output_name": "selected-buildings"
|
|
}
|
|
```
|
|
|
|
Response: `DatasetRead` in the canonical API envelope.
|
|
|
|
Rules:
|
|
|
|
- Only vector/GeoJSON datasets are supported.
|
|
- Coordinates are EPSG:4326 longitude/latitude.
|
|
- The new dataset uses `dataset_role="derived"`, `source="operation:selection"`,
|
|
`source_name="map_selection"` and `derived_from_dataset_id` pointing to the
|
|
source dataset.
|
|
- The persisted GeoJSON properties retain source provenance as
|
|
`source_dataset_id` and `source_vector_feature_id`.
|
|
- Empty selections return `VECTOR_OPERATION_EMPTY_RESULT` and do not create a dataset.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/content`
|
|
|
|
Returns stored vector dataset content through the canonical API envelope.
|
|
Vector content is returned as GeoJSON/JSON payload data. Raster content is not
|
|
served through this endpoint.
|
|
|
|
## Jobs
|
|
|
|
### POST `/api/v1/projects/{project_id}/jobs`
|
|
|
|
Create a job.
|
|
|
|
### GET `/api/v1/projects/{project_id}/jobs`
|
|
|
|
List jobs.
|
|
|
|
### GET `/api/v1/projects/{project_id}/jobs/{job_id}`
|
|
|
|
Read job detail.
|
|
|
|
### GET `/api/v1/projects/{project_id}/jobs/{job_id}/status`
|
|
|
|
Read simplified job status payload.
|
|
|
|
## Provider registry
|
|
|
|
### GET `/api/v1/external/providers`
|
|
|
|
Returns all configured provider capability descriptors.
|
|
|
|
### GET `/api/v1/external/providers/capabilities`
|
|
|
|
Compatibility alias for listing provider capability descriptors.
|
|
|
|
### GET `/api/v1/external/providers/{provider_name}`
|
|
|
|
Returns one provider capability descriptor.
|
|
|
|
### GET `/api/v1/external/providers/{provider_name}/layers`
|
|
|
|
Returns the supported provider layers.
|
|
|
|
### GET `/api/v1/external/providers/{provider_name}/status`
|
|
|
|
Returns configured/status/limitation fields.
|
|
|
|
### POST `/api/v1/external/providers/{provider_name}/import`
|
|
|
|
Defines the future provider import contract. Sprint 7B does not perform live imports or write datasets.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid-or-local-id",
|
|
"area_id": "optional uuid-or-local-id",
|
|
"layers": ["buildings"],
|
|
"dataset_role": "optional source|reference"
|
|
}
|
|
```
|
|
|
|
GRB/OSM response:
|
|
|
|
```json
|
|
{
|
|
"provider_name": "grb",
|
|
"status": "not_configured",
|
|
"message": "No live GRB import is configured in Sprint 7B.",
|
|
"requested_layers": ["buildings"],
|
|
"dataset_id": null,
|
|
"dataset_role": "reference",
|
|
"source_name": "grb"
|
|
}
|
|
```
|
|
|
|
Manual and fixture providers point callers to existing upload/fixture flows. No provider writes directly to `vector_features`; all future provider output must flow through `DatasetService` and `VectorFeatureService`.
|
|
|
|
## External data fetchers
|
|
|
|
### POST `/api/v1/external/osm/fetch`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"area_id": "uuid",
|
|
"layers": ["buildings", "roads", "water", "green"]
|
|
}
|
|
```
|
|
|
|
### POST `/api/v1/external/grb/fetch`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"area_id": "uuid",
|
|
"layers": ["buildings"]
|
|
}
|
|
```
|
|
|
|
V1 may initially implement this as a service interface with a clear `not_configured` response until the exact WFS endpoint is wired.
|
|
|
|
Sprint 7B provider contract responses expose capabilities only. Providers must report:
|
|
|
|
```json
|
|
{
|
|
"provider_name": "osm",
|
|
"display_name": "OpenStreetMap",
|
|
"authority_level": "contextual",
|
|
"supported_layers": ["buildings", "roads", "water", "landuse"],
|
|
"supported_geometry_types": ["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
|
|
"supported_query_modes": ["area"],
|
|
"configured": false,
|
|
"status": "not_configured",
|
|
"limitation_message": "OSM live Overpass/download integration is not configured in Sprint 7B.",
|
|
"attribution": "OpenStreetMap contributors",
|
|
"license_note": "OpenStreetMap data is available under ODbL; attribution is required."
|
|
}
|
|
```
|
|
|
|
No GRB WFS, OSM Overpass or provider downloads are implemented in Sprint 7B.
|
|
|
|
## Demo workflow
|
|
|
|
### POST `/api/v1/demo/workflow`
|
|
|
|
Seeds an explicit offline demo workflow from local fixture files. This endpoint
|
|
does not fetch live GRB/OSM data and does not run AI inference. It creates or
|
|
returns:
|
|
|
|
- one demo project
|
|
- one demo AOI
|
|
- one fixture reference building dataset
|
|
- one fixture candidate/predicted building dataset
|
|
- one persisted QA/QC result with metric rows
|
|
|
|
The endpoint is idempotent for the named demo project.
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"area_id": "uuid",
|
|
"reference_dataset_id": "uuid",
|
|
"candidate_dataset_id": "uuid",
|
|
"quality_check_id": "uuid",
|
|
"metric_count": 6,
|
|
"status": "ready",
|
|
"message": "Demo workflow seeded from explicit local fixtures.",
|
|
"created": true
|
|
}
|
|
```
|
|
|
|
## Analysis
|
|
|
|
## Detection Lab
|
|
|
|
Sprint 8 implements Detection Lab foundation only. YOLO/PyTorch real inference is not enabled, no model is downloaded, and fixture detections require explicit fixture mode.
|
|
|
|
### Guided browser orchestration
|
|
|
|
The current frontend offers one guided building-analysis action, but does not add a parallel backend workflow endpoint. It deliberately composes the canonical contracts in this order:
|
|
|
|
1. optional explicit `POST /api/v1/projects/{project_id}/datasets/upload` for a georeferenced GeoTIFF;
|
|
2. `POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/tile` with 512 px tiles and 64 px overlap;
|
|
3. `GET /api/v1/detection/yolo/preflight` with the returned manifest and selected local model asset;
|
|
4. `POST /api/v1/detection/run` only after successful preflight;
|
|
5. persisted run, Detection list and Detection GeoJSON reads;
|
|
6. optional persisted reference QA through the existing detection QA endpoint.
|
|
|
|
The strict `POST /api/v1/detection/run` contract still requires `tile_manifest_path` for configured YOLO. The frontend does not create fake tiles, bypass tile limits, fetch external imagery or download model weights.
|
|
|
|
### GET `/api/v1/detection/models`
|
|
|
|
Returns object-detection model capability descriptors.
|
|
|
|
```json
|
|
{
|
|
"models": [
|
|
{
|
|
"model_id": "yolo-placeholder",
|
|
"display_name": "YOLO detector placeholder",
|
|
"framework": "ultralytics/pytorch",
|
|
"task_type": "object_detection",
|
|
"supported_classes": ["building", "road", "water", "landuse"],
|
|
"configured": false,
|
|
"status": "not_configured",
|
|
"limitation_message": "YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed.",
|
|
"version": null
|
|
},
|
|
{
|
|
"model_id": "yolo-configured",
|
|
"display_name": "Configured YOLO detector",
|
|
"framework": "ultralytics/pytorch",
|
|
"task_type": "object_detection",
|
|
"supported_classes": ["building", "road", "water", "landuse"],
|
|
"configured": false,
|
|
"status": "not_configured",
|
|
"limitation_message": "YOLO is disabled. Set YOLO_ENABLED=true and YOLO_MODEL_PATH to a local model file to enable inference.",
|
|
"version": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/detection/model-assets`
|
|
|
|
Returns local runtime model files discovered in the configured model directory.
|
|
This is a read-only catalog. GeoIntel never downloads, creates, mutates or
|
|
deletes model weights from this endpoint.
|
|
|
|
The backend scans `YOLO_MODELS_DIR` (default `/app/models`) and reports
|
|
supported local model files such as `.pt`, `.onnx` and `.engine`. The active
|
|
model is the file matching `YOLO_MODEL_PATH`.
|
|
|
|
Response data:
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"model_asset_id": "building-detector-pt",
|
|
"filename": "building-detector.pt",
|
|
"display_name": "building-detector",
|
|
"model_path": "/app/models/building-detector.pt",
|
|
"suffix": ".pt",
|
|
"framework": "ultralytics/pytorch",
|
|
"task_type": "object_detection",
|
|
"size_bytes": 123456,
|
|
"sha256": "sha256hex",
|
|
"active": true,
|
|
"status": "available",
|
|
"limitation_message": "Local runtime model asset. GeoIntel will not download or mutate model weights.",
|
|
"will_download_models": false
|
|
}
|
|
],
|
|
"total": 1,
|
|
"model_directory": "/app/models"
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/detection/yolo/preflight`
|
|
|
|
Returns a canonical envelope with read-only configured-YOLO runtime preflight
|
|
state. Optional query parameters:
|
|
|
|
- `tile_manifest_path`: existing raster tile manifest path to validate.
|
|
- `model_asset_id`: optional local model asset ID from
|
|
`GET /api/v1/detection/model-assets`; when supplied, preflight validates that
|
|
asset path instead of the default `YOLO_MODEL_PATH`.
|
|
- `check_model_load`: default `false`; when `true`, explicitly loads only the
|
|
configured local model file for compatibility smoke. It never downloads
|
|
weights and never runs inference.
|
|
|
|
Response data:
|
|
|
|
```json
|
|
{
|
|
"model_id": "yolo-configured",
|
|
"model_asset_id": null,
|
|
"model_path": null,
|
|
"tile_manifest_path": null,
|
|
"status": "not_configured",
|
|
"message": "YOLO_MODEL_PATH is not set. GeoIntel will not download model weights automatically.",
|
|
"checks": {
|
|
"enabled": true,
|
|
"dependencies_available": true,
|
|
"model_path_set": false,
|
|
"model_file_exists": null,
|
|
"model_load_requested": false,
|
|
"model_load_ok": null,
|
|
"manifest_path_set": null,
|
|
"manifest_valid": null,
|
|
"tile_paths_exist": null,
|
|
"tile_limit_ok": null
|
|
},
|
|
"runtime": {
|
|
"dependencies_assumed": false,
|
|
"model_directory": null,
|
|
"yolo_config_dir": "/app/storage/ultralytics",
|
|
"torch_version": "2.12.1",
|
|
"ultralytics_version": "8.4.88",
|
|
"cuda_available": false
|
|
},
|
|
"tile_count": 0,
|
|
"max_tiles": 100,
|
|
"will_download_models": false,
|
|
"will_run_inference": false
|
|
}
|
|
```
|
|
|
|
### POST `/api/v1/detection/run`
|
|
|
|
Creates a detection job and detection analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `DETECTION_MODEL_UNAVAILABLE` or `DETECTION_DEPENDENCY_UNAVAILABLE`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "yolo-placeholder",
|
|
"model_asset_id": null,
|
|
"confidence_threshold": 0.5,
|
|
"class_filter": ["building"],
|
|
"tile_manifest_path": null,
|
|
"parameters_json": {}
|
|
}
|
|
```
|
|
|
|
Sprint 8B configured YOLO mode uses `model_id: "yolo-configured"`. It requires:
|
|
|
|
- `YOLO_ENABLED=true`
|
|
- `YOLO_MODEL_PATH` pointing to an existing local model file
|
|
- backend optional AI dependencies installed with `geointel-backend[ai]`
|
|
- `tile_manifest_path` pointing to an existing raster tile manifest generated by the raster tile operation
|
|
|
|
`model_asset_id` may be supplied with `model_id: "yolo-configured"` to select a
|
|
specific local model file from the read-only model asset catalog. The backend
|
|
resolves the ID to a file inside the configured model directory and persists the
|
|
asset ID, path and SHA-256 in the job and analysis-run parameters for
|
|
reproducibility. Clients must not submit arbitrary model paths.
|
|
|
|
GeoIntel does not download model weights automatically. Configured YOLO runs read existing tile files from the manifest, convert YOLO pixel-space boxes to EPSG:4326 detection polygons and persist detections as first-class records.
|
|
|
|
Unavailable model response:
|
|
|
|
```json
|
|
{
|
|
"analysis_run_id": "uuid",
|
|
"job_id": "uuid",
|
|
"project_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "yolo-placeholder",
|
|
"status": "failed",
|
|
"detection_count": 0,
|
|
"error_code": "DETECTION_MODEL_UNAVAILABLE",
|
|
"message": "YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed."
|
|
}
|
|
```
|
|
|
|
Validation errors:
|
|
|
|
- `INVALID_DATASET_TYPE` when the dataset is not raster.
|
|
- `DETECTION_MODEL_NOT_FOUND` when the model id is unknown.
|
|
- `DETECTION_MODEL_ASSET_NOT_FOUND` when `model_asset_id` is not present in the configured model directory.
|
|
- `FIXTURE_MODE_REQUIRED` when `manual-fixture-detector` is requested without `parameters_json.fixture_mode=true`.
|
|
- `DETECTION_TILE_MANIFEST_REQUIRED` when `yolo-configured` is requested without `tile_manifest_path`.
|
|
- `DETECTION_TILE_MANIFEST_NOT_FOUND` when the provided manifest path does not exist.
|
|
- `DETECTION_TILE_MANIFEST_INVALID` when the manifest cannot be parsed or lacks tile metadata.
|
|
- `DETECTION_TILE_LIMIT_EXCEEDED` when the manifest exceeds `YOLO_MAX_TILES`.
|
|
- Configured YOLO inference forwards `YOLO_MAX_DETECTIONS` to Ultralytics
|
|
`max_det` and defaults to `1000` so dense building AOIs are not silently
|
|
limited by the upstream default of 300 detections before persisted QA/QC.
|
|
- Configured YOLO applies cross-tile duplicate suppression after pixel boxes are
|
|
converted to EPSG:4326 geometries and before `Detection` rows are persisted.
|
|
Same-class candidates are confidence-sorted and lower-confidence candidates
|
|
with geometry IoU greater than or equal to
|
|
`YOLO_DUPLICATE_IOU_THRESHOLD` are suppressed. The default is `0.5`; `0`
|
|
disables this GeoIntel-side post-processing for debugging.
|
|
- `DETECTION_DEPENDENCY_UNAVAILABLE` when YOLO dependencies are not installed.
|
|
- `DETECTION_MODEL_LOAD_FAILED` when the local model file exists but cannot be loaded.
|
|
|
|
Fixture detector mode is test/demo-only. It persists only explicit `parameters_json.fixture_detections` entries and is never invoked automatically.
|
|
|
|
### GET `/api/v1/detection/runs/{analysis_run_id}`
|
|
|
|
Returns one detection analysis run.
|
|
|
|
### GET `/api/v1/detection/runs`
|
|
|
|
Returns detection analysis runs, optionally filtered by `project_id` and `dataset_id`.
|
|
|
|
### GET `/api/v1/detection/runs/{analysis_run_id}/detections`
|
|
|
|
Returns persisted detections for a detection analysis run. Optional filters:
|
|
|
|
- `dataset_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/detection/datasets/{dataset_id}/detections`
|
|
|
|
Returns persisted detections for a raster dataset. Optional filters:
|
|
|
|
- `analysis_run_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/detection/detections/{detection_id}`
|
|
|
|
Returns one persisted detection.
|
|
|
|
### GET `/api/v1/detection/runs/{analysis_run_id}/geojson`
|
|
|
|
Returns persisted detections for a run as a GeoJSON FeatureCollection. Geometry comes from persisted PostGIS detection geometry in EPSG:4326.
|
|
|
|
Each feature includes:
|
|
|
|
- `detection_id`
|
|
- `class_name`
|
|
- `confidence`
|
|
- `model_name`
|
|
- `model_version`
|
|
- `analysis_run_id`
|
|
- `dataset_id`
|
|
- `job_id`
|
|
- `source_tile_path`
|
|
- `bbox_json`
|
|
|
|
### GET `/api/v1/detection/datasets/{dataset_id}/geojson`
|
|
|
|
Returns persisted detections for a dataset as a GeoJSON FeatureCollection. Optional filters match the detection list endpoint.
|
|
|
|
### POST `/api/v1/detection/runs/{analysis_run_id}/qa/reference`
|
|
|
|
Compares persisted detection geometries from an analysis run against persisted `vector_features` from a reference vector dataset.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"reference_dataset_id": "uuid",
|
|
"iou_threshold": 0.5,
|
|
"class_name": "building",
|
|
"min_confidence": 0.5
|
|
}
|
|
```
|
|
|
|
Response persists a `quality_check` and `metrics` rows through the existing QA/QC persistence architecture and returns:
|
|
|
|
- `precision`
|
|
- `recall`
|
|
- `f1_score`
|
|
- `mean_iou`
|
|
- `false_positives`
|
|
- `false_negatives`
|
|
- `quality_check_id`
|
|
|
|
Configured-YOLO QA automatically reads `tile_manifest_path` from the persisted
|
|
`AnalysisRun.parameters_json`. Candidate and reference geometries are clipped
|
|
to the union of the manifest's tile bounds after explicit CRS transformation to
|
|
EPSG:4326. Before reference geometries are materialized, the service applies
|
|
that coverage with an indexed PostGIS `ST_Intersects` predicate. The full
|
|
dataset count is retained separately so raw/evaluated/excluded counts remain
|
|
auditable without transferring a regional reference dataset to Python. The
|
|
response additionally returns:
|
|
|
|
- `candidate_feature_count_raw` and `reference_feature_count_raw`;
|
|
- `coverage`, including raw/evaluated/excluded/boundary-clipped population
|
|
counts, tile count, source CRS values and coverage mode;
|
|
- `box_to_footprint_diagnostics`, which compares candidate boxes with reference
|
|
envelopes at the same IoU threshold.
|
|
|
|
The canonical precision, recall, F1 and mean IoU always remain based on
|
|
candidate geometry versus the persisted reference footprint. Envelope results
|
|
are explicitly `diagnostic_only` and are persisted in
|
|
`quality_checks.findings_json`; they never replace or inflate canonical metrics.
|
|
|
|
Configured-YOLO QA fails closed with `DETECTION_QA_COVERAGE_UNAVAILABLE` when
|
|
manifest provenance is absent, `DETECTION_QA_COVERAGE_MISMATCH` when it belongs
|
|
to another raster, `DETECTION_QA_COVERAGE_INVALID` when bounds/CRS are invalid,
|
|
or `REFERENCE_FEATURES_OUTSIDE_COVERAGE` when no reference polygons overlap the
|
|
actual inference coverage. Explicit fixture/legacy runs without a manifest keep
|
|
the documented unbounded comparison behavior.
|
|
|
|
If the reference dataset has no persisted vector features, the endpoint returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
|
|
|
|
#### Future analysis route: `/api/v1/analysis/building-stats`
|
|
|
|
Not implemented in the active API surface. Future input is expected to combine
|
|
an area with a vector building layer.
|
|
|
|
#### Future analysis route: `/api/v1/analysis/object-detection`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"area_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "optional uuid",
|
|
"classes": ["building"],
|
|
"confidence_threshold": 0.35,
|
|
"tile_size": 640,
|
|
"overlap": 64
|
|
}
|
|
```
|
|
|
|
Response: `AnalysisRunRead`.
|
|
|
|
#### Future analysis route: `/api/v1/analysis/segmentation`
|
|
|
|
Same pattern as object detection, but output includes masks and polygonized geometries.
|
|
|
|
## Segmentation Lab
|
|
|
|
Sprint 9 implements Segmentation Lab foundation only. Real SAM and YOLO-seg inference are not enabled, no model is downloaded, and fixture segmentations require explicit fixture mode.
|
|
|
|
### GET `/api/v1/segmentation/models`
|
|
|
|
Returns segmentation model capability descriptors:
|
|
|
|
- `segmentation-placeholder`: `not_configured`
|
|
- `fixture-segmenter`: configured for explicit test/demo fixtures only
|
|
- `yolo-seg-configured`: `not_configured`
|
|
- `sam-configured`: `not_configured`
|
|
|
|
### POST `/api/v1/segmentation/run`
|
|
|
|
Creates a segmentation job and segmentation analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `SEGMENTATION_MODEL_UNAVAILABLE`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "segmentation-placeholder",
|
|
"confidence_threshold": 0.5,
|
|
"class_filter": ["vegetation"],
|
|
"tile_manifest_path": null,
|
|
"parameters_json": {}
|
|
}
|
|
```
|
|
|
|
Fixture segmenter mode is test/demo-only. It persists only explicit `parameters_json.fixture_segmentations` entries when `parameters_json.fixture_mode=true`; it is never invoked automatically and does not represent production inference.
|
|
|
|
Validation errors:
|
|
|
|
- `INVALID_DATASET_TYPE` when the dataset is not raster.
|
|
- `SEGMENTATION_MODEL_NOT_FOUND` when the model id is unknown.
|
|
- `FIXTURE_MODE_REQUIRED` when `fixture-segmenter` is requested without `parameters_json.fixture_mode=true`.
|
|
- `INVALID_FIXTURE_SEGMENTATIONS` when fixture payloads are not a list.
|
|
- `INVALID_FIXTURE_GEOMETRY` when fixture geometry is empty, invalid or not Polygon/MultiPolygon.
|
|
|
|
### GET `/api/v1/segmentation/runs`
|
|
|
|
Returns segmentation analysis runs, optionally filtered by `project_id` and `dataset_id`.
|
|
|
|
### GET `/api/v1/segmentation/runs/{analysis_run_id}`
|
|
|
|
Returns one segmentation analysis run.
|
|
|
|
### GET `/api/v1/segmentation/runs/{analysis_run_id}/segmentations`
|
|
|
|
Returns persisted segmentation records for a segmentation analysis run. Optional filters:
|
|
|
|
- `dataset_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/segmentation/datasets/{dataset_id}/segmentations`
|
|
|
|
Returns persisted segmentation records for a raster dataset. Optional filters:
|
|
|
|
- `analysis_run_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/segmentation/segmentations/{segmentation_id}`
|
|
|
|
Returns one persisted segmentation record.
|
|
|
|
### GET `/api/v1/segmentation/runs/{analysis_run_id}/geojson`
|
|
|
|
Returns persisted segmentations for a run as a GeoJSON FeatureCollection. Geometry comes from persisted PostGIS segmentation geometry in EPSG:4326.
|
|
|
|
Each feature includes:
|
|
|
|
- `segmentation_id`
|
|
- `class_name`
|
|
- `confidence`
|
|
- `area_m2`
|
|
- `model_name`
|
|
- `model_version`
|
|
- `analysis_run_id`
|
|
- `dataset_id`
|
|
- `job_id`
|
|
- `source_tile_path`
|
|
- `tile_index`
|
|
- `mask_path`
|
|
- `bbox_json`
|
|
- `provenance_json`
|
|
|
|
### GET `/api/v1/segmentation/datasets/{dataset_id}/geojson`
|
|
|
|
Returns persisted segmentations for a dataset as a GeoJSON FeatureCollection. Optional filters match the segmentation list endpoint.
|
|
|
|
### POST `/api/v1/segmentation/runs/{analysis_run_id}/qa/reference`
|
|
|
|
Compares persisted segmentation geometries from an analysis run against persisted `vector_features` from a reference vector dataset.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"reference_dataset_id": "uuid",
|
|
"iou_threshold": 0.5,
|
|
"class_name": "vegetation",
|
|
"min_confidence": 0.5
|
|
}
|
|
```
|
|
|
|
Response persists a `quality_check` and `metrics` rows through the existing QA/QC persistence architecture and returns precision, recall, F1, mean IoU and false positive/negative counts.
|
|
|
|
If the segmentation run has no persisted geometries, the endpoint returns `SEGMENTATIONS_NOT_FOUND`. If the reference dataset has no persisted vector features, it returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
|
|
|
|
### POST `/api/v1/analysis/change-detection`
|
|
|
|
Compares two persisted vector datasets in the same project and returns a synchronous
|
|
job envelope. This is a lightweight V1 foundation for added/removed object review,
|
|
not a temporal run-history engine.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"source_dataset_id": "uuid",
|
|
"target_dataset_id": "uuid",
|
|
"iou_threshold": 0.8,
|
|
"include_unchanged": true
|
|
}
|
|
```
|
|
|
|
Response is a canonical API envelope containing a `JobRead` payload. On success,
|
|
`result_json` contains:
|
|
|
|
```json
|
|
{
|
|
"source_dataset_id": "uuid",
|
|
"target_dataset_id": "uuid",
|
|
"source_feature_count": 2,
|
|
"target_feature_count": 2,
|
|
"added_count": 1,
|
|
"removed_count": 1,
|
|
"unchanged_count": 1,
|
|
"iou_threshold": 0.8,
|
|
"warnings": [],
|
|
"generated_at": "2026-06-16T00:00:00Z",
|
|
"geojson": {
|
|
"type": "FeatureCollection",
|
|
"features": []
|
|
}
|
|
}
|
|
```
|
|
|
|
Change detection prefers persisted `vector_features`. If an older vector dataset
|
|
has no persisted vector rows, it falls back to the stored GeoJSON artifact and
|
|
adds a warning to `result_json.warnings`. Supported comparable geometry types are
|
|
`Polygon` and `MultiPolygon`; point/line geometries return `UNSUPPORTED_GEOMETRY`.
|
|
|
|
GeoJSON feature properties include:
|
|
|
|
- `change_type`: `added`, `removed` or `unchanged`
|
|
- `source_dataset_id`
|
|
- `target_dataset_id`
|
|
- `source_feature_id`
|
|
- `target_feature_id`
|
|
- `iou`
|
|
|
|
Limitations:
|
|
|
|
- No live GRB/OSM/Sentinel fetching.
|
|
- No fake object lifecycle classification.
|
|
- No `changed` classification without durable object ids/versioning.
|
|
- No first-class change table yet; the current output is stored in job
|
|
`result_json` and rendered in the frontend map.
|
|
|
|
## QA/QC
|
|
|
|
### POST `/api/v1/qa/detections-vs-reference`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"candidate_dataset_id": "uuid",
|
|
"reference_dataset_id": "uuid",
|
|
"iou_threshold": 0.5,
|
|
"area_id": "optional uuid"
|
|
}
|
|
```
|
|
|
|
Response is wrapped in the job envelope. On success, `result_json` includes precision, recall, F1, mean IoU, false positives, false negatives and `quality_check_id`.
|
|
|
|
Sprint 111 also includes feature-level evidence arrays for map/review handoff:
|
|
|
|
- `match_evidence`: matched candidate/reference feature ids with IoU.
|
|
- `false_positive_evidence`: unmatched candidate feature ids.
|
|
- `false_negative_evidence`: unmatched reference feature ids.
|
|
|
|
These arrays are derived from the same persisted/source geometries used for IoU matching. They are not separate QA records yet; they are persisted inside `quality_checks.findings_json`.
|
|
|
|
Sprint 7A persists the QA/QC result as:
|
|
|
|
- `jobs`: execution state.
|
|
- `quality_checks`: domain result.
|
|
- `metrics`: individual measurements.
|
|
|
|
Future Detection and Segmentation flows may add an `analysis_run_id` path without replacing persisted quality checks.
|
|
|
|
### GET `/api/v1/projects/{project_id}/quality-checks`
|
|
|
|
Lists persisted QA/QC quality checks for a project with metric rows.
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"id": "uuid",
|
|
"project_id": "uuid",
|
|
"job_id": "uuid-or-null",
|
|
"analysis_run_id": "uuid-or-null",
|
|
"candidate_dataset_id": "uuid-or-null",
|
|
"reference_dataset_id": "uuid",
|
|
"check_type": "demo_candidate_vs_reference",
|
|
"status": "ok",
|
|
"score": 0.5,
|
|
"parameters_json": {},
|
|
"findings_json": {
|
|
"matches": 1,
|
|
"false_positives": 1,
|
|
"false_negatives": 1,
|
|
"match_evidence": [
|
|
{
|
|
"candidate_feature_id": "candidate-feature-id",
|
|
"reference_feature_id": "reference-feature-id",
|
|
"iou": 0.83
|
|
}
|
|
],
|
|
"false_positive_evidence": [
|
|
{
|
|
"candidate_feature_id": "candidate-extra-id"
|
|
}
|
|
],
|
|
"false_negative_evidence": [
|
|
{
|
|
"reference_feature_id": "reference-missing-id"
|
|
}
|
|
]
|
|
},
|
|
"metrics": [
|
|
{
|
|
"metric_key": "precision",
|
|
"metric_value": 0.5
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"total": 1,
|
|
"limit": 50,
|
|
"offset": 0
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/evidence/geojson`
|
|
|
|
Returns a canonical envelope containing a read-only QA/QC evidence overlay for a
|
|
persisted quality check. The endpoint reads feature ids from
|
|
`quality_checks.findings_json.match_evidence`,
|
|
`false_positive_evidence` and `false_negative_evidence`, resolves them against
|
|
persisted candidate/reference geometries and returns a GeoJSON FeatureCollection.
|
|
|
|
Supported resolution paths:
|
|
|
|
- dataset QA candidate/reference geometries from `vector_features`;
|
|
- detection QA candidate geometries from persisted `detections`;
|
|
- segmentation QA candidate geometries from persisted `segmentations`;
|
|
- reference geometries from persisted `vector_features`.
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"quality_check_id": "uuid",
|
|
"project_id": "uuid",
|
|
"candidate_dataset_id": "uuid-or-null",
|
|
"reference_dataset_id": "uuid",
|
|
"analysis_run_id": "uuid-or-null",
|
|
"feature_count": 4,
|
|
"warnings": [],
|
|
"geojson": {
|
|
"type": "FeatureCollection",
|
|
"features": [
|
|
{
|
|
"type": "Feature",
|
|
"id": "match_candidate:feature-id",
|
|
"geometry": {},
|
|
"properties": {
|
|
"qa_evidence_role": "match_candidate",
|
|
"quality_check_id": "uuid",
|
|
"candidate_feature_id": "candidate-feature-id",
|
|
"reference_feature_id": "reference-feature-id",
|
|
"iou": 0.83
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
`qa_evidence_role` is one of `match_candidate`, `match_reference`,
|
|
`false_positive` or `false_negative`. Missing persisted feature ids are reported
|
|
in `warnings`; no fake geometries are produced.
|
|
|
|
Detection-backed candidate evidence also exposes provenance read from the
|
|
persisted `detections` row: `detection_id`, `job_id`, `confidence`,
|
|
`model_name`, `model_version`, `source_tile_path` and `bbox_json`. Existing
|
|
`properties_json` fields such as `tile_index` remain present. Segmentation-backed
|
|
candidate evidence exposes the equivalent persisted model/source fields plus
|
|
`segmentation_id`, `mask_path` and `area_m2`. These are additive GeoJSON
|
|
properties; the canonical envelope and endpoint path are unchanged.
|
|
|
|
For detection QA, false-positive and false-negative evidence properties also
|
|
include `review_decision`, `review_notes`, `reviewed_by` and `reviewed_at`.
|
|
Missing review rows are represented as `review_decision=unreviewed`. Evidence
|
|
resolution is bounded to identifiers stored by the selected quality check.
|
|
|
|
### GET `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/reviews`
|
|
|
|
Returns the paginated operator review queue for a persisted
|
|
`detections_vs_reference` quality check. Optional query parameters are
|
|
`evidence_role=false_positive|false_negative`, `decision`, `reviewed=true|false`,
|
|
`limit` (1-200) and `offset`. Items derive only from persisted QA evidence.
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"items": [{
|
|
"id": null,
|
|
"project_id": "uuid",
|
|
"quality_check_id": "uuid",
|
|
"analysis_run_id": "uuid",
|
|
"evidence_role": "false_positive",
|
|
"evidence_feature_id": "detection-uuid",
|
|
"detection_id": "detection-uuid",
|
|
"decision": "unreviewed",
|
|
"confidence": 0.62,
|
|
"class_name": "building"
|
|
}],
|
|
"total": 1,
|
|
"limit": 50,
|
|
"offset": 0,
|
|
"summary": {
|
|
"total": 73,
|
|
"reviewed": 0,
|
|
"remaining": 73,
|
|
"false_positive_total": 17,
|
|
"false_negative_total": 56,
|
|
"decision_counts": {"unreviewed": 73}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### POST `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/reviews`
|
|
|
|
Creates or updates one durable operator decision. The evidence id must belong
|
|
to the quality check and resolve to the persisted Detection or reference
|
|
VectorFeature. False-positive and false-negative roles accept only their
|
|
role-specific decisions.
|
|
|
|
```json
|
|
{
|
|
"evidence_role": "false_positive",
|
|
"evidence_feature_id": "detection-uuid",
|
|
"decision": "qa_alignment_mismatch",
|
|
"notes": "The detection box overlaps the irregular GRB footprint.",
|
|
"reviewed_by": "operator"
|
|
}
|
|
```
|
|
|
|
Allowed decisions are `confirmed_model_false_positive`,
|
|
`confirmed_model_false_negative`, `reference_gap_or_change`,
|
|
`qa_alignment_mismatch`, `imagery_obscured_or_uncertain`, `uncertain` and
|
|
`unreviewed`. Invalid role/decision combinations return
|
|
`INVALID_DETECTION_REVIEW_DECISION`.
|
|
|
|
## Exports
|
|
|
|
### POST `/api/v1/exports/geojson`
|
|
|
|
Export detections, segmentations or vector layer to GeoJSON.
|
|
|
|
Dataset vector export request:
|
|
|
|
```json
|
|
{
|
|
"export_kind": "dataset",
|
|
"dataset_id": "uuid",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Map vector selection export request:
|
|
|
|
```json
|
|
{
|
|
"export_kind": "vector_selection",
|
|
"dataset_id": "uuid",
|
|
"bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"limit": 250,
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Detection run export request:
|
|
|
|
```json
|
|
{
|
|
"export_kind": "detection_run",
|
|
"analysis_run_id": "uuid",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Segmentation run export request:
|
|
|
|
```json
|
|
{
|
|
"export_kind": "segmentation_run",
|
|
"analysis_run_id": "uuid",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Response persists an `exports` row and writes a deterministic JSON artifact:
|
|
|
|
```json
|
|
{
|
|
"export_id": "uuid",
|
|
"path": "storage/exports/{project_id}/datasets/{target}/{name}.geojson",
|
|
"status": "ready",
|
|
"export_type": "dataset_geojson",
|
|
"metadata_json": {
|
|
"source": "dataset",
|
|
"feature_count": 0
|
|
}
|
|
}
|
|
```
|
|
|
|
Vector dataset exports use the stored dataset GeoJSON. Detection and
|
|
segmentation exports use persisted first-class geometry records and the
|
|
existing Detection/Segmentation GeoJSON conversion services. Vector selection
|
|
exports query persisted PostGIS `vector_features` with the supplied EPSG:4326
|
|
bbox, write the selected FeatureCollection as a `vector_selection_geojson`
|
|
artifact, and persist bbox/feature-count metadata in the export record. Raster
|
|
datasets are rejected for dataset and selection GeoJSON export.
|
|
|
|
### POST `/api/v1/exports/metadata`
|
|
|
|
Exports project metadata JSON for projects, datasets, persisted QA/QC summary
|
|
rows and existing export history.
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/exports/projects/{project_id}/exports`
|
|
|
|
Lists persisted export records for a project.
|
|
|
|
### GET `/api/v1/exports/{export_id}`
|
|
|
|
Returns one persisted export record.
|
|
|
|
### GET `/api/v1/exports/{export_id}/content`
|
|
|
|
Returns the stored JSON artifact content through the standard API envelope.
|
|
HTML report artifacts are intentionally download-only through `/download`.
|
|
Calling `/content` for `project_report_html` returns:
|
|
|
|
```text
|
|
code: EXPORT_CONTENT_UNSUPPORTED
|
|
message: Export content preview is only available for JSON and GeoJSON artifacts. Download HTML report artifacts instead.
|
|
```
|
|
|
|
### GET `/api/v1/exports/{export_id}/download`
|
|
|
|
Downloads the stored JSON/GeoJSON/HTML export artifact as a raw file response
|
|
with a `Content-Disposition` attachment filename. JSON and GeoJSON artifacts
|
|
use `application/json`; HTML report artifacts use `text/html`. This endpoint
|
|
intentionally does not use the JSON envelope because it is a browser/file-download
|
|
path; callers that need canonical API JSON should use `/content` for JSON/GeoJSON
|
|
artifacts.
|
|
|
|
#### Future export route: `/api/v1/exports/yolo`
|
|
|
|
Export annotations/detections to YOLO format.
|
|
|
|
### POST `/api/v1/exports/report`
|
|
|
|
Creates a lightweight HTML project report artifact from persisted project,
|
|
dataset, V1 readiness summary, QA/QC summary, known limitations and export
|
|
history state. This does not create a PDF and does not introduce a report
|
|
designer.
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Response persists an `exports` row with `export_type:
|
|
project_report_html`. Download the report through:
|
|
|
|
```text
|
|
GET /api/v1/exports/{export_id}/download
|
|
```
|
|
|
|
PDF/report-designer functionality can be added after core GeoAI workflows work.
|
|
|
|
## Temporal datasets and area evolution
|
|
|
|
Temporal metadata describes the source observation, not API run history. A
|
|
snapshot is a normal persisted dataset grouped by `temporal_series_key` and
|
|
ordered by `observed_at`. Optional validity uses `valid_from` and `valid_to`;
|
|
`temporal_granularity` is `snapshot`, `day`, `month`, `year` or `period`.
|
|
|
|
Dataset upload accepts those temporal fields plus `source_version`. When a
|
|
dataset declares `source_metadata.selection_aggregation`, the vector bbox
|
|
selection response also contains a `summary` with metric label/value/unit,
|
|
aggregation method, feature count, estimate status and an optional warning.
|
|
Supported PostGIS aggregations are feature count, intersection area,
|
|
intersection length, numeric sum and area-weighted numeric sum. Area and length
|
|
are measured after transformation to EPSG:31370.
|
|
|
|
### PATCH `/api/v1/projects/{project_id}/datasets/{dataset_id}/temporal`
|
|
|
|
Updates the temporal provenance of an existing dataset. Series key and
|
|
observation date are required together. It does not alter features or
|
|
manufacture a historical observation.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/versions`
|
|
|
|
Lists immutable storage/provenance versions. Uploads and derived datasets
|
|
create version 1 in the same persistence transaction.
|
|
|
|
### GET `/api/v1/projects/{project_id}/temporal/series`
|
|
|
|
Returns dated project series in the canonical envelope. Each item contains its
|
|
source/layer identity, first and last observations and ordered datasets.
|
|
|
|
### POST `/api/v1/projects/{project_id}/temporal/compare`
|
|
|
|
```json
|
|
{
|
|
"earlier_dataset_id": "uuid",
|
|
"later_dataset_id": "uuid",
|
|
"bbox": {"west": 5.0, "south": 51.0, "east": 5.2, "north": 51.2},
|
|
"preview_limit": 500
|
|
}
|
|
```
|
|
|
|
Both datasets must belong to the project and the same temporal series, with
|
|
the earlier observation preceding the later one. The response contains source
|
|
snapshot references, selection bbox, earlier/later metric values,
|
|
absolute/percentage change, estimate status, warnings and GeoJSON evidence.
|
|
Added/removed/modified object changes are calculated only when source
|
|
provenance declares stable feature identities; otherwise
|
|
`object_changes.available=false` and no object history is inferred.
|