/detection/runs/{id}/detections and its GeoJSON sibling returned every
persisted detection, as did the segmentation equivalents. A regional run holds
tens of thousands, and these are the endpoints the results table and the map
overlay call after every run.
They now take limit and offset, default to 2.000, and report total, limit,
offset and truncated so the complete population stays visible while what is
transferred does not. The GeoJSON responses carry the same window in a
geointel_result_window foreign member.
Rows are ordered by confidence, so a capped overlay draws the strongest
detections rather than an arbitrary slice, and the lab says how many of how
many are being shown rather than silently presenting a page as the whole run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2920 lines
113 KiB
Markdown
2920 lines
113 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.
|
|
- Every successful JSON endpoint has a concrete Pydantic response model and
|
|
uses the canonical `{"data": ...}` envelope. Readiness runs an OpenAPI audit
|
|
that rejects free-form dictionary responses and envelope drift.
|
|
- The only successful non-envelope responses are `/health`, `/health/live`,
|
|
`/health/ready`, the four documented persisted-raster PNG endpoints and the
|
|
export artifact download endpoint.
|
|
|
|
## 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"
|
|
}
|
|
```
|
|
|
|
## Operator authentication and guest demo
|
|
|
|
Authentication remains an optional single-operator access gate, not multi-user
|
|
account management or tenant isolation. When `GEOINTEL_AUTH_ENABLED=true`,
|
|
every `/api/v1/*` request except the four authentication endpoints below
|
|
requires a valid signed `geointel_session` cookie. Missing, expired or modified
|
|
sessions return HTTP 401 with `AUTHENTICATION_REQUIRED`. Direct loopback calls
|
|
to the backend without proxy headers remain available to trusted in-container
|
|
operator tools; the backend is bound to loopback in the all-in-one runtime.
|
|
|
|
The runtime stores only a PBKDF2-SHA256 operator password hash and an
|
|
independent session-signing secret. The browser receives an HttpOnly,
|
|
SameSite=Strict, time-limited cookie. Five failed operator-login attempts for
|
|
one client/username combination within five minutes temporarily return HTTP
|
|
429 `LOGIN_RATE_LIMITED`.
|
|
|
|
Optional guest access is a configuration-gated demonstration mode. It creates
|
|
a shorter signed session with role `guest`, scopes that session to the
|
|
idempotently seeded demo project and blocks mutating operator routes. Project
|
|
listing is filtered to the bound demo project. The frontend exposes the same
|
|
exploration, assistant, model-selection, analysis, QA and export workspaces as
|
|
an operator. Model catalogs are globally readable; every run, result and export
|
|
request remains explicitly bound to the demo-project UUID. Project and area
|
|
management, uploads, source/runtime configuration, evidence review and other
|
|
administrative mutations remain unavailable. This is deliberately **not**
|
|
a substitute for user accounts, authorization or tenant isolation; expose it
|
|
only on a dedicated demo installation without private or operational data.
|
|
|
|
### GET `/api/v1/auth/session`
|
|
|
|
Public session probe used by the frontend before it mounts the workbench.
|
|
When authentication is disabled, `authenticated` is true and
|
|
`authentication_required` is false so local development retains its existing
|
|
direct workflow. `guest_access_enabled` tells the landing page whether it may
|
|
show the guest action.
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"authentication_required": true,
|
|
"authenticated": false,
|
|
"username": null,
|
|
"expires_at": null,
|
|
"role": null,
|
|
"guest_access_enabled": true,
|
|
"guest_project_id": null
|
|
}
|
|
}
|
|
```
|
|
|
|
Authenticated operator sessions return `role: "operator"`. Guest sessions
|
|
return `role: "guest"` and the UUID of their bound demo project in
|
|
`guest_project_id`.
|
|
|
|
### POST `/api/v1/auth/login`
|
|
|
|
```json
|
|
{
|
|
"username": "operator",
|
|
"password": "user-supplied secret"
|
|
}
|
|
```
|
|
|
|
Successful login sets the session cookie and returns the authenticated session
|
|
shape. Invalid credentials return HTTP 401 `INVALID_CREDENTIALS`; username
|
|
existence is not disclosed.
|
|
|
|
### POST `/api/v1/auth/guest`
|
|
|
|
No request body is required. The endpoint is available only when both
|
|
`GEOINTEL_AUTH_ENABLED=true` and `GEOINTEL_GUEST_ACCESS_ENABLED=true`. It
|
|
idempotently prepares the canonical demo workflow, creates a short-lived guest
|
|
session bound to that project and returns the normal session shape.
|
|
|
|
Disabled guest access returns HTTP 403 `GUEST_ACCESS_DISABLED`. A guest request
|
|
for a different project returns HTTP 403 `GUEST_PROJECT_SCOPE_REQUIRED`; a
|
|
blocked mutation returns HTTP 403 `GUEST_READ_ONLY`. Unscoped read routes that
|
|
are not needed by the demo return HTTP 403 `GUEST_ROUTE_NOT_AVAILABLE`.
|
|
Guest reads are limited to the filtered project list, provider/model metadata,
|
|
the bound project tree and project-scoped detection, segmentation and export
|
|
results. An explicit set of `POST` selection, assistant, AI/QA and export routes
|
|
is available for that bound demo project. Unscoped analysis routes require the
|
|
same UUID as a `project_id` query parameter; cross-project values fail before
|
|
route execution. Coverage resolution additionally verifies the `project_id` in
|
|
the request body against the guest-session scope.
|
|
|
|
### POST `/api/v1/auth/logout`
|
|
|
|
Clears the browser cookie and returns an unauthenticated session. Logout is
|
|
idempotent and remains callable when the current cookie is missing or expired.
|
|
|
|
## Health
|
|
|
|
### GET `/health/live`
|
|
|
|
Returns process liveness only. It never queries PostgreSQL.
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "geointel-backend",
|
|
"version": "1.0.0",
|
|
"build_sha": null,
|
|
"build_time": null
|
|
}
|
|
```
|
|
|
|
### GET `/health`
|
|
|
|
Backward-compatible alias for dependency readiness.
|
|
|
|
### GET `/health/ready`
|
|
|
|
Returns dependency readiness. Both readiness routes return HTTP 503 when the
|
|
database, PostGIS, single migration head or writable storage check is
|
|
degraded. Docker uses `/health/ready`.
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "geointel-backend",
|
|
"version": "1.0.0",
|
|
"database": "ok",
|
|
"postgis": "ok:3.x",
|
|
"migration": "ok:202607160001",
|
|
"storage": "ok",
|
|
"checks": {
|
|
"database": "ok",
|
|
"postgis": "ok:3.x",
|
|
"migration": "ok:202607160001",
|
|
"storage": "ok"
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/system/capabilities`
|
|
|
|
Returns enabled feature flags and tool availability in the canonical data
|
|
envelope. PostGIS and configured YOLO state are derived at runtime.
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"postgis": true,
|
|
"rasterio": true,
|
|
"geopandas": true,
|
|
"yolo": true,
|
|
"yolo_status": "configured",
|
|
"sam": false,
|
|
"grb": "bounded",
|
|
"sentinel": "planned",
|
|
"version": "1.0.0",
|
|
"build_sha": null,
|
|
"providers": []
|
|
}
|
|
}
|
|
```
|
|
|
|
## Projects
|
|
|
|
### GET `/api/v1/projects`
|
|
|
|
Returns active projects by default with `limit`/`offset` pagination. The
|
|
optional `status` query accepts `active`, `archived` or `all`; deleted projects
|
|
are never returned. Optional exact `name` filtering supports stable lookup of
|
|
a canonical operational workspace without depending on its position among
|
|
newer operator or benchmark projects:
|
|
|
|
```text
|
|
GET /api/v1/projects?name=Belgium%20and%20North%20Sea%20Workbench&limit=1
|
|
GET /api/v1/projects?status=archived&limit=50
|
|
```
|
|
|
|
### POST `/api/v1/projects`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"name": "Geel building detection demo",
|
|
"description": "Detect buildings and validate against GRB",
|
|
"region": "Belgium and Belgian North Sea"
|
|
}
|
|
```
|
|
|
|
Response: `ProjectRead`.
|
|
|
|
### GET `/api/v1/projects/{project_id}`
|
|
|
|
Returns one project with summary counts.
|
|
|
|
### PATCH `/api/v1/projects/{project_id}`
|
|
|
|
Updates name, description, region or the ordinary lifecycle status. The status
|
|
can only be `active` or `archived`. Archiving keeps datasets, jobs, analyses,
|
|
quality checks and exports intact while removing the workspace from the
|
|
default active-project list.
|
|
|
|
### DELETE `/api/v1/projects/{project_id}`
|
|
|
|
Marks the project as deleted. This route does not remove storage artifacts or
|
|
related persistence and is not the ordinary workspace-cleanup path. Operators
|
|
and the UI use `PATCH` with `status: "archived"` for reversible cleanup.
|
|
|
|
## 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.
|
|
|
|
### GET `/api/v1/projects/{project_id}/areas/municipalities`
|
|
|
|
Searches the persisted authoritative NGI AdminVector municipality layer by
|
|
Dutch, French or German name and NIS code. `query` is optional and `limit` is
|
|
bounded to 50. Results contain names and NIS identity but no fabricated or
|
|
browser-fetched geometry.
|
|
|
|
### POST `/api/v1/projects/{project_id}/areas/municipalities/{niscode}/activate`
|
|
|
|
Idempotently creates or returns a project Area from the exact persisted NGI
|
|
municipality geometry. The resulting Area can be used by all existing bounded
|
|
selection, acquisition, analysis and export contracts.
|
|
|
|
## Resumable AOI operations
|
|
|
|
### POST `/api/v1/projects/{project_id}/aoi-operations`
|
|
|
|
Creates one persisted parent operation and deterministic bounded partitions.
|
|
Exactly one of `area_id` or an EPSG:4326 `bbox` is required. Partitioning is
|
|
calculated in EPSG:31370 and clipped to the exact AOI; source side limits remain
|
|
server concerns. Optional `coverage_zone` clips the immutable AOI snapshot to
|
|
the persisted legal/regional scope before planning. The production worker
|
|
automatically claims queued children; clients poll the parent instead of
|
|
driving provider requests.
|
|
|
|
```json
|
|
{
|
|
"area_id": "optional-uuid",
|
|
"operation_type": "acquire",
|
|
"provider_key": "grb",
|
|
"product_key": "buildings",
|
|
"max_partition_side_m": null,
|
|
"max_attempts": 3,
|
|
"parameters_json": {"force_refresh": false}
|
|
}
|
|
```
|
|
|
|
When `max_partition_side_m` is omitted, the backend derives the limit from the
|
|
governed provider registry. An explicit value can only make partitions smaller,
|
|
never relax the provider budget. The response reports parent status, progress from `0.0` to `1.0`, per-status
|
|
partition counts and child partition evidence. Stable partition keys make
|
|
planning and completion idempotent.
|
|
|
|
### GET `/api/v1/projects/{project_id}/aoi-operations/{operation_id}`
|
|
|
|
Returns the persisted operation, aggregate progress and every child state.
|
|
|
|
### GET `/api/v1/projects/{project_id}/aoi-operations`
|
|
|
|
Lists the most recent parent operations for the project with their derived
|
|
progress, status counts and child evidence.
|
|
|
|
### POST `/api/v1/projects/{project_id}/aoi-operations/{operation_id}/execute-next`
|
|
|
|
Claims and executes one queued partition through the registered governed
|
|
provider. Registered executors cover `grb`, `orthophoto`, `dhmv`,
|
|
`spw_terrain`, `official_vector`, `flood_hazard`, `thematic_raster`, `walous`,
|
|
`bathymetry_profiles` and `mdk_bathymetry`; their existing configuration and
|
|
zone contracts remain authoritative. Every
|
|
execution creates a linked child Job. Transient provider failures are retried
|
|
within the stored attempt budget; validation failures fail immediately.
|
|
|
|
### POST `/api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/claim`
|
|
|
|
Atomically claims the next queued child using a locked, skip-locked database
|
|
selection. Returns `data: null` when no queued child remains.
|
|
|
|
### PUT `/api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/{partition_id}/checkpoint`
|
|
|
|
Persists provider-specific restart evidence for a running child.
|
|
|
|
### POST `/api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/{partition_id}/complete`
|
|
|
|
Marks a running child successful or explicitly skipped. Repeating completion
|
|
for a terminal successful/skipped child is idempotent.
|
|
|
|
### POST `/api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/{partition_id}/fail`
|
|
|
|
Records an error and either returns the child to `queued` within its attempt
|
|
budget or terminalizes it as `failed`.
|
|
|
|
## Datasets
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/upload`
|
|
|
|
Multipart upload.
|
|
|
|
Fields:
|
|
|
|
- `file`: dataset file.
|
|
- `dataset_type`: `vector`, `geojson` (legacy), `raster`.
|
|
- `source`: descriptive caller text, e.g. `user_upload`. It is retained as a
|
|
claim only and never establishes authority.
|
|
- `dataset_role`: `source`, `derived`, or `reference` (default `source`).
|
|
- `source_name`: optional descriptive claim. Public uploads are always bound
|
|
to the server-owned `manual` registry entry, including when this field says
|
|
`grb`, `osm` or another official name. Reference uploads also default to
|
|
`manual`.
|
|
- `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, plus
|
|
`source_registry_id`, `source_snapshot_id`, exact data-contract key/version,
|
|
validation report/status, provenance/lineage status, quarantine status and an
|
|
idempotent ingest key. A malformed or doubtful artifact is retained as
|
|
`status=quarantined`; it is not silently discarded or made ready.
|
|
|
|
Vector uploads remain stored as original files and are also persisted into
|
|
`vector_features` as queryable PostGIS state only after their contract passes.
|
|
Non-EPSG:4326 vector coordinates are explicitly transformed before canonical
|
|
feature persistence; relabelling Lambert coordinates as EPSG:4326 is rejected.
|
|
|
|
### GET `/api/v1/source-registry`
|
|
|
|
Lists server-owned source definitions. Optional query parameter
|
|
`classification` is one of `authoritative`, `corroborative`, `contextual`,
|
|
`derived`, or `experimental`. This endpoint reports policy and snapshot counts;
|
|
it does not assert that historical datasets carrying a matching text field are
|
|
trusted. It is operator-only because source snapshots can contain
|
|
operator-acquisition provenance. A demo session uses its project-scoped
|
|
dataset provenance endpoint instead.
|
|
|
|
### GET `/api/v1/source-registry/{source_key}`
|
|
|
|
Returns one source definition and its immutable snapshots, including authority
|
|
scope, licence/restrictions, expected geometry/attributes, freshness and known
|
|
limitations. Unknown source keys return `SOURCE_REGISTRY_ENTRY_NOT_FOUND`.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/provenance`
|
|
|
|
Returns the dataset's bound source/snapshot, contract report, lineage edges and
|
|
quarantine records. A missing binding is visible as incomplete provenance; it
|
|
is never backfilled from a display name or caller metadata.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/orthophoto/products`
|
|
|
|
Return the governed Belgian orthophoto product allowlist in the canonical
|
|
envelope. It contains Digitaal Vlaanderen products plus `wallonia_latest`
|
|
(SPW) and `brussels_latest` (Paradigm UrbIS). Every product reports its key,
|
|
provider, coverage zone, display/observation label, temporal granularity,
|
|
native resolution, colour mode, catalogue URL, attribution, licence note,
|
|
limitations and whether configured-YOLO detection is allowed.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/orthophoto/acquire`
|
|
|
|
Explicitly acquire a bounded orthophoto selection from a governed official
|
|
regional WMS product. Arbitrary WMS URLs and layer names are not accepted.
|
|
The regional products require the persisted project Areas `Wallonia` or
|
|
`Brussels-Capital Region` and reject a rectangle unless that Area covers at
|
|
least 99% of it.
|
|
|
|
```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",
|
|
"product_key": "most_recent",
|
|
"resolution_m": 0.25,
|
|
"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. Historical products also persist their
|
|
observation/validity period and a spatially scoped temporal-series key.
|
|
`resolution_m` is optional (0.1-2.0 m) and can never be finer than the
|
|
allowlisted product's native resolution. It exists for governed training and
|
|
review exports; ordinary workbench requests retain the configured 1 m default.
|
|
For rolling `latest` products, acquisition time is not represented as the
|
|
per-pixel observation date: provenance explicitly records
|
|
`observation_time_precision=unknown_per_pixel`.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/grb/products`
|
|
|
|
Returns the fixed official GRB vector registry in the canonical envelope.
|
|
Only `buildings` (`GBG`), `roads` (`Wegsegment`), `water`
|
|
(`WTZ`/`WLAS`/`WGR`) and `parcels` (`ADP`) are exposed. Each product reports
|
|
its exact source collections, supported geometry types, attribution, licence
|
|
and semantic limitation.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/grb/acquire`
|
|
|
|
Acquires one explicitly bounded GRB product from the allowlisted Digitaal
|
|
Vlaanderen OGC API Features service behind the synchronous Job abstraction:
|
|
|
|
```json
|
|
{
|
|
"bbox": {"min_x": 5.12, "min_y": 51.18, "max_x": 5.17, "max_y": 51.22, "crs": "EPSG:4326"},
|
|
"area_id": "optional-project-area-uuid",
|
|
"product_key": "buildings",
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The backend requires EPSG:4326, intersects the rectangle with the optional
|
|
persisted Area, rejects sides below 10 m or above 20 km, follows only
|
|
same-host allowlisted collection pagination and fails instead of truncating
|
|
when page, transfer or feature limits are reached. Every official feature id,
|
|
request URL, response checksum and artifact checksum remains provenance.
|
|
Although GRB is stored natively in EPSG:31370, the service explicitly
|
|
negotiates OGC CRS84 longitude/latitude GeoJSON for both bbox and output before
|
|
performing the exact geometry intersection.
|
|
|
|
Output is an ordinary reference Dataset persisted through `DatasetService`,
|
|
`DatasetVersion` and `VectorFeatureService`; providers never write directly
|
|
to `vector_features`. Exact requests are reused for 24 hours unless
|
|
`force_refresh=true`. Selection metrics are footprint hectares for buildings,
|
|
line kilometres for roads, hectares plus supporting line kilometres for
|
|
water, and hectares for parcels. Water volume is unsupported because GRB
|
|
contains no depth or bathymetry.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/dhmv/products`
|
|
|
|
Returns the fixed official DHMV II product registry in the canonical envelope.
|
|
The registry contains only `dtm_1m` (`DHMVII_DTM_1m`) and `dsm_1m`
|
|
(`DHMVII_DSM_1m`). Each item records native 1 m resolution, EPSG:31370, TAW,
|
|
the 2013-2015 acquisition period, attribution, catalogue and limitations.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/dhmv/acquire`
|
|
|
|
Runs a bounded WCS 2.0.1 `GetCoverage` request behind the existing synchronous
|
|
Job abstraction. Arbitrary coverage identifiers are rejected.
|
|
|
|
```json
|
|
{
|
|
"bbox": {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"},
|
|
"area_id": "optional-project-area-uuid",
|
|
"product_key": "dtm_1m",
|
|
"resolution_m": 5.0,
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The service extracts the GeoTIFF from the official multipart response, clips
|
|
to the exact persisted Area when supplied, validates EPSG:31370, one band,
|
|
resolution, nodata and valid cells, then persists through `DatasetService`.
|
|
The default 5 m file is an analysis copy of the retained 1 m source product;
|
|
both resolutions and all request/response/output checksums remain provenance.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/terrain/select`
|
|
|
|
Accepts an EPSG:4326 rectangle and optional Area id. It reads only a governed,
|
|
ready DHMV Dataset and returns a canonical envelope with valid-cell coverage,
|
|
mean/min/max/P10/P90 height in `m TAW`, relief in metres and mean/P90/max slope
|
|
in degrees. Area geometry is an exact mask, not only a bounding box.
|
|
|
|
The response always lists `water_depth_m` and `water_volume_m3` under
|
|
`unsupported_metrics`. Drainage is not calculated by this endpoint.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/raster/terrain/select`
|
|
|
|
Runs the same exact terrain calculation over every persisted municipal DHMV
|
|
partition intersecting one bounded EPSG:4326 rectangle. The request adds the
|
|
governed `product_key` (`dtm_1m` or `dsm_1m`) to the ordinary selection bbox
|
|
and optional Area id. The backend mosaics only the intersecting windows in
|
|
EPSG:31370, enforces the existing 12-million-cell limit and calculates global
|
|
cell statistics. The canonical response includes `dataset_ids` and
|
|
`partition_count`; percentiles are calculated from the combined cells and are
|
|
not averages of municipal summaries.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/terrain/image`
|
|
|
|
Returns a browser-safe PNG colour relief for the persisted governed DHMV
|
|
Dataset. This binary MapLibre source never accepts an arbitrary file path.
|
|
|
|
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;
|
|
- only `most_recent`, `wallonia_latest` and `brussels_latest` can enter the
|
|
configured-YOLO path; QA must use the matching regional reference source
|
|
(GRB, PICC or UrbIS). Historical products remain visual evidence;
|
|
- product periods such as `1979_1990` remain explicitly multi-year and are not
|
|
presented as exact annual observations.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/flood-hazard/products`
|
|
|
|
Returns the fixed twelve-product VMM flood-depth registry in the canonical
|
|
envelope. Products combine `pluviaal`/`fluviaal`, current climate/climate
|
|
projection 2050 and T10/T100/T1000. Each product keeps the official WCS
|
|
coverage id, probability class, source unit centimetres, normalized unit
|
|
metres, publication metadata, attribution and limitation.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/flood-hazard/acquire`
|
|
|
|
Acquires one bounded official VMM OGRK WCS 1.1 coverage behind the synchronous
|
|
Job abstraction. Arbitrary coverage identifiers and service URLs are rejected.
|
|
When `product_key` is omitted, the governed default is
|
|
`pluviaal_current_t100`; the default is itself present in the fixed product
|
|
registry.
|
|
|
|
```json
|
|
{
|
|
"bbox": {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"},
|
|
"area_id": "optional-project-area-uuid",
|
|
"product_key": "pluviaal_current_t100",
|
|
"resolution_m": 5.0,
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The service tiles municipality-size requests, validates EPSG:31370 and one
|
|
Float32 depth band, converts positive source centimetres to metres, clips to
|
|
the exact persisted Area and stores an ordinary raster Dataset and
|
|
DatasetVersion. Zero/null source cells become transparent nodata. The scenario
|
|
is not a temporal observation and receives no fabricated `observed_at` value.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/flood-hazard/select`
|
|
|
|
Returns mapped positive-depth area in hectares, share of the *modelled* area,
|
|
mean, P90 and maximum modeled local depth and
|
|
`modelled_max_depth_area_integral_m3`. Every result identifies mechanism,
|
|
climate context, probability class and return period. The integral sums local
|
|
modeled maximum depth times cell area; it is explicitly not concurrent flood
|
|
storage, permanent waterbody content, current water level or bathymetry. These
|
|
unsupported metrics remain listed in the response.
|
|
|
|
Three cell populations are reported separately, because conflating them turns
|
|
missing data into a claim of safety:
|
|
|
|
- `selected_cell_count` — cells inside the drawn selection;
|
|
- `valid_cell_count` / `no_data_cell_count` — the split between cells the VMM
|
|
raster models and cells it does not;
|
|
- `inundated_cell_count` — modelled cells with a positive depth.
|
|
|
|
`inundated_fraction` and `modelled_inundated_share_pct` are shares of the
|
|
modelled cells, not of the drawn selection. `inundated_fraction` is `null` when
|
|
nothing was modelled at all. `data_coverage_ratio`, `model_coverage_pct`,
|
|
`modelled_area_ha` and `selection_area_ha` make the difference between the
|
|
drawn area and the analysed area explicit, and `coverage_warning` states it in
|
|
words. A selection reaching past the modelled extent previously reported a
|
|
diluted risk share for the whole rectangle.
|
|
|
|
**Selections finer than one source cell.** `geometry_mask` selects a cell when
|
|
its centre falls inside the geometry, so a rectangle smaller than a cell — or
|
|
one landing between four centres — selected nothing and the analysis returned
|
|
zeros indistinguishable from "nothing here". Every raster selection now falls
|
|
back to the cells the geometry touches and reports that in
|
|
`cell_selection_warning` (`coverage_warning` for flood hazard), because the
|
|
result then covers more ground than was drawn. This applies to terrain,
|
|
bathymetry, thematic raster and flood hazard, in both the single-dataset and
|
|
the partitioned paths.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/raster/flood-hazard/select`
|
|
|
|
Runs exact bounded analysis over the persisted municipal VMM partitions for
|
|
one governed `product_key`. Only partitions intersecting the selection are
|
|
opened, the normalized metre grids are combined at their common 5 m analysis
|
|
resolution and the global area/depth metrics are calculated from the combined
|
|
cells. The canonical response includes every contributing Dataset id in
|
|
`dataset_ids` plus `partition_count`. The existing flood-volume and bathymetry
|
|
prohibitions are unchanged.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/flood-hazard/image`
|
|
|
|
Returns a constrained transparent PNG for a persisted governed VMM flood-depth
|
|
Dataset. It never accepts an arbitrary path or coverage id and is used by the
|
|
existing MapLibre image-overlay path.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/thematic-raster/products`
|
|
|
|
Returns the fixed MercatorNet registry for `space_occupation_2025`,
|
|
`open_space_2022`, `population_density_2019`, `node_value_2022` and
|
|
`service_level_2022`. Every item includes the governed WCS coverage id, native
|
|
resolution, source unit, observation year, legend, attribution and limitation.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/thematic-raster/acquire`
|
|
|
|
Acquires one allowlisted official coverage behind the synchronous Job
|
|
abstraction. The request accepts only an EPSG:4326 bbox, optional project Area,
|
|
one registry product key and an explicit refresh flag:
|
|
|
|
```json
|
|
{
|
|
"bbox": {"min_x": 5.03, "min_y": 51.15, "max_x": 5.25, "max_y": 51.33, "crs": "EPSG:4326"},
|
|
"area_id": "optional-project-area-uuid",
|
|
"product_key": "population_density_2019",
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The backend uses native 10 m or 100 m resolution, splits requests into bounded
|
|
WCS 1.0 tiles, validates EPSG:31370 and documented source values, masks the
|
|
exact Area and persists an ordinary raster Dataset and DatasetVersion. It does
|
|
not accept arbitrary URLs, coverage ids, resolutions or expressions.
|
|
|
|
The Flanders map workbench orchestrates this existing endpoint only after an
|
|
explicit municipality or rectangle selection. It requests all five governed
|
|
products for the same `bbox ∩ Area`, reuses the checksum-bound Dataset for an
|
|
identical request and then calls the normal selection endpoint. The browser
|
|
never contacts WCS directly. A whole-Flanders raster request is intentionally
|
|
unavailable in the map flow because it exceeds the bounded 60 km/30 million
|
|
cell guardrail; users must choose a municipality or draw a smaller rectangle.
|
|
An Area is recorded as `coverage_scope=municipality` only when its canonical
|
|
name starts with `Gemeente `; a regional Area remains `bounded_selection`.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/thematic/select`
|
|
|
|
Returns source-correct metrics for a bbox and optional exact Area mask:
|
|
|
|
- occupied/open hectares, share and valid raster area for binary products;
|
|
- estimated inhabitants plus mean/P90 inhabitants per hectare for the 2019
|
|
population raster;
|
|
- mean, P10, median and P90 source score for node value and service level.
|
|
|
|
The response names estimate status, aggregation method, source unit,
|
|
observation year, attribution, unsupported metrics and product limitation.
|
|
Current register population, live public-transport availability and causal
|
|
interpretations are not produced.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/thematic/image`
|
|
|
|
Returns a constrained transparent PNG generated from the persisted governed
|
|
raster. It accepts neither an arbitrary file path nor a provider URL and feeds
|
|
the existing MapLibre image-overlay path.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/walous/products`
|
|
|
|
Returns the fixed official WALOUS 2018/2020/2023 registry. Each product reports its
|
|
observation year, EPSG:3812 source contract, 1 m source semantics, configured
|
|
state, attribution, licence and documented edition accuracy. `configured`
|
|
becomes true only when the checksum-validated source GeoTIFF exists below
|
|
`WALOUS_SOURCE_DIR`; the endpoint never downloads an archive.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/walous/acquire`
|
|
|
|
Reads a bounded window from one provisioned official 1 m WALOUS source,
|
|
applies nearest-neighbour resampling to the configured analysis resolution,
|
|
masks `bbox intersect Area`, validates the official non-contiguous class-code
|
|
set `1, 2, 3, 4, 5, 6, 7, 8, 9, 80, 90` and persists a normal raster Dataset
|
|
through `DatasetService`. URLs, paths, classes and resolutions are not
|
|
caller-controlled. The 2018 source retains official stacked two-digit values;
|
|
GeoIntel applies the published `Classe vue` mapping and explicitly groups the
|
|
2018-only greenhouse class `62` with artificial constructions. Source value
|
|
`0` is treated only as implicit background/nodata. Equal spatial
|
|
requests for 2018, 2020 and 2023 share one temporal
|
|
series key. The exact official observation ranges are retained as
|
|
2018-01-01/2018-12-31, 2020-04-01/2020-04-24 and
|
|
2023-05-27/2023-06-25.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/walous/select`
|
|
|
|
Returns mapped hectares for total observed land cover, trees/forest, surface
|
|
water, artificial cover, annual and permanent herbaceous cover and bare soil.
|
|
Values are cell-area estimates from the persisted derived raster. The response
|
|
explicitly rejects legal land use, ownership, tree count, timber volume and
|
|
water volume as unsupported interpretations.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/walous/image`
|
|
|
|
Returns a transparent PNG using the governed 11-class colour table and only
|
|
the persisted Dataset geometry. It never proxies the source archive.
|
|
|
|
The Map workbench acquires every configured comparable WALOUS observation for
|
|
the same Walloon selection when current land cover is first requested. The
|
|
latest edition drives the current result; the ordinary temporal comparison API
|
|
then compares 2018, 2020 and 2023 semantic area metrics. The response retains
|
|
the earlier 2018 methodology and crosswalk limitation; raster evolution does
|
|
not claim individual object additions or removals.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/spw-terrain/products`
|
|
|
|
Returns the governed official SPW 1 m MNT 2021-2022 product and its actual
|
|
runtime provisioning state. The source is EPSG:3812; its vertical reference is
|
|
DNG / EPSG:5710. `configured` becomes true only after the checksum-validated
|
|
GeoTIFF exists below `SPW_TERRAIN_SOURCE_DIR`.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/spw-terrain/acquire`
|
|
|
|
Reads only `bbox intersect Area` from the operator-provisioned official MNT,
|
|
validates the one-band EPSG:3812/1 m source contract and plausible terrain
|
|
values, bilinearly resamples the bounded derivative to 1-10 m and persists it
|
|
through `DatasetService`. The request cannot choose a URL or file path. Source,
|
|
archive and derived checksums, acquisition range, CRS, DNG datum, exact bounds,
|
|
resolution and interpolation limitations remain provenance. The ordinary
|
|
persisted terrain selection and PNG endpoints then analyze/render this Dataset.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets`
|
|
|
|
List datasets.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/source-freshness`
|
|
|
|
Returns one read-only, canonical source-governance report for all persisted
|
|
project datasets. Datasets are grouped by `source_name` (falling back to
|
|
`source`) and classified as a rolling snapshot, annual release, fixed edition,
|
|
scenario, historical archive or local artifact.
|
|
|
|
Each source item reports dataset/version counts, latest import and observation
|
|
evidence, latest source version, next review date where meaningful, historical
|
|
series availability and local integrity counts for missing DatasetVersions,
|
|
checksum mismatches, missing storage files and size mismatches. Status is one
|
|
of `current`, `due`, `review_required` or `local`.
|
|
|
|
This endpoint never contacts an external provider, downloads data, mutates a
|
|
Dataset or silently refreshes a publication. Fixed editions and scenarios are
|
|
not marked stale merely because their source date is old. Unknown sources are
|
|
`review_required` until an explicit publication policy is defined.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/source-catalog-probes`
|
|
|
|
Performs an explicit, read-only release probe for the allowlisted GRB WFS,
|
|
most-recent orthophoto WMS, Statbel population DCAT catalog and ALZ
|
|
agricultural-use parcel publication page.
|
|
For OGC services, the endpoint reads bounded `GetCapabilities` responses,
|
|
confirms the expected layers and follows only HTTPS ISO 19139 `GetRecordById`
|
|
links on `metadata.vlaanderen.be`. For Statbel, it reads the bounded official
|
|
RDF/Turtle catalog and selects the latest uniquely identified Dutch
|
|
`Bevolking per statistische sector` release. It requires the official landing
|
|
page, CC BY 4.0 license and allowlisted distribution identities but never
|
|
requests a ZIP or XLSX file. For ALZ, it reads only the bounded official
|
|
HTML release page and accepts only exact
|
|
`www.landbouwvlaanderen.be/bestanden/gis/agpa_<year>_<date>_public.zip` link
|
|
identities. It does not request those archives. Query parameter `refresh=true`
|
|
bypasses the short in-memory response cache.
|
|
|
|
Each provider item returns service reachability, matched/missing layers, the
|
|
official metadata identifier, title, edition, publication/metadata dates,
|
|
local `source_version` and one comparison status: `same`, `different`,
|
|
`not_comparable`, `no_local_data` or `unavailable`. Provider status is
|
|
`available`, `degraded`, `unavailable` or `disabled`. A difference means only
|
|
that an operator should review provenance; it is not an update instruction.
|
|
|
|
ALZ comparisons use only the latest definitive third snapshot, normalized as
|
|
`<campaign>-v3`. A newer first or second snapshot is reported in the item title
|
|
and message as provisional but cannot mark a local definitive historical
|
|
edition as outdated. `service_type=HTML` uses `definitive_archive` and
|
|
`current_snapshot` as evidence markers in the existing expected/matched/missing
|
|
arrays; no parallel response shape is introduced.
|
|
|
|
Statbel comparisons use the four-digit population reference year. The 2025
|
|
release requires the new REDEGEO TXT/ZIP variant; its old-sector-layout file is
|
|
reported only as transition evidence. `service_type=DCAT` uses
|
|
`population_txt_current`, `landing_page` and `cc_by_4_0` as evidence markers.
|
|
A newer statistical-sector geometry edition is not interpreted as a newer
|
|
population release.
|
|
|
|
Future Statbel execution remains outside the HTTP request cycle in
|
|
`scripts/manage_statbel_population_release.py`. It reuses this read-only
|
|
catalog response for `plan`, then separates filesystem-only `stage`, named
|
|
human `review` and checksum-confirmed `apply`. No additional public endpoint
|
|
is introduced. Apply delegates to the existing dataset upload contract and
|
|
creates a new immutable annual snapshot only after all evidence is unchanged.
|
|
|
|
Future definitive ALZ execution likewise remains outside the HTTP request
|
|
cycle in `scripts/manage_alz_agriculture_release.py`. It derives the exact
|
|
`agpa_<campaign>_<publication-date>_public.zip` identity from the existing
|
|
catalog response and accepts only `YYYY-v3`. `stage` is filesystem-only,
|
|
`review` is a named approval and `apply` requires exact plan/review hashes,
|
|
revalidates the current catalog and delegates to the existing Dataset upload
|
|
contract. No ALZ release endpoint, background task or provider URL parameter
|
|
is added; v1/v2 campaign snapshots remain non-importable.
|
|
|
|
Current-orthophoto release preflight also remains outside the HTTP request
|
|
cycle in `scripts/orthophoto_release_preflight.py`. It composes the existing
|
|
product-registry and source-catalog envelopes with allowlisted WMS
|
|
`GetCapabilities`, WCS `DescribeCoverage` and bounded `Vliegdagcontour`
|
|
`GetFeatureInfo` evidence for one 128-1,024 m EPSG:4326 selection. The result
|
|
reports product variant, official `YYYY.NN` edition, exact raster-domain
|
|
containment, sampled flight dates/years, local comparison state and
|
|
`staging_permitted`. It performs no pixel request, upload, Job, Dataset write
|
|
or legacy metadata rewrite. This operator script adds no public API contract.
|
|
|
|
Governed pixel promotion remains outside the HTTP request cycle in
|
|
`scripts/manage_orthophoto_release.py`. `plan` reruns that read-only preflight;
|
|
`stage` makes exactly one allowlisted bounded `Ortho` GetMap request and writes
|
|
only checksummed source/raster/preview evidence; `review` requires a named
|
|
approval; and `apply` requires the exact plan/review SHA-256 values. Apply
|
|
revalidates the remote identity and local comparison state, then delegates to
|
|
the existing `POST .../datasets/upload` contract with `dataset_type=raster`,
|
|
`source_name=digitaal_vlaanderen_orthophoto`, the official `YYYY.NN`
|
|
`source_version` and complete source/provenance metadata. No release endpoint,
|
|
provider URL parameter, Job type or alternate response envelope is added.
|
|
|
|
The endpoint accepts no arbitrary URL, feature query, area or layer. It does
|
|
not fetch vector features, raster pixels or models, create jobs/datasets, write
|
|
to PostGIS or trigger an import. The normal `source-freshness` endpoint remains
|
|
local-only and never invokes this probe implicitly.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/grb-refresh-plan`
|
|
|
|
Builds a read-only refresh decision for the governed
|
|
`kempen-transport-region` GRB snapshot series. Query parameter
|
|
`refresh_catalog=true` explicitly bypasses the catalog cache. The response
|
|
maps the official dated edition to `buildings`, `roads`, `water` and `parcels`
|
|
and reports each latest local Dataset, source version, observation date,
|
|
feature count, artifact size and refresh state.
|
|
|
|
Layer state is `current`, `update_available`, `not_loaded`, `review_required`
|
|
or `remote_unavailable`. The summary reports how many new immutable Datasets
|
|
would be created and how many existing snapshots remain retained. The endpoint
|
|
does not fetch GRB features, stage files, create a Job, write to PostGIS or
|
|
start an operator process. Exact remote deltas remain unavailable until every
|
|
municipality partition has been staged and validated.
|
|
|
|
Regional execution uses `scripts/manage_grb_refresh.py` outside the request
|
|
cycle. `stage` requires the exact official ISO edition, invokes the existing
|
|
regional GRB operators with `--fetch-only`, validates all artifact/partition
|
|
checksums and emits a SHA-256-bound plan. `apply` requires that exact plan hash,
|
|
revalidates every staged byte and delegates persistence to DatasetService and
|
|
VectorFeatureService. It creates new temporal snapshots and never deletes or
|
|
overwrites an older Dataset.
|
|
|
|
### 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...`
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/image`
|
|
|
|
Return a persisted orthophoto Dataset as a bounded browser-safe PNG. This is an
|
|
explicit binary non-envelope endpoint used by the MapLibre image source. It
|
|
accepts only ready datasets from the governed orthophoto provider and never
|
|
reads arbitrary filesystem paths.
|
|
|
|
### 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.
|
|
|
|
|
|
### Detection and segmentation result windows
|
|
|
|
`GET /detection/runs/{id}/detections`, `/detection/datasets/{id}/detections`
|
|
and their segmentation equivalents accept `limit` (default 2.000, `0` for
|
|
everything) and `offset`, and return `total`, `limit`, `offset` and
|
|
`truncated`. `total` always describes the complete population; `items` is one
|
|
page of it.
|
|
|
|
The `/geojson` siblings accept `limit` and report the window in a
|
|
`geointel_result_window` foreign member. Rows are ordered by confidence, so a
|
|
capped overlay draws the strongest detections rather than an arbitrary slice.
|
|
|
|
A regional run holds tens of thousands of detections, and these are the
|
|
endpoints the results table and the map overlay call after every run; they
|
|
previously returned all of them.
|
|
|
|
### GET `/api/v1/projects/{project_id}/quality-checks/{id}/evidence/geojson`
|
|
|
|
Returns the reviewable geometry behind one quality check: the objects the model
|
|
missed, the ones it found without a reference, and the confirmed matches.
|
|
|
|
The overlay is capped by `limit` (default 5.000, `0` returns everything). A
|
|
regional check emitted one feature per false positive, one per false negative
|
|
and *two* per match with no bound at all, so a run of 40k detections against
|
|
45k footprints produced well over a hundred thousand features in one response —
|
|
the endpoint the whole review workflow depends on stopped working exactly where
|
|
review matters most.
|
|
|
|
What to draw is decided before any geometry is fetched, so the database work is
|
|
proportional to what is returned rather than to the size of the check. The
|
|
budget is split between misses and false positives in proportion to their
|
|
populations, with at least one of each: strict priority would mean a check with
|
|
50.000 misses and three false positives never showed one. Confirmations fill
|
|
what is left, and a match is kept or dropped as a candidate/reference pair
|
|
because half a match is not reviewable evidence.
|
|
|
|
`total_feature_count` and `role_counts` describe the complete population,
|
|
`truncated` says whether the cap applied, and unresolvable identifiers are
|
|
summarised in one warning rather than one per identifier.
|
|
|
|
### Export provenance
|
|
|
|
Every exported GeoJSON carries a `geointel_provenance` foreign member on the
|
|
FeatureCollection. RFC 7946 requires parsers to ignore members they do not
|
|
know, so QGIS and ogr2ogr read the file normally.
|
|
|
|
It names the source, the dataset and its `source_version`/`observed_at`, the
|
|
selection the export was taken from, and — most importantly — whether the file
|
|
is complete. A capped selection export previously recorded `truncated` on the
|
|
export *record* only: the downloaded file looked whole, and an operator opening
|
|
250 of 1.400 buildings in QGIS had nothing to tell them so. Completeness is
|
|
derived from the counts as well as the flag, so a caller that forgets to pass
|
|
it cannot produce a file that claims to hold everything.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select`
|
|
|
|
Selection metrics describe two different populations and now say so.
|
|
`intersection_area` and `intersection_length` clip each feature to the
|
|
selection, while the object count treats any feature touching the selection as
|
|
whole — which is what an operator expects from "objecten", but overstates the
|
|
count along every edge. The response therefore adds
|
|
`fully_covered_feature_count`, `partially_covered_feature_count` and
|
|
`selection_edge_warning`, and marks the count metric as an estimate whenever
|
|
the selection cuts features. Area and length metrics stay exact and do not
|
|
inherit that caveat.
|
|
|
|
Partitioned selection (`/datasets/vector/partitions/select`) de-duplicates on
|
|
`source_feature_id` across municipal partitions for the returned geometry as
|
|
well as for the count. A feature on a shared boundary was previously counted
|
|
once but drawn once per partition.
|
|
|
|
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": []
|
|
},
|
|
"summary": {
|
|
"metric_label": "Wateroppervlakte",
|
|
"metric_value": 5.25,
|
|
"metric_unit": "ha",
|
|
"aggregation_method": "intersection_area",
|
|
"primary_metric_key": "water_area",
|
|
"feature_count": 23,
|
|
"is_estimate": false,
|
|
"warning": "Watervolume is niet berekenbaar zonder betrouwbare diepte- of bathymetrische gegevens.",
|
|
"metrics": [
|
|
{
|
|
"metric_key": "water_area",
|
|
"metric_label": "Wateroppervlakte",
|
|
"metric_value": 5.25,
|
|
"metric_unit": "ha",
|
|
"aggregation_method": "intersection_area",
|
|
"is_estimate": false
|
|
},
|
|
{
|
|
"metric_key": "watercourse_length",
|
|
"metric_label": "Lengte waterlopen",
|
|
"metric_value": 12.75,
|
|
"metric_unit": "km",
|
|
"aggregation_method": "intersection_length",
|
|
"is_estimate": false
|
|
},
|
|
{
|
|
"metric_key": "feature_count",
|
|
"metric_label": "Waterobjecten",
|
|
"metric_value": 23,
|
|
"metric_unit": "objecten",
|
|
"aggregation_method": "feature_count",
|
|
"is_estimate": false
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
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,
|
|
PostGIS filtering and configured aggregations use `bbox ∩ Area`. A bbox that
|
|
encloses the complete Area therefore produces the exact full-work-area
|
|
result, while a drawn rectangle that crosses a municipality boundary is
|
|
clipped to that official boundary.
|
|
- 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.
|
|
- `summary` keeps one backwards-compatible primary metric and exposes all relevant measurements in `metrics`. Known themes use metric PostGIS calculations: building/forest/water/parcel surfaces in hectares, road and watercourse lengths in kilometres, population in inhabitants and intersecting feature counts as supporting evidence.
|
|
- Governed datasets may declare additional `source_metadata.selection_metrics`.
|
|
Each metric may constrain one persisted feature-property to an explicit value
|
|
allowlist before the same PostGIS aggregation runs. The BWK/Natura 2000
|
|
dataset uses this only for official `EVAL` classes; it does not collapse mixed
|
|
classes into a made-up score.
|
|
- Area and length calculations transform geometry to Belgian Lambert 72 (`EPSG:31370`); they are never calculated in geographic degrees.
|
|
- Water volume is not inferred from 2D GRB geometry. It remains unavailable until a source provides reliable depth or bathymetry with compatible spatial coverage and provenance.
|
|
- 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/vector/partitions/select`
|
|
|
|
Combines `1..16` bounded vector acquisitions of one governed source product
|
|
into one read-only selection result. The request uses the same `bbox`, optional
|
|
`area_id` and `limit` contract as vector selection plus `dataset_ids`.
|
|
|
|
The backend rejects mixed projects, non-ready vector datasets and partitions
|
|
whose `source_name` or `product_key` differs. PostGIS calculates area and length
|
|
metrics across all persisted tile geometries. `total_feature_count` is
|
|
deduplicated by provider `source_feature_id` where available so a source object
|
|
crossing a tile edge is not presented as two objects. The response includes
|
|
`partition_count`, `source_name` and the exact `dataset_ids` used.
|
|
|
|
The Map workbench uses this route only for regional selections up to 50 by 50
|
|
kilometres. Provider calls remain individually bounded below 20 kilometres;
|
|
larger overview selections do not fan out into unbounded high-resolution
|
|
downloads.
|
|
|
|
### 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
|
|
|
|
The legacy Sprint 7 provider registry remains the exact
|
|
`grb|osm|manual|fixture` import abstraction. National and maritime source
|
|
authority is exposed separately so adding official source families cannot
|
|
silently change that import contract.
|
|
|
|
### GET `/api/v1/external/coverage/catalog`
|
|
|
|
Returns the normalized Belgium and Belgian North Sea theme vocabulary, legal
|
|
coverage zones, allowed availability states and audited source contracts.
|
|
Every source includes authority, native layers, geometry types, acquisition
|
|
mode, source URL, attribution, licence and limitation text.
|
|
|
|
Allowed result states are exactly:
|
|
|
|
- `operational`: one matching `ready` Dataset or the spatial union of governed
|
|
partition Datasets covers the complete selection;
|
|
- `partial`: the governed integration exists but matching project data is
|
|
absent or incomplete;
|
|
- `not_configured`: an audited source has no operational adapter;
|
|
- `unsupported`: no audited contract supports the theme in that zone.
|
|
|
|
### POST `/api/v1/external/coverage/resolve`
|
|
|
|
Resolves a user-drawn EPSG:4326 bbox against persisted national and maritime
|
|
Areas. Cross-region and land/sea selections remain split by zone.
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"bbox": {
|
|
"minx": 2.65,
|
|
"miny": 51.05,
|
|
"maxx": 2.85,
|
|
"maxy": 51.20
|
|
},
|
|
"themes": ["buildings", "surface_water", "bathymetry"]
|
|
}
|
|
```
|
|
|
|
The response returns `intersected_zones`, `outside_supported_scope`, one item
|
|
per zone/theme combination, matching source names and IDs of actually
|
|
materialized Datasets. Every materialized item also carries evidence for
|
|
authority, source version, observation/publication time, CRS, resolution,
|
|
coverage bbox, attribution, licence and checksum where persisted. An empty `themes` list requests the complete normalized
|
|
vocabulary. Unknown themes fail with `COVERAGE_THEME_UNSUPPORTED`.
|
|
|
|
The Map workbench uses this response before a rectangle analysis. It queries
|
|
matching persisted Datasets and may call the already documented bounded
|
|
acquisition endpoints for applicable `partial` source contracts. Those calls
|
|
run through a client queue with at most three concurrent acquisitions. An
|
|
`unsupported` or `not_configured` theme is not rendered as a failed
|
|
measurement; an attempted provider failure is reported explicitly.
|
|
|
|
### 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`
|
|
|
|
Compatibility contract for provider-specific import flows. It never writes
|
|
datasets itself.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid-or-local-id",
|
|
"area_id": "optional uuid-or-local-id",
|
|
"layers": ["buildings"],
|
|
"dataset_role": "optional source|reference"
|
|
}
|
|
```
|
|
|
|
GRB response:
|
|
|
|
```json
|
|
{
|
|
"provider_name": "grb",
|
|
"status": "bounded_request_required",
|
|
"message": "Use the governed project GRB acquisition endpoint with an EPSG:4326 bounding box and one supported layer.",
|
|
"requested_layers": ["buildings"],
|
|
"dataset_id": null,
|
|
"dataset_role": "reference",
|
|
"source_name": "grb"
|
|
}
|
|
```
|
|
|
|
OSM still returns `not_configured`. Manual and fixture providers point callers
|
|
to existing upload/fixture flows. No provider writes directly to
|
|
`vector_features`; the bounded GRB integration and all future provider output
|
|
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"]
|
|
}
|
|
```
|
|
|
|
This legacy compatibility endpoint never fetches. It returns
|
|
`bounded_request_required` and directs callers to
|
|
`POST /api/v1/projects/{project_id}/datasets/grb/acquire`, where bbox, Area,
|
|
product allowlist, limits and persistence are enforceable.
|
|
|
|
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 unbounded GRB WFS download or OSM Overpass integration is implemented.
|
|
Bounded GRB OGC API Features acquisition is documented under Datasets.
|
|
|
|
## 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.
|
|
|
|
Each descriptor exposes machine-readable `training_scope`, `validation_scope`,
|
|
`validated_regions`, `nationally_validated` and `operator_review_required`
|
|
fields. The configured local YOLO capability remains
|
|
`nationally_validated=false`: the runtime binds only the existing Mol/Kempen
|
|
operator evidence and cannot be promoted to a Belgian national claim by a UI
|
|
label or model filename.
|
|
|
|
```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 governed local runtime model files. 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`). When
|
|
`YOLO_MODEL_PATH` resolves to an existing file, production catalog output is
|
|
restricted to that explicitly approved active model. When no active model is
|
|
configured, supported `.pt`, `.onnx` and `.engine` files remain visible for
|
|
development/operator discovery but cannot make the configured detector ready.
|
|
|
|
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": "approved",
|
|
"limitation_message": "Approved 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,
|
|
"accelerator_ready": 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": true,
|
|
"configured_device": "cuda:0",
|
|
"cuda_required": true
|
|
},
|
|
"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`. When `YOLO_REQUIRE_CUDA=true`, missing CUDA or a CPU device selection fails closed with `DETECTION_ACCELERATOR_UNAVAILABLE` or `DETECTION_ACCELERATOR_MISCONFIGURED`; production inference never silently falls back to CPU.
|
|
|
|
The configured model exposes only classes declared by `YOLO_MODEL_CLASSES`;
|
|
the current promoted server detector is building-only. Unsupported class
|
|
filters fail with `DETECTION_CLASS_NOT_VALIDATED`. With production scope
|
|
enforcement enabled, the raster must belong to a persisted Area matching the
|
|
configured validated Mol/Kempen evidence or inference fails with
|
|
`DETECTION_VALIDATION_SCOPE_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.
|
|
|
|
The manifest must carry explicit CRS metadata (`crs`, `source_crs` or
|
|
`dataset_crs`). A manifest without it fails with
|
|
`DETECTION_TILE_MANIFEST_INVALID` rather than being georeferenced against an
|
|
assumed EPSG:4326, which would place detections plausibly but wrongly.
|
|
|
|
Tiles are read with rasterio: the visible RGB bands are selected explicitly and
|
|
percentile-stretched to 8-bit, so 16-bit and 4-band (RGB + NIR) orthophotos
|
|
reach the model as the kind of image it was trained on. Tiles are predicted in
|
|
batches of `YOLO_BATCH_SIZE`.
|
|
|
|
Post-processing removes two artefacts of tiled inference:
|
|
|
|
- boxes truncated by an *interior* tile edge are dropped, because the
|
|
overlapping neighbouring tile observed the same object completely
|
|
(`YOLO_SUPPRESS_TILE_EDGE_DETECTIONS`, default on). Boxes against the outer
|
|
raster edge are kept;
|
|
- duplicates are suppressed on IoU *and* on intersection-over-smaller-area, so
|
|
an object wider than the tile overlap does not survive as two partial boxes.
|
|
|
|
`result_json` reports `raw_detection_count`, `suppressed_detection_count`,
|
|
`tile_edge_truncated_count`, `duplicate_iou_threshold` and
|
|
`containment_suppression_threshold`.
|
|
|
|
### POST `/api/v1/detection/run-async`
|
|
|
|
Same request body as `POST /api/v1/detection/run`, but queues the run instead of
|
|
executing it inside the request, and returns a `JobRead`. Tiled GPU inference
|
|
over up to `YOLO_MAX_TILES` tiles takes minutes; performing it in the request
|
|
holds a worker thread and times the client out. Cheap validation (project,
|
|
dataset, dataset type) still happens synchronously, so an invalid request is
|
|
rejected immediately rather than by a job that fails minutes later.
|
|
|
|
Queued jobs are executed by the background analysis worker
|
|
(`GEOINTEL_ANALYSIS_WORKER_ENABLED`, poll interval
|
|
`GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS`), which claims a job before dispatching
|
|
it so the same run is never started twice. Poll `GET /api/v1/jobs/{id}` for
|
|
progress. `POST /api/v1/segmentation/run-async` behaves identically.
|
|
|
|
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.
|
|
|
|
The map-driven building workflow immediately loads the persisted evidence from
|
|
the resulting quality check. For current Flemish building validation it labels
|
|
strict matches as GRB-confirmed, unmatched detections as unconfirmed AI-only
|
|
proposals and unmatched reference footprints as official GRB buildings missed
|
|
by AI. AI-only proposals are never promoted to authoritative building results.
|
|
|
|
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.
|
|
|
|
`box_to_footprint_diagnostics.candidate_geometry_mode` reports whether the
|
|
candidates are `axis_aligned_boxes` or `footprint_polygons`. For a box detector
|
|
the strict footprint IoU has a ceiling below 1 on rotated or non-rectangular
|
|
buildings, and the response says so in `warnings` and in `interpretation`.
|
|
|
|
Matching is deterministic. Candidates are ranked by confidence, highest first,
|
|
with feature identity as tiebreaker, before the greedy IoU assignment. Database
|
|
row order is not usable for this: every detection in a run shares one
|
|
transaction timestamp, so ordering by `created_at` left the assignment — and
|
|
therefore the score and the false-positive evidence shown to a reviewer —
|
|
undefined between identical runs.
|
|
|
|
The response also returns `precision_recall_curve`: precision, recall and F1 at
|
|
every confidence value present in the run, plus `average_precision`, `best_f1`
|
|
and `best_f1_threshold`. A single F1 describes one operating point and cannot
|
|
compare two models whose calibration differs; the curve can. `average_precision`,
|
|
`best_f1` and `best_f1_threshold` are persisted as `metrics` rows alongside the
|
|
existing ones.
|
|
|
|
Segmentation QA (`POST /api/v1/segmentation/runs/{analysis_run_id}/qa/reference`)
|
|
applies the same tile-coverage clipping and returns the same `coverage` block.
|
|
Without it, every reference feature outside the inferred tiles counted as a
|
|
false negative and recall was understated by an arbitrary amount.
|
|
|
|
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 object-level change review,
|
|
not a temporal run-history engine.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"source_dataset_id": "uuid",
|
|
"target_dataset_id": "uuid",
|
|
"iou_threshold": 0.8,
|
|
"modified_threshold": 0.3,
|
|
"include_unchanged": true,
|
|
"bbox": {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"},
|
|
"area_id": "uuid",
|
|
"preview_limit": 2000
|
|
}
|
|
```
|
|
|
|
`bbox` and `area_id` bound the comparison to the operator's selection, resolved
|
|
the same way every other analysis resolves it: the drawn rectangle intersected
|
|
with the named work area. Both populations are loaded through an indexed
|
|
`ST_Intersects` predicate rather than being read into Python in full. Without a
|
|
selection the comparison still covers both datasets entire, which is rarely the
|
|
question and — with `include_unchanged` true — previously returned a
|
|
FeatureCollection holding both datasets.
|
|
|
|
Features are **not** clipped to the selection. A change class describes a whole
|
|
object, so comparing a clipped earlier footprint against an unclipped later one
|
|
would manufacture "modified" along the selection edge. Features the edge crosses
|
|
are compared in full and counted in a warning instead.
|
|
|
|
`modified_threshold` separates a redrawn footprint from two distinct objects.
|
|
Between it and `iou_threshold` the change class is `modified`; below it the
|
|
source is `removed` and the target `added`. Without that class an extended
|
|
building appeared as one removal plus one addition, hiding the category the
|
|
analysis exists to show and inflating both counts.
|
|
|
|
`preview_limit` caps the returned geometry — modified, added, removed and only
|
|
then unchanged — while every count still describes the whole selection.
|
|
`preview_truncated` says whether the cap applied.
|
|
|
|
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:
|
|
|
|
- Change detection itself performs no provider fetch. Bounded GRB acquisition
|
|
is a separate explicit Dataset operation; OSM and Sentinel remain disabled.
|
|
- 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",
|
|
"area_id": "optional-uuid-used-as-an-official-area-constraint",
|
|
"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",
|
|
"intended_use": "review",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
`intended_use` is `review` by default. Every detection FeatureCollection and
|
|
export record contains a machine-readable `geointel_result` / `result_trust`
|
|
contract. Raw model output remains `authoritative: false` and is classified as
|
|
`unverified_ai_review_output` when authoritative QA is absent or incomplete.
|
|
Feature properties repeat the classification so it survives GIS workflows
|
|
that discard collection-level metadata.
|
|
|
|
`intended_use: operational` fails closed with
|
|
`DETECTION_OPERATIONAL_EXPORT_BLOCKED` unless the same persisted run has a
|
|
completed detection-versus-reference check that proves all of the following:
|
|
|
|
- the reference Dataset is authoritative and specifically approved as primary
|
|
authority for building validation;
|
|
- inference coverage was derived from the persisted tile-manifest union;
|
|
- imagery/reference time compatibility is proven;
|
|
- geometry is supported and no QA warnings remain;
|
|
- false-positive and false-negative counts are both explicitly present and
|
|
zero.
|
|
|
|
Even a passing operational export remains AI-derived and therefore retains
|
|
`authoritative: false`, the exact quality-check/reference identifiers and an
|
|
operator-review limitation. Model confidence by itself never unlocks export.
|
|
|
|
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. Detection export
|
|
in the frontend is deliberately labelled as a control layer and requests
|
|
`intended_use: review`; it cannot silently produce an operationally approved
|
|
artifact. 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. When
|
|
`area_id` is present, the export scope is `bbox ∩ Area`. The frontend sends the
|
|
active Area for drawn, manual and full-work-area selections so no result can
|
|
leak outside the chosen official boundary. A bbox enclosing the complete Area
|
|
still resolves to the exact Area geometry. Raster
|
|
datasets are rejected for dataset and selection GeoJSON export.
|
|
|
|
### POST `/api/v1/exports/map-result`
|
|
|
|
Persists the active map result before opening the Downloads workspace. The
|
|
backend recomputes the result from persisted data; it never accepts client
|
|
metrics as authoritative export content.
|
|
|
|
Current vector or governed raster request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"mode": "current",
|
|
"dataset_id": "uuid",
|
|
"bbox": {
|
|
"min_x": 5.10,
|
|
"min_y": 51.17,
|
|
"max_x": 5.11,
|
|
"max_y": 51.18,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"area_id": null,
|
|
"partitioned": false,
|
|
"product_key": null,
|
|
"theme_id": "buildings",
|
|
"name": "buildings-analysis"
|
|
}
|
|
```
|
|
|
|
Historical comparison request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"mode": "evolution",
|
|
"earlier_dataset_id": "uuid",
|
|
"later_dataset_id": "uuid",
|
|
"bbox": {
|
|
"min_x": 5.10,
|
|
"min_y": 51.17,
|
|
"max_x": 5.11,
|
|
"max_y": 51.18,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"theme_id": "forest"
|
|
}
|
|
```
|
|
|
|
Vector results reuse the authoritative `vector_selection_geojson` flow.
|
|
Governed raster results are persisted as `map_analysis_json`; temporal
|
|
comparisons use `map_evolution_json`. Metadata records the bbox, optional exact
|
|
Area scope, source datasets, theme and `server_recomputed=true`.
|
|
|
|
### 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.
|
|
Configured supplemental metrics can use `filter_property` plus
|
|
`filter_values`. Filters are server-owned dataset metadata, not arbitrary
|
|
client SQL or request expressions.
|
|
|
|
### 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": {"min_x": 5.0, "min_y": 51.0, "max_x": 5.2, "max_y": 51.2, "crs": "EPSG:4326"},
|
|
"area_id": "optional persisted Area uuid",
|
|
"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.
|
|
`metric` remains the backwards-compatible primary measurement. `metrics`
|
|
contains every aggregation that is compatible between both snapshots and
|
|
`timeline` contains the same persisted metric for every dated snapshot in the
|
|
series. When `area_id` is supplied it must belong to the project and the exact
|
|
persisted Area geometry is used; the bbox remains only the bounded map extent.
|
|
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.
|
|
|
|
Governed regional GRB snapshots use the official OGC feature identifier as
|
|
their object identity. New imports declare the identity scheme and accepted
|
|
collection prefixes explicitly. Existing operator-managed GRB snapshots are
|
|
accepted only when their authoritative, complete and area-clipped provenance
|
|
matches an approved regional operator and every selected identifier is
|
|
present, unique and uses the expected prefix. Missing, duplicate or unexpected
|
|
identifiers fail closed to metric-only comparison. The existing 5,000-feature
|
|
selection limit also remains in force. Differences between daily GRB editions
|
|
describe changes in the official registration; they do not prove that a
|
|
physical change happened on the exact publication date.
|
|
|
|
For `reference_layer_name=agriculture`, the primary map metric is exact
|
|
intersected declared-use area in hectares. Supplemental metrics use
|
|
server-owned filters on the normalized official main-crop group. Annual ALZ
|
|
Datasets share one scope-specific temporal series, but declare
|
|
`identity_stable=false`; their temporal response compares area totals and
|
|
returns no parcel-level added/removed/modified claims.
|
|
|
|
For `reference_layer_name=building_registry`, the primary metric is exact
|
|
intersected building-footprint area in hectares. Supplemental server-owned
|
|
metrics expose register building/status counts, aggregate building-unit and
|
|
address-status counts, and confirmed GRB matches. Filtered `feature_count`
|
|
metrics apply their configured property filter in PostGIS just like filtered
|
|
area/sum metrics. Address labels and house/box numbers are never part of the
|
|
queryable Feature properties or response contract.
|
|
|
|
The governed snapshot is scoped to its persisted `area_id`. The frontend may
|
|
prefer it over the regional GRB building layer only when that exact Area is
|
|
active; another municipality or the full region must continue to use the
|
|
regional GRB Dataset. No new register-specific API endpoint exists: upload,
|
|
GeoJSON, exact selection and temporal provenance use the existing Dataset
|
|
contracts.
|
|
|
|
## Local GeoIntel assistant
|
|
|
|
The assistant is an optional read-only language interface over persisted
|
|
GeoIntel measurements. The browser never connects to Ollama directly and does
|
|
not choose an arbitrary provider URL.
|
|
|
|
### GET `/api/v1/assistant/status`
|
|
|
|
Returns `configured`, `not_configured` or `unavailable`, the configured default
|
|
model and the number of locally installed models. It never downloads a model.
|
|
|
|
### GET `/api/v1/assistant/models`
|
|
|
|
Returns the models reported by Ollama `GET /api/tags` in the canonical
|
|
envelope. A chat request can only select a model from this list.
|
|
|
|
### POST `/api/v1/projects/{project_id}/assistant/query`
|
|
|
|
```json
|
|
{
|
|
"question": "Hoe evolueerde de bosoppervlakte?",
|
|
"model": "qwen3.5:9b",
|
|
"bbox": {"min_x": 5.0, "min_y": 51.0, "max_x": 5.2, "max_y": 51.2, "crs": "EPSG:4326"},
|
|
"area_id": "optional persisted Area uuid",
|
|
"history": []
|
|
}
|
|
```
|
|
|
|
The backend validates project/Area ownership, calculates current semantic
|
|
metrics from PostGIS and includes dated observations only for persisted
|
|
temporal series. Geometry is not sent to Ollama. The response contains the
|
|
answer, used model, scope label, context metrics, discovered temporal series,
|
|
source dataset ids and warnings. Missing measurements remain unavailable;
|
|
specifically, no water volume is inferred from 2D water geometry. The backend
|
|
sets an explicit Ollama context window and returns
|
|
`OLLAMA_RESPONSE_TRUNCATED` instead of accepting a response with
|
|
`done_reason=length` as a complete answer.
|
|
|
|
## Bathymetry and VHA profile acquisition
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/bathymetry/sources`
|
|
|
|
Returns the governed bathymetry source registry in the canonical envelope.
|
|
VHA inland profiles are `operational`. MDK Belgian Continental Shelf is
|
|
`not_configured` by default and becomes `operational` only after the operator
|
|
explicitly enables bounded acquisition and pins a coverage identifier. The
|
|
pinned SPW Walloon bathymetry archive is `operational` through a bounded,
|
|
explicit operator import. There is no browser-side source fetch and no
|
|
arbitrary source URL.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/bathymetry/sources/mdk_bcp_bathymetry/readiness`
|
|
|
|
Runs one bounded, read-only WCS 1.0.0 `GetCapabilities` request with mandatory
|
|
system TLS verification and a configured response-size limit. Status is one
|
|
of `disabled`, `invalid_configuration`, `tls_error`,
|
|
`endpoint_unavailable`, `invalid_capabilities` or `reachable`. A reachable
|
|
response lists coverage identifiers, advertised formats and CRS values. There
|
|
is no insecure TLS fallback and this readiness endpoint never performs a
|
|
`GetCoverage` request.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/bathymetry/mdk/acquire`
|
|
|
|
Runs one explicit, bounded WCS 1.0.0 `GetCoverage` acquisition as a synchronous
|
|
Job. The request contains an EPSG:4326 `bbox`, optional persisted `area_id` and
|
|
`force_refresh`. Acquisition is fail-closed unless
|
|
`MDK_BATHYMETRY_ACQUISITION_ENABLED=true`, a coverage identifier is explicitly
|
|
configured, the strict-TLS readiness probe is reachable and that identifier is
|
|
advertised by the live capabilities document.
|
|
|
|
The configured bbox-area, response-size, timeout and pixel-dimension limits are
|
|
always enforced. A successful GeoTIFF is validated and imported through the
|
|
existing Dataset raster flow with request hash, response hash, acquisition
|
|
time, MDK attribution and the `LAT` vertical reference in provenance. No depth
|
|
values are synthesized and no water volume is inferred.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/bathymetry/profiles/acquire`
|
|
|
|
```json
|
|
{
|
|
"bbox": {"min_x": 5.0, "min_y": 51.0, "max_x": 5.3, "max_y": 51.3, "crs": "EPSG:4326"},
|
|
"area_id": "optional persisted Area uuid",
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The endpoint creates a synchronous `vector.bathymetry_profiles.acquire` Job.
|
|
It queries the official VHA Digital Atlas in bounded pages, intersects every
|
|
point with the persisted Area when supplied and persists one ordinary
|
|
reference Dataset plus VectorFeature rows. The result reports exact profile,
|
|
document, structured-depth/width and watercourse counts and the source
|
|
measurement-date range.
|
|
|
|
The acquired Dataset uses
|
|
`source_name=vmm_vha_bathymetry_profiles`,
|
|
`dataset_role=reference` and
|
|
`reference_layer_name=bathymetry_profiles`. Existing vector content and
|
|
selection endpoints provide GeoJSON and metrics. The contract does not expose
|
|
a continuous bathymetric surface, current water depth or water volume.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/bathymetry/profiles/partitions/finalize`
|
|
|
|
Finalizes a complete municipality-partition manifest. The request supplies a
|
|
scope key, every expected municipality Area id, one ready VHA Dataset id for
|
|
each non-empty partition, explicit Area ids with zero source profiles, a
|
|
SHA-256 manifest identity and observation time. The backend verifies project,
|
|
Area, provider, Dataset and one-Dataset-per-Area consistency. Only a complete
|
|
accounting sets `partitioned_source_audit=true` and
|
|
`regional_partitions_complete=true`; partial operator runs remain hidden at
|
|
regional scope.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/bathymetry/profiles/partitions/select`
|
|
|
|
Runs one bounded spatial selection across the latest complete `flanders`
|
|
VHA manifest. The request reuses `VectorSelectionRequest`: EPSG:4326 bbox,
|
|
optional persisted Area and a result limit of at most 1,000 features.
|
|
|
|
The backend first rejects incomplete or inconsistent manifests, selects only
|
|
municipality Dataset partitions whose recorded bounds overlap the request and
|
|
then queries their persisted `vector_features` with one PostGIS intersection.
|
|
When the Area is a municipality, only that exact Area partition is eligible.
|
|
A regional Area can combine all intersecting partitions without treating one
|
|
municipality Dataset as representative regional data.
|
|
|
|
The canonical response extends the ordinary vector-selection result with
|
|
`partition_count`, `available_partition_count`, `partition_scope_key`,
|
|
`source_name` and the contributing `dataset_ids`. GeoJSON is capped by the
|
|
request limit, while `total_feature_count` and all configured depth/width
|
|
metrics are calculated across the complete spatial result. Empty municipalities
|
|
return an empty, honest result. No provider request occurs during analysis.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/bathymetry/select`
|
|
|
|
Runs a bounded selection against a ready persisted
|
|
`source_name=spw_bathymetry` raster. The request contains an EPSG:4326 bbox
|
|
and optional persisted `area_id`, matching the other governed raster-selection
|
|
contracts.
|
|
|
|
The response reports:
|
|
|
|
- mean, minimum, maximum, p10 and p90 waterbed elevation in `m mDNG`;
|
|
- exact raster-cell surface with surveyed bed values in hectares;
|
|
- source coverage percentage inside the selected geometry;
|
|
- source resolution, survey period `2019-2022`, vertical reference and
|
|
explicit unsupported metrics.
|
|
|
|
`current_water_depth_m`, `water_volume_m3` and vertical-datum conversion remain
|
|
unsupported. The endpoint reads the persisted EPSG:3812 geometry-aligned
|
|
raster; it does not query SPW and does not synthesize missing cells.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/bathymetry/image`
|
|
|
|
Returns a transparent PNG rendering of the persisted bathymetry COG for the
|
|
MapLibre image-overlay path. Rendering changes presentation only. Analytical
|
|
values always come from the stored Float32 source cells.
|
|
|
|
Future provider output continues to use DatasetService and, for vectors,
|
|
VectorFeatureService. Arbitrary service URLs, browser-side fetches, insecure
|
|
TLS bypasses and startup downloads remain forbidden.
|
|
## Governed regional official-vector acquisition
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/official-vector/products`
|
|
|
|
Returns the fixed official vector registry in the canonical `{ "data": ... }`
|
|
envelope. Every item includes its theme, geometry contract, authority,
|
|
licence/attribution, provider-native collection, query mode and
|
|
`coverage_zones`. The fixed allowlist contains:
|
|
|
|
- Flanders: `bwk_natura2000_2025` and `dov_soil_types`;
|
|
- Wallonia: `spw_picc_buildings`, `spw_picc_roads`,
|
|
`spw_picc_waterways` and `spw_picc_water_surfaces`;
|
|
- Brussels: `urbis_buildings` and `urbis_cadastral_parcels`.
|
|
|
|
Arbitrary collection names or URLs are never accepted.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/official-vector/acquire`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"bbox": {
|
|
"min_x": 5.05,
|
|
"min_y": 51.15,
|
|
"max_x": 5.25,
|
|
"max_y": 51.30,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"area_id": "optional-area-uuid",
|
|
"product_key": "bwk_natura2000_2025",
|
|
"force_refresh": false
|
|
}
|
|
```
|
|
|
|
The synchronous `vector.official.acquire` Job validates the metric request
|
|
size, intersects `bbox` with the persisted Area, retrieves every bounded page,
|
|
clips source geometry in the provider-native metric CRS and persists
|
|
EPSG:4326 features through `DatasetService`. A repeated exact request can
|
|
reuse the 24-hour cache. Provider errors, unstable/incomplete pagination and
|
|
safety-limit violations fail without persisting a truncated Dataset.
|
|
|
|
Walloon and Brussels products additionally require a persisted exact regional
|
|
coverage Area (`Wallonia` or `Brussels-Capital Region`). SPW/PICC uses stable
|
|
`OBJECTID` ArcGIS REST paging; UrbIS uses WFS 2.0 paging ordered by
|
|
`INSPIRE_ID`. A caller cannot make a Flemish product cover Wallonia, or merge
|
|
different regional authorities into one Dataset.
|
|
|
|
`bwk_natura2000_2025` preserves BWK `EVAL`, `EENH*`, `HAB*` and `PHAB*`
|
|
semantics. `dov_soil_types` preserves mapped soil, texture, drainage, profile
|
|
and substrate classes and is dated as the 1949-1971 survey period. Neither
|
|
contract accepts a caller-supplied endpoint.
|
|
|
|
PICC building footprints, road axes and hydrographic axes/surfaces retain
|
|
their source identifiers and report source-appropriate counts, footprint/
|
|
surface hectares or line kilometres. UrbIS building footprints and cadastral
|
|
parcels remain separate products with their own Paradigm/FPS Finance licence
|
|
notes. These products do not claim semantic parity with GRB and do not expose
|
|
water volume.
|
|
|
|
## Governed Landgebruik Vlaanderen forest and agriculture
|
|
|
|
The existing
|
|
`GET /api/v1/projects/{project_id}/datasets/thematic-raster/products` registry
|
|
also returns `forest_land_use_2025` for source class 12 and
|
|
`agricultural_land_use_2025` for source classes 13 and 14. Both use the
|
|
existing thematic acquisition and selection contracts. Persisted rasters are
|
|
binary masks; the source class allowlist and original source-value range are
|
|
retained and validated.
|