2246 lines
80 KiB
Markdown
2246 lines
80 KiB
Markdown
# API Contracts v1
|
|
|
|
This document freezes the first API shape. Codex may add implementation details but must not rename these routes without updating this file and the frontend API client.
|
|
|
|
## API principles
|
|
|
|
- Base path: `/api/v1`.
|
|
- JSON by default.
|
|
- GeoJSON accepted for geometries where possible.
|
|
- Long processing tasks return a job or analysis run record instead of blocking.
|
|
- Error responses use the shared `ApiError` schema.
|
|
|
|
## Shared schemas
|
|
|
|
### ApiError
|
|
|
|
```json
|
|
{
|
|
"error": "string",
|
|
"message": "human readable message",
|
|
"details": {},
|
|
"request_id": "optional string"
|
|
}
|
|
```
|
|
|
|
### GeoJsonGeometry
|
|
|
|
Any valid GeoJSON geometry object. V1 primarily expects `Polygon` and `MultiPolygon` for areas.
|
|
|
|
### BoundingBox
|
|
|
|
```json
|
|
{
|
|
"min_x": 0.0,
|
|
"min_y": 0.0,
|
|
"max_x": 0.0,
|
|
"max_y": 0.0,
|
|
"crs": "EPSG:4326"
|
|
}
|
|
```
|
|
|
|
## Health
|
|
|
|
### GET `/health`
|
|
|
|
Returns service status.
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "geointel-backend",
|
|
"version": "0.1.0"
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/system/capabilities`
|
|
|
|
Returns enabled feature flags and tool availability.
|
|
|
|
```json
|
|
{
|
|
"postgis": true,
|
|
"rasterio": true,
|
|
"geopandas": true,
|
|
"yolo": false,
|
|
"sam": false,
|
|
"grb": "bounded",
|
|
"sentinel": "planned",
|
|
"providers": [
|
|
{
|
|
"provider_name": "grb",
|
|
"display_name": "GRB",
|
|
"authority_level": "authoritative",
|
|
"supported_layers": ["buildings", "roads", "water", "parcels"],
|
|
"supported_geometry_types": ["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
|
|
"supported_query_modes": ["bbox", "persisted_area"],
|
|
"fetch_signature": "POST /api/v1/projects/{project_id}/datasets/grb/acquire",
|
|
"configured": true,
|
|
"status": "configured",
|
|
"limitation_message": "Alleen expliciet begrensde selecties tot 20 km per zijde worden opgehaald.",
|
|
"attribution": "Bron: Grootschalig Referentie Bestand Vlaanderen, Digitaal Vlaanderen",
|
|
"license_note": "Hergebruik volgens de open-datavoorwaarden en bronvermelding van Digitaal Vlaanderen.",
|
|
"not_configured_reason": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## 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=Kempen%20Regional%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": "Kempen"
|
|
}
|
|
```
|
|
|
|
Response: `ProjectRead`.
|
|
|
|
### GET `/api/v1/projects/{project_id}`
|
|
|
|
Returns one project with summary counts.
|
|
|
|
### PATCH `/api/v1/projects/{project_id}`
|
|
|
|
Updates name, description, region 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.
|
|
|
|
## Datasets
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/upload`
|
|
|
|
Multipart upload.
|
|
|
|
Fields:
|
|
|
|
- `file`: dataset file.
|
|
- `dataset_type`: `vector`, `geojson` (legacy), `raster`.
|
|
- `source`: free text, e.g. `user_upload`, `grb`, `osm`.
|
|
- `dataset_role`: `source`, `derived`, or `reference` (default `source`).
|
|
- `source_name`: optional source identity, e.g. `manual`, `grb`, `osm`; reference uploads default to `manual` when omitted.
|
|
- `reference_layer_name`: optional reference layer label, e.g. `buildings`; only retained for reference datasets.
|
|
- `area_id`: optional.
|
|
|
|
Response: `DatasetRead` with extracted metadata if supported.
|
|
|
|
Vector uploads remain stored as original files and are also persisted into `vector_features` as queryable PostGIS state.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/orthophoto/products`
|
|
|
|
Return the governed Digitaal Vlaanderen orthophoto product allowlist in the
|
|
canonical envelope. Every product reports its key, display/observation label,
|
|
temporal granularity, native resolution, colour mode, catalogue URL,
|
|
limitations and whether current configured-YOLO detection is allowed.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/orthophoto/acquire`
|
|
|
|
Explicitly acquire a bounded orthophoto selection from a governed official
|
|
Digitaal Vlaanderen WMS product. Arbitrary WMS URLs and layer names are not
|
|
accepted.
|
|
|
|
```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",
|
|
"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.
|
|
|
|
### 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` can enter the current configured-YOLO plus GRB-QA path;
|
|
historical products are visual evidence and are never validated against the
|
|
current GRB state;
|
|
- 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 selection, 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.
|
|
|
|
### 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`
|
|
|
|
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.
|
|
|
|
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select`
|
|
|
|
Read-only spatial selection over persisted `vector_features`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"area_id": "optional persisted area UUID",
|
|
"limit": 250
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"selection_bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"selection_area_id": "present when area_id was requested",
|
|
"feature_count": 2,
|
|
"total_feature_count": 2,
|
|
"limit": 250,
|
|
"truncated": false,
|
|
"geojson": {
|
|
"type": "FeatureCollection",
|
|
"features": []
|
|
},
|
|
"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/{dataset_id}/vector/select/derive`
|
|
|
|
Persists a bbox selection as a new derived vector dataset and indexes the
|
|
selected output into `vector_features`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"bbox": {
|
|
"min_x": 5.0,
|
|
"min_y": 51.0,
|
|
"max_x": 5.1,
|
|
"max_y": 51.1,
|
|
"crs": "EPSG:4326"
|
|
},
|
|
"limit": 250,
|
|
"output_name": "selected-buildings"
|
|
}
|
|
```
|
|
|
|
Response: `DatasetRead` in the canonical API envelope.
|
|
|
|
Rules:
|
|
|
|
- Only vector/GeoJSON datasets are supported.
|
|
- Coordinates are EPSG:4326 longitude/latitude.
|
|
- The new dataset uses `dataset_role="derived"`, `source="operation:selection"`,
|
|
`source_name="map_selection"` and `derived_from_dataset_id` pointing to the
|
|
source dataset.
|
|
- The persisted GeoJSON properties retain source provenance as
|
|
`source_dataset_id` and `source_vector_feature_id`.
|
|
- Empty selections return `VECTOR_OPERATION_EMPTY_RESULT` and do not create a dataset.
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/content`
|
|
|
|
Returns stored vector dataset content through the canonical API envelope.
|
|
Vector content is returned as GeoJSON/JSON payload data. Raster content is not
|
|
served through this endpoint.
|
|
|
|
## Jobs
|
|
|
|
### POST `/api/v1/projects/{project_id}/jobs`
|
|
|
|
Create a job.
|
|
|
|
### GET `/api/v1/projects/{project_id}/jobs`
|
|
|
|
List jobs.
|
|
|
|
### GET `/api/v1/projects/{project_id}/jobs/{job_id}`
|
|
|
|
Read job detail.
|
|
|
|
### GET `/api/v1/projects/{project_id}/jobs/{job_id}/status`
|
|
|
|
Read simplified job status payload.
|
|
|
|
## Provider registry
|
|
|
|
### GET `/api/v1/external/providers`
|
|
|
|
Returns all configured provider capability descriptors.
|
|
|
|
### GET `/api/v1/external/providers/capabilities`
|
|
|
|
Compatibility alias for listing provider capability descriptors.
|
|
|
|
### GET `/api/v1/external/providers/{provider_name}`
|
|
|
|
Returns one provider capability descriptor.
|
|
|
|
### GET `/api/v1/external/providers/{provider_name}/layers`
|
|
|
|
Returns the supported provider layers.
|
|
|
|
### GET `/api/v1/external/providers/{provider_name}/status`
|
|
|
|
Returns configured/status/limitation fields.
|
|
|
|
### POST `/api/v1/external/providers/{provider_name}/import`
|
|
|
|
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.
|
|
|
|
```json
|
|
{
|
|
"models": [
|
|
{
|
|
"model_id": "yolo-placeholder",
|
|
"display_name": "YOLO detector placeholder",
|
|
"framework": "ultralytics/pytorch",
|
|
"task_type": "object_detection",
|
|
"supported_classes": ["building", "road", "water", "landuse"],
|
|
"configured": false,
|
|
"status": "not_configured",
|
|
"limitation_message": "YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed.",
|
|
"version": null
|
|
},
|
|
{
|
|
"model_id": "yolo-configured",
|
|
"display_name": "Configured YOLO detector",
|
|
"framework": "ultralytics/pytorch",
|
|
"task_type": "object_detection",
|
|
"supported_classes": ["building", "road", "water", "landuse"],
|
|
"configured": false,
|
|
"status": "not_configured",
|
|
"limitation_message": "YOLO is disabled. Set YOLO_ENABLED=true and YOLO_MODEL_PATH to a local model file to enable inference.",
|
|
"version": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/detection/model-assets`
|
|
|
|
Returns local runtime model files discovered in the configured model directory.
|
|
This is a read-only catalog. GeoIntel never downloads, creates, mutates or
|
|
deletes model weights from this endpoint.
|
|
|
|
The backend scans `YOLO_MODELS_DIR` (default `/app/models`) and reports
|
|
supported local model files such as `.pt`, `.onnx` and `.engine`. The active
|
|
model is the file matching `YOLO_MODEL_PATH`.
|
|
|
|
Response data:
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"model_asset_id": "building-detector-pt",
|
|
"filename": "building-detector.pt",
|
|
"display_name": "building-detector",
|
|
"model_path": "/app/models/building-detector.pt",
|
|
"suffix": ".pt",
|
|
"framework": "ultralytics/pytorch",
|
|
"task_type": "object_detection",
|
|
"size_bytes": 123456,
|
|
"sha256": "sha256hex",
|
|
"active": true,
|
|
"status": "available",
|
|
"limitation_message": "Local runtime model asset. GeoIntel will not download or mutate model weights.",
|
|
"will_download_models": false
|
|
}
|
|
],
|
|
"total": 1,
|
|
"model_directory": "/app/models"
|
|
}
|
|
```
|
|
|
|
### GET `/api/v1/detection/yolo/preflight`
|
|
|
|
Returns a canonical envelope with read-only configured-YOLO runtime preflight
|
|
state. Optional query parameters:
|
|
|
|
- `tile_manifest_path`: existing raster tile manifest path to validate.
|
|
- `model_asset_id`: optional local model asset ID from
|
|
`GET /api/v1/detection/model-assets`; when supplied, preflight validates that
|
|
asset path instead of the default `YOLO_MODEL_PATH`.
|
|
- `check_model_load`: default `false`; when `true`, explicitly loads only the
|
|
configured local model file for compatibility smoke. It never downloads
|
|
weights and never runs inference.
|
|
|
|
Response data:
|
|
|
|
```json
|
|
{
|
|
"model_id": "yolo-configured",
|
|
"model_asset_id": null,
|
|
"model_path": null,
|
|
"tile_manifest_path": null,
|
|
"status": "not_configured",
|
|
"message": "YOLO_MODEL_PATH is not set. GeoIntel will not download model weights automatically.",
|
|
"checks": {
|
|
"enabled": true,
|
|
"dependencies_available": true,
|
|
"model_path_set": false,
|
|
"model_file_exists": null,
|
|
"model_load_requested": false,
|
|
"model_load_ok": null,
|
|
"manifest_path_set": null,
|
|
"manifest_valid": null,
|
|
"tile_paths_exist": null,
|
|
"tile_limit_ok": null
|
|
},
|
|
"runtime": {
|
|
"dependencies_assumed": false,
|
|
"model_directory": null,
|
|
"yolo_config_dir": "/app/storage/ultralytics",
|
|
"torch_version": "2.12.1",
|
|
"ultralytics_version": "8.4.88",
|
|
"cuda_available": false
|
|
},
|
|
"tile_count": 0,
|
|
"max_tiles": 100,
|
|
"will_download_models": false,
|
|
"will_run_inference": false
|
|
}
|
|
```
|
|
|
|
### POST `/api/v1/detection/run`
|
|
|
|
Creates a detection job and detection analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `DETECTION_MODEL_UNAVAILABLE` or `DETECTION_DEPENDENCY_UNAVAILABLE`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "yolo-placeholder",
|
|
"model_asset_id": null,
|
|
"confidence_threshold": 0.5,
|
|
"class_filter": ["building"],
|
|
"tile_manifest_path": null,
|
|
"parameters_json": {}
|
|
}
|
|
```
|
|
|
|
Sprint 8B configured YOLO mode uses `model_id: "yolo-configured"`. It requires:
|
|
|
|
- `YOLO_ENABLED=true`
|
|
- `YOLO_MODEL_PATH` pointing to an existing local model file
|
|
- backend optional AI dependencies installed with `geointel-backend[ai]`
|
|
- `tile_manifest_path` pointing to an existing raster tile manifest generated by the raster tile operation
|
|
|
|
`model_asset_id` may be supplied with `model_id: "yolo-configured"` to select a
|
|
specific local model file from the read-only model asset catalog. The backend
|
|
resolves the ID to a file inside the configured model directory and persists the
|
|
asset ID, path and SHA-256 in the job and analysis-run parameters for
|
|
reproducibility. Clients must not submit arbitrary model paths.
|
|
|
|
GeoIntel does not download model weights automatically. Configured YOLO runs read existing tile files from the manifest, convert YOLO pixel-space boxes to EPSG:4326 detection polygons and persist detections as first-class records.
|
|
|
|
Unavailable model response:
|
|
|
|
```json
|
|
{
|
|
"analysis_run_id": "uuid",
|
|
"job_id": "uuid",
|
|
"project_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "yolo-placeholder",
|
|
"status": "failed",
|
|
"detection_count": 0,
|
|
"error_code": "DETECTION_MODEL_UNAVAILABLE",
|
|
"message": "YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed."
|
|
}
|
|
```
|
|
|
|
Validation errors:
|
|
|
|
- `INVALID_DATASET_TYPE` when the dataset is not raster.
|
|
- `DETECTION_MODEL_NOT_FOUND` when the model id is unknown.
|
|
- `DETECTION_MODEL_ASSET_NOT_FOUND` when `model_asset_id` is not present in the configured model directory.
|
|
- `FIXTURE_MODE_REQUIRED` when `manual-fixture-detector` is requested without `parameters_json.fixture_mode=true`.
|
|
- `DETECTION_TILE_MANIFEST_REQUIRED` when `yolo-configured` is requested without `tile_manifest_path`.
|
|
- `DETECTION_TILE_MANIFEST_NOT_FOUND` when the provided manifest path does not exist.
|
|
- `DETECTION_TILE_MANIFEST_INVALID` when the manifest cannot be parsed or lacks tile metadata.
|
|
- `DETECTION_TILE_LIMIT_EXCEEDED` when the manifest exceeds `YOLO_MAX_TILES`.
|
|
- Configured YOLO inference forwards `YOLO_MAX_DETECTIONS` to Ultralytics
|
|
`max_det` and defaults to `1000` so dense building AOIs are not silently
|
|
limited by the upstream default of 300 detections before persisted QA/QC.
|
|
- Configured YOLO applies cross-tile duplicate suppression after pixel boxes are
|
|
converted to EPSG:4326 geometries and before `Detection` rows are persisted.
|
|
Same-class candidates are confidence-sorted and lower-confidence candidates
|
|
with geometry IoU greater than or equal to
|
|
`YOLO_DUPLICATE_IOU_THRESHOLD` are suppressed. The default is `0.5`; `0`
|
|
disables this GeoIntel-side post-processing for debugging.
|
|
- `DETECTION_DEPENDENCY_UNAVAILABLE` when YOLO dependencies are not installed.
|
|
- `DETECTION_MODEL_LOAD_FAILED` when the local model file exists but cannot be loaded.
|
|
|
|
Fixture detector mode is test/demo-only. It persists only explicit `parameters_json.fixture_detections` entries and is never invoked automatically.
|
|
|
|
### GET `/api/v1/detection/runs/{analysis_run_id}`
|
|
|
|
Returns one detection analysis run.
|
|
|
|
### GET `/api/v1/detection/runs`
|
|
|
|
Returns detection analysis runs, optionally filtered by `project_id` and `dataset_id`.
|
|
|
|
### GET `/api/v1/detection/runs/{analysis_run_id}/detections`
|
|
|
|
Returns persisted detections for a detection analysis run. Optional filters:
|
|
|
|
- `dataset_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/detection/datasets/{dataset_id}/detections`
|
|
|
|
Returns persisted detections for a raster dataset. Optional filters:
|
|
|
|
- `analysis_run_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/detection/detections/{detection_id}`
|
|
|
|
Returns one persisted detection.
|
|
|
|
### GET `/api/v1/detection/runs/{analysis_run_id}/geojson`
|
|
|
|
Returns persisted detections for a run as a GeoJSON FeatureCollection. Geometry comes from persisted PostGIS detection geometry in EPSG:4326.
|
|
|
|
Each feature includes:
|
|
|
|
- `detection_id`
|
|
- `class_name`
|
|
- `confidence`
|
|
- `model_name`
|
|
- `model_version`
|
|
- `analysis_run_id`
|
|
- `dataset_id`
|
|
- `job_id`
|
|
- `source_tile_path`
|
|
- `bbox_json`
|
|
|
|
### GET `/api/v1/detection/datasets/{dataset_id}/geojson`
|
|
|
|
Returns persisted detections for a dataset as a GeoJSON FeatureCollection. Optional filters match the detection list endpoint.
|
|
|
|
### POST `/api/v1/detection/runs/{analysis_run_id}/qa/reference`
|
|
|
|
Compares persisted detection geometries from an analysis run against persisted `vector_features` from a reference vector dataset.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"reference_dataset_id": "uuid",
|
|
"iou_threshold": 0.5,
|
|
"class_name": "building",
|
|
"min_confidence": 0.5
|
|
}
|
|
```
|
|
|
|
Response persists a `quality_check` and `metrics` rows through the existing QA/QC persistence architecture and returns:
|
|
|
|
- `precision`
|
|
- `recall`
|
|
- `f1_score`
|
|
- `mean_iou`
|
|
- `false_positives`
|
|
- `false_negatives`
|
|
- `quality_check_id`
|
|
|
|
Configured-YOLO QA automatically reads `tile_manifest_path` from the persisted
|
|
`AnalysisRun.parameters_json`. Candidate and reference geometries are clipped
|
|
to the union of the manifest's tile bounds after explicit CRS transformation to
|
|
EPSG:4326. Before reference geometries are materialized, the service applies
|
|
that coverage with an indexed PostGIS `ST_Intersects` predicate. The full
|
|
dataset count is retained separately so raw/evaluated/excluded counts remain
|
|
auditable without transferring a regional reference dataset to Python. The
|
|
response additionally returns:
|
|
|
|
- `candidate_feature_count_raw` and `reference_feature_count_raw`;
|
|
- `coverage`, including raw/evaluated/excluded/boundary-clipped population
|
|
counts, tile count, source CRS values and coverage mode;
|
|
- `box_to_footprint_diagnostics`, which compares candidate boxes with reference
|
|
envelopes at the same IoU threshold.
|
|
|
|
The canonical precision, recall, F1 and mean IoU always remain based on
|
|
candidate geometry versus the persisted reference footprint. Envelope results
|
|
are explicitly `diagnostic_only` and are persisted in
|
|
`quality_checks.findings_json`; they never replace or inflate canonical metrics.
|
|
|
|
Configured-YOLO QA fails closed with `DETECTION_QA_COVERAGE_UNAVAILABLE` when
|
|
manifest provenance is absent, `DETECTION_QA_COVERAGE_MISMATCH` when it belongs
|
|
to another raster, `DETECTION_QA_COVERAGE_INVALID` when bounds/CRS are invalid,
|
|
or `REFERENCE_FEATURES_OUTSIDE_COVERAGE` when no reference polygons overlap the
|
|
actual inference coverage. Explicit fixture/legacy runs without a manifest keep
|
|
the documented unbounded comparison behavior.
|
|
|
|
If the reference dataset has no persisted vector features, the endpoint returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
|
|
|
|
#### Future analysis route: `/api/v1/analysis/building-stats`
|
|
|
|
Not implemented in the active API surface. Future input is expected to combine
|
|
an area with a vector building layer.
|
|
|
|
#### Future analysis route: `/api/v1/analysis/object-detection`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"area_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "optional uuid",
|
|
"classes": ["building"],
|
|
"confidence_threshold": 0.35,
|
|
"tile_size": 640,
|
|
"overlap": 64
|
|
}
|
|
```
|
|
|
|
Response: `AnalysisRunRead`.
|
|
|
|
#### Future analysis route: `/api/v1/analysis/segmentation`
|
|
|
|
Same pattern as object detection, but output includes masks and polygonized geometries.
|
|
|
|
## Segmentation Lab
|
|
|
|
Sprint 9 implements Segmentation Lab foundation only. Real SAM and YOLO-seg inference are not enabled, no model is downloaded, and fixture segmentations require explicit fixture mode.
|
|
|
|
### GET `/api/v1/segmentation/models`
|
|
|
|
Returns segmentation model capability descriptors:
|
|
|
|
- `segmentation-placeholder`: `not_configured`
|
|
- `fixture-segmenter`: configured for explicit test/demo fixtures only
|
|
- `yolo-seg-configured`: `not_configured`
|
|
- `sam-configured`: `not_configured`
|
|
|
|
### POST `/api/v1/segmentation/run`
|
|
|
|
Creates a segmentation job and segmentation analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `SEGMENTATION_MODEL_UNAVAILABLE`.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"dataset_id": "uuid",
|
|
"model_id": "segmentation-placeholder",
|
|
"confidence_threshold": 0.5,
|
|
"class_filter": ["vegetation"],
|
|
"tile_manifest_path": null,
|
|
"parameters_json": {}
|
|
}
|
|
```
|
|
|
|
Fixture segmenter mode is test/demo-only. It persists only explicit `parameters_json.fixture_segmentations` entries when `parameters_json.fixture_mode=true`; it is never invoked automatically and does not represent production inference.
|
|
|
|
Validation errors:
|
|
|
|
- `INVALID_DATASET_TYPE` when the dataset is not raster.
|
|
- `SEGMENTATION_MODEL_NOT_FOUND` when the model id is unknown.
|
|
- `FIXTURE_MODE_REQUIRED` when `fixture-segmenter` is requested without `parameters_json.fixture_mode=true`.
|
|
- `INVALID_FIXTURE_SEGMENTATIONS` when fixture payloads are not a list.
|
|
- `INVALID_FIXTURE_GEOMETRY` when fixture geometry is empty, invalid or not Polygon/MultiPolygon.
|
|
|
|
### GET `/api/v1/segmentation/runs`
|
|
|
|
Returns segmentation analysis runs, optionally filtered by `project_id` and `dataset_id`.
|
|
|
|
### GET `/api/v1/segmentation/runs/{analysis_run_id}`
|
|
|
|
Returns one segmentation analysis run.
|
|
|
|
### GET `/api/v1/segmentation/runs/{analysis_run_id}/segmentations`
|
|
|
|
Returns persisted segmentation records for a segmentation analysis run. Optional filters:
|
|
|
|
- `dataset_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/segmentation/datasets/{dataset_id}/segmentations`
|
|
|
|
Returns persisted segmentation records for a raster dataset. Optional filters:
|
|
|
|
- `analysis_run_id`
|
|
- `class_name`
|
|
- `min_confidence`
|
|
|
|
### GET `/api/v1/segmentation/segmentations/{segmentation_id}`
|
|
|
|
Returns one persisted segmentation record.
|
|
|
|
### GET `/api/v1/segmentation/runs/{analysis_run_id}/geojson`
|
|
|
|
Returns persisted segmentations for a run as a GeoJSON FeatureCollection. Geometry comes from persisted PostGIS segmentation geometry in EPSG:4326.
|
|
|
|
Each feature includes:
|
|
|
|
- `segmentation_id`
|
|
- `class_name`
|
|
- `confidence`
|
|
- `area_m2`
|
|
- `model_name`
|
|
- `model_version`
|
|
- `analysis_run_id`
|
|
- `dataset_id`
|
|
- `job_id`
|
|
- `source_tile_path`
|
|
- `tile_index`
|
|
- `mask_path`
|
|
- `bbox_json`
|
|
- `provenance_json`
|
|
|
|
### GET `/api/v1/segmentation/datasets/{dataset_id}/geojson`
|
|
|
|
Returns persisted segmentations for a dataset as a GeoJSON FeatureCollection. Optional filters match the segmentation list endpoint.
|
|
|
|
### POST `/api/v1/segmentation/runs/{analysis_run_id}/qa/reference`
|
|
|
|
Compares persisted segmentation geometries from an analysis run against persisted `vector_features` from a reference vector dataset.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"reference_dataset_id": "uuid",
|
|
"iou_threshold": 0.5,
|
|
"class_name": "vegetation",
|
|
"min_confidence": 0.5
|
|
}
|
|
```
|
|
|
|
Response persists a `quality_check` and `metrics` rows through the existing QA/QC persistence architecture and returns precision, recall, F1, mean IoU and false positive/negative counts.
|
|
|
|
If the segmentation run has no persisted geometries, the endpoint returns `SEGMENTATIONS_NOT_FOUND`. If the reference dataset has no persisted vector features, it returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
|
|
|
|
### POST `/api/v1/analysis/change-detection`
|
|
|
|
Compares two persisted vector datasets in the same project and returns a synchronous
|
|
job envelope. This is a lightweight V1 foundation for added/removed object review,
|
|
not a temporal run-history engine.
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"source_dataset_id": "uuid",
|
|
"target_dataset_id": "uuid",
|
|
"iou_threshold": 0.8,
|
|
"include_unchanged": true
|
|
}
|
|
```
|
|
|
|
Response is a canonical API envelope containing a `JobRead` payload. On success,
|
|
`result_json` contains:
|
|
|
|
```json
|
|
{
|
|
"source_dataset_id": "uuid",
|
|
"target_dataset_id": "uuid",
|
|
"source_feature_count": 2,
|
|
"target_feature_count": 2,
|
|
"added_count": 1,
|
|
"removed_count": 1,
|
|
"unchanged_count": 1,
|
|
"iou_threshold": 0.8,
|
|
"warnings": [],
|
|
"generated_at": "2026-06-16T00:00:00Z",
|
|
"geojson": {
|
|
"type": "FeatureCollection",
|
|
"features": []
|
|
}
|
|
}
|
|
```
|
|
|
|
Change detection prefers persisted `vector_features`. If an older vector dataset
|
|
has no persisted vector rows, it falls back to the stored GeoJSON artifact and
|
|
adds a warning to `result_json.warnings`. Supported comparable geometry types are
|
|
`Polygon` and `MultiPolygon`; point/line geometries return `UNSUPPORTED_GEOMETRY`.
|
|
|
|
GeoJSON feature properties include:
|
|
|
|
- `change_type`: `added`, `removed` or `unchanged`
|
|
- `source_dataset_id`
|
|
- `target_dataset_id`
|
|
- `source_feature_id`
|
|
- `target_feature_id`
|
|
- `iou`
|
|
|
|
Limitations:
|
|
|
|
- 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",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Segmentation run export request:
|
|
|
|
```json
|
|
{
|
|
"export_kind": "segmentation_run",
|
|
"analysis_run_id": "uuid",
|
|
"name": "optional-basename"
|
|
}
|
|
```
|
|
|
|
Response persists an `exports` row and writes a deterministic JSON artifact:
|
|
|
|
```json
|
|
{
|
|
"export_id": "uuid",
|
|
"path": "storage/exports/{project_id}/datasets/{target}/{name}.geojson",
|
|
"status": "ready",
|
|
"export_type": "dataset_geojson",
|
|
"metadata_json": {
|
|
"source": "dataset",
|
|
"feature_count": 0
|
|
}
|
|
}
|
|
```
|
|
|
|
Vector dataset exports use the stored dataset GeoJSON. Detection and
|
|
segmentation exports use persisted first-class geometry records and the
|
|
existing Detection/Segmentation GeoJSON conversion services. Vector selection
|
|
exports query persisted PostGIS `vector_features` with the supplied EPSG:4326
|
|
bbox, write the selected FeatureCollection as a `vector_selection_geojson`
|
|
artifact, and persist bbox/feature-count metadata in the export record. 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
|
|
`probe_only`; SPW Walloon bathymetry remains `available_not_integrated`.
|
|
Neither source can be acquired until its raster/download and vertical-datum
|
|
flow passes live validation.
|
|
|
|
### 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, but
|
|
always returns `acquisition_supported=false`. There is no insecure TLS
|
|
fallback and no `GetCoverage` request.
|
|
|
|
### 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.
|
|
|
|
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 official nature and soil acquisition
|
|
|
|
### GET `/api/v1/projects/{project_id}/datasets/official-vector/products`
|
|
|
|
Returns the fixed official vector registry in the canonical `{ "data": ... }`
|
|
envelope. The allowlist contains `bwk_natura2000_2025` and `dov_soil_types`;
|
|
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 polygon geometry in EPSG:31370 and persists EPSG:4326 features through
|
|
`DatasetService`. A repeated exact request can reuse the 24-hour cache.
|
|
Provider errors, unstable or incomplete WFS pagination and safety-limit violations
|
|
fail without persisting a truncated 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.
|
|
|
|
## 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.
|