{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://app.shockai.co/developers/schemas/v0/BlastCase.json",
  "title": "BlastCase",
  "type": "object",
  "properties": {
    "specVersion": {
      "type": "string",
      "const": "0.1",
      "description": "Version of the ShockAI Model API specification this document follows."
    },
    "caseId": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$",
      "description": "SHA-256 of the canonical case (see Canonical JSON) without `caseId` and `createdAt`, and without the `uri` of any BlobRef (any object carrying `uri`, `sha256` and `mediaType`): its bytes are named by the hash, and a signed URL changes from run to run. Identical cases have identical ids, which is what makes results cacheable and reproducible."
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
      "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
    },
    "coordinateSystem": {
      "$ref": "#/$defs/CoordinateSystem"
    },
    "site": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "domains": {
          "maxItems": 100,
          "type": "array",
          "items": {
            "$ref": "#/$defs/RockDomain"
          }
        },
        "terrain": {
          "description": "Surface model, when the provider needs one.",
          "$ref": "#/$defs/BlobRef"
        }
      },
      "required": [
        "domains"
      ]
    },
    "design": {
      "$ref": "#/$defs/Design"
    },
    "asBuilt": {
      "description": "Present once the blast is drilled, loaded or fired. A pre-blast prediction leaves it out.",
      "type": "object",
      "properties": {
        "holes": {
          "maxItems": 5000,
          "type": "array",
          "items": {
            "$ref": "#/$defs/AsBuiltHole"
          }
        },
        "firing": {
          "$ref": "#/$defs/FiringRecord"
        }
      }
    },
    "receivers": {
      "maxItems": 1000,
      "type": "array",
      "items": {
        "$ref": "#/$defs/Receiver"
      }
    },
    "calibration": {
      "$ref": "#/$defs/Calibration"
    },
    "measured": {
      "description": "Present once the blast has fired and its records are in.",
      "$ref": "#/$defs/Measured"
    },
    "request": {
      "$ref": "#/$defs/OutputRequest"
    },
    "consent": {
      "$ref": "#/$defs/Consent"
    }
  },
  "required": [
    "specVersion",
    "caseId",
    "createdAt",
    "coordinateSystem",
    "site",
    "design",
    "receivers",
    "request",
    "consent"
  ],
  "description": "Everything a model may read about one blast: design, as-built record, site, receivers, calibration and the question asked.",
  "$defs": {
    "CoordinateSystem": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "kind": {
              "type": "string",
              "const": "epsg"
            },
            "code": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991,
              "description": "EPSG code of a projected system, for example 28350 (GDA94 / MGA zone 50)."
            }
          },
          "required": [
            "kind",
            "code"
          ]
        },
        {
          "type": "object",
          "properties": {
            "kind": {
              "type": "string",
              "const": "local"
            },
            "name": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "description": "The mine grid name."
            },
            "gridBearingDeg": {
              "description": "Bearing of grid north from true north, degrees clockwise.",
              "type": "number",
              "minimum": -360,
              "maximum": 360
            },
            "origin": {
              "description": "WGS 84 position of the local origin, when known.",
              "type": "object",
              "properties": {
                "latitude": {
                  "type": "number",
                  "minimum": -90,
                  "maximum": 90
                },
                "longitude": {
                  "type": "number",
                  "minimum": -180,
                  "maximum": 180
                }
              },
              "required": [
                "latitude",
                "longitude"
              ]
            }
          },
          "required": [
            "kind",
            "name"
          ]
        }
      ],
      "description": "The frame every position in the case is expressed in."
    },
    "RockDomain": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "rockClass": {
          "description": "Coarse class when nothing better is known.",
          "$ref": "#/$defs/RockClass"
        },
        "densityKgM3": {
          "description": "In-situ density, kg/m³.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "ucsMPa": {
          "description": "Uniaxial compressive strength, MPa.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "youngsModulusGPa": {
          "description": "Young's modulus, GPa.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "hoekBrownMi": {
          "description": "Hoek–Brown constant m_i of the intact rock: the m in σ1 = σ3 + σci·(m·σ3/σci + 1)^0.5, which with the UCS gives its tensile strength σci·(√(m² + 4) − m)/2, about UCS/m_i.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "poissonsRatio": {
          "description": "Poisson's ratio of the intact rock, between 0 and 0.5.",
          "type": "number",
          "exclusiveMinimum": 0,
          "exclusiveMaximum": 0.5
        },
        "strengthStage": {
          "description": "Which stage of failure the strength figures describe (Martin and Chandler 1994): crack initiation, the onset of strain localisation, or peak. A model judging damage reads the UCS and m_i as that stage's; peak when unstated.",
          "$ref": "#/$defs/StrengthStage"
        },
        "strainCriterion": {
          "description": "A strain criterion for this rock, usually regressed from its measured failure strains, which a model judging damage reads in place of the strength it derives from the figures.",
          "$ref": "#/$defs/StrainCriterion"
        },
        "pWaveVelocityMs": {
          "description": "P-wave velocity, m/s.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "rmr": {
          "description": "Rock mass rating (Bieniawski).",
          "type": "number",
          "minimum": 0,
          "maximum": 100
        },
        "gsi": {
          "description": "Geological strength index.",
          "type": "number",
          "minimum": 0,
          "maximum": 100
        },
        "kuzRamRockFactor": {
          "description": "Kuz-Ram rock factor A, when calibrated.",
          "type": "number",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "id"
      ],
      "description": "A zone of rock the site treats as one material. Holes name the domain they sit in."
    },
    "RockClass": {
      "type": "string",
      "enum": [
        "super_soft",
        "soft",
        "medium",
        "hard"
      ],
      "description": "Coarse rock class used when no measured properties are available."
    },
    "StrengthStage": {
      "type": "string",
      "enum": [
        "crack_initiation",
        "strain_localisation",
        "peak"
      ],
      "description": "A stage of the progressive failure of intact rock, from the first cracks to the peak load."
    },
    "StrainCriterion": {
      "type": "object",
      "properties": {
        "stage": {
          "description": "The stage of failure the criterion describes; peak when unstated.",
          "$ref": "#/$defs/StrengthStage"
        },
        "sigmaCiMPa": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "The σci the constants were fitted or derived with, MPa."
        },
        "a": {
          "type": "number",
          "description": "Per MPa, for strain as a fraction (not percent)."
        },
        "b": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Per MPa², for strain as a fraction. Must exceed c², or the curve does not enclose the unstrained state: a model judges such a criterion by the form it derives from the figures instead, and warns."
        },
        "c": {
          "type": "number",
          "description": "Per MPa, for strain as a fraction."
        },
        "lesserStrain": {
          "description": "The least compressive principal strain ε3 over the failures the fit was made from: compression positive, so the lateral extension of a triaxial test is negative. A model warns where it reads the curve outside it; an empty range covers nothing.",
          "$ref": "#/$defs/Range"
        },
        "greaterStrain": {
          "description": "The greatest compressive principal strain ε1 over the same failures. The curve has two limbs, and at a lesser strain inside its range the other limb was not fitted: with this, a model can tell which it read.",
          "$ref": "#/$defs/Range"
        },
        "basis": {
          "type": "string",
          "enum": [
            "regression",
            "derived"
          ],
          "description": "Whether the constants were regressed from measured failure strains (their eq. 14) or derived from E, μ and m (their eqs 5–7)."
        },
        "note": {
          "description": "Where the failure strains or figures came from, for a reader.",
          "type": "string",
          "maxLength": 500
        }
      },
      "required": [
        "sigmaCiMPa",
        "a",
        "b",
        "c",
        "basis"
      ],
      "description": "A strain-based Hoek–Brown criterion of the intact rock as Yang and Green (2025) write it, eq. 4: ε1 = ε3 ± (a·σci·ε3 + b·σci²)^0.5 + c·σci, strain a dimensionless fraction, compression positive. A model judging damage reads it on a state's greatest and least compressive principal strains, the middle one ignored, with no tension cutoff beyond the curve's own, in place of the strength it would derive from the domain's figures: so a criterion derived from the same E, μ and m need not answer as the figures do."
    },
    "Range": {
      "type": "object",
      "properties": {
        "min": {
          "type": "number"
        },
        "max": {
          "type": "number"
        }
      },
      "description": "An inclusive interval. Either bound may be omitted. A range whose `min` exceeds its `max` is empty: a model declared valid over one is valid nowhere, and no prediction falls inside a fitted range that is empty."
    },
    "BlobRef": {
      "type": "object",
      "properties": {
        "uri": {
          "type": "string",
          "format": "uri",
          "description": "A signed HTTPS URL. It expires; fetch it once when the run starts."
        },
        "sha256": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$",
          "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
        },
        "mediaType": {
          "type": "string",
          "minLength": 3,
          "maxLength": 120,
          "description": "IANA media type, for example `image/tiff` or `application/vnd.las`."
        },
        "bytes": {
          "description": "Size in bytes.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "uri",
        "sha256",
        "mediaType"
      ],
      "description": "A large payload passed by reference rather than inline: terrain, point clouds, long waveforms."
    },
    "Design": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "pattern": {
          "description": "Layout family.",
          "type": "string",
          "enum": [
            "square",
            "rectangular",
            "staggered",
            "ring",
            "development",
            "other"
          ]
        },
        "holes": {
          "maxItems": 5000,
          "type": "array",
          "items": {
            "$ref": "#/$defs/DesignHole"
          },
          "description": "Every hole, collar to toe. Empty when the platform holds only a summary of the design, as for a what-if question about a charge at a distance."
        },
        "products": {
          "maxItems": 50,
          "type": "array",
          "items": {
            "$ref": "#/$defs/Product"
          }
        },
        "initiation": {
          "description": "Omitted when the platform holds only a summary of the design.",
          "$ref": "#/$defs/Initiation"
        },
        "maxInstantaneousCharge": {
          "$ref": "#/$defs/MaxInstantaneousCharge"
        },
        "drillingDeviationM": {
          "description": "One standard deviation of collar and drilling error, m (Cunningham’s W): what fragmentation uniformity loses to imperfect drilling.",
          "type": "number",
          "minimum": 0
        }
      },
      "required": [
        "holes",
        "products"
      ],
      "description": "The blast as designed."
    },
    "DesignHole": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "row": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "column": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "collar": {
          "$ref": "#/$defs/Point3"
        },
        "toe": {
          "$ref": "#/$defs/Point3"
        },
        "diameterMm": {
          "description": "Hole diameter, mm. Left out when the plan does not state it.",
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 2000
        },
        "domainId": {
          "description": "The rock domain the hole sits in.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
        },
        "burdenM": {
          "description": "Burden this hole breaks, m.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "spacingM": {
          "description": "Spacing to its neighbour in the row, m.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "subdrillM": {
          "description": "Drilled length below grade, m.",
          "type": "number"
        },
        "decks": {
          "minItems": 1,
          "maxItems": 32,
          "type": "array",
          "items": {
            "$ref": "#/$defs/Deck"
          },
          "description": "The hole from collar to toe. Stemming is a deck like any other."
        },
        "delay": {
          "$ref": "#/$defs/HoleDelay"
        }
      },
      "required": [
        "id",
        "collar",
        "toe",
        "decks",
        "delay"
      ],
      "description": "A hole as designed."
    },
    "Point3": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number",
          "description": "Easting, metres."
        },
        "y": {
          "type": "number",
          "description": "Northing, metres."
        },
        "z": {
          "type": "number",
          "description": "Elevation, metres. Up is positive."
        }
      },
      "required": [
        "x",
        "y",
        "z"
      ],
      "description": "A position in the case coordinate system, metres."
    },
    "Deck": {
      "oneOf": [
        {
          "$ref": "#/$defs/ExplosiveDeck"
        },
        {
          "$ref": "#/$defs/UnchargedDeck"
        }
      ],
      "description": "One interval of a hole, collar downwards. Only an explosive deck carries a product and a mass."
    },
    "ExplosiveDeck": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "const": "explosive"
        },
        "topDepthM": {
          "type": "number",
          "minimum": 0,
          "description": "Depth of the top of the deck along the hole, from the collar."
        },
        "bottomDepthM": {
          "type": "number",
          "minimum": 0,
          "description": "Depth of the bottom of the deck along the hole."
        },
        "productId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "The product loaded."
        },
        "massKg": {
          "type": "number",
          "minimum": 0,
          "description": "Charge mass."
        }
      },
      "required": [
        "kind",
        "topDepthM",
        "bottomDepthM",
        "productId",
        "massKg"
      ],
      "description": "An interval of a hole loaded with explosive."
    },
    "UnchargedDeck": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "stemming",
            "air",
            "water",
            "inert"
          ],
          "description": "What fills this interval of the hole."
        },
        "topDepthM": {
          "type": "number",
          "minimum": 0,
          "description": "Depth of the top of the deck along the hole, from the collar."
        },
        "bottomDepthM": {
          "type": "number",
          "minimum": 0,
          "description": "Depth of the bottom of the deck along the hole."
        }
      },
      "required": [
        "kind",
        "topDepthM",
        "bottomDepthM"
      ],
      "description": "An interval of a hole with no explosive in it."
    },
    "HoleDelay": {
      "type": "object",
      "properties": {
        "totalMs": {
          "type": "number",
          "description": "Designed firing time from T0, milliseconds."
        },
        "surfaceMs": {
          "description": "Delay carried by the surface connector.",
          "type": "number",
          "minimum": 0
        },
        "downholeMs": {
          "description": "Delay of the in-hole unit.",
          "type": "number",
          "minimum": 0
        },
        "tieInFrom": {
          "description": "The hole this one receives its signal from on the surface tie-in.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
        }
      },
      "required": [
        "totalMs"
      ],
      "description": "When a hole is designed to fire."
    },
    "Product": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "class": {
          "type": "string",
          "enum": [
            "anfo",
            "heavy_anfo",
            "emulsion",
            "watergel",
            "packaged",
            "booster",
            "other"
          ],
          "description": "Product family."
        },
        "densityGcc": {
          "description": "In-hole density, g/cm³.",
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 3
        },
        "relativeWeightStrength": {
          "description": "Relative weight strength, ANFO = 100.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "energyMjKg": {
          "description": "Rated energy, MJ/kg.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "nominalVodMs": {
          "description": "Nominal velocity of detonation, m/s.",
          "type": "number",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "id",
        "name",
        "class"
      ],
      "description": "An explosive product as loaded."
    },
    "Initiation": {
      "type": "object",
      "properties": {
        "system": {
          "$ref": "#/$defs/InitiationSystem"
        },
        "delayScatter": {
          "description": "One standard deviation of detonator timing error: a fixed part plus a percentage of the nominal delay.",
          "type": "object",
          "properties": {
            "fixedMs": {
              "type": "number",
              "minimum": 0
            },
            "percent": {
              "type": "number",
              "minimum": 0
            }
          }
        }
      },
      "required": [
        "system"
      ],
      "description": "How the blast is initiated."
    },
    "InitiationSystem": {
      "type": "string",
      "enum": [
        "electronic",
        "nonel",
        "electric",
        "detonating_cord"
      ],
      "description": "The detonator technology. It sets the delay scatter a model should assume when none is given."
    },
    "MaxInstantaneousCharge": {
      "type": "object",
      "properties": {
        "massKg": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "The largest charge detonating inside any one window, kg."
        },
        "windowMs": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "The window, ms. Jurisdictions set it; 8 ms is common."
        }
      },
      "required": [
        "massKg",
        "windowMs"
      ],
      "description": "The maximum instantaneous charge (MIC) as the platform computed it for compliance. A model that works from the charge rather than from every hole should use it; one that models the holes may recompute it."
    },
    "AsBuiltHole": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "The design hole this record amends."
        },
        "collar": {
          "$ref": "#/$defs/Point3"
        },
        "toe": {
          "$ref": "#/$defs/Point3"
        },
        "waterDepthM": {
          "description": "Standing water measured at loading, m.",
          "type": "number",
          "minimum": 0
        },
        "decks": {
          "description": "The hole as loaded, when it differs from the design.",
          "minItems": 1,
          "maxItems": 32,
          "type": "array",
          "items": {
            "$ref": "#/$defs/Deck"
          }
        }
      },
      "required": [
        "id"
      ],
      "description": "A hole as drilled and loaded. Fields left out are as designed."
    },
    "FiringRecord": {
      "type": "object",
      "properties": {
        "source": {
          "type": "string",
          "enum": [
            "det_log",
            "timing_import",
            "design"
          ],
          "description": "Where the firing times come from: a detonator log, an imported timing file, or the design itself."
        },
        "firedAt": {
          "description": "Wall-clock time of T0.",
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
        },
        "holes": {
          "maxItems": 5000,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
              },
              "firedMs": {
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Measured firing time from T0, ms; null when the hole did not fire."
              },
              "status": {
                "type": "string",
                "enum": [
                  "fired",
                  "misfire",
                  "cutoff",
                  "unknown"
                ]
              }
            },
            "required": [
              "id",
              "firedMs",
              "status"
            ]
          }
        }
      },
      "required": [
        "source",
        "holes"
      ],
      "description": "What actually fired, and when."
    },
    "Receiver": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "kind": {
          "type": "string",
          "enum": [
            "monitor",
            "structure",
            "boundary",
            "point"
          ],
          "description": "A seismograph location, a structure to protect, a lease or property boundary point, or any point of interest."
        },
        "position": {
          "description": "Where the receiver is. Omitted when only its distance is known. A monitor placed on a plan often has no surveyed height: its `z` is then left out.",
          "$ref": "#/$defs/SurfacePosition"
        },
        "distanceM": {
          "description": "The source-to-receiver distance the platform resolved: a surveyed or entered distance, or the slant distance to the nearest charge. A model should use it unless it models the geometry itself.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "distanceSource": {
          "description": "Where `distanceM` came from, for example `manual`, `placement` or `georeference`.",
          "type": "string",
          "minLength": 1,
          "maxLength": 40
        },
        "limits": {
          "description": "The limits that apply here: a model reports exceedance probability against them, and a charge limit is held to the PPV limit.",
          "type": "object",
          "properties": {
            "ppvMmS": {
              "type": "number",
              "exclusiveMinimum": 0
            },
            "airblastDbL": {
              "type": "number",
              "exclusiveMinimum": 0
            },
            "standard": {
              "description": "The standard the limit comes from, for example `AS 2187.2`.",
              "type": "string",
              "maxLength": 120
            }
          }
        }
      },
      "required": [
        "id",
        "kind"
      ],
      "description": "A place a prediction is wanted. It has a position, a resolved distance, or both.",
      "anyOf": [
        {
          "required": [
            "position"
          ]
        },
        {
          "required": [
            "distanceM"
          ]
        }
      ]
    },
    "SurfacePosition": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number",
          "description": "Easting, metres."
        },
        "y": {
          "type": "number",
          "description": "Northing, metres."
        },
        "z": {
          "description": "Elevation, metres. Up is positive. Left out where the height was never surveyed: unknown, not zero.",
          "type": "number"
        }
      },
      "required": [
        "x",
        "y"
      ],
      "description": "A place on the ground in the case coordinate system, metres, whose elevation may not be known."
    },
    "Calibration": {
      "type": "object",
      "properties": {
        "siteLaw": {
          "$ref": "#/$defs/SiteLaw"
        },
        "airblastLaw": {
          "$ref": "#/$defs/AirblastLaw"
        },
        "seedWaveforms": {
          "maxItems": 500,
          "type": "array",
          "items": {
            "$ref": "#/$defs/SeedWaveform"
          }
        },
        "vod": {
          "maxItems": 5000,
          "type": "array",
          "items": {
            "$ref": "#/$defs/VodMeasurement"
          }
        },
        "outcomes": {
          "maxItems": 10000,
          "type": "array",
          "items": {
            "$ref": "#/$defs/MeasuredOutcome"
          }
        },
        "strainAmplitude": {
          "$ref": "#/$defs/StrainAmplitude"
        }
      },
      "description": "What the site has measured. This is what lets a kinematic model be calibrated rather than assumed."
    },
    "SiteLaw": {
      "type": "object",
      "properties": {
        "metric": {
          "type": "string",
          "const": "ppv"
        },
        "basis": {
          "$ref": "#/$defs/PpvBasis"
        },
        "scaling": {
          "$ref": "#/$defs/Scaling"
        },
        "k": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Site constant K in PPV = K·SD^−β, mm/s."
        },
        "beta": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Attenuation exponent β."
        },
        "sigmaLn": {
          "description": "Residual standard deviation in natural-log units.",
          "type": "number",
          "minimum": 0
        },
        "n": {
          "description": "Readings in the fit. Fractional when a regional prior lends the fit its weight in readings.",
          "type": "number",
          "minimum": 0
        },
        "scaledDistance": {
          "description": "The scaled-distance range the fit was made over.",
          "$ref": "#/$defs/Range"
        },
        "fit": {
          "description": "What a prediction interval needs beyond σ and n.",
          "$ref": "#/$defs/LawFitStatistics"
        },
        "frequencyBins": {
          "description": "Dominant frequency by scaled distance, from the site’s own readings.",
          "maxItems": 60,
          "type": "array",
          "items": {
            "$ref": "#/$defs/FrequencyBin"
          }
        },
        "source": {
          "type": "string",
          "enum": [
            "platform_fit",
            "user",
            "prior"
          ],
          "description": "A law fitted by the platform from this site’s readings, one entered by the user, or a regional prior."
        }
      },
      "required": [
        "metric",
        "basis",
        "scaling",
        "k",
        "beta",
        "source"
      ],
      "description": "A fitted scaled-distance attenuation law."
    },
    "PpvBasis": {
      "type": "string",
      "enum": [
        "max_axis",
        "pvs"
      ],
      "description": "How peak particle velocity is taken: the largest single component (`max_axis`) or the peak vector sum (`pvs`)."
    },
    "Scaling": {
      "type": "string",
      "enum": [
        "square_root",
        "cube_root"
      ],
      "description": "Charge scaling for scaled distance: D/√Q for ground vibration, D/∛Q for airblast and near-field work."
    },
    "LawFitStatistics": {
      "type": "object",
      "properties": {
        "dof": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Residual degrees of freedom of the fit."
        },
        "meanLnScaledDistance": {
          "type": "number",
          "description": "Mean of ln(scaled distance) over the readings."
        },
        "sxx": {
          "anyOf": [
            {
              "type": "number",
              "exclusiveMinimum": 0
            },
            {
              "type": "null"
            }
          ],
          "description": "Σ(ln SD − mean)² over the readings. Null when only the law’s summary is known; intervals then leave out the leverage term."
        }
      },
      "required": [
        "dof",
        "meanLnScaledDistance",
        "sxx"
      ],
      "description": "The least-squares statistics of a log-log fit, for prediction intervals: ln y ± t(p, dof)·σ·√(1 + 1/n + (ln SD − mean)²/sxx)."
    },
    "FrequencyBin": {
      "type": "object",
      "properties": {
        "log10SdMin": {
          "type": "number"
        },
        "log10SdMax": {
          "type": "number"
        },
        "medianHz": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Median dominant frequency of the readings in the band, Hz."
        },
        "count": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "log10SdMin",
        "log10SdMax",
        "medianHz",
        "count"
      ],
      "description": "One band of log10(scaled distance) and the median dominant frequency the site recorded in it."
    },
    "AirblastLaw": {
      "type": "object",
      "properties": {
        "scaling": {
          "type": "string",
          "const": "cube_root"
        },
        "kPa": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Site constant in P = K·SD^−β, pascals."
        },
        "beta": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Attenuation exponent β."
        },
        "sigmaLn": {
          "type": "number",
          "minimum": 0,
          "description": "Residual standard deviation in ln(Pa)."
        },
        "n": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Readings in the fit."
        },
        "fit": {
          "$ref": "#/$defs/LawFitStatistics"
        },
        "scaledDistance": {
          "description": "The scaled-distance range the fit was made over.",
          "$ref": "#/$defs/Range"
        },
        "source": {
          "type": "string",
          "enum": [
            "platform_fit",
            "user",
            "prior"
          ]
        }
      },
      "required": [
        "scaling",
        "kPa",
        "beta",
        "sigmaLn",
        "n",
        "fit",
        "scaledDistance",
        "source"
      ],
      "description": "A fitted airblast attenuation law on cube-root scaled distance."
    },
    "SeedWaveform": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "holeId": {
          "description": "The single hole that produced it.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
        },
        "receiverId": {
          "description": "Where it was recorded.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
        },
        "distanceM": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Charge-to-sensor distance, m."
        },
        "chargeKg": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Charge mass of the seed hole, kg."
        },
        "sampleRateHz": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "frame": {
          "type": "string",
          "enum": [
            "xyz",
            "tvl"
          ],
          "description": "Component frame: instrument axes, or transverse / vertical / longitudinal to the source."
        },
        "samples": {
          "$ref": "#/$defs/ParticleVelocity"
        },
        "recordedAt": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
          "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
        }
      },
      "required": [
        "id",
        "distanceM",
        "chargeKg",
        "sampleRateHz",
        "frame",
        "samples"
      ],
      "description": "A single-hole ground-motion record: the kinematic input a superposition model is calibrated from."
    },
    "ParticleVelocity": {
      "anyOf": [
        {
          "maxItems": 200000,
          "type": "array",
          "items": {
            "minItems": 3,
            "maxItems": 3,
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "description": "One row per sample, `[a, b, c]` in frame order, mm/s."
        },
        {
          "$ref": "#/$defs/BlobRef"
        }
      ],
      "description": "Three-component particle velocity, mm/s: one `[a, b, c]` row per sample in frame order, so every component has the same length; or a reference to the same rows."
    },
    "VodMeasurement": {
      "type": "object",
      "properties": {
        "holeId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "productId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "vodMs": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Measured velocity of detonation, m/s."
        },
        "method": {
          "type": "string",
          "enum": [
            "continuous_resistance",
            "point_to_point",
            "optical"
          ]
        }
      },
      "required": [
        "holeId",
        "vodMs",
        "method"
      ],
      "description": "A measured velocity of detonation."
    },
    "MeasuredOutcome": {
      "type": "object",
      "properties": {
        "blastRef": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Which earlier blast this came from."
        },
        "receiverId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "distanceM": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "micKg": {
          "description": "Maximum instantaneous charge of that blast, kg.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "ppvMmS": {
          "type": "number",
          "minimum": 0
        },
        "basis": {
          "$ref": "#/$defs/PpvBasis"
        },
        "dominantFrequencyHz": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "airblastDbL": {
          "type": "number",
          "minimum": 0
        }
      },
      "required": [
        "blastRef",
        "receiverId"
      ],
      "description": "A reading from an earlier blast at this site."
    },
    "StrainAmplitude": {
      "type": "object",
      "properties": {
        "factor": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "The ratio of the P wave's amplitude near the charges to the site law's PPV extrapolated inward, as the model that fitted it reads both: above 1 the rock is strained more than the law alone says. A model judging damage scales its near-field amplitude by it."
        },
        "basis": {
          "type": "string",
          "enum": [
            "backbreak",
            "half_cast",
            "scan",
            "strain_array",
            "judgement"
          ],
          "description": "What the factor was fitted to: how far behind the back row the wall broke, half-cast counts on the final wall, a post-blast scan against the design, strain read from an array of synchronised monitors, or judgement."
        },
        "blastRef": {
          "description": "Which blast it was fitted on.",
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "fittedWith": {
          "description": "The model and version whose amplitude the factor scales. A factor fitted against one model's assumptions is that model's: another model, another version, or any model when this is left out, reads it as judgement.",
          "type": "object",
          "properties": {
            "modelId": {
              "$ref": "#/$defs/ModelId"
            },
            "modelVersion": {
              "type": "string",
              "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
              "description": "A semantic version. A result is always pinned to the exact version that produced it."
            }
          },
          "required": [
            "modelId",
            "modelVersion"
          ]
        },
        "note": {
          "description": "How the fit was made, for a reader.",
          "type": "string",
          "maxLength": 500
        }
      },
      "required": [
        "factor",
        "basis"
      ],
      "description": "The site's fit of the strain a blast puts in its rock near the charges, which a model judging damage scales its amplitude by."
    },
    "ModelId": {
      "type": "string",
      "minLength": 3,
      "maxLength": 128,
      "pattern": "^[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9.-]*$",
      "description": "A model identifier in the form `vendor/model`, lower case, for example `shockai/site-law`."
    },
    "Measured": {
      "type": "object",
      "properties": {
        "records": {
          "maxItems": 64,
          "type": "array",
          "items": {
            "$ref": "#/$defs/MeasuredRecord"
          }
        },
        "backbreak": {
          "description": "How far behind its holes the blast broke the wall, once it was seen.",
          "maxItems": 5000,
          "type": "array",
          "items": {
            "$ref": "#/$defs/BackbreakReading"
          }
        }
      },
      "description": "What was measured of this blast itself, once it has fired. `calibration` holds what earlier blasts taught the site; this holds the blast in question."
    },
    "MeasuredRecord": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
        },
        "receiverId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
          "description": "The receiver it was recorded at. That receiver carries a `position`, with or without an elevation: a record with no place on the grid tells a model nothing about which hole it heard."
        },
        "sampleRateHz": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "axes": {
          "type": "string",
          "enum": [
            "recorded",
            "levelled",
            "oriented"
          ],
          "description": "How the rows’ three components lie. `recorded`: as the unit wrote them, with nothing known of how it lay. `levelled`: turned so the third component is vertical, up positive, and the first two lie in the level plane on an unknown bearing. `oriented`: levelled, with the first component along `headingDeg` and the second a quarter turn clockwise of it seen from above."
        },
        "headingDeg": {
          "description": "Bearing of the first component, degrees clockwise from grid north. Given for `oriented` axes only.",
          "type": "number",
          "minimum": 0,
          "maximum": 360
        },
        "quietUntilIndex": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "description": "The rows before this index were recorded before the blast’s motion arrived: what a model measures the noise from."
        },
        "dominantFrequencyHz": {
          "description": "Dominant frequency of the record, Hz, as the platform’s analysis found it.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "clipMmS": {
          "description": "The recorder’s full scale, mm/s. A sample at it was clipped.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "recordedAt": {
          "description": "Wall-clock time of the first row, by the recorder’s own clock.",
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
        },
        "samples": {
          "$ref": "#/$defs/ParticleVelocity"
        }
      },
      "required": [
        "id",
        "receiverId",
        "sampleRateHz",
        "axes",
        "quietUntilIndex",
        "samples"
      ],
      "description": "A ground-motion record of this blast, as a monitor wrote it. Its time against T0 is not known: a recorder starts on the motion reaching it, and its clock is not the initiation system’s. A model that needs the two aligned finds the alignment itself."
    },
    "BackbreakReading": {
      "type": "object",
      "properties": {
        "distanceM": {
          "type": "number",
          "minimum": 0,
          "description": "How far behind the holes the final wall broke, m. A model judging damage reads it as a radius about each hole’s charge, square to its axis: for vertical holes, the distance in plan from the line of their collars to the broken crest."
        },
        "holeIds": {
          "minItems": 1,
          "maxItems": 5000,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "description": "The holes it was measured behind: the back row for one reading of the blast, or one hole for a reading per hole."
        },
        "method": {
          "description": "How it was measured: taped, surveyed, read from a scan, or estimated by eye.",
          "type": "string",
          "enum": [
            "tape",
            "survey",
            "scan",
            "estimate"
          ]
        },
        "note": {
          "description": "Anything a reader should know.",
          "type": "string",
          "maxLength": 500
        }
      },
      "required": [
        "distanceM",
        "holeIds"
      ],
      "description": "How far behind some of its holes a fired blast broke the wall it left: the damage a model judging it can be fitted to."
    },
    "OutputRequest": {
      "type": "object",
      "properties": {
        "outputs": {
          "minItems": 1,
          "type": "array",
          "items": {
            "$ref": "#/$defs/OutputKind"
          },
          "description": "What the caller wants back."
        },
        "receiverIds": {
          "description": "Receivers to predict at; all receivers when omitted.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          }
        },
        "grid": {
          "$ref": "#/$defs/Grid"
        },
        "percentiles": {
          "default": [
            50,
            95
          ],
          "description": "Points of the predictive distribution to report. 50 and 95 when omitted.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          }
        },
        "designPercentile": {
          "default": 95,
          "description": "The percentile design values are read at: a charge limit is the largest charge whose prediction at this percentile stays under the limit. 95 when omitted.",
          "type": "number",
          "minimum": 50,
          "maximum": 99.9
        },
        "seed": {
          "description": "Seed for any sampling, so a run can be reproduced exactly.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "outputs"
      ],
      "description": "The question the case asks."
    },
    "OutputKind": {
      "type": "string",
      "enum": [
        "receiver_ppv",
        "max_charge",
        "receiver_waveform",
        "receiver_spectrum",
        "ppv_field",
        "airblast",
        "damage_envelope",
        "strain_field",
        "flyrock_zone",
        "fragmentation_curve",
        "movement_field",
        "timing_proposal",
        "hole_diagnosis",
        "strain_amplitude"
      ],
      "description": "A kind of answer: a prediction of what a blast will do, or, for `hole_diagnosis` and `strain_amplitude`, a reading of what a fired blast did."
    },
    "Grid": {
      "type": "object",
      "properties": {
        "minX": {
          "type": "number"
        },
        "minY": {
          "type": "number"
        },
        "maxX": {
          "type": "number"
        },
        "maxY": {
          "type": "number"
        },
        "cellM": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Cell size, m."
        },
        "elevation": {
          "type": "string",
          "enum": [
            "terrain",
            "collar_plane"
          ],
          "description": "Where grid points sit vertically."
        }
      },
      "required": [
        "minX",
        "minY",
        "maxX",
        "maxY",
        "cellM",
        "elevation"
      ],
      "description": "A plan-view grid for field outputs. A field’s values sit at the nodes minX + i·cellM, minY + j·cellM for i below its `columns` and j below its `rows`, the last node within a cell of maxX and maxY; a reader takes `columns` and `rows` from the output and never recomputes them."
    },
    "Consent": {
      "type": "object",
      "properties": {
        "purpose": {
          "type": "string",
          "enum": [
            "prediction",
            "diagnosis"
          ],
          "description": "What the owner agreed the case be used for: `prediction` of what a blast will do, or `diagnosis` of what a fired blast did, which sends the blast’s own records. A case asks only for the kinds of its purpose."
        },
        "retention": {
          "type": "string",
          "enum": [
            "none",
            "30d",
            "contract"
          ],
          "description": "How long the provider may keep the case after the run."
        },
        "allowTraining": {
          "type": "boolean",
          "description": "Whether the provider may use the case to improve its model. False unless the owner opts in."
        }
      },
      "required": [
        "purpose",
        "retention",
        "allowTraining"
      ],
      "description": "The data owner’s terms for this case. A provider that cannot honour them must refuse the run."
    }
  }
}
