393 lines
14 KiB
Markdown
393 lines
14 KiB
Markdown
# 指数算法说明(代码对齐版)
|
||
|
||
本文根据当前代码实现整理,包含老客挽回指数(WBI)、新客转化指数(NCI)与关系指数(RS/OS/MS/ML)的计算流程、参数含义、归一化逻辑与用途说明。
|
||
如需业务版本(非代码版)说明,请另行补充。
|
||
|
||
## 0. 版本更新(2026-02-08)
|
||
|
||
1. 关系指数从旧 `INTIMACY` 切换为单任务 `RelationIndexTask`,统一产出 `RS/OS/MS/ML`。
|
||
2. `ML` 口径调整为人工台账唯一真源(`dws_ml_manual_order_alloc`)。
|
||
3. `dwd_recharge_order` 的 last-touch 仅保留备用路径,默认关闭(`ML.source_mode=0`)。
|
||
4. `BaseIndexTask` 已支持按 `index_type` 隔离参数缓存与分位平滑历史,避免单任务串参。
|
||
|
||
## 1. 通用约定
|
||
|
||
1) **时间口径**
|
||
- 以 `datetime.now(self.tz)` 为"当前时间"基准。
|
||
- 窗口仅回溯 **近 60 天**(`lookback_days`)。
|
||
|
||
2) **天数截断**
|
||
- 所有"天数差"在参与衰减或间隔计算时,都会被截断到 `<= lookback_days`(默认 60 天)。
|
||
|
||
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 数据来源与口径
|
||
|
||
- **到店记录**:`billiards_dwd.dwd_settlement_head`
|
||
条件:`site_id`、`member_id > 0`、`settle_type=1`
|
||
或 `settle_type=3` 且关联 `dwd_assistant_service_log` 中 **附加课/奖励课(BONUS)**。
|
||
使用 `pay_time` 作为到店/服务结束时间(按天去重)。
|
||
- **充值记录**:`billiards_dwd.dwd_recharge_order`
|
||
条件:`site_id`、`member_id > 0`、`settle_type=5`,取最近充值时间(回溯 60 天)。
|
||
- **会员档案**:`billiards_dwd.dim_member.create_time`
|
||
- **储值卡余额**:`billiards_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 输出表
|
||
|
||
`billiards_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 输出表
|
||
|
||
`billiards_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)
|
||
|
||
### 4.0 作用/业务场景
|
||
|
||
- 衡量客户与助教的关系强度与“近期温度”。
|
||
- 用于助教约课精力分配、客户-助教匹配与成功率预估等排序参考。
|
||
- 结果以 0–10 展示分提供横向比较。
|
||
|
||
### 4.1 数据来源与口径
|
||
|
||
- **服务记录**:`billiards_dwd.dwd_assistant_service_log`
|
||
条件:`site_id`、`tenant_member_id > 0`、`is_delete = 0`、`user_id > 0`
|
||
时间口径:`last_use_time` 在近 `lookback_days` 天内
|
||
- **助教维度**:`billiards_dwd.dim_assistant`
|
||
通过 `user_id` 关联获取 `assistant_id`(`scd2_is_current=1`)
|
||
- **充值记录**:`billiards_dwd.dwd_recharge_order`
|
||
条件:`settle_type = 5` 且 `pay_time >= now - lookback_days`
|
||
- 计算粒度为 `(member_id, assistant_id)`
|
||
|
||
### 4.2 会话合并
|
||
|
||
以 `(member_id, assistant_id)` 分组,按 `start_use_time` 排序:
|
||
- **间隔 ≤ session_merge_hours**(默认 4 小时)视为同一会话
|
||
- 合并后:
|
||
- `session_end` 取最大结束时间
|
||
- `duration` 累加
|
||
- `course_weight` 取最大值(附加课权重更高)
|
||
- `is_incentive` 取 OR
|
||
- 课型权重:
|
||
- `BONUS`:`incentive_weight`(默认 1.5)
|
||
- 其他:权重 1.0
|
||
|
||
### 4.3 归因充值
|
||
|
||
从 `dwd_recharge_order` 取近 `lookback_days` 天充值记录(`settle_type=5`):
|
||
- 若充值发生在某会话结束后的 **recharge_attribute_hours**(默认 1 小时)内,归因为该助教
|
||
- 单笔充值在该 `(member_id, assistant_id)` 对内只计一次
|
||
|
||
### 4.4 分项得分
|
||
|
||
所有 `days_ago` 均截断到 `<= lookback_days`。
|
||
|
||
**F:频次强度**
|
||
```
|
||
F = Σ( τ_i * decay(days_ago_i; h_sess) )
|
||
```
|
||
|
||
**R:最近温度**
|
||
```
|
||
R = decay(min(d_last, lookback_days); h_last)
|
||
```
|
||
|
||
**M:归因充值强度**
|
||
```
|
||
M = Σ( ln(1 + amt/A0) * decay(min(days_ago, lookback_days); h_pay) )
|
||
```
|
||
|
||
**D:时长贡献**
|
||
```
|
||
D = Σ( sqrt(dur_hours) * τ_i * decay(days_ago_i; h_sess) )
|
||
```
|
||
|
||
**burst:频率激增放大**
|
||
```
|
||
F_short = Σ( τ_i * decay(days_ago_i; h_short) )
|
||
F_long = Σ( τ_i * decay(days_ago_i; h_long) )
|
||
ratio = F_short / (F_long + 1e-6)
|
||
burst = max(0, ln(1 + (ratio - 1)))
|
||
mult = 1 + γ * burst
|
||
```
|
||
|
||
### 4.5 Raw Score 与 Display Score
|
||
|
||
```
|
||
INTIMACY_raw = (w_F*F + w_R*R + w_M*M + w_D*D) * mult
|
||
```
|
||
|
||
Display Score 使用 `BaseIndexTask` 的分位截断 + 压缩 + MinMax 映射到 0–10,并可选 EWMA 平滑(见第 1/5 节通用说明)。
|
||
|
||
### 4.6 输出字段
|
||
|
||
写入表:`billiards_dws.dws_member_assistant_intimacy`,主要字段:
|
||
- 会话统计:`session_count`、`total_duration_minutes`、`basic_session_count`、`incentive_session_count`
|
||
- 最近与充值:`days_since_last_session`、`attributed_recharge_count`、`attributed_recharge_amount`
|
||
- 分项得分:`score_frequency`、`score_recency`、`score_recharge`、`score_duration`、`burst_multiplier`
|
||
- 汇总:`raw_score`、`display_score`
|
||
|
||
### 4.7 INTIMACY 默认参数
|
||
|
||
| 参数 | 默认值 | 含义 |
|
||
|---|---:|---|
|
||
| `lookback_days` | 60 | 回看窗口(天) |
|
||
| `session_merge_hours` | 4 | 会话合并间隔(小时) |
|
||
| `recharge_attribute_hours` | 1 | 充值归因窗口(小时) |
|
||
| `amount_base` | 500 | 充值强度压缩基数 |
|
||
| `incentive_weight` | 1.5 | 附加课权重 |
|
||
| `halflife_session` | 14 | 会话衰减半衰期 |
|
||
| `halflife_last` | 10 | 最近服务衰减半衰期 |
|
||
| `halflife_recharge` | 21 | 充值衰减半衰期 |
|
||
| `halflife_short` | 7 | 短期频次半衰期 |
|
||
| `halflife_long` | 30 | 长期频次半衰期 |
|
||
| `weight_frequency` | 2.0 | F 权重 |
|
||
| `weight_recency` | 1.5 | R 权重 |
|
||
| `weight_recharge` | 2.0 | M 权重 |
|
||
| `weight_duration` | 0.5 | D 权重 |
|
||
| `burst_gamma` | 0.6 | 激增放大系数 |
|
||
| `percentile_lower` | 5 | 下分位(P5) |
|
||
| `percentile_upper` | 95 | 上分位(P95) |
|
||
| `compression_mode` | 1 | 压缩方式(1=log1p) |
|
||
| `use_smoothing` | 1 | 是否启用 EWMA |
|
||
| `ewma_alpha` | 0.2 | 分位平滑系数 |
|
||
|
||
---
|
||
|
||
## 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 参数来源
|
||
|
||
参数来自 `billiards_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`
|
||
- **INTIMACY 关键参数**:`halflife_session/last/recharge/short/long`、`amount_base`、`incentive_weight`、`weight_*`、`burst_gamma`
|
||
- **通用参数**:`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 小时"计算(由任务描述定义)
|
||
- INTIMACY:默认"每 4 小时"计算(由任务描述定义)
|
||
- 写入方式:对本次参与计算的实体进行 **delete-before-insert** 覆盖写入
|
||
(不在窗口内的实体不会被重算)
|