diff --git a/plugins/value_rank/README.md b/plugins/value_rank/README.md new file mode 100644 index 0000000..8c3e639 --- /dev/null +++ b/plugins/value_rank/README.md @@ -0,0 +1,302 @@ +# Value Rank 插件设计文档(优化版) + +> 目标:基于你现有插件生态(`point_trade` / `daily_ranking` / `message_sign` / `inactive_rank` / `stats_collector`)实现一个可落地、可运营、可扩展的“群成员身价排行”插件。 + +## 1. 先说结论(对当前方案的评估) + +你原文档的方向是对的,但当前版本存在 4 个落地风险: + +1. 指标可用性不一致 +- 你设计了 `被@次数`,但当前代码链路里没有稳定落库这个指标,直接作为核心权重会导致实现卡住。 + +2. 分值可刷问题 +- 如果直接用“发言量线性加分”,会被短消息刷屏轻易拉爆,排行榜失真。 + +3. 指标量纲不统一 +- 积分、发言数、签到天数的数值范围差异大,直接加权会导致大数指标统治结果。 + +4. 结果不可追溯 +- 如果只算“当前分”,没有“每日快照”,后续很难解释“为什么今天涨跌”。 + +因此建议: +- **V1 先用现成数据做稳定可用版**(积分 + 发言 + 活跃天数 + 潜水惩罚)。 +- **V2 再接入社交中心度(被@)**,但前提是先补齐数据采集链路。 + +--- + +## 2. 与现有插件/数据库的复用关系 + +### 2.1 可直接复用的数据来源 + +1. 积分存量 +- 来源:`db/points_db.py` -> `PointsDBOperator.get_user_points()` / `get_points_ranking()` +- 表:`t_user_points`(兼容旧数据迁移) + +2. 发言活跃 +- 来源:`db/message_storage.py` +- 可用接口: + - `get_group_member_message_ranking(group_id, start_time, end_time, limit)` + - `get_member_active_dates(group_id, wxid, days)` + - 或直接 SQL 聚合 `messages` + +3. 连续签到/签到数据 +- 来源:`db/sign_in.py` + `plugins/message_sign` +- 表:`t_sign_record`(`signin_streak`、`last_sign_date` 等) + +4. 潜水信息 +- 来源:`plugins/inactive_rank`(内部用 `ContactsDBOperator.get_inactive_members_rank`) +- 可作为负向修正项 + +### 2.2 暂时不可直接复用(需补链路) + +1. 被@次数(社交中心度) +- 当前 `messages` 里虽有 `message_xml`,但还没有统一抽取与聚合逻辑。 +- 建议放到 V2,不阻塞 V1 上线。 + +--- + +## 3. 推荐算法(V1 可直接上线) + +## 3.1 指标定义 + +按群独立计算(`group_id` 维度): + +1. `P`:用户当前积分(财富存量) +2. `M7`:近 7 天有效发言量 +3. `A30`:近 30 天活跃天数(有发言记 1 天) +4. `I`:潜水惩罚项(最近发言距今天数) + +> “有效发言”建议过滤:长度过短、纯命令消息、纯重复刷屏消息。 + +## 3.2 归一化与评分 + +建议将所有指标归一化到 `[0,1]` 再加权,避免量纲污染。 + +- `P_norm = min(log1p(P) / log1p(P95), 1)` +- `M_norm = min(M7 / M95, 1)` +- `A_norm = min(A30 / 30, 1)` +- `I_penalty = min(inactive_days / 30, 1)` + +其中 `P95`、`M95` 为群内当日 95 分位,用于抗极端值。 + +最终分数: + +```text +score = 1000 * ( + 0.35 * P_norm + + 0.45 * M_norm + + 0.20 * A_norm +) - 150 * I_penalty +``` + +边界处理: + +- `score < 0` 则记为 `0` +- 新成员冷启动:若无积分但有发言,可获得基础活跃分,避免“永远垫底” + +## 3.3 为什么比原线性模型更优 + +1. 抗刷屏:`M7` 上限由分位数截断,极端刷量收益递减 +2. 抗土豪统治:积分用 `log1p`,防止高积分一票否决 +3. 可解释:每个分项都能单独展示(资产、热度、活跃、惩罚) + +--- + +## 4. 称号系统(可运营版) + +不建议用固定分数区间,建议用“分位段位”,因为不同群体量级差异很大。 + +- Top 1%:`群之巨鳄` +- Top 5%:`社交名流` +- Top 20%:`活跃中产` +- 20%~80%:`稳定居民` +- Bottom 20%:`潜力新人` +- Bottom 10% 且 `inactive_days >= 30`:`潜水观察员` + +这样跨群也更公平,且无需频繁调阈值。 + +--- + +## 5. 数据库存储设计(建议新增) + +## 5.1 每日快照表(核心) + +```sql +CREATE TABLE IF NOT EXISTS t_value_rank_snapshot ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + stat_date DATE NOT NULL COMMENT '统计日期', + group_id VARCHAR(100) NOT NULL COMMENT '群ID', + user_id VARCHAR(100) NOT NULL COMMENT '用户ID', + score DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '身价分', + rank_no INT NOT NULL DEFAULT 0 COMMENT '名次', + title VARCHAR(50) NOT NULL DEFAULT '' COMMENT '称号', + + points_total INT NOT NULL DEFAULT 0 COMMENT '当前积分', + msg_count_7d INT NOT NULL DEFAULT 0 COMMENT '7日有效发言数', + active_days_30 INT NOT NULL DEFAULT 0 COMMENT '30日活跃天数', + inactive_days INT NOT NULL DEFAULT 0 COMMENT '距今未发言天数', + + score_detail_json JSON NULL COMMENT '分项得分详情', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + UNIQUE KEY uniq_day_group_user (stat_date, group_id, user_id), + KEY idx_group_day_rank (group_id, stat_date, rank_no) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +> 这个表是关键:支持“涨跌解释”“历史回溯”“趋势展示”。 + +## 5.2(可选)社交指标表(V2) + +```sql +CREATE TABLE IF NOT EXISTS t_value_rank_social_daily ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + stat_date DATE NOT NULL, + group_id VARCHAR(100) NOT NULL, + user_id VARCHAR(100) NOT NULL, + mentioned_count INT NOT NULL DEFAULT 0, + mention_others_count INT NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uniq_day_group_user (stat_date, group_id, user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +## 6. 插件交互设计(命令与输出) + +## 6.1 命令建议 + +1. `我的身价` +- 查询自己今日身价报告 + +2. `身价排行 [N]` +- 默认前 10,最大 50 + +3. `身价说明` +- 返回算法权重与统计周期说明,减少争议 + +4. `重算身价`(管理员) +- 手动触发当前群重算(用于调试/补数据) + +## 6.2 输出模板 + +### 个人报告 + +```text +📊 [昵称] 的身价报告(2026-04-21) +总身价:786.4 +群内排名:第 4 / 126(社交名流) + +资产分:+210.2(积分 13240) +热度分:+365.8(7日发言 188) +活跃分:+142.0(30日活跃 24 天) +潜水惩罚:-31.6(2 天未发言) + +较昨日:+5.8% +``` + +### 排行榜 + +```text +🏆 身价排行榜(Top10) +1. 张三 942.1 群之巨鳄 +2. 李四 901.3 社交名流 +3. 王五 860.0 社交名流 +... +``` + +--- + +## 7. 插件实现方案(按你项目结构) + +推荐文件: + +```text +plugins/value_rank/ + ├─ __init__.py + ├─ main.py + ├─ config.toml + └─ README.md +``` + +## 7.1 `main.py` 关键职责 + +1. 继承 `MessagePluginInterface` +2. 注册 feature 权限(如 `VALUE_RANK`) +3. 处理命令:`我的身价` / `身价排行` / `身价说明` / `重算身价` +4. 提供定时任务:每天 `04:00` 全量重算 +5. 复用:`PointsDBOperator`、`MessageStorageDB`、联系人昵称管理 + +## 7.2 调度逻辑(对齐 `daily_ranking`) + +- `get_schedule_actions()` 返回 `value_rank_daily_recompute` +- `run_scheduled_action()` 按启用群批量执行 +- 每群执行: + 1. 拉取候选成员(近 30 天有消息或有积分记录) + 2. 计算分项与总分 + 3. 生成 rank + title + 4. upsert 到 `t_value_rank_snapshot` + 5. (可选)推送 TopN + +--- + +## 8. 防刷与风控建议(必须做) + +1. 发言去噪 +- 过滤超短文本、纯命令、重复消息 + +2. 单日贡献上限 +- 单用户单日“发言贡献值”设上限(例如 200 条) + +3. 冷启动保护 +- 新人有发言即可入榜,但不会瞬间冲顶 + +4. 异常波动检测 +- 日涨跌超过阈值(如 ±40%)写日志,便于排查 + +--- + +## 9. V2 增量(社交中心度:被@) + +当你准备上线“被@”指标时,建议: + +1. 在消息入库或消息处理总入口解析 `@` 数据 +2. 日聚合写入 `t_value_rank_social_daily` +3. 新增权重项: + +```text +score = 1000 * (0.30*P_norm + 0.35*M_norm + 0.20*A_norm + 0.15*C_norm) - penalty +``` + +其中 `C_norm` 为被@次数归一化值。 + +--- + +## 10. 最小可行上线计划(建议) + +1. 第 1 天:建表 + 插件骨架 + `我的身价` 查询 +2. 第 2 天:`身价排行` + 定时重算 + 排名持久化 +3. 第 3 天:防刷规则 + 涨跌展示 + 管理员重算命令 +4. 第 4 天:灰度 1~2 个群,观察一周后再全量 + +--- + +## 11. 你原方案中建议调整的点(明确结论) + +1. “被@权重 30%” +- 暂不建议直接上,先补采集链路,否则会造成空值偏差。 + +2. “倒数群友自动踢出警告” +- 建议改为轻量提醒,避免群体验冲突。 + +3. “打击低身价用户(禁言/掠夺)” +- 建议改为娱乐化、低惩罚玩法,避免强负反馈导致活跃进一步下降。 + +4. 固定阈值称号 +- 建议改为分位阈值,更适配不同规模群。 + +--- + +如果你愿意,下一步我可以直接把 `plugins/value_rank/main.py` 和 `config.toml` 的首版骨架按这份文档落地出来(含详细中文注释、可直接运行)。