{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://touchstonestandard.org/schemas/0.3/score-result.schema.json",
  "title": "Touchstone score result",
  "description": "The full show-your-work output of scoring an assessment: per-region deduction line items, region sub-scores, the binding (worse) side, points, and the resulting grade/label/band.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "rubric_id",
    "rubric_version",
    "points",
    "grade",
    "grade_label",
    "band",
    "binding_face",
    "binding_region",
    "faces",
    "line_items"
  ],
  "properties": {
    "rubric_id": {
      "type": "string",
      "description": "Which rubric produced this result."
    },
    "rubric_version": {
      "type": "string",
      "description": "Two-part major.minor version of the rubric that produced this result; the npm package uses a separate three-part package version."
    },
    "points": {
      "type": "integer",
      "minimum": 0,
      "description": "The card's points: the worse of the two sides' side_points, floored to an integer."
    },
    "grade": {
      "enum": [
        1,
        1.5,
        2,
        2.5,
        3,
        3.5,
        4,
        4.5,
        5,
        5.5,
        6,
        6.5,
        7,
        7.5,
        8,
        8.5,
        9,
        10
      ],
      "description": "Whole and half grades from 1 through 8.5, then 9 and 10 — there is no 9.5. That slot is promoted to 10, the market's 9→10 value cliff expressed as arithmetic."
    },
    "grade_label": {
      "type": "string",
      "description": "NON-NORMATIVE human-readable rendering of the grade — consumers should map `grade` to their own copy, not parse this. The reference scorer emits the bare numeral (e.g. '10', '8.5')."
    },
    "band": {
      "enum": [
        "NM",
        "LP",
        "MP",
        "HP",
        "DMG"
      ],
      "description": "Condition band the grade falls into."
    },
    "binding_face": {
      "enum": [
        "front",
        "back"
      ],
      "description": "Which face's side_points determined the card's points. Ties resolve to front."
    },
    "binding_region": {
      "enum": [
        "centering",
        "corners",
        "edges",
        "surface",
        "print"
      ],
      "description": "Which region on the binding face determined that side's side_points. Ties resolve to the earliest in this order. These are the scored buckets — four normally, five (adding 'print') when the rubric defines print_attributes. Input regions are singular (corner/edge/surface/crease/stain/print_defect); input 'corner' maps to 'corners', 'edge' to 'edges'; crease and stain pool into 'surface'; print_defect and the print attribute ladders pool into 'print'."
    },
    "faces": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "front",
        "back"
      ],
      "description": "Per-face breakdown: each region's sub-score after penalties, and the side's overall points (the min of the regions).",
      "properties": {
        "front": {
          "$ref": "#/$defs/faceResult"
        },
        "back": {
          "$ref": "#/$defs/faceResult"
        }
      }
    },
    "line_items": {
      "type": "array",
      "description": "One entry per deduction applied, in the order encountered.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "face",
          "region",
          "detail",
          "penalty"
        ],
        "properties": {
          "face": {
            "enum": [
              "front",
              "back"
            ]
          },
          "region": {
            "enum": [
              "centering",
              "corners",
              "edges",
              "surface",
              "print"
            ],
            "description": "The scored bucket this deduction applied to — input regions are singular; input 'corner' maps to 'corners', 'edge' to 'edges'; crease/stain deductions land in 'surface'; print_defect and print attribute-ladder deductions land in 'print' (rubrics that define print_attributes only)."
          },
          "attribute": {
            "enum": [
              "focus",
              "gloss",
              "border"
            ],
            "description": "Present ONLY on print-ATTRIBUTE line items (from front/back print.focus/gloss/border) — discriminates them from print_defect DEFECT items, which also carry region 'print' but never this field. Absent on every other line item (centering/corners/edges/surface/crease/stain/print_defect)."
          },
          "detail": {
            "type": "string",
            "description": "NON-NORMATIVE human-readable summary — consumers should map enums to their own copy, not parse this."
          },
          "penalty": {
            "type": "integer",
            "minimum": 0,
            "description": "Points deducted from that region for this line item."
          }
        }
      }
    }
  },
  "$defs": {
    "faceResult": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "regions",
        "side_points"
      ],
      "properties": {
        "regions": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "centering",
            "corners",
            "edges",
            "surface"
          ],
          "description": "Each region's remaining sub-score for this face, floored at 0. The four listed in `required` always appear; `print` appears only in results produced under a rubric that defines print_attributes (config-gated fifth region) — rubrics without it produce strictly four-region results.",
          "properties": {
            "centering": {
              "type": "integer",
              "minimum": 0
            },
            "corners": {
              "type": "integer",
              "minimum": 0
            },
            "edges": {
              "type": "integer",
              "minimum": 0
            },
            "surface": {
              "type": "integer",
              "minimum": 0
            },
            "print": {
              "type": "integer",
              "minimum": 0,
              "description": "Present only under rubrics with print_attributes: the print region's remaining sub-score (attribute-ladder penalties + pooled placed print defects)."
            }
          }
        },
        "side_points": {
          "type": "integer",
          "minimum": 0,
          "description": "The min of the region sub-scores for this face (four, or five under a rubric with print_attributes) — the pure bottleneck."
        }
      }
    }
  }
}
