{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:analysis:v0",
  "title": "CEDE analyse run, v0",
  "description": "The result of POST /objects/{id}/analyse — the named analyses SPEC.md section 3.2 runs over one canonical risk object, and the artifacts they produced. Section 3.2 requires assumptions and data vintages to be exposed on every artifact, so assumptions and data_vintages are required members of every artifact here rather than prose around it: an artifact that cannot say what it assumed, or which pinned bytes it read, is not a publishable analysis. Each artifact is a reading of a fixed public record. It carries no loading, no premium and no statement about risk appetite, and nothing in this document asserts that anything was transacted.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "run_id",
    "object_id",
    "analyses",
    "completed_at",
    "artifacts",
    "disclosure"
  ],
  "properties": {
    "schema_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
    "run_id": {
      "type": "string",
      "minLength": 1,
      "description": "Identity of this run. The same id is written onto the analysed object's provenance, which is how an artifact read here and the record on the object are known to be the same run."
    },
    "object_id": {
      "type": "string",
      "minLength": 1,
      "description": "The canonical risk object these analyses were run over."
    },
    "analyses": {
      "type": "array",
      "minItems": 1,
      "description": "The analyses this run ran, in the order their artifacts appear. Named by the caller; an unknown name is refused before the run exists, with the valid set in the message.",
      "items": { "type": "string", "minLength": 1 }
    },
    "completed_at": { "$ref": "#/$defs/timestamp" },
    "artifacts": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/$defs/artifact" }
    },
    "disclosure": {
      "type": "string",
      "minLength": 1,
      "description": "What this run is and is not, in the response rather than in the docs: a measurement over pinned public records, never a view of risk and never a price."
    }
  },
  "$defs": {
    "timestamp": {
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$",
      "description": "RFC 3339 date-time with an explicit UTC offset, as on the risk object."
    },
    "data_source": {
      "type": "object",
      "required": ["id", "version", "vintage", "sha256"],
      "description": "A pinned snapshot, in the same shape a technical price, a backtest and a hazard lookup name it in their assumption sets, so one reader's lineage check works on all of them.",
      "properties": {
        "id": { "type": "string", "minLength": 1 },
        "name": { "type": "string", "minLength": 1 },
        "publisher": { "type": "string", "minLength": 1 },
        "kind": { "type": "string", "minLength": 1 },
        "version": { "type": "string", "minLength": 1 },
        "vintage": { "type": "string", "minLength": 1 },
        "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
        "licence": { "type": "string", "minLength": 1 },
        "query_url": { "type": "string", "minLength": 1 },
        "record_count": { "type": "integer", "minimum": 0 }
      }
    },
    "assumptions": {
      "type": "object",
      "additionalProperties": false,
      "required": ["method"],
      "description": "How the artifact's numbers were arrived at, in words a reader can check the arithmetic against. Required on every artifact (SPEC.md section 3.2).",
      "properties": {
        "method": { "type": "string", "minLength": 1 },
        "notes": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 }
        }
      }
    },
    "location_identity": {
      "description": "The source file's own words for a location, when it carried any. Never invented by Cede.",
      "type": "object",
      "properties": {
        "ref": { "type": "string", "minLength": 1 },
        "name": { "type": "string", "minLength": 1 }
      }
    },
    "artifact": {
      "description": "One named analysis's result. Every artifact carries its analysis name, its assumptions, the vintages of every pinned snapshot it read, and what it is not.",
      "oneOf": [
        { "$ref": "#/$defs/point_hazard_artifact" },
        { "$ref": "#/$defs/event_intersection_artifact" }
      ]
    },
    "point_hazard_artifact": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "analysis",
        "peril",
        "summary",
        "counts",
        "locations",
        "not_analysed",
        "assumptions",
        "data_vintages",
        "limitations"
      ],
      "description": "The pinned record read at each of the object's own coordinates — the document GET /hazard serves, once per location.",
      "properties": {
        "analysis": { "const": "point_hazard" },
        "peril": { "type": "string", "minLength": 1 },
        "summary": { "type": "string", "minLength": 1 },
        "counts": {
          "type": "object",
          "additionalProperties": false,
          "required": ["locations_analysed", "locations_not_analysed"],
          "properties": {
            "locations_analysed": { "type": "integer", "minimum": 1 },
            "locations_not_analysed": { "type": "integer", "minimum": 0 }
          }
        },
        "locations": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["location_index", "point", "hazard"],
            "properties": {
              "location_index": {
                "type": "integer",
                "minimum": 0,
                "description": "Position of this location in the object's exposure.locations, so a reader can put the reading back where it came from."
              },
              "ref": { "$ref": "#/$defs/location_identity/properties/ref" },
              "name": { "$ref": "#/$defs/location_identity/properties/name" },
              "point": {
                "type": "object",
                "additionalProperties": false,
                "required": ["latitude", "longitude"],
                "properties": {
                  "latitude": { "type": "number", "minimum": -90, "maximum": 90 },
                  "longitude": { "type": "number", "minimum": -180, "maximum": 180 }
                }
              },
              "hazard": {
                "type": "object",
                "description": "A point hazard lookup document. Validated in full against urn:cede:schema:hazard:v0 by the contract oracle; restated loosely here so that the two schemas cannot drift into disagreeing about the same bytes."
              }
            }
          }
        },
        "not_analysed": {
          "type": "array",
          "description": "Locations that carried coordinates no pinned snapshot covers, each with the reason. Present and empty rather than absent: an exposure half of which was read must never read as an exposure that was read.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["location_index", "reason"],
            "properties": {
              "location_index": { "type": "integer", "minimum": 0 },
              "ref": { "$ref": "#/$defs/location_identity/properties/ref" },
              "name": { "$ref": "#/$defs/location_identity/properties/name" },
              "reason": { "type": "string", "minLength": 1 }
            }
          }
        },
        "assumptions": { "$ref": "#/$defs/assumptions" },
        "data_vintages": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/data_source" }
        },
        "limitations": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 }
        }
      }
    },
    "event_intersection_artifact": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "analysis",
        "peril",
        "summary",
        "box",
        "counts",
        "catalogue",
        "assumptions",
        "data_vintages",
        "limitations"
      ],
      "description": "The pinned catalogue events that fall in the box the object's exposure spans — the document GET /events/{peril} serves, selected by the same rules a backtest selects with.",
      "properties": {
        "analysis": { "const": "event_intersection" },
        "peril": { "type": "string", "minLength": 1 },
        "summary": { "type": "string", "minLength": 1 },
        "box": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "min_latitude",
            "max_latitude",
            "min_longitude",
            "max_longitude",
            "bounds",
            "cell_half_width_degrees",
            "locations_spanned"
          ],
          "properties": {
            "min_latitude": { "type": "number" },
            "max_latitude": { "type": "number" },
            "min_longitude": { "type": "number" },
            "max_longitude": { "type": "number" },
            "bounds": { "type": "string", "minLength": 1 },
            "cell_half_width_degrees": {
              "type": "number",
              "exclusiveMinimum": 0,
              "description": "How far the exposure's own extent was grown to make this box, in degrees. The same half-cell a point lookup counts events in, so the two analyses of one run describe the same neighbourhood."
            },
            "locations_spanned": { "type": "integer", "minimum": 1 }
          }
        },
        "counts": {
          "type": "object",
          "additionalProperties": false,
          "required": ["events_in_snapshot", "intersecting", "returned"],
          "properties": {
            "events_in_snapshot": { "type": "integer", "minimum": 0 },
            "intersecting": { "type": "integer", "minimum": 0 },
            "returned": {
              "type": "integer",
              "minimum": 0,
              "description": "How many of the intersecting events are embedded below. Fewer than intersecting means the page ended, never that the rest were dropped: GET /events/{peril} serves the remainder with the same selector."
            }
          }
        },
        "catalogue": {
          "type": "object",
          "description": "An event catalogue page. Validated in full against urn:cede:schema:event-catalogue:v0 by the contract oracle; restated loosely here for the same reason as the hazard document above."
        },
        "assumptions": { "$ref": "#/$defs/assumptions" },
        "data_vintages": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/data_source" }
        },
        "limitations": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 }
        }
      }
    }
  }
}
