# Hermes Cron Jobs — we-mp-rss Consumer Ecosystem

All Hermes cron jobs that read from `~/project/we-mp-rss-data/db.db` or control we-mp-rss.

| Job ID | Name | Schedule | DB Access | Deliver | Purpose |
|--------|------|----------|-----------|---------|---------|
| `94d230f73e7f` | 春考选题-文章抓取 | every 120m | API trigger (`/mps/update/{id}`) | origin | Calls `fetch_and_push.py` → triggers we-mp-rss update → pushes new articles to 企微群 |
| `218c1e953f63` | 选题日报 | 55 8 * * * | `daily_topic_report.py` (no_agent=true, no LLM) | wecom_callback:TuKeXin,wecom_callback:CaoXiaoFen | Queries DB directly via Python, keyword-clusters articles, generates formatted daily report. Zero hallucination risk. |
| `472ca7495b44` | 热点雷达 | */30 8-22 * * * | `hot_radar.py` (no_agent=true, no LLM) | wecom_callback:TuKeXin,wecom_callback:CaoXiaoFen | Detects cross-source hot topics via topic-pattern matching. Silent when no hotspot; alert markdown when detected. Deduplicates via `~/project/state/hot_radar_sent.json`. |
| `533d92e64b59` | gdcjgk-每日发布 | 55 9 * * * | `daily_gdcjgk_check.py` (no_agent=true, no LLM) | wecom_callback:TuKeXin,wecom_callback:CaoXiaoFen | Checks DB for new articles and lists candidates. Silent when no articles; outputs article list when articles found. Article writing is manual (CMS paste). |
| `eba217dfc155` | we-mp-rss 登录状态巡检 | 0 9 * * * | Redis + DB + HTTP (`check_werss_login.py`, no_agent=true) | wecom_callback:TuKeXin,wecom_callback:CaoXiaoFen | Checks Redis login status + DB freshness + service reachability. Silent on success, alert markdown on failure. No hardcoded webhook. |
| `c27628909af0` | 小红书每日选题 | 5 9 * * * | via `daily_xhs_topics.py` | origin | Generates XHS topics + cover images |

## Debugging Cron Delivery Issues

When a user reports receiving a cron notification with suspicious content (e.g., stale data, wrong timestamps):

1. **Check the saved output file first**: `~/.hermes/cron/output/<job_id>/<timestamp>.md` — contains the full agent log including the actual response produced
2. **Compare against what the user received**: if they differ, it's a delivery anomaly, not a data/analysis problem
3. **Verify the DB independently**: query the DB directly to establish ground truth, don't trust either the cron output or the user's received message alone

Example from 2026-07-14: User received "📭 近 36 小时无新文章 (last fetch June 23)" from job 218c1e953f63, but the saved output file showed a normal report with 1 article from July 12, and the DB confirmed articles through July 12. Root cause: delivery/platform anomaly, not a data problem.

## Data Extraction Scripts

**Rule: Cron jobs that need DB data MUST use pre-built scripts, NEVER LLM-written SQL.** LLMs hallucinate database content (fake article counts, wrong timestamps, stale dates) when allowed to write their own queries. The script becomes the single authoritative data source.

### `daily_topic_report.py` (选题日报 — no-LLM replacement)
Path: `~/.hermes/scripts/daily_topic_report.py` (and `skills/devops/we-mp-rss/scripts/daily_topic_report.py`)
Full replacement for the LLM-driven 选题日报 cron. Queries DB, extracts keywords, matches against 15 topic patterns, detects cross-account hot topics, formats markdown report with calendar integration. Output ≤3800 bytes (WeCom markdown limit). Zero hallucination risk.

### `daily_gdcjgk_check.py` (gdcjgk-每日发布 — no-LLM replacement)
Path: `~/.hermes/scripts/daily_gdcjgk_check.py`
Replaces the LLM-driven gdcjgk-每日发布 cron. Checks DB for new articles in window. Silent when no articles (exit 0, empty stdout). Outputs candidate article list with source/times when articles exist. Notes that articles still require manual review + CMS publish. Also detects DB staleness (>48h gap → alert with login check link).

### `hot_radar.py` (热点雷达 — no-LLM replacement)
Path: `~/.hermes/scripts/hot_radar.py`
Detects cross-source hot topics in recent N-hour window. Uses topic-pattern matching (same 16 patterns as daily_topic_report.py). Groups articles by topic tag, checks if ≥2 different sources cover same topic. Deduplicates via `~/project/state/hot_radar_sent.json`. Silent when <2 sources or no cross-source matches.

### `daily_topic_data.py` (选题日报 — deprecated raw data feed)
Path: `~/.hermes/scripts/daily_topic_data.py`
Reads from `DB_PATH = "/home/ubuntu/project/we-mp-rss-data/db.db"`. Usage: `python3 daily_topic_data.py [hours=36]`
Output: article list table, article details (summary/link), exam calendar (next 14 days).

### `daily_gdcjgk_data.py` (gdcjgk-每日发布)
Path: `~/.hermes/scripts/daily_gdcjgk_data.py`
Same DB, wider output: total article count, latest article time, candidate articles (with IDs and abstracts), exam calendar (next 30 days), image library stats. Usage: `python3 daily_gdcjgk_data.py [hours=36]`

### Script Creation Template
When creating a new data script for a cron job:
```python
#!/home/ubuntu/project/we-mp-rss-main/venv/bin/python3
"""One-line purpose. Usage: python3 script.py [hours=N]"""
import sqlite3, json, sys
from datetime import datetime, timezone, timedelta

HOURS = int(sys.argv[1]) if len(sys.argv) > 1 else 36
DB_PATH = "/home/ubuntu/project/we-mp-rss-data/db.db"
beijing = timezone(timedelta(hours=8))
now = datetime.now(beijing)
cutoff_ts = int((now - timedelta(hours=HOURS)).timestamp())

db = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)
db.row_factory = sqlite3.Row
rows = db.execute("""
    SELECT a.id, a.title, a.url, a.publish_time, a.description, f.mp_name
    FROM articles a JOIN feeds f ON a.mp_id = f.id
    WHERE a.publish_time > ?
    ORDER BY a.publish_time DESC
""", (cutoff_ts,)).fetchall()
db.close()
# ... format and print results
```

## DeepSeek API Peak-Time Avoidance (partial fix) → `no_agent=true` (permanent fix)

DeepSeek API (`api.deepseek.com`) experiences severe latency at peak hours, especially with large prompts (e.g. loading the `wecom-bot-integration` skill adds ~38KB). Symptoms:
- `Stream stale for 180s — no chunks received`
- `[Errno 32] Broken pipe` on all retries
- "Streaming failed before delivery" delivers **partial/hallucinated LLM output** — resulting in TWO messages: one hallucinated (from partial delivery), one correct (from retry). This is the root cause of duplicate daily reports with fake data.

**Partial fix** (schedule avoidance): Schedule cron jobs at off-peak minutes (`55 8 * * *`, `55 9 * * *`). Reduces but does NOT eliminate the race condition.

**Permanent fix** (`no_agent=true` scripts): Convert hallucination-prone cron jobs to self-contained Python scripts with `no_agent=true`. The script queries the DB directly (no LLM in path), prints output to stdout, Hermes delivers verbatim. If reasoning/analysis is truly needed, use a two-stage pipeline: Stage 1 = `no_agent=true` data collection script, Stage 2 = LLM cron with `context_from` (much smaller prompt). Applied to:
- `eba217dfc155` (login巡检): `no_agent=true`, script-only
- `218c1e953f63` (选题日报): `no_agent=true`, `daily_topic_report.py` replaces LLM with keyword clustering + templated output
