{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/XwK-P/medh5/schemas/medh5-sample-1.0.schema.json",
  "title": "MEDH5 1.0 sample document",
  "description": "JSON document stored in the scalar string dataset `meta` at a MEDH5 sample root. See docs/spec/medh5-1.0.md §2.4.",
  "type": "object",
  "required": ["identity", "timepoints"],
  "additionalProperties": false,
  "properties": {
    "identity": { "$ref": "#/$defs/identity" },
    "timepoints": {
      "type": "array",
      "description": "Ordered declaration of the sample's observation occasions. See docs/spec/medh5-1.0.md §3.7.",
      "minItems": 1,
      "items": { "$ref": "#/$defs/timepoint" }
    },
    "cohort": { "$ref": "#/$defs/cohort" },
    "label_set": { "$ref": "#/$defs/labelSet" },
    "provenance": { "$ref": "#/$defs/provenance" },
    "quality": {
      "type": "object",
      "description": "Quality records keyed by the value of an annotation's `quality` attribute.",
      "additionalProperties": { "$ref": "#/$defs/qualityRecord" }
    },
    "splits": { "type": "array", "items": { "$ref": "#/$defs/splitClaim" } },
    "acquisition": {
      "type": "object",
      "description": "Acquisition parameters keyed by image id. Keys SHOULD follow DICOM keyword names.",
      "additionalProperties": { "type": "object" }
    },
    "deidentification": { "$ref": "#/$defs/deidentification" },
    "extra": {
      "type": "object",
      "description": "Free-form writer data. Third-party extensions SHOULD use a reverse-DNS namespace key."
    }
  },

  "$defs": {
    "id": { "type": "string", "pattern": "^[A-Za-z0-9_.-]{1,128}$" },
    "timestamp": { "type": "string", "format": "date-time" },
    "classId": { "type": "integer", "minimum": 1, "maximum": 65534 },

    "identity": {
      "type": "object",
      "required": ["sample_id", "subject_id"],
      "additionalProperties": true,
      "description": "Subject-scoped identity. Per-occasion identifiers (study_uid, series_uids, dates, age) belong to the timepoint entry, not here.",
      "properties": {
        "sample_id": { "type": "string", "minLength": 1 },
        "subject_id": { "type": "string", "minLength": 1,
          "description": "Pseudonymised subject key. A sample never spans subjects, so assigning whole files to splits is subject-safe." },
        "sex": { "type": ["string", "null"], "enum": ["F", "M", "O", "unknown", null] },
        "laterality": { "type": ["string", "null"], "enum": ["left", "right", "bilateral", null] },
        "bodypart": { "type": "string" }
      }
    },

    "timepoint": {
      "type": "object",
      "required": ["id", "index"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/id",
          "description": "Referenced by grid `timepoint` attributes and annotation `timepoints` attributes." },
        "index": { "type": "integer", "minimum": 0,
          "description": "0-based acquisition order; dense and strictly increasing with time." },
        "label": { "type": "string", "examples": ["baseline", "follow_up_3mo", "post_treatment"] },
        "date": { "type": "string", "description": "Shifted per deidentification.date_shift_days." },
        "days_from_baseline": { "type": "number",
          "description": "Interval from index 0. Survives date shifting; prefer this over `date` in models." },
        "study_uid": { "type": "string" },
        "series_uids": { "type": "object", "additionalProperties": { "type": "string" },
          "description": "Image id -> pseudonymised source series identifier." },
        "subject_age_years": { "type": "number", "minimum": 0 },
        "description": { "type": "string" }
      }
    },

    "cohort": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "dataset_id": { "type": "string" },
        "site_id": { "type": "string" },
        "scanner_id": { "type": "string" },
        "group_id": { "type": "string", "description": "Split grouping key; defaults to identity.subject_id." },
        "acquisition_protocol": { "type": "string" }
      }
    },

    "labelSet": {
      "type": "object",
      "required": ["id", "version", "form"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string", "minLength": 1 },
        "version": { "type": "string", "minLength": 1 },
        "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
        "uri": { "type": "string" },
        "form": { "enum": ["inline", "ref"] },
        "classes": { "type": "array", "items": { "$ref": "#/$defs/labelClass" } },
        "relations": { "type": "array", "items": { "$ref": "#/$defs/relation" } },
        "skeletons": { "type": "array", "items": { "$ref": "#/$defs/skeleton" } }
      },
      "allOf": [
        { "if": { "properties": { "form": { "const": "inline" } } },
          "then": { "required": ["classes"] } },
        { "if": { "properties": { "form": { "const": "ref" } } },
          "then": { "required": ["uri", "sha256"] } }
      ]
    },

    "labelClass": {
      "type": "object",
      "required": ["id", "key", "name"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/classId" },
        "key": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_]*$" },
        "name": { "type": "string", "minLength": 1 },
        "parents": { "type": "array", "items": { "$ref": "#/$defs/classId" },
          "description": "is_a parents. Zero or more: the hierarchy is a DAG." },
        "category": { "type": "string" },
        "laterality": { "type": ["string", "null"], "enum": ["left", "right", "bilateral", null] },
        "color": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 255 },
                   "minItems": 4, "maxItems": 4 },
        "codes": { "type": "array", "items": { "$ref": "#/$defs/ontologyCode" } },
        "properties": { "type": "object" }
      }
    },

    "ontologyCode": {
      "type": "object",
      "required": ["system", "code"],
      "additionalProperties": false,
      "properties": {
        "system": { "type": "string", "examples": ["SNOMED-CT", "RadLex", "FMA", "UBERON", "ICD-10", "LOINC"] },
        "code": { "type": "string" },
        "name": { "type": "string" }
      }
    },

    "relation": {
      "type": "object",
      "required": ["subject", "predicate", "object"],
      "additionalProperties": false,
      "properties": {
        "subject": { "$ref": "#/$defs/classId" },
        "predicate": { "type": "string", "examples": ["part_of", "adjacent_to", "supplies", "drains"] },
        "object": { "$ref": "#/$defs/classId" }
      }
    },

    "skeleton": {
      "type": "object",
      "required": ["id", "keypoints", "edges"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/id" },
        "keypoints": { "type": "array", "items": { "$ref": "#/$defs/classId" } },
        "edges": { "type": "array",
          "items": { "type": "array", "items": { "$ref": "#/$defs/classId" }, "minItems": 2, "maxItems": 2 } }
      }
    },

    "provenance": {
      "type": "object",
      "required": ["agents", "activities"],
      "additionalProperties": false,
      "properties": {
        "agents": { "type": "array", "items": { "$ref": "#/$defs/agent" } },
        "activities": { "type": "array", "items": { "$ref": "#/$defs/activity" } }
      }
    },

    "agent": {
      "type": "object",
      "required": ["id", "type", "name"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/id" },
        "type": { "enum": ["person", "software", "organization"] },
        "name": { "type": "string", "minLength": 1,
          "description": "MUST NOT be a direct identifier of a natural person; use a pseudonym." },
        "version": { "type": "string" },
        "role": { "enum": ["annotator", "reviewer", "arbiter", "converter", "predictor", "curator", "other"] },
        "qualification": { "type": "string" },
        "organization": { "$ref": "#/$defs/id" }
      }
    },

    "activity": {
      "type": "object",
      "required": ["id", "type", "agent"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/id" },
        "type": { "enum": ["import", "annotate", "review", "predict", "resample", "register",
                           "derive", "deidentify", "transcode", "other"] },
        "agent": { "$ref": "#/$defs/id" },
        "started": { "$ref": "#/$defs/timestamp" },
        "ended": { "$ref": "#/$defs/timestamp" },
        "tool": { "type": "string" },
        "inputs": { "type": "array", "items": { "type": "string" },
          "description": "HDF5 object paths, or external URIs for out-of-file sources." },
        "outputs": { "type": "array", "items": { "type": "string" } },
        "params": { "type": "object" }
      }
    },

    "qualityRecord": {
      "type": "object",
      "required": ["status"],
      "additionalProperties": false,
      "properties": {
        "status": { "enum": ["draft", "submitted", "reviewed", "approved", "rejected", "deprecated"] },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "reviewed_by": { "type": "array", "items": { "$ref": "#/$defs/id" } },
        "agreement": { "type": "array", "items": { "$ref": "#/$defs/agreement" } },
        "issues": { "type": "array", "items": { "$ref": "#/$defs/issue" } },
        "edit_effort_s": { "type": "number", "minimum": 0 }
      }
    },

    "agreement": {
      "type": "object",
      "required": ["metric", "value", "against"],
      "additionalProperties": false,
      "properties": {
        "metric": { "type": "string", "examples": ["dice", "iou", "hausdorff95", "surface_dice", "kappa", "tre"] },
        "value": { "type": "number" },
        "against": { "type": "string", "description": "HDF5 path of the annotation compared against." },
        "per_class": { "type": "object",
          "patternProperties": { "^[0-9]{1,5}$": { "type": "number" } },
          "additionalProperties": false }
      }
    },

    "issue": {
      "type": "object",
      "required": ["code", "severity"],
      "additionalProperties": false,
      "properties": {
        "code": { "type": "string" },
        "severity": { "enum": ["info", "warning", "error"] },
        "class_ids": { "type": "array", "items": { "$ref": "#/$defs/classId" } },
        "note": { "type": "string" }
      }
    },

    "splitClaim": {
      "type": "object",
      "required": ["set_id", "partition"],
      "additionalProperties": false,
      "properties": {
        "set_id": { "type": "string", "minLength": 1 },
        "partition": { "type": "string", "examples": ["train", "val", "test", "holdout"] },
        "fold": { "type": "integer", "minimum": 0 },
        "assigned_by": { "type": "string" },
        "assigned_at": { "$ref": "#/$defs/timestamp" },
        "manifest_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$",
          "description": "Digest of the authoritative dataset manifest; lets readers detect stale claims." }
      }
    },

    "deidentification": {
      "type": "object",
      "required": ["method"],
      "additionalProperties": false,
      "properties": {
        "method": { "type": "string" },
        "profile": { "type": "string", "examples": ["DICOM PS3.15 E.1 basic", "custom"] },
        "date_shift_days": { "type": "integer" },
        "id_mapping": { "enum": ["external", "irreversible", "none"] },
        "performed_by": { "$ref": "#/$defs/id" },
        "date": { "$ref": "#/$defs/timestamp" },
        "burned_in_annotation_checked": { "type": "boolean" }
      }
    }
  }
}
