Files
llm-intelligence/docs/plans/2026-05-25-intraday-discovery-verification-implementation-plan.md

421 lines
16 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.
# Intraday Discovery + Verification Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 在不污染正式日报语义的前提下,为现有日内链路增加“搜索引擎 + 大模型候选发现层”和“官方来源验证层”,让当天的大模型价格新闻、版本发布、活动窗口能更早进入候选池,并只把已验证事实接入现有 `daily_signal_snapshot` / 日报语义链路。
**Architecture:** 保留现有 `scripts/run_intraday_price_watch.sh` 作为结构化价格事实刷新入口,不改它“只刷新价格/信号、不生成正式日报”的边界。新增一条独立的 `run_intraday_discovery_watch.sh` 发现链路:先用搜索引擎与 LLM 生成候选事件,再通过官方页面 / 价格页 / docs / 公告页做二次验证。候选与验证结果分别落入新表;只有 `official_confirmed` 的事件才允许映射进 `materialize_daily_signals.go``signalModelEvent`,并由现有 `generate_daily_report.go` 继续消费,不新造第二套日报事实系统。发现层与验证层必须通过仓库内可运行的 provider adapter 落地,不能依赖当前会话专属工具;实现上采用“命令或 HTTP provider 适配层 + fixture 测试”的方式,确保本地 cron 和 CI 环境可执行。已验证 discovery 事件接入现有事件流时必须去重:若同一 `provider + model + event_type + date` 已由 importer / 原生 loader 给出则以原生事实为准discovery 事件只补缺,不覆盖。
**Tech Stack:** Go 1.22、PostgreSQL、Bash、可配置搜索/LLM provider adapter、JSONB
---
### Task 1: 为候选发现与验证链路定义持久化结构
**Files:**
- Create: `db/migrations/017_intraday_news_candidates.sql`
- Modify: `docs/CONFIGURATION.md`
- Modify: `DEPLOYMENT.md`
**Step 1: 新增候选表与验证表 migration**
创建两张表:
- `intraday_news_candidate`
- `intraday_news_verification`
候选表至少包含:
- `candidate_date`
- `event_type`
- `provider_name`
- `model_name`
- `provider_country`
- `title`
- `summary`
- `candidate_urls JSONB`
- `discovery_source`
- `discovery_query`
- `discovery_evidence JSONB`
- `normalized_key`
- `status`
- `verification_confidence`
- `verification_notes`
验证表至少包含:
- `candidate_id`
- `verifier_source`
- `verifier_url`
- `verifier_status`
- `extracted_facts JSONB`
- `notes`
约束:
- `intraday_news_candidate.normalized_key` 必须唯一,用于防止同日重复发现
- `status` 至少支持:`candidate` / `verifying` / `verified` / `rejected` / `stale`
- `verification_confidence` 至少支持:`candidate` / `secondary_confirmed` / `official_confirmed`
**Step 2: 明确与正式事实层的边界文档**
`docs/CONFIGURATION.md``DEPLOYMENT.md` 写明:
- 候选发现层不会直接写 `daily_report`
- 候选发现层不会覆盖 `latest_report`
- `daily_signal_snapshot` 只消费已验证事实,不消费 `candidate_only`
- `leak_or_rumor` 默认只保留在候选层,不进入正式日报事实
**Step 3: 运行 migration 验证**
Run:
- `bash scripts/apply_migration.sh`
Expected:
- 新表创建成功
- 重复执行 migration 不报错
**Step 4: Commit**
```bash
git add db/migrations/017_intraday_news_candidates.sql docs/CONFIGURATION.md DEPLOYMENT.md
git commit -m "feat(intraday): add candidate and verification persistence"
```
---
### Task 2: 实现候选发现层最小闭环
**Files:**
- Create: `scripts/discover_intraday_news_candidates.go`
- Create: `scripts/discover_intraday_news_candidates_test.go`
- Create: `scripts/testdata/intraday_discovery_search_sample.json`
- Create: `scripts/testdata/intraday_discovery_llm_sample.json`
- Modify: `docs/CONFIGURATION.md`
- Create: `scripts/intraday_discovery_provider.go`
**Step 1: 先写失败测试**
补 4 组测试:
- 搜索结果解析测试:验证能从样例结果提取 title / summary / url / provider 线索
- LLM 输出解析测试:验证能把 LLM JSON 输出转成候选事件
- 候选归一化测试:验证同一事件经过标题差异改写后仍生成同一 `normalized_key`
- URL 过滤测试:验证没有 URL 的候选被丢弃,避免 LLM 空口造线索
**Step 2: 运行失败测试**
Run:
- `go test -count=1 -tags llm_script ./scripts/discover_intraday_news_candidates.go ./scripts/discover_intraday_news_candidates_test.go`
Expected:
- 新增测试失败
- 失败原因是缺少解析、归一化或去重逻辑
**Step 3: 实现最小候选发现器**
`discover_intraday_news_candidates.go` 中实现:
- 固定 provider 查询模板集(中英双语)
- 搜索结果抓取适配层
- LLM 候选摘要适配层
- 去重与归一化逻辑
- 写入 `intraday_news_candidate`
- provider adapter 抽象层(搜索 / LLM 均可通过命令或 HTTP provider 接入,默认实现不可依赖当前会话专属工具)
限制:
- LLM 只允许输出候选,不允许直接标成 `verified`
- 无 URL 候选直接丢弃
- 搜索 / LLM provider 未配置时必须以前置条件错误退出,不能伪装成业务无新闻
- 默认事件类型至少支持:
- `price_cut`
- `price_increase`
- `official_release`
- `promo_campaign`
- `leak_or_rumor`
- `unknown`
**Step 4: 重新运行测试**
Run:
- `go test -count=1 -tags llm_script ./scripts/discover_intraday_news_candidates.go ./scripts/discover_intraday_news_candidates_test.go`
Expected:
- 候选解析与归一化测试通过
**Step 5: 运行一次 dry-run 验证**
Run:
- `go run -tags llm_script ./scripts/discover_intraday_news_candidates.go --date=2026-05-25 --dry-run`
Expected:
- 输出 `candidate_total` / `provider_hit_count` / `event_type_counts`
- dry-run 不写 `daily_report`
- dry-run 不改 `latest_report`
**Step 6: Commit**
```bash
git add scripts/discover_intraday_news_candidates.go scripts/discover_intraday_news_candidates_test.go scripts/testdata/intraday_discovery_search_sample.json scripts/testdata/intraday_discovery_llm_sample.json docs/CONFIGURATION.md
git commit -m "feat(intraday): add news candidate discovery pipeline"
```
---
### Task 3: 实现候选验证层并固化“只信官方事实”的规则
**Files:**
- Create: `scripts/verify_intraday_news_candidates.go`
- Create: `scripts/verify_intraday_news_candidates_test.go`
- Create: `scripts/testdata/intraday_verification_official_release.html`
- Create: `scripts/testdata/intraday_verification_pricing_page.html`
- Create: `scripts/testdata/intraday_verification_secondary_media.html`
- Modify: `docs/CONFIGURATION.md`
**Step 1: 先写失败测试**
补 5 组测试:
- 官方发布页验证测试:命中模型名与发布时间时,产出 `official_confirmed`
- 官方价格页验证测试:只有拿到真实价格变化时,才允许产出 `price_cut` / `price_increase`
- 活动页验证测试:官方活动页可映射为 `promo_campaign`
- 二手媒体降级测试:二手媒体最多得到 `secondary_confirmed`,不能直接进入正式事实层
- 泄露类隔离测试:`leak_or_rumor` 即使有外部讨论,也不会升级为正式日报事实
**Step 2: 运行失败测试**
Run:
- `go test -count=1 -tags llm_script ./scripts/verify_intraday_news_candidates.go ./scripts/verify_intraday_news_candidates_test.go`
Expected:
- 新增测试失败
- 失败原因是缺少来源分类与验证状态映射逻辑
**Step 3: 实现验证器**
`verify_intraday_news_candidates.go` 中实现:
- 读取 `candidate` / `verifying` 状态候选
- 拉取 `candidate_urls`
- 基于域名与页面内容判定:
- `official_page`
- `pricing_page`
- `official_docs`
- `official_blog`
- `secondary_media`
- 把验证轨迹写入 `intraday_news_verification`
- 更新 `intraday_news_candidate.status``verification_confidence`
- 验证成功后只更新候选层状态,不直接写 `daily_signal_snapshot`;正式事实仍统一由物化器汇总
规则:
- 只有官方页面 / 价格页 / docs / 公告页可以产出 `official_confirmed`
- 价格新闻若无法拿到真实价格事实,只能维持候选或二级确认,不能伪造价格变化事件
- `leak_or_rumor` 默认不升级为正式事实
**Step 4: 重新运行测试**
Run:
- `go test -count=1 -tags llm_script ./scripts/verify_intraday_news_candidates.go ./scripts/verify_intraday_news_candidates_test.go`
Expected:
- 验证规则测试通过
**Step 5: 运行一次 dry-run 验证**
Run:
- `go run -tags llm_script ./scripts/verify_intraday_news_candidates.go --date=2026-05-25 --dry-run`
Expected:
- 输出 `verified_total` / `official_confirmed_total` / `secondary_confirmed_total`
- dry-run 只打印摘要,不写 `daily_report`
**Step 6: Commit**
```bash
git add scripts/verify_intraday_news_candidates.go scripts/verify_intraday_news_candidates_test.go scripts/testdata/intraday_verification_official_release.html scripts/testdata/intraday_verification_pricing_page.html scripts/testdata/intraday_verification_secondary_media.html docs/CONFIGURATION.md
git commit -m "feat(intraday): add candidate verification pipeline"
```
---
### Task 4: 把已验证事件接入现有 `materialize_daily_signals.go`
**Files:**
- Modify: `scripts/materialize_daily_signals.go`
- Create or Modify: `scripts/materialize_daily_signals_test.go`
- Modify: `docs/plans/2026-05-27-intraday-price-watch-plan.md`
- Modify: `README.md`
- Modify: `docs/PRODUCTION_CHECKLIST.md`
**Step 1: 先写失败测试**
补 4 组测试:
- 已验证官方发布事件会进入 `daily_signal_snapshot.top_events`
- 已验证活动事件会进入 `daily_signal_snapshot.top_events`
- `candidate_only``leak_or_rumor` 不进入正式快照
- 未拿到真实价格变化数据的“价格新闻”不会被错误映射为 `price_cut` / `price_increase`
**Step 2: 运行失败测试**
Run:
- `go test -count=1 -tags llm_script ./scripts/materialize_daily_signals.go ./scripts/materialize_daily_signals_test.go`
Expected:
- 新增测试失败
- 失败原因是当前物化器还不会读取已验证候选事件
**Step 3: 最小实现 verified event loader**
`materialize_daily_signals.go` 中新增:
- `loadVerifiedIntradayNewsEvents(db, date string)`
-`official_confirmed` 的:
- `official_release`
- `promo_campaign`
- 已确认真实价格变化的 `price_cut` / `price_increase`
映射为现有 `signalModelEvent`
- 与现有 `loadSignalModelEvents` 结果做去重合并;同日同模型同事件类型若已由 importer / 原生 loader 给出,则 discovery 事件仅补 `SourceURL` / 证据缺口,不抢占优先级
约束:
- 不新造第二套快照表
- 不改变 `daily_signal_snapshot` 的正式事实语义
- `secondary_confirmed` 默认不进入正式快照
**Step 4: 重新运行测试**
Run:
- `go test -count=1 -tags llm_script ./scripts/materialize_daily_signals.go ./scripts/materialize_daily_signals_test.go`
Expected:
- verified event 相关测试通过
**Step 5: 联合验证日内边界**
Run:
- `REPORT_TRIGGER_SOURCE=intraday_discovery go run -tags llm_script ./scripts/materialize_daily_signals.go --date=2026-05-25 --dry-run`
Expected:
- 输出含 `page_mode` / `event_count`
- 不写 `daily_report`
- 不覆盖 `latest_report`
**Step 6: Commit**
```bash
git add scripts/materialize_daily_signals.go scripts/materialize_daily_signals_test.go README.md docs/PRODUCTION_CHECKLIST.md docs/plans/2026-05-27-intraday-price-watch-plan.md
git commit -m "feat(intraday): materialize verified discovery events"
```
---
### Task 5: 组装新的日内发现入口并补部署说明
**Files:**
- Create: `scripts/run_intraday_discovery_watch.sh`
- Modify: `README.md`
- Modify: `docs/CONFIGURATION.md`
- Modify: `DEPLOYMENT.md`
- Modify: `docs/PRODUCTION_CHECKLIST.md`
**Step 1: 实现独立入口脚本**
脚本顺序固定为:
1. `discover_intraday_news_candidates.go`
2. `verify_intraday_news_candidates.go`
3. `materialize_daily_signals.go`(仅消费 verified 事件)
要求:
- 明确要求 `DATABASE_URL`
- 搜索 / LLM 所需 key 缺失时,输出前置条件错误,不伪装成代码失败
- 不执行 `generate_daily_report.go`
- 不写 `daily_report`
- 不覆盖 `latest_report`
**Step 2: 更新调度文档**
文档里明确两条 cron
- 结构化价格刷新:`run_intraday_price_watch.sh`
- 新闻发现与验证:`run_intraday_discovery_watch.sh`
推荐起步频率:
- `run_intraday_discovery_watch.sh`:每 2 小时一次
- `run_intraday_price_watch.sh`:每 4 小时一次
**Step 3: 运行脚本级 dry-run**
Run:
- `bash scripts/run_intraday_discovery_watch.sh --dry-run`
Expected:
- 输出候选发现摘要 + 验证摘要 + 信号物化摘要
- 不生成正式日报产物
**Step 4: Commit**
```bash
git add scripts/run_intraday_discovery_watch.sh README.md docs/CONFIGURATION.md DEPLOYMENT.md docs/PRODUCTION_CHECKLIST.md
git commit -m "feat(intraday): add discovery watch runner"
```
---
### Task 6: 运行最终联合验收并准备本地提交
**Files:**
- Modify: `README.md`(仅在最终说明缺失时)
- Modify: `docs/CONFIGURATION.md`(仅在最终说明缺失时)
- Modify: `DEPLOYMENT.md`(仅在最终说明缺失时)
**Step 1: 运行 focused Go tests**
Run:
- `go test -count=1 -tags llm_script ./scripts/discover_intraday_news_candidates.go ./scripts/discover_intraday_news_candidates_test.go`
- `go test -count=1 -tags llm_script ./scripts/verify_intraday_news_candidates.go ./scripts/verify_intraday_news_candidates_test.go`
- `go test -count=1 -tags llm_script ./scripts/materialize_daily_signals.go ./scripts/materialize_daily_signals_test.go`
Expected:
- 发现层、验证层、信号物化层 focused tests 全通过
**Step 2: 运行现有日报/前端回归边界**
Run:
- `go test -count=1 -tags llm_script ./scripts/generate_daily_report.go ./scripts/generate_daily_report_test.go ./scripts/official_import_signature_audit_query_lib.go`
- `bash scripts/secret_gate_test.sh`
- `bash scripts/test_importers.sh`
- `cd frontend && npm test -- --run`
- `cd frontend && npm run build`
Expected:
- 原有日报与前端链路不回归
- discovery 新增能力不污染正式日报边界
**Step 3: 运行脚本级联合 dry-run**
Run:
- `bash scripts/run_intraday_discovery_watch.sh --dry-run`
- `REPORT_TRIGGER_SOURCE=intraday go run -tags llm_script ./scripts/materialize_daily_signals.go --date=2026-05-25 --dry-run`
Expected:
- 不写 `daily_report`
- 不覆盖 `latest_report`
- 能稳定输出候选数、验证数、事件数、page_mode、source_audit
**Step 4: 本地提交**
```bash
git add db/migrations/017_intraday_news_candidates.sql scripts/discover_intraday_news_candidates.go scripts/discover_intraday_news_candidates_test.go scripts/verify_intraday_news_candidates.go scripts/verify_intraday_news_candidates_test.go scripts/materialize_daily_signals.go scripts/materialize_daily_signals_test.go scripts/run_intraday_discovery_watch.sh README.md docs/CONFIGURATION.md DEPLOYMENT.md docs/PRODUCTION_CHECKLIST.md docs/plans/2026-05-25-intraday-discovery-verification-implementation-plan.md docs/plans/2026-05-27-intraday-price-watch-plan.md
git commit -m "feat(intraday): add discovery and verification watch pipeline"
```
---
## 验收标准
实现完成后,必须同时满足:
- 搜索 + LLM 只能产生候选事件,不能直接写成正式日报事实
- 只有 `official_confirmed` 的事件才能进入正式 `daily_signal_snapshot` 语义链路
- `leak_or_rumor` 不进入正式日报事实层
- `run_intraday_discovery_watch.sh``run_intraday_price_watch.sh` 职责分离
- 正式日报仍只由 `run_daily.sh` 负责
- 新增链路不会写 `daily_report`、不会覆盖 `latest_report`
- discovery provider adapter 在无配置时会明确报前置条件错误;有 fixture / dry-run 模式可本地验证
- 新增 focused tests、现有日报测试、前端构建全部通过
## 非目标
本计划刻意不做:
- 不新增第二套正式日报系统
- 不让 LLM 直接替代价格 importer 或官方发布 importer
- 不把二手媒体新闻直接映射为 `price_cut` / `price_increase`
- 不在第一阶段引入新的前端“候选情报面板”复杂交互;若后续需要,单独立计划