{
  "rulesetId": "rs-risk-score",
  "name": "Trip financial risk score",
  "description": "Accumulates a risk score from trip cost, bank balance and purpose; first-match waterfall.",
  "contractVersion": "app-contract@v2",
  "strategy": "first-match",
  "nodes": [
    {
      "type": "start",
      "id": "start",
      "inputs": ["trip.cost", "trip.purpose", "finance.bankBalance"],
      "next": "c-cost"
    },
    {
      "type": "condition",
      "id": "c-cost",
      "expr": { "op": ">", "left": { "field": "trip.cost" }, "right": { "lit": 5000 } },
      "onTrue": "s-cost",
      "onFalse": "c-balance-missing"
    },
    {
      "type": "score",
      "id": "s-cost",
      "accumulator": "risk",
      "delta": { "lit": 20 },
      "next": "c-balance-missing"
    },
    {
      "type": "condition",
      "id": "c-balance-missing",
      "expr": { "op": "is_null", "arg": { "field": "finance.bankBalance" } },
      "onTrue": "s-balance-missing",
      "onFalse": "sv-required-balance"
    },
    {
      "type": "score",
      "id": "s-balance-missing",
      "accumulator": "risk",
      "delta": { "lit": 30 },
      "next": "sw-purpose"
    },
    {
      "type": "setVariable",
      "id": "sv-required-balance",
      "name": "requiredBalance",
      "expr": { "op": "mul", "left": { "field": "trip.cost" }, "right": { "lit": 2 } },
      "next": "c-balance-ratio"
    },
    {
      "type": "condition",
      "id": "c-balance-ratio",
      "expr": { "op": "<", "left": { "field": "finance.bankBalance" }, "right": { "var": "requiredBalance" } },
      "onTrue": "s-balance-low",
      "onFalse": "sw-purpose"
    },
    {
      "type": "score",
      "id": "s-balance-low",
      "accumulator": "risk",
      "delta": { "lit": 15 },
      "next": "sw-purpose"
    },
    {
      "type": "switch",
      "id": "sw-purpose",
      "field": "trip.purpose",
      "cases": {
        "business": "rh-business",
        "tourism": "c-total",
        "family_visit": "c-total"
      },
      "default": "o-unknown-purpose"
    },
    {
      "type": "routeHint",
      "id": "rh-business",
      "hint": "business-desk",
      "next": "s-business"
    },
    {
      "type": "score",
      "id": "s-business",
      "accumulator": "risk",
      "delta": { "lit": -5 },
      "next": "c-total"
    },
    {
      "type": "outcome",
      "id": "o-unknown-purpose",
      "outcome": "FLAG",
      "reasonCode": "UNKNOWN_PURPOSE",
      "reasonText": "Trip purpose '{trip.purpose}' is not recognised.",
      "severity": "warning"
    },
    {
      "type": "condition",
      "id": "c-total",
      "expr": { "op": ">=", "left": { "acc": "risk" }, "right": { "lit": 40 } },
      "onTrue": "o-high-risk",
      "onFalse": "o-low-risk"
    },
    {
      "type": "outcome",
      "id": "o-high-risk",
      "outcome": "FLAG",
      "reasonCode": "HIGH_RISK",
      "reasonText": "Risk score {accumulators.risk} exceeds threshold 40.",
      "severity": "warning"
    },
    {
      "type": "outcome",
      "id": "o-low-risk",
      "outcome": "PASS",
      "reasonCode": "LOW_RISK",
      "reasonText": "Risk score {accumulators.risk} is acceptable.",
      "severity": "info"
    }
  ]
}
