# 指数算法说明(代码对齐版) 本文根据当前代码实现整理,包含老客挽回指数(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** 覆盖写入 (不在窗口内的实体不会被重算)