{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://remizo.dev/schemas/v1/evaluation-result.schema.json",
  "title": "EvaluationResult",
  "description": "Result returned by the engine for simulate / evaluate / commit / refund calls. Everything the POS needs to adjust the ticket (discounts per line), print (messages, coupons) and display (balances), plus the explanation of every offer considered. Amounts are integer cents.",
  "type": "object",
  "additionalProperties": false,
  "required": ["evaluationId", "mode", "status", "ticketKey", "totals", "applied", "rejected", "pointsMovements", "balances", "couponsIssued", "couponsRedeemed", "messages", "warnings", "engine"],
  "properties": {
    "schemaVersion": { "type": "string", "const": "1" },
    "evaluationId": { "type": "string", "format": "uuid", "description": "Unique id of this computation (also the trace id suffix)." },
    "transactionId": { "type": ["string", "null"], "format": "uuid", "description": "Persisted transaction id (null in simulate mode)." },
    "ticketKey": { "type": "string", "description": "Canonical key <storeCode>:<posId>:<businessDate>:<ticketNumber>." },
    "mode": { "type": "string", "enum": ["simulate", "evaluate", "commit", "refund"] },
    "status": {
      "type": "string",
      "enum": ["ok", "ok_with_warnings", "customer_unknown", "rejected"],
      "description": "rejected is only used by commit when the pending transaction cannot be committed (see problem details); evaluation errors are HTTP errors, not results."
    },
    "transactionState": { "type": ["string", "null"], "enum": ["pending", "committed", "cancelled", "refunded", "partially_refunded", "expired", null] },
    "idempotentReplay": { "type": "boolean", "default": false, "description": "true when this response was served from the idempotency store." },
    "customer": { "$ref": "#/$defs/CustomerResolution" },
    "totals": { "$ref": "#/$defs/Totals" },
    "lines": {
      "type": "array",
      "items": { "$ref": "#/$defs/LineAdjustment" },
      "description": "Per-line net effect (all offers merged). Lines with no adjustment are omitted."
    },
    "addedLines": {
      "type": "array",
      "items": { "$ref": "#/$defs/AddedLine" },
      "description": "Lines the POS must add (free_product mode add_line)."
    },
    "applied": { "type": "array", "items": { "$ref": "#/$defs/AppliedOffer" } },
    "rejected": { "type": "array", "items": { "$ref": "#/$defs/RejectedOffer" }, "description": "Present when options.explain is true (default)." },
    "pointsMovements": { "type": "array", "items": { "$ref": "#/$defs/PointsMovement" } },
    "balances": { "type": "array", "items": { "$ref": "#/$defs/Balance" }, "description": "Balances after this ticket (projected in simulate/evaluate, actual in commit)." },
    "couponsIssued": { "type": "array", "items": { "$ref": "#/$defs/CouponIssued" } },
    "couponsRedeemed": { "type": "array", "items": { "$ref": "#/$defs/CouponRedeemed" } },
    "couponsRejected": { "type": "array", "items": { "$ref": "#/$defs/CouponRejected" }, "description": "Presented codes that could not be used." },
    "messages": { "type": "array", "items": { "$ref": "#/$defs/Message" } },
    "warnings": { "type": "array", "items": { "$ref": "#/$defs/Warning" } },
    "engine": { "$ref": "#/$defs/EngineInfo" }
  },
  "$defs": {
    "Cents": { "type": "integer" },
    "CustomerResolution": {
      "type": "object",
      "additionalProperties": false,
      "required": ["resolved"],
      "properties": {
        "resolved": { "type": "boolean" },
        "customerId": { "type": "string", "format": "uuid" },
        "accountId": { "type": "string", "format": "uuid" },
        "cardNumber": { "type": "string" },
        "externalRef": { "type": "string" },
        "status": { "type": "string", "enum": ["active", "blocked", "prospect", "anonymized"] },
        "loyaltyStatus": { "type": "string" },
        "firstName": { "type": "string" },
        "created": { "type": "boolean", "description": "true when the card/customer was auto-created by this call." },
        "matchedIdentifier": { "type": "object", "additionalProperties": false, "properties": { "type": { "type": "string" }, "value": { "type": "string" } } },
        "segments": { "type": "array", "items": { "type": "string", "format": "uuid" } }
      }
    },
    "Totals": {
      "type": "object",
      "additionalProperties": false,
      "required": ["grossCents", "discountCents", "netCents"],
      "properties": {
        "grossCents": { "$ref": "#/$defs/Cents", "description": "Ticket gross as received." },
        "discountCents": { "$ref": "#/$defs/Cents", "description": "Total Remizo discount (ticket-level + line-level)." },
        "ticketDiscountCents": { "$ref": "#/$defs/Cents", "description": "Part granted as ticket-level discount (not attributable to one line by the POS)." },
        "lineDiscountCents": { "$ref": "#/$defs/Cents", "description": "Part granted on lines (sum of lines[].discountCents)." },
        "netCents": { "$ref": "#/$defs/Cents", "description": "grossCents - discountCents. Informational; the POS remains the source of truth." },
        "pointsBurnCents": { "$ref": "#/$defs/Cents", "description": "Value of counter units burnt as payment (payments method points / options.pointsBurn)." },
        "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
      }
    },
    "LineAdjustment": {
      "type": "object",
      "additionalProperties": false,
      "required": ["lineNo", "discountCents"],
      "properties": {
        "lineNo": { "type": "integer", "minimum": 1 },
        "discountCents": { "$ref": "#/$defs/Cents", "description": "Total Remizo discount on the line." },
        "qtyAffected": { "type": "number", "description": "Units concerned (for N bought M free)." },
        "newLineTotalCents": { "$ref": "#/$defs/Cents" },
        "pointsEarned": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["counterTypeCode", "amount"], "properties": { "counterTypeCode": { "type": "string" }, "amount": { "type": "integer" } } } },
        "offers": {
          "type": "array",
          "items": {
            "type": "object", "additionalProperties": false, "required": ["offerId", "discountCents"],
            "properties": {
              "offerId": { "type": "string", "format": "uuid" },
              "actionId": { "type": "string" },
              "discountCents": { "$ref": "#/$defs/Cents" },
              "qtyAffected": { "type": "number" }
            }
          }
        }
      }
    },
    "AddedLine": {
      "type": "object",
      "additionalProperties": false,
      "required": ["offerId", "qty", "unitPriceCents"],
      "properties": {
        "offerId": { "type": "string", "format": "uuid" },
        "actionId": { "type": "string" },
        "ean": { "type": "string" },
        "ref": { "type": "string" },
        "name": { "type": "string" },
        "qty": { "type": "integer", "minimum": 1 },
        "unitPriceCents": { "$ref": "#/$defs/Cents", "const": 0 },
        "flags": { "type": "array", "items": { "type": "string" }, "default": ["remizo_free_product"] }
      }
    },
    "AppliedOffer": {
      "type": "object",
      "additionalProperties": false,
      "required": ["offerId", "offerCode", "offerVersion", "name", "level", "mode", "applications", "valueCents", "actions"],
      "properties": {
        "offerId": { "type": "string", "format": "uuid" },
        "offerCode": { "type": "string" },
        "offerVersion": { "type": "integer" },
        "name": { "type": "string" },
        "level": { "type": "integer" },
        "mode": { "type": "string", "enum": ["cumulative", "best_offer", "best_line"] },
        "priority": { "type": "integer" },
        "applications": { "type": "integer", "minimum": 1, "description": "Times the offer repeated in the ticket (limits.perTicket)." },
        "valueCents": { "$ref": "#/$defs/Cents", "description": "Total discount value of this offer (0 for pure points/coupon offers)." },
        "triggeredBy": {
          "type": "object", "additionalProperties": false,
          "properties": {
            "couponCode": { "type": "string" },
            "eventId": { "type": "string", "format": "uuid" },
            "abTestId": { "type": "string", "format": "uuid" },
            "abVariant": { "type": "string" }
          }
        },
        "actions": { "type": "array", "items": { "$ref": "#/$defs/ActionResult" } },
        "explanation": { "type": "string", "description": "Human-readable (workspace locale) summary, e.g. \"Ticket >= 50,00 EUR and store in group IDF: -10%\"." },
        "criteriaTrace": {
          "type": "array",
          "description": "Simulator / explain mode only: evaluation of each criterion node.",
          "items": {
            "type": "object", "additionalProperties": false, "required": ["nodeId", "type", "result"],
            "properties": {
              "nodeId": { "type": "string" },
              "type": { "type": "string" },
              "result": { "type": "boolean" },
              "matchedLines": { "type": "array", "items": { "type": "integer" } },
              "detail": { "type": "string" }
            }
          }
        }
      }
    },
    "ActionResult": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "status"],
      "properties": {
        "actionId": { "type": "string" },
        "type": {
          "type": "string",
          "enum": [
            "discount_amount", "discount_percent", "discount_fixed_price", "cheapest_free", "free_product", "lot_price",
            "points_fixed", "points_per_amount", "points_multiplier", "counter_update",
            "coupon_issue", "message", "customer_tag", "customer_status", "custom_event", "stamp_card"
          ]
        },
        "status": { "type": "string", "enum": ["applied", "skipped", "deferred"], "description": "deferred = will happen at commit (coupon render, customer status...)." },
        "skipReason": { "type": "string", "enum": ["free_product_absent", "no_eligible_lines", "insufficient_counter", "cap_reached", "no_customer", "zero_value"] },
        "discountCents": { "$ref": "#/$defs/Cents" },
        "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["lineNo", "discountCents"], "properties": { "lineNo": { "type": "integer" }, "discountCents": { "$ref": "#/$defs/Cents" }, "qtyAffected": { "type": "number" } } } },
        "counterTypeCode": { "type": "string" },
        "amount": { "type": "integer", "description": "Counter units credited/debited by this action." },
        "couponIds": { "type": "array", "items": { "type": "string", "format": "uuid" } },
        "messageIndex": { "type": "integer", "description": "Index into messages[]." },
        "detail": { "type": "string" },
        "stampCard": {
          "type": "object", "additionalProperties": false, "required": ["stampsRequired", "stampsBefore", "stampsAdded", "stampsAfter", "completed"],
          "description": "Stamp card progress (type stamp_card): stamps on the current card before and after the ticket, completed cards.",
          "properties": {
            "stampsRequired": { "type": "integer" },
            "stampsBefore": { "type": "integer" },
            "stampsAdded": { "type": "integer" },
            "stampsAfter": { "type": "integer" },
            "completed": { "type": "integer" }
          }
        }
      }
    },
    "RejectedOffer": {
      "type": "object",
      "additionalProperties": false,
      "required": ["offerId", "offerCode", "name", "reason"],
      "properties": {
        "offerId": { "type": "string", "format": "uuid" },
        "offerCode": { "type": "string" },
        "offerVersion": { "type": "integer" },
        "name": { "type": "string" },
        "level": { "type": "integer" },
        "reason": { "$ref": "#/$defs/RejectReason" },
        "criterionId": { "type": "string", "description": "First failing criterion node (reason criteria_not_met)." },
        "criterionType": { "type": "string" },
        "byOfferId": { "type": "string", "format": "uuid", "description": "Winning/excluding offer (reasons lost_best_offer, lost_best_line, excluded_by, exclusive_applied)." },
        "detail": { "type": "string" },
        "criteriaTrace": { "$ref": "#/$defs/AppliedOffer/properties/criteriaTrace" }
      }
    },
    "RejectReason": {
      "type": "string",
      "enum": [
        "criteria_not_met", "not_in_validity", "trigger_mismatch", "customer_required", "customer_blocked",
        "excluded_by", "exclusive_applied", "lost_best_offer", "lost_best_line",
        "global_limit_reached", "customer_limit_reached", "store_limit_reached", "ticket_limit_reached",
        "coupon_required", "coupon_invalid", "coupon_expired", "coupon_already_used", "coupon_not_owned",
        "ab_not_in_variant", "suspended", "no_eligible_lines", "zero_value", "action_error"
      ]
    },
    "PointsMovement": {
      "type": "object",
      "additionalProperties": false,
      "required": ["counterTypeCode", "kind", "amount", "state"],
      "properties": {
        "movementId": { "type": ["string", "null"], "format": "uuid", "description": "Ledger id (null until commit)." },
        "counterTypeCode": { "type": "string" },
        "kind": { "type": "string", "enum": ["earn", "burn", "reserve", "release", "adjust", "expire", "reset"] },
        "amount": { "type": "integer", "description": "Signed: positive credit, negative debit." },
        "state": { "type": "string", "enum": ["projected", "pending_activation", "posted", "reserved", "confirmed", "cancelled"] },
        "offerId": { "type": ["string", "null"], "format": "uuid" },
        "reservationId": { "type": ["string", "null"], "format": "uuid" },
        "activatesAt": { "type": ["string", "null"], "format": "date-time" },
        "expiresAt": { "type": ["string", "null"], "format": "date-time" },
        "reason": { "type": "string" }
      }
    },
    "Balance": {
      "type": "object",
      "additionalProperties": false,
      "required": ["counterTypeCode", "name", "unit", "total", "available"],
      "properties": {
        "counterTypeCode": { "type": "string" },
        "name": { "type": "string" },
        "kind": { "type": "string", "enum": ["points", "cashback", "visits", "technical"] },
        "unit": { "type": "string", "description": "pts, EUR cents, visits..." },
        "total": { "type": "integer" },
        "available": { "type": "integer", "description": "total - reserved - pending_activation." },
        "reserved": { "type": "integer" },
        "pending": { "type": "integer", "description": "Credited but not yet active." },
        "previousTotal": { "type": "integer", "description": "Before this ticket." },
        "nextExpiry": { "type": ["object", "null"], "additionalProperties": false, "properties": { "amount": { "type": "integer" }, "at": { "type": "string", "format": "date-time" } } }
      }
    },
    "CouponIssued": {
      "type": "object",
      "additionalProperties": false,
      "required": ["couponId", "code", "format", "templateId", "validFrom", "validTo", "render"],
      "properties": {
        "couponId": { "type": "string", "format": "uuid" },
        "code": { "type": "string" },
        "format": { "type": "string", "enum": ["ean13", "qr", "short"] },
        "templateId": { "type": "string", "format": "uuid" },
        "templateName": { "type": "string" },
        "offerId": { "type": "string", "format": "uuid" },
        "linkedOfferId": { "type": ["string", "null"], "format": "uuid" },
        "faceValue": { "type": "object", "additionalProperties": false, "properties": { "kind": { "type": "string" }, "value": { "type": ["integer", "string"] }, "label": { "type": "string" } } },
        "validFrom": { "type": "string", "format": "date-time" },
        "validTo": { "type": "string", "format": "date-time" },
        "state": { "type": "string", "enum": ["projected", "available"], "description": "projected until commit." },
        "render": {
          "type": "object", "additionalProperties": false, "required": ["mode", "status"],
          "properties": {
            "mode": { "type": "string", "enum": ["none", "png", "pdf"] },
            "status": { "type": "string", "enum": ["none", "pending", "ready", "failed"] },
            "url": { "type": "string", "format": "uri", "description": "Signed hub URL (/v1/public/coupons/{token}); valid even while pending (returns 202 until ready)." }
          }
        },
        "printText": { "type": "string", "description": "Short text to print under the barcode." }
      }
    },
    "CouponRedeemed": {
      "type": "object",
      "additionalProperties": false,
      "required": ["code", "couponId", "offerId", "state"],
      "properties": {
        "code": { "type": "string" },
        "couponId": { "type": "string", "format": "uuid" },
        "offerId": { "type": "string", "format": "uuid" },
        "state": { "type": "string", "enum": ["reserved", "used"], "description": "reserved after evaluate, used after commit." }
      }
    },
    "CouponRejected": {
      "type": "object",
      "additionalProperties": false,
      "required": ["code", "reason"],
      "properties": {
        "code": { "type": "string" },
        "reason": { "type": "string", "enum": ["unknown", "expired", "not_yet_valid", "already_used", "reserved_elsewhere", "cancelled", "not_owned", "no_matching_offer", "offer_criteria_not_met"] },
        "offerId": { "type": ["string", "null"], "format": "uuid" }
      }
    },
    "Message": {
      "type": "object",
      "additionalProperties": false,
      "required": ["text", "placement"],
      "properties": {
        "text": { "type": "string" },
        "placement": { "type": "string", "enum": ["ticket", "screen", "both"] },
        "offerId": { "type": ["string", "null"], "format": "uuid" },
        "kind": { "type": "string", "enum": ["offer", "balance", "coupon", "system"], "default": "offer" }
      }
    },
    "Warning": {
      "type": "object",
      "additionalProperties": false,
      "required": ["code", "message"],
      "properties": {
        "code": {
          "type": "string",
          "enum": [
            "coherence_lines_vs_total", "coherence_item_count", "unknown_store", "currency_mismatch", "customer_auto_created",
            "customer_blocked", "payments_incomplete", "offer_load_stale", "limit_near_exhaustion", "coupon_render_disabled",
            "unknown_counter_type", "line_without_identifier", "duplicate_line_no", "points_reservation_mismatch"
          ]
        },
        "message": { "type": "string" },
        "detail": { "type": "object", "additionalProperties": true }
      }
    },
    "EngineInfo": {
      "type": "object",
      "additionalProperties": false,
      "required": ["instance", "offersLoadedAt", "computeMs"],
      "properties": {
        "instance": { "type": "string", "description": "Container name, e.g. remizo-acme-engine-1." },
        "version": { "type": "string" },
        "offersLoadedAt": { "type": "string", "format": "date-time" },
        "offersCount": { "type": "integer" },
        "computeMs": { "type": "number" },
        "totalMs": { "type": "number" }
      }
    }
  }
}
