openapi: 3.0.3
info:
  title: DSTA ML Service
  description: Feature engineering, model registry, training, predictions, and forecasting.
  version: 0.1.0
servers:
  - url: http://localhost:8004
    description: Local development
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    PageResponse:
      type: object
      properties:
        items:
          type: array
          items: {}
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    ModelRegistryRead:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        model_type:
          type: string
        description:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
    ModelVersionRead:
      type: object
      properties:
        id:
          type: string
          format: uuid
        model_id:
          type: string
          format: uuid
        version:
          type: string
        status:
          type: string
          enum: [staging, production, archived]
        metrics:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
    TrainingJobRead:
      type: object
      properties:
        id:
          type: string
          format: uuid
        model_version_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, running, completed, failed, cancelled]
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
    PredictionRead:
      type: object
      properties:
        id:
          type: string
          format: uuid
        model_version_id:
          type: string
          format: uuid
        symbol:
          type: string
        predicted_at:
          type: string
          format: date-time
        horizon:
          type: string
        value:
          type: number
        confidence:
          type: number
          nullable: true
    FeatureSetRead:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        is_active:
          type: boolean
        feature_count:
          type: integer
        created_at:
          type: string
          format: date-time
    ForecastResponse:
      type: object
      properties:
        model:
          type: string
        symbol:
          type: string
        horizon:
          type: integer
        forecasts:
          type: array
          items:
            type: number
        quantiles:
          type: object
          nullable: true
    Error:
      type: object
      properties:
        detail:
          type: string
security:
  - bearerAuth: []
paths:
  /health:
    get:
      operationId: health
      summary: Liveness probe
      tags: [health]
      security: []
      responses:
        '200':
          description: Service is alive
  /ready:
    get:
      operationId: ready
      summary: Readiness probe
      tags: [health]
      security: []
      responses:
        '200':
          description: Service is ready

  # ── Models (Registry) ─────────────────────────────────────────────────────
  /api/v1/models:
    get:
      operationId: listModels
      summary: List registered models
      tags: [registry]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createModel
      summary: Register a new model
      tags: [registry]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, model_type]
              properties:
                name:
                  type: string
                model_type:
                  type: string
                description:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelRegistryRead'
  /api/v1/models/{model_id}:
    get:
      operationId: getModel
      summary: Get a model
      tags: [registry]
      parameters:
        - name: model_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Model
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelRegistryRead'
        '404':
          description: Not found
    patch:
      operationId: updateModel
      summary: Update a model
      tags: [registry]
      parameters:
        - name: model_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
    delete:
      operationId: deleteModel
      summary: Delete a model
      tags: [registry]
      parameters:
        - name: model_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted

  # ── Model Versions ────────────────────────────────────────────────────────
  /api/v1/model-versions:
    get:
      operationId: listModelVersions
      summary: List model versions
      tags: [registry]
      parameters:
        - name: model_id
          in: query
          schema:
            type: string
            format: uuid
        - name: status
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createModelVersion
      summary: Create a model version
      tags: [registry]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model_id, version]
              properties:
                model_id:
                  type: string
                  format: uuid
                version:
                  type: string
                artifact_uri:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelVersionRead'
  /api/v1/model-versions/{mv_id}:
    get:
      operationId: getModelVersion
      summary: Get a model version
      tags: [registry]
      parameters:
        - name: mv_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Model version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelVersionRead'
        '404':
          description: Not found
    patch:
      operationId: updateModelVersion
      summary: Update a model version
      tags: [registry]
      parameters:
        - name: mv_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
    delete:
      operationId: deleteModelVersion
      summary: Delete a model version
      tags: [registry]
      parameters:
        - name: mv_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted
  /api/v1/model-versions/{mv_id}/deploy:
    post:
      operationId: deployModelVersion
      summary: Promote a model version to production
      tags: [registry]
      parameters:
        - name: mv_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deployed version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelVersionRead'

  # ── Training Jobs ─────────────────────────────────────────────────────────
  /api/v1/training-jobs:
    get:
      operationId: listTrainingJobs
      summary: List training jobs
      tags: [training]
      parameters:
        - name: status
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createTrainingJob
      summary: Create a training job
      tags: [training]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model_version_id]
              properties:
                model_version_id:
                  type: string
                  format: uuid
                hyperparameters:
                  type: object
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrainingJobRead'
  /api/v1/training-jobs/{job_id}:
    get:
      operationId: getTrainingJob
      summary: Get a training job
      tags: [training]
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Training job with metrics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrainingJobRead'
        '404':
          description: Not found
    patch:
      operationId: updateTrainingJob
      summary: Update a training job
      tags: [training]
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
    delete:
      operationId: deleteTrainingJob
      summary: Delete a training job
      tags: [training]
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted
  /api/v1/training-jobs/{job_id}/start:
    post:
      operationId: startTrainingJob
      summary: Start a pending training job
      tags: [training]
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrainingJobRead'
  /api/v1/training-jobs/{job_id}/cancel:
    post:
      operationId: cancelTrainingJob
      summary: Cancel a running training job
      tags: [training]
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrainingJobRead'

  # ── Predictions ───────────────────────────────────────────────────────────
  /api/v1/predictions:
    get:
      operationId: listPredictions
      summary: List predictions
      tags: [predictions]
      parameters:
        - name: symbol
          in: query
          schema:
            type: string
        - name: model_version_id
          in: query
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
  /api/v1/predictions/{pred_id}:
    get:
      operationId: getPrediction
      summary: Get a prediction
      tags: [predictions]
      parameters:
        - name: pred_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Prediction
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PredictionRead'
        '404':
          description: Not found
  /api/v1/realtime-predictions/latest:
    get:
      operationId: latestRealtimePrediction
      summary: Get the latest realtime prediction
      tags: [predictions]
      parameters:
        - name: symbol
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Latest prediction
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PredictionRead'

  # ── Feature Sets ──────────────────────────────────────────────────────────
  /api/v1/feature-sets/active:
    get:
      operationId: getActiveFeatureSet
      summary: Get the currently active feature set
      tags: [features]
      responses:
        '200':
          description: Active feature set
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureSetRead'
  /api/v1/feature-sets:
    get:
      operationId: listFeatureSets
      summary: List feature sets
      tags: [features]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createFeatureSet
      summary: Create a feature set
      tags: [features]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureSetRead'
  /api/v1/feature-sets/{fs_id}:
    get:
      operationId: getFeatureSet
      summary: Get a feature set with definitions
      tags: [features]
      parameters:
        - name: fs_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Feature set detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureSetRead'
        '404':
          description: Not found
    patch:
      operationId: updateFeatureSet
      summary: Update a feature set
      tags: [features]
      parameters:
        - name: fs_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
    delete:
      operationId: deleteFeatureSet
      summary: Delete a feature set
      tags: [features]
      parameters:
        - name: fs_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted

  # ── Forecast ──────────────────────────────────────────────────────────────
  /api/v1/forecast:
    post:
      operationId: forecast
      summary: Run a price forecast
      tags: [forecast]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [symbol, data, horizon]
              properties:
                symbol:
                  type: string
                data:
                  type: array
                  items:
                    type: array
                    items:
                      type: number
                  description: OHLCV rows as [[ts, o, h, l, c, v], ...]
                horizon:
                  type: integer
                  default: 24
                model:
                  type: string
                  default: chronos
                  enum: [chronos, lstm, transformer]
      responses:
        '200':
          description: Forecast result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ForecastResponse'
  /api/v1/forecast/models:
    get:
      operationId: listForecastModels
      summary: List available forecast models
      tags: [forecast]
      responses:
        '200':
          description: Available models
          content:
            application/json:
              schema:
                type: object
                properties:
                  models:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        description:
                          type: string

  # ── Experiments ───────────────────────────────────────────────────────────
  /api/v1/experiments:
    get:
      operationId: listExperiments
      summary: List MLflow experiments
      tags: [experiments]
      responses:
        '200':
          description: List of experiments
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/experiments/{experiment_name}/runs:
    get:
      operationId: listRuns
      summary: List runs for an experiment
      tags: [experiments]
      parameters:
        - name: experiment_name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: List of runs
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/experiments/{experiment_name}/best:
    get:
      operationId: getBestRun
      summary: Get the best run for an experiment
      tags: [experiments]
      parameters:
        - name: experiment_name
          in: path
          required: true
          schema:
            type: string
        - name: metric
          in: query
          schema:
            type: string
            default: sharpe_ratio
      responses:
        '200':
          description: Best run
          content:
            application/json:
              schema:
                type: object
  /api/v1/experiments/runs/{run_id}:
    delete:
      operationId: deleteRun
      summary: Delete an MLflow run
      tags: [experiments]
      parameters:
        - name: run_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted
