{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "urn:cede:schema:accumulation:v0",
    "title": "CEDE accumulation rollup, v0",
    "description": "The body of POST /accumulation — what a named set of the account's own canonical objects holds, rolled up at portfolio level. SPEC.md section 3.2 makes accumulation part of Analyse and requires assumptions and data vintages to be exposed on every artifact, so both are first-class members here rather than prose. SPEC.md section 4 (P10) governs the shape: every number below is a sum, a count or a grid cell, and no individual location — its reference, its name, its address, its exact coordinates or its own insured value — appears anywhere in this document. The rollup is a measurement over exposure the caller already stored: it carries no loading, no premium and no statement about risk appetite.",
    "type": "object",
    "additionalProperties": false,
    "required": [
        "schema_version",
        "set",
        "totals",
        "by_peril",
        "by_region",
        "concentrations",
        "coverage",
        "assumptions",
        "data_vintages",
        "disclosure"
    ],
    "properties": {
        "schema_version": {
            "type": "string",
            "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
        },
        "set": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "object_count",
                "object_ids"
            ],
            "description": "The set the caller named, echoed as it was read, so a rollup is always attributable to the exact objects behind it.",
            "properties": {
                "object_count": {
                    "type": "integer",
                    "minimum": 1
                },
                "object_ids": {
                    "type": "array",
                    "minItems": 1,
                    "uniqueItems": true,
                    "items": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        },
        "totals": {
            "$ref": "#/$defs/rollup"
        },
        "by_peril": {
            "type": "array",
            "minItems": 1,
            "description": "One row per peril declared across the set, plus at most one row for the objects that declare none. The rows add up to `totals`: a row missing from the table would leave a reader guessing where the difference went.",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "declared",
                    "objects",
                    "locations_counted",
                    "exposure"
                ],
                "properties": {
                    "declared": {
                        "type": "boolean",
                        "description": "False on the row holding objects that declare no peril. `peril` is present exactly when this is true."
                    },
                    "peril": {
                        "type": "string",
                        "minLength": 1,
                        "description": "A peril code from the risk object schema's peril vocabulary, as the object declared it."
                    },
                    "objects": {
                        "$ref": "#/$defs/count"
                    },
                    "locations_counted": {
                        "$ref": "#/$defs/count"
                    },
                    "exposure": {
                        "$ref": "#/$defs/amounts"
                    }
                }
            }
        },
        "by_region": {
            "type": "array",
            "minItems": 1,
            "description": "One row per set of territories declared across the objects, plus at most one row for the objects that declare none. Regions are what the objects say they are: nothing is inferred from a coordinate, because that would be reverse geocoding, and enrichment is outside this platform (SPEC.md section 3.1).",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "declared",
                    "objects",
                    "locations_counted",
                    "exposure"
                ],
                "properties": {
                    "declared": {
                        "type": "boolean",
                        "description": "False on the row holding objects that declare no territory. `territories` and `basis` are present exactly when this is true."
                    },
                    "territories": {
                        "type": "array",
                        "minItems": 1,
                        "uniqueItems": true,
                        "description": "The territory codes the objects in this row declare. An object declaring several is counted once, under the set it declares, rather than split between them.",
                        "items": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    "basis": {
                        "type": "string",
                        "minLength": 1,
                        "description": "Which block of the object declared these territories: peril.region.countries, or jurisdiction.territories."
                    },
                    "objects": {
                        "$ref": "#/$defs/count"
                    },
                    "locations_counted": {
                        "$ref": "#/$defs/count"
                    },
                    "exposure": {
                        "$ref": "#/$defs/amounts"
                    }
                }
            }
        },
        "concentrations": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "basis",
                "cell_size_degrees",
                "limit",
                "placed_locations",
                "unplaced_locations",
                "groups"
            ],
            "description": "Where the value sits, as ranked square cells of the globe. A cell is an aggregate over the locations inside it and never a location: it carries a count and a sum, never an identity, an address or a coordinate of anything in it (SPEC.md section 4, P10).",
            "properties": {
                "basis": {
                    "const": "grid_cell"
                },
                "cell_size_degrees": {
                    "type": "number",
                    "exclusiveMinimum": 0
                },
                "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "How many cells each group reports. A ranked list is a summary; the whole grid would be the schedule again in another shape."
                },
                "placed_locations": {
                    "$ref": "#/$defs/count"
                },
                "unplaced_locations": {
                    "$ref": "#/$defs/count",
                    "description": "Locations carrying values but no coordinates, which therefore fall in no cell. Disclosed rather than dropped silently."
                },
                "groups": {
                    "type": "array",
                    "description": "One group per currency, because ranking across currencies would need a conversion this platform does not invent. The group with no `currency` holds untagged amounts (the source file stated no currency anywhere).",
                    "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                            "cells_in_group",
                            "total",
                            "cells"
                        ],
                        "properties": {
                            "currency": {
                                "type": "string",
                                "pattern": "^[A-Z]{3}$"
                            },
                            "cells_in_group": {
                                "$ref": "#/$defs/count"
                            },
                            "total": {
                                "type": "number",
                                "minimum": 0,
                                "description": "Sum over every cell in this group, which is the placed exposure in this currency."
                            },
                            "cells": {
                                "type": "array",
                                "items": {
                                    "$ref": "#/$defs/cell"
                                }
                            }
                        }
                    }
                }
            }
        },
        "coverage": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "objects_counted_from_locations",
                "objects_counted_from_exposure_totals",
                "objects_with_no_values_at_all",
                "objects_with_no_declared_peril",
                "objects_with_no_declared_region",
                "locations_in_the_set",
                "locations_carrying_no_values"
            ],
            "description": "What the sums did and did not see. A rollup whose gaps live in the documentation is a rollup that will be read without them.",
            "properties": {
                "objects_counted_from_locations": {
                    "$ref": "#/$defs/count",
                    "description": "Objects summed from their locations' own values."
                },
                "objects_counted_from_exposure_totals": {
                    "$ref": "#/$defs/count",
                    "description": "Objects whose locations carry no values, summed from the exposure-level totals instead. Never both: adding a schedule to its own total would double it."
                },
                "objects_with_no_values_at_all": {
                    "$ref": "#/$defs/count"
                },
                "objects_with_no_declared_peril": {
                    "$ref": "#/$defs/count"
                },
                "objects_with_no_declared_region": {
                    "$ref": "#/$defs/count"
                },
                "locations_in_the_set": {
                    "$ref": "#/$defs/count"
                },
                "locations_carrying_no_values": {
                    "$ref": "#/$defs/count"
                }
            }
        },
        "assumptions": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "method",
                "currency_basis",
                "limitations"
            ],
            "description": "Everything the numbers rest on, stated in the response rather than in a footnote (SPEC.md section 3.2).",
            "properties": {
                "method": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                        "name",
                        "description",
                        "cell_size_degrees",
                        "coverage_parts"
                    ],
                    "properties": {
                        "name": {
                            "type": "string",
                            "minLength": 1
                        },
                        "description": {
                            "type": "string",
                            "minLength": 1
                        },
                        "cell_size_degrees": {
                            "type": "number",
                            "exclusiveMinimum": 0
                        },
                        "coverage_parts": {
                            "type": "array",
                            "minItems": 1,
                            "description": "The coverage parts summed, in the order they are read off each exposure.",
                            "items": {
                                "type": "string",
                                "minLength": 1
                            }
                        }
                    }
                },
                "currency_basis": {
                    "type": "string",
                    "minLength": 1,
                    "description": "How currency is handled. No conversion is performed anywhere in this document."
                },
                "limitations": {
                    "type": "array",
                    "minItems": 1,
                    "description": "What this rollup cannot tell you, in the response itself.",
                    "items": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        },
        "data_vintages": {
            "type": "array",
            "description": "One entry per peril declared in the set: the pinned snapshot this build would measure that peril against, named with its vintage and the digest of its exact bytes. The sums in this document are arithmetic over stored exposure and read no feed; these are the vintages a hazard question about the same set would be answered from, and a peril nothing is pinned for says so rather than being omitted. Empty only when no object in the set declares a peril.",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "peril",
                    "pinned"
                ],
                "properties": {
                    "peril": {
                        "type": "string",
                        "minLength": 1
                    },
                    "pinned": {
                        "type": "boolean",
                        "description": "True when this build holds a snapshot that speaks to the peril. `source` and `links` are present exactly when this is true; `note` exactly when it is false."
                    },
                    "source": {
                        "$ref": "#/$defs/data_source"
                    },
                    "links": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                            "feed",
                            "data"
                        ],
                        "description": "API paths, relative to the deployment's base URL: the snapshot's manifest, and its bytes.",
                        "properties": {
                            "feed": {
                                "type": "string",
                                "minLength": 1
                            },
                            "data": {
                                "type": "string",
                                "minLength": 1
                            }
                        }
                    },
                    "note": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        },
        "disclosure": {
            "type": "string",
            "minLength": 1,
            "description": "The fixed sentence stating what this document is and is not (SPEC.md section 4). A rollup of stored exposure, never a view of risk and never a price."
        }
    },
    "$defs": {
        "count": {
            "type": "integer",
            "minimum": 0
        },
        "rollup": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "objects",
                "locations_counted",
                "exposure"
            ],
            "description": "One rollup: how many objects and locations were counted into it, and how much they hold in each currency.",
            "properties": {
                "objects": {
                    "$ref": "#/$defs/count"
                },
                "locations_counted": {
                    "$ref": "#/$defs/count",
                    "description": "Locations whose own values were summed into this rollup. Zero for an object counted from its exposure-level totals."
                },
                "exposure": {
                    "$ref": "#/$defs/amounts"
                }
            }
        },
        "amounts": {
            "type": "array",
            "description": "Sums held per currency and never added across them: a set holding two currencies has two sums and no single total. At most one entry carries no `currency` — the untagged amounts, whose source file stated no currency anywhere (adr/ADR-0029-untagged-insured-values.md).",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "amount"
                ],
                "properties": {
                    "currency": {
                        "type": "string",
                        "pattern": "^[A-Z]{3}$"
                    },
                    "amount": {
                        "type": "number",
                        "minimum": 0
                    }
                }
            }
        },
        "cell": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "min_latitude",
                "max_latitude",
                "min_longitude",
                "max_longitude",
                "locations",
                "objects",
                "amount",
                "share_of_group"
            ],
            "description": "One square of the grid: the box, how many locations and objects stand behind it, and how much value sits in it. Deliberately the least resolution that can answer 'where is the value concentrated': the box is the cell, never the extent of anything inside it.",
            "properties": {
                "min_latitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                },
                "max_latitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                },
                "min_longitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                },
                "max_longitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                },
                "locations": {
                    "$ref": "#/$defs/count"
                },
                "objects": {
                    "$ref": "#/$defs/count"
                },
                "amount": {
                    "type": "number",
                    "minimum": 0
                },
                "share_of_group": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "This cell's share of the group's placed exposure, in that group's own currency."
                }
            }
        },
        "data_source": {
            "type": "object",
            "required": [
                "id",
                "version",
                "vintage",
                "sha256"
            ],
            "description": "The 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
                }
            }
        }
    }
}
