***

## 概述

### 什么是策略模型？

策略模型是一个**高度抽象、完全通用**的规则引擎系统，用于处理各种业务场景中的条件判断、限制校验和动作执行。

### 适用场景

* ✅ 营销活动（优惠券、满减、满赠、折扣）

* ✅ Wallet Pass（储值卡、礼品卡、代金券、积分券、次卡）

* ✅ 会员权益（等级权益、积分规则）

* ✅ 价格策略（动态定价、差异化定价）

* ✅ 库存规则（限购、预售）

* ✅ 任何需要条件判断和规则执行的业务场景

### 核心特点

| 特点        | 说明                           |
| --------- | ---------------------------- |
| **完全解耦**  | 策略模型不包含任何业务概念，完全通用           |
| **高度抽象**  | 所有配置项均为可扩展的键值对结构             |
| **无状态设计** | 纯函数式计算，相同输入产生相同输出            |
| **分层架构**  | Config层、Engine层、Adapter层清晰分离 |
| **易于扩展**  | 新增业务场景只需实现适配器，无需修改引擎         |

***

## 核心概念与名词解释

### 配置相关

#### StrategyConfig（策略配置）

**是什么**：一份完整的策略规则文档，以 JSON 格式存储。

**用来干什么**：定义"什么情况下可以使用"以及"能得到什么优惠"的完整规则。

**在哪使用**：由业务人员或产品经理配置，存储在nocobase中，前端/后端从数据库读取这份配置来判断策略是否适用。

**包含内容**：

* 策略的基本信息（ID、名称、类型）

* 适用条件（哪些商品、哪些用户、什么时间段）

* 可执行的动作（减多少钱、送什么礼品）

***



#### ConditionGroup（条件组）

**是什么**：一组判断规则，用来决定策略是否可以使用。

**用来干什么**：检查当前情况是否满足策略的使用条件，比如"订单金额是否大于100"、"商品是否在适用范围内"。

**在哪使用**：策略引擎在评估策略时，会按照条件组的规则逐层检查，只有所有条件都满足时，策略才适用。

**特点**：

* 可以嵌套（条件里包含条件）

* 支持"并且"、"或者"、"非"等逻辑关系

* 每一层可以绑定不同的动作

***

#### ActionEffect（执行动作）

**是什么**：策略生效后要执行的具体操作。

**用来干什么**：定义用户能获得的优惠或权益，比如"抵扣10元"、"打9折"、"送赠品"、"加50积分"。

**在哪使用**：当条件满足时，策略引擎会找到对应的动作，业务层根据这些动作计算最终的优惠金额并应用到订单上。

**包含内容**：

* 动作类型（减价、折扣、赠品等）

* 具体数值（减多少、打几折）

* 优先级（多个动作时的执行顺序）

***

### 执行相关

#### RuntimeContext（运行时上下文）

**是什么**：策略执行时需要的所有实时数据。

**用来干什么**：提供"当前是什么情况"的信息，让策略引擎可以判断条件是否满足。

**在哪使用**：前端或后端在调用策略引擎时，需要把当前的订单信息、商品信息、用户信息等打包成 RuntimeContext 传给引擎。

**包含内容**：

* 订单数据（总金额、商品列表、商品数量）

* 用户数据（用户ID、会员等级）

* 时间数据（当前时间、日期）

* 使用记录（已使用次数）

***

#### EvaluationResult（评估结果）

**是什么**：策略引擎执行后返回的结果报告。

**用来干什么**：告诉业务层"这个策略能不能用"、"如果能用，有哪些优惠"、"最终能省多少钱"。

**在哪使用**：前端收到这个结果后，根据结果显示"可用/不可用"状态、显示抵扣金额、更新订单总价。

**包含内容**：

* `applicable`：策略是否可用（true/false）

* `matchedActions`：匹配到的动作列表

* `outputs`：计算好的业务结果（抵扣金额、使用明细、推荐信息）

* `code` 和 `message`：结果码和提示信息（如"条件不满足"、"使用次数已达上限"）

***

### 系统组件

#### StrategyEngine（策略引擎）

**是什么**：策略模型的"计算器"，负责计算规则。

**用来干什么**：读取策略配置（StrategyConfig），根据运行时上下文（RuntimeContext），判断条件是否满足，找出应该执行的动作。

**在哪使用**：在策略模型的核心层，完全不包含任何业务逻辑，只负责纯粹的条件判断和动作匹配。

**工作流程**：

1. 接收策略配置和当前情况

2. 递归评估所有条件

3. 收集满足条件的动作

4. 按优先级排序返回

**特点**：通用、可复用、业务无关



***

#### BusinessAdapter（业务适配器）

**是什么**：策略模型的"翻译官"，连接通用引擎和具体业务。

**用来干什么**：

* 把业务数据转换成引擎能理解的格式（RuntimeContext）

* 执行引擎返回的动作（matchedActions）

* 计算具体的业务结果（抵扣多少钱、如何分摊）

**在哪使用**：不同业务场景有不同的适配器：

* `WalletPassAdapter`：处理代金券、储值卡

* `PromotionAdapter`：处理促销活动、满减满赠

* `MembershipAdapter`：处理会员权益

**举例**：就像不同国家使用不同货币，但都需要"翻译"成统一的标准才能比较，适配器就是做这个"翻译"工作的。

***

## 设计目标

### 1. 统一策略引擎

所有业务场景共用一套策略计算逻辑，避免重复开发。

### 2. 极强扩展性

* 支持任意条件维度（transaction、entity、attribute...）

* 支持任意运算符（=、>、<、in、contains...）

* 支持任意动作类型（deduct、discount、gift...）

* 支持嵌套条件组合（and、or、not）

### 3. 业务无关性

引擎代码不包含任何业务逻辑，业务逻辑通过适配器实现。

### 4. 高性能

* 支持规则缓存

* 支持批量计算

* 支持异步执行

***

## 核心架构

### 架构图

![](images/diagram.png)



![](images/diagram-1.png)

### 职责划分

| 层级       | 职责                  | 是否包含业务逻辑  |
| -------- | ------------------- | --------- |
| **业务层**  | 多券组合、推荐算法、用户交互、订单管理 | ✅ 是       |
| **适配器层** | 数据转换、业务特定计算         | ✅ 是       |
| **引擎层**  | 范围检查、条件评估、限制校验、动作执行 | ❌ 否（完全通用） |
| **配置层**  | 策略定义（JSON数据）        | ❌ 否（纯数据）  |

***

## 数据结构定义

### 核心类型概览

```typescript
// 策略配置
interface StrategyConfig {
  metadata: StrategyMetadata;
  conditions: ConditionGroup; 
  actions: ActionEffect[];
  display?: DisplayConfig;
}

// 运行时上下文
interface RuntimeContext {
  entities: Record<string, any>;
  attributes: Record<string, any>;
  metadata: ContextMetadata;
}

// 执行结果
interface EvaluationResult {
  success: boolean;
  applicable: boolean;
  code: string;
  message?: string;
  matched: MatchedInfo;
  matchedActions: ActionEffect[]; 
  outputs: Record<string, any>;
  trace?: ExecutionTrace;
}
```

***

## 策略配置规范

### 1. StrategyConfig - 策略配置主结构

```typescript
interface StrategyConfig {
  /**
   * 元信息
   */
  metadata: StrategyMetadata;
  
  /**
   * 条件组
   * 可嵌套的条件结构，每一层都可以绑定动作
   * 通过 actionIds 关联要执行的动作
   */
  conditions: ConditionGroup;
  
  /**
   * 动作列表
   * 所有可用的动作，通过 id 被条件引用
   */
  actions: ActionEffect[];
  
  /**
   * 展示配置（可选）
   */
  display?: DisplayConfig;
}
```

***

### 2. StrategyMetadata - 元信息

```typescript
interface StrategyMetadata {
  /**
   * 策略唯一标识
   */
  id: string;
  
  /**
   * 策略名称
   */
  name: string | Record<string, string>;
  
  /**
   * 业务类型（用于适配器路由）
   * 例如: "wallet_pass", "promotion", "coupon", "membership"
   */
  type: string;
  
  /**
   * 描述说明
   */
  description?: string | Record<string, string>;
  
  /**
   * 备注
   */
  note?: Record<string, string>;
}
```

***

### 3. Conditions - 触发条件

```typescript
/**
 * 条件组（支持嵌套）
 */
interface ConditionGroup {
  /**
   * 逻辑运算符
   */
  operator: "and" | "or" | "not";
  
  /**
   * 条件规则列表（可以是单条件或嵌套的条件组）
   */
  rules: Array<ConditionRule | ConditionGroup>;
  
  /**
   * 关联的动作ID列表（必填）
   * 当此条件组满足时，执行这些动作
   * 可以为空数组 [] 表示此层不绑定动作
   * 嵌套的每一层都必须配置此字段
   */
  actionIds: string[];
}

/**
 * 条件规则（原子条件）支持code模式 使用eval执行 
  （context）{
   return context
  }
 */
interface ConditionRule {
  type: "code" | "operator";
  code?: string;
  /**
   * 维度（完全可扩展）
   * 常见维度: "transaction", "entity", "attribute", "time", "custom"
   */
  dimension: string;
  
  /**
   * 字段名（完全可扩展）
   * 例如: "amount", "count", "status", "level"
   */
  field: string;
  
  /**
   * 运算符（完全可扩展）
   * 常见运算符: "=", "!=", ">", ">=", "<", "<=", "in", "not_in", "contains", "not_contains", "between", "regex"
   */
  operator: string;
  
  /**
   * 比较值
   */
  value: any;
  
  /**
   * 值类型（用于类型转换）
   */
  valueType?: "number" | "string" | "boolean" | "array" | "object" | "date";
  
  /**
   * 值单位（用于展示）
   */
  valueUnit?: string;
  
  /**
   * 计算目标（可选）
   * 例如: "all_products", "applicable_products"
   */
  target?: string;
  
  /**
   * 额外配置
   */
  config?: Record<string, any>;
}
```

**Conditions 配置示例：**

```typescript
// 完整的 Wallet Pass 代金券条件组配置
{
  operator: "and",
  rules: [
    // ========== 范围维度 ==========
    // 适用商品
    {
      dimension: "scope_product",
      field: "productId",
      operator: "in",
      value: [60734, 60735],
      valueType: "array"
    },
    
    // 适用渠道
    {
      dimension: "scope_channel",
      field: "channel",
      operator: "in",
      value: ["pos", "mini_program"],
      valueType: "array"
    },
    
    // 适用订单类型
    {
      dimension: "scope_order_type",
      field: "orderType",
      operator: "in",
      value: ["retail", "booking"],
      valueType: "array"
    },
    
    // 有效期
    {
      dimension: "time",
      field: "currentTime",
      operator: "between",
      value: ["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
      valueType: "date"
    },
    
    // ========== 交易维度（触发条件）==========
    // 适用商品总金额 >= 100
    {
      dimension: "transaction",
      field: "applicableProductTotal",
      operator: ">=",
      value: 100,
      valueType: "number",
      valueUnit: "元",
      scope: "applicable"
    },
    
    // ========== 限制维度 ==========
    // 每用户最多使用1次
    {
      dimension: "limit_usage",
      field: "perUserUsedCount",
      operator: "<",
      value: 1,
      valueType: "number"
    },
    
    // 每单最多使用1张
    {
      dimension: "limit_usage",
      field: "perTransactionUsedCount",
      operator: "<",
      value: 1,
      valueType: "number"
    },
    
    // 最小使用金额
    {
      dimension: "limit_amount",
      field: "applicableProductTotal",
      operator: ">=",
      value: 100,
      valueType: "number"
    }
  ],
  actionIds: ["deduct_amount"]  // 绑定抵扣动作
}

// 示例2：嵌套条件 - 阶梯满减
{
  operator: "or",  // 满足任一档位
  rules: [
    // 档位1：100-199 减10
    {
      operator: "and",
      rules: [
        {
          dimension: "transaction",
          field: "orderTotal",
          operator: ">=",
          value: 100
        },
        {
          dimension: "transaction",
          field: "orderTotal",
          operator: "<",
          value: 200
        }
      ],
      actionIds: ["deduct_10"]  // 嵌套层绑定动作
    },
    // 档位2：200-299 减25
    {
      operator: "and",
      rules: [
        {
          dimension: "transaction",
          field: "orderTotal",
          operator: ">=",
          value: 200
        },
        {
          dimension: "transaction",
          field: "orderTotal",
          operator: "<",
          value: 300
        }
      ],
      actionIds: ["deduct_25"]
    },
    // 档位3：>=300 减40
    {
      operator: "and",
      rules: [
        {
          dimension: "transaction",
          field: "orderTotal",
          operator: ">=",
          value: 300
        }
      ],
      actionIds: ["deduct_40"]
    }
  ],
  actionIds: []  // 外层不绑定，由嵌套层决定
}

// 示例3：复杂嵌套条件
{
  operator: "and",
  rules: [
    // 范围：指定商品
    {
      dimension: "scope_product",
      field: "productId",
      operator: "in",
      value: [1, 2, 3]
    },
    
    // 条件：订单金额>=100 或 (是VIP且购买数量>=2)
    {
      operator: "or",
      rules: [
        {
          dimension: "transaction",
          field: "orderTotal",
          operator: ">=",
          value: 100
        },
        {
          operator: "and",
          rules: [
            {
              dimension: "entity",
              field: "customerLevel",
              operator: "=",
              value: "vip"
            },
            {
              dimension: "transaction",
              field: "productCount",
              operator: ">=",
              value: 2,
              scope: "applicable"
            }
          ],
          actionIds: []  // 嵌套条件也必须配置actionIds
        }
      ],
      actionIds: []  // 嵌套条件必须配置actionIds
    },
    
    // 限制：总使用次数
    {
      dimension: "limit_usage",
      field: "totalUsedCount",
      operator: "<",
      value: 1000
    }
  ],
  actionIds: ["discount_action"]  // 外层绑定动作
}
```

***

### 4. ActionEffect - 执行动作

```typescript
/**
 * 动作效果
 */
interface ActionEffect {
  /**
   * 动作的唯一标识
   * 用于被条件组的 actionIds 引用
   */
  id: string;
  
  /**
   * 效果类型（完全可扩展）
   * 常见类型: 
   * - DEDUCT_AMOUNT: 抵扣金额
   * - DISCOUNT_RATE: 折扣率
   * - CHANGE_AMOUNT: 改价
   * - FREE_ITEM: 赠品
   * - ADD_POINTS: 增加积分
   */
  type: string;
  
  /**
   * 运算符（可选）
   * - set: 设置为
   * - add: 增加
   * - subtract: 减少
   * - multiply: 乘以
   * - divide: 除以
   */
  operator?: string;
  
  /**
   * 值
   */
  value: any;
  
  /**
   * 值类型
   */
  valueType?: "number" | "string" | "boolean" | "array" | "object";
  
  /**
   * 值单位
   */
  valueUnit?: string;
  
  /**
   * 作用目标（完全可扩展）
   * 例如: "order", "product", "shipping", "applicable_products"
   */
  target: string;
  
  /**
   * 优先级（数字越大优先级越高）
   * 用于排序多个匹配的动作
   * 当多个条件层都满足时，按此优先级排序返回的动作
   */
  priority?: number;
  
  /**
   * 配置（完全可扩展）
   */
  config?: Record<string, any>;
}
```

**完整配置示例：**

```typescript
// ===== 示例1: 简单的代金券（单层条件）=====
{
  metadata: {
    id: "VOUCHER_10",
    name: "10元代金券",
    type: "wallet_pass",
    calculationType: "single"
  },
  
  // 条件组
  conditions: {
    operator: "and",
    rules: [
      {
        dimension: "scope_product",
        field: "productId",
        operator: "in",
        value: [60734, 60735]
      },
      {
        dimension: "scope_channel",
        field: "channel",
        operator: "in",
        value: ["pos", "mini_program"]
      },
      {
        dimension: "transaction",
        field: "applicableProductTotal",
        operator: ">=",
        value: 100
      },
      {
        dimension: "limit_usage",
        field: "perUserUsedCount",
        operator: "<",
        value: 1
      }
    ],
    actionIds: ["deduct_10"]  // 绑定动作
  },
  
  // 动作列表
  actions: [
    {
      id: "deduct_10",
      type: "DEDUCT_AMOUNT",
      value: 10,
      valueType: "number",
      valueUnit: "元",
      target: "applicable_products",
      priority: 1,
      config: {
        allowCrossProduct: true,
        deductTaxAndFee: true
      }
    }
  ]
}

// ===== 示例2: 阶梯式满减（嵌套条件）=====
// 满100减10，满200减25，满300减40
{
  metadata: {
    id: "TIERED_DISCOUNT",
    name: "阶梯满减",
    type: "promotion",
    calculationType: "single"
  },
  
  // 条件组（嵌套结构）
  conditions: {
    operator: "or",  // 满足任一档位即可
    rules: [
      // 档位1：100-199 减10
      {
        operator: "and",
        rules: [
          {
            dimension: "transaction",
            field: "orderTotal",
            operator: ">=",
            value: 100
          },
          {
            dimension: "transaction",
            field: "orderTotal",
            operator: "<",
            value: 200
          }
        ],
        actionIds: ["deduct_10"]  // 嵌套层绑定动作
      },
      // 档位2：200-299 减25
      {
        operator: "and",
        rules: [
          {
            dimension: "transaction",
            field: "orderTotal",
            operator: ">=",
            value: 200
          },
          {
            dimension: "transaction",
            field: "orderTotal",
            operator: "<",
            value: 300
          }
        ],
        actionIds: ["deduct_25"]
      },
      // 档位3：>= 300 减40
      {
        operator: "and",
        rules: [
          {
            dimension: "transaction",
            field: "orderTotal",
            operator: ">=",
            value: 300
          }
        ],
        actionIds: ["deduct_40"]
      }
    ],
    actionIds: []  // 外层不绑定动作，由嵌套层决定
  },
  
  // 动作列表
  actions: [
    {
      id: "deduct_10",
      type: "DEDUCT_AMOUNT",
      value: 10,
      target: "order",
      priority: 1
    },
    {
      id: "deduct_25",
      type: "DEDUCT_AMOUNT",
      value: 25,
      target: "order",
      priority: 2
    },
    {
      id: "deduct_40",
      type: "DEDUCT_AMOUNT",
      value: 40,
      target: "order",
      priority: 3
    }
  ]
}

// ===== 示例3: 多层嵌套 + 外层内层都绑定动作 =====
// 适用商品的会员享折扣，并且满200额外送礼品
{
  metadata: {
    id: "MEMBER_DISCOUNT_WITH_GIFT",
    name: "会员折扣+赠品",
    type: "promotion",
    calculationType: "single"
  },
  
  // 条件组（多层嵌套）
  conditions: {
    operator: "and",
    rules: [
      // 基础条件：适用商品范围
      {
        dimension: "scope_product",
        field: "productId",
        operator: "in",
        value: [1, 2, 3]
      },
      
      // 嵌套条件：会员等级（任一即可）
      {
        operator: "or",
        rules: [
          {
            operator: "and",
            rules: [
              {
                dimension: "entity",
                field: "customerLevel",
                operator: "=",
                value: "vip"
              }
            ],
            actionIds: ["discount_90"]  // VIP 9折
          },
          {
            operator: "and",
            rules: [
              {
                dimension: "entity",
                field: "customerLevel",
                operator: "=",
                value: "svip"
              }
            ],
            actionIds: ["discount_85"]  // SVIP 85折
          }
        ],
        actionIds: []  // 此层不绑定，由子层决定
      },
      
      // 嵌套条件：满200送礼品
      {
        operator: "and",
        rules: [
          {
            dimension: "transaction",
            field: "orderTotal",
            operator: ">=",
            value: 200
          }
        ],
        actionIds: ["free_gift"]  // 送礼品
      }
    ],
    actionIds: []  // 最外层不绑定，由嵌套层决定
  },
  
  // 动作列表
  actions: [
    {
      id: "discount_90",
      type: "DISCOUNT_RATE",
      value: 0.9,
      target: "applicable_products",
      priority: 2  // 优先级较高，先执行折扣
    },
    {
      id: "discount_85",
      type: "DISCOUNT_RATE",
      value: 0.85,
      target: "applicable_products",
      priority: 3  // 优先级更高
    },
    {
      id: "free_gift",
      type: "FREE_ITEM",
      value: { productId: 999, quantity: 1 },
      target: "order",
      priority: 1  // 优先级较低，后执行赠品
    }
  ]
}
```

***

### 5. DisplayConfig - 展示配置（可选）

```typescript
interface DisplayConfig {
  /**
   * 商品卡片展示
   */
  productCard?: {
    text: string | Record<string, string>;
    type: "tag" | "badge" | "label";
    image?: string;
    style?: Record<string, any>;
  };
  
  /**
   * 详情页展示
   */
  detail?: {
    title: string | Record<string, string>;
    description: string | Record<string, string>;
    image?: string;
  };
  
  /**
   * 自定义展示配置
   */
  custom?: Record<string, any>;
}
```

***

## 运行时上下文

### RuntimeContext - 运行时上下文结构

```typescript
interface RuntimeContext {
  /**
   * 实体数据（完全开放，由业务层定义）
   * 例如: order, customer, cart, user, passes, products...
   */
  entities: Record<string, any>;
  
  /**
   * 属性数据（扁平化的计算值，用于条件判断）
   * 例如: orderTotal, itemCount, userLevel, channelType...
   */
  attributes: Record<string, any>;
  
  /**
   * 元数据
   */
  metadata: ContextMetadata;
}

interface ContextMetadata {
  /**
   * 当前时间戳
   */
  timestamp: Date;
  
  /**
   * 时区
   */
  timezone?: string;
  
  /**
   * 请求来源
   */
  source?: string;
  
  /**
   * 自定义元数据
   */
  [key: string]: any;
}
```

**RuntimeContext 示例：**

```typescript
// Wallet Pass 场景的上下文
const context: RuntimeContext = {
  entities: {
    order: {
      id: "ORD123456",
      totalAmount: 150,
      items: [
        {
          productId: 60734,
          productName: "咖啡",
          price: 50,
          quantity: 2,
          tax: 5,
          surcharge: 2
        },
        {
          productId: 60735,
          productName: "橙汁",
          price: 30,
          quantity: 1,
          tax: 3,
          surcharge: 1
        }
      ],
      channel: "pos",
      orderType: "retail",
      fulfillmentMethod: "dine_in",
      paymentMethod: "wechat",
      status: "pending"
    },
    customer: {
      id: "CUST001",
      name: "张三",
      level: "vip",
      tags: ["loyal", "high_value"],
      registerDate: "2024-01-01",
      lastOrderDate: "2025-10-20"
    },
    passes: [
      {
        id: "PASS001",
        type: "voucher",
        balance: 50,
        usedCount: 0,
        strategyId: "STRATEGY_VOUCHER_10"
      }
    ]
  },
  attributes: {
    orderTotal: 150,
    itemCount: 3,
    applicableProductTotal: 130,  // 适用商品总额
    applicableProductCount: 3,
    userLevel: "vip",
    channelType: "pos",
    orderType: "retail",
    customerRegisterDays: 308
  },
  metadata: {
    timestamp: new Date("2025-11-04T10:00:00Z"),
    timezone: "Asia/Shanghai",
    source: "pos_terminal"
  }
};
```

***

## 执行结果规范

### EvaluationResult - 执行结果结构

```typescript
interface EvaluationResult {
  /**
   * 是否成功执行（引擎层面）
   */
  success: boolean;
  
  /**
   * 策略是否适用（业务层面）
   */
  applicable: boolean;
  
  /**
   * 结果码
   */
  code: string;
  
  /**
   * 消息（多语言key或直接文本）
   */
  message?: string;
  
  /**
   * 匹配信息
   */
  matched: MatchedInfo;
  
  /**
   * 匹配的动作列表
   * 策略引擎返回的匹配动作，按priority排序（降序）
   */
  matchedActions: ActionEffect[];
  
  /**
   * 输出结果（完全开放，由适配器定义）
   * 由业务适配器执行 matchedActions 后生成
   */
  outputs: Record<string, any>;
  
  /**
   * 执行轨迹（可选，用于调试）
   */
  trace?: ExecutionTrace;
}

interface MatchedInfo {
  /**
   * 条件是否满足
   */
  conditions: boolean;
  
  /**
   * 收集到的 actionIds
   */
  actionIds: string[];
  
  /**
   * 详细匹配信息
   */
  details: Record<string, any>;
}
```

### 结果码规范

```typescript
// 成功码
const SUCCESS_CODES = {
  SUCCESS: "SUCCESS",  // 成功且适用
};

// 不适用码
const NOT_APPLICABLE_CODES = {
  SCOPE_NOT_MATCH: "SCOPE_NOT_MATCH",  // 范围不匹配
  CONDITION_NOT_MET: "CONDITION_NOT_MET",  // 条件不满足
  LIMITATION_EXCEEDED: "LIMITATION_EXCEEDED",  // 超出限制
  SCHEDULE_NOT_VALID: "SCHEDULE_NOT_VALID",  // 时间不符
};

// 错误码
const ERROR_CODES = {
  INVALID_CONFIG: "INVALID_CONFIG",  // 配置无效
  INVALID_CONTEXT: "INVALID_CONTEXT",  // 上下文无效
  EXECUTION_ERROR: "EXECUTION_ERROR",  // 执行错误
  ADAPTER_NOT_FOUND: "ADAPTER_NOT_FOUND",  // 适配器未找到
};
```

**EvaluationResult 示例：**

```typescript
{
  success: true,
  applicable: true,
  code: "SUCCESS",
  message: "策略适用",
  matched: {
    conditions: true,
    details: {
      passedRules: [
        "scope_product.productId in [60734, 60735]",
        "scope_channel.channel in ['pos', 'mini_program']",
        "transaction.applicableProductTotal >= 100",
        "limit_usage.perUserUsedCount < 1"
      ]
    }
  },
  outputs: {
    // Wallet Pass 适配器返回的结果
    canUseCount: 1,
    maxDeduction: 50,
    deductionDetail: {
      totalDeduction: 50,
      byPass: [
        {
          passId: "PASS001",
          deductionAmount: 50,
          affectedProducts: [
            {
              productId: 60734,
              deductionAmount: 40,
              deductTax: 4,
              deductFee: 1.6
            },
            {
              productId: 60735,
              deductionAmount: 10,
              deductTax: 1,
              deductFee: 0.4
            }
          ],
          remainingBalance: 0
        }
      ]
    }
  },
  trace: {
    executionTime: 15,
    evaluatedRules: [
      "scope_product.productId",
      "scope_channel.channel", 
      "transaction.applicableProductTotal",
      "limit_usage.perUserUsedCount"
    ],
    failedRules: []
  }
}

// 不适用示例
{
  success: true,
  applicable: false,
  code: "CONDITION_NOT_MET",
  message: "未达到使用条件",
  matched: {
    conditions: false,
    details: {
      failedRule: "transaction.applicableProductTotal >= 100",
      actualValue: 50,
      requiredValue: 100
    }
  },
  outputs: {},
  trace: {
    executionTime: 8,
    evaluatedRules: [
      "scope_product.productId",
      "transaction.applicableProductTotal"
    ],
    failedRules: [
      {
        rule: "transaction.applicableProductTotal >= 100",
        reason: "适用商品金额50小于最小金额100"
      }
    ]
  }
}
```

***

## 适配器接口

### BusinessAdapter - 业务适配器接口

```typescript
interface BusinessAdapter {
  /**
   * 适配器名称
   */
  name: string;
  
  /**
   * 适配器版本
   */
  version: string;
  
  /**
   * 准备运行时上下文
   * 将业务数据转换为策略引擎可识别的上下文
   */
  prepareContext(businessData: any): RuntimeContext;
  
  /**
   * 转换执行结果
   * 将策略引擎的通用结果转换为业务层需要的格式
   */
  transformResult(result: EvaluationResult, businessData?: any): any;
  
  /**
   * 验证配置
   * 验证策略配置是否符合业务要求
   */
  validateConfig?(result: EvaluationResult, businessData?: any) {result: EvaluationResult, businessData?: any};
}
```

### 适配器实现模板

```typescript
class WalletPassAdapter implements BusinessAdapter {
  name = "WalletPassAdapter";
  version = "1.0.0";
  
  /**
   * 准备上下文
   */
  prepareContext(businessData: {
    order: any;
    customer: any;
    passes: any[];
    currentSelection?: any[];
  }): RuntimeContext {
    const { order, customer, passes, currentSelection = [] } = businessData;
    
    // 计算适用商品总额和数量
    const applicableProducts = this.getApplicableProducts(order.items, passes);
    const applicableTotal = applicableProducts.reduce((sum, p) => sum + p.price * p.quantity, 0);
    const applicableCount = applicableProducts.reduce((sum, p) => sum + p.quantity, 0);
    
    return {
      entities: {
        order,
        customer,
        passes,
        currentSelection
      },
      attributes: {
        orderTotal: order.totalAmount,
        itemCount: order.items.length,
        applicableProductTotal: applicableTotal,
        applicableProductCount: applicableCount,
        userLevel: customer.level,
        channelType: order.channel,
        orderType: order.orderType,
        customerRegisterDays: this.calculateDaysSince(customer.registerDate)
      },
      metadata: {
        timestamp: new Date(),
        timezone: "Asia/Shanghai"
      }
    };
  }
  
  /**
   * 转换结果
   */
  transformResult(result: EvaluationResult, businessData?: any): any {
    if (!result.applicable) {
      return {
        isApplicable: false,
        reason: result.message,
        reasonCode: result.code
      };
    }
    
    return {
      isApplicable: true,
      canUseCount: result.outputs.canUseCount || 0,
      maxDeduction: result.outputs.maxDeduction || 0,
      deductionDetail: result.outputs.deductionDetail || null,
      remainingBalance: result.outputs.remainingBalance || 0
    };
  }
  
  /**
   * 执行动作
   */
  executeAction(action: ActionEffect, context: RuntimeContext): any {
    switch (action.type) {
      case "DEDUCT_AMOUNT":
        return this.calculateDeduction(action, context);
      
      case "DISCOUNT_RATE":
        return this.calculateDiscount(action, context);
      
      default:
        throw new Error(`Unsupported action type: ${action.type}`);
    }
  }
  
  /**
   * 计算抵扣（业务特定逻辑）
   */
  private calculateDeduction(action: ActionEffect, context: RuntimeContext): any {
    const { order, passes } = context.entities;
    const config = action.config || {};
    
    // 获取适用商品
    const applicableProducts = this.getApplicableProducts(order.items, passes);
    
    // 根据配置计算抵扣
    const deduction = {
      totalDeduction: 0,
      byPass: [],
      canUseCount: 0
    };
    
    // 实现具体的抵扣计算逻辑...
    // 考虑：allowCrossProduct, deductTaxAndFee, applicableProductLimit 等
    
    return deduction;
  }
  
  /**
   * 获取适用商品
   */
  private getApplicableProducts(items: any[], passes: any[]): any[] {
    // 实现获取适用商品的逻辑
    return items;
  }
  
  /**
   * 计算天数差
   */
  private calculateDaysSince(date: string): number {
    const start = new Date(date);
    const now = new Date();
    return Math.floor((now.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
  }
}
```

***

## 使用示例

### 示例1：Wallet Pass - 代金券抵扣

```typescript
// 1. 定义策略配置
const voucherStrategy: StrategyConfig = {
  metadata: {
    id: "STRATEGY_VOUCHER_50",
    name: { "zh-CN": "50元代金券", "en": "50 Yuan Voucher" },
    type: "wallet_pass",
    calculationType: "single",
    version: "1.0.0",
    tags: ["voucher", "deduction"]
  },
  
  scope: {
    targets: {
      product: {
        type: "specified",
        values: [60734, 60735]  // 适用商品ID
      },
      customer: {
        type: "all"
      }
    },
    contexts: {
      channel: ["pos", "mini_program"],
      orderType: ["retail"]
    },
    schedules: [
      {
        startAt: "2025-01-01 00:00:00",
        endAt: "2025-12-31 23:59:59"
      }
    ]
  },
  
  conditions: {
    operator: "and",
    rules: [
      {
        dimension: "transaction",
        field: "applicableProductTotal",
        operator: ">=",
        value: 100,
        valueType: "number"
      }
    ]
  },
  
  limitations: {
    usage: {
      total: { limit: 0, period: null },
      perUser: { limit: 1, period: "lifetime" },
      frequency: { limit: 0, period: null },
      perTransaction: { limit: 1, period: "transaction" }
    },
    amounts: {
      minAmount: 100,
      maxAmount: 50,
      applicableProductLimit: 0
    }
  },
  
  actions: {
    effects: [
      {
        type: "DEDUCT_AMOUNT",
        value: 50,
        valueType: "number",
        valueUnit: "元",
        target: "applicable_products",
        priority: 1,
        config: {
          allowCrossProduct: true,
          deductTaxAndFee: true,
          applicableProductLimit: 0,
          maxDeductionPerUse: 50
        }
      }
    ]
  },
  
};

// 2. 初始化引擎和适配器
const engine = new StrategyEngine();
const walletPassAdapter = new WalletPassAdapter();
engine.registerAdapter("wallet_pass", walletPassAdapter);

// 3. 准备业务数据
const order = {
  id: "ORD123456",
  totalAmount: 150,
  items: [
    {
      productId: 60734,
      productName: "咖啡",
      price: 50,
      quantity: 2,
      tax: 5,
      surcharge: 2
    },
    {
      productId: 60735,
      productName: "橙汁",
      price: 30,
      quantity: 1,
      tax: 3,
      surcharge: 1
    }
  ],
  channel: "pos",
  orderType: "retail"
};

const customer = {
  id: "CUST001",
  name: "张三",
  level: "vip"
};

const pass = {
  id: "PASS001",
  type: "voucher",
  balance: 50,
  usedCount: 0,
  strategyId: "STRATEGY_VOUCHER_50"
};

// 4. 准备上下文
const context = walletPassAdapter.prepareContext({
  order,
  customer,
  passes: [pass]
});

// 5. 执行评估
const result = engine.evaluate(voucherStrategy, context);

// 6. 转换结果
const businessResult = walletPassAdapter.transformResult(result);

console.log(businessResult);
// {
//   isApplicable: true,
//   canUseCount: 1,
//   maxDeduction: 50,
//   deductionDetail: { ... }
// }
```

***

### 示例2：Promotion - 满减活动

```typescript
// 1. 定义促销策略配置
const promotionStrategy: StrategyConfig = {
  metadata: {
    id: "PROMO_FULL_100_MINUS_10",
    name: { "zh-CN": "满100减10", "en": "Spend 100 Save 10" },
    type: "promotion",
    calculationType: "single",
    version: "1.0.0"
  },
  
  scope: {
    targets: {
      product: {
        type: "excluded",
        values: [999, 1000]  // 排除某些商品
      }
    },
    contexts: {
      channel: ["pos", "mini_program", "app"]
    },
    schedules: []
  },
  
  conditions: {
    operator: "and",
    rules: [
      {
        dimension: "transaction",
        field: "amount",
        operator: ">=",
        value: 100,
        valueType: "number",
        scope: "order"
      }
    ]
  },
  
  limitations: {
    usage: {
      total: { limit: 1000, period: null },
      perUser: { limit: 3, period: "month" }
    },
    amounts: {}
  },
  
  actions: {
    effects: [
      {
        type: "DEDUCT_AMOUNT",
        operator: "subtract",
        value: 10,
        valueType: "number",
        target: "order",
        priority: 1
      }
    ]
  },
  
};

// 2. 创建 Promotion 适配器
class PromotionAdapter implements BusinessAdapter {
  name = "PromotionAdapter";
  version = "1.0.0";
  
  prepareContext(businessData: { cart: any; user: any }): RuntimeContext {
    const { cart, user } = businessData;
    
    return {
      entities: { cart, user },
      attributes: {
        amount: cart.total,
        itemCount: cart.items.length,
        userLevel: user.level
      },
      metadata: { timestamp: new Date() }
    };
  }
  
  transformResult(result: EvaluationResult): any {
    return {
      applicable: result.applicable,
      discount: result.outputs.discount || 0,
      finalPrice: result.outputs.finalPrice || 0
    };
  }
  
  executeAction(action: ActionEffect, context: RuntimeContext): any {
    if (action.type === "DEDUCT_AMOUNT") {
      const cart = context.entities.cart;
      const discount = action.value;
      
      return {
        discount,
        finalPrice: cart.total - discount
      };
    }
    
    return {};
  }
}

// 3. 注册适配器
const promotionAdapter = new PromotionAdapter();
engine.registerAdapter("promotion", promotionAdapter);

// 4. 使用
const cart = { total: 150, items: [...] };
const user = { id: "USER001", level: "normal" };

const promotionContext = promotionAdapter.prepareContext({ cart, user });
const promotionResult = engine.evaluate(promotionStrategy, promotionContext);
const businessResult = promotionAdapter.transformResult(promotionResult);

console.log(businessResult);
// {
//   applicable: true,
//   discount: 10,
//   finalPrice: 140
// }
```

***

### 示例3：业务层 - 多券组合优化

```typescript
class WalletPassService {
  constructor(
    private engine: StrategyEngine,
    private adapter: WalletPassAdapter
  ) {}
  
  /**
   * 计算可用券列表并推荐最佳组合
   */
  async calculateAvailablePasses(
    order: Order,
    customer: Customer,
    passes: WalletPass[]
  ) {
    // 1. 遍历每张券，计算可用性
    const passResults = [];
    
    for (const pass of passes) {
      // 获取策略配置
      const strategyConfig = await this.getStrategyConfig(pass.strategyId);
      
      // 准备上下文
      const context = this.adapter.prepareContext({
        order,
        customer,
        passes: [pass],
        currentSelection: []
      });
      
      // 评估
      const result = this.engine.evaluate(strategyConfig, context);
      const businessResult = this.adapter.transformResult(result);
      
      passResults.push({
        pass,
        ...businessResult
      });
    }
    
    // 2. 筛选可用券
    const availablePasses = passResults.filter(p => p.isApplicable);
    
    // 3. 推荐最佳组合
    const recommended = this.recommendBestCombination(availablePasses, order);
    
    return {
      allPasses: passResults,
      availablePasses,
      recommended
    };
  }
  
  /**
   * 推荐最佳券组合（贪心算法）
   */
  private recommendBestCombination(
    availablePasses: any[],
    order: Order
  ): any[] {
    // 按抵扣金额从大到小排序
    const sorted = [...availablePasses].sort(
      (a, b) => b.maxDeduction - a.maxDeduction
    );
    
    let remainingAmount = order.totalAmount;
    const selected = [];
    
    for (const passResult of sorted) {
      if (remainingAmount <= 0) break;
      
      // 检查是否可以添加这张券
      if (this.canAddPass(passResult, selected)) {
        selected.push(passResult);
        remainingAmount -= Math.min(passResult.maxDeduction, remainingAmount);
      }
    }
    
    return selected;
  }
  
  /**
   * 检查是否可以添加券（考虑每单张数限制等）
   */
  private canAddPass(passResult: any, currentSelection: any[]): boolean {
    // 实现业务规则检查
    // 例如：检查每单张数限制、互斥规则等
    return true;
  }
  
  /**
   * 用户取消选中券时重新计算
   */
  async onPassDeselected(
    deselectedPass: WalletPass,
    currentSelection: WalletPass[],
    order: Order,
    customer: Customer,
    allPasses: WalletPass[]
  ) {
    // 1. 更新选中列表
    const newSelection = currentSelection.filter(p => p.id !== deselectedPass.id);
    
    // 2. 计算当前抵扣
    const currentDeduction = await this.calculateTotalDeduction(
      newSelection,
      order,
      customer
    );
    
    // 3. 更新订单剩余金额
    const remainingAmount = order.totalAmount - currentDeduction.total;
    
    // 4. 重新计算未选中券的可用性
    const unselectedPasses = allPasses.filter(
      p => !newSelection.some(s => s.id === p.id)
    );
    
    const updatedResults = [];
    
    for (const pass of unselectedPasses) {
      const strategyConfig = await this.getStrategyConfig(pass.strategyId);
      
      // 传入更新后的订单状态
      const context = this.adapter.prepareContext({
        order: {
          ...order,
          appliedPasses: newSelection,
          remainingAmount
        },
        customer,
        passes: [pass],
        currentSelection: newSelection
      });
      
      const result = this.engine.evaluate(strategyConfig, context);
      const businessResult = this.adapter.transformResult(result);
      
      updatedResults.push({
        pass,
        ...businessResult
      });
    }
    
    return {
      selectedPasses: newSelection,
      unselectedPasses: updatedResults,
      totalDeduction: currentDeduction.total,
      remainingAmount
    };
  }
  
  /**
   * 计算多张券的总抵扣（考虑叠加）
   */
  private async calculateTotalDeduction(
    selectedPasses: WalletPass[],
    order: Order,
    customer: Customer
  ) {
    // 按优先级排序
    const sorted = await this.sortByPriority(selectedPasses);
    
    let currentOrder = { ...order };
    let totalDeduction = 0;
    const details = [];
    
    // 逐张应用券
    for (const pass of sorted) {
      const strategyConfig = await this.getStrategyConfig(pass.strategyId);
      
      const context = this.adapter.prepareContext({
        order: currentOrder,
        customer,
        passes: [pass]
      });
      
      const result = this.engine.evaluate(strategyConfig, context);
      
      if (result.applicable) {
        const deduction = result.outputs.deduction || 0;
        totalDeduction += deduction;
        details.push({
          passId: pass.id,
          deduction
        });
        
        // 更新订单状态
        currentOrder = {
          ...currentOrder,
          totalAmount: currentOrder.totalAmount - deduction
        };
      }
    }
    
    return {
      total: totalDeduction,
      details
    };
  }
  
  /**
   * 获取策略配置
   */
  private async getStrategyConfig(strategyId: string): Promise<StrategyConfig> {
    // 从数据库或缓存中获取策略配置
    // 这里简化为直接返回
    return {} as StrategyConfig;
  }
  
  /**
   * 按优先级排序
   */
  private async sortByPriority(passes: WalletPass[]): Promise<WalletPass[]> {
    // 实现优先级排序逻辑
    return passes;
  }
}
```

***

## 扩展指南

### 1. 新增业务场景

**步骤：**

1. 实现 `BusinessAdapter` 接口

2. 在 `prepareContext` 中定义如何转换业务数据

3. 在 `executeAction` 中实现业务特定的计算逻辑

4. 注册适配器到引擎

**示例：会员等级权益**

```typescript
class MembershipAdapter implements BusinessAdapter {
  name = "MembershipAdapter";
  version = "1.0.0";
  
  prepareContext(businessData: { user: any; action: string }): RuntimeContext {
    const { user, action } = businessData;
    
    return {
      entities: { user, action },
      attributes: {
        userLevel: user.level,
        userPoints: user.points,
        actionType: action
      },
      metadata: { timestamp: new Date() }
    };
  }
  
  transformResult(result: EvaluationResult): any {
    return {
      hasPermission: result.applicable,
      benefits: result.outputs.benefits || []
    };
  }
  
  executeAction(action: ActionEffect, context: RuntimeContext): any {
    // 实现会员权益的计算逻辑
    switch (action.type) {
      case "GRANT_DISCOUNT":
        return { benefits: ["专属折扣"] };
      case "ADD_POINTS":
        return { benefits: ["积分翻倍"] };
      default:
        return {};
    }
  }
}

// 注册
engine.registerAdapter("membership", new MembershipAdapter());
```

***

### 2. 新增条件类型

策略模型的条件完全可扩展，无需修改引擎代码，直接在配置中添加即可。

**示例：新增"天气条件"**

```typescript
// 在策略配置中
{
  conditions: {
    operator: "and",
    rules: [
      {
        dimension: "weather",  // 新维度
        field: "temperature",  // 新字段
        operator: ">",
        value: 30,
        valueType: "number"
      }
    ]
  }
}

// 在适配器的 prepareContext 中提供数据
prepareContext(businessData) {
  return {
    entities: { ... },
    attributes: {
      weather_temperature: 35,  // 提供天气数据
      // ...
    },
    metadata: { ... }
  };
}
```

***

### 3. 新增运算符

如果需要新的运算符，在引擎的 `compare` 方法中添加：

```typescript
private compare(actual: any, operator: string, expected: any): boolean {
  switch (operator) {
    // ... 现有运算符
    
    // 新增运算符
    case "starts_with":
      return String(actual).startsWith(String(expected));
    
    case "ends_with":
      return String(actual).endsWith(String(expected));
    
    case "is_empty":
      return !actual || actual.length === 0;
    
    default:
      return false;
  }
}
```

***

### 4. 新增动作类型

在适配器的 `executeAction` 中添加新的动作类型：

```typescript
executeAction(action: ActionEffect, context: RuntimeContext): any {
  switch (action.type) {
    // 现有动作类型
    case "DEDUCT_AMOUNT":
      return this.calculateDeduction(action, context);
    
    // 新增动作类型
    case "SEND_MESSAGE":
      return this.sendMessage(action, context);
    
    case "UPDATE_INVENTORY":
      return this.updateInventory(action, context);
    
    default:
      throw new Error(`Unsupported action type: ${action.type}`);
  }
}
```

***

### 5. 实现累计型策略

累计型策略需要历史数据支持，在 `prepareContext` 时准备历史统计数据：

```typescript
prepareContext(businessData) {
  const { user, order, historicalData } = businessData;
  
  return {
    entities: { user, order },
    attributes: {
      // 累计数据
      monthlySpending: historicalData.monthlySpending,
      monthlyOrderCount: historicalData.monthlyOrderCount,
      lifetimeValue: historicalData.lifetimeValue,
      // ...
    },
    metadata: { timestamp: new Date() }
  };
}

// 策略配置
{
  conditions: {
    operator: "and",
    rules: [
      {
        dimension: "cumulative",
        field: "monthlySpending",
        operator: ">=",
        value: 1000
      }
    ]
  }
}
```



预留 action和条件相互绑定的口子。方便后期扩展



## 业务问题点&#x20;

1. 策略模型和绑定商品还有wallet pass关联关系？ 一期策略模型绑定wallet

2. Kiosk中支付时的使用的原生模块，Terminal中折扣卡，需要使用这个吗？怎么使用这块？

3. 商品券和折扣卡是否也要走这套逻辑吗？

