17 KiB
第一批 · 联调验证清单(v2,2026-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 已代查的前置事实(你不需要再验证,列出来是为了让你知道风险面有多大)
- 改这 4 个视图的影响面 = 0。
grep -rn "v_factor_" backend/ frontend/src在 astock-kg 里零命中 —— 四视图目前只有桥在消费,替换它们不会波及基座任何功能。 ALTER TABLE transmission_candidates ADD COLUMN安全。全仓没有SELECT *打在这张表上:transmission.list_candidates用显式列清单、INSERT也是显式列清单、factor_coverage_probe只取scan_date, quiet。加一个可空列不会撑坏任何列序假设。- 不删任何数据。§5 的 6 条语句 = 4 个
CREATE OR REPLACE VIEW+ 1 个ALTER TABLE ADD COLUMN IF NOT EXISTS+ 1 个CREATE INDEX IF NOT EXISTS。claims/documents一个字节不动,不需要 replay,不需要重抽。 - 基座 PG 是
postgres:16.9-alpine且端口已发布(ports: "${PG_PORT:-5432}:5432"), 所以桥跨机连得上,§5 的一次性 psql 容器用postgres:16-alpine客户端版本也对得上。 - 基座 beat 时刻表(工作日):05:30 主数据 → 17:30 行情快照 → 18:00 hotspot → 18:15 传导扫描 → 周一 06:00 池刷新 → 07:00 日报。 ⇒ 桥的 daily build 必须在 18:15 之后跑,否则当日传导表是空的。
§3 版本对齐(开发机 ↔ 桥服务器,两处必须同一个 commit)
# 开发机
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 基线快照(改动前跑,用于事后证明"抽取成果没白费")
在基座机器上:
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 是本批新增的命令,旧代码没有)。
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 账号跑这一次:
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 还没做完时的即时方案)
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 带过去)
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 会逐条打 ✅/❌ 并在末尾汇总,缺权限时能精确定位到是哪一条。)
回滚
# 视图退回 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(新代码) | ✅ 可跑,但退回旧口径并打 ⚠️ 告警v_factor_transmission 还是旧版(无 n_sources) |
✅ 目标状态,用 n_sources |
你现在处于左下格(桥已 pull 到 v2、视图还是 v1)——这是合法的过渡态, 不是故障。做完 §5 就进右下格。
判定右下格已达成:
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 · 只读(不写任何库,可反复跑)
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 的生产表写行)
# 注册(幂等,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-23:upside 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这个字符串。查一下真实取值: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 跑完会自动冻结。检查:
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 真的修好了)
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 拓扑登记表(填好)
- §4 基线 SQL 的两段输出(含
mkt_daily按 kind 的天数 —— 它决定传导理论上能重扫到哪天) - §5 的执行输出(6 条语句的结果)
- §6
run.py views全部输出 - §7a
probe --section corr全部输出 ← 最重要 - §7a
probe --section upside的 q 档位表 - §7b
build all的完整日志(尤其 event 的剔除分布、transmission 的两项 ⚠️ 各命中几个环节) - §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参数取消。