{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://touchstonestandard.org/schemas/0.1/assessment-input.schema.json",
  "title": "Touchstone assessment input",
  "description": "A card condition assessment: measured centering plus an enumerated list of defects per face, plus an optional per-face print-quality attribute block. Severity classes are defined in $defs.severity (corner/edge) and $defs.crease_severity / stain_severity / print_defect_severity (their own disjoint ladders).",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "rubric": { "type": "string", "description": "Optional rubric pin as rubric_id@version, e.g. 'default@0.3.0'. Omit for the latest default rubric." },
    "subject": {
      "type": "object", "additionalProperties": false,
      "description": "OPTIONAL card identity, e.g. game 'pokemon', set 'Base Set', number '4', name 'Charizard'. Never scored: it affects no deduction, sub-score, or grade.",
      "properties": { "game": {"type":"string"}, "set": {"type":"string"}, "number": {"type":"string"}, "name": {"type":"string"} }
    },
    "front": { "$ref": "#/$defs/face" },
    "back": { "$ref": "#/$defs/face" }
  },
  "$defs": {
    "severity": {
      "enum": ["de_minimis", "minor", "moderate", "heavy"],
      "description": "Shared corner/edge severity ladder. de_minimis: faint whitening only visible up close or under angled light (the GEM budget is face-dependent because of the ×1.3 front multiplier: a GEM card may keep three in a back region, but only two in a front region — see RUBRIC.md, 'Corner & edge defects'). minor: visible at arm's length but small — visible whitening or a small nick at arm's length; a soft corner touch. moderate: obvious at a glance — the corner point is flattened or rounded but the card layers are intact. heavy: structural — layers visibly separated, frayed, or the corner is fully blunted/bent. NOTE: crease, stain, and print_defect each define their OWN disjoint severity vocabulary ($defs.crease_severity / stain_severity / print_defect_severity) — those words are never valid here and vice versa, even where spelled the same (e.g. 'heavy' is a distinct, independently-penalized word in each vocabulary)."
    },
    "crease_severity": {
      "enum": ["light", "full_card", "heavy", "through_layers"],
      "description": "Crease severity (region=crease; pools into the surface result bucket). Disjoint from $defs.severity — e.g. 'minor' is NOT a valid crease severity even though it's valid for corner/edge/print_defect. light: a single light crease, doesn't break the surface. full_card: a crease running the full length of the card. heavy: multiple or more pronounced creases. through_layers: the crease breaks through the card's layers (a paper split)."
    },
    "stain_severity": {
      "enum": ["very_slight", "slight", "obvious", "heavy"],
      "description": "Stain severity (region=stain; pools into the surface result bucket). Disjoint from $defs.severity. Penalized per-face — a back stain is forgiven far more than the same severity on the front (rubric.stain_penalties.<severity>.{front,back}); the generic front_defect_multiplier does NOT apply. very_slight: a faint mark, e.g. light wax transfer on the back. slight: a small, definite stain. obvious: a clearly visible stain. heavy: a large or dark stain."
    },
    "print_defect_severity": {
      "enum": ["slight", "minor", "blemish", "heavy"],
      "description": "Print-defect severity (region=print_defect — a placed line/dot/snow-type printing flaw; pools into the print result bucket, requires a rubric with both print_defect_penalties and print_attributes). Disjoint from $defs.severity. slight: a single minor printing imperfection — GEM-compatible: one on its own still grades 10. minor: a small but definite print flaw. blemish: an obvious print blemish. heavy: a significant print defect."
    },
    "line_endpoint": {
      "type": "number", "minimum": 0, "maximum": 1,
      "description": "Line endpoint coordinate (normalized 0–1 from the top-left of the face, same semantics as x/y). An OPTIONAL paired x2/y2 turns a placed surface/crease/edge defect into a line segment — a presentational/derivation aid (e.g. a UI derives the size class from the drawn physical length), NEVER scored: the reference scorer validates the pair when present and the arithmetic ignores it, so a defect with endpoints scores identically to the same defect without them. x2 and y2 must be provided together (dependentRequired, both directions). Only the three linear regions declare them; point-only regions (corner/stain/print_defect) do not."
    },
    "face": {
      "type": "object", "additionalProperties": false,
      "properties": {
        "centering": {
          "type": "object", "additionalProperties": false,
          "description": "Measured border centering. 50.0 = perfect. lr_pct = the left border's share of left+right border width; tb_pct = the top border's share of top+bottom. A card described as '60/40 left-right' has lr_pct 60. Defaults to 50/50 if omitted.",
          "properties": { "lr_pct": { "type": "number", "minimum": 0, "maximum": 100 },
                          "tb_pct": { "type": "number", "minimum": 0, "maximum": 100 } }
        },
        "defects": { "type": "array", "items": { "$ref": "#/$defs/defect" } },
        "print": {
          "type": "object", "additionalProperties": false,
          "description": "Per-face print-quality attribute ladders (requires a rubric with print_attributes — omit entirely under rubrics that don't define it, or scoring fails loudly). focus, gloss, and border are assessed independently PER FACE — a front can be sharp while the back is slightly out, etc. Penalties are absolute mid-band grade fits and are multiplier-EXEMPT: the front_defect_multiplier applied to corner/edge/surface defects does NOT apply here. Omitting an attribute — or the whole print object — means perfect (no penalty) for that attribute.",
          "properties": {
            "focus":  { "enum": ["sharp", "slightly_out", "noticeably_out", "severely_out"], "description": "Print focus/registration sharpness. Grade caps: sharp 10, slightly_out 7, noticeably_out 5, severely_out 2." },
            "gloss":  { "enum": ["full", "most_retained", "some_loss", "much_lost", "absent"], "description": "Surface gloss/sheen retention. Grade caps: full 10, most_retained 7, some_loss 6, much_lost 3, absent 2." },
            "border": { "enum": ["clean", "slightly_off_white", "yellowed", "browned"], "description": "Border/paper whiteness. Grade caps: clean 10, slightly_off_white 9, yellowed 3, browned 1.5." }
          }
        }
      }
    },
    "defect": {
      "type": "object",
      "$comment": "Deliberately NOT additionalProperties:false — defects may carry annotations (note, future fields); the reference scorer ignores unknown defect fields. Everything ABOVE this level is strict. Region-specific constraints are enforced by the six allOf/if/then branches below (one per region value), each mirroring what the reference scorer enforces: corner requires corner+severity; edge requires edge+severity; surface requires depth+size+x+y; crease/stain/print_defect require severity+x+y. The three LINEAR regions (surface/crease/edge) additionally declare the optional paired line endpoints x2/y2 ($defs.line_endpoint — presentational, never scored); the scorer validates them only on those regions, so they are deliberately NOT declared at this level. Do NOT widen $defs.severity into a shared union across regions: crease/stain/print_defect each have their own DISJOINT severity vocabulary and the reference scorer rejects cross-vocabulary combinations (e.g. region=crease with severity=minor is invalid even though 'minor' is a valid corner/edge/print_defect word).",
      "description": "One defect. One object per distinct flaw; multiple defects may share a corner/edge; penalties sum within the face's region. The named region is canonical for scoring; x,y (normalized 0-1 from the top-left of the face) are REQUIRED for surface, crease, stain, and print_defect defects (location matters for placed flaws) and optional annotation for corner/edge defects. surface/crease/edge defects MAY additionally carry paired line endpoints x2,y2 (see $defs.line_endpoint — presentational/derivation aid, never scored).",
      "required": ["region"],
      "properties": {
        "region": { "enum": ["corner", "edge", "surface", "crease", "stain", "print_defect"], "description": "corner/edge defects need severity (shared vocabulary, $defs.severity); surface defects need depth+size+x,y; crease/stain/print_defect defects need severity — each its OWN disjoint vocabulary ($defs.crease_severity / stain_severity / print_defect_severity) — plus x,y. Input regions are singular; the score result reports the scored buckets (centering/corners/edges/surface/print — print only appears when the rubric defines print_attributes). Input 'corner' maps to result region 'corners', 'edge' to 'edges'; crease and stain pool into the 'surface' result bucket (binding_region 'surface' no longer implies scuff/scratch/deep-type wear specifically — it may be a crease or stain), and print_defect pools into the 'print' result bucket." },
        "corner": { "enum": ["tl", "tr", "bl", "br"], "description": "Which corner (required when region=corner): tl=top-left, tr=top-right, bl=bottom-left, br=bottom-right." },
        "edge": { "enum": ["top", "right", "bottom", "left"], "description": "Which edge (required when region=edge)." },
        "severity": { "type": "string", "description": "Region-specific severity word. corner/edge share $defs.severity (de_minimis/minor/moderate/heavy). crease/stain/print_defect each define their OWN vocabulary — see $defs.crease_severity / stain_severity / print_defect_severity respectively — enforced below by the allOf/if/then blocks keyed on `region`." },
        "depth": { "enum": ["surface", "scratch", "deep"], "description": "Surface defects only. surface: does not break the gloss (scuff, print line). scratch: a visible line you can catch a fingernail on. deep: a crease or break in the paper." },
        "size": { "enum": ["dot", "lt_1cm", "lt_5cm", "full_card"], "description": "Surface defects only. dot: ~2mm or less. lt_1cm: under 1cm. lt_5cm: under 5cm. full_card: spans most of the card." },
        "x": { "type": "number", "minimum": 0, "maximum": 1 },
        "y": { "type": "number", "minimum": 0, "maximum": 1 },
        "note": { "type": "string", "maxLength": 200, "description": "Free-text annotation; never affects the score." }
      },
      "allOf": [
        { "if": { "properties": { "region": { "const": "corner" } }, "required": ["region"] },
          "then": { "required": ["corner", "severity"], "properties": { "severity": { "$ref": "#/$defs/severity" } } } },
        { "if": { "properties": { "region": { "const": "edge" } }, "required": ["region"] },
          "then": { "required": ["edge", "severity"],
                    "properties": { "severity": { "$ref": "#/$defs/severity" },
                                    "x2": { "$ref": "#/$defs/line_endpoint" }, "y2": { "$ref": "#/$defs/line_endpoint" } },
                    "dependentRequired": { "x2": ["y2"], "y2": ["x2"] } } },
        { "if": { "properties": { "region": { "const": "surface" } }, "required": ["region"] },
          "then": { "required": ["depth", "size", "x", "y"],
                    "properties": { "x2": { "$ref": "#/$defs/line_endpoint" }, "y2": { "$ref": "#/$defs/line_endpoint" } },
                    "dependentRequired": { "x2": ["y2"], "y2": ["x2"] } } },
        { "if": { "properties": { "region": { "const": "crease" } }, "required": ["region"] },
          "then": { "required": ["severity", "x", "y"],
                    "properties": { "severity": { "$ref": "#/$defs/crease_severity" },
                                    "x2": { "$ref": "#/$defs/line_endpoint" }, "y2": { "$ref": "#/$defs/line_endpoint" } },
                    "dependentRequired": { "x2": ["y2"], "y2": ["x2"] } } },
        { "if": { "properties": { "region": { "const": "stain" } }, "required": ["region"] },
          "then": { "required": ["severity", "x", "y"], "properties": { "severity": { "$ref": "#/$defs/stain_severity" } } } },
        { "if": { "properties": { "region": { "const": "print_defect" } }, "required": ["region"] },
          "then": { "required": ["severity", "x", "y"], "properties": { "severity": { "$ref": "#/$defs/print_defect_severity" } } } }
      ]
    }
  }
}
