{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://chainchoice.io/methodology/spec/receipt.schema.json",
  "title": "ChainChoice Decision Receipt",
  "description": "Canonical receipt format for a recommendation served by the ChainChoice decisioning engine. Every receipt is the artifact of one automated decision; the GDPR Article 22 explanation endpoint references this schema as its source of truth. Field names mirror the recommendation_receipts table: content_hash is SHA-256 over the canonical-JSON immutable block; prev_hash chains receipts within an anon_session_id so tampering breaks the chain.",
  "type": "object",
  "required": ["id", "engine_version", "issued_at", "query", "recommended_provider", "alternatives", "rank_objective", "content_hash"],
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "Stable UUIDv4. Used as the join key across receipts, affiliate_clicks, and partner_outcome_confirmations."
    },
    "engine_version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
      "description": "SemVer of the scoring engine at decision time. A bump means scoring math, weights, or input features changed."
    },
    "issued_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp when the engine emitted this recommendation. Time-of-answer anchor for market regime + provider state."
    },
    "query": {
      "type": "object",
      "required": ["text"],
      "properties": {
        "text": { "type": "string", "minLength": 1, "maxLength": 4000 },
        "category": { "type": "string", "description": "One of the 12 canonical categories (exchange | staking | …)." },
        "intent": { "type": "string", "enum": ["buy", "stake", "earn", "store", "send", "spend", "bridge", "compare"] },
        "asset": { "type": "string", "maxLength": 24 },
        "amount_usd": { "type": "number", "minimum": 0 },
        "mode": { "type": "string", "enum": ["recommend", "compare"], "default": "recommend" }
      }
    },
    "recommended_provider": {
      "type": "object",
      "required": ["id", "name", "score"],
      "properties": {
        "id": { "type": "string" },
        "name": { "type": "string" },
        "score": { "type": "number", "minimum": 0, "maximum": 100 },
        "effective_cost_pct": { "type": ["number", "null"] },
        "breakdown": { "$ref": "#/$defs/score_breakdown" }
      }
    },
    "alternatives": {
      "type": "array",
      "description": "Top-N ranked alternatives the engine considered. Order is canonical rank, never re-shuffled by surface.",
      "items": {
        "type": "object",
        "required": ["id", "name", "rank", "score"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "rank": { "type": "integer", "minimum": 1 },
          "score": { "type": "number", "minimum": 0, "maximum": 100 },
          "breakdown": { "$ref": "#/$defs/score_breakdown" }
        }
      }
    },
    "rank_objective": {
      "type": "string",
      "description": "Plain-language label for the optimization target. Matches the methodology page section ID.",
      "examples": ["lowest_total_cost_at_volume", "highest_net_yield", "lowest_regret", "best_security_posture"]
    },
    "market_regime": {
      "type": "object",
      "description": "Snapshot of the market at issuance — anchors the receipt against drift checks later.",
      "properties": {
        "regime_label": { "type": "string", "enum": ["bull", "bear", "ranging", "volatile", "calm"] },
        "btc_volatility_30d": { "type": "number" },
        "regulatory_pulse": { "type": "string", "description": "Most recent material reg event ID at issuance, or null." }
      }
    },
    "sensitivity": {
      "type": "object",
      "description": "Counterfactual: how stable is the top pick under input perturbation?",
      "properties": {
        "is_stable": { "type": "boolean" },
        "narrative": { "type": "string" },
        "runner_up_delta": { "type": "number", "description": "Score gap between top pick and runner-up, in points." }
      }
    },
    "content_hash": {
      "type": "string",
      "pattern": "^[a-f0-9]{64}$",
      "description": "SHA-256 of the canonical-JSON immutable block (all fields except is_public, bound_user_id, anon_session_id, content_hash, prev_hash). Keys sorted lexically. Tamper-evident."
    },
    "prev_hash": {
      "type": ["string", "null"],
      "pattern": "^[a-f0-9]{64}$",
      "description": "content_hash of the previous receipt within the same anon_session_id. Hash-links the user's receipt chain so a single tampered receipt breaks every subsequent verification. Null for the first receipt in a session."
    },
    "schema_version": {
      "type": "integer",
      "minimum": 1,
      "description": "Receipt schema version. Bump when the immutable block's shape changes; existing receipts retain their original version + hash."
    },
    "explanation_url": {
      "type": "string",
      "format": "uri",
      "description": "GDPR Article 22 endpoint — meaningful information about the logic involved."
    }
  },
  "$defs": {
    "score_breakdown": {
      "type": "object",
      "description": "Per-dimension contributions to the total score. Sum-to-score is not enforced (weights may be non-linear).",
      "additionalProperties": {
        "type": "object",
        "properties": {
          "weight": { "type": "number", "minimum": 0, "maximum": 1 },
          "raw": { "type": "number" },
          "contribution": { "type": "number" }
        }
      }
    }
  }
}
