{
  "openapi": "3.0.3",
  "info": {
    "title": "akiAPI",
    "version": "0.2.0",
    "description": "Crop fertilizer recommendation API for Sub-Saharan Africa. Models: QUEFTS (Rquefts) and AgWise (ML, ranger).",
    "license": {
      "name": "see LICENSE"
    }
  },
  "servers": [
    {
      "url": "/2.0/v1",
      "description": "API base URL"
    }
  ],
  "tags": [
    {
      "name": "system",
      "description": "Operational endpoints"
    },
    {
      "name": "models",
      "description": "Recommendation models"
    },
    {
      "name": "partner",
      "description": "Versioned partner contract (FieldContext -> Prediction)"
    },
    {
      "name": "data",
      "description": "Data plane: outcome ingestion + the learning loop"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "system"
        ],
        "summary": "Liveness / readiness probe",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/version": {
      "get": {
        "tags": [
          "system"
        ],
        "summary": "Build provenance — package version, git SHA, data artifact dates",
        "operationId": "getVersion",
        "responses": {
          "200": {
            "description": "Provenance metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Version"
                }
              }
            }
          }
        }
      }
    },
    "/models": {
      "get": {
        "tags": [
          "models"
        ],
        "summary": "List registered models with their input schema, model card and release status",
        "operationId": "getModels",
        "responses": {
          "200": {
            "description": "Registered models",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelsList"
                }
              }
            }
          }
        }
      }
    },
    "/release-gates": {
      "get": {
        "tags": [
          "system"
        ],
        "summary": "The eight release gates a model must pass to be fully released",
        "operationId": "getReleaseGates",
        "responses": {
          "200": {
            "description": "Release-gate catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseGatesList"
                }
              }
            }
          }
        }
      }
    },
    "/recommendation": {
      "post": {
        "tags": [
          "partner"
        ],
        "summary": "Recommendation for a field context (D5 partner contract)",
        "operationId": "postRecommendation",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FieldContext"
              },
              "examples": {
                "maize": {
                  "summary": "Maize, thin context",
                  "value": {
                    "crop": "maize",
                    "location": {
                      "lat": 9,
                      "lon": 8
                    },
                    "season": "2026A"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recommendation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prediction"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "422": {
            "description": "Well-formed but unservable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/explanation": {
      "post": {
        "tags": [
          "partner"
        ],
        "summary": "Rationale, confidence and benchmark behind a recommendation",
        "operationId": "postExplanation",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FieldContext"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Explanation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Explanation"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "422": {
            "description": "Well-formed but unservable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/crops": {
      "get": {
        "tags": [
          "partner"
        ],
        "summary": "Supported crops and the model(s) that serve each",
        "operationId": "getCrops",
        "responses": {
          "200": {
            "description": "Supported crops",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CropsList"
                }
              }
            }
          }
        }
      }
    },
    "/regions": {
      "get": {
        "tags": [
          "partner"
        ],
        "summary": "Supported regions / validated geographies",
        "operationId": "getRegions",
        "responses": {
          "200": {
            "description": "Supported regions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegionsList"
                }
              }
            }
          }
        }
      }
    },
    "/prices": {
      "get": {
        "tags": [
          "partner"
        ],
        "summary": "Location-specific crop + fertilizer prices (spatial raster, catalogue fallback)",
        "operationId": "getPrices",
        "parameters": [
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            },
            "example": 8
          },
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            },
            "example": 9
          },
          {
            "name": "crop",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "maize"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "NG"
          }
        ],
        "responses": {
          "200": {
            "description": "Prices at the location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prices"
                }
              }
            }
          }
        }
      }
    },
    "/outcomes": {
      "post": {
        "tags": [
          "data"
        ],
        "summary": "Report an on-farm outcome (closes the learning loop)",
        "description": "The data plane's outcome processor: validate -> normalise -> quality-check -> store. A partner reports the rates applied and the yield obtained on a field; the record is tenant-scoped and feeds the training + feature stores.",
        "operationId": "postOutcome",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Outcome"
              },
              "examples": {
                "maize": {
                  "summary": "Maize field, one season",
                  "value": {
                    "crop": "maize",
                    "location": {
                      "lat": 9,
                      "lon": 8
                    },
                    "country": "NG",
                    "applied": {
                      "N": 90,
                      "P": 20,
                      "K": 20
                    },
                    "observed_yield_kg_ha": 4200,
                    "area_ha": 1.5,
                    "season": "2026A",
                    "recommendation_id": "rec_20260903T101500.123-1a2b3c4d"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Outcome accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutcomeAck"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "422": {
            "description": "Well-formed but unservable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/outcomes/summary": {
      "get": {
        "tags": [
          "data"
        ],
        "summary": "Learning-loop status: outcomes captured, feature-store coverage, retrain readiness",
        "operationId": "getOutcomesSummary",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Learning-loop summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutcomesSummary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/ingest": {
      "get": {
        "tags": [
          "data"
        ],
        "summary": "List the data-plane ingestion processors (one per data type)",
        "operationId": "getIngest",
        "responses": {
          "200": {
            "description": "Processor registry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProcessorList"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "data"
        ],
        "summary": "Ingest one record through its data-type processor (validate/normalise/QC/store)",
        "operationId": "postIngest",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRecord"
              },
              "examples": {
                "soil": {
                  "summary": "A soil sample",
                  "value": {
                    "type": "soil",
                    "record": {
                      "lon": 8,
                      "lat": 9,
                      "ph": 5.8,
                      "oc": 1.2,
                      "k_cmol": 0.3
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Record accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestAck"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "422": {
            "description": "Well-formed but unservable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/promotion": {
      "get": {
        "tags": [
          "data"
        ],
        "summary": "Promotion status: release gates + field-validation benchmark per model",
        "description": "The registry & promotion plane. Each model's release gates, the recorded field-validation benchmark (accuracy vs held-out data), and a promotion verdict.",
        "operationId": "getPromotion",
        "responses": {
          "200": {
            "description": "Per-model promotion status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromotionList"
                }
              }
            }
          }
        }
      }
    },
    "/sign-off": {
      "post": {
        "tags": [
          "data"
        ],
        "summary": "Record an agronomist's sign-off on a model (flips the agronomist_review gate)",
        "operationId": "postSignOff",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignOff"
              },
              "examples": {
                "approve": {
                  "summary": "Approve a model",
                  "value": {
                    "model": "agwise",
                    "by": "Dr. A. Bello",
                    "decision": "approved",
                    "note": "reviewed a 40-field sample; rates agronomically sound",
                    "sample_size": 40
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sign-off recorded + new gate state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignOffAck"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "422": {
            "description": "Well-formed but unservable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/retrain": {
      "post": {
        "tags": [
          "data"
        ],
        "summary": "Fire a retrain cycle when enough outcomes have accumulated",
        "description": "The learning plane's retrain trigger. Assembles the versioned training set + refreshes the feature store when the outcome threshold is met (or force=true); the model fit is the ops step (AKIAPI_RETRAIN_FIT).",
        "operationId": "postRetrain",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "force": {
                    "type": "boolean",
                    "description": "Retrain even if below the outcome threshold."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Retrain trigger result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RetrainResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            }
          }
        }
      }
    },
    "/impact": {
      "get": {
        "tags": [
          "data"
        ],
        "summary": "Impact dashboard: advice served, outcomes/adoption, model performance, governance",
        "operationId": "getImpact",
        "responses": {
          "200": {
            "description": "Impact metrics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImpactMetrics"
                }
              }
            }
          }
        }
      }
    },
    "/soil": {
      "get": {
        "tags": [
          "models"
        ],
        "summary": "Soil nutrient supply at a single point (no model run)",
        "operationId": "getSoil",
        "parameters": [
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            },
            "example": 20
          },
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            },
            "example": 10
          }
        ],
        "responses": {
          "200": {
            "description": "Soil supply at point",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SoilResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or out-of-range coords",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No soil data at this location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/compute": {
      "post": {
        "tags": [
          "models"
        ],
        "summary": "Run a fertilizer recommendation model",
        "operationId": "postCompute",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComputeRequest"
              },
              "examples": {
                "maize-quefts": {
                  "summary": "Maize, QUEFTS, 10 ha",
                  "value": {
                    "lon": [
                      20
                    ],
                    "lat": [
                      10
                    ],
                    "crop": [
                      "maize"
                    ],
                    "area": [
                      10
                    ],
                    "areaUnit": [
                      "ha"
                    ],
                    "crop_value": [
                      0.25
                    ],
                    "minROI": [
                      18
                    ],
                    "model": [
                      "quefts"
                    ],
                    "currency": [
                      "USD"
                    ],
                    "language": [
                      "english"
                    ],
                    "irrigated": [
                      false
                    ],
                    "priceUnit": [
                      "kg"
                    ],
                    "fertilizers": [
                      {
                        "product": "urea",
                        "price": 1,
                        "N": 46,
                        "P": 0,
                        "K": 0
                      }
                    ]
                  }
                },
                "maize-agwise": {
                  "summary": "Maize, AgWise ML model, 10 ha, efficiency objective",
                  "value": {
                    "lon": [
                      8
                    ],
                    "lat": [
                      9
                    ],
                    "crop": [
                      "maize"
                    ],
                    "area": [
                      10
                    ],
                    "areaUnit": [
                      "ha"
                    ],
                    "crop_value": [
                      0.25
                    ],
                    "minROI": [
                      18
                    ],
                    "model": [
                      "agwise"
                    ],
                    "currency": [
                      "USD"
                    ],
                    "language": [
                      "english"
                    ],
                    "priceUnit": [
                      "kg"
                    ],
                    "objective": [
                      "efficiency"
                    ],
                    "improvement_ratio": [
                      1.3
                    ],
                    "N_grid": {
                      "start": 0,
                      "stop": 200,
                      "by": 20
                    },
                    "P_grid": {
                      "start": 0,
                      "stop": 120,
                      "by": 10
                    },
                    "K_grid": {
                      "start": 0,
                      "stop": 120,
                      "by": 10
                    },
                    "fertilizers": [
                      {
                        "product": "urea",
                        "price": 1,
                        "N": 46,
                        "P": 0,
                        "K": 0
                      },
                      {
                        "product": "TSP",
                        "price": 1.2,
                        "N": 0,
                        "P": 20,
                        "K": 0
                      },
                      {
                        "product": "MOP",
                        "price": 0.9,
                        "N": 0,
                        "P": 0,
                        "K": 50
                      }
                    ]
                  }
                },
                "maize-fre": {
                  "summary": "Maize, FRE empirical model, 10 ha",
                  "value": {
                    "lon": [
                      20
                    ],
                    "lat": [
                      10
                    ],
                    "crop": [
                      "maize"
                    ],
                    "area": [
                      10
                    ],
                    "areaUnit": [
                      "ha"
                    ],
                    "crop_value": [
                      0.25
                    ],
                    "minROI": [
                      18
                    ],
                    "model": [
                      "fre"
                    ],
                    "currency": [
                      "USD"
                    ],
                    "language": [
                      "english"
                    ],
                    "priceUnit": [
                      "kg"
                    ],
                    "fertilizers": [
                      {
                        "product": "urea",
                        "price": 1,
                        "N": 46,
                        "P": 0,
                        "K": 0
                      },
                      {
                        "product": "TSP",
                        "price": 1.2,
                        "N": 0,
                        "P": 20,
                        "K": 0
                      },
                      {
                        "product": "MOP",
                        "price": 0.9,
                        "N": 0,
                        "P": 0,
                        "K": 50
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recommendation produced",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComputeSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Health": {
        "type": "object",
        "required": [
          "status",
          "models"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "models": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Version": {
        "type": "object",
        "required": [
          "name",
          "version"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "akiAPI"
          },
          "version": {
            "type": "string",
            "example": "0.1.0"
          },
          "git_sha": {
            "type": "string",
            "nullable": true,
            "description": "Set at image build time via AKIAPI_GIT_SHA env"
          },
          "built_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "r_version": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "soil_supply_tif": {
                "type": "string",
                "nullable": true,
                "format": "date-time",
                "description": "mtime of data/soil/soil_supply.tif"
              },
              "fertilizers_json": {
                "type": "string",
                "nullable": true,
                "format": "date-time",
                "description": "mtime of web/fertilizers.json"
              }
            }
          }
        }
      },
      "ModelInfo": {
        "type": "object",
        "required": [
          "name",
          "required"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "quefts"
          },
          "required": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Required input fields for /compute"
          },
          "description": {
            "type": "string"
          },
          "crops": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "engine": {
            "type": "string",
            "example": "Rquefts"
          },
          "engine_version": {
            "type": "string",
            "example": "1.2-8"
          },
          "data_source": {
            "type": "string"
          },
          "variants": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "agwise: registered pretrained models selectable via model_variant"
          },
          "card": {
            "$ref": "#/components/schemas/ModelCard"
          },
          "release": {
            "$ref": "#/components/schemas/ReleaseStatus"
          }
        }
      },
      "ModelCard": {
        "type": "object",
        "description": "FAIR metadata: provenance, licence, validated domain, uncertainty, references.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "intended_use": {
            "type": "string"
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "domain": {
            "type": "object",
            "additionalProperties": true
          },
          "provenance": {
            "type": "object",
            "additionalProperties": true
          },
          "license": {
            "type": "object",
            "additionalProperties": true
          },
          "uncertainty": {
            "type": "object",
            "additionalProperties": true
          },
          "references": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "gates": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ReleaseGate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "passed",
              "pending",
              "failed"
            ]
          },
          "detail": {
            "type": "string"
          },
          "auto": {
            "type": "boolean"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "ReleaseStatus": {
        "type": "object",
        "description": "Evaluation of the eight release gates for a model.",
        "properties": {
          "released": {
            "type": "boolean"
          },
          "passed": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "summary": {
            "type": "string",
            "example": "6/8"
          },
          "blocking": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "gates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReleaseGate"
            }
          }
        }
      },
      "ReleaseGatesList": {
        "type": "object",
        "required": [
          "gates"
        ],
        "properties": {
          "gates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReleaseGate"
            }
          }
        }
      },
      "ModelsList": {
        "type": "object",
        "required": [
          "models"
        ],
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ModelInfo"
            }
          }
        }
      },
      "SoilSupply": {
        "type": "object",
        "description": "Nutrient supply (kg/ha equivalent) derived from iSDA layers.",
        "properties": {
          "N_base_supply": {
            "type": "number"
          },
          "P_base_supply": {
            "type": "number"
          },
          "K_base_supply": {
            "type": "number"
          }
        }
      },
      "SoilResponse": {
        "type": "object",
        "required": [
          "lon",
          "lat",
          "supply"
        ],
        "properties": {
          "lon": {
            "type": "number"
          },
          "lat": {
            "type": "number"
          },
          "supply": {
            "$ref": "#/components/schemas/SoilSupply"
          },
          "source": {
            "type": "string"
          }
        }
      },
      "Fertilizer": {
        "type": "object",
        "required": [
          "product",
          "price",
          "N",
          "P",
          "K"
        ],
        "properties": {
          "product": {
            "type": "string",
            "example": "urea"
          },
          "price": {
            "type": "number",
            "description": "Price per priceUnit",
            "example": 1
          },
          "N": {
            "type": "number",
            "description": "N content (%)",
            "example": 46
          },
          "P": {
            "type": "number",
            "description": "P content (%)",
            "example": 0
          },
          "K": {
            "type": "number",
            "description": "K content (%)",
            "example": 0
          }
        }
      },
      "ComputeRequest": {
        "type": "object",
        "required": [
          "lon",
          "lat",
          "crop",
          "area",
          "areaUnit",
          "model"
        ],
        "description": "Plumber/jsonlite accepts scalars as length-1 arrays; both shapes work.",
        "properties": {
          "lon": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            }
          },
          "lat": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            }
          },
          "crop": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "area": {
            "type": "array",
            "items": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          "areaUnit": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "ha",
                "hekta",
                "acre",
                "ekari",
                "are",
                "m2",
                "string"
              ]
            }
          },
          "crop_value": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "minROI": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0
            }
          },
          "model": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "quefts",
                "agwise",
                "fre"
              ]
            }
          },
          "currency": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "language": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "english",
                "french",
                "swahili",
                "portuguese",
                "arabic",
                "spanish",
                "tigrinya",
                "amharic",
                "kinyarwanda",
                "somali"
              ]
            }
          },
          "irrigated": {
            "type": "array",
            "items": {
              "type": "boolean"
            }
          },
          "priceUnit": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "kg",
                "25kg",
                "50kg"
              ]
            }
          },
          "fertilizers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Fertilizer"
            }
          },
          "model_variant": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "agwise only: name of a registered pretrained model (see GET /models). Defaults to the server-configured model."
            }
          },
          "objective": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "attainable",
                "efficiency",
                "target",
                "max_yield",
                "expected_utility"
              ],
              "description": "agwise only: optimal-NPK selection objective. 'expected_utility' runs the risk-aware, QRF-based optimisation of Asamoah et al. (2026)."
            }
          },
          "improvement_ratio": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 1,
              "description": "agwise only: target yield as a multiple of baseline (default 1.3)"
            }
          },
          "yield_fraction": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "agwise only: fraction of max yield to attain (default 0.90)"
            }
          },
          "risk_profile": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "neutral",
                "moderate",
                "strong",
                "all"
              ],
              "description": "agwise expected_utility only: farmer risk profile (default neutral). 'all' returns the safe/balanced/opportunity triple with a balanced headline."
            }
          },
          "risk_options": {
            "type": "array",
            "items": {
              "type": "boolean",
              "description": "agwise expected_utility only: also return the safe/balanced/opportunity option triple in decision.options"
            }
          },
          "risk_gamma": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0,
              "exclusiveMaximum": 1,
              "description": "agwise expected_utility only: override risk-aversion curvature (0 = risk-neutral)"
            }
          },
          "risk_lambda": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0,
              "description": "agwise expected_utility only: override loss-aversion weight (>1 penalises losses)"
            }
          },
          "profit_reference": {
            "type": "array",
            "items": {
              "type": "number",
              "description": "agwise expected_utility only: reference profit P0 (USD/ha) separating gains from losses (default 100)"
            }
          },
          "N_grid": {
            "$ref": "#/components/schemas/NpkGridAxis"
          },
          "P_grid": {
            "$ref": "#/components/schemas/NpkGridAxis"
          },
          "K_grid": {
            "$ref": "#/components/schemas/NpkGridAxis"
          }
        }
      },
      "NpkGridAxis": {
        "type": "object",
        "description": "agwise only: search range (kg/ha of element) for one nutrient.",
        "properties": {
          "start": {
            "type": "number",
            "example": 0
          },
          "stop": {
            "type": "number",
            "example": 200
          },
          "by": {
            "type": "number",
            "exclusiveMinimum": 0,
            "example": 20
          }
        }
      },
      "Recommendation": {
        "type": "object",
        "description": "Self-describing, versioned record of how this answer was produced — for reproducibility and audit ('why did it say that?').",
        "required": [
          "id",
          "schema_version",
          "generated_at",
          "model",
          "inputs"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "rec_20260903T101500.123-1a2b3c4d"
          },
          "schema_version": {
            "type": "string",
            "example": "1.0"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "tenant": {
            "type": "string",
            "nullable": true
          },
          "request_id": {
            "type": "string",
            "nullable": true,
            "description": "Transport request id, when supplied by the caller/edge."
          },
          "model": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "variant": {
                "type": "string",
                "nullable": true
              },
              "engine": {
                "type": "string",
                "nullable": true
              },
              "engine_version": {
                "type": "string",
                "nullable": true
              },
              "pkg_version": {
                "type": "string"
              },
              "git_sha": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "inputs": {
            "type": "object",
            "additionalProperties": true,
            "description": "Decision-relevant inputs echoed post-sanitize."
          },
          "confidence": {
            "type": "object",
            "properties": {
              "grade": {
                "type": "string",
                "enum": [
                  "high",
                  "medium",
                  "low"
                ]
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "prediction": {
                "type": "object",
                "nullable": true,
                "description": "Predictive distribution over the outcome (not a point). A quantile set is the model-agnostic form; chance-of-loss and the risk-framed options are derived from it.",
                "properties": {
                  "basis": {
                    "type": "string",
                    "example": "yield_kg_ha"
                  },
                  "point": {
                    "type": "number",
                    "description": "Median (p50)."
                  },
                  "distribution": {
                    "type": "object",
                    "properties": {
                      "family": {
                        "type": "string"
                      },
                      "method": {
                        "type": "string"
                      },
                      "quantiles": {
                        "type": "object",
                        "additionalProperties": true,
                        "description": "p05,p25,p50,p75,p95 -> kg/ha"
                      }
                    }
                  },
                  "interval": {
                    "type": "object",
                    "description": "Convenience band derived from the quantiles.",
                    "properties": {
                      "level": {
                        "type": "number",
                        "example": 0.9
                      },
                      "lower": {
                        "type": "number"
                      },
                      "upper": {
                        "type": "number"
                      }
                    }
                  }
                }
              }
            }
          },
          "guardrails": {
            "type": "object",
            "properties": {
              "within_envelope": {
                "type": "boolean"
              },
              "ceilings": {
                "type": "object",
                "additionalProperties": true
              },
              "flags": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "decision": {
            "type": "object",
            "nullable": true,
            "description": "Present only for agwise objective 'expected_utility': the risk-aware optimisation of Asamoah et al. (2026). Includes the farmer risk profile, expected utility, chance of loss, and the profit distribution the chosen rate implies.",
            "properties": {
              "method": {
                "type": "string"
              },
              "risk_profile": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "gamma": {
                    "type": "number"
                  },
                  "lambda": {
                    "type": "number"
                  },
                  "reference_profit": {
                    "type": "number"
                  }
                }
              },
              "maize_price_per_kg": {
                "type": "number"
              },
              "expected_utility": {
                "type": "number"
              },
              "chance_of_loss": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "candidates_evaluated": {
                "type": "integer"
              },
              "profit_per_ha": {
                "type": "object",
                "properties": {
                  "mean": {
                    "type": "number"
                  },
                  "quantiles": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              },
              "options": {
                "type": "array",
                "nullable": true,
                "description": "Present when risk_options/all: safe (strong), balanced (moderate) and opportunity (neutral) alternatives.",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      },
      "FieldContextRefinements": {
        "type": "object",
        "additionalProperties": true,
        "description": "Optional refinements on a FieldContext. All optional; unknown keys are tolerated (the engine gap-fills). Mirrors the typed /compute inputs.",
        "properties": {
          "country": {
            "type": "string",
            "example": "NG",
            "description": "ISO-2; drives catalogue pricing + country scoping."
          },
          "model": {
            "type": "string",
            "enum": [
              "quefts",
              "agwise",
              "fre"
            ],
            "description": "Force a model; default is the best model advertising the crop."
          },
          "model_variant": {
            "type": "string",
            "description": "agwise: a registered pretrained model (see GET /models)."
          },
          "fertilizers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Fertilizer"
            },
            "description": "Caller-supplied basket + prices; else catalogue / raster."
          },
          "crop_value": {
            "type": "number",
            "description": "Crop output price (USD/kg)."
          },
          "currency": {
            "type": "string",
            "example": "USD"
          },
          "area": {
            "type": "number",
            "exclusiveMinimum": 0,
            "default": 1
          },
          "areaUnit": {
            "type": "string",
            "enum": [
              "ha",
              "hekta",
              "acre",
              "ekari",
              "are",
              "m2"
            ],
            "default": "ha"
          },
          "priceUnit": {
            "type": "string",
            "enum": [
              "kg",
              "25kg",
              "50kg"
            ],
            "default": "kg"
          },
          "minROI": {
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "language": {
            "type": "string",
            "enum": [
              "english",
              "french",
              "swahili",
              "portuguese",
              "arabic",
              "spanish",
              "tigrinya",
              "amharic",
              "kinyarwanda",
              "somali"
            ],
            "default": "english"
          },
          "objective": {
            "type": "string",
            "enum": [
              "attainable",
              "efficiency",
              "target",
              "max_yield",
              "expected_utility"
            ],
            "description": "agwise: optimal-NPK selection objective."
          },
          "risk_profile": {
            "type": "string",
            "enum": [
              "neutral",
              "moderate",
              "strong",
              "all"
            ],
            "description": "agwise expected_utility: farmer risk profile."
          },
          "risk_options": {
            "type": "boolean",
            "description": "agwise expected_utility: also return the safe/balanced/opportunity triple."
          },
          "risk_gamma": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 1,
            "description": "agwise expected_utility: override risk-aversion curvature."
          },
          "risk_lambda": {
            "type": "number",
            "minimum": 0,
            "description": "agwise expected_utility: override loss-aversion weight."
          },
          "profit_reference": {
            "type": "number",
            "description": "agwise expected_utility: reference profit P0 (USD/ha)."
          }
        }
      },
      "FieldContext": {
        "type": "object",
        "required": [
          "crop",
          "location"
        ],
        "description": "D5 request contract. Only crop + location are required; the rest defaults or is gap-filled from the geospatial layers.",
        "properties": {
          "crop": {
            "type": "string",
            "example": "maize"
          },
          "location": {
            "type": "object",
            "description": "Field coordinates, or a resolvable admin_unit / country for a zone-level answer.",
            "properties": {
              "lat": {
                "type": "number",
                "example": 9
              },
              "lon": {
                "type": "number",
                "example": 8
              },
              "country": {
                "type": "string",
                "description": "ISO-2; used if context.country is absent."
              },
              "admin_unit": {
                "type": "string",
                "description": "Resolvable place name when lat/lon are absent."
              }
            }
          },
          "season": {
            "type": "string",
            "example": "2026A"
          },
          "context": {
            "$ref": "#/components/schemas/FieldContextRefinements"
          }
        }
      },
      "Prediction": {
        "type": "object",
        "description": "D5 response contract. Economic, benchmark and reasoning fields are populated as those capabilities come online; the shape is stable from the start.",
        "properties": {
          "recommendation_id": {
            "type": "string"
          },
          "prediction": {
            "type": "object",
            "properties": {
              "rate": {
                "type": "object",
                "additionalProperties": true,
                "description": "N/P/K (kg/ha) + product breakdown"
              },
              "yield": {
                "type": "object",
                "additionalProperties": true,
                "description": "mean + predictive distribution (quantiles) + interval"
              },
              "evidence": {
                "type": "object",
                "nullable": true,
                "description": "Local ground truth from the feature store: observed_fields and mean realised yield backing this crop x country. Corroborates the rate; never alters it.",
                "additionalProperties": true
              }
            }
          },
          "economics": {
            "type": "object",
            "nullable": true,
            "properties": {
              "expected_net_return": {
                "type": "number"
              },
              "currency": {
                "type": "string",
                "example": "USD/ha"
              },
              "prob_loss": {
                "type": "number"
              },
              "downside_p10": {
                "type": "number"
              },
              "value_cost_ratio": {
                "type": "number"
              },
              "prices": {
                "type": "string",
                "enum": [
                  "request",
                  "raster",
                  "catalogue",
                  "default-placeholder"
                ]
              }
            }
          },
          "reasoning": {
            "type": "string"
          },
          "benchmark": {
            "type": "object",
            "additionalProperties": true
          },
          "nutrient_balance": {
            "$ref": "#/components/schemas/NutrientBalance"
          },
          "model": {
            "type": "object",
            "additionalProperties": true
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "low_confidence",
                "out_of_range",
                "zone_level",
                "field_evidence",
                "nutrient_imbalance",
                "measured_soil",
                "water_limited"
              ]
            }
          },
          "decision": {
            "type": "object",
            "nullable": true,
            "description": "risk-aware options triple when objective=expected_utility"
          }
        }
      },
      "Explanation": {
        "type": "object",
        "additionalProperties": true,
        "description": "Fuller rationale: model, summary, confidence, guardrails, benchmark."
      },
      "CropsList": {
        "type": "object",
        "required": [
          "crops"
        ],
        "properties": {
          "crops": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "RegionsList": {
        "type": "object",
        "required": [
          "regions"
        ],
        "properties": {
          "regions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Prices": {
        "type": "object",
        "description": "Location-specific prices — the spatial raster when it covers the field, else the per-country catalogue.",
        "properties": {
          "lon": {
            "type": "number"
          },
          "lat": {
            "type": "number"
          },
          "crop": {
            "type": "string"
          },
          "crop_price": {
            "type": "number",
            "nullable": true,
            "description": "USD/kg crop output price"
          },
          "fertilizer_price": {
            "type": "number",
            "nullable": true,
            "description": "USD/kg fertilizer price"
          },
          "currency": {
            "type": "string",
            "example": "USD/kg"
          },
          "source": {
            "type": "string",
            "enum": [
              "raster",
              "catalogue",
              "none"
            ]
          }
        }
      },
      "Outcome": {
        "type": "object",
        "required": [
          "crop",
          "observed_yield_kg_ha"
        ],
        "description": "A reported on-farm outcome. crop + observed_yield_kg_ha are required; location is coordinates or a resolvable admin_unit/country.",
        "properties": {
          "crop": {
            "type": "string",
            "example": "maize"
          },
          "location": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "admin_unit": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            }
          },
          "country": {
            "type": "string",
            "example": "NG"
          },
          "applied": {
            "type": "object",
            "description": "Rates the farmer actually applied (kg/ha of element).",
            "properties": {
              "N": {
                "type": "number",
                "minimum": 0,
                "maximum": 1000
              },
              "P": {
                "type": "number",
                "minimum": 0,
                "maximum": 1000
              },
              "K": {
                "type": "number",
                "minimum": 0,
                "maximum": 1000
              }
            }
          },
          "observed_yield_kg_ha": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100000
          },
          "area_ha": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "season": {
            "type": "string",
            "example": "2026A"
          },
          "recommendation_id": {
            "type": "string",
            "nullable": true,
            "description": "Links this outcome back to the recommendation that advised the field."
          },
          "source": {
            "type": "string",
            "example": "partner-report"
          }
        }
      },
      "OutcomeAck": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "out_20260903T101500.123-1a2b3c4d"
          },
          "status": {
            "type": "string",
            "enum": [
              "stored",
              "validated"
            ],
            "description": "'stored' when persisted; 'validated' when accepted but the store is disabled (AKIAPI_OUTCOMES off)."
          },
          "quality_flags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Soft quality flags, e.g. unknown_crop, no_rates_reported, implausibly_low_yield."
          },
          "outcome": {
            "type": "object",
            "additionalProperties": true,
            "description": "The normalised outcome record as stored."
          }
        }
      },
      "OutcomesSummary": {
        "type": "object",
        "description": "Learning-loop status feeding the impact dashboard.",
        "properties": {
          "outcomes_captured": {
            "type": "integer"
          },
          "linked_to_recommendation": {
            "type": "integer"
          },
          "by_crop": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "by_country": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "quality_flags": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "feature_store": {
            "type": "object",
            "properties": {
              "cells": {
                "type": "integer"
              },
              "file": {
                "type": "string"
              }
            }
          },
          "retrain": {
            "type": "object",
            "properties": {
              "min_outcomes": {
                "type": "integer"
              },
              "ready": {
                "type": "boolean"
              },
              "remaining": {
                "type": "integer"
              }
            }
          },
          "store": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "dir": {
                "type": "string"
              }
            }
          }
        }
      },
      "PromotionList": {
        "type": "object",
        "required": [
          "models"
        ],
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "Release status + field-validation benchmark + promotion verdict.",
              "properties": {
                "model": {
                  "type": "string"
                },
                "released": {
                  "type": "boolean"
                },
                "gates": {
                  "type": "string",
                  "example": "7/8"
                },
                "blocking": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "verdict": {
                  "type": "string",
                  "example": "field-validated; awaiting sign-off"
                },
                "benchmark": {
                  "type": "object",
                  "nullable": true,
                  "additionalProperties": true,
                  "description": "n, rmse, r2, p90 interval coverage, source, timestamp"
                }
              }
            }
          }
        }
      },
      "RetrainResult": {
        "type": "object",
        "required": [
          "triggered"
        ],
        "properties": {
          "triggered": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "description": "Why it was skipped, when not triggered."
          },
          "remaining": {
            "type": "integer",
            "description": "Outcomes still needed to reach the threshold."
          },
          "run": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The recorded training run: id, training_version, n_observations, fitted."
          }
        }
      },
      "ImpactMetrics": {
        "type": "object",
        "description": "Dashboard rollup of the observability streams (audit log + outcomes) + governance.",
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "served": {
            "type": "object",
            "additionalProperties": true,
            "description": "recommendations served, by crop / model / country"
          },
          "economics": {
            "type": "object",
            "additionalProperties": true
          },
          "outcomes": {
            "type": "object",
            "additionalProperties": true,
            "description": "captured, adoption rate, mean realised yield"
          },
          "models": {
            "type": "object",
            "additionalProperties": true,
            "description": "registered / released counts + benchmark performance"
          },
          "retrain": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ProcessorList": {
        "type": "object",
        "required": [
          "processors"
        ],
        "properties": {
          "processors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "example": "soil"
                },
                "label": {
                  "type": "string"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "bespoke": {
                  "type": "boolean",
                  "description": "Has a dedicated processor (e.g. outcome)"
                }
              }
            }
          }
        }
      },
      "IngestRecord": {
        "type": "object",
        "required": [
          "type"
        ],
        "description": "A record for one data-type processor. Fields go in `record`, or inline alongside `type`.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "soil",
              "weather",
              "trial",
              "lab",
              "survey",
              "farmer_input",
              "outcome"
            ]
          },
          "record": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "IngestAck": {
        "type": "object",
        "required": [
          "id",
          "type",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "stored",
              "validated"
            ]
          },
          "quality_flags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SignOff": {
        "type": "object",
        "required": [
          "model"
        ],
        "description": "An agronomist's governance decision, bound to the current model version. Provide reviewer{} (or a flat `by`).",
        "properties": {
          "model": {
            "type": "string",
            "example": "agwise"
          },
          "reviewer": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "affiliation": {
                "type": "string"
              },
              "role": {
                "type": "string"
              },
              "id": {
                "type": "string",
                "description": "ORCID / email / staff id"
              }
            }
          },
          "by": {
            "type": "string",
            "description": "Reviewer name (shorthand for reviewer.name)."
          },
          "decision": {
            "type": "string",
            "enum": [
              "approved",
              "approved_with_conditions",
              "rejected"
            ],
            "default": "approved"
          },
          "sample_size": {
            "type": "integer",
            "description": "How many recommendations were reviewed."
          },
          "crops": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scope: crops the sign-off covers."
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scope: countries the sign-off covers."
          },
          "criteria": {
            "type": "string",
            "description": "What was checked."
          },
          "method": {
            "type": "string",
            "description": "How the sample was reviewed."
          },
          "conditions": {
            "type": "string",
            "description": "Caveats when approved_with_conditions."
          },
          "rationale": {
            "type": "string",
            "description": "Reasoning for the decision."
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "Expiry; defaults to AKIAPI_SIGNOFF_VALIDITY_DAYS from now."
          }
        }
      },
      "SignOffAck": {
        "type": "object",
        "properties": {
          "recorded": {
            "type": "object",
            "additionalProperties": true,
            "description": "signoff_id, model_version the decision is bound to, decision, valid_until"
          },
          "current": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The now-effective sign-off for the current model version (or a 'stale' marker)."
          },
          "released": {
            "type": "boolean"
          },
          "gates": {
            "type": "string",
            "example": "8/8"
          },
          "blocking": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "NutrientBalance": {
        "type": "object",
        "description": "Process-based (QUEFTS) nutrient-balance cross-check on the recommended rates.",
        "properties": {
          "checked": {
            "type": "boolean"
          },
          "method": {
            "type": "string"
          },
          "balanced": {
            "type": "boolean"
          },
          "per_nutrient": {
            "type": "object",
            "additionalProperties": true,
            "description": "N/P/K each: consistent | over_balance | under_balance"
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "V1Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "unprocessable"
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string"
              }
            }
          }
        }
      },
      "ComputeSuccess": {
        "type": "object",
        "required": [
          "status",
          "recommendation",
          "message",
          "data"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ]
          },
          "recommendation": {
            "$ref": "#/components/schemas/Recommendation"
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error",
          "data"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "request_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "user_error",
                  "internal_error",
                  "bad_request"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            }
          },
          "data": {
            "type": "object",
            "nullable": true
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Partner API key (D5 §6). Required on /recommendation, /explanation, /compute, /soil, /outcomes(+/summary), /retrain (admin) when AKIAPI_AUTH is enabled."
      }
    }
  }
}