<!-- AI_CHANGELOG [2026-02-13] 移除 §4 INTIMACY 整节；更新版本历史；替换 §5.2/§6 中 INTIMACY 引用 -->
# 指数算法说明（代码对齐版）

本文根据当前代码实现整理，包含老客挽回指数（WBI）、新客转化指数（NCI）与关系指数（RS/OS/MS/ML）的计算流程、参数含义、归一化逻辑与用途说明。
如需业务版本（非代码版）说明，请另行补充。

## 0. 版本更新

### 2026-02-13
1. 移除旧版 `RecallIndexTask`（已由 WBI+NCI 替代）和 `IntimacyIndexTask`（已由 RelationIndexTask 替代）。
2. `ML` 移除 last-touch 备用路径（`source_mode` / `recharge_attribute_hours`），仅保留人工台账唯一真源。
3. WBI 修复 `STOP_HIGH_BALANCE` 会员不参与评分的问题。

### 2026-02-08
1. 关系指数从旧 `INTIMACY` 切换为单任务 `RelationIndexTask`，统一产出 `RS/OS/MS/ML`。
2. `ML` 口径调整为人工台账唯一真源（`dws_ml_manual_order_alloc`）。
3. `BaseIndexTask` 已支持按 `index_type` 隔离参数缓存与分位平滑历史，避免单任务串参。

## 1. 通用约定

1) **时间口径**
- 以 `datetime.now(self.tz)` 为"当前时间"基准。
- 窗口仅回溯 **近 60 天**（`lookback_days`）。

2) **天数截断**
- 所有"天数差"在参与衰减或间隔计算时，都会被截断到 `<= lookback_days`（默认 90 天）。

3) **半衰期衰减**
```
decay(d; h) = exp(-ln(2) * d / h)
```
- `d` 为距今天数，`h` 为半衰期（天）。
- `d=0` 时权重 1.0，`d=h` 时权重 0.5。

4) **0–10 映射（Raw → Display）**
- 取全体 Raw 分数，计算 `P5/P95`。
- 对 Raw 进行 **Winsorize** 截断到 `[P5, P95]`。
- 可选压缩：`none / log1p / asinh`。
- MinMax 映射到 `[0, 10]`。
- 若范围过小（分母 < 1e-6），直接返回 5.0。
- 最终展示分数保留两位小数。

5) **分位点平滑（可选）**
- 若 `use_smoothing=1` 且存在历史分位点：
  `Q_t = (1-α) * Q_{t-1} + α * Q_now`
- `α` 来自参数 `ewma_alpha`（默认 0.2）。

> 以上逻辑由 `BaseIndexTask` 提供。

---

## 2. 老客挽回指数（WBI）

### 2.0 作用/业务场景

- 识别需要重点"唤回"的老客，并给出可排序的优先级分数。
- 综合超期、降频、充值未回访与价值信号，衡量"召回紧迫度 + 价值潜力"。
- 结果通常用于运营触达/回访任务优先级与名单筛选。

### 2.1 数据来源与口径

- **到店记录**：`dwd.dwd_settlement_head`
  条件：`site_id`、`member_id > 0`、`settle_type=1`
  或 `settle_type=3` 且关联 `dwd_assistant_service_log` 中 **附加课/奖励课(BONUS)**。
  使用 `pay_time` 作为到店/服务结束时间（按天去重）。
- **充值记录**：`dwd.dwd_recharge_order`
  条件：`site_id`、`member_id > 0`、`settle_type=5`，取最近充值时间（回溯 60 天）。
- **会员档案**：`dwd.dim_member.create_time`
- **储值卡余额**：`dwd.dim_member_card_account.balance`
  口径：现金储值卡 `card_type_id=2793249295533893`。

> 计算窗口：到店历史取近 180 天；recency 按 60 天封顶。

### 2.2 特征与分流

- `t_v = min(lookback_days_recency, days_since_last_visit)`
- `t_r = min(lookback_days_recency, days_since_last_recharge)`
- `t_a = min(t_v, t_r)`
- `visits_14d / visits_60d / visits_total(近 180 天)`
- `spend_30d / spend_180d`：按 `pay_amount`（实付）汇总
- 到店间隔：按天计算并封顶到 `lookback_days_recency`；同时记录间隔的"距今年龄"用于加权 CDF
- `recharge_unconsumed`：最近一次充值晚于最近一次到店（或无到店）时为 1

**分流规则：**
- STOP：`t_a >= lookback_days_recency`（默认不写入；高余额例外可选）
- STOP_HIGH_BALANCE：当 `enable_stop_high_balance_exception=1` 且 `sv_balance >= high_balance_threshold` 时，仍进入 WBI 计算
- NEW：`visits_total <= new_visit_threshold`
  或 `days_since_first_visit <= new_days_threshold`
  或 `recharge_unconsumed=1` 且 `days_since_last_recharge <= recharge_recent_days`
- OLD：非 STOP 且非 NEW

### 2.3 分项得分

**Overdue（个人周期超期分）**
基于加权经验 CDF：
```
p = weighted_cdf(intervals, t_v, halflife_days, blend_min_samples)
overdue = p ^ alpha
```
- 加权 CDF 使用半衰期对历史间隔加权，近期间隔权重更高
- 若无历史间隔数据，`p = 0.5`
- 同时计算理想回访间隔（加权中位数），用于推算理想下次到店日期

**Drop（近期降频）**
```
expected14 = visits_60d * 14/60
drop = clip((expected14 - visits_14d) / (expected14 + 1), 0, 1)
```

**Recharge（充值未回访压力）**
```
recharge = decay(t_r; h_recharge)  if recharge_unconsumed=1 else 0
```

**Value（价值）**
```
S_spend = ln(1 + spend_180d / M0)
S_bal   = ln(1 + sv_balance / B0)
value   = w_spend * S_spend + w_bal * S_bal
```

### 2.4 Raw Score

```
WBI_raw = w_over * overdue
        + w_drop * drop
        + w_re   * recharge
        + w_value * value
```

**Recency suppression（近访抑制）：**
```
suppression = sigmoid((t_v - recency_gate_days) / recency_gate_slope_days)
WBI_raw = WBI_raw * suppression
```
- Hard floor（硬门槛）：
```
if t_v < recency_hard_floor_days: suppression = 0
```
- 默认：`recency_gate_days=14`，`recency_gate_slope_days=3`
- 默认：`recency_hard_floor_days=14`
- 当 `recency_gate_slope_days <= 0` 时，退化为硬门槛：`t_v < recency_gate_days => suppression=0`
- 限制在 0 以上

### 2.5 输出表

`dws.dws_member_winback_index`

### 2.6 WBI 默认参数

| 参数 | 默认值 | 含义 |
|---|---:|---|
| `lookback_days_recency` | 60 | recency 窗口（天） |
| `visit_lookback_days` | 180 | 到店历史窗口（天） |
| `overdue_alpha` | 2.0 | 超期分幂次 |
| `overdue_weight_halflife_days` | 30 | 加权 CDF 半衰期 |
| `overdue_weight_blend_min_samples` | 8 | 加权/等权混合最小样本 |
| `h_recharge` | 7 | 充值衰减半衰期 |
| `amount_base_M0` | 300 | 消费压缩基数 |
| `balance_base_B0` | 500 | 余额压缩基数 |
| `w_over` | 2.0 | 超期分权重 |
| `w_drop` | 1.0 | 降频分权重 |
| `w_re` | 0.4 | 充值分权重 |
| `w_value` | 1.2 | 价值分权重 |
| `recency_gate_days` | 14 | 近访抑制门槛 |
| `recency_gate_slope_days` | 3 | 近访抑制斜率 |
| `recency_hard_floor_days` | 14 | 硬门槛天数 |
| `new_visit_threshold` | 2 | 新客到店次数阈值 |
| `new_days_threshold` | 30 | 新客建档天数阈值 |

---

## 3. 新客转化指数（NCI）

### 3.0 作用/业务场景

- 识别新客的"欢迎建联"与"转化召回"优先级。
- 兼顾首访后快速触达与二访转化窗口，避免对近期活跃新客过度打扰。
- 结果通常用于新客欢迎、转化跟进与触达节奏排序。

### 3.1 数据来源与口径

- 使用 `MemberIndexBaseTask` 的共享口径，与 WBI 完全一致（到店/充值/会员档案/余额、`t_v/t_r/t_a`、`visits_*`、`spend_*`、`recharge_unconsumed` 等）。
- 适用对象：仅 NEW 分群（分流规则见 2.2）。

### 3.2 关键分项

**Need（转化紧迫度）**
```
t2_max = 2 * t2_target_days
Need = clip((t_v - no_touch_days_new) / (t2_max - no_touch_days_new), 0, 1)
```

**Salvage（可救度）**
```
if t_a <= salvage_start: 1
elif t_a >= salvage_end: 0
else: (salvage_end - t_a) / (salvage_end - salvage_start)
```

**Recharge（充值未回访压力）** 同 WBI
**Value（价值）** 同 WBI（权重可不同）

### 3.3 Raw Score（含欢迎建联与活跃抑制）

新增逻辑：
- `Welcome`：仅首访/单访新客在 `welcome_window_days` 内触发，越接近当天分越高。
- `active_multiplier`：若新客近14天来店次数较高且最近仍活跃，则用 `active_new_penalty` 抑制转化召回分。
- `touch_multiplier`：`t_v` 未达到 `no_touch_days_new` 前，`Recharge/Value` 贡献按比例衰减，减少"刚来过就高分"。

```
NCI_raw = w_welcome * Welcome
        + active_multiplier * (
            w_need * (Need * Salvage)
          + w_re   * Recharge * touch_multiplier
          + w_value * Value   * touch_multiplier
          )
```

NCI 额外提供三个维度的展示分：`display_score`（总分）、`display_score_welcome`（欢迎分）、`display_score_convert`（转化分）。

### 3.4 输出表

`dws.dws_member_newconv_index`

### 3.5 NCI 默认参数

| 参数 | 默认值 | 含义 |
|---|---:|---|
| `no_touch_days_new` | 3 | 免打扰天数 |
| `t2_target_days` | 7 | 目标回访天数 |
| `salvage_start` | 30 | 可救度开始衰减天数 |
| `salvage_end` | 60 | 可救度归零天数 |
| `welcome_window_days` | 3 | 欢迎窗口（天） |
| `active_new_visit_threshold_14d` | 2 | 活跃抑制到店阈值 |
| `active_new_recency_days` | 7 | 活跃抑制近期天数 |
| `active_new_penalty` | 0.2 | 活跃抑制系数 |
| `w_welcome` | 1.0 | 欢迎分权重 |
| `w_need` | 1.6 | 紧迫度权重 |
| `w_re` | 0.8 | 充值分权重 |
| `w_value` | 1.0 | 价值分权重 |

---

> **注意**：旧版第 4 节「亲密指数（INTIMACY）」已于 2026-02-13 移除，功能由 `RelationIndexTask`（RS/OS/MS/ML）替代。

## 5. 映射与参数配置

### 5.1 映射流程

1) 计算 Raw Score
2) 计算 P5/P95
3) Winsorize 截断
4) 可选压缩（`none/log1p/asinh`）
5) MinMax → `[0, 10]`
6) 可选 EWMA 平滑（`use_smoothing` + `ewma_alpha`）

### 5.2 参数来源

参数来自 `dws.cfg_index_parameters`，按 `index_type` 加载，默认值见代码：
- **WBI 关键参数**：`lookback_days_recency`、`overdue_alpha`、`overdue_weight_halflife_days`、`h_recharge`、`w_over/w_drop/w_re/w_value`
- **NCI 关键参数**：`no_touch_days_new`、`t2_target_days`、`salvage_start/end`、`w_welcome/w_need/w_re/w_value`
- **RS 关键参数**：`lookback_days`、`halflife_session/last`、`weight_f/weight_d`、`gate_alpha`
- **OS 关键参数**：`min_rs_raw_for_ownership`、`ownership_main_threshold/comanage_threshold/gap_threshold`
- **MS 关键参数**：`halflife_short/long`
- **ML 关键参数**：`amount_base`、`halflife_recharge`
- **通用参数**：`percentile_lower/upper`、`compression_mode`、`use_smoothing`、`ewma_alpha`

`compression_mode` 取值：
- `0`：不压缩
- `1`：log1p
- `2`：asinh

### 5.3 参数优先级

- 先用代码默认参数（`DEFAULT_PARAMS`）
- 再用数据库参数覆盖（`cfg_index_parameters`，取 `effective_from <= CURRENT_DATE` 且未过期，同名参数取最近生效的一条）
- GUI/环境变量可通过 `run.index_lookback_days` 覆盖 recency 窗口

即：**GUI/环境变量 > DB > 代码默认值**。

---

## 6. 运行与覆盖策略

- WBI/NCI：默认"每 2 小时"计算（由任务描述定义）
- RS/OS/MS/ML（RelationIndexTask）：默认"每 4 小时"计算（由任务描述定义）
- 写入方式：对本次参与计算的实体进行 **delete-before-insert** 覆盖写入
  （不在窗口内的实体不会被重算）