主题
工程约定与事故记录
这一页收不属于任何单个分析步骤、但会静默毁掉结果或交付物的问题。 每条都是踩出来的,不是预防性清单。
成文日期:2026-08-07
一、★★ 编码约定:写进 CSV 的取值一律 ASCII
事故
用户在 Windows 上打开 results/reverse_mr/reverse_mr_results.csv, step3_verdict 列显示成:
鍚﹀喅(鍙嶅悜鏄捐憲)诊断(逐字节复现,不是猜)
| 检查 | 结果 |
|---|---|
file 探测 | CSV Unicode text, **UTF-8 text** —— 文件本身正确 |
"否决(反向显著)" 的 UTF-8 字节 | e5 90 a6 e5 86 b3 28 ... |
| 把这串字节当 GBK 解 | 鍚﹀喅(鍙嶅悜鏄捐憲) —— 与用户看到的完全一致 |
→ 文件是 UTF-8,查看器(Excel / 默认 ANSI 的编辑器)按 GBK 解码。
临时看法:Excel 用「数据 → 从文本/CSV 导入」选 65001 UTF-8; 或用 VS Code / Notepad++ 手动切 UTF-8。直接双击 CSV 交给 Excel 一定花。
★ 但真正该改的不是编码,是列里不该放中文
三条理由:
- 编码脆弱 —— 就是上面这个
- 下游按字面量匹配枚举值 —— 中文一改动就全线错配。 这与已拍板删除的
tier(下游匹配"C-MHC区(LD混杂风险)"这类字符串)是同一类毛病 - 补充材料是英文的 —— 中文枚举值不能出现在论文表格里
约定(2026-08-07 立)
凡是会写进 CSV 的取值,一律用 ASCII 枚举。中文只出现在注释与控制台日志里(日志不进交付物)。
已改的枚举:
| 列 | 旧(中文) | 新(ASCII) |
|---|---|---|
step3_verdict | 否决(反向显著) | veto_reverse_significant |
| 通过 | pass | |
| 功效不足(工具<5)-无法评估 | underpowered_few_instruments | |
| 不可评估(无可用工具) | not_assessable | |
status | 无重叠位点 | no_overlapping_variant |
| 剔除 cis 区后无工具 | no_instrument_after_cis_exclusion | |
| 等位全不一致 | all_alleles_mismatched | |
note(4a) | 结局里没有该 SNP | snp_absent_in_outcome |
| 回文且 MAF 接近 0.5,无法定链,剔除 | palindromic_maf_near_half | |
| deCODE 上无达阈 cis 工具 | no_cis_instrument_at_threshold |
落实方式:代码内置断言,不靠自觉
r
assert_ascii <- function(d, what) {
cc <- names(d)[vapply(d, is.character, logical(1))]
bad <- cc[vapply(cc, function(cn)
any(grepl("[^\x01-\x7F]", d[[cn]], useBytes = TRUE), na.rm = TRUE), logical(1))]
if (length(bad)) stop(what, " 含非 ASCII 取值 -- 列: ", paste(bad, collapse = ", "))
}写文件前调用,违反直接 stop() —— 写坏了当场炸,而不是等用户在 Excel 里发现。
已接入:35_reverse_mr.R、28_decode_reselect.R。
⚠️ 17_replicate_external.R 等其余脚本尚未接入,待办。
二、★★ 部署事故:构建失败却照常上线,整站被掏空
事故经过(2026-08-07)
线上整站文件数从 760 掉到 224,05-R9v3重跑实录 目录下 6 个页面全部消失约 10 分钟, 而终端照常打印「部署完成」。
根因链(我的操作 + 脚本缺陷各占一半)
- 我用 Bash 验证产物时,工作目录停在
dist/mr/05-R9v3重跑实录/里 → Windows 锁住该目录 vitepress build清空dist时递归删除:先删光目录内文件(成功), 再删目录本身(被锁,EPERM) → 构建中止- ★
deploy.ps1的npm run build没有检查退出码 → 脚本继续, 打包上传了这个刚被删空的 dist
★ 关键知识点
$ErrorActionPreference = "Stop"对原生命令(npm/tar/scp/git) 的非零退出码不生效。 必须逐个显式判$LASTEXITCODE。
这与 2026-08-03 修掉的 scp 缺陷是同一类——当时注释里就写着 「失败不检查会静默部署旧包并照常打印部署完成」,但只修了 scp,没修 build。
修复(两道闸门 + 6 组用例实测)
powershell
npm run build
if ($LASTEXITCODE -ne 0) { throw "构建失败 (exit $LASTEXITCODE),已中止(未部署,线上仍是上一版)" }
# 第二道:构建"成功"也可能产出残缺 dist
$distFiles = @(Get-ChildItem -Path $dist -Recurse -File).Count
if ($distFiles -lt 50) { throw "构建产物只有 $distFiles 个文件,明显异常,已中止" }
if ($last -gt 0 -and $distFiles -lt [math]::Floor($last / 2)) {
throw "产物 $distFiles 个,比上次 $last 个少一半以上,疑似残缺,已中止"
}基准数量存在 .last-dist-count,只在整条链路成功后才更新—— 否则一次残缺部署会把基准带低,使下次的熔断失效。
用例实测(含真实事故那一组):
| 产物数 | 上次基准 | 期望 | 实际 |
|---|---|---|---|
| 760 | 760 | 通过 | ✅ |
| 224 | 760 | 拦截 | ✅ 拦截 |
| 0 | 760 | 拦截 | ✅ |
| 380 | 760 | 通过(恰好一半) | ✅ |
| 379 | 760 | 拦截 | ✅ |
| 700 | 0 | 通过(无基准) | ✅ |
顺带一个 PowerShell 坑
Select-Object -First N 会提前中止上游管道(发 StopUpstreamCommandsException)。 用它看部署输出时,把部署本身掐断在半路。查看输出一律用 -Last N。
三、⚠️ 输入集漂移:下游没接上游
已知实例
| 脚本 | 应读 | 实读 | 状态 |
|---|---|---|---|
35_reverse_mr.R(第 3 步) | 第 2 步 screen_R9_dm/*_significant.csv | ✅ 同左 | 正确 |
28_decode_reselect.R(4a) | 第 3 步 reverse_mr_results.csv 的 step3_survivor | ✅ 同左 | 正确 |
17_replicate_external.R(4b) | 第 3 步存活集 | ❌ 第 2 步 *_significant.csv(第 109 行) | 待修 |
后果:直接跑 4b 会在 57 对 / 31 蛋白上跑, 把第 3 步已否决的 4 对又捡回来(APOE/BTN3A2/ITGB7/TIGIT × 视网膜)。
★ 这是老毛病的复发:记忆里早记着「下游输入集漂移 3 vs 13 vs 40」, 当时定位过但只修了个别脚本,17 漏了。
容易混淆的一处,别一起改
35_reverse_mr.R 第 407 行也读 *_significant.csv, 但那是为了拼「正反向并排表」取正向效应量(要 β_fwd 做对比), 不是输入集。两处用途不同,改的时候要分清。
待办
- [ ] 修
17_replicate_external.R:109,改读step3_survivor == TRUE,与 4a 一致 —— 具体记录进 4b 的跑前记录 - [ ] 全脚本扫一遍还有没有别的地方直接读
*_significant.csv当输入集
四、★★ 内部流程编号不该出现在结果文件里(2026-08-07 重构)
问题
4a / 4b / 4c / 4X / step3 是我们自己给流程图编的号,不是任何领域标准。 它们当时遍布结果表的列名甚至文件名:
| 文件 | 含内部编号的列 |
|---|---|
decode_4a_verdict.csv | b_4a se_4a OR_4a p_4a fdr_4a bexp_4a pexp_4a F_4a eaf_4a bout_4a seout_4a pout_4a OR_lci_4a OR_uci_4a step4a_class to_4X |
replication_external.csv | step4_survivor step4b_class to_4X to_4X_route |
reverse_mr_results.csv / _verdict.csv | step3_verdict step3_survivor |
| 文件名本身 | decode_4a_verdict.csv · decode_4a_pair_verdict.csv |
这与 T1D_gcst(内部 ID)、中文枚举是同一类问题: 内部标识符混进了可能进补充材料的交付物。审稿人看到 step4b_class 会问「4b 是什么」, 而答案是「我们自己编的号」。
★ 而且比前两类更糟:前两类改展示层即可, 这个编号一改(比如 4b 拆成 4b-1/4b-2),所有列名与下游字面匹配全废。
修复(A + B + C 三件一起做)
A · 判定产物移到独立目录
results/external_replication/
replication_alt_platform.csv 4a 逐适配体判定
replication_alt_platform_pair.csv 4a 对层判定
replication_alt_outcome_R9_dm.csv 4b 判定此前 4a 写 platform_check/、4b 写 screen_R9_dm/(混在第 2 步的 16 个产物里), 第 3 步有自己的 reverse_mr/ —— 三处不一致。 中间产物(decode_cis/、逐工具 MR、质控台账)仍留 platform_check/。
B · 列名去编号
| 旧 | 新 |
|---|---|
step3_verdict / step3_survivor | directionality_verdict / passed_directionality |
step4a_class | exposure_platform_replication |
step4b_class | outcome_cohort_replication |
b_4a se_4a OR_4a p_4a fdr_4a | b_alt se_alt OR_alt p_alt fdr_alt |
bexp_4a pexp_4a F_4a eaf_4a | beta_exposure_alt p_exposure_alt F_alt eaf_exposure_alt |
bout_4a seout_4a pout_4a | beta_outcome se_outcome p_outcome |
to_4X / to_4X_route | flagged_for_artifact_review / artifact_review_route |
文件 decode_4a_verdict.csv | replication_alt_platform.csv |
step4_survivor 改名为 legacy_step4_survivor 而非删除: 它在 v4 下语义已作废(第 4 步不判存活),前缀是给读表人的明确信号,第 7 步删。
C · repl_dataset 改 ASCII
| 旧(中文) | 新(ASCII) |
|---|---|
| MVP糖网(GCST90475689) | MVP_DR_GCST90475689 |
| MVP神经(GCST90475676) | MVP_neuropathy_GCST90475676 |
| Salem2019糖肾DN | Salem2019_JDRF_DNCRI_DN |
| 无外部队列(FinnGen单源) | no_external_cohort_finngen_only |
| 本身即外部参照队列(不适用复制) | is_itself_external_reference |
中文移到 label_zh,只走控制台日志,不进 CSV。
并把 17_replicate_external.R 的 ASCII 断言从只打印告警改为阻断, 与 35 / 28 一致 —— 此前同一条约定三个脚本三种执行力度。
★ 重构验证:主链数字必须一字不差
改名重构最大的风险是悄悄改变结果。做法是改之前先存基线,改完逐行 diff:
| 指标 | 基线 | 重构后 |
|---|---|---|
| 第 3 步 57 对 / 存活 53 / 29 蛋白 / 否决 4 | ✅ | ✅ 一致 |
第 3 步判定分布 pass=49; underpowered=4; veto=4 | ✅ | ✅ 一致 |
4a not_assessable=27; opposite=4; robust=22 | ✅ | ✅ 一致 |
4b not_assessable=19; opposite=1; replicated=7; same_dir_ns=21; ns_opposite=5 | ✅ | ✅ 一致 |
| 4a 六条验收断言 | 全 PASS | 全 PASS |
卫生检查:6 个产物全部「非 ASCII = 0」,5 个「内部编号列 = 无」 (唯一剩下的是有意保留的 legacy_step4_survivor)。
五、这四条的共同点
都不会报错,都会静默产出「看起来正常」的结果。
- 编码问题 → CSV 照样能读,只是人看着是乱码
- 部署问题 → 终端照报成功
- 输入集漂移 → 数字照样算得出来,只是多算了几对
→ 只能靠断言和熔断堵,靠不了自觉。 这也是本项目反复得到的同一个教训。