{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.radlerparfums.com/data/device-api/schema.json",
  "title": "Radler scent instruction, version 0.1 (a design draft)",
  "description": "A standardised representation of a scent instruction, derived only from the site's public teaching-accord records: which components (by record id), in what modeled proportions, with what timing and relative intensity. A design for discussion: no device implements it, no hardware is involved, and nothing executes it. It never carries a Radler formula or any real perfume's composition; the source is always a teaching accord, published for learning.",
  "type": "object",
  "required": [
    "format",
    "version",
    "status",
    "source",
    "disclaimer",
    "units",
    "channels",
    "timeline",
    "safety"
  ],
  "additionalProperties": false,
  "properties": {
    "format": {
      "const": "radler-scent-instruction",
      "description": "Names this representation."
    },
    "version": {
      "type": "string",
      "pattern": "^0\\.1\\.[0-9]+$",
      "description": "The version of this draft."
    },
    "status": {
      "const": "design draft: no device implements this, and nothing executes it",
      "description": "Says what the file is, every time."
    },
    "source": {
      "type": "object",
      "description": "The public record the instruction is derived from. Only a teaching accord is allowed.",
      "required": [
        "type",
        "id",
        "record_version",
        "url",
        "purpose",
        "licence"
      ],
      "additionalProperties": false,
      "properties": {
        "type": {
          "const": "Accord"
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9-]+-teaching-accord$"
        },
        "record_version": {
          "type": "integer",
          "minimum": 1
        },
        "url": {
          "type": "string",
          "pattern": "^https://www\\.radlerparfums\\.com/accord/[a-z0-9-]+$"
        },
        "data": {
          "type": "string",
          "pattern": "^https://www\\.radlerparfums\\.com/data/accord/[a-z0-9-]+\\.json$"
        },
        "purpose": {
          "const": "teaching"
        },
        "licence": {
          "const": "https://creativecommons.org/licenses/by/4.0/"
        }
      }
    },
    "disclaimer": {
      "type": "string",
      "minLength": 40,
      "description": "Carried from the teaching accord record, unchanged."
    },
    "units": {
      "type": "object",
      "required": [
        "time",
        "proportion",
        "intensity"
      ],
      "additionalProperties": false,
      "properties": {
        "time": {
          "const": "seconds from the start of the instruction"
        },
        "proportion": {
          "const": "share of the accord, 0 to 1 (modeled teaching share)"
        },
        "intensity": {
          "const": "relative, 0 to 1: the channel's share against the largest share (proportion, not perceived strength)"
        }
      }
    },
    "channels": {
      "type": "array",
      "minItems": 1,
      "description": "One channel per component of the accord.",
      "items": {
        "$ref": "#/$defs/channel"
      }
    },
    "timeline": {
      "type": "array",
      "minItems": 2,
      "description": "Keyframes: at each time, the level of every channel in channel order (envelope times intensity).",
      "items": {
        "$ref": "#/$defs/keyframe"
      }
    },
    "safety": {
      "type": "object",
      "required": [
        "for_skin",
        "assessed",
        "regulation_refs",
        "note"
      ],
      "additionalProperties": false,
      "properties": {
        "for_skin": {
          "const": false
        },
        "assessed": {
          "const": false,
          "description": "Not safety-assessed; not assessed against any standard."
        },
        "regulation_refs": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9-]+$"
          },
          "description": "Regulation record ids the source accord names, for reading; their wording names their jurisdiction."
        },
        "note": {
          "type": "string"
        }
      }
    }
  },
  "$defs": {
    "channel": {
      "type": "object",
      "required": [
        "channel",
        "component",
        "proportion",
        "volatility_class",
        "class_from",
        "envelope",
        "intensity"
      ],
      "additionalProperties": false,
      "properties": {
        "channel": {
          "type": "integer",
          "minimum": 1
        },
        "component": {
          "type": "object",
          "required": [
            "type",
            "id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "type": {
              "enum": [
                "Molecule",
                "Material",
                "Unrecorded"
              ]
            },
            "id": {
              "type": [
                "string",
                "null"
              ],
              "pattern": "^[a-z0-9-]+$"
            },
            "name": {
              "type": "string"
            },
            "pubchem_cid": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 1
            },
            "url": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        },
        "proportion": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "volatility_class": {
          "enum": [
            "top",
            "heart",
            "base"
          ]
        },
        "class_from": {
          "type": "string",
          "description": "Where the class came from on the records, or that it is modeled."
        },
        "envelope": {
          "type": "object",
          "description": "Modeled timing for a demonstration playback: when the channel starts, rises, holds and fades.",
          "required": [
            "onset_s",
            "attack_s",
            "hold_s",
            "release_s"
          ],
          "additionalProperties": false,
          "properties": {
            "onset_s": {
              "type": "number",
              "minimum": 0
            },
            "attack_s": {
              "type": "number",
              "minimum": 0
            },
            "hold_s": {
              "type": "number",
              "minimum": 0
            },
            "release_s": {
              "type": "number",
              "minimum": 0
            }
          }
        },
        "intensity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      }
    },
    "keyframe": {
      "type": "object",
      "required": [
        "t_s",
        "levels"
      ],
      "additionalProperties": false,
      "properties": {
        "t_s": {
          "type": "number",
          "minimum": 0
        },
        "levels": {
          "type": "array",
          "items": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        }
      }
    }
  }
}
