{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://aalexmmaldonado.com/schemas/theme-set.schema.json",
  "title": "Course theme set",
  "description": "Output of Stage 1 (theme discovery) and, after human curation, the locked registry consumed by Stage 2. Keys are stable snake_case theme codes; values describe each theme. The reserved code 'unknown' is added during curation and is never produced by discovery.",
  "type": "object",
  "additionalProperties": false,
  "required": ["theme_set_id", "themes"],
  "properties": {
    "theme_set_id": {
      "type": "string",
      "minLength": 1,
      "description": "Identifier for this theme set, e.g., course-CHEM101-2026-fall."
    },
    "source": {
      "type": "object",
      "description": "Optional provenance for the corpus the themes were induced from.",
      "additionalProperties": true,
      "properties": {
        "n_comments": { "type": "integer", "minimum": 0 },
        "course": { "type": "string" },
        "term": { "type": "string" },
        "generated_by": { "type": "string" },
        "generated_at": { "type": "string", "format": "date-time" }
      }
    },
    "themes": {
      "type": "object",
      "minProperties": 1,
      "description": "Map of theme code to theme entry.",
      "propertyNames": {
        "pattern": "^[a-z][a-z0-9_]{0,29}$",
        "description": "snake_case, starts with a letter, up to 30 chars."
      },
      "additionalProperties": { "$ref": "#/$defs/theme" }
    },
    "notes": {
      "type": ["string", "null"],
      "description": "Optional analyst or model notes: overlaps, uncertainties, candidates to merge."
    }
  },

  "$defs": {
    "category": {
      "type": "string",
      "enum": [
        "Clarity of explanation",
        "Organization and structure",
        "Assessment, grading, and feedback",
        "Workload, pace, and difficulty",
        "Course content and materials",
        "Availability and responsiveness",
        "Enthusiasm and engagement",
        "Interaction, inclusion, and climate",
        "Logistics and environment",
        "Non-pedagogical or identity-focused remarks",
        "Off-topic or uninformative"
      ]
    },
    "theme": {
      "type": "object",
      "additionalProperties": false,
      "required": ["label", "definition", "recommended_category"],
      "properties": {
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "Short human-readable name used for display."
        },
        "definition": {
          "type": "string",
          "minLength": 1,
          "description": "One or two sentences stating what belongs under this theme."
        },
        "recommended_category": {
          "description": "Advisory link to one of the eleven stable categories, or null for the reserved 'unknown' theme. Never enforced against a segment's category.",
          "oneOf": [
            { "$ref": "#/$defs/category" },
            { "type": "null" }
          ]
        },
        "inclusion": {
          "type": ["string", "null"],
          "description": "Optional cues for what to include or exclude."
        },
        "example_evidence": {
          "type": "array",
          "maxItems": 5,
          "description": "Short verbatim snippets from the corpus that grounded this theme. Required for discovered themes by the Stage 1 prompt; each snippet is checked downstream to be a substring of some input comment.",
          "items": { "type": "string", "minLength": 1 }
        },
        "prevalence_estimate": {
          "type": ["string", "null"],
          "enum": ["rare", "occasional", "common", "pervasive", null],
          "description": "Coarse prevalence. Coarse on purpose: avoids fabricated exact counts."
        },
        "reserved": {
          "type": "boolean",
          "default": false,
          "description": "true only for the 'unknown' fallback added during curation."
        }
      }
    }
  }
}
