Files
abot/plugins/value_rank/README.md
liuwei 2730595a88 完善 value_rank 社交图设计并落地 @ 结构化存储
- messages 表新增 mentioned_user_ids 字段设计,使用 JSON 数组字符串存储被@用户清单

- 新增社交图相关表设计:t_message_mentions、t_social_edges_daily、t_value_rank_social_daily

- 增加迁移脚本 20260421_add_mentions_and_social_graph_tables.sql,支持现网平滑升级

- 改造 MessageStorageDB 入库流程:解析 msg_source.atuserlist 并写入 mentioned_user_ids

- 更新 value_rank README:补充社交图数据链路、可产出图表及实现说明
2026-04-21 13:34:19 +08:00

368 lines
12 KiB
Markdown
Raw 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.
# 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 社交图数据层(建议直接上)
```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 COMMENT '被@次数(入度)',
mention_others_count INT NOT NULL DEFAULT 0 COMMENT '@他人次数(出度)',
unique_interactors INT NOT NULL DEFAULT 0 COMMENT '互动去重人数',
interaction_score DECIMAL(10,2) NOT NULL DEFAULT 0 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_score (group_id, stat_date, interaction_score)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
## 5.3 消息表结构化字段(提升提取效率)
`messages` 表新增字段:
- `raw_payload LONGTEXT`:完整原始消息(已支持)
- `mentioned_user_ids LONGTEXT`:该消息里被 @ 的用户 ID 清单JSON 数组字符串)
示例值:
```json
["wxid_abc", "wxid_xyz"]
```
> 设计目的:避免每次统计都扫 `raw_payload`,在入库阶段就把最常用的社交特征结构化。
## 5.4 社交关系明细与边表(用于关系网图)
```sql
CREATE TABLE IF NOT EXISTS t_message_mentions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
message_id VARCHAR(32) NOT NULL,
group_id VARCHAR(100) NOT NULL,
sender_id VARCHAR(100) NOT NULL,
mentioned_user_id VARCHAR(100) NOT NULL,
stat_date DATE NOT NULL,
msg_time DATETIME NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_message_sender_mentioned (message_id, sender_id, mentioned_user_id),
KEY idx_group_date (group_id, stat_date),
KEY idx_mentioned_group_date (mentioned_user_id, group_id, stat_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS t_social_edges_daily (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
stat_date DATE NOT NULL,
group_id VARCHAR(100) NOT NULL,
from_user_id VARCHAR(100) NOT NULL,
to_user_id VARCHAR(100) NOT NULL,
mention_count INT NOT NULL DEFAULT 0,
reply_count INT NOT NULL DEFAULT 0,
interaction_score DECIMAL(10,2) NOT NULL DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_day_group_edge (stat_date, group_id, from_user_id, to_user_id),
KEY idx_group_day_score (group_id, stat_date, interaction_score)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
---
## 5.5 社交图可产出图表(周报/日报)
1. 群社交关系网图(节点=成员,边=互动强度)
2.@热度榜Top10
3. 最强搭子榜(双向互动最强的成员对)
4. 社交桥梁榜(连接不同圈层的关键成员)
5. 个人影响力趋势图7天/30天
> 这些图表都基于 `t_social_edges_daily` + `t_value_rank_social_daily` 即可生成,不需要回扫全量原始消息。
---
## 6. 插件交互设计(命令与输出)
## 6.1 命令建议
1. `我的身价`
- 查询自己今日身价报告
2. `身价排行 [N]`
- 默认前 10最大 50
3. `身价说明`
- 返回算法权重与统计周期说明,减少争议
4. `重算身价`(管理员)
- 手动触发当前群重算(用于调试/补数据)
## 6.2 输出模板
### 个人报告
```text
📊 [昵称] 的身价报告2026-04-21
总身价786.4
群内排名:第 4 / 126社交名流
资产分:+210.2(积分 13240
热度分:+365.87日发言 188
活跃分:+142.030日活跃 24 天)
潜水惩罚:-31.62 天未发言)
较昨日:+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. 在消息入库时同步解析 `@`,并直接写 `messages.mentioned_user_ids`
2. 同步写 `t_message_mentions` 明细,方便追溯和反查
3. 日聚合写入 `t_social_edges_daily``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` 的首版骨架按这份文档落地出来(含详细中文注释、可直接运行)。