{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://babelspan.local/schema/work-score.schema.json",
  "title": "WorkScore",
  "description": "Auditable work score object shared by Track A scorecards, Track B read API, and Track C roundtable. Supports dynamic dimensions for fiction and non-fiction cards (D17). Non-fiction cards carry an integrity gate (D23) that can withhold publication independent of dimension scores.",
  "type": "object",
  "required": [
    "id",
    "title",
    "serialization_status",
    "coverage",
    "score_version",
    "l1"
  ],
  "additionalProperties": false,
  "properties": {
    "id": {
      "type": "string",
      "minLength": 1,
      "examples": ["pilot-v0.lotm", "science-card.quantum-physics", "finance-card.behavioral-econ"]
    },
    "title": { "type": "string" },
    "author": { "type": "string" },
    "band": { "type": "string", "enum": ["G-print", "G-science", "G-finance", "G-textbook", "G-pilot"] },
    "source_text": { "type": "string", "format": "uri" },
    "serialization_status": {
      "type": "string",
      "enum": ["completed", "ongoing", "abandoned", "status_uncertain"]
    },
    "coverage": {
      "type": "string",
      "description": "Protocol id, e.g. sample_v0, sample_science_v0, sample_finance_v0. Non-fiction accuracy passes may append +factcheck_v1 (see templates/nonfiction/fact-check-protocol.md)."
    },
    "integrity": {
      "$ref": "#/$defs/integrity",
      "description": "Fact-integrity gate for non-fiction bands (D23). Absent on legacy/fiction cards = treated as clean. disqualified cards MUST NOT publish l1 scores."
    },
    "coverage_notes": { "type": "string" },
    "score_version": {
      "type": "string",
      "description": "Immutable label for this scoring pass"
    },
    "scored_at": { "type": "string", "format": "date" },
    "rubric": { "type": "string", "format": "uri" },
    "l1": {
      "type": "object",
      "description": "Primary dimensions 0–100; null score = not yet evidenced. Dimension names vary by card type.",
      "additionalProperties": { "$ref": "#/$defs/dimScore" }
    },
    "dim_names": {
      "type": "object",
      "description": "Map from English dimension key to Chinese display name (e.g., {\"clarity\": \"清晰度\"})",
      "additionalProperties": { "type": "string" }
    },
    "l1_mean": {
      "type": ["number", "null"],
      "minimum": 0,
      "maximum": 100,
      "description": "Equal-weight mean of dimensions with non-null scores only"
    },
    "evidence_v1": {
      "type": "object",
      "description": "D14 evidence per dimension (quote, why_score, locus, optional contrast). Keys match l1 dimension names.",
      "additionalProperties": {
        "type": "object",
        "properties": {
          "locus": { "type": "string" },
          "quote": { "type": "string" },
          "why_score": { "type": "string" },
          "contrast": { "type": "string" }
        },
        "required": ["quote", "why_score"]
      }
    },
    "l2": {
      "type": ["object", "null"],
      "properties": {
        "note_affinity": { "type": "string" },
        "note_why": { "type": "string" },
        "note_tags": { "type": "array", "items": { "type": "string" } },
        "note_cards": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "locus": { "type": "string" },
              "quote": { "type": "string" },
              "paraphrase": { "type": "string" }
            }
          }
        }
      }
    },
    "l3": {
      "type": "object",
      "description": "Auxiliary only — must not override L1 (D3/D4). Dimension-specific auxiliary data.",
      "additionalProperties": true
    },
    "wechat_url": { "type": ["string", "null"], "format": "uri" },
    "video_url": { "type": ["string", "null"], "format": "uri" },
    "podcast": {
      "type": ["object", "null"],
      "properties": {
        "episodes": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "n": { "type": "integer" },
              "title": { "type": "string" },
              "audio_url": { "type": ["string", "null"], "format": "uri" },
              "script_url": { "type": ["string", "null"], "format": "uri" }
            },
            "required": ["n", "title"]
          }
        }
      }
    },
    "why_worth_reading": {
      "type": ["object", "null"],
      "properties": {
        "hook": { "type": "string" },
        "stake": { "type": "string" },
        "takeaway": { "type": "string" }
      }
    },
    "revisit": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["gap", "severity"],
        "properties": {
          "gap": { "type": "string" },
          "severity": { "type": "string", "enum": ["blocker", "medium", "low", "info"] },
          "status": { "type": "string", "enum": ["open", "closed"] }
        }
      }
    }
  },
  "$defs": {
    "dimScore": {
      "type": ["number", "null"],
      "minimum": 0,
      "maximum": 100,
      "description": "Score for a single dimension, 0–100 or null if not scored"
    },
    "integrity": {
      "type": "object",
      "description": "Fact-integrity gate (D23). Status escalation is one-way during a scoring pass; disputed/disqualified require at least one finding with quote + conflicting_source. Findings may be closed via the revisit[] workflow when rebutted.",
      "required": ["status"],
      "additionalProperties": false,
      "properties": {
        "status": {
          "type": "string",
          "enum": ["clean", "disputed", "disqualified"],
          "description": "clean = no fabrication indicators found in sampled claims. disputed = unsupported assertions presented as fact, or contested claims; card publishes with score cap. disqualified = deliberate fabrication evidenced; l1 scores withheld from publication."
        },
        "claims_checked": {
          "type": "integer",
          "minimum": 0,
          "description": "Number of verifiable factual claims sampled and checked under the fact-check protocol named in coverage."
        },
        "claims_failed": {
          "type": "integer",
          "minimum": 0,
          "description": "Sampled claims that conflict with reliable sources. failure_rate = claims_failed / claims_checked drives the accuracy dimension and score cap."
        },
        "cap": {
          "type": ["number", "null"],
          "minimum": 0,
          "maximum": 100,
          "description": "Published l1_mean ceiling when status = disputed (floor rule). Null when no cap applies. Suggested: 40 when failure_rate > 0.2."
        },
        "findings": {
          "type": "array",
          "description": "Evidence chain for disputed/disqualified status. Each finding is one fabricated or unsupported claim.",
          "items": {
            "type": "object",
            "required": ["claim_locus", "claim_quote", "verdict"],
            "additionalProperties": false,
            "properties": {
              "claim_locus": { "type": "string", "description": "Chapter/section/page of the claim in the source text" },
              "claim_quote": { "type": "string", "description": "Verbatim claim as printed" },
              "verdict": {
                "type": "string",
                "enum": ["fabricated", "unsupported", "outdated"],
                "description": "fabricated = invented fact presented as real (disqualifier). unsupported = speculation/assertion presented as established fact with no source (dispute). outdated = once-true claim superseded by newer knowledge; NOT an integrity fault — route to the currency dimension instead."
              },
              "conflicting_source": {
                "type": "string",
                "description": "Reliable source that refutes the claim. Required when verdict = fabricated."
              },
              "note": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
