feat(landscape): HTTP API, worker consumer, wiring, docs
Adds the /landscape routes (slots, slot upload, runs, run detail, results), mounts them in the router, wires the landscape repository, job publisher, godal raster IO and Compute into the composition root, and registers the gis-landscape-runner consumer in the worker. Also fixes the Dockerfile so the image actually runs: godal's cgo needs g++, and the runtime base must match the builder's Alpine release or libgdal's soname does not resolve. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
374
api/openapi.yaml
374
api/openapi.yaml
@@ -35,6 +35,9 @@ tags:
|
||||
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:
|
||||
@@ -750,6 +753,141 @@ paths:
|
||||
"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.
|
||||
responses:
|
||||
"200":
|
||||
description: The input slots
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/LandscapeSlot" }
|
||||
|
||||
/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}`.
|
||||
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.
|
||||
responses:
|
||||
"200":
|
||||
description: The result layers
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/LandscapeResult" }
|
||||
|
||||
components:
|
||||
parameters:
|
||||
IdParam:
|
||||
@@ -1309,3 +1447,239 @@ components:
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user