{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://xcathletes.org/contract/schemas/training-session.json",
  "title": "training_session",
  "description": "GET /v1/training-sessions, GET /v1/training-sessions/{id}: one recorded session. Response schema, PRIN-04 open.",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "id",
    "person_id",
    "date",
    "instant_utc",
    "tz",
    "technique",
    "note",
    "rpe",
    "distance_km",
    "duration_seconds",
    "modality",
    "modality_text",
    "blocks",
    "computed",
    "duplicates",
    "external_ref",
    "origin_team_id",
    "origin_person_id",
    "version",
    "created_at",
    "updated_at"
  ],
  "properties": {
    "id": {
      "type": "string",
      "description": "A pre-existing platform id (a legacy training session predating the /v1/ contract) or a new PRIN-05 128-bit hex id may both appear here; format is not otherwise constrained."
    },
    "person_id": {
      "type": "string",
      "description": "A pre-existing platform id (a legacy person predating the /v1/ contract) or a new PRIN-05 128-bit hex id may both appear here; format is not otherwise constrained."
    },
    "date": {
      "type": "string",
      "format": "date",
      "description": "ENT-01: the athlete's local calendar date, no time part."
    },
    "instant_utc": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "tz": {
      "type": [
        "string",
        "null"
      ],
      "description": "An IANA zone name, or null."
    },
    "technique": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "classic",
        "skate",
        "double_pole",
        null
      ],
      "description": "Required on skiing/roller_skiing, null on every other modality. An unrecognized value falls back to null (treat as unspecified technique)."
    },
    "note": {
      "type": [
        "string",
        "null"
      ]
    },
    "rpe": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "maximum": 10
    },
    "distance_km": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0
    },
    "duration_seconds": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": 0,
      "description": "ENT-02/ENT-05: when present, authoritative over the block sum."
    },
    "modality": {
      "type": "array",
      "description": "ENT-06: one or more codings; the platform's own registry versions independently of /v1/.",
      "items": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "system",
          "code",
          "display"
        ],
        "properties": {
          "system": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "PRIN-03: a code this consumer's own registry does not recognize falls back to `other`."
          },
          "display": {
            "type": "string"
          }
        }
      }
    },
    "modality_text": {
      "type": [
        "string",
        "null"
      ]
    },
    "blocks": {
      "type": "array",
      "items": {
        "$ref": "training-block.json"
      }
    },
    "computed": {
      "type": "object",
      "description": "ENT-05: read-only, published formula (block_seconds = sum(duration_seconds * reps); seconds_by_zone groups the same sum by zone). Rejected on write (computed_field_readonly).",
      "additionalProperties": true,
      "required": [
        "block_seconds",
        "seconds_by_zone"
      ],
      "properties": {
        "block_seconds": {
          "type": "integer",
          "minimum": 0
        },
        "seconds_by_zone": {
          "type": "object",
          "additionalProperties": {
            "type": "integer",
            "minimum": 0
          }
        }
      }
    },
    "duplicates": {
      "type": "array",
      "description": "MW-06: direct pairwise duplicate-cluster hints, confidence-marked; never causes a row to be omitted.",
      "items": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "session_id",
          "confidence"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "description": "A pre-existing platform id (a legacy training session predating the /v1/ contract) or a new PRIN-05 128-bit hex id may both appear here; format is not otherwise constrained."
          },
          "confidence": {
            "type": "string",
            "enum": [
              "high",
              "medium"
            ],
            "description": "MW-06. An unrecognized value falls back to `medium` (the more cautious hint)."
          }
        }
      }
    },
    "external_ref": {
      "type": [
        "string",
        "null"
      ],
      "description": "WR-02: the originating consumer's own natural key."
    },
    "origin_team_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "ENT-08. The originating team id, never a token id (AUTH-04). A pre-existing platform id (a legacy team predating the /v1/ contract) or a new PRIN-05 128-bit hex id may both appear here; format is not otherwise constrained."
    },
    "origin_person_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "A pre-existing platform id (a legacy person predating the /v1/ contract) or a new PRIN-05 128-bit hex id may both appear here; format is not otherwise constrained."
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "MW-02: the optimistic-locking version an If-Match precondition names."
    },
    "created_at": {
      "type": "string",
      "format": "date-time"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "$defs": {
    "createRequest": {
      "title": "training_session create request",
      "description": "POST /v1/training-sessions (WR-01..09, TIER-02). Request schema, PRIN-04 strict: additionalProperties forbidden, computed rejected outright (ENT-05).",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "person_id",
        "date",
        "modality"
      ],
      "properties": {
        "person_id": {
          "type": "string",
          "description": "A pre-existing platform id (a legacy person predating the /v1/ contract) or a new PRIN-05 128-bit hex id may both appear here; format is not otherwise constrained."
        },
        "date": {
          "type": "string",
          "format": "date"
        },
        "instant_utc": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "tz": {
          "type": [
            "string",
            "null"
          ]
        },
        "technique": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "classic",
            "skate",
            "double_pole",
            null
          ],
          "description": "Required on skiing/roller_skiing, null on every other modality. An unrecognized value falls back to null (treat as unspecified technique)."
        },
        "note": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 4000
        },
        "rpe": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 10
        },
        "distance_km": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 500
        },
        "duration_seconds": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "maximum": 86400
        },
        "modality_text": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 4000
        },
        "external_ref": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 200
        },
        "modality": {
          "description": "ENT-06's coding array, or a legacy bare modality string (backward-compatible wire shape).",
          "oneOf": [
            {
              "type": "string",
              "enum": [
                "running",
                "hill_bounding",
                "strength",
                "spenst",
                "skiing",
                "roller_skiing",
                "bike",
                "other"
              ],
              "description": "The legacy bare modality string. PRIN-03: a code this consumer's own registry does not recognize falls back to `other`."
            },
            {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "system",
                  "code",
                  "display"
                ],
                "properties": {
                  "system": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "display": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          ]
        },
        "blocks": {
          "type": "array",
          "items": {
            "$ref": "training-block.json#/$defs/writeItem"
          }
        }
      }
    }
  }
}
