Skip to content

工程约定与事故记录

这一页收不属于任何单个分析步骤、但会静默毁掉结果或交付物的问题。 每条都是踩出来的,不是预防性清单。

成文日期:2026-08-07


一、★★ 编码约定:写进 CSV 的取值一律 ASCII

事故

用户在 Windows 上打开 results/reverse_mr/reverse_mr_results.csvstep3_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 一定花。

★ 但真正该改的不是编码,是列里不该放中文

三条理由:

  1. 编码脆弱 —— 就是上面这个
  2. 下游按字面量匹配枚举值 —— 中文一改动就全线错配。 这与已拍板删除的 tier(下游匹配 "C-MHC区(LD混杂风险)" 这类字符串)是同一类毛病
  3. 补充材料是英文的 —— 中文枚举值不能出现在论文表格里

约定(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)结局里没有该 SNPsnp_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.R28_decode_reselect.R

⚠️ 17_replicate_external.R 等其余脚本尚未接入,待办。

注意与「内部 ID 不上图」是两条不同的规则

  • 本条:CSV 取值不许有中文(编码问题)
  • 那条:图与论文表格不许有内部 ID(如 T1D_gcst),见总览页对照表

两条都成立,互不替代。


二、★★ 部署事故:构建失败却照常上线,整站被掏空

事故经过(2026-08-07)

线上整站文件数从 760 掉到 22405-R9v3重跑实录 目录下 6 个页面全部消失约 10 分钟, 而终端照常打印「部署完成」

根因链(我的操作 + 脚本缺陷各占一半)

  1. 我用 Bash 验证产物时,工作目录停在 dist/mr/05-R9v3重跑实录/ → Windows 锁住该目录
  2. vitepress build 清空 dist递归删除:先删光目录内文件(成功), 再删目录本身(被锁,EPERM) → 构建中止
  3. deploy.ps1npm 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只在整条链路成功后才更新—— 否则一次残缺部署会把基准带低,使下次的熔断失效。

用例实测(含真实事故那一组)

产物数上次基准期望实际
760760通过
224760拦截✅ 拦截
0760拦截
380760通过(恰好一半)
379760拦截
7000通过(无基准)

顺带一个 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.csvstep3_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.csvb_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.csvstep4_survivor step4b_class to_4X to_4X_route
reverse_mr_results.csv / _verdict.csvstep3_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_survivordirectionality_verdict / passed_directionality
step4a_classexposure_platform_replication
step4b_classoutcome_cohort_replication
b_4a se_4a OR_4a p_4a fdr_4ab_alt se_alt OR_alt p_alt fdr_alt
bexp_4a pexp_4a F_4a eaf_4abeta_exposure_alt p_exposure_alt F_alt eaf_exposure_alt
bout_4a seout_4a pout_4abeta_outcome se_outcome p_outcome
to_4X / to_4X_routeflagged_for_artifact_review / artifact_review_route
文件 decode_4a_verdict.csvreplication_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糖肾DNSalem2019_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 照样能读,只是人看着是乱码
  • 部署问题 → 终端照报成功
  • 输入集漂移 → 数字照样算得出来,只是多算了几对

只能靠断言和熔断堵,靠不了自觉。 这也是本项目反复得到的同一个教训。

个人科研与运维文档 · 内容持续修订