# 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`)下,以下区块**保持**: - opening(2–3 句,首句含具体证据) - headline / 今日主题 - 今日信号(3–5 条) - 今日首推 - 国际 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 文笔问题。 - 明确否决静态页、今日一装、轮换、锚点后,方案边界清晰,实现可分期。 - 推送闸门必须**把新闻算进去**,否则静默日会被新闻绕过。