- 新增功能完整度说明,明确已上线命令、定时任务和数据链路状态 - 增加快速上手步骤:配置检查、模板检查、表结构检查、命令验证顺序 - 增加常见问题排查,覆盖社交关系图、社交榜单、趋势数据不足场景
466 lines
15 KiB
Markdown
466 lines
15 KiB
Markdown
# Value Rank 插件设计文档(优化版)
|
||
|
||
> 目标:基于你现有插件生态(`point_trade` / `daily_ranking` / `message_sign` / `inactive_rank` / `stats_collector`)实现一个可落地、可运营、可扩展的“群成员身价排行”插件。
|
||
|
||
## 0. 当前实现状态(2026-04-21)
|
||
|
||
当前插件核心闭环已完成,可直接在群里使用:
|
||
|
||
1. 排行与报告能力
|
||
- `我的身价`
|
||
- `身价排行 [名次]`
|
||
- `身价周报`
|
||
- `我的趋势 [天数]`
|
||
- `身价说明`
|
||
- `重算身价`(管理员)
|
||
|
||
2. 社交图谱能力
|
||
- `社交热度榜 [名次]`
|
||
- `搭子榜 [名次]`
|
||
- `社交桥梁榜 [名次]`
|
||
- `社交关系图 [人数]`(基于 HTML 模板渲染为图片)
|
||
|
||
3. 定时任务能力
|
||
- 每日重算身价快照
|
||
- 每 10 分钟批处理一次 @ 关系(默认只处理“当前前 20~10 分钟”的消息窗口)
|
||
- 每周自动推送周报
|
||
|
||
4. 数据链路状态
|
||
- `messages.mentioned_user_ids`:已用于标记“是否处理过 @ 抽取”,`[]` 代表已处理但无 @。
|
||
- `messages.raw_payload`:已支持存储完整原始消息载荷(不脱敏)。
|
||
- 社交明细/边/日汇总:已落 `t_message_mentions`、`t_social_edges_daily`、`t_value_rank_social_daily`。
|
||
|
||
## 0.1 操作指南(快速上手)
|
||
|
||
### 步骤 1:确认配置与命令
|
||
|
||
检查 [config.toml](/D:/learn/abot/plugins/value_rank/config.toml):
|
||
|
||
1. `enable = true`
|
||
2. `command` 包含下列命令:
|
||
- `我的身价`
|
||
- `身价排行`
|
||
- `社交热度榜`
|
||
- `搭子榜`
|
||
- `社交桥梁榜`
|
||
- `社交关系图`
|
||
- `我的趋势`
|
||
- `身价周报`
|
||
- `身价说明`
|
||
- `重算身价`
|
||
|
||
### 步骤 2:确认社交关系图模板
|
||
|
||
`社交关系图` 命令强依赖模板文件:
|
||
|
||
1. 配置项:
|
||
- `social_graph_template_path = "plugins/value_rank/templates/social_graph.html"`
|
||
2. 模板文件:
|
||
- [social_graph.html](/D:/learn/abot/plugins/value_rank/templates/social_graph.html)
|
||
|
||
说明:当前不再兼容内嵌模板;模板缺失会导致关系图生成失败。
|
||
|
||
### 步骤 3:确认数据库结构
|
||
|
||
至少需要以下表/字段可用:
|
||
|
||
1. `t_value_rank_snapshot`
|
||
2. `t_value_rank_social_daily`
|
||
3. `t_message_mentions`
|
||
4. `t_social_edges_daily`
|
||
5. `messages.mentioned_user_ids`
|
||
6. `messages.raw_payload`
|
||
|
||
### 步骤 4:群内验证命令
|
||
|
||
建议按以下顺序验证:
|
||
|
||
1. 发 `重算身价`(管理员)确保当天快照可用。
|
||
2. 发 `我的身价`、`身价排行 10` 验证基础分与排行。
|
||
3. 发 `社交热度榜 10`、`搭子榜 10`、`社交桥梁榜 10` 验证社交统计。
|
||
4. 发 `社交关系图 12` 验证模板渲染与发图链路。
|
||
5. 发 `我的趋势 7`、`身价周报` 验证历史快照与趋势计算。
|
||
|
||
### 步骤 5:定时任务观察点
|
||
|
||
1. `@` 抽取任务:每 10 分钟跑一次,只处理指定时间窗口内、`mentioned_user_ids` 为空的消息。
|
||
2. 每日消息快照:用于第二天趋势与周报对比。
|
||
3. 每周周报推送:依赖当日快照和近 7 天对比数据。
|
||
|
||
### 常见问题
|
||
|
||
1. `社交关系图` 无图返回
|
||
- 优先检查模板路径和模板文件是否存在。
|
||
- 再检查近 `social_window_days` 是否有足够社交边数据。
|
||
|
||
2. 社交榜单无数据
|
||
- 检查 `@` 批处理任务是否正常执行。
|
||
- 检查消息是否被标记为已处理(`mentioned_user_ids = []` 或有值)。
|
||
|
||
3. `我的趋势` 数据过少
|
||
- 说明历史快照不足,等待每日重算积累后会逐步完整。
|
||
|
||
## 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.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. 在消息入库时同步解析 `@`,并直接写 `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` 的首版骨架按这份文档落地出来(含详细中文注释、可直接运行)。
|