akg-factor-bridge/docs/第一批联调验证清单_2026-07-26.md

356 lines
17 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# 第一批 · 联调验证清单v22026-07-26
> **本清单取代 `第一批验证清单_2026-07-26.md`**(那份把「基座机器」和「桥机器」混为一谈,
> 第 1 步的 `docker exec -i akg-postgres psql` 在桥的服务器上根本不存在这个容器)。
>
> 这一批跨 **三个工程 / 至少两台机器**,所以每一步都标注了「在哪台机器、哪个工程、
> 哪个容器里执行」,以及判定标准、失败去哪、怎么回滚。**不要跨节跳着做**——
> 节与节之间有依赖顺序。
---
## §0 这一批到底在联调什么
| 工程 | 本批是否改动 | 改了什么 | 谁消费 |
|---|---|---|---|
| **astock-kg基座** | **只改 DB 里的 4 个视图 + 1 个可空列 + 1 个索引,代码零改动** | `sql/astock_kg_slot_views.sql`文件在桥仓库DDL 落在基座库) | 只有桥 |
| **akg-factor-bridge** | 代码全批 | 四路口径 / 事件过滤 / 分块 / 冻结 / probe corr | —— |
| **quant_factor_service平台** | 无改动 | 桥往 `t_factor_akg_*` + `factor_metadata` 写行 | 平台评价与下游 |
**耦合面只有一个**:桥代码 ↔ 基座库里那 4 个视图。其余都是各自内部的事。
---
## §1 拓扑登记(**请你填,我不知道**,填完发我一份,后面所有命令按它对号入座)
| 角色 | 主机 / 用户 | 容器名 | 本批需要的权限 | 已确认可达? |
|---|---|---|---|---|
| 基座 PG | `______` | `akg-postgres` | **DDL**(建视图/加列/建索引) | ☐ |
| 基座 backend/beat | 同上 | `akg-backend` / beat | 本批不动 | —— |
| 桥 | `factor@factorevaluation:~/project/akg-factor-bridge` | `akg_factor_bridge` | 读基座 PG / 读 153 / 写平台 MySQL | ☐ |
| 153 代理(热度) | `______` | —— | 只读 | ☐ |
| 平台因子库 + `gp_day_data` | `______` | —— | **写** `t_factor_akg_*`、`factor_metadata` | ☐ |
| 开发机 | `baobao@…/Documents/work/project/` | —— | git 源头 | —— |
> **关于 DDL 账号**:基座 compose 里 `POSTGRES_USER: ${PG_USER:-akg}` —— 这个账号就是
> 库的 owner**天然有 DDL 权限**。如果桥 `.env` 的 `AKG_PG_USER` 就是它§5 直接能跑;
> 如果你另外建了只读账号给桥用§5 需要临时用 owner 账号。
---
## §2 已代查的前置事实(**你不需要再验证**,列出来是为了让你知道风险面有多大)
1. **改这 4 个视图的影响面 = 0**。`grep -rn "v_factor_" backend/ frontend/src` 在 astock-kg
里**零命中** —— 四视图目前只有桥在消费,替换它们不会波及基座任何功能。
2. **`ALTER TABLE transmission_candidates ADD COLUMN` 安全**。全仓没有 `SELECT *` 打在这张表上:
`transmission.list_candidates` 用显式列清单、`INSERT` 也是显式列清单、
`factor_coverage_probe` 只取 `scan_date, quiet`。加一个可空列不会撑坏任何列序假设。
3. **不删任何数据**。§5 的 6 条语句 = 4 个 `CREATE OR REPLACE VIEW` + 1 个
`ALTER TABLE ADD COLUMN IF NOT EXISTS` + 1 个 `CREATE INDEX IF NOT EXISTS`
`claims` / `documents` 一个字节不动,不需要 replay不需要重抽。
4. **基座 PG 是 `postgres:16.9-alpine` 且端口已发布**`ports: "${PG_PORT:-5432}:5432"`
所以桥跨机连得上§5 的一次性 psql 容器用 `postgres:16-alpine` 客户端版本也对得上。
5. **基座 beat 时刻表**工作日05:30 主数据 → 17:30 行情快照 → 18:00 hotspot →
**18:15 传导扫描** → 周一 06:00 池刷新 → 07:00 日报。
**桥的 daily build 必须在 18:15 之后跑**,否则当日传导表是空的。
---
## §3 版本对齐(开发机 ↔ 桥服务器,**两处必须同一个 commit**
```bash
# 开发机
cd ~/Documents/work/project/akg-factor-bridge && git rev-parse --short HEAD
# 桥服务器
cd ~/project/akg-factor-bridge && git rev-parse --short HEAD
```
**判定**:两个短 hash 一致 → 通过。不一致 → 开发机 `git push`、桥服务器 `git pull`
然后**必须** `docker compose up -d --build`(代码卷挂载虽然是 `.:/app`,但依赖变了要重建)。
> ⚠️ 桥服务器上 `.env` 是**不进版本库的**(本批新加的 `.gitignore` 已排除它)。
> `git pull` 不会动它。新旋钮全部有默认值,`.env` 不改也能跑。
---
## §4 基线快照(**改动前跑**,用于事后证明"抽取成果没白费"
在**基座机器**上:
```bash
docker exec -i akg-postgres psql -U akg -d akg <<'SQL'
SELECT 'claims' t, count(*) n, max(ingestion_date)::date latest FROM claims
UNION ALL SELECT 'documents', count(*), max(ingested_at)::date FROM documents
UNION ALL SELECT 'entity_links', count(*), max(created_at)::date FROM entity_links
UNION ALL SELECT 'company_master', count(*), max(synced_at)::date FROM company_master
UNION ALL SELECT 'consensus_daily', count(*), max(asof_date) FROM consensus_daily
UNION ALL SELECT 'mkt_daily', count(*), max(trade_date) FROM mkt_daily
UNION ALL SELECT 'transmission_candidates', count(*), max(scan_date) FROM transmission_candidates
UNION ALL SELECT 'industry_pools', count(*), max(refreshed_at)::date FROM industry_pools;
-- 顺带回答一个对回填边界很关键的问题mkt_daily 是持久快照表、全库无删除)
SELECT kind, min(trade_date), max(trade_date), count(DISTINCT trade_date) AS days
FROM mkt_daily GROUP BY kind ORDER BY kind;
SQL
```
**留档**把输出存下来。§8 判收时再跑一次,前 4 行必须**逐字不变**。
---
## §5 应用视图(**本批唯一一次对基座库的写操作**
按你人在哪台机器,三选一。**只需要跑一次**,跑在基座库上,跟你从哪台机器发起无关。
### 5a · 在桥服务器上,用桥自己的连接(推荐;无需 psql 客户端)
⚠️ 需要先完成 §3`apply-views` 是本批新增的命令,旧代码没有)。
```bash
cd ~/project/akg-factor-bridge
docker compose exec -T akg-factor-bridge python run.py apply-views --dry-run # 先看要跑哪 6 条
docker compose exec -T akg-factor-bridge python run.py apply-views
```
`.env` 里是只读账号,会逐条报 `permission denied`,改用 owner 账号跑这一次:
```bash
docker compose exec -T \
-e AKG_PG_USER=akg -e AKG_PG_PASSWORD=<基座 PG_PASSWORD> \
akg-factor-bridge python run.py apply-views
```
> 必须用 `docker compose exec -e`。写成 `AKG_PG_USER=x docker compose exec …`
> 只设置宿主机上 compose 客户端进程的环境变量,**传不进容器**。
### 5b · 在桥服务器上,用一次性 psql 容器§3 还没做完时的即时方案)
```bash
cd ~/project/akg-factor-bridge
set -a; . ./.env; set +a
docker run --rm -i -e PGPASSWORD="$AKG_PG_PASSWORD" postgres:16-alpine \
psql -h "$AKG_PG_HOST" -p "${AKG_PG_PORT:-5432}" -U "$AKG_PG_USER" -d "$AKG_PG_DB" \
-v ON_ERROR_STOP=1 < sql/astock_kg_slot_views.sql
```
### 5c · 在基座机器上(需要先把 `sql/astock_kg_slot_views.sql` 带过去)
```bash
docker exec -i akg-postgres psql -U akg -d akg < sql/astock_kg_slot_views.sql
```
### 判定标准(三条路一样)
6 条语句全部成功:`CREATE VIEW` ×4 + `ALTER TABLE` + `CREATE INDEX`,无 ERROR。
5a 会逐条打 ✅/❌ 并在末尾汇总,缺权限时能精确定位到是哪一条。)
### 回滚
```bash
# 视图退回 v1原文件在这两处任选其一
# 桥服务器: .review_backup_20260726/sql_astock_kg_slot_views.sql
# 或 git: git show 42659b9:sql/astock_kg_slot_views.sql > /tmp/v1.sql
docker exec -i akg-postgres psql -U akg -d akg < /tmp/v1.sql
# ALTER 加的列与索引是纯增量、无害,不需要回滚;真要清:
# ALTER TABLE transmission_candidates DROP COLUMN IF EXISTS mkt_trade_date;
# DROP INDEX IF EXISTS idx_tc_target;
```
---
## §6 兼容性矩阵(联调的核心,四个象限**现在全都是安全的**
`n_paths` 列在 v2 视图里**原名原义保留**(我原来改成了 `n_paths_raw`,那会让旧桥代码
查不到列而崩 —— 已修正)。所以两个工程可以**任意顺序**升级、任意一侧独立回滚:
| | 视图 v1 | 视图 v2 |
|---|---|---|
| **桥 v1旧代码** | 原状态 | ✅ 照常工作(读 `n_paths`,旧口径) |
| **桥 v2新代码** | ✅ 可跑,但退回旧口径并打 ⚠️ 告警<br>`v_factor_transmission 还是旧版(无 n_sources` | ✅ **目标状态**,用 `n_sources` |
**你现在处于左下格**(桥已 pull 到 v2、视图还是 v1——这是**合法的过渡态**
不是故障。做完 §5 就进右下格。
**判定右下格已达成**
```bash
docker compose exec akg-factor-bridge python run.py views
```
看新增的「视图版本」段,前三项必须是 ✅:
```
✅ v_factor_transmission.n_sources
✅ v_factor_transmission.mkt_trade_date
✅ v_factor_events.source_type
⬜ v_factor_segment_members第五插槽G2 ← 这一项现在就该是 ⬜
```
---
## §7 桥侧功能验证
### 7a · 只读(不写任何库,可反复跑)
```bash
cd ~/project/akg-factor-bridge
docker compose exec akg-factor-bridge python run.py views
docker compose exec akg-factor-bridge python run.py probe --section corr # ★ 最重要
docker compose exec akg-factor-bridge python run.py probe --section upside
docker compose exec akg-factor-bridge python run.py probe --section pools
docker compose exec akg-factor-bridge python run.py probe --section price # 全表聚合1~3 分钟
```
> `pools` 节直读基座 `industry_pools` 表(一次性诊断的例外)。若桥用的是只读账号
> 且没授这张表,此节会 permission denied——**不影响其余三节**,跳过即可。
**`corr` 节要回答的两件事**(这一节决定第三批怎么定,请把整段原样发我):
- **① `corr(z_H, z_V)`**:若 |值| > 0.5,「还没热」与「便宜」高度共线,
「三项加权」实际是两项§9-8 权重讨论要重开。
- **② `w_T=0.50` 那行的 top-K 命中数**:若 ≈ `min(K, 传导票数)`
实锤「0.5/0.3/0.2 事实上是传导优先而非加权混合」。
同节还会打印**两段式与当前公式的 top20 重合度**——越接近 20/20
说明改两段式零代价,第三批 3.2 直接选 A。
### 7b · 写平台因子库(**这一步会往 quant_factor_service 的生产表写行**
```bash
# 注册幂等ON DUPLICATE KEY UPDATE
docker compose exec akg-factor-bridge python run.py register
# 构建:日期选「传导有数据」的那天,且必须在基座 18:15 传导扫描之后
# run.py views 的「数据历史深度」段会告诉你各源前沿在哪)
docker compose exec akg-factor-bridge python run.py build all --mode daily --date 2026-07-24
```
**写入是幂等的**(按日期区间 DELETE 再 INSERT重跑不会产生重复。
**回滚**`DELETE FROM t_factor_akg_* WHERE trade_date = '<日期>'`。
**逐路对照上一次的行数**2026-07-23upside 795 / heat 1674 / event 195 / transmission 83
| 因子 | 预期变化 | 变化原因 | 不符时看哪里 |
|---|---|---|---|
| `akg_upside` | 基本持平 | 只改了分块读取,口径未动 | 若骤降 → `gp_day_data` 代码列没对上 |
| `akg_heat` | 持平 | 未动 | —— |
| `akg_event` | **大概率下降** | 新加 `source_type='announcement'` 白名单 + 单文档封顶 | **见下** |
| `akg_transmission` | 持平或略变 | `n_paths``n_sources`,重复路径不再重复计数 | 值会变、行数不该变 |
> **`akg_event` 掉到接近 0 的处置**:日志会打印
> `(事件:按 source_type 剔除 N 条 {...分布...})`。若剔除后为空,说明基座里公告的
> `documents.source_type` 不是 `announcement` 这个字符串。查一下真实取值:
> ```bash
> docker exec -i akg-postgres psql -U akg -d akg -c \
> "SELECT source_type, count(*) FROM documents GROUP BY 1 ORDER BY 2 DESC;"
> ```
> 然后在桥服务器 `.env` 里写 `EVENT_SOURCE_TYPES=<真实值>` 并重跑,**不要改代码**。
### 7c · 传导告警的正确读法(**容易误判,请按这个读**
`build akg_transmission` 会打三类 ⚠️。**它们的"没出现"含义各不相同**
| 告警 | 出现 = | **没出现 = ?** |
|---|---|---|
| `quiet 存满 12 条` | 撞 `transmission.py``quiet[:12]` 截断 | 真的没撞(环节未动成员本来就 <12 |
| `members_total >= 30` | 撞基座 `topic_context` `LIMIT cap` | 真的没撞 |
| `mkt 快照日 ≠ scan_date` | movers 陈旧 | ** 不能判定**——`mkt_trade_date` 要等**第二批**改了基座才会被写入本批全是 NULL桥会跳过这项检查。**本批它不出现是必然的不是"通过"。** |
前两个告警各命中几个环节请发我——那是硬伤 1 的实测量级直接决定第二批里
`topic_context` cap 该提到多少
### 7d · 输入冻结
daily build 跑完会自动冻结检查
```bash
docker compose exec akg-factor-bridge cat /app/data/frozen/2026-07-24/manifest.json
ls -la data/frozen/2026-07-24/
```
**判定**`manifest.json` 存在`rows` universe / consensus / price_close / heat /
transmission 各有行数heat 可能是 0—— T+1 到达属正常
`segment_members` / `segment_edges` 0第五/六视图 G2 才有属正常)。
### 7e · 事件回填冒烟(验证硬伤 3 真的修好了)
```bash
docker compose exec akg-factor-bridge python run.py build akg_event \
--mode history --start 2024-01-01 --end 2024-03-31 --no-freeze
```
**判定**改之前这条命令必然静默返回无数据跳过)」(日历取自热度表最早 2025-03)。
现在应该有真实行数前提是 2024 年的公告已抽取)。
若仍为空先看日志是不是被 `EVENT_SOURCE_TYPES` 过滤掉了——两种""日志不一样
---
## §8 判收标准(逐条可独立判定,**一条不过不判收**
**工程正确性**
- [ ] §3 两处 git hash 一致
- [ ] §5 六条 DDL 全部成功
- [ ] §6 `views` 的视图版本段前三项 ✅、第四项
- [ ] §7a 四节 probe 至少 `corr` / `upside` / `price` 三节出数`pools` 权限不足可跳
- [ ] §7b `register` + `build all` ❌,四路都有行数
- [ ] §7d `manifest.json` 生成且各源有行数
- [ ] §7e 2024 年区间不再静默返空
**数据无损**回答"抽取成果会不会白费"
- [ ] §4 的基线 SQL 重跑一次`claims` / `documents` / `entity_links` / `company_master`
四行的 `count` `latest` **逐字不变**
**幂等**
- [ ] `build all --mode daily --date <同一天>` 连跑两次第二次行数与第一次相同
平台表无重复主键 `(trade_date, stock_code)` 本就保证此处是验证 DELETE 区间正确
---
## §9 失败对照表
| 现象 | 根因 | 处置 |
|---|---|---|
| `No such container: akg-postgres` | 你在**桥服务器**基座容器不在这台机器 | §5a §5b |
| `permission denied for ...` (DDL) | 桥用的是只读账号 | §5a `-e` 覆盖成 owner 账号 |
| `views` 里视图版本三项都 | §5 没跑成功 | §5 5a 的逐条报错 |
| 桥打 `v_factor_transmission 还是旧版` | 处于兼容矩阵左下格 | 正常过渡态做完 §5 即消失 |
| `akg_event` 行数为 0 且日志显示剔除全部 | `source_type` 实际值不是 `announcement` | 查真实取值 `.env` `EVENT_SOURCE_TYPES` |
| `akg_transmission` 行数为 0 | 当日基座传导扫描没产出beat 18:15 未跑 / 被饿 | 换一个 `--date`或先在基座跑 `transmission_scan` |
| `corr` 节报无传导台账 | 同上 | 同上 |
| `probe --section pools` permission denied | 只读账号没授 `industry_pools` | 跳过该节不影响判收 |
| 平台 `gp_day_data` 查不到 / upside 0 | 代码列形态没对上 | `.env` `PRICE_CODE_COL`实测应为 `symbol` |
---
## §10 请回传给我的(拿到这些才能定第三批,不用再靠我的模拟)
1. §1 拓扑登记表填好
2. §4 基线 SQL 的两段输出** `mkt_daily` kind 的天数** —— 它决定传导理论上能重扫到哪天
3. §5 的执行输出6 条语句的结果
4. §6 `run.py views` 全部输出
5. **§7a `probe --section corr` 全部输出 最重要**
6. §7a `probe --section upside` q 档位表
7. §7b `build all` 的完整日志尤其 event 的剔除分布transmission 的两项 各命中几个环节
8. §8 判收清单的勾选情况
---
## §11 本批不做的事(避免混淆)
- **不改 astock-kg 任何代码**。`topic_context` `ORDER BY`、`transmission.scan` 去掉
`quiet[:12]`、`_latest_mkt` 加参数、`sync_consensus` `asof` —— 全在**第二批**。
- 因此 `mkt_trade_date` 本批全是 NULL §7c`quiet[:12]` 截断依然存在
`moved_ratio` 依然建立在任意顺序的 30 抽样上本批只是让这些问题**看得见**。
- 赛道门槛 C、`akg_score`、`akg_gate` 都在 G2/G3本批没有
## §12 已拍板的决定(第二批据此实现)
- **节奏**先实机验证第一批通过后再动基座
- **传导新鲜度断言****照常落库只打告警 + `mkt_trade_date`不中止扫描**。
理由保覆盖——传导每日只有几十行公告批量入库期 beat 一旦被饿就整天空白
改由桥侧按 `mkt_trade_date` 自行判断是否采信第二批的 `transmission.scan` 已按此改写
`allow_stale` 参数取消