guardrail-sim
byJeff Green

Discount Stacking

Control how multiple discounts combine

Discount Stacking

A common challenge: allowing some discount combinations while preventing abuse.

The Scenario

Your B2B pricing policy needs to handle:

  • Volume discounts: Based on order quantity
  • Customer tier discounts: Based on relationship
  • Promotional discounts: Seasonal offers
  • Stacking limits: Preventing abuse

Business rules:

  • Maximum total discount: 25%
  • Margin must stay above 15%
  • Volume orders (100+ units) get up to 15% discount
  • Smaller orders limited to 10%

The Policy

import { PolicyEngine } from '@guardrail-sim/policy-engine';
import type { Policy, PolicyRule } from '@guardrail-sim/policy-engine';
 
const stackingPolicy: Policy = {
  id: 'discount-stacking-rules',
  name: 'Discount Stacking Rules',
  rules: [
    // Overall discount cap
    {
      name: 'max_discount',
      conditions: {
        all: [
          { fact: 'proposed_discount', operator: 'greaterThan', value: 0.25 }
        ]
      },
      event: {
        type: 'violation',
        params: { message: 'Total discount cannot exceed 25%' }
      },
      priority: 1
    },
    // Margin floor
    {
      name: 'margin_floor',
      conditions: {
        all: [
          { fact: 'calculated_margin', operator: 'lessThan', value: 0.15 }
        ]
      },
      event: {
        type: 'violation',
        params: { message: 'Discount would reduce margin below 15%' }
      },
      priority: 2
    },
    // Volume tier limit
    {
      name: 'volume_tier',
      conditions: {
        all: [
          { fact: 'quantity', operator: 'lessThan', value: 100 },
          { fact: 'proposed_discount', operator: 'greaterThan', value: 0.10 }
        ]
      },
      event: {
        type: 'violation',
        params: { message: 'Orders under 100 units limited to 10% discount' }
      },
      priority: 3
    }
  ]
};
 
const engine = new PolicyEngine(stackingPolicy);

Testing Scenarios

// Scenario 1: Small order, small discount (allowed)
const result1 = await engine.evaluate(
  { order_value: 1000, quantity: 50, product_margin: 0.4 },
  0.08  // 8% discount
);
// result1.approved: true
// result1.calculated_margin: 0.32
 
// Scenario 2: Small order, large discount (rejected)
const result2 = await engine.evaluate(
  { order_value: 1000, quantity: 50, product_margin: 0.4 },
  0.15  // 15% discount
);
// result2.approved: false
// result2.violations: [{ rule: 'volume_tier', message: '...' }]
 
// Scenario 3: Large order, large discount (allowed)
const result3 = await engine.evaluate(
  { order_value: 10000, quantity: 200, product_margin: 0.4 },
  0.15  // 15% discount
);
// result3.approved: true
// result3.calculated_margin: 0.25
 
// Scenario 4: Any order, excessive discount (rejected)
const result4 = await engine.evaluate(
  { order_value: 10000, quantity: 200, product_margin: 0.4 },
  0.30  // 30% discount
);
// result4.approved: false
// result4.violations: [{ rule: 'max_discount', message: '...' }]

Using with MCP

Test discount requests via MCP tools:

{
  "name": "evaluate_policy",
  "arguments": {
    "order": {
      "order_value": 5000,
      "quantity": 150,
      "product_margin": 0.4,
      "customer_segment": "gold"
    },
    "proposed_discount": 0.12
  }
}

Finding Maximum Allowed

Use get_max_discount to find the ceiling:

{
  "name": "get_max_discount",
  "arguments": {
    "order": {
      "order_value": 5000,
      "quantity": 50,
      "product_margin": 0.4
    }
  }
}

Response:

{
  "max_discount": 0.10,
  "max_discount_pct": "10%",
  "limiting_factor": "volume_tier",
  "details": "Orders under 100 units limited to 10% discount"
}

On this page