{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://voxtra.ai/schemas/call-summary-v1.json",
  "title": "CallSummaryV1",
  "description": "Structured extraction payload produced by the call-summary LLM worker.",
  "type": "object",
  "required": [
    "schema_version",
    "outcome",
    "summary",
    "contact",
    "intent",
    "objections",
    "followups",
    "booking",
    "metrics",
    "redaction_flags",
    "vertical",
    "transcript_provenance",
    "warnings"
  ],
  "additionalProperties": false,

  "$defs": {

    "FactString": {
      "type": "object",
      "description": "Fact envelope for a string value.",
      "required": ["value", "confidence", "evidence_turn_ids"],
      "additionalProperties": false,
      "properties": {
        "value": { "type": ["string", "null"] },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "evidence_turn_ids": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 }
        },
        "source": { "type": "string" }
      }
    },

    "FactBoolean": {
      "type": "object",
      "description": "Fact envelope for a boolean value.",
      "required": ["value", "confidence", "evidence_turn_ids"],
      "additionalProperties": false,
      "properties": {
        "value": { "type": ["boolean", "null"] },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "evidence_turn_ids": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 }
        },
        "source": { "type": "string" }
      }
    },

    "FactNumber": {
      "type": "object",
      "description": "Fact envelope for a numeric value.",
      "required": ["value", "confidence", "evidence_turn_ids"],
      "additionalProperties": false,
      "properties": {
        "value": { "type": ["number", "null"] },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "evidence_turn_ids": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 }
        },
        "source": { "type": "string" }
      }
    },

    "FactStringArray": {
      "type": "object",
      "description": "Fact envelope for a string-array value.",
      "required": ["value", "confidence", "evidence_turn_ids"],
      "additionalProperties": false,
      "properties": {
        "value": {
          "oneOf": [
            { "type": "array", "items": { "type": "string" } },
            { "type": "null" }
          ]
        },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "evidence_turn_ids": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 }
        },
        "source": { "type": "string" }
      }
    },

    "Objection": {
      "type": "object",
      "required": ["category", "quote", "evidence_turn_ids", "resolved"],
      "additionalProperties": false,
      "properties": {
        "category": {
          "type": "string",
          "enum": [
            "price",
            "timeline",
            "trust",
            "competitor",
            "not_interested",
            "already_represented",
            "wrong_time",
            "other"
          ]
        },
        "quote": { "type": "string", "minLength": 1 },
        "evidence_turn_ids": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 },
          "minItems": 1
        },
        "resolved": { "type": "boolean" }
      }
    },

    "Followup": {
      "type": "object",
      "required": ["task", "owner", "due_by", "evidence_turn_ids"],
      "additionalProperties": false,
      "properties": {
        "task": { "type": "string", "minLength": 1 },
        "owner": {
          "type": "string",
          "enum": ["agent", "advisor", "system", "unknown"]
        },
        "due_by": { "type": ["string", "null"], "description": "ISO 8601 date-time or null." },
        "evidence_turn_ids": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 },
          "minItems": 1
        }
      }
    },

    "RealEstateVertical": {
      "type": "object",
      "description": "Per-vertical facts for real_estate calls.",
      "required": [
        "budget",
        "timeline",
        "area",
        "pre_approved",
        "property_type",
        "beds",
        "baths"
      ],
      "additionalProperties": false,
      "properties": {
        "budget":        { "$ref": "#/$defs/FactString" },
        "timeline":      { "$ref": "#/$defs/FactString" },
        "area":          { "$ref": "#/$defs/FactString" },
        "pre_approved":  { "$ref": "#/$defs/FactBoolean" },
        "property_type": { "$ref": "#/$defs/FactString" },
        "beds":          { "$ref": "#/$defs/FactNumber" },
        "baths":         { "$ref": "#/$defs/FactNumber" }
      }
    },

    "GenericVertical": {
      "type": "object",
      "description": "Generic vertical placeholder. No additional facts.",
      "required": [],
      "additionalProperties": false,
      "properties": {}
    }

  },

  "properties": {

    "schema_version": {
      "type": "integer",
      "const": 1
    },

    "outcome": {
      "type": "string",
      "enum": [
        "booked",
        "qualified",
        "disqualified",
        "callback",
        "abandoned",
        "voicemail",
        "wrong_number",
        "complaint",
        "other"
      ]
    },

    "summary": {
      "type": "object",
      "required": ["headline", "narrative", "next_best_action"],
      "additionalProperties": false,
      "properties": {
        "headline": {
          "type": "string",
          "maxLength": 140,
          "description": "One sentence. Caller name + outcome + key detail."
        },
        "narrative": {
          "type": "string",
          "maxLength": 600,
          "description": "3-5 sentences covering who called, what they want, key objections, and outcome."
        },
        "next_best_action": {
          "type": "string",
          "maxLength": 200,
          "description": "Concrete action item for the human advisor or system."
        }
      }
    },

    "contact": {
      "type": "object",
      "required": ["captured", "name", "email", "phone", "consent_to_followup"],
      "additionalProperties": false,
      "properties": {
        "captured":           { "$ref": "#/$defs/FactBoolean" },
        "name":               { "$ref": "#/$defs/FactString" },
        "email":              { "$ref": "#/$defs/FactString" },
        "phone":              { "$ref": "#/$defs/FactString" },
        "consent_to_followup":{ "$ref": "#/$defs/FactBoolean" }
      }
    },

    "intent": {
      "type": "object",
      "required": ["primary", "secondary"],
      "additionalProperties": false,
      "properties": {
        "primary": {
          "type": "string",
          "enum": [
            "buy",
            "sell",
            "rent",
            "invest",
            "inquire",
            "complaint",
            "unsubscribe",
            "other"
          ]
        },
        "secondary": {
          "type": ["string", "null"],
          "enum": [
            "buy",
            "sell",
            "rent",
            "invest",
            "inquire",
            "complaint",
            "unsubscribe",
            "other",
            null
          ]
        }
      }
    },

    "objections": {
      "type": "array",
      "items": { "$ref": "#/$defs/Objection" }
    },

    "followups": {
      "type": "array",
      "items": { "$ref": "#/$defs/Followup" }
    },

    "booking": {
      "type": "object",
      "required": ["attempted", "succeeded", "scheduled_for", "channel"],
      "additionalProperties": false,
      "properties": {
        "attempted":     { "type": "boolean" },
        "succeeded":     { "type": "boolean" },
        "scheduled_for": { "type": ["string", "null"], "description": "ISO 8601 date-time or null." },
        "channel": {
          "type": ["string", "null"],
          "enum": ["phone", "video", "in_person", "email", null]
        }
      }
    },

    "metrics": {
      "type": "object",
      "required": ["talk_ratio", "interruptions", "dead_air_seconds", "sentiment_overall"],
      "additionalProperties": false,
      "properties": {
        "talk_ratio": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Fraction of speaking time held by the caller (0 = agent spoke everything, 1 = caller spoke everything)."
        },
        "interruptions": {
          "type": "integer",
          "minimum": 0,
          "description": "Number of turns where one party cut off the other mid-sentence."
        },
        "dead_air_seconds": {
          "type": "number",
          "minimum": 0,
          "description": "Total silence duration in seconds (sum of gaps > 2s)."
        },
        "sentiment_overall": {
          "type": "string",
          "enum": ["positive", "neutral", "negative", "mixed"]
        }
      }
    },

    "redaction_flags": {
      "type": "object",
      "required": ["pii", "payment_info", "health_info"],
      "additionalProperties": false,
      "properties": {
        "pii":          { "type": "boolean" },
        "payment_info": { "type": "boolean" },
        "health_info":  { "type": "boolean" }
      }
    },

    "vertical": {
      "type": "object",
      "required": ["id", "facts"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "enum": ["real_estate", "generic"]
        },
        "facts": {
          "oneOf": [
            {
              "if": true,
              "then": {
                "description": "Discriminator is on vertical.id in the parent object. real_estate uses RealEstateVertical; generic uses GenericVertical. AJV evaluates both; the worker enforces matching id+facts at the application layer."
              }
            }
          ],
          "description": "Per-vertical fact block. Shape depends on vertical.id.",
          "oneOf": [
            { "$ref": "#/$defs/RealEstateVertical" },
            { "$ref": "#/$defs/GenericVertical" }
          ]
        }
      }
    },

    "transcript_provenance": {
      "type": "object",
      "required": ["turn_count", "turns_referenced", "transcript_sha256"],
      "additionalProperties": false,
      "properties": {
        "turn_count": {
          "type": "integer",
          "minimum": 0
        },
        "turns_referenced": {
          "type": "array",
          "items": { "type": "integer", "minimum": 1 },
          "description": "Sorted deduplicated list of all turn_ids cited anywhere in this payload."
        },
        "transcript_sha256": {
          "type": "string",
          "description": "SHA-256 hex digest of the canonical transcript JSON string."
        }
      }
    },

    "warnings": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "transcript_empty",
          "transcript_very_short",
          "low_confidence_outcome",
          "contact_not_captured",
          "booking_attempted_but_failed",
          "possible_opt_out_request",
          "possible_complaint",
          "redaction_flag_raised",
          "repair_used",
          "metrics_unavailable"
        ]
      }
    }

  }
}
