691 lines
22 KiB
YAML
691 lines
22 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: stratoflights-predictor API
|
|
version: "1.0.0"
|
|
description: |
|
|
Balloon trajectory prediction and wind-dataset management.
|
|
|
|
Three prediction surfaces are exposed:
|
|
|
|
* **`GET /api/v1/prediction`** — Tawhiri-compatible, drop-in for the
|
|
Cambridge University Spaceflight predictor.
|
|
* **`POST /api/v2/prediction`** — profile-driven synchronous prediction
|
|
(arbitrary chains of propagators with constraints).
|
|
* **`POST /api/v1/predictions`** — the same profile API run asynchronously
|
|
via a worker pool, polled by job id.
|
|
|
|
Dataset management (download, list, delete, job status) lives under
|
|
`/api/v1/admin/`, and wind-field visualization data (leaflet-velocity /
|
|
wind-layer format) under `/api/v1/wind/`.
|
|
|
|
servers:
|
|
- url: /
|
|
description: This server.
|
|
|
|
tags:
|
|
- name: Prediction
|
|
- name: Datasets
|
|
- name: Wind
|
|
- name: Health
|
|
|
|
paths:
|
|
/ready:
|
|
get:
|
|
tags: [Health]
|
|
summary: Readiness check
|
|
operationId: readinessCheck
|
|
responses:
|
|
"200":
|
|
description: Readiness status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ReadinessResponse"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/prediction:
|
|
get:
|
|
tags: [Prediction]
|
|
summary: Tawhiri-compatible prediction
|
|
operationId: performPrediction
|
|
parameters:
|
|
- { in: query, name: launch_latitude, required: true, schema: { type: number } }
|
|
- { in: query, name: launch_longitude, required: true, schema: { type: number } }
|
|
- { in: query, name: launch_datetime, required: true, schema: { type: string, format: date-time } }
|
|
- { in: query, name: launch_altitude, schema: { type: number } }
|
|
- { in: query, name: profile, schema: { type: string, enum: [standard_profile, float_profile], default: standard_profile } }
|
|
- { in: query, name: ascent_rate, schema: { type: number } }
|
|
- { in: query, name: burst_altitude, schema: { type: number } }
|
|
- { in: query, name: descent_rate, schema: { type: number } }
|
|
- { in: query, name: float_altitude, schema: { type: number } }
|
|
- { in: query, name: stop_datetime, schema: { type: string, format: date-time } }
|
|
- { in: query, name: dataset, schema: { type: string, format: date-time } }
|
|
responses:
|
|
"200":
|
|
description: Prediction response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PredictionResponse"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v2/prediction:
|
|
post:
|
|
tags: [Prediction]
|
|
summary: Profile-driven prediction (synchronous)
|
|
operationId: performPredictionV2
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PredictionV2Request"
|
|
responses:
|
|
"200":
|
|
description: Prediction result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PredictionV2Response"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/predictions:
|
|
post:
|
|
tags: [Prediction]
|
|
summary: Enqueue an asynchronous prediction
|
|
operationId: createPredictionJob
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PredictionV2Request"
|
|
responses:
|
|
"202":
|
|
description: Job accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PredictionJob"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/predictions/{id}:
|
|
get:
|
|
tags: [Prediction]
|
|
summary: Poll an asynchronous prediction job
|
|
operationId: getPredictionJob
|
|
parameters:
|
|
- { in: path, name: id, required: true, schema: { type: string } }
|
|
responses:
|
|
"200":
|
|
description: Job status (with result when complete)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PredictionJob"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
delete:
|
|
tags: [Prediction]
|
|
summary: Cancel a queued prediction job
|
|
operationId: cancelPredictionJob
|
|
parameters:
|
|
- { in: path, name: id, required: true, schema: { type: string } }
|
|
responses:
|
|
"204":
|
|
description: Cancelled
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/admin/datasets:
|
|
get:
|
|
tags: [Datasets]
|
|
summary: List stored datasets
|
|
operationId: listDatasets
|
|
responses:
|
|
"200":
|
|
description: Stored datasets
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DatasetList"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
post:
|
|
tags: [Datasets]
|
|
summary: Trigger a dataset download
|
|
operationId: triggerDatasetDownload
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DownloadRequest"
|
|
responses:
|
|
"202":
|
|
description: Download accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DownloadAccepted"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/admin/datasets/{name}:
|
|
delete:
|
|
tags: [Datasets]
|
|
summary: Delete a stored dataset by filename
|
|
operationId: deleteDataset
|
|
parameters:
|
|
- { in: path, name: name, required: true, schema: { type: string } }
|
|
responses:
|
|
"204":
|
|
description: Deleted
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/admin/jobs:
|
|
get:
|
|
tags: [Datasets]
|
|
summary: List dataset download jobs
|
|
operationId: listDatasetJobs
|
|
responses:
|
|
"200":
|
|
description: Download jobs
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DownloadJob"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/admin/jobs/{id}:
|
|
get:
|
|
tags: [Datasets]
|
|
summary: Get a dataset download job
|
|
operationId: getDatasetJob
|
|
parameters:
|
|
- { in: path, name: id, required: true, schema: { type: string } }
|
|
responses:
|
|
"200":
|
|
description: Download job
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DownloadJob"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
delete:
|
|
tags: [Datasets]
|
|
summary: Cancel a running download job
|
|
operationId: cancelDatasetJob
|
|
parameters:
|
|
- { in: path, name: id, required: true, schema: { type: string } }
|
|
responses:
|
|
"204":
|
|
description: Cancelled
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/admin/status:
|
|
get:
|
|
tags: [Datasets]
|
|
summary: Service status summary
|
|
operationId: getServiceStatus
|
|
responses:
|
|
"200":
|
|
description: Status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/wind/meta:
|
|
get:
|
|
tags: [Wind]
|
|
summary: Wind-field visualization metadata
|
|
operationId: getWindMeta
|
|
responses:
|
|
"200":
|
|
description: Metadata describing the active dataset for visualization
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/WindMeta"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
/api/v1/wind/field:
|
|
get:
|
|
tags: [Wind]
|
|
summary: Wind-field velocity grid (leaflet-velocity / wind-layer format)
|
|
operationId: getWindField
|
|
parameters:
|
|
- { in: query, name: time, schema: { type: string, format: date-time } }
|
|
- { in: query, name: altitude, schema: { type: number } }
|
|
- { in: query, name: min_lat, schema: { type: number } }
|
|
- { in: query, name: max_lat, schema: { type: number } }
|
|
- { in: query, name: min_lng, schema: { type: number } }
|
|
- { in: query, name: max_lng, schema: { type: number } }
|
|
- { in: query, name: step, schema: { type: number } }
|
|
responses:
|
|
"200":
|
|
description: Two-component (U, V) velocity grid
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/WindComponent"
|
|
default:
|
|
$ref: "#/components/responses/DefaultError"
|
|
|
|
components:
|
|
responses:
|
|
DefaultError:
|
|
description: Error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
schemas:
|
|
Error:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error:
|
|
type: object
|
|
required: [type, description]
|
|
properties:
|
|
type: { type: string }
|
|
description: { type: string }
|
|
|
|
ReadinessResponse:
|
|
type: object
|
|
required: [status]
|
|
properties:
|
|
status: { type: string, enum: [ok, not_ready, error] }
|
|
dataset_time: { type: string, format: date-time }
|
|
error_message: { type: string }
|
|
|
|
# --- Tawhiri v1 ---------------------------------------------------------
|
|
PredictionResponse:
|
|
type: object
|
|
required: [prediction, metadata]
|
|
properties:
|
|
request:
|
|
type: object
|
|
properties:
|
|
dataset: { type: string }
|
|
launch_latitude: { type: number }
|
|
launch_longitude: { type: number }
|
|
launch_datetime: { type: string }
|
|
launch_altitude: { type: number }
|
|
profile: { type: string }
|
|
ascent_rate: { type: number }
|
|
burst_altitude: { type: number }
|
|
descent_rate: { type: number }
|
|
prediction:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [stage, trajectory]
|
|
properties:
|
|
stage: { type: string, enum: [ascent, descent, float] }
|
|
trajectory:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/TawhiriPoint"
|
|
metadata:
|
|
type: object
|
|
required: [start_datetime, complete_datetime]
|
|
properties:
|
|
start_datetime: { type: string, format: date-time }
|
|
complete_datetime: { type: string, format: date-time }
|
|
warnings:
|
|
type: object
|
|
additionalProperties: true
|
|
|
|
TawhiriPoint:
|
|
type: object
|
|
required: [datetime, latitude, longitude, altitude]
|
|
properties:
|
|
datetime: { type: string, format: date-time }
|
|
latitude: { type: number }
|
|
longitude: { type: number }
|
|
altitude: { type: number }
|
|
|
|
# --- v2 profile-driven --------------------------------------------------
|
|
PredictionV2Request:
|
|
type: object
|
|
required: [launch, profile]
|
|
description: |
|
|
A profile-driven prediction. `profile` is an ordered chain of
|
|
propagators; each integrates from where the previous ended. A stage's
|
|
`constraints` decide when it ends and what happens next: stop the
|
|
profile, hand off to `fallback_index`, or clip to the boundary.
|
|
properties:
|
|
launch: { $ref: "#/components/schemas/Launch" }
|
|
direction:
|
|
type: string
|
|
enum: [forward, reverse]
|
|
default: forward
|
|
description: forward integrates launch→landing; reverse integrates backward in time.
|
|
profile:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/StageSpec" }
|
|
globals:
|
|
type: array
|
|
description: constraints evaluated on every stage in addition to its own.
|
|
items: { $ref: "#/components/schemas/ConstraintSpec" }
|
|
options: { $ref: "#/components/schemas/Options" }
|
|
example:
|
|
launch: { time: "2026-03-28T12:00:00Z", latitude: 52.2, longitude: 0.1, altitude: 0 }
|
|
profile:
|
|
- name: ascent
|
|
model: { type: constant_rate, rate: 5, include_wind: true }
|
|
constraints: [{ type: altitude, op: ">=", limit: 30000 }]
|
|
- name: descent
|
|
model: { type: parachute_descent, sea_level_rate: 5, include_wind: true }
|
|
constraints: [{ type: terrain_contact }]
|
|
|
|
Launch:
|
|
type: object
|
|
required: [time, latitude, longitude]
|
|
properties:
|
|
time: { type: string, format: date-time }
|
|
latitude: { type: number }
|
|
longitude: { type: number }
|
|
altitude: { type: number }
|
|
|
|
StageSpec:
|
|
type: object
|
|
required: [name, model]
|
|
properties:
|
|
name: { type: string }
|
|
model: { $ref: "#/components/schemas/ModelSpec" }
|
|
constraints:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/ConstraintSpec" }
|
|
fallback_index: { type: integer }
|
|
|
|
ModelSpec:
|
|
type: object
|
|
required: [type]
|
|
properties:
|
|
type: { type: string, enum: [constant_rate, parachute_descent, piecewise, wind] }
|
|
rate: { type: number }
|
|
sea_level_rate: { type: number }
|
|
include_wind: { type: boolean }
|
|
segments:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/PiecewiseSegment" }
|
|
|
|
PiecewiseSegment:
|
|
type: object
|
|
required: [until, rate]
|
|
properties:
|
|
until: { type: number }
|
|
rate: { type: number }
|
|
reference: { type: string, enum: [absolute, profile_start, propagator_start], default: absolute }
|
|
|
|
ConstraintSpec:
|
|
type: object
|
|
required: [type]
|
|
properties:
|
|
type: { type: string, enum: [altitude, time, terrain_contact, polygon] }
|
|
op: { type: string, enum: ["<", "<=", ">", ">=", "=="] }
|
|
limit: { type: number }
|
|
action: { type: string, enum: [stop, fallback, clip], default: stop }
|
|
mode: { type: string, enum: [inside, outside] }
|
|
label: { type: string }
|
|
vertices:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/PolygonVertex" }
|
|
|
|
PolygonVertex:
|
|
type: object
|
|
required: [lat, lng]
|
|
properties:
|
|
lat: { type: number }
|
|
lng: { type: number }
|
|
|
|
Options:
|
|
type: object
|
|
properties:
|
|
step_seconds: { type: number }
|
|
tolerance: { type: number }
|
|
|
|
PredictionV2Response:
|
|
type: object
|
|
required: [stages, dataset, started_at, completed_at]
|
|
properties:
|
|
stages:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/StageResult" }
|
|
events:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/EventSummary" }
|
|
dataset: { $ref: "#/components/schemas/DatasetInfo" }
|
|
started_at: { type: string, format: date-time }
|
|
completed_at: { type: string, format: date-time }
|
|
|
|
StageResult:
|
|
type: object
|
|
required: [name, outcome, trajectory]
|
|
properties:
|
|
name: { type: string }
|
|
outcome: { type: string, enum: [stopped, fallback, continued] }
|
|
constraint: { type: string }
|
|
termination: { $ref: "#/components/schemas/TerminationInfo" }
|
|
events:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/EventSummary" }
|
|
trajectory:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/TrajectoryPoint" }
|
|
|
|
TrajectoryPoint:
|
|
type: object
|
|
required: [time, latitude, longitude, altitude]
|
|
properties:
|
|
time: { type: string, format: date-time }
|
|
latitude: { type: number }
|
|
longitude: { type: number }
|
|
altitude: { type: number }
|
|
|
|
GeoState:
|
|
type: object
|
|
required: [lat, lng, altitude]
|
|
properties:
|
|
lat: { type: number }
|
|
lng: { type: number }
|
|
altitude: { type: number }
|
|
|
|
TerminationInfo:
|
|
type: object
|
|
required: [violation_time, violation_state, refined_time, refined_state]
|
|
properties:
|
|
violation_time: { type: string, format: date-time }
|
|
violation_state: { $ref: "#/components/schemas/GeoState" }
|
|
refined_time: { type: string, format: date-time }
|
|
refined_state: { $ref: "#/components/schemas/GeoState" }
|
|
|
|
EventSummary:
|
|
type: object
|
|
required: [type, count]
|
|
properties:
|
|
type: { type: string }
|
|
count: { type: integer, format: int64 }
|
|
first_time: { type: number }
|
|
last_time: { type: number }
|
|
first_state: { $ref: "#/components/schemas/GeoState" }
|
|
last_state: { $ref: "#/components/schemas/GeoState" }
|
|
message: { type: string }
|
|
|
|
DatasetInfo:
|
|
type: object
|
|
required: [source, epoch]
|
|
properties:
|
|
source: { type: string }
|
|
epoch: { type: string, format: date-time }
|
|
|
|
# --- async jobs ---------------------------------------------------------
|
|
PredictionJob:
|
|
type: object
|
|
required: [id, status, created_at]
|
|
properties:
|
|
id: { type: string }
|
|
status: { type: string, enum: [pending, running, complete, failed, cancelled] }
|
|
created_at: { type: string, format: date-time }
|
|
started_at: { type: string, format: date-time }
|
|
completed_at: { type: string, format: date-time }
|
|
error: { type: string }
|
|
result: { $ref: "#/components/schemas/PredictionV2Response" }
|
|
|
|
# --- dataset admin ------------------------------------------------------
|
|
Region:
|
|
type: object
|
|
required: [min_lat, max_lat, min_lng, max_lng]
|
|
properties:
|
|
min_lat: { type: number }
|
|
max_lat: { type: number }
|
|
min_lng: { type: number }
|
|
max_lng: { type: number }
|
|
|
|
HourRange:
|
|
type: object
|
|
required: [min_hour, max_hour]
|
|
properties:
|
|
min_hour: { type: integer }
|
|
max_hour: { type: integer }
|
|
|
|
SubsetSpec:
|
|
type: object
|
|
properties:
|
|
region: { $ref: "#/components/schemas/Region" }
|
|
hour_range: { $ref: "#/components/schemas/HourRange" }
|
|
members:
|
|
type: array
|
|
items: { type: integer }
|
|
|
|
Coverage:
|
|
type: object
|
|
required: [region, start_time, end_time]
|
|
properties:
|
|
region: { $ref: "#/components/schemas/Region" }
|
|
start_time: { type: string, format: date-time }
|
|
end_time: { type: string, format: date-time }
|
|
|
|
DownloadRequest:
|
|
type: object
|
|
properties:
|
|
epoch: { type: string, format: date-time }
|
|
latest: { type: boolean }
|
|
subset: { $ref: "#/components/schemas/SubsetSpec" }
|
|
|
|
DownloadAccepted:
|
|
type: object
|
|
required: [job_id]
|
|
properties:
|
|
job_id: { type: string }
|
|
|
|
DatasetEntry:
|
|
type: object
|
|
required: [filename, epoch, loaded]
|
|
properties:
|
|
filename: { type: string }
|
|
epoch: { type: string, format: date-time }
|
|
subset: { $ref: "#/components/schemas/SubsetSpec" }
|
|
coverage: { $ref: "#/components/schemas/Coverage" }
|
|
loaded: { type: boolean }
|
|
|
|
DatasetList:
|
|
type: object
|
|
required: [source, datasets]
|
|
properties:
|
|
source: { type: string }
|
|
datasets:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/DatasetEntry" }
|
|
|
|
DownloadJob:
|
|
type: object
|
|
required: [id, source, dataset, epoch, status, started_at, total_units, done_units, bytes]
|
|
properties:
|
|
id: { type: string }
|
|
source: { type: string }
|
|
dataset: { type: string }
|
|
epoch: { type: string, format: date-time }
|
|
status: { type: string, enum: [pending, running, complete, failed, cancelled] }
|
|
started_at: { type: string, format: date-time }
|
|
ended_at: { type: string, format: date-time }
|
|
error: { type: string }
|
|
total_units: { type: integer }
|
|
done_units: { type: integer }
|
|
bytes: { type: integer, format: int64 }
|
|
|
|
StatusResponse:
|
|
type: object
|
|
required: [source, uptime, goroutines, memory_mb, jobs_by_status, stored_datasets, loaded_datasets]
|
|
properties:
|
|
source: { type: string }
|
|
uptime: { type: string }
|
|
goroutines: { type: integer }
|
|
memory_mb: { type: integer, format: int64 }
|
|
jobs_by_status:
|
|
type: object
|
|
additionalProperties: { type: integer }
|
|
stored_datasets: { type: integer }
|
|
loaded_datasets: { type: integer }
|
|
|
|
# --- wind visualization -------------------------------------------------
|
|
WindMeta:
|
|
type: object
|
|
required: [source, epoch, default_step, min_step, suggested_altitudes, bbox]
|
|
properties:
|
|
source: { type: string }
|
|
epoch: { type: string, format: date-time }
|
|
default_step: { type: number }
|
|
min_step: { type: number }
|
|
suggested_altitudes:
|
|
type: array
|
|
items: { type: integer }
|
|
bbox: { $ref: "#/components/schemas/Region" }
|
|
|
|
WindComponent:
|
|
type: object
|
|
required: [header, data]
|
|
properties:
|
|
header: { $ref: "#/components/schemas/WindHeader" }
|
|
data:
|
|
type: array
|
|
items: { type: number }
|
|
|
|
WindHeader:
|
|
type: object
|
|
required: [parameterCategory, parameterNumber, nx, ny, lo1, la1, lo2, la2, dx, dy, refTime, forecastTime]
|
|
properties:
|
|
parameterCategory: { type: integer }
|
|
parameterNumber: { type: integer }
|
|
parameterNumberName: { type: string }
|
|
parameterUnit: { type: string }
|
|
nx: { type: integer }
|
|
ny: { type: integer }
|
|
lo1: { type: number }
|
|
la1: { type: number }
|
|
lo2: { type: number }
|
|
la2: { type: number }
|
|
dx: { type: number }
|
|
dy: { type: number }
|
|
refTime: { type: string }
|
|
forecastTime: { type: integer }
|