Files
gis/api/openapi.yaml
Bakhtiyar Issakhmetov 6aec00ee29 fix(landscape): accept an empty run body; pin the builder's Alpine release
POST /landscape/runs documents an optional body, but DecodeJSON returned
io.EOF on an empty one and the handler turned that into 400 — breaking the
most common call, an all-defaults run. decodeRunRequest now treats io.EOF as
the zero request; a malformed body is still 400.

The Dockerfile's Alpine ABI coupling was enforced only by a comment: a
floating golang:1.26-alpine builder against a pinned alpine:3.24 runtime
would build green and die at startup on a libgdal soname mismatch as soon as
the Go image rolls forward. Both stages are now pinned to 3.24.

Also: the route-mount test walks the router so all six routes are asserted
(GET /slots and GET /results included), and the spec documents the 404 those
two return when migration 00011's seeded groups are absent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 14:22:26 +05:00

1699 lines
54 KiB
YAML

openapi: 3.1.1
info:
title: GIS API
version: "1.0.0"
summary: Hierarchical categories and geo datasets with async processing.
description: |
HTTP API for hierarchical categories and geo datasets (vector,
vector_with_kato, raster).
Datasets are processed asynchronously after upload, dispatched by `file_type`:
- `vector` — the attribute table is extracted and, with the geometry,
assembled into the `.geojson` output.
- `raster` — converted to a Cloud-Optimized GeoTIFF (served at
`GET /datasets/{id}.cog`); `bbox` is derived from the raster extent.
- `vector_with_kato` — columns are detected for selection; the client then
submits a KATO/year mapping, unpivoted into observations.
Poll `GET /datasets/{id}/status` (optionally long-polling) to follow progress
through the status lifecycle:
`pending → processing|parsing → awaiting_mapping → extracting → ready`
(or `failed`).
license:
name: Proprietary
servers:
- url: https://dssgis.dwh.kz/
description: Production
tags:
- name: Health
description: Liveness and readiness probes
- name: Categories
description: Hierarchical dataset categories
- name: Datasets
description: Geo dataset upload, processing, and retrieval
- name: Dataset Groups
description: Groups of same-named datasets versioned by period
- name: Landscape
description: >-
Landscape Program (ЛП): raster inputs, graph runs, and computed results
paths:
/healthz:
get:
tags: [Health]
summary: Liveness probe
responses:
"200":
description: Process is alive
content:
application/json:
schema:
type: object
properties:
status:
type: string
/readyz:
get:
tags: [Health]
summary: Readiness probe
description: Runs dependency checks (Postgres, S3/MinIO, RabbitMQ).
responses:
"200":
description: All dependencies reachable
content:
application/json:
schema: { $ref: "#/components/schemas/Readiness" }
"503":
description: One or more dependencies unavailable
content:
application/json:
schema: { $ref: "#/components/schemas/Readiness" }
/categories:
get:
tags: [Categories]
summary: List categories
parameters:
- name: parent_id
in: query
required: false
description: Filter to the direct children of this category.
schema:
type: string
format: uuid
responses:
"200":
description: Categories ordered by name
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Category" }
"400": { $ref: "#/components/responses/BadRequest" }
post:
tags: [Categories]
summary: Create a category
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CategoryInput" }
responses:
"201":
description: Created
content:
application/json:
schema: { $ref: "#/components/schemas/Category" }
"400": { $ref: "#/components/responses/BadRequest" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
/categories/{id}:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Categories]
summary: Get a category
responses:
"200":
description: The category
content:
application/json:
schema: { $ref: "#/components/schemas/Category" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [Categories]
summary: Update a category
description: |
Updates name, description, and parent. The new parent must exist and must
not create a cycle (a category cannot be its own ancestor).
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CategoryInput" }
responses:
"200":
description: Updated
content:
application/json:
schema: { $ref: "#/components/schemas/Category" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
delete:
tags: [Categories]
summary: Delete a category
description: Fails with 409 if datasets or child categories still reference it.
responses:
"204": { description: Deleted }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
/dataset-groups:
get:
tags: [Dataset Groups]
summary: List dataset groups (paginated)
parameters:
- name: code
in: query
required: false
description: Filter by exact group code.
schema:
type: string
- $ref: "#/components/parameters/PageParam"
- $ref: "#/components/parameters/PageSizeParam"
responses:
"200":
description: Groups ordered by code
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroupPage" }
"400": { $ref: "#/components/responses/BadRequest" }
post:
tags: [Dataset Groups]
summary: Create a dataset group
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroupInput" }
responses:
"201":
description: Created
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroup" }
"400": { $ref: "#/components/responses/BadRequest" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
/dataset-groups/{id}:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Dataset Groups]
summary: Get a dataset group
responses:
"200":
description: The group
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroup" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [Dataset Groups]
summary: Update a dataset group
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroupInput" }
responses:
"200":
description: Updated
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroup" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
delete:
tags: [Dataset Groups]
summary: Delete a dataset group
description: Fails with 409 if datasets still reference the group.
responses:
"204": { description: Deleted }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
/dataset-groups/{id}/latest:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Dataset Groups]
summary: Get the freshest dataset of a group
description: |
Returns the group's dataset with the greatest `end_date` (datasets
without an `end_date` come last, ties broken by newest `created_at`).
Returns 404 when the group does not exist or has no matching dataset.
parameters:
- name: status
in: query
required: false
description: Only consider datasets in this status.
schema: { $ref: "#/components/schemas/DatasetStatus" }
- name: file_type
in: query
required: false
description: Only consider datasets of this file type.
schema: { $ref: "#/components/schemas/FileType" }
responses:
"200":
description: The freshest dataset in the group
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/dataset-groups/{id}/sld:
parameters:
- $ref: "#/components/parameters/IdParam"
post:
tags: [Dataset Groups]
summary: Upload (or replace) a group's shared SLD style file
description: |
Multipart upload of an SLD (Styled Layer Descriptor) style file shared by
every dataset of the group. A dataset with no SLD of its own falls back
to this one on `GET /datasets/{id}/sld`. Uploading again replaces the
previously stored SLD.
Allowed extensions: `.sld`, `.xml`.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
description: The SLD file.
responses:
"200":
description: SLD stored; the updated group
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetGroup" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"422": { $ref: "#/components/responses/ValidationError" }
get:
tags: [Dataset Groups]
summary: Download a group's shared SLD style file
description: Returns 404 if no SLD has been uploaded for this group yet.
responses:
"200":
description: The SLD file stream
content:
application/xml:
schema:
type: string
format: binary
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/datasets:
get:
tags: [Datasets]
summary: List datasets (paginated summaries)
parameters:
- $ref: "#/components/parameters/PageParam"
- $ref: "#/components/parameters/PageSizeParam"
- name: category_id
in: query
required: false
description: Filter to a category by id.
schema:
type: string
format: uuid
- name: category_code
in: query
required: false
description: >-
Filter to a category by its code (slug). An unknown code yields an
empty page.
schema:
type: string
- name: file_type
in: query
required: false
description: Filter by file type.
schema:
type: string
enum: [vector_with_kato, vector, raster]
- name: automated
in: query
required: false
description: Filter by the automated flag.
schema:
type: boolean
- name: status
in: query
required: false
description: Filter by lifecycle status.
schema:
type: string
enum:
[pending, parsing, processing, awaiting_mapping, extracting, ready, failed]
- name: dg_id
in: query
required: false
description: Filter to a dataset group by id.
schema:
type: string
format: uuid
- name: start_date_from
in: query
required: false
description: Keep datasets whose `start_date` is on or after this date.
schema:
type: string
format: date
- name: start_date_to
in: query
required: false
description: Keep datasets whose `start_date` is on or before this date.
schema:
type: string
format: date
- name: end_date_from
in: query
required: false
description: Keep datasets whose `end_date` is on or after this date.
schema:
type: string
format: date
- name: end_date_to
in: query
required: false
description: Keep datasets whose `end_date` is on or before this date.
schema:
type: string
format: date
responses:
"200":
description: A page of dataset summaries
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetSummaryPage" }
"400": { $ref: "#/components/responses/BadRequest" }
post:
tags: [Datasets]
summary: Upload a dataset
description: |
Multipart upload, validated by `file_type`, extension, and a content
magic-byte check, then processed asynchronously (poll
`GET /datasets/{id}/status`).
Allowed extensions: `vector`/`vector_with_kato` = `.geojson`/`.gpkg`/`.zip`;
`raster` = `.tif`/`.tiff`.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file, file_type, category_id]
properties:
file:
type: string
format: binary
description: The geo file.
file_type:
$ref: "#/components/schemas/FileType"
category_id:
type: string
format: uuid
name:
type: string
description: Display name; defaults to the filename if omitted.
description:
type: string
unit:
type: string
meta:
type: string
description: Arbitrary user JSON (sent as a JSON string).
automated:
type: boolean
dg_id:
type: string
format: uuid
description: Optional dataset group the dataset belongs to.
start_date:
type: string
format: date
description: Start of the period the dataset describes.
end_date:
type: string
format: date
description: >-
End of the period (inclusive); must not be before
`start_date`.
responses:
"201":
description: Dataset created; processing enqueued
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"422": { $ref: "#/components/responses/ValidationError" }
/datasets/{id}:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Datasets]
summary: Get a dataset
description: |
Dataset metadata, plus `bbox` for rasters. The heavy `geometry` and
`properties` fields are not included; fetch geometry via
`GET /datasets/{id}.geojson` and a raster's COG via
`GET /datasets/{id}.cog`.
responses:
"200":
description: The dataset
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [Datasets]
summary: Replace a dataset's editable metadata
description: |
Updates category, name, description, and unit. Every field is
required; the file, file type, and processing/lifecycle state are
never touched here. Sending an empty string for `description` or
`unit` clears that (optional) field. `dg_id`, `start_date` and
`end_date` are optional: omitting one clears that (nullable) column.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetPut" }
responses:
"200":
description: Updated
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"422": { $ref: "#/components/responses/ValidationError" }
patch:
tags: [Datasets]
summary: Partially update a dataset's editable metadata
description: |
Updates only the fields present in the body, from category, name,
description, unit, `dg_id`, `start_date` and `end_date`. The file,
file type, and processing/lifecycle state are never touched here.
Sending an explicit `null` for a nullable field clears it; omitting a
field leaves it unchanged.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetPatch" }
responses:
"200":
description: Updated
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"422": { $ref: "#/components/responses/ValidationError" }
delete:
tags: [Datasets]
summary: Delete a dataset
description: Removes the row and the stored object(s).
responses:
"204": { description: Deleted }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/datasets/{id}.geojson:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Datasets]
summary: Get a dataset as GeoJSON (vector / vector_with_kato)
description: |
Assembles a GeoJSON `FeatureCollection` (RFC 7946) from the dataset.
A plain `vector` dataset has no KATO mapping or observations, so the
result is a single Feature wrapping the dataset's own (dissolved)
geometry. Its extracted attribute table (e.g. a GeoPackage's table data,
stored in `properties`) is exposed as the Feature's top-level properties:
a single row becomes the properties object directly, multiple rows are
kept under a `rows` key. An empty collection is returned when the dataset
has no geometry.
A `vector_with_kato` dataset always ignores any geometry it carries and
joins the `districts` table on KATO code: one Feature is emitted per KATO,
its boundary taken from the matching district and the per-year observation
values nested under a `data` object (keyed by date) alongside `kato` and
`name`. KATO codes with no matching district are skipped.
Only `ready` datasets are served: a dataset still being processed
returns 409. Other file types (e.g. `raster`) return 422.
responses:
"200":
description: The dataset as a GeoJSON FeatureCollection
content:
application/geo+json:
schema: { $ref: "#/components/schemas/GeoJSONFeatureCollection" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
/datasets/{id}.cog:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Datasets]
summary: Get a raster dataset's Cloud-Optimized GeoTIFF
description: |
Streams the raster dataset's Cloud-Optimized GeoTIFF (`image/tiff`),
produced when a `raster` upload is converted.
Only `raster` datasets are served: other file types return 422, and a
raster whose COG has not been produced yet returns 409.
responses:
"200":
description: The Cloud-Optimized GeoTIFF
content:
image/tiff:
schema: { type: string, format: binary }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
/datasets/{id}/status:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Datasets]
summary: Get processing status (supports long polling)
description: |
Returns the current status immediately. If `current` is supplied, the
request is held until the status differs from it, or until `wait` seconds
elapse (then the unchanged status is returned).
parameters:
- name: current
in: query
required: false
description: The client's last-known status; enables long polling.
schema: { $ref: "#/components/schemas/DatasetStatus" }
- name: wait
in: query
required: false
description: Max seconds to hold the request (default 25, max 60).
schema:
type: integer
minimum: 0
maximum: 60
default: 25
responses:
"200":
description: The dataset's status
content:
application/json:
schema: { $ref: "#/components/schemas/DatasetStatusInfo" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/datasets/{id}/download:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Datasets]
summary: Download the original uploaded file
responses:
"200":
description: The file stream
content:
application/octet-stream:
schema:
type: string
format: binary
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/datasets/{id}/sld:
parameters:
- $ref: "#/components/parameters/IdParam"
post:
tags: [Datasets]
summary: Upload (or replace) a dataset's SLD style file
description: |
Multipart upload of an SLD (Styled Layer Descriptor) style file,
mirroring `POST /datasets`. Applies to every `file_type`. Uploading
again replaces the previously stored SLD.
Allowed extensions: `.sld`, `.xml`.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
description: The SLD file.
responses:
"200":
description: SLD stored; the updated dataset
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"422": { $ref: "#/components/responses/ValidationError" }
get:
tags: [Datasets]
summary: Download a dataset's SLD style file
description: |
Returns the dataset's own SLD. When the dataset has none but belongs to a
dataset group that carries a shared SLD, the group's SLD is returned
instead. Returns 404 when neither exists.
responses:
"200":
description: The SLD file stream
content:
application/xml:
schema:
type: string
format: binary
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/datasets/{id}/mapping:
parameters:
- $ref: "#/components/parameters/IdParam"
post:
tags: [Datasets]
summary: Set the KATO column and year mapping (vector_with_kato)
description: |
Valid only for `vector_with_kato` datasets in `awaiting_mapping` (or
`ready`, to re-map). The KATO column and every mapped column must be among
the dataset's detected `attribute_columns`. Moves the dataset to
`extracting`; observations are produced asynchronously.
Each year column maps to either a single point in time (`start_date`
only, the original behavior) or a period (`start_date` and `end_date`,
inclusive). `end_date`, when present, must not be before `start_date`.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/MappingInput" }
responses:
"200":
description: Mapping saved; extraction enqueued
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"422": { $ref: "#/components/responses/ValidationError" }
/datasets/{id}/observations:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Datasets]
summary: List a dataset's observations (paginated)
description: Long-format values unpivoted from a vector_with_kato dataset.
parameters:
- name: kato_code
in: query
required: false
description: Filter to a single KATO code.
schema:
type: string
- $ref: "#/components/parameters/PageParam"
- $ref: "#/components/parameters/PageSizeParam"
responses:
"200":
description: A page of observations
content:
application/json:
schema: { $ref: "#/components/schemas/ObservationPage" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/landscape/slots:
get:
tags: [Landscape]
summary: List the Landscape Program input slots
description: |
Every leaf input group of the computation graph, in graph order, with
the dataset currently filling it (any status) or `null` when nothing
has been uploaded yet.
Yields 404 when a slot's dataset group is missing — that means
migration `00011_landscape.sql`, which seeds the groups, has not been
applied to this database.
responses:
"200":
description: The input slots
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/LandscapeSlot" }
"404": { $ref: "#/components/responses/NotFound" }
/landscape/slots/{code}:
parameters:
- name: code
in: path
required: true
description: Slot (dataset group) code, e.g. `lp-z-biodiversity`.
schema:
type: string
post:
tags: [Landscape]
summary: Upload a raster into an input slot
description: |
Multipart upload of a GeoTIFF (`.tif`/`.tiff`, TIFF magic bytes checked)
into the slot's dataset group under the `landscape` category. It goes
through the regular raster upload path, so the COG conversion is
enqueued as usual. An unknown slot code yields 404.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
description: The GeoTIFF to store in the slot.
name:
type: string
description: Dataset name; defaults to the uploaded file name.
start_date:
type: string
format: date
description: Start of the period the raster describes.
end_date:
type: string
format: date
description: >-
End of the period (inclusive); drives the "freshest in
group" rule used to resolve run inputs.
responses:
"201":
description: The created dataset
content:
application/json:
schema: { $ref: "#/components/schemas/Dataset" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"422": { $ref: "#/components/responses/ValidationError" }
/landscape/runs:
get:
tags: [Landscape]
summary: List Landscape Program runs (paginated, newest first)
parameters:
- $ref: "#/components/parameters/PageParam"
- $ref: "#/components/parameters/PageSizeParam"
responses:
"200":
description: A page of runs
content:
application/json:
schema: { $ref: "#/components/schemas/LandscapeRunPage" }
"400": { $ref: "#/components/responses/BadRequest" }
post:
tags: [Landscape]
summary: Queue a Landscape Program run
description: |
Validates the params and overrides, persists the run with one pending
node per graph node, and publishes the worker job. Returns 202: the
graph is executed asynchronously, poll `GET /landscape/runs/{id}`.
Every field has a default, so the body is optional: sending no body at
all (or `{}`) starts a default incremental run.
requestBody:
required: false
content:
application/json:
schema: { $ref: "#/components/schemas/LandscapeRunInput" }
responses:
"202":
description: Run queued
content:
application/json:
schema: { $ref: "#/components/schemas/LandscapeRun" }
"400": { $ref: "#/components/responses/BadRequest" }
"422": { $ref: "#/components/responses/ValidationError" }
/landscape/runs/{id}:
parameters:
- $ref: "#/components/parameters/IdParam"
get:
tags: [Landscape]
summary: Get a Landscape Program run with its nodes
responses:
"200":
description: The run
content:
application/json:
schema: { $ref: "#/components/schemas/LandscapeRun" }
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
/landscape/results:
get:
tags: [Landscape]
summary: List the Landscape Program result layers
description: |
One entry per output group of the graph with its freshest `ready`
raster, the COG URL to render it, and the group's SLD URL when one has
been uploaded. `latest` is `null` while nothing has been computed.
Yields 404 when an output group is missing — that means migration
`00011_landscape.sql`, which seeds the groups, has not been applied to
this database.
responses:
"200":
description: The result layers
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/LandscapeResult" }
"404": { $ref: "#/components/responses/NotFound" }
components:
parameters:
IdParam:
name: id
in: path
required: true
schema:
type: string
format: uuid
PageParam:
name: page
in: query
required: false
description: 1-based page number.
schema:
type: integer
minimum: 1
default: 1
PageSizeParam:
name: page_size
in: query
required: false
description: Items per page (default 20, max 100).
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
BadRequest:
description: Malformed request (e.g. invalid UUID or query parameter)
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
NotFound:
description: Resource not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
Conflict:
description: Operation conflicts with existing data (e.g. foreign key)
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
ValidationError:
description: Input failed validation
content:
application/json:
schema:
oneOf:
- { $ref: "#/components/schemas/Error" }
- { $ref: "#/components/schemas/ValidationErrors" }
schemas:
Error:
type: object
required: [error]
properties:
error:
type: string
ValidationErrors:
type: object
description: Per-field validation messages.
properties:
errors:
type: object
additionalProperties:
type: string
Readiness:
type: object
properties:
ready:
type: boolean
components:
type: object
additionalProperties:
type: string
FileType:
type: string
enum: [vector_with_kato, vector, raster]
DatasetStatus:
type: string
description: Dataset processing lifecycle status.
enum: [pending, processing, parsing, awaiting_mapping, extracting, ready, failed]
DatasetGroup:
type: object
description: |
A group of same-named datasets delivered for different periods. The
freshest member is the one with the greatest `end_date`.
required: [id, code, name, description, created_at, updated_at]
properties:
id:
type: string
format: uuid
code:
type: string
description: Slug (lowercase latin letters, digits, and dashes).
name:
type: string
description:
type: string
sld_storage_key:
type: [string, "null"]
description: Object-store key of the group's shared SLD, if uploaded.
sld_filename:
type: [string, "null"]
sld_content_type:
type: [string, "null"]
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
DatasetGroupInput:
type: object
required: [code, name]
properties:
code:
type: string
maxLength: 255
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
description: Slug (lowercase latin letters, digits, and dashes).
name:
type: string
maxLength: 255
description:
type: string
maxLength: 2000
DatasetGroupPage:
type: object
required: [data, page, page_size, total, total_pages]
properties:
data:
type: array
items:
$ref: "#/components/schemas/DatasetGroup"
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
Category:
type: object
required: [id, code, name, description, created_at, updated_at]
properties:
id:
type: string
format: uuid
parent_id:
type: [string, "null"]
format: uuid
code:
type: string
description: Slug (lowercase latin letters, digits, and dashes).
name:
type: string
description:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
CategoryInput:
type: object
required: [code, name]
properties:
parent_id:
type: [string, "null"]
format: uuid
code:
type: string
maxLength: 255
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
description: Slug (lowercase latin letters, digits, and dashes).
name:
type: string
maxLength: 255
description:
type: string
maxLength: 2000
DatasetPut:
type: object
required: [category_id, name, description, unit]
properties:
category_id:
type: string
format: uuid
name:
type: string
maxLength: 255
description:
type: string
description: An empty string clears the field.
unit:
type: string
description: An empty string clears the field.
dg_id:
type: [string, "null"]
format: uuid
description: Dataset group; omitted or null clears the field.
start_date:
type: [string, "null"]
format: date
description: Start of the dataset's period; omitted or null clears it.
end_date:
type: [string, "null"]
format: date
description: >-
End of the dataset's period (inclusive); omitted or null clears it.
Must not be before `start_date`.
DatasetPatch:
type: object
description: At least one field is required. Omitted fields are left unchanged.
minProperties: 1
properties:
category_id:
type: string
format: uuid
name:
type: string
maxLength: 255
description:
type: [string, "null"]
description: null clears the field.
unit:
type: [string, "null"]
description: null clears the field.
dg_id:
type: [string, "null"]
format: uuid
description: null clears the field.
start_date:
type: [string, "null"]
format: date
description: null clears the field.
end_date:
type: [string, "null"]
format: date
description: >-
null clears the field. Must not be before the resulting
`start_date`.
AttributeColumn:
type: object
required: [name]
properties:
name:
type: string
samples:
type: array
items:
type: string
YearColumn:
type: object
required: [column, start_date]
properties:
column:
type: string
start_date:
type: string
format: date
description: >-
Point in time, or the start of the period when `end_date` is also
set.
end_date:
type: [string, "null"]
format: date
description: >-
Optional period end (inclusive), must not be before `start_date`.
Omitted or null means a single point in time (the original,
pre-period behavior).
GeoJSONGeometry:
type: [object, "null"]
description: A GeoJSON geometry object (e.g. Point, Polygon).
properties:
type:
type: string
coordinates: true
GeoJSONFeature:
type: object
description: A GeoJSON Feature (RFC 7946).
required: [type, geometry, properties]
properties:
type:
type: string
enum: [Feature]
geometry:
$ref: "#/components/schemas/GeoJSONGeometry"
properties:
type: [object, "null"]
description: |
Arbitrary key/value map. For per-KATO features (no dataset geometry)
this holds `kato`, `name`, and one entry per mapped year keyed by
date (YYYY-MM-DD). For the single-feature case (dataset has its own
geometry) it holds only the observations, keyed by KATO code, each
mapping to its date->value pairs.
For a `vector_with_kato` dataset mapped with one or more period
year columns (`end_date` set), a sibling `periods` object is also
present, mapping each period's start date (the same key used in
`data`) to its end date. `periods` is omitted entirely when the
dataset has no period mappings.
additionalProperties: true
GeoJSONFeatureCollection:
type: object
description: A GeoJSON FeatureCollection (RFC 7946).
required: [type, features]
properties:
type:
type: string
enum: [FeatureCollection]
features:
type: array
items:
$ref: "#/components/schemas/GeoJSONFeature"
DatasetSummary:
type: object
required: [id, category_id, name, file_type, size_bytes, status, created_at, updated_at]
properties:
id:
type: string
format: uuid
category_id:
type: string
format: uuid
dg_id:
type: [string, "null"]
format: uuid
description: The dataset group this dataset belongs to, if any.
start_date:
type: [string, "null"]
format: date
description: Start of the period the dataset describes.
end_date:
type: [string, "null"]
format: date
description: >-
End of the period (inclusive). Drives the "freshest in group" rule.
name:
type: string
description:
type: [string, "null"]
unit:
type: [string, "null"]
file_type:
$ref: "#/components/schemas/FileType"
size_bytes:
type: integer
format: int64
status:
$ref: "#/components/schemas/DatasetStatus"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Dataset:
type: object
required:
[id, category_id, name, filename, storage_key, file_type,
size_bytes, content_type, automated, status, created_at, updated_at]
properties:
id:
type: string
format: uuid
category_id:
type: string
format: uuid
dg_id:
type: [string, "null"]
format: uuid
description: The dataset group this dataset belongs to, if any.
start_date:
type: [string, "null"]
format: date
description: Start of the period the dataset describes.
end_date:
type: [string, "null"]
format: date
description: >-
End of the period (inclusive). Drives the "freshest in group" rule.
name:
type: string
description:
type: [string, "null"]
unit:
type: [string, "null"]
filename:
type: string
storage_key:
type: string
cog_storage_key:
type: [string, "null"]
description: Set for rasters once the Cloud-Optimized GeoTIFF is produced.
sld_storage_key:
type: [string, "null"]
description: Set once an SLD style file has been uploaded (any file_type).
sld_filename:
type: [string, "null"]
sld_content_type:
type: [string, "null"]
file_type:
$ref: "#/components/schemas/FileType"
size_bytes:
type: integer
format: int64
content_type:
type: string
meta:
type: [object, "null"]
description: Arbitrary user-supplied JSON.
automated:
type: boolean
status:
$ref: "#/components/schemas/DatasetStatus"
attribute_columns:
type: [array, "null"]
description: Detected columns (vector_with_kato), for mapping.
items:
$ref: "#/components/schemas/AttributeColumn"
kato_column:
type: [string, "null"]
year_columns:
type: [array, "null"]
items:
$ref: "#/components/schemas/YearColumn"
parse_error:
type: [string, "null"]
description: Failure reason when status is `failed`.
bbox:
type: array
description: "[minX, minY, maxX, maxY]; present only for rasters."
items:
type: number
minItems: 4
maxItems: 4
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
MappingInput:
type: object
required: [kato_column, year_columns]
properties:
kato_column:
type: string
examples: ["като"]
year_columns:
type: array
minItems: 1
items:
$ref: "#/components/schemas/YearColumn"
examples:
- kato_column: "като"
year_columns:
- { column: F_2023, start_date: "2023-01-01" }
- { column: D_2025, start_date: "2025-01-01" }
Observation:
type: object
required: [id, dataset_id, kato_code, start_date]
properties:
id:
type: string
format: uuid
dataset_id:
type: string
format: uuid
kato_code:
type: string
start_date:
type: string
format: date
end_date:
type: [string, "null"]
format: date
description: >-
Period end (inclusive), carried over from the year mapping's
`end_date`. Null for a point-in-time observation.
value:
type: [number, "null"]
description: Numeric cell value, or null when non-numeric/empty.
value_text:
type: [string, "null"]
description: Non-numeric cell value, or null.
DatasetStatusInfo:
type: object
required: [id, status]
properties:
id:
type: string
format: uuid
status:
$ref: "#/components/schemas/DatasetStatus"
parse_error:
type: [string, "null"]
DatasetSummaryPage:
type: object
required: [data, page, page_size, total, total_pages]
properties:
data:
type: array
items:
$ref: "#/components/schemas/DatasetSummary"
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
ObservationPage:
type: object
required: [data, page, page_size, total, total_pages]
properties:
data:
type: array
items:
$ref: "#/components/schemas/Observation"
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
LandscapeSlot:
type: object
description: A leaf input group of the Landscape Program graph.
required: [code, title, group_id, latest]
properties:
code:
type: string
description: Dataset group code, e.g. `lp-z-biodiversity`.
title:
type: string
group_id:
type: string
format: uuid
latest:
oneOf:
- $ref: "#/components/schemas/DatasetSummary"
- type: "null"
description: The freshest dataset of the slot's group, any status.
LandscapeParams:
type: object
description: |
Run-time knobs of the graph. Every field is optional; omitted fields
take the defaults shown below.
properties:
window_size:
type: integer
minimum: 256
maximum: 8192
default: 2048
description: Side of the square window the executor tiles rasters into.
tie_priority:
type: array
description: >-
Sector preference when several sectors tie in functional zoning; a
permutation of 1..5 (1 irrigation, 2 snow, 3 crop, 4 pasture,
5 tourism).
items:
type: integer
minimum: 1
maximum: 5
minItems: 5
maxItems: 5
default: [1, 2, 3, 4, 5]
v1_min_s_e12:
type: integer
minimum: 1
maximum: 5
default: 3
description: V1 minimum suitability where E <= 2.
v1_min_s_e3:
type: integer
minimum: 1
maximum: 5
default: 4
description: V1 minimum suitability where E == 3.
v2_min_s_e3:
type: integer
minimum: 1
maximum: 5
default: 3
description: V2 minimum suitability where E == 3.
v2_min_s_e4:
type: integer
minimum: 1
maximum: 5
default: 5
description: V2 minimum suitability where E == 4.
LandscapeRunInput:
type: object
description: Options of a new Landscape Program run. All fields optional.
properties:
from_stage:
type: [integer, "null"]
minimum: 0
maximum: 3
description: >-
Force recomputation from this stage on. Omitted (or null) reuses
every node whose inputs and params are unchanged; `0` recomputes
everything.
overrides:
type: object
description: >-
Pins a specific dataset for a group code instead of resolving the
freshest ready raster. The dataset must exist, be a raster, and be
`ready`.
additionalProperties:
type: string
format: uuid
params:
$ref: "#/components/schemas/LandscapeParams"
examples:
- from_stage: 0
overrides: { "lp-z-lcr": "3f7c1f5e-9a1a-4a54-8b2e-2d0f5c9d1a11" }
params: { window_size: 1024 }
LandscapeDatasetRef:
type: object
description: A dataset bound to one group slot of a node.
required: [group, dataset_id]
properties:
group:
type: string
description: Dataset group code.
dataset_id:
type: string
format: uuid
LandscapeRunNode:
type: object
description: Execution state of one graph node within a run.
required: [id, run_id, node_code, stage, status, fingerprint]
properties:
id:
type: string
format: uuid
run_id:
type: string
format: uuid
node_code:
type: string
description: "Node code `XY`: X = stage, Y = function within the stage."
stage:
type: integer
status:
type: string
enum: [pending, running, skipped, succeeded, failed]
description: >-
`skipped` means a previous run's identical result was reused.
fingerprint:
type: string
description: >-
sha256 over the node code, its resolved inputs and the
node-relevant params; equal fingerprints allow reuse.
inputs:
type: [array, "null"]
items:
$ref: "#/components/schemas/LandscapeDatasetRef"
outputs:
type: [array, "null"]
items:
$ref: "#/components/schemas/LandscapeDatasetRef"
error:
type: [string, "null"]
started_at:
type: [string, "null"]
format: date-time
finished_at:
type: [string, "null"]
format: date-time
LandscapeRun:
type: object
description: One execution of the Landscape Program graph.
required: [id, status, params, overrides, created_at]
properties:
id:
type: string
format: uuid
status:
type: string
enum: [queued, running, succeeded, failed]
from_stage:
type: [integer, "null"]
params:
$ref: "#/components/schemas/LandscapeParams"
overrides:
type: object
additionalProperties:
type: string
format: uuid
error:
type: [string, "null"]
description: Failure reason when status is `failed`.
created_at:
type: string
format: date-time
started_at:
type: [string, "null"]
format: date-time
finished_at:
type: [string, "null"]
format: date-time
nodes:
type: [array, "null"]
items:
$ref: "#/components/schemas/LandscapeRunNode"
LandscapeRunPage:
type: object
required: [data, page, page_size, total, total_pages]
properties:
data:
type: array
items:
$ref: "#/components/schemas/LandscapeRun"
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
LandscapeResult:
type: object
description: One computed output group of the graph and its latest layer.
required: [group, title, node, group_id, latest, cog_url, sld_url]
properties:
group:
type: string
description: Dataset group code, e.g. `lp-v1-conservative`.
title:
type: string
node:
type: string
description: Code of the graph node producing this group.
group_id:
type: string
format: uuid
latest:
oneOf:
- $ref: "#/components/schemas/DatasetSummary"
- type: "null"
description: The freshest `ready` raster of the group, if any.
cog_url:
type: [string, "null"]
description: "`/datasets/{id}.cog` for `latest`, or null."
sld_url:
type: [string, "null"]
description: >-
`/dataset-groups/{group_id}/sld` when the group has an SLD, else
null.