# AI Use Case Daily Scout — Milestone 2 实录

> 完成日期：2026-02-28
> 状态：✅ 三数据源全部打通，按兴趣方向优化关键词，按热度排序，已配置每日定时运行

---

## 一、Milestone 1 → 2 解决了什么

Milestone 1 完成时的状态：只有 HN 能跑通，Reddit 全部 403，没有 GitHub，每天产出 3-5 条内容。

Milestone 2 目标：**三个数据源全部打通 + 内容精准对齐用户兴趣 + 生产化部署**。

```
之前（M1）:  HN(3-5条) + Reddit(0) + GitHub(无) → 3 条有价值
之后（M2）:  HN(400+) + Reddit(精准子版块) + GitHub(125+) → 每日 30-40 条有价值
```

---

## 二、Phase 2 新增功能全记录

### 2.1 GitHub Trending 采集器

新增 `collectors/github.py`，使用 GitHub Search API，按 topic 搜索最近有推送的 AI 相关仓库。

**关键设计**：GitHub Search API 不支持 `topic:X OR topic:Y` 语法（返回 422），必须**每个 topic 单独发一次请求**，再按 `repo.id` 去重合并。

```python
_TOPICS = ["openclaw", "claude-code", "vibe-coding", "mcp",
           "ai-agent", "local-llm", "ollama", "ai-saas"]
```

采集到的字段：仓库名、描述、topics、语言、star 数，拼成 `raw_content` 供 LLM 分析。

---

### 2.2 Reddit：从 OAuth2 到 Exa.ai

**OAuth2 方案（已废弃）**：Reddit 在 2023 年 API 政策收紧后，公共 JSON 端点全面封锁服务器 IP。虽然实现了 OAuth2 application-only 流程（client_id + client_secret → Bearer token），但维护成本高、Reddit 风控策略不稳定，最终放弃。

**新方案：Exa.ai Python SDK**

```python
from exa_py import Exa

exa = Exa(api_key=EXA_API_KEY)
response = exa.search_and_contents(
    query,
    include_domains=[f"reddit.com/r/{subreddit}"],
    start_published_date=start_date,   # 过去 24 小时
    text=True,
    num_results=10,
)
```

**踩坑：Exa 的 `include_domains` 不严格过滤**

Exa 将 `include_domains` 当作软提示而非硬约束，实际返回的 URL 中混入了 dev.to、blockchain.news 等非 Reddit 链接。

**修复**：采集后对 URL 做硬过滤：

```python
if f"/r/{sub.lower()}/" not in url.lower():
    continue
```

**架构升级：从通用查询改为按子版块精准抓取**

旧方案：4 条通用查询语句搜全 reddit.com，信噪比低。
新方案：每个目标子版块单独发一次请求，锁定精准社区。

```python
_DEFAULT_SUBREDDITS = ["openclaw", "vibecoding", "ClaudeCode", "singularity"]
```

`.env` 新增配置：

```
EXA_API_KEY=your_exa_api_key
REDDIT_SUBREDDITS=["openclaw","vibecoding","ClaudeCode","singularity"]
```

---

### 2.3 修复采集漏斗截断 Bug

**Bug**：原始代码将三个来源直接拼接后取前 N 条：

```python
all_items = hn_items + reddit_items + github_items
all_items = all_items[:max_items_per_run]  # HN 有 479 条，前 N 条全是 HN
```

HN 返回 479 条，Reddit/GitHub 被完全截断，永远不会被处理。

**修复**：改为三源**轮询交替**（round-robin interleave）再截断：

```python
interleaved = [
    item
    for item in itertools.chain.from_iterable(
        itertools.zip_longest(hn_items, reddit_items, github_items)
    )
    if item is not None
]
all_items = interleaved[:max_items_per_run]
# 结果：{'hackernews': 39, 'reddit': 3, 'github': 38} — 三源均衡
```

---

### 2.4 关键词精准化：对齐兴趣方向

根据实际关注方向，全面替换三处关键词配置。

**关注方向**：
- 🔧 **OpenClaw** — 本地优先个人 AI 助手（TypeScript，多消息渠道，50+ 服务集成）
- 💻 **AI 编程** — Cursor、Claude Code、vibe coding 工作流
- 🧑‍💼 **超级个体** — AI 辅助的一人公司、独立开发者
- 🏢 **AI 商业模式** — AI SaaS、AI 产品变现
- 🌍 **AI 落地场景** — 真实生产环境中的 AI 工作流

**HN 关键词**（旧 → 新）：

| 移除（噪音高） | 新增（精准） |
|---|---|
| `claude`（太泛） | `openclaw` |
| `llm workflow` | `claude code` |
| `copilot` | `solopreneur ai` |
| `rag pipeline` | `ai saas` |
| `langchain` | `ai workflow` |
| `local llm` | `local ai`、`ai use case` |

**GitHub Topics**（旧 → 新）：

| 移除 | 新增 |
|---|---|
| `llm`、`rag`、`langchain` | `openclaw`、`claude-code`、`vibe-coding`、`ai-saas` |

**Reddit 子版块**：

```
r/openclaw    — OpenClaw 官方社区，使用案例、插件开发
r/vibecoding  — AI 辅助编程工作流讨论
r/ClaudeCode  — Claude Code CLI 专题
r/singularity — AI 技术奇点、超级个体话题
```

---

### 2.5 按热度排序：高 up 优先处理

**问题**：轮询虽然保证了平台多样性，但每个平台内部按 Algolia 返回时间排序，高质量内容可能排在后面被截断丢弃。

**方案**：给 `RawItem` 增加 `score` 字段，各平台填入对应的社区热度指标：

| 平台 | score 来源 |
|------|-----------|
| HackerNews | Algolia 返回的 `points`（点赞数） |
| GitHub | `stargazers_count`（star 数） |
| Reddit | `0`（Exa 不返回投票数） |

在轮询截断之前，**先对每个平台的列表内部按分数降序排序**：

```python
hn_items.sort(key=lambda x: x.score, reverse=True)
github_items.sort(key=lambda x: x.score, reverse=True)
# 然后再轮询交替
```

效果：原来可能被截断的 968pt 帖子（`How I use Claude Code: Separation of planning and execution`）现在稳定出现在报告中。

---

## 三、架构演进对比

```
Milestone 1:
  HN ──► 拼接 ──► 取前N条 ──► LLM 三步 ──► SQLite ──► Obsidian
  Reddit（全部 403，跳过）

Milestone 2:
  HN(按points排序) ──┐
  Reddit(Exa, 按子版块) ──► 轮询交替 ──► 取前N条 ──► LLM 三步 ──► SQLite ──► Obsidian
  GitHub(按stars排序) ──┘
```

---

## 四、Milestone 2 最终运行效果

```
HN: 444 items | Reddit: 3 items | GitHub: 125 items
Interleaved: {'hackernews': 39, 'reddit': 3, 'github': 38}
→ 处理 top 80 (round-robin)
→ 36 条有价值 → 新场景: 36 个
```

典型输出（2026-02-27）：

| 场景 | 内容 |
|------|------|
| AI 编程 | `How I use Claude Code: Separation of planning and execution`（968pt） |
| AI 编程 | r/vibecoding `2个月AI代理编程经验总结` |
| OpenClaw | `openclaw/openclaw` 本体 + NanoClaw 对比项目 |
| 超级个体 | `I quit coding years ago. AI brought me back` — 用 Claude+Windsurf 2周建 60 个计算器 |
| AI 工作流 | `n8n`、`dify`、`activepieces` 等开源工作流平台 |
| 本地 AI | `LocalAI`、`anything-llm`、`LocalGPT`（Rust 版） |

---

## 五、测试覆盖

```
86 passed in 4.54s
```

| 模块 | 测试数 | 关键覆盖 |
|------|--------|---------|
| models | 18 | Pydantic 约束、StrEnum 映射 |
| storage | 15 | SQLite CRUD、JSON 序列化 |
| collectors | 24 | HN(7) + Reddit(9) + GitHub(7)，全部 mock |
| processors | 11 | LLM 三步、JSON 容错 |
| reporters | 12 | Markdown 生成格式 |
| main | 6 | 编排器集成、dry-run |

Reddit 测试全面升级为 `unittest.mock.patch` 模式（mock `Exa` 类），新增：
- `test_filters_out_wrong_subreddit_urls` — 验证 Exa 域名过滤硬保障
- `test_passes_subreddit_domain_to_exa` — 验证每个子版块使用独立 domain

---

## 六、生产化部署

### 6.1 配置 .env

```bash
cp .env.example .env
# 必填：
ANTHROPIC_API_KEY=sk-ant-...
EXA_API_KEY=...           # Exa.ai API Key（用于 Reddit 抓取）
# 选填：
GITHUB_TOKEN=ghp_...      # 提升 GitHub API 限额：60 → 5000 次/小时
ANTHROPIC_BASE_URL=...    # 代理地址
```

### 6.2 写入 Crontab（每天早 7:00 自动运行）

```bash
crontab -e
```

加入以下一行：

```
0 7 * * * /root/projects/ai_usecases_explorer/venv/bin/python -m ai_usecases_explorer.main >> /var/log/ai_usecases_scout.log 2>&1
```

验证：

```bash
crontab -l   # 确认条目存在
# 查看运行日志：
tail -f /var/log/ai_usecases_scout.log
```

当前已写入的 crontab：

```
*/5 * * * * flock -xn /tmp/stargate.lock ...   # 原有条目
0 7 * * * /root/projects/ai_usecases_explorer/venv/bin/python -m ai_usecases_explorer.main >> /var/log/ai_usecases_scout.log 2>&1
```

### 6.3 日常操作

```bash
source venv/bin/activate

# 快速试跑（不写 DB，top 20 条）
MAX_ITEMS_PER_RUN=20 python -m ai_usecases_explorer.main --dry-run

# 完整试跑（不写 DB，top 80 条）
python -m ai_usecases_explorer.main --dry-run

# 正式运行（写 DB + 生成报告）
python -m ai_usecases_explorer.main

# 补跑历史某天
python -m ai_usecases_explorer.main --date 2026-02-26

# 查看运行日志
tail -f /var/log/ai_usecases_scout.log
```

### 6.4 可调参数（.env）

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `ANTHROPIC_API_KEY` | 必填 | Claude API Key |
| `EXA_API_KEY` | 必填 | Exa.ai Key，用于 Reddit 子版块搜索 |
| `ANTHROPIC_BASE_URL` | （空） | 代理地址 |
| `CLAUDE_MODEL` | `claude-haiku-4-5-20251001` | 改 sonnet 质量更高但更贵 |
| `HN_MIN_POINTS` | `10` | HN 最低分数门槛 |
| `GITHUB_TOKEN` | （空） | 填入后 GitHub API 限额 60→5000/小时 |
| `GITHUB_MIN_STARS` | `10` | GitHub 最低 star 数 |
| `REDDIT_SUBREDDITS` | `["openclaw","vibecoding","ClaudeCode","singularity"]` | 监控的子版块列表 |
| `MAX_ITEMS_PER_RUN` | `80` | 每次最多处理条数，控制 LLM 调用成本 |
| `DB_PATH` | `data/usecases.db` | SQLite 文件位置 |
| `OBSIDIAN_REPORT_DIR` | `/root/vault/obsidian_vault/obsidian/Documents/obsidian/auto_report` | 晨报输出目录 |

---

## 七、当前已知限制与 Phase 3 候选

| 优先级 | 方向 | 说明 |
|--------|------|------|
| 高 | Reddit 内容量不足 | Exa 对小众子版块（r/openclaw、r/ClaudeCode）24h 内索引条数少，可考虑延长到 48h 或增加子版块 |
| 高 | 价值过滤误判 | 部分 GitHub 仓库（如 Micasa）通过了价值过滤但与 AI 关联较弱，需优化 prompt |
| 中 | Twitter/X | API 方案待确定 |
| 中 | 相似度去重优化 | 目前 Deduplicator 依赖 LLM 语义判断，可引入向量嵌入加速 |
| 低 | 报告多语言支持 | 摘要目前为中文，可加 `REPORT_LANGUAGE` 配置 |

---

## 八、技术栈（M2 新增）

| 层次 | M1 | M2 新增 |
|------|-----|---------|
| Reddit 采集 | 公共 JSON API（已废弃）| **exa-py SDK**（Exa.ai 神经搜索） |
| GitHub 采集 | 无 | **GitHub Search API**（httpx） |
| 排序策略 | 无（时间顺序） | **按 score 排序**（HN points / GitHub stars） |
| 截断策略 | 顺序取前 N | **轮询交替**（itertools.zip_longest） |
| 测试 mock | pytest-httpx | pytest-httpx + **unittest.mock.patch（Exa）** |
