Files
daily-robots/docs/design-wecom-delta-mode.md
yumao 6ea2a4e4c6 feat: 早报系统重构与功能增强
- 新增常驻调度器 daily/scheduler.py + run-scheduler.ps1(定时生成/推送)
- 新增 daily/bridge_manager.py:Windows 兼容的 Cursor SDK 桥接
- 新增 skills/daily-featured-pick 首推 Skill 与叙事轴/去重逻辑
- 新闻抓取窗口、GitHub 搜索、企微 delta 模式等多项改进
- 补充设计文档与 superpowers 计划/规范
- 新增对应测试(scheduler、featured_pick、github_search、news_fetch_window 等)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:12:00 +08:00

274 lines
10 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.

# Design: 企微早报 Delta 模式
Generated: 2026-07-09
Repo: daily-robots
Status: DRAFT
Mode: Builder
## Problem Statement
企微早报每天推送五榜 Top 10 + 18 条新闻,内容与前几日高度重复(`find-skills``openclaw`、飞书集群等长期霸榜)。读者真实需求是「今天有什么新变化」,而非「再读一遍黄页」。
根因:
1. `daily-agent/SKILL.md` 要求即使较昨日无新增,仍须完整列出 Top 榜。
2. `movement` 仅用于 opening / signals列表区块仍全量渲染。
3. Trending 与 Hot 独立展示,同一 skill 描述写两遍。
4. 新闻 `DAILY_AI_NEWS_HOURS=72`,无已推送 link 去重,旧闻可连续出现。
## What Makes This Cool
把早报从「日报复印机」变成「变化通知」:只有新入榜、新新闻、编辑推荐时才占版面;榜全稳且无新新闻时静默不推。读者打开企微即知「今天值得扫一眼的是什么」。
## Explicit Non-Goals已否决方案
以下方案**不在本设计范围内**
| 方案 | 状态 |
|------|------|
| 静态页 / 外链档案库 | ❌ 不做 |
| 今日一装(每天一个 `npx skills add` | ❌ 不做 |
| 按星期轮换版面 | ❌ 不做 |
| 榜首锚点(稳定日仍展示 #1 | ❌ 不做 |
## Premises
1. 重复感主要来自**列表区块全量复印**,而非 opening 里引用榜首数字。
2. `output/*.data.json``daily/delta.py` 已具备新入榜对比能力,应上升为**列表渲染主数据源**。
3. 企微消息仍在应用内读完,不依赖外部页面。
4. 叙事层opening、信号、首推、新闻保持充实缩短的是**榜单列表**,不是整报。
## Recommended Approach: Delta 模式
### 环境变量
```env
# full = 现有行为(全量 Top 榜列表)
# delta = 本设计(默认推荐)
DAILY_WECOM_MODE=delta
# 无对比基准时(首日或缺历史 data.json是否自动 full 一次
DAILY_DELTA_BASELINE_FALLBACK=full # full | empty
# 推送闸门全不满足时是否跳过 webhook仍写 output 文件)
DAILY_SKIP_PUSH_WHEN_SILENT=1
# 强制推送(忽略静默)
# DAILY_FORCE_PUSH=1
# 新闻:缩短窗口 + 去重天数
DAILY_AI_NEWS_HOURS=24
DAILY_NEWS_DEDUP_DAYS=7
```
### 推送闸门Push Gate
满足**任一**条件则生成并推送企微早报:
| 条件 | 数据源 |
|------|--------|
| 任榜单有新入条目 | `movement.*_moves` 非空 |
| 去重后仍有新新闻 | 国际或国内 AI 时讯 |
| 存在 `featured_pick` | Step 0 编辑推荐 |
| `DAILY_FORCE_PUSH=1` | 环境变量 |
**静默日**:以上皆不满足 → 不调用 webhook`DAILY_SKIP_PUSH_WHEN_SILENT=1` 时)。
仍执行 `daily generate`,写入 `output/{date}.md``output/{date}.wecom.md``output/{date}.data.json` 留档。
**注意**:仅新闻有新、榜单全稳时**仍推送**,但 Skills/GitHub 列表区块整块省略(不是全天静默)。
### 列表渲染Delta 列表)
`DAILY_WECOM_MODE=delta` 时:
#### Skills
- **仅展示** `movement.skills_trending_moves` / `movement.skills_hot_moves` 中的新入榜条目。
- **跨榜去重**:按 `skill_id``id``source/title`)合并;同一 skill 只出现一次,标注来源榜(如 `Trending #4 · Hot #2`)。
- **无新入**:该榜区块**整块不出现**(不写多行「较昨日无新增」)。
#### GitHub
- 仅展示 `movement.github_trending_moves``github_emerging_moves``github_topic_moves`
- 无新入则区块省略。
#### 不包含
- 全量 Top N 列表
- 榜首锚点
- `(新入 … #n` 括号标注(与现 `agent_workflow._strip_new_entry_notes` 一致,列表标题用 `[新入 #n]` 前缀即可)
### 固定骨架(不因 Delta 缩短)
Agent 模式(`DAILY_REPORT_MODE=agent`)下,以下区块**保持**
- opening23 句,首句含具体证据)
- headline / 今日主题
- 今日信号35 条)
- 今日首推
- 国际 AI / 国内 AI 精选(条数仍由 `DAILY_WECOM_AI_NEWS` 等控制)
榜单变短;叙事与新闻不主动砍到 0。
### Full 模式逃生口
`DAILY_WECOM_MODE=full` 时行为与**当前生产一致**`format_wecom.build_wecom_report` / `replace_wecom_skill_sections` 全量 Top N。用于手动切回或对比测试。
### 首日 / 无历史基准
`find_previous_data(date)` 返回 `None` 时:
| `DAILY_DELTA_BASELINE_FALLBACK` | 行为 |
|----------------------------------|------|
| `full`(推荐) | 当日按 full 模式渲染列表一次;次日起 delta |
| `empty` | 当日列表区块为空opening 须说明「首日报,暂无对比基准」 |
实现时在 `generate_report``build_llm_input` 传入 `baseline_date` 供 Agent 引用。
## News Dedup
### P0本阶段
- 维护 `cache/pushed-news-links.json`(或写入 `output/` 旁 cache最近 `DAILY_NEWS_DEDUP_DAYS` 天已出现在企微早报中的 `link` 集合。
- `prepare_wecom_news_items` / `prepare_wecom_cn_news_items` 输出前过滤已见 link。
- `DAILY_AI_NEWS_HOURS` 默认改为 `24``.env.example` 同步)。
### P1可选后续
- 标题归一化去重(同一事件多源报道)
-`source_name` 每日上限 N 条
## Agent Skill 变更
文件:`skills/daily-agent/SKILL.md`
### 删除 / 修改
- 删除规则:「即使某榜较昨日无新增,仍须完整列出 Top 榜条目」。
- 删除:「禁止改用 movement 作为列表来源」(在 delta 模式下反转)。
### 新增
`DAILY_WECOM_MODE=delta`(或 llm_input 含 `wecom_mode: delta`
1. Agent **不写** Skills Trending / Hot / GitHub 列表(仍由 Python 插入,与现流程一致)。
2. opening / signals **可引用**榜首与 movement 摘要;禁止在 signals 重复列表已展示的同一事实。
3. 榜全稳时signals 聚焦新闻与首推,不必编造榜单变化。
`wecom_mode: full` 时保持现有 SKILL 规则。
## Python 模块变更
| 模块 | 变更 |
|------|------|
| `daily/config.py` | `wecom_mode()`, `news_dedup_days()`, `skip_push_when_silent()`, `delta_baseline_fallback()` |
| `daily/delta.py` | 可选:`merge_skill_moves_for_wecom(trending_moves, hot_moves)` 跨榜去重 |
| `daily/format_wecom.py` | `build_skills_delta_section()`, `build_github_delta_section()``replace_wecom_skill_sections` 支持 delta |
| `daily/news/fetch.py` | `filter_pushed_news()` + cache 读写 |
| `daily/generate.py` | 推送闸门baseline fallback静默 skip push |
| `daily/report_data.py` | `llm_input` 增加 `wecom_mode`, `push_gate` 摘要 |
| `daily/agent_workflow.py` | 无逻辑变更;依赖 Python 插入 delta 列表 |
| `.env.example` | 新 env 文档 |
## 企微消息示例
### 有变化日
```markdown
📰 **早报 · 2026-07-10**
[opening今天最大变化含数字/条目名]
🎯 **{headline}**
💡 **今日信号**
> ...
📦 **今日首推**
[...]
🌍 **国际 AI · 精选 N**
...
📈 **Skills Trending 变化**
1. [新入 #4] [**xxx**](...) · ...
描述一行
🔥 **Skills Hot 变化**
1. [新入 #2] [**yyy**](...) · ...
🐙 **GitHub Trending 变化**
1. [新入 #4] [owner/repo](...) · ...
```
### 仅新闻有新(榜稳)
- 无 📈/🔥/🐙 区块
- opening 可一句:「榜单较昨日 Top15 无新入;以下为今日 AI 时讯。」
### 静默日
- 不推送企微
- `output/` 仍落盘;日志:`[silent] no push gate matched for 2026-07-10`
## Approaches Considered
### Approach A: 配置瘦身(缩 Top N、24h 新闻)
- Effort: S | Risk: Low
- 只减篇幅,榜头仍天天重复;未解决根因。
### Approach B: Delta 列表 + 推送闸门 + 新闻去重(本设计)
- Effort: M | Risk: Med
- 复用 `delta.py`;改 format + skill + push 逻辑。
### Approach C: 仅改 Agent 文案
- Effort: S | Risk: High
- Python 仍插入全量列表,规则冲突,不可持续。
**Recommendation: B** — 数据层与展示层一致,静默日与跨榜去重可测。
## Success Criteria
1. 连续 3 天对比 `output/*.data.json`:企微列表区块**重复 skill_id 占比**显著下降。
2. 榜全稳且无新新闻日:`DAILY_SKIP_PUSH_WHEN_SILENT=1` 时不发 webhook。
3. Trending/Hot 同一 skill 在列表中**最多出现 1 次**。
4. `DAILY_WECOM_MODE=full` 与现网行为一致(回归用)。
5. 首日 `DAILY_DELTA_BASELINE_FALLBACK=full` 不产生空列表投诉。
## Open Questions
1. 静默日是否需要在企微发一行「今日无更新」?当前设计:**不发**。
2. 新闻去重 cache 是否纳入 git建议**否**,放 `cache/`(已在 `.gitignore`)。
3. Classic 模式(非 agent是否同步 delta建议**是**,同一 `format_wecom` 路径。
## Implementation Tasks
| ID | Priority | Task | Files |
|----|----------|------|-------|
| T1 | P1 | 新增 config helpers + `.env.example` | `daily/config.py`, `.env.example` |
| T2 | P1 | 新闻 link 去重 cache | `daily/news/fetch.py`, `daily/config.py` |
| T3 | P1 | Delta 列表渲染 + 跨榜去重 | `daily/format_wecom.py`, `daily/delta.py` |
| T4 | P1 | 推送闸门 + 静默 skip push | `daily/generate.py`, `daily/webhook.py` |
| T5 | P1 | baseline fallback full 一次 | `daily/generate.py` |
| T6 | P1 | 更新 `daily-agent/SKILL.md` | `skills/daily-agent/SKILL.md` |
| T7 | P2 | `llm_input``wecom_mode` / push 摘要 | `daily/report_data.py` |
| T8 | P2 | 单元测试:跨榜去重、推送闸门、新闻去重 | `tests/test_wecom_delta.py` |
## Test Plan
- [ ]`2026-07-09.data.json` 时生成 `2026-07-10`:列表仅含新入项
- [ ] 人造「全稳 + 无新新闻」:不 push
- [ ] 人造「全稳 + 有新新闻」push无 Skills/GitHub 块
- [ ] `DAILY_WECOM_MODE=full` 输出与改前 `2026-07-09.wecom.md` 结构一致
- [ ] 无 baseline + `DAILY_DELTA_BASELINE_FALLBACK=full`:首日全量列表
- [ ] 同一 skill 在 Trending/Hot moves 均出现:列表只 1 条
## What I Noticed
- 重复感是**产品形态**问题,不是 Agent 文笔问题。
- 明确否决静态页、今日一装、轮换、锚点后,方案边界清晰,实现可分期。
- 推送闸门必须**把新闻算进去**,否则静默日会被新闻绕过。