{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/freightutils-com/json-schema/freightutils-com-container-spec-schema.json",
  "title": "ContainerSpec",
  "x-generated": "2026-11-09",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/freightutils-com-openapi.json#/components/schemas/ContainerSpec",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Container type name."
    },
    "slug": {
      "type": "string",
      "description": "URL-friendly identifier."
    },
    "internal_length_cm": {
      "type": "number",
      "description": "Internal length in cm."
    },
    "internal_width_cm": {
      "type": "number",
      "description": "Internal width in cm."
    },
    "internal_height_cm": {
      "type": [
        "number",
        "null"
      ],
      "description": "Internal height in cm. NULL on open decks (flat racks) — no roof, so no bounding height. Read height_basis to learn what a null means. Never 0: a zero here would make a length x width x height calculation return zero volume."
    },
    "external_length_cm": {
      "type": "number",
      "description": "External length in cm."
    },
    "external_width_cm": {
      "type": "number",
      "description": "External width in cm."
    },
    "external_height_cm": {
      "type": "number",
      "description": "External height in cm."
    },
    "door_width_cm": {
      "type": [
        "number",
        "null"
      ],
      "description": "Door opening width in cm."
    },
    "door_height_cm": {
      "type": [
        "number",
        "null"
      ],
      "description": "Door opening height in cm."
    },
    "capacity_cbm": {
      "type": [
        "number",
        "null"
      ],
      "description": "Internal volume in CBM. NULL on open decks (flat racks), which have no roof and no side walls and therefore no cubic capacity — read capacity_basis, and use deck_area_sqm instead. Never 0."
    },
    "tare_weightKg": {
      "type": "number",
      "description": "Empty container weight in kg."
    },
    "max_gross_kg": {
      "type": "number",
      "description": "Maximum gross weight in kg."
    },
    "max_payload_kg": {
      "type": "number",
      "description": "Maximum cargo weight in kg."
    },
    "euro_pallets": {
      "type": "string",
      "deprecated": true,
      "description": "DEPRECATED — use euro_pallets_min / euro_pallets_max. A string, and sometimes an EN-DASH range such as \"23–24\", so Number() returns NaN and parseInt() silently discards the upper bound. Kept unchanged for back-compat and now derived from the numeric pair. Will be removed in a future major version."
    },
    "gma_pallets": {
      "type": "string",
      "deprecated": true,
      "description": "DEPRECATED — use gma_pallets_min / gma_pallets_max. A numeric value stored as a string. Kept unchanged for back-compat and now derived from the numeric pair. Will be removed in a future major version."
    },
    "euro_pallets_min": {
      "type": "integer",
      "description": "EUR pallets (1200x800mm) that fit — the conservative figure. Always numeric. Replaces euro_pallets."
    },
    "euro_pallets_max": {
      "type": [
        "integer",
        "null"
      ],
      "description": "Upper bound when the achievable EUR-pallet count is genuinely a range (depends on overhang tolerance and whether the last row is turned). Null when the figure is exact."
    },
    "gma_pallets_min": {
      "type": "integer",
      "description": "GMA pallets (48x40in) that fit — the conservative figure. Always numeric. Replaces gma_pallets."
    },
    "gma_pallets_max": {
      "type": [
        "integer",
        "null"
      ],
      "description": "Upper bound when the GMA-pallet count is a range; null when exact."
    },
    "deck_area_sqm": {
      "type": [
        "number",
        "null"
      ],
      "description": "Deck area in m² — the meaningful capacity figure for an open deck (flat racks), where a cubic capacity does not exist. Null on enclosed containers."
    },
    "pallet_capacity_basis": {
      "type": "string",
      "enum": [
        "floor-positions",
        "weight-limited",
        "industry-convention",
        "unverified"
      ],
      "description": "Which constraint the pallet counts express. Container counts are 'industry-convention' because published figures assume a pinwheel load and exceed an aligned-row tiling of the same floor."
    },
    "height_basis": {
      "type": "string",
      "enum": [
        "unbounded-open-top",
        "constrained-removable-roof",
        "varies-by-configuration",
        "unverified"
      ],
      "description": "What internal_height_cm means, INCLUDING what a null means. 'unbounded-open-top' = no roof, so no height constraint exists (flat racks; height is null). 'constrained-removable-roof' = a real roof-fitted height on a unit whose roof comes off (open tops). Never infer this from open_top."
    },
    "open_top": {
      "type": "boolean",
      "description": "Affirmative physical fact: the unit has no fixed roof. NOT the discriminator for what a null height means — read height_basis for that."
    },
    "capacity_basis": {
      "type": "string",
      "enum": [
        "carrier-published",
        "computed-from-internal-dimensions",
        "not-applicable-open-deck",
        "unverified"
      ],
      "description": "What capacity_cbm is. Carrier-published capacities do NOT equal length x width x height, so an agent computing volume from dimensions and an agent reading capacity_cbm will differ; this says which figure is held. 'not-applicable-open-deck' means capacity_cbm is null because the unit is unbounded."
    },
    "max_gross_basis": {
      "type": "string",
      "enum": [
        "carrier-published",
        "iso-668-series-1",
        "unverified"
      ],
      "description": "Which authority max_gross_kg comes from. 'iso-668-series-1' is a regulatory ceiling and is frequently LOWER than what carriers actually operate — where it applies, max_payload_kg (derived as max gross minus tare) understates usable payload."
    },
    "description": {
      "type": "string",
      "description": "Container description."
    },
    "notes": {
      "type": "string",
      "description": "Usage notes."
    },
    "tare_weight_kg": {
      "type": "number",
      "description": "Tare (empty) weight in kg."
    },
    "fu_id": {
      "$ref": "#/$defs/FuId"
    },
    "fu_version_id": {
      "$ref": "#/$defs/FuVersionId"
    }
  },
  "required": [
    "name",
    "slug",
    "internal_length_cm",
    "internal_width_cm",
    "internal_height_cm",
    "capacity_cbm",
    "tare_weight_kg",
    "max_gross_kg",
    "max_payload_kg",
    "euro_pallets",
    "gma_pallets",
    "euro_pallets_min",
    "gma_pallets_min",
    "pallet_capacity_basis",
    "height_basis",
    "open_top",
    "capacity_basis",
    "max_gross_basis"
  ],
  "$defs": {
    "FuId": {
      "type": "string",
      "pattern": "^fu:[a-z][a-z0-9-]*(?::(?:[A-Za-z0-9._~()+-]|%[0-9A-F]{2})+)+$",
      "description": "Stable, edition-free identifier of this record: fu:<namespace>:<segment>[:<segment>…]. A join key, the same in every edition, kept in an append-only ledger. Not a regulatory reference, a citation or a promise the content never changes. GET /api/resolve?q=<fu_id> returns it."
    },
    "FuVersionId": {
      "type": "string",
      "pattern": "^(fu:[a-z][a-z0-9-]*(?::(?:[A-Za-z0-9._~()+-]|%[0-9A-F]{2})+)+)@([A-Za-z0-9._-]+)$",
      "description": "The identifier of this edition's record: <fu_id>@<edition token>. Present only where the dataset has editions (ADR, HS, UN/LOCODE, airports, Incoterms, ADR codes, ICS2, emission factors); airlines, ULDs, containers, vehicles and pallets have none."
    }
  }
}