{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:exchange-model:v0",
  "title": "Craton model exchange listing, v0",
  "description": "One catastrophe model on the model exchange: a model that runs on its provider's own Oasis platform, listed so that any Craton account (or the accounts the provider grants) can run it from the model lab beside every other model. Returned by POST /exchange/models, GET /exchange/models/{id}, PATCH /exchange/models/{id} and the review and withdraw routes. Craton never holds the model's files and moves no money: a listing is either free to run, or run only by the accounts its provider grants, and access is arranged between the user and the provider outside Craton. Running a listed model sends the user's locations and values to the provider's platform. The platform credentials a provider sends are stored encrypted and appear in no response, to anyone.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "id",
    "provider",
    "name",
    "version",
    "perils",
    "coverage",
    "access",
    "access_url",
    "card",
    "connector",
    "status",
    "review",
    "created_at",
    "updated_at"
  ],
  "properties": {
    "schema_version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
      "description": "Version of this schema the document conforms to."
    },
    "id": {
      "type": "string",
      "pattern": "^xm_[0-9a-f]{16}$",
      "description": "Server-assigned identity of the listing. The model lab names the model exchange:<id>."
    },
    "provider": {
      "type": "object",
      "additionalProperties": false,
      "required": ["account_id", "name"],
      "description": "Who lists the model: the Craton account that created the listing, and the name it is shown under.",
      "properties": {
        "account_id": {
          "type": "string",
          "pattern": "^acct_[0-9a-f]{24}$",
          "description": "The account that owns the listing. Set by Craton from the key that created it; only this account may change it."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120,
          "description": "The provider's display name, as the provider wrote it."
        }
      }
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120,
      "description": "The model's name. Listings are shown in order of name, then id, and never ranked."
    },
    "version": {
      "type": "string",
      "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$",
      "description": "Semantic version of the model. A change of model is a new version, and a new version of a listed model goes back to review before it is listed again."
    },
    "perils": {
      "type": "array",
      "minItems": 1,
      "uniqueItems": true,
      "description": "The perils the model computes losses for, in the canonical peril codes of the risk object schema.",
      "items": {
        "type": "string",
        "enum": [
          "earthquake",
          "tropical_cyclone",
          "flood",
          "wildfire",
          "severe_convective_storm",
          "weather_station",
          "cloud_outage",
          "grid_outage",
          "extratropical_cyclone"
        ]
      }
    },
    "coverage": {
      "type": "object",
      "additionalProperties": false,
      "required": ["description", "bbox"],
      "description": "Where the model answers.",
      "properties": {
        "description": {
          "type": "string",
          "minLength": 1,
          "maxLength": 1000,
          "description": "The region in words, as the provider states it."
        },
        "bbox": {
          "type": "array",
          "minItems": 4,
          "maxItems": 4,
          "description": "The box the model covers: [min_lon, min_lat, max_lon, max_lat] in decimal degrees, WGS 84. The minimum of each pair is below its maximum; a box crossing the antimeridian is not expressible in this version.",
          "prefixItems": [
            { "type": "number", "minimum": -180, "maximum": 180 },
            { "type": "number", "minimum": -90, "maximum": 90 },
            { "type": "number", "minimum": -180, "maximum": 180 },
            { "type": "number", "minimum": -90, "maximum": 90 }
          ],
          "items": false
        }
      }
    },
    "access": {
      "enum": ["free", "by_grant"],
      "description": "Who may run the model. free: any Craton account. by_grant: only the accounts the provider has granted, and the provider itself. Craton collects no payment for either; access by grant is arranged with the provider at access_url."
    },
    "access_url": {
      "type": ["string", "null"],
      "maxLength": 2048,
      "pattern": "^https://[^\\s]+$",
      "description": "Where a person asks the provider for access, outside Craton. Required when access is by_grant; null or a link otherwise."
    },
    "card": {
      "type": "object",
      "additionalProperties": false,
      "required": ["summary", "assumptions", "limitations", "licence", "contact"],
      "description": "The model card, written by the provider and checked by a person at review before the listing is public.",
      "properties": {
        "summary": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "What the model computes, in a paragraph." },
        "assumptions": {
          "type": "array",
          "maxItems": 50,
          "items": { "type": "string", "minLength": 1, "maxLength": 1000 },
          "description": "What the model assumes, one statement each."
        },
        "limitations": {
          "type": "array",
          "maxItems": 50,
          "items": { "type": "string", "minLength": 1, "maxLength": 1000 },
          "description": "What the model does not do, one statement each."
        },
        "licence": { "type": "string", "minLength": 1, "maxLength": 200, "description": "The terms the model is run under, as the provider states them." },
        "contact": { "type": "string", "minLength": 1, "maxLength": 320, "description": "How to reach the provider about the model." }
      }
    },
    "connector": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "base_url", "model_pk", "samples", "credentials_stored"],
      "description": "How Craton reaches the model on the provider's platform. The credentials the platform's login needs are accepted on create and update and never returned; credentials_stored says whether some are held.",
      "properties": {
        "kind": {
          "const": "oasis-platform-v2",
          "description": "The platform's API: the OasisPlatform API, version 2."
        },
        "base_url": {
          "type": "string",
          "maxLength": 2048,
          "pattern": "^https://[^\\s?#@]+$",
          "description": "The platform's API root, https only. Every address its host resolves to must be public: Craton refuses a base URL that resolves to a private, loopback, link-local or shared address, when the listing is stored and again before every connection."
        },
        "model_pk": {
          "type": "integer",
          "minimum": 1,
          "description": "The model's primary key on the provider's platform (GET {base_url}/v2/models/{model_pk}/)."
        },
        "samples": {
          "type": "integer",
          "minimum": 1,
          "maximum": 1000,
          "description": "Number of samples each run asks the platform's analysis for."
        },
        "credentials_stored": {
          "type": "boolean",
          "description": "Whether platform credentials are held for this listing. The credentials themselves are never returned."
        },
        "oed": {
          "type": "object",
          "additionalProperties": false,
          "description": "Optional. The OED location attributes the model expects on every location the lab sends it, where the user's own data does not say: the country, the occupancy and construction codes, and the currency the values are written in. Each member is optional; the lab's own defaults apply to any left out.",
          "properties": {
            "country_code": {
              "type": "string",
              "pattern": "^[A-Z]{2}$",
              "description": "ISO 3166-1 alpha-2 country code, upper case."
            },
            "occupancy_code": {
              "type": "integer",
              "minimum": 1,
              "description": "OED occupancy code."
            },
            "construction_code": {
              "type": "integer",
              "minimum": 1,
              "description": "OED construction code."
            },
            "currency": {
              "type": "string",
              "pattern": "^[A-Z]{3}$",
              "description": "ISO 4217 currency code, upper case: the currency the location values are written in for the model."
            }
          }
        }
      }
    },
    "status": {
      "enum": ["draft", "listed", "withdrawn"],
      "description": "draft: visible to its provider only. listed: public, and runnable by the accounts access allows. withdrawn: taken off the exchange by its provider. Only a person's approval at review makes a listing listed."
    },
    "review": {
      "type": "object",
      "additionalProperties": false,
      "required": ["state", "note"],
      "description": "The person's check before a listing is public: the provider and the card. A provider asks for it; Craton's operator decides it.",
      "properties": {
        "state": {
          "enum": ["not_requested", "pending", "approved", "rejected"],
          "description": "not_requested until the provider asks; pending while a person has not decided; approved or rejected once one has."
        },
        "note": {
          "type": ["string", "null"],
          "maxLength": 2000,
          "description": "What the reviewer wrote with the decision, or null."
        }
      }
    },
    "created_at": { "type": "string", "format": "date-time", "description": "When the listing was created, RFC 3339 in UTC." },
    "updated_at": { "type": "string", "format": "date-time", "description": "When the listing last changed, RFC 3339 in UTC." }
  },
  "allOf": [
    {
      "if": { "properties": { "access": { "const": "by_grant" } } },
      "then": { "properties": { "access_url": { "type": "string" } } }
    },
    {
      "if": { "properties": { "status": { "const": "listed" } } },
      "then": { "properties": { "review": { "properties": { "state": { "const": "approved" } } } } }
    }
  ]
}
