{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:multi-building-flags:v0",
  "title": "Craton multi-building check answer, v0",
  "description": "The body of POST /multi-building-flags: for each row of schedule-of-values text, whether it describes more than one separate building, so a person can split it before modeling. A model (TypeSafe's Jev, a decision model that answers closed questions only) gives the probability that the row covers more than one building; Craton's deterministic code flags a row only when that probability clears the gate. A row that leans several buildings below the gate is below_gate, never flagged. Beside the flag, a bucket for how many buildings the row describes, as a hint for splitting. No member carries text a model wrote: numbers, enum members, and model and provider identifiers. Nothing is stored.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "run_id",
    "checked_at",
    "model",
    "question_set",
    "gate",
    "counts",
    "rows"
  ],
  "properties": {
    "schema_version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
    },
    "run_id": {
      "type": "string",
      "pattern": "^multi-building-flags-v0:[0-9a-f]{16}:[0-9a-f]{16}$",
      "description": "Identity of this run: the question set's digest, then a digest of the model, the providers that served it, the question set and every row's outcome including its probability. Two runs that decided anything differently never share a run id."
    },
    "checked_at": {
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$",
      "description": "When the rows were checked, UTC. The gateway reports no dated model version, so the date, the serving provider and each row's generation_id are the version evidence."
    },
    "model": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name",
        "served_by",
        "via"
      ],
      "properties": {
        "name": {
          "type": "string",
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}(/[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$",
          "description": "The model as the gateway reported it, for example typesafe-ai/jev."
        },
        "served_by": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
          },
          "description": "The providers the gateway says served this run's rows. Empty when it named none."
        },
        "via": {
          "type": "string",
          "enum": [
            "vercel-ai-gateway"
          ],
          "description": "How Craton reaches the model."
        }
      }
    },
    "question_set": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "version",
        "digest"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "version": {
          "type": "string",
          "minLength": 1
        },
        "digest": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$",
          "description": "SHA-256 of the question set file Craton asks every row, byte for byte. It is this route's own set, separate from exposure coding's. The question is Craton's, never the caller's."
        }
      }
    },
    "gate": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "several_buildings"
      ],
      "properties": {
        "several_buildings": {
          "type": "number",
          "minimum": 0.5,
          "maximum": 1,
          "description": "The lowest probability that flags a row, inclusive. Never below an even chance."
        }
      }
    },
    "counts": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "rows",
        "flagged",
        "below_gate",
        "not_flagged",
        "unanswered"
      ],
      "properties": {
        "rows": {
          "type": "integer",
          "minimum": 1
        },
        "flagged": {
          "type": "integer",
          "minimum": 0
        },
        "below_gate": {
          "type": "integer",
          "minimum": 0
        },
        "not_flagged": {
          "type": "integer",
          "minimum": 0
        },
        "unanswered": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "rows": {
      "type": "array",
      "minItems": 1,
      "maxItems": 50,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "row",
          "status",
          "several_buildings",
          "probability",
          "cleared",
          "buildings",
          "generation_id"
        ],
        "properties": {
          "row": {
            "type": "integer",
            "minimum": 1,
            "description": "The row's position in the request, from 1. The text is not echoed back."
          },
          "status": {
            "type": "string",
            "enum": [
              "flagged",
              "below_gate",
              "not_flagged",
              "unanswered"
            ],
            "description": "flagged: the probability cleared the gate; split the row. below_gate: at least an even chance, under the gate; a person should read it. not_flagged: under an even chance. unanswered: no usable answer came back; send the row again."
          },
          "several_buildings": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "The model's answer read at an even chance: true when the probability is at least 0.5. Null when unanswered. Whether the row is flagged is status and cleared, not this."
          },
          "probability": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1,
            "description": "The model's probability that the row covers more than one separate building. Null when unanswered."
          },
          "cleared": {
            "type": "boolean",
            "description": "True exactly when the probability is at or above gate.several_buildings, which is when status is flagged."
          },
          "buildings": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "one",
              "two_to_five",
              "six_to_twenty",
              "over_twenty",
              "not_stated",
              null
            ],
            "description": "How many separate buildings the model says the row describes, in buckets: one, two_to_five, six_to_twenty, over_twenty, or not_stated (several, number not given). A hint for splitting the row, never a count read from it, and never a flag by itself. Null when unanswered, or when the question set does not ask it."
          },
          "generation_id": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^gen_[A-Za-z0-9]{1,64}$",
            "description": "The AI gateway's id for the call that answered the row, or null."
          }
        }
      }
    }
  }
}
