Files
ZQYY.FQ-ETL/docs/index/index_algorithm_cn.md

393 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 指数算法说明(代码对齐版)
本文根据当前代码实现整理包含老客挽回指数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) **010 映射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 作用/业务场景
- 衡量客户与助教的关系强度与“近期温度”。
- 用于助教约课精力分配、客户-助教匹配与成功率预估等排序参考。
- 结果以 010 展示分提供横向比较。
### 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 映射到 010并可选 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** 覆盖写入
(不在窗口内的实体不会被重算)