@dsh-bio/dsh-bio-gem 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -196,7 +196,7 @@ unzip -o diamond.zip diamond.exe -d "$HOME/.dsh/dsh-bio-gem/venv-carveme/Scripts
196
196
  npx -y @deepseek-ai/dsh --profile web --dump-config | grep dsh-bio-gem
197
197
  ```
198
198
 
199
- CarveMe + diamond are required only by `gem_build`; the other 20 tools need nothing but a `cobra`-enabled Python.
199
+ CarveMe + diamond are required only by `gem_build`; the other 22 tools need nothing but a `cobra`-enabled Python.
200
200
 
201
201
  ## 工具(23)
202
202
 
@@ -6,18 +6,15 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
6
6
 
7
7
  硬性原则(沿袭 bio-genie):**用户零手动安装、零自愈、通用化(不针对特定机器特化)、结论可溯源**。
8
8
 
9
- ## 2. 决策记录(为什么这么设计)
10
-
11
- | 日期 | 决策 | 依据 |
12
- |---|---|---|
13
- | 08-28 | 插件名 dsh-bio-gem;资产盘点:消费侧已就绪、补构建侧闭环 | 用户决策 |
14
- | 08-29 | 引擎路线:**任务门槛路由**(不是简单 auto);落地节奏 **M1 CarveMe+补洞 → M2 gapseq WSL 桥 → M3 双引擎交叉** | 独立设计评估 + 实测(CarveMe AB 不生长=补洞是生存线;WSL 桥显著降级交付风险;Docker 非 WSL 替代)|
15
- | 08-29 | MVP 工具集:gem_build / gem_validate(G1G2G3 必做,G4 条件、G5 抽检)/ gem_gapfind(L1L2L3)/ gem_gapfill(L1L2 规则自动)/ gem_report(薄版模型卡);**gem_essentiality 不进首版** | 消费侧 bio_gene_knockout 已存在,避免重复实现 |
16
- | 08-29 | 修正建议:弃 μ 判据用 FBA 通量判据;pyrodigal 注释前端降 backlog;测试矩阵首版收敛 C58+2 公开株 | 输出口径为 objective_value;默认输入是带注释基因组 |
17
-
18
- **采纳原则**:外部分析缺实际环境上下文时,凡冲突处以实测与产品原则为准。
19
-
20
- ## 3. 工具契约(21 工具 ↔ Python 层;21 op + build CLI)
9
+ ## 2. 关键设计取舍
10
+
11
+ | 取舍 | 结论 | 依据 |
12
+ |---|---|---|
13
+ | 引擎路线 | **任务门槛路由**(不是简单 auto);落地顺序 CarveMe+补洞 → gapseq WSL 桥 → 双引擎交叉 | 实测(CarveMe AB 不生长=补洞是生存线;WSL 桥显著降级交付风险;Docker 非 WSL 替代)|
14
+ | MVP 工具集 | gem_build / gem_validate(G1G2G3 必做,G4 条件、G5 抽检)/ gem_gapfind(L1L2L3)/ gem_gapfill(L1L2 规则自动)/ gem_report(薄版模型卡);gem_essentiality 不进首版 | 消费侧 bio_gene_knockout 已存在,避免重复实现 |
15
+ | 判据口径 | 弃 μ 判据用 FBA 通量判据;注释前端(pyrodigal)为可选兜底;回归矩阵覆盖 C58 与公开株 | 输出口径为 objective_value;默认输入是带注释基因组 |
16
+
17
+ ## 3. 工具契约(23 工具 ↔ Python 层;23 op + build CLI)
21
18
 
22
19
  | 工具 | Python 层 | 状态 |
23
20
  |---|---|---|
@@ -26,8 +23,8 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
26
23
  | gem_gapfind | op gapfind(L1-L3 分级 + 跨引擎介质归一化)| ✅ 已完成 |
27
24
  | gem_gapfill | op gapfill(L1/L2 规则 + provenance)| ✅ 已完成 |
28
25
  | gem_phenotype | op phenotype_fix(表型回填迭代)| ✅ 已完成 |
29
- | gem_essentiality | op essential_scan(FVA 预筛 + 手工敲除;预测自动入账本)| ✅ P0 DONE |
30
- | gem_annotate | op annotate(官方优先 + pyrodigal)| ✅ P0 DONE |
26
+ | gem_essentiality | op essential_scan(FVA 预筛 + 手工敲除;预测自动入账本)| ✅ 已完成 |
27
+ | gem_annotate | op annotate(官方优先 + pyrodigal)| ✅ 已完成 |
31
28
  | gem_gapseq | op gapseq(WSL 原子四步,可选项)| ✅ 桥全通 |
32
29
  | gem_l3_fix | op l3_fix(L3 补洞:L3a 连通性 + L3b 白名单/BiGG;证据分级 + 预算闸门 + G6 回滚)| ✅ 已完成(C58 Arabinose 0→0.851)|
33
30
  | gem_report | op model_info(+ ledger_summary 基率摘要)| ✅ DONE |
@@ -41,21 +38,23 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
41
38
  | gem_double_knockout | op double_knockout(双敲 v1:GPR 穷尽先验+全扫 max_pairs 预算;假设声明内置)| ✅ 已完成(Atu3364↔Atu4682 对应命中)|
42
39
  | gem_enrichment | op enrichment(必需基因通路富集:超几何+BH FDR;无注释 annotation_unavailable 兜底)| ✅ 已完成(C58 55 条 FDR 显著)|
43
40
  | gem_targets | op targets(靶点规范导出:11 字段锁定 schema;账本计数闭合;引物设计不做)| ✅ 已完成(258 行三类闭合)|
44
- | gem_precursor_scan | op precursor_scan(阻塞前体分析:基线通量→可生长即返「无阻塞」;不生长则逐前体移除测试定位阻塞点)| ✅ 2026-09-11(实测归因产出)|
41
+ | gem_precursor_scan | op precursor_scan(阻塞前体分析:基线通量→可生长即返「无阻塞」;不生长则逐前体移除测试定位阻塞点)| ✅ 2026-09-11(实测归因产出)|
42
+ | gem_quality | op quality(模型质量报告:blocked/cyclic/GPR 覆盖等可分解审计)| ✅ 已完成 |
43
+ | gem_sample | op sample(ACHR 通量采样;growth_floor 与边界声明)| ✅ 已完成 |
45
44
 
46
45
  > Python 分发器 `gem_ops.py` 共 **23 个 op**(annotate/benchmark/biomass_apply/biomass_inspect/double_knockout/enrichment/essential_scan/fluxscan/gapfill/gapfind/gapseq/l3_fix/ledger/media_resolve/model_info/phenotype_fix/precursor_scan/quality/sample/secretion/sensitivity/targets/validate);`gem_build` 不经分发器,由 `build.py` CLI 直接调用(长任务,jobs.js 拉起)。
47
46
  >
48
- > **工具数(23)与 op 数(23)的关系**:不等且不是简单的 +1 —— `gem_biomass` 一个工具映射 `biomass_inspect` / `biomass_apply` 两个 op(工具 −1),而 `gem_build` 走 CLI 不占 op(工具 +1),两项相抵故数值相同。核验口径:`len(gem_ops.OPS)` 与 `grep -c 'ctx.tools.register(' src/tools.js`。
47
+ > **工具数(23)与 op 数(23)**:数值恰好相同但并非恒等——`gem_biomass` 一个工具映射两个 op(`biomass_inspect` / `biomass_apply`),而 `gem_build` 走 CLI 不占 op,两项相抵。核验口径:`len(gem_ops.OPS)` 与 `grep -c 'ctx.tools.register(' src/tools.js`。
49
48
 
50
49
  > **precursor_scan 的判据取舍(勿回退)**:初版曾用「全开交换下逐前体 demand 能否净生产」的**绝对可达性**判据,在教科书模型 e_coli_core 上把 atp_c/accoa_c/nad_c/nadph_c 误报为「结构缺失」(辅因子有循环补给路径,稳态下不净生产 ≠ 网络不能供给),故否决。现行判据为**相对判断**:先测基线通量,可生长即直接返回「无阻塞」;不生长才逐前体做移除测试,由「移除后是否恢复通量」直接定义阻塞点。验证锚:toy 单点阻塞模型(精确命中)、e_coli_core(growable,零误报)、iNX1344_v3(infeasible_or_constrained,与 agent 手工探索结论一致)。
51
50
 
52
- > 其余工具层约定:附模型卡统一写入 `python/model_card.py`(lineage/verified_phenotypes/essential_genes/robustness v3)与往返保真自检 `python/roundtrip_check.py`;预测账本 `python/ledger.py`(一个模型一个账本:`~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl`,按模型 basename 分,显式 ledger_path 可覆盖;无参查询=聚合全局视图;旧全局 predictions.jsonl 已迁移为 legacy)。**生长/通量数值口径**:所有产出生长/通量数值的工具输出均带 `units: mmol/gDW/h` 与单点 FBA 声明;条件间通量对比一律走 gem_fluxscan 区间分离判定(overlap=伪影禁止引用)。
51
+ > 其余工具层约定:附模型卡统一写入 `python/model_card.py`(lineage/verified_phenotypes/essential_genes/robustness v3)与往返保真自检 `python/roundtrip_check.py`;预测账本 `python/ledger.py`(一个模型一个账本:`~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl`,按模型 basename 分,显式 ledger_path 可覆盖;无参查询=聚合全局视图;旧全局 predictions.jsonl 已迁移为 legacy)。**生长/通量数值口径**:所有产出生长/通量数值的工具输出均带 `units` 声明——归一化 biomass 反应(产物系数=1)的生长值为 `1/h`(比生长速率 μ);一般反应通量为 `mmol/gDW/h`。另带单点 FBA 声明;条件间通量对比一律走 gem_fluxscan 区间分离判定(overlap=伪影禁止引用)。
53
52
 
54
- ## 4. 引擎路线(M1→M2→M3)
53
+ ## 4. 引擎路线(三个阶段)
55
54
 
56
- - **M1(已完成 08-29,C58 实测)**:CarveMe 纯 Windows(独立 venv ~/.dsh/dsh-bio-gem/venv-carveme + diamond PATH 注入)。输入(protein.faa)→ carve -g M9(54s)→ 精确 M9 介质(media_db 提取)G3 PASS(C58 测 0.782)→ 用户目标介质 resolve(跨引擎自然名)→ G3 FAIL 时 L1/L2 规则补洞 → 模型卡。**CarveMe 模型实测:M9 可生长;AB 目标介质 FAIL 且为 L3 内部路径(L1/L2 规则不可修)——诚实报告为已知边界(研究设计既有结论:CarveMe M9 补洞局限)。**
57
- - **M2(2026-08-29 起)**:gapseq WSL2 桥(`python/gapseq_wsl.py`)。能力探测四件套(wsl/发行版/gapseq 版本/序列库注册 up-to-date——防假已装 UniProt 灾难);新版 wsl.exe 输出 UTF-8(旧版 UTF-16LE,双解码兼容);doall 哨兵文件轮询(30-60min,每 2min 进度 + 日志尾部旁观);产物拷回 → 目标介质验证(AB 自然名)→ L1/L2 补洞闭环 → 模型卡。gem_build `engine` 参数(carveme|gapseq)+ 60min 超时。分发时采用**私有发行版**(wsl --import 自包含 bundle:R+gapseq+序列库 v1.5+哈希校验,版本钉死)。任务分步化(draft/build/transport/fill/adjust 每步落盘 → 断点续跑)待做。
58
- - **M3**:双引擎交叉验证,产出**分歧清单**(两引擎不一致反应/基因 = 低置信区,需文献/实验校验)而非平均;可选集成 gemsembler(先验证成熟度);所有比对按**反应级等价类**而非基因级(引擎 GPR 粒度不同)。
55
+ - **阶段一 · CarveMe 纯 Windows(已完成,C58 实测)**:(独立 venv ~/.dsh/dsh-bio-gem/venv-carveme + diamond PATH 注入)。输入(protein.faa)→ carve -g M9(54s)→ 精确 M9 介质(media_db 提取)G3 PASS(C58 测 0.782)→ 用户目标介质 resolve(跨引擎自然名)→ G3 FAIL 时 L1/L2 规则补洞 → 模型卡。**CarveMe 模型实测:M9 可生长;AB 目标介质 FAIL 且为 L3 内部路径(L1/L2 规则不可修)——诚实报告为已知边界(研究设计既有结论:CarveMe M9 补洞局限)。**
56
+ - **阶段二 · gapseq WSL2 桥(2026-08-29 起)**:(`python/gapseq_wsl.py`)。能力探测四件套(wsl/发行版/gapseq 版本/序列库注册 up-to-date——防假已装 UniProt 灾难);新版 wsl.exe 输出 UTF-8(旧版 UTF-16LE,双解码兼容);doall 哨兵文件轮询(30-60min,每 2min 进度 + 日志尾部旁观);产物拷回 → 目标介质验证(AB 自然名)→ L1/L2 补洞闭环 → 模型卡。gem_build `engine` 参数(carveme|gapseq)+ 60min 超时。分发时采用**私有发行版**(wsl --import 自包含 bundle:R+gapseq+序列库 v1.5+哈希校验,版本钉死)。任务分步化(draft/build/transport/fill/adjust 每步落盘 → 断点续跑)待做。
57
+ - **阶段三 · 双引擎交叉验证**:产出**分歧清单**(两引擎不一致反应/基因 = 低置信区,需文献/实验校验)而非平均;可选集成 gemsembler(先验证成熟度);所有比对按**反应级等价类**而非基因级(引擎 GPR 粒度不同)。
59
58
 
60
59
  ## 5. 验证关卡规格(产品化 + G0)
61
60
 
@@ -70,8 +69,8 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
70
69
  | G6 | ATP 泄漏检测(全关交换后 ATP demand 应≈0)| ✅ | leak ≤0.01 判 PASS;ATP 解析走 id→name→formula 三级回退(跨 ID 体系)|
71
70
 
72
71
  **G0 的由来(2026-09-10 实测)**:MetaCyc 风格 id 的公开模型(iNX1344_v3)上,
73
- `gem_gapfind` 报 5 个 L3「内部通路缺口」,实为 biomass 前体未映射所致——agent 为逐个
74
- 证伪手写 cobra 代码 18 次。现 `gem_validate` 在 G1 之前输出 `g0`,`gem_gapfind` 返回
72
+ `gem_gapfind` 报 5 个 L3「内部通路缺口」,实为 biomass 前体未映射所致——逐个
73
+ 证伪需手写 cobra 代码,成本高。现 `gem_validate` 在 G1 之前输出 `g0`,`gem_gapfind` 返回
75
74
  `coherence_warning` + `interpretation_guard`,把该结论前置给 agent。
76
75
 
77
76
  > ⚠️ **G0 判据的取舍(勿回退)**:曾试过「biomass 元素配平」与「前体可达性(demand 逐前体
@@ -87,13 +86,13 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
87
86
 
88
87
  - **L1 缺交换**:培养基成分表 vs 模型 EX_ 列表的集合差 → 修复=补 EX_ 反应(完善环境定义,最安全)
89
88
  - **L2 缺转运**:e0↔c0 区室连通性(代谢物在胞外存在但无转运反应入胞)→ 修复=补转运(GPR 可空,标注未表征)
90
- - **L3 内部路径**:底物有交换+转运却无法达中心代谢 → 需文献反应(M1 报告清单,不自动补)
89
+ - **L3 内部路径**:底物有交换+转运却无法达中心代谢 → 需文献反应(补洞报告清单,不自动补)
91
90
 
92
91
  已知规律(P1 实测):多数"不能利用某碳源"缺口是 L1/L2 而非 L3。
93
92
  **2026-09-11 补充**:L3 清单须与 G0 一起解读——模型数据质量有问题时 L3 多为症状
94
93
  (`find_gaps` 返回值已内置 `coherence_warning` 与 `interpretation_guard`)。
95
94
 
96
- **防过补四闸门**:分级规则优先于 MILP(M1 不做 MILP);新增反应数封顶(max_add=20);逐条 provenance 打标(来源/原因/是否借自模板);修复后强制重验 G3 + 生长值合理性上限告警(>1.0 时 WARN 过补嫌疑)。
95
+ **防过补四闸门**:分级规则优先于 MILP;新增反应数封顶(max_add=20);逐条 provenance 打标(来源/原因/是否借自模板);修复后强制重验 G3 + 生长值合理性上限告警(>1.0 时 WARN 过补嫌疑)。
97
96
 
98
97
  ## 7. 模型卡(sidecar JSON,与 SBML 同目录同名 .card.json)
99
98
 
@@ -105,9 +104,9 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
105
104
  ```
106
105
  写盘用 cobra.io.write_sbml_model(cobra 0.32.1 无 Model.save_model——坑位记档)。
107
106
 
108
- ## 8. 后台任务(M1 基建,约 30% 工程量)
107
+ ## 8. 后台任务(基建)
109
108
 
110
- job 化 + 进度事件(粒度 ≤5s)+ 分步 checkpoint(每步落盘,可断点续跑)+ 结果可重入。引擎无关,M2 gapseq 直接复用。
109
+ job 化 + 进度事件(粒度 ≤5s)+ 分步 checkpoint(每步落盘,可断点续跑)+ 结果可重入。引擎无关,gapseq 引擎直接复用。
111
110
 
112
111
  ## 9. 与 bio-genie 衔接
113
112
 
@@ -122,7 +121,7 @@ job 化 + 进度事件(粒度 ≤5s)+ 分步 checkpoint(每步落盘,可
122
121
  - 静态 Cordis `inject` 只声明 `tools`、`skills`;`webServer` 通过 `ctx.inject(['webServer'], cb)` 动态等待。无 webServer 时仍照常注册 23 个 `gem_*` 工具和 gem-expert skill;服务出现后才注册两条路由,并在约 8 秒后后台预热一次 status 缓存。gapseq 先做 `wsl.exe -l -q` 发行版预检,再用固定只读版本命令;子进程 stdin 使用 pipe 并立即关闭,避免 WSL 因 `stdin=ignore` 慢启动。gem 不注册浏览器设置入口,一级入口和五态 UI 由 BioGenie 唯一拥有。
123
122
  - 本批严格只读:不实现 job API、安装/删除、配置 schema、自动修复或跨插件命令执行。
124
123
 
125
- ## 10. 验收(M1 最小可用判定线)
124
+ ## 10. 验收(最小可用判定线)
126
125
 
127
126
  零手动干预下:**基因组进 → 四个消费工具(FBA/必需性/包络线/面板)不经修改即可用的 SBML 出**,且模型在声明培养基上生长为正;C58 端到端演示通过(build→面板可见→FBA 可跑→必需性可跑);模型卡齐全(引擎/版本/补洞记录/验证结果,同输入重跑一致);5-6 Mb 基因组 p95 ≤ 20 min。
128
127
 
@@ -133,7 +132,7 @@ job 化 + 进度事件(粒度 ≤5s)+ 分步 checkpoint(每步落盘,可
133
132
  | 项目 | C58 | iNX1344_v4 |
134
133
  |---|---|---|
135
134
  | model_info(读模+摘要) | 6.6s | 3.7s |
136
- | validate G1-G6 | 7.9s(G3 PASS 0.519981) | 3.7s(G3 WARN,介质层不兼容见 M5) |
135
+ | validate G1-G6 | 7.9s(G3 PASS 0.519981) | 3.7s(G3 WARN,介质层不兼容) |
137
136
  | essential_scan 全量(FVA 预筛+手工敲除) | ~50s(FVA 32.3s + 敲除 16.8s,818 候选) | ~30s(FVA 11.9s + 敲除 16.8s,1066 候选) |
138
137
  | fluxscan 1 条件(读模+FBA+FVA+pFBA) | ~31s(FVA 24-42s 为主) | ~14s(FVA ~12s) |
139
138
  | fluxscan 2 条件 1 对 | 63-72s | 28.5s |
@@ -0,0 +1,30 @@
1
+ # @dsh-bio/dsh-bio-gem v0.1.18
2
+
3
+ > **主题**:公开信息审计复核修复批次(含小型行为修复)。
4
+ > 基因组尺度代谢模型(GEM)构建插件:基因组输入 → 构建 → 验证 → 补洞 → 出报告(标准 SBML + 模型卡)。
5
+
6
+ ## 修复与改进
7
+
8
+ **行为修复**
9
+ - BiGG 模型下载的代理回退:显式将 `ALL_PROXY` 映射到 http/https(此前仅声明支持、实际未生效);直连失败时按系统/环境代理重试。
10
+ - 白名单构建入口守卫:`GEM_GAPSEQ_DB` 未配置或无效时在任何文件操作前给出可操作错误(生产调用链同样受控,不再隐式使用相对目录)。
11
+ - 回归脚本解释器默认值沿用 `DSH_HOME`(与生产解析规则一致;显式 `GEM_PYTHON` 仍最高优先)。
12
+
13
+ **文档与一致性**
14
+ - 架构文档:去内部阶段代号与决策史;工具契约表补 `gem_quality` / `gem_sample`(23 工具 / 23 op);生长/通量单位口径统一(归一化 biomass 为 1/h;一般通量为 mmol/gDW/h)。
15
+ - 计数一致性:README 英文段 22 工具(除 build 外);中文标题 23。
16
+ - 过程叙事中性化:代码注释与工具返回文案中的开发阶段标签、调用经历记录全部清除(保留判据依据与错误条件)。
17
+
18
+ ## 安装
19
+
20
+ ```sh
21
+ npx -y @deepseek-ai/dsh plugin --profile web add @dsh-bio/dsh-bio-gem
22
+ ```
23
+
24
+ 桌面端(0.2.0+):用桌面端自带 CLI(`--profile desktop`)或应用内「插件」页安装。
25
+ 分析用 Python(cobra)优先复用 dsh-bio-genie 自举环境;未装 genie 时见 README 第 2 节。
26
+
27
+ ## 已知边界(诚实清单)
28
+
29
+ - `gem_build` 的 gapseq 引擎为实验性(WSL2 环境绑定,30–60 分钟/模型);默认 carveme 引擎已可出可验证模型。
30
+ - 其余边界见 v0.1.12 清单。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-bio/dsh-bio-gem",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "基因组尺度代谢模型(GEM)构建插件:输入细菌全基因组(蛋白FASTA,支持多质粒/多染色体),自动构建+验证+补洞+出报告(SBML + 模型卡),供 dsh-bio-genie 消费工具加载使用 | Genome-scale metabolic model builder for dsh",
5
5
  "repository": {
6
6
  "type": "git",
@@ -75,7 +75,7 @@ def nucleotide_to_protein(fna_path, out_faa=None, prefer_existing=True):
75
75
  base = os.path.splitext(os.path.basename(fna_path))[0]
76
76
  if out_faa is None:
77
77
  out_faa = os.path.join(d, base + ".gem_annot.faa")
78
- # P2-8 修复(2026-08-31 LBA9402 会话首调真实事故):输出目录不存在直接 FileNotFoundError
78
+ # 输出目录不存在时自动创建(避免 FileNotFoundError)
79
79
  out_dir = os.path.dirname(os.path.abspath(out_faa)) or "."
80
80
  os.makedirs(out_dir, exist_ok=True)
81
81
 
@@ -1,4 +1,4 @@
1
- # benchmark.py — 阶段B-B1 gem_benchmark 通用基准对比(与物种无关)
1
+ # benchmark.py — gem_benchmark 通用基准对比(与物种无关)
2
2
  # 六件通用能力:①介质解析两级策略复用(gapfind.build_ex_index 回退 boundary)②biomass 可行性探针
3
3
  # ③六关 G1-G6 并列 ④必需性对比+差异归因(退化侧只报结构不做垃圾对比)⑤账本 comparison_refs 回填
4
4
  # (update 语义幂等可重入)⑥可复现性评估。
@@ -400,6 +400,10 @@ def fetch_bigg_model(model_id, dest_dir=None):
400
400
  if os.path.exists(dest) and os.path.getsize(dest) > 100000:
401
401
  return dest, "cached(已有本地副本)"
402
402
  env_proxies = {k: v for k, v in urllib.request.getproxies().items() if v}
403
+ # ALL_PROXY(getproxies 的 "all" 键)对 http/https 请求不会自动生效,显式映射
404
+ if env_proxies.get("all"):
405
+ for _sch in ("http", "https"):
406
+ env_proxies.setdefault(_sch, env_proxies["all"])
403
407
  fallbacks = [("direct", None)] + ([("env-proxy", env_proxies)] if env_proxies else [])
404
408
  last_err = None
405
409
  for url in urls:
@@ -423,7 +427,7 @@ def fetch_bigg_model(model_id, dest_dir=None):
423
427
 
424
428
 
425
429
  def medium_adaptation_hints(model_path, medium, max_hints=5, progress=None):
426
- """阶段C-C5:B3 molybdate 式诊断自动化(通用函数)。介质下 wt<=EPS 时,在严格介质基础上
430
+ """B3 molybdate 式诊断自动化(通用函数)。介质下 wt<=EPS 时,在严格介质基础上
427
431
  逐个补充模型其它交换反应(单变量 lb=-10 试验),报告能恢复生长的成分 -> '介质疑似缺 X'。"""
428
432
  m = silent_read_sbml(model_path)
429
433
  resolved, unresolved, preset = setup_model_medium(m, medium)
@@ -293,7 +293,7 @@ def apply_biomass(model_path, biomass_profile, medium=None, phenotype_table=None
293
293
  "out": out, "units": GROWTH_UNITS,
294
294
  "before_after": {
295
295
  "growth": {"before": growth_b, "after": growth_a, "units": GROWTH_UNITS,
296
- # 阶段A-M4 口径声明(只增)
296
+ # 口径声明(只增)
297
297
  "point_value_note": "单点 FBA 值,非解空间硬结论;条件对比请用 gem_fluxscan 区间分离判定"},
298
298
  "phenotype": {"before": {"matched": ph_b.get("matched"), "total": ph_b.get("total")},
299
299
  "after": {"matched": ph_a.get("matched"), "total": ph_a.get("total")}},
@@ -14,6 +14,10 @@ DIAMOND = os.environ.get(
14
14
 
15
15
  def build_rxn_fasta(out_fa, out_map=None, min_size=100):
16
16
  """聚合 rxn/ 非空文件(文件名=反应 ID)→ 序列 fasta + mapping。返回 (seqs, files_used)。"""
17
+ if not SEQDB or not os.path.isdir(os.path.join(SEQDB, "rxn")):
18
+ raise RuntimeError(
19
+ "GEM_GAPSEQ_DB 未配置或无效:构建白名单需要 gapseq 序列库目录"
20
+ "(<db>/Bacteria,内含 rxn/ 子目录)")
17
21
  rxn_dir = os.path.join(SEQDB, "rxn")
18
22
  n_seq = 0
19
23
  n_file = 0
@@ -120,8 +124,7 @@ if __name__ == "__main__":
120
124
  import json
121
125
  fa = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
122
126
  os.path.expanduser("~"), ".dsh", "dsh-bio-gem", "whitelist", "rxn_all.fa")
123
- if not SEQDB or not os.path.isdir(os.path.join(SEQDB, "rxn")):
124
- sys.exit("GEM_GAPSEQ_DB 未设置或无效:请指向 gapseq 序列库目录(<db>/Bacteria,内含 rxn/ 子目录)")
127
+ # SEQDB 有效性由 build_rxn_fasta 统一守卫(生产调用链同样受控)
125
128
  out_dir = os.path.dirname(fa) or "."
126
129
  os.makedirs(out_dir, exist_ok=True)
127
130
  map_p = os.path.join(out_dir, "rxn_map.tsv")
@@ -3,9 +3,9 @@
3
3
  # 目的:在 G3/G4/G5 与 gapfind 之前,先判断**模型自身数据质量是否允许下结论**,
4
4
  # 避免把「未映射代谢物」这类数据问题,误报成「通路缺口 / 必需基因异常」。
5
5
  #
6
- # 实测来源(2026-09-10,iNX1344_v3 —— MetaCyc 风格 id 的公开模型):
6
+ # 触发场景(iNX1344_v3 —— MetaCyc 风格 id 的公开模型):
7
7
  # - gem_gapfind 报 5 个 L3「内部通路缺口」,根因实为 biomass 前体未映射;
8
- # - agent 为证伪这些假阳性,手写 cobra 代码 18 次(占该轮调用的一半)。
8
+ # - 逐个手工证伪成本高(需手写 cobra 代码),故把该结论前置给 agent。
9
9
  #
10
10
  # ⚠️⚠️ 判据设计原则:**零误报优先**。以下两类判据在设计中被实测否决,切勿加回:
11
11
  #
@@ -16,7 +16,7 @@
16
16
  # 2. 「前体可达性(demand 逐前体 FBA)」——初版实现同样在 e_coli_core 上把
17
17
  # atp_c / accoa_c / nad_c / nadph_c 误报为「既不能合成也不能摄取」。全开交换下
18
18
  # 的 demand 语义与胞内辅因子/能量货币的循环补给路径纠缠,判据未成熟。
19
- # 正确方法(agent 曾手工探索过)待重新设计后引入。
19
+ # 正确方法待重新设计后引入。
20
20
  #
21
21
  # 保留的判据都经过「问题模型报出真问题 + 标准模型零误报」双向验证:
22
22
  # - id 体系识别(信息性,决定下游名称映射口径)
@@ -1,11 +1,11 @@
1
- # double_knockout.py — 阶段C-C2 双敲 v1(合成致死预测,L2 非平凡)
1
+ # double_knockout.py — 双敲 v1(合成致死预测,L2 非平凡)
2
2
  # 候选池(预算可控):① GPR 结构先验——纯 or 型且恰 2 基因的反应 = 穷尽型同工酶对(廉价必做);
3
3
  # ② FVA 预筛活性反应关联基因中"共享反应"的基因对(复用 essential_scan.prescreen_candidates +
4
4
  # scan_essentiality;全扫受 max_pairs 预算上限,默认 5000,超限截断+报告)。
5
5
  # 判定:单敲双活(>EPS)且双敲死(<=EPS)→ 合成致死对。单敲生长值按对惰性计算并缓存。
6
6
  # 假设声明(方案文件要求,内置于输出与 description):细菌双敲验证率无大规模实验数据支撑,
7
7
  # 本结果=假设生成,供实验设计参考非结论。
8
- # 退化护栏(阶段 A/B 教训):wt<=EPS(介质下不生长)→ 不扫描不登记账本,degenerate:true + 介质适配提示。
8
+ # 退化护栏(实测教训):wt<=EPS(介质下不生长)→ 不扫描不登记账本,degenerate:true + 介质适配提示。
9
9
  import os
10
10
  import re
11
11
  import sys
@@ -70,7 +70,7 @@ def double_knockout(model_path, medium=None, max_pairs=5000, export_csv=None,
70
70
  if out["degenerate"]:
71
71
  out["degenerate_note"] = (f"wt_growth={wt}<=EPS:被测模型在指定介质下不生长,双敲判定无意义,"
72
72
  "未扫描、未登记账本。提示:内置介质预设为根瘤菌科(C58)调校,"
73
- "非根瘤菌模型需先做介质适配(阶段B-B3 molybdate 教训)。")
73
+ "非根瘤菌模型需先做介质适配(molybdate 介质适配教训)。")
74
74
  try:
75
75
  from benchmark import medium_adaptation_hints
76
76
  out["medium_adaptation_hints"] = medium_adaptation_hints(model_path, medium)
@@ -1,4 +1,4 @@
1
- # enrichment.py — 阶段C-C3 必需基因通路富集(超几何 + BH FDR;菌种通用)
1
+ # enrichment.py — 必需基因通路富集(超几何 + BH FDR;菌种通用)
2
2
  # 通路注释源(C3 开工探索结论):gapseq 模型在 SBML groups 里写入 MetaCyc 通路分组
3
3
  # (C58 实测 1051 个 PWY groups,9942 个反应成员关系);BiGG 静态下载模型无 groups
4
4
  # (iML1515 实测 0)——无 groups 时按契约返回 annotation_unavailable 兜底(不伪造通路)。
@@ -2,7 +2,7 @@
2
2
  # 流程: 介质设置 -> FVA(全范围) 预筛可通量基因(死基因免敲)-> 手工单基因敲除
3
3
  # -> 必需基因列表 + 模型卡章节数据(证据分级配色字段)
4
4
  # Windows 纪律: FVA processes=1(无 fork);手工敲除循环;GLPK 快速线性
5
- # 阶段A-M2: 介质 setup 与扫描核心拆为 setup_model_medium/scan_essentiality 供 sensitivity 复用
5
+ # 介质 setup 与扫描核心拆为 setup_model_medium/scan_essentiality 供 sensitivity 复用
6
6
  # (行为不变,C58 AB 必需 155 锚点必须原样复现)
7
7
  import os
8
8
  import sys
@@ -19,7 +19,7 @@ FVA_EPS = 1e-9
19
19
 
20
20
  def setup_model_medium(m, medium=None):
21
21
  """介质 setup(对齐 validate G3):expand_medium -> 全交换清零 -> resolve_medium 设 bounds。
22
- 阶段A-M2 抽出:essential_scan 与 sensitivity 共用同一介质口径。返回 (resolved, unresolved, preset)。"""
22
+ 抽出(供复用):essential_scan 与 sensitivity 共用同一介质口径。返回 (resolved, unresolved, preset)。"""
23
23
  med, preset = expand_medium(medium) if medium else ({}, None)
24
24
  resolved, unresolved = resolve_medium(m, med) if med else ({}, [])
25
25
  for r in m.reactions:
@@ -32,7 +32,7 @@ def setup_model_medium(m, medium=None):
32
32
 
33
33
 
34
34
  def prescreen_candidates(m, gene_subset=None):
35
- """FVA(fraction=0) 预筛(阶段C-C2 抽出复用):返回 (active_rxns, cand_genes, n_tested, fva_s)。
35
+ """FVA(fraction=0) 预筛(抽出复用):返回 (active_rxns, cand_genes, n_tested, fva_s)。
36
36
  cand_genes = 关联至少一个可通量反应的基因(死基因免敲);n_tested = FVA 检查的反应总数。"""
37
37
  t0 = time.time()
38
38
  fva = flux_variability_analysis(m, fraction_of_optimum=0.0, processes=1)
@@ -50,7 +50,7 @@ def prescreen_candidates(m, gene_subset=None):
50
50
 
51
51
 
52
52
  def scan_essentiality(m, gene_subset=None, progress=None, return_candidates=False):
53
- """必需性扫描核心(阶段A-M2 抽出复用)。输入:介质 bounds 已设好的模型。
53
+ """必需性扫描核心(抽出复用)。输入:介质 bounds 已设好的模型。
54
54
  FVA(fraction=0) 预筛 -> 可通量反应关联基因 -> 手工敲除(<EPS 判必需)。
55
55
  返回 {wt_growth, total_genes, fva_tested_reactions, active_reactions, tested_genes,
56
56
  essential_count, essential_genes, organs, fva_seconds, knock_seconds}。"""
@@ -88,7 +88,7 @@ def scan_essentiality(m, gene_subset=None, progress=None, return_candidates=Fals
88
88
  "fva_seconds": fva_s,
89
89
  "knock_seconds": knock_s,
90
90
  }
91
- if return_candidates: # 阶段C-C2:double_knockout 复用(避免二次 FVA 预筛)
91
+ if return_candidates: # double_knockout 复用(避免二次 FVA 预筛)
92
92
  out["candidate_genes"] = sorted(cand_genes)
93
93
  out["active_rxn_ids"] = sorted(active_rxns)
94
94
  return out
@@ -150,7 +150,7 @@ def essential_scan(model_path, medium=None, gene_subset=None, progress=None, led
150
150
  "medium_preset": preset,
151
151
  "medium_unresolved": unresolved,
152
152
  "note": "必需判定=A 培养基下敲除生长<1e-6;evidence 分级按基因支撑反应是否含 EVIDENCE_math",
153
- # 阶段A-M4 口径声明(只增):wt_growth 为单点 FBA 值
153
+ # 口径声明(只增):wt_growth 为单点 FBA 值
154
154
  "units": "1/h",
155
155
  "point_value_note": "单点 FBA 值,非解空间硬结论;条件对比请用 gem_fluxscan 区间分离判定",
156
156
  })
@@ -161,7 +161,7 @@ def essential_scan(model_path, medium=None, gene_subset=None, progress=None, led
161
161
  set_essential_genes(model_path, result, model=m)
162
162
  except Exception:
163
163
  pass
164
- # 阶段A遗留修正(退化护栏,只增分支):wt<=EPS 时必需性判定恒真(v=0 使全部候选判"必需",
164
+ # 退化护栏(只增分支):wt<=EPS 时必需性判定恒真(v=0 使全部候选判"必需",
165
165
  # M5 实测曾把 iNX1344_v4 的 1066 条此类退化预测写入真实账本)——跳过 ledger 自动登记。
166
166
  # C58 正常路径(wt>EPS)不进此分支,登记行为不变。
167
167
  if result["wt_growth"] <= EPS:
@@ -171,7 +171,7 @@ def essential_scan(model_path, medium=None, gene_subset=None, progress=None, led
171
171
  "skipped_degraded": len(result["essential_genes"]),
172
172
  "degraded": True, "warn": warn}
173
173
  return result
174
- # 阶段A-M3: prediction ledger 自动登记(每必需基因一条;幂等去重;写入失败仅 WARN 不阻塞)
174
+ # prediction ledger 自动登记(每必需基因一条;幂等去重;写入失败仅 WARN 不阻塞)
175
175
  try:
176
176
  import ledger as _ledger
177
177
  from model_card import load_card as _load_card
@@ -1,4 +1,4 @@
1
- # fluxscan.py — M1 通量区间制(阶段 A 可信度内核第一件)
1
+ # fluxscan.py — 通量区间制(FVA 区间 + pFBA 点值 + 条件对区间分离判定)
2
2
  # 语义(已锁定):每反应输出 fva_min/fva_max/pfba;条件对比消费区间分离判定;
3
3
  # overlap = 点值差异是求解器伪影,禁止引用。
4
4
  # 计算口径:每 condition 独立 silent_read_sbml 重读模型(勿深拷贝);介质 setup 对齐 validate G3
package/python/gapfind.py CHANGED
@@ -133,7 +133,7 @@ def build_met_index(m, compartment="c0"):
133
133
 
134
134
  def build_ex_index(m):
135
135
  """EX 交换名索引(去 -e0 后缀小写)-> EX 反应 id。
136
- 两级策略(阶段B):
136
+ 两级策略:
137
137
  ① EX_/DM_/SK_ 前缀优先——现有行为逐字保留(仅 EX_ 前缀反应入索引);
138
138
  ② 仅当模型 EX_ 索引整体为空(全模型无 EX_ 前缀交换反应)时,回退用 boundary
139
139
  单代谢物反应(恰好 1 个代谢物且 rxn.boundary=True)作为交换候选,返回
@@ -150,7 +150,7 @@ def build_ex_index(m):
150
150
  idx.setdefault(norm(nm), r.id)
151
151
  if idx:
152
152
  return idx
153
- # 阶段B 两级策略②:EX_ 层整体缺失 -> boundary 单代谢物反应回退
153
+ # 两级策略②:EX_ 层整体缺失 -> boundary 单代谢物反应回退
154
154
  bidx = BoundaryExIndex()
155
155
  for r in m.reactions:
156
156
  if r.boundary and len(r.metabolites) == 1:
@@ -170,7 +170,7 @@ def match_ex(sub, ex_idx, allow_substring=None):
170
170
  - 短 key(<=4 且不含 '+'):只允许前缀匹配(n.startswith(key))
171
171
  - 含 '+' 的 key(金属离子):允许前缀或后缀(iron(fe3+)→ironfe3+ endswith fe3+)
172
172
  - 长 key(>=5):允许子串
173
- 阶段B:受控子串层可对单个索引禁用——boundary 回退索引(BoundaryExIndex)跨命名空间
173
+ 受控子串层可对单个索引禁用——boundary 回退索引(BoundaryExIndex)跨命名空间
174
174
  短名易误配(实测 'd-glucose' ⊂ 'd-glucose1-phosphate' 会把 G1P 交换错当葡萄糖),
175
175
  故其 allow_substring=False;普通 dict 索引默认 True,既有行为逐字保留。"""
176
176
  key = norm(sub)
@@ -209,7 +209,7 @@ def match_ex(sub, ex_idx, allow_substring=None):
209
209
 
210
210
 
211
211
  class BoundaryExIndex(dict):
212
- """两级策略②的 boundary 回退索引(阶段B)。
212
+ """两级策略②的 boundary 回退索引。
213
213
  仅当全模型无 EX_ 前缀交换反应时由 build_ex_index 产出;禁用受控子串回退层
214
214
  (跨命名空间误配实证:d-glucose ⊂ d-glucose1-phosphate)。"""
215
215
  allow_substring = False
package/python/gem_ops.py CHANGED
@@ -69,7 +69,7 @@ def op_model_info(args):
69
69
  repl["_".join(parts[:2])] += 1
70
70
  else:
71
71
  repl["other"] += 1
72
- # 阶段A-M3: prediction ledger 基率摘要(文件不存在 -> {total: 0};ledger_path 可覆盖默认账本)
72
+ # prediction ledger 基率摘要(文件不存在 -> {total: 0};ledger_path 可覆盖默认账本)
73
73
  # P1-3:传 model=path 让摘要给 by_model 分布 + own_model_entries(防「本模型账本 N 条」误报)
74
74
  ledger_summary, ledger_context = None, None
75
75
  try:
@@ -234,7 +234,7 @@ def op_media_resolve(args):
234
234
  "medium_preset": preset,
235
235
  "unresolved": unresolved,
236
236
  "model": model,
237
- # 阶段B-B1 附加(只增):两级策略②启用标注 + 规范展示名
237
+ # 附加(只增):两级策略②启用标注 + 规范展示名
238
238
  "boundary_style": boundary_style,
239
239
  }
240
240
  if boundary_style:
@@ -263,7 +263,7 @@ def op_l3_fix(args):
263
263
 
264
264
 
265
265
  # ---------------------------------------------------------------------------
266
- # op: fluxscan — 阶段A-M1 通量区间制(FVA 区间 + pFBA 点值 + 条件对区间分离判定)
266
+ # op: fluxscan — 通量区间制(FVA 区间 + pFBA 点值 + 条件对区间分离判定)
267
267
  # 语义锁定:overlap = 求解器伪影禁止引用;判定公式见 fluxscan.judge_interval(单测锁定)
268
268
  # ---------------------------------------------------------------------------
269
269
  def op_fluxscan(args):
@@ -282,7 +282,7 @@ def op_fluxscan(args):
282
282
 
283
283
 
284
284
  # ---------------------------------------------------------------------------
285
- # op: sensitivity — 阶段A-M2 结构性灵敏度(GAM×biomass 网格 22 组合 + 必需性重扫 + 单组分漂移)
285
+ # op: sensitivity — 结构性灵敏度(GAM×biomass 网格 22 组合 + 必需性重扫 + 单组分漂移)
286
286
  # action=probe 秒级只读(GAM 载体定位/组分计数);缺省 full=22 组合全量(约 35-45min,长任务)
287
287
  # ---------------------------------------------------------------------------
288
288
  def op_sensitivity(args):
@@ -410,7 +410,7 @@ OPS["sensitivity"] = op_sensitivity
410
410
 
411
411
 
412
412
  # ---------------------------------------------------------------------------
413
- # op: ledger — 阶段A-M3 prediction ledger(list/query/update;只读/追加/更新,不删行)
413
+ # op: ledger — prediction ledger(list/query/update;只读/追加/更新,不删行)
414
414
  # 默认账本 ~/.dsh/dsh-bio-gem/ledger/predictions.jsonl;ledger_path 可覆盖(测试用临时路径)
415
415
  # ---------------------------------------------------------------------------
416
416
  def op_ledger(args):
@@ -436,7 +436,7 @@ def op_ledger(args):
436
436
 
437
437
 
438
438
  # ---------------------------------------------------------------------------
439
- # op: benchmark — 阶段B-B1 通用基准对比(六关并列/生长/含边界介质回退/biomass 探针/必需性对比
439
+ # op: benchmark — 通用基准对比(六关并列/生长/含边界介质回退/biomass 探针/必需性对比
440
440
  # 含退化护栏/表型/可复现性/账本 comparison_refs 回填;md 落盘可选)
441
441
  # ---------------------------------------------------------------------------
442
442
  def op_benchmark(args):
@@ -462,7 +462,7 @@ OPS["benchmark"] = op_benchmark
462
462
 
463
463
 
464
464
  # ---------------------------------------------------------------------------
465
- # op: secretion — 阶段C-C1 可分泌代谢物谱(production envelope 扫描;纯拓扑边界声明内置;
465
+ # op: secretion — 可分泌代谢物谱(production envelope 扫描;纯拓扑边界声明内置;
466
466
  # wt<=EPS 退化护栏不登记;type=secretion 账本登记幂等)
467
467
  # ---------------------------------------------------------------------------
468
468
  def op_secretion(args):
@@ -481,7 +481,7 @@ OPS["secretion"] = op_secretion
481
481
 
482
482
 
483
483
  # ---------------------------------------------------------------------------
484
- # op: double_knockout — 阶段C-C2 双敲 v1(合成致死;GPR 穷尽先验 + FVA 预筛全扫,max_pairs 预算;
484
+ # op: double_knockout — 双敲 v1(合成致死;GPR 穷尽先验 + FVA 预筛全扫,max_pairs 预算;
485
485
  # 假设声明内置;wt<=EPS 退化护栏不登记;type=synthetic_lethal 账本登记幂等)
486
486
  # ---------------------------------------------------------------------------
487
487
  def op_double_knockout(args):
@@ -499,7 +499,7 @@ OPS["double_knockout"] = op_double_knockout
499
499
 
500
500
 
501
501
  # ---------------------------------------------------------------------------
502
- # op: enrichment — 阶段C-C3 必需基因通路富集(超几何单侧 + BH FDR;通路源=SBML groups
502
+ # op: enrichment — 必需基因通路富集(超几何单侧 + BH FDR;通路源=SBML groups
503
503
  # [gapseq MetaCyc PWY];无注释模型按契约 annotation_unavailable 兜底;不登记账本)
504
504
  # ---------------------------------------------------------------------------
505
505
  def op_enrichment(args):
@@ -516,7 +516,7 @@ OPS["enrichment"] = op_enrichment
516
516
 
517
517
 
518
518
  # ---------------------------------------------------------------------------
519
- # op: targets — 阶段C-C4 靶点清单规范导出(账本三类预测 -> 锁定 schema;供下游引物/编辑
519
+ # op: targets — 靶点清单规范导出(账本三类预测 -> 锁定 schema;供下游引物/编辑
520
520
  # 工具直接输入;与账本计数闭合;引物/质粒设计本身不做)
521
521
  # ---------------------------------------------------------------------------
522
522
  def op_targets(args):
package/python/l3_fix.py CHANGED
@@ -422,7 +422,7 @@ def l3_fix(model_path, medium=None, substrates=None, out=None,
422
422
  g = _growth_sole(m, resolved_med, exid)
423
423
  if exid and exid in m.reactions and g < 1e-6:
424
424
  l3.append({"substrate": sub, "exchange": exid, "growth_sole_before": round(g, 6),
425
- # 阶段A-M4 口径声明(只增)
425
+ # 口径声明(只增)
426
426
  "units": "1/h",
427
427
  "point_value_note": "单点 FBA 值,非解空间硬结论;条件对比请用 gem_fluxscan 区间分离判定"})
428
428
 
package/python/ledger.py CHANGED
@@ -1,5 +1,5 @@
1
- # ledger.py — 阶段A-M3 prediction ledger(预测账本,可追踪可实验兑现)
2
- # 文件: ~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl(一个模型一个账本,2026-08-31 用户决策)
1
+ # ledger.py — prediction ledger(预测账本,可追踪可实验兑现)
2
+ # 文件: ~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl(一个模型一个账本)
3
3
  # ——默认账本按模型文件 basename 推导(model_ledger_path),显式 ledger_path 仍可覆盖;
4
4
  # ——无 model 无 path 的查询/摘要 = 聚合所有模型账本(全局视图,by_model 分布保留)。
5
5
  # 迁移:旧全局 predictions.jsonl 已拆分为各模型账本(predictions.jsonl.legacy-20260831 保留备份,
@@ -42,7 +42,7 @@ def _norm_path(p):
42
42
 
43
43
 
44
44
  # ---------------------------------------------------------------------------
45
- # 账本文件解析:一个模型一个账本(2026-08-31 用户决策)
45
+ # 账本文件解析:一个模型一个账本
46
46
  # ---------------------------------------------------------------------------
47
47
  def model_ledger_path(model_path):
48
48
  """默认账本 = ledger/<模型文件名去扩展名>.jsonl(一个模型一个账本)。"""
@@ -161,7 +161,7 @@ def query_ledger(rtype=None, status=None, condition=None, model=None,
161
161
  limit=None, offset=0, path=None, deprecated=None):
162
162
  """条件过滤(type/status/condition/model 前缀匹配、大小写不敏感、可组合)+ 分页。
163
163
  账本定位:显式 path > model(该模型自己的账本)> 聚合所有模型账本。
164
- 阶段D-P2:deprecated 过滤(True=仅打标行;False=仅未打标行;缺省=全部)。"""
164
+ deprecated 过滤(True=仅打标行;False=仅未打标行;缺省=全部)。"""
165
165
  rows, corrupt = _resolve_rows(path=path, model=model)
166
166
 
167
167
  def _pref(v, q):
@@ -189,7 +189,7 @@ def query_ledger(rtype=None, status=None, condition=None, model=None,
189
189
 
190
190
 
191
191
  def mark_deprecated_duplicates(path=None, progress=None):
192
- """阶段D-P2:跨斜杠风格重复打标(一次性运维用;不删行、不改既有字段)。
192
+ """跨斜杠风格重复打标(一次性运维用;不删行、不改既有字段)。
193
193
  识别规则:同一归一化 content hash 的行组内,model 含反斜杠且存在正斜杠同预测 -> 打标
194
194
  deprecated=true + superseded_by=<正斜杠版 prediction_id>。path=None 时对每个活动账本执行。
195
195
  返回聚合计数。"""
@@ -310,7 +310,7 @@ def ledger_summary(path=None, model=None):
310
310
  """账本摘要:{total, by_status, by_type, by_model, deprecated_count}。
311
311
  账本定位:显式 path > model(该模型自己的账本)> 聚合所有模型账本。
312
312
  P1-3(2026-08-31):按模型给 own_model_entries=该模型账本条数,防「把全局账本当作本模型预测」误读。
313
- 2026-08-31 用户决策后:一个模型一个账本,model 定位时 total 即该模型预测数。"""
313
+ 约定:一个模型一个账本,model 定位时 total 即该模型预测数。"""
314
314
  rows, corrupt = _resolve_rows(path=path, model=model)
315
315
  by_status, by_type, by_model = {}, {}, {}
316
316
  dep = 0
@@ -474,7 +474,7 @@ if __name__ == "__main__":
474
474
  # 幂等:同内容复跑不追加
475
475
  r2 = register_predictions([mk(1), mk(2), mk(3)], path=p)
476
476
  assert r2["appended"] == 0 and r2["skipped_duplicates"] == 3, r2
477
- # 阶段D P1:model 路径斜杠/大小写不同但指向同一文件 -> 仍判重复
477
+ # model 路径斜杠/大小写不同但指向同一文件 -> 仍判重复
478
478
  r2b = register_predictions([{**mk(1), "model": "f:\\m.XML"}], path=p)
479
479
  assert r2b["appended"] == 0 and r2b["skipped_duplicates"] == 1, r2b
480
480
  # 新增一条 + ID 连续
@@ -506,7 +506,7 @@ if __name__ == "__main__":
506
506
  update_row("P0002", status="literature_supported", path=p)
507
507
  rows2, corrupt2 = load_rows(p)
508
508
  assert len(corrupt2) == 1 and len(rows2) == 4, (corrupt2, rows2)
509
- # 阶段D-P2:直写一条反斜杠 model 的历史态重复行(模拟归一化修复前的存量),
509
+ # 直写一条反斜杠 model 的历史态重复行(模拟归一化修复前的存量),
510
510
  # mark_deprecated_duplicates 打标(不删行)+ query 过滤 + summary deprecated_count
511
511
  n_before = len(load_rows(p)[0])
512
512
  with open(p, "a", encoding="utf-8") as f:
@@ -39,7 +39,7 @@ def save_card(model_path, card):
39
39
 
40
40
  def _ensure_v2(card):
41
41
  """legacy 卡(build 直写,无 schema/lineage)即时迁移到 v2。
42
- v3 卡(阶段A-M2 起,含 robustness 章节)视为已迁移,不降级。"""
42
+ v3 卡(含 robustness 章节)视为已迁移,不降级。"""
43
43
  if card.get("schema") in (SCHEMA, "v3") and "model_lineage" in card:
44
44
  return card
45
45
  card.setdefault("schema", SCHEMA)
@@ -165,7 +165,7 @@ def set_essential_genes(model_path, scan_result, model=None):
165
165
 
166
166
 
167
167
  def set_robustness(model_path, sensitivity_result):
168
- """sensitivity 结果写入 card.robustness(阶段A-M2:schema v3 起步,向后兼容 v2 卡只增字段)。
168
+ """sensitivity 结果写入 card.robustness(schema v3 起步,向后兼容 v2 卡只增字段)。
169
169
  sensitivity_result: sensitivity() 返回(含 wt_growth_grid/stability/component_sensitivity/gam_carrier)。
170
170
  无 card 返回 None(不凭空造卡——纪律同 set_essential_genes)。"""
171
171
  card = load_card(model_path)
@@ -218,7 +218,7 @@ if __name__ == "__main__":
218
218
  assert back["verified_phenotypes"]["matched"] == 13
219
219
  assert back["essential_genes"]["count"] == 2
220
220
  assert back["essential_genes"]["genes"][0]["evidence_level"] == "high_confidence"
221
- # 阶段A-M2:robustness 章节(v2→v3 只增字段)+ 无 card 不造卡
221
+ # robustness 章节(v2→v3 只增字段)+ 无 card 不造卡
222
222
  assert set_robustness(mp + ".nonexistent", {"wt_growth_grid": []}) is None
223
223
  c4 = set_robustness(mp, {"combinations": 22, "baseline_reproduced": True,
224
224
  "wt_growth_grid": [{"biomass": 1.0, "gam": 40.0, "growth": 0.519981,
@@ -92,7 +92,7 @@ def phenotype_fix(model_path, phenotype_table=None, medium=None, max_add=20, out
92
92
  except Exception:
93
93
  pass
94
94
  result["card_version"] = card_version
95
- # 阶段A-M3: prediction ledger 自动登记(每底物一条 G4 结果;幂等去重;失败仅 WARN)
95
+ # prediction ledger 自动登记(每底物一条 G4 结果;幂等去重;失败仅 WARN)
96
96
  try:
97
97
  import ledger as _ledger
98
98
  from model_card import load_card as _load_card
@@ -1,11 +1,11 @@
1
- # secretion.py — 阶段C-C1 可分泌代谢物谱(菌种通用)
1
+ # secretion.py — 可分泌代谢物谱(菌种通用)
2
2
  # 候选 = 介质层两级策略导出的交换反应(build_ex_index:EX_ 型与 boundary 型模型都适用)。
3
3
  # 可分泌判定 = production envelope 扫描:固定生长分数 {0.25,0.5,0.75,0.9,0.99,1.0} 下产物交换最大化
4
4
  # (强制 biomass 通量 >= fraction*wt,最大化交换反应的分泌方向通量);
5
5
  # 任一分数 >0 且产物交换 > 1e-6 → 可分泌。
6
6
  # 边界声明(方案文件要求,内置于输出):未考虑毒性/渗透压/调控,纯拓扑/线性规划结果。
7
- # 退化护栏(阶段 A/B 教训):被测模型 wt<=EPS(介质下不生长,如 AB 预设对非根瘤菌科物种——
8
- # 阶段B-B3 molybdate 教训)→ 不扫描不登记账本,输出 degenerate:true + 介质适配提示。
7
+ # 退化护栏(实测教训):被测模型 wt<=EPS(介质下不生长,如 AB 预设对非根瘤菌科物种——
8
+ # molybdate 介质适配经验)→ 不扫描不登记账本,输出 degenerate:true + 介质适配提示。
9
9
  import os
10
10
  import sys
11
11
  import csv
@@ -77,7 +77,7 @@ def secretion(model_path, medium=None, fractions=None, export_csv=None,
77
77
  if out["degenerate"]:
78
78
  out["degenerate_note"] = (f"wt_growth={wt}<=EPS:被测模型在指定介质下不生长,production envelope 无意义,"
79
79
  "未扫描、未登记账本。提示:内置介质预设为根瘤菌科(C58)调校,非根瘤菌模型需先做介质适配"
80
- "(阶段B-B3 教训:iML1515 严格 AB 缺 molybdate)。")
80
+ "(经验:iML1515 严格 AB 缺 molybdate)。")
81
81
  try:
82
82
  from benchmark import medium_adaptation_hints
83
83
  out["medium_adaptation_hints"] = medium_adaptation_hints(model_path, medium)
@@ -1,4 +1,4 @@
1
- # sensitivity.py — 阶段A-M2 结构性灵敏度(把"模型不确定"量化)
1
+ # sensitivity.py — 结构性灵敏度(把"模型不确定"量化)
2
2
  # 网格(全量,不抽样): biomass 组分系数 ×{0.75, 1.0, 1.25} × GAM {1,5,10,20,30,40,50} = 21 扫点
3
3
  # + 1 基准组合(biomass×1.0 且 GAM=原始值,不扰动)= 22 组合;每组合 wt growth + 必需性重扫
4
4
  # (复用 essential_scan.setup_model_medium / scan_essentiality——M2 顺带工程改进)。
@@ -85,7 +85,7 @@ def find_biomass_gam(m):
85
85
  if role and abs(coeff) > 1.0 and role not in stub:
86
86
  stub[role] = met.id
87
87
  stub_coeffs[role] = coeff
88
- # 阶段A-M5 适配(iNX1344_v4 探索结论):H2O 角色的代谢物可能公式缺失(如 M00001_c
88
+ # 适配(iNX1344_v4 探索结论):H2O 角色的代谢物可能公式缺失(如 M00001_c
89
89
  # formula=None),且其量级与 GAM 净水电解量(ADP 系数)一致——量级回退补判。
90
90
  if {"atp", "adp", "pi", "h"} <= set(stub) and "h2o" not in stub:
91
91
  gam_scale = abs(stub_coeffs["adp"])
@@ -240,7 +240,7 @@ def sensitivity(model_path, medium=None, biomass_scales=None, gam_grid=None,
240
240
  base_row["baseline"] = True
241
241
  grid.append(base_row)
242
242
  baseline_set = set(base_row["essential_genes"])
243
- degenerate = base_row["growth"] <= EPS # 阶段A-M5 发现:wt=0 时必需性判定退化(候选全判"必需")
243
+ degenerate = base_row["growth"] <= EPS # 实测:wt=0 时必需性判定退化(候选全判"必需")
244
244
  if degenerate:
245
245
  log("[sens] WARN: 基准组合 wt_growth<=0(介质不可解析/模型不生长)——必需性判定退化,"
246
246
  "结果仅证明工具在该模型上跑通,essential 集无生物学意义")
package/python/targets.py CHANGED
@@ -1,4 +1,4 @@
1
- # targets.py — 阶段C-C4 靶点清单规范导出(下游接口面;菌种通用)
1
+ # targets.py — 靶点清单规范导出(下游接口面;菌种通用)
2
2
  # 汇总来源:账本(essentiality/synthetic_lethal/secretion 预测;essential 默认读账本不重扫)。
3
3
  # 输出 schema(锁定,每行 11 字段):target_id/type/genes/met_ids/condition/rationale/
4
4
  # evidence_tier/status/growth_or_maxprod/source/exported_at。
@@ -196,7 +196,7 @@ class Validator:
196
196
  "growth_all_closed": round(g_closed, 6),
197
197
  "ratio_vs_reference": round(ratio, 4) if ratio is not None else None,
198
198
  "checks": {"medium>0": ok_grow, "no_carbon==0": ok_noc, "closed==0": ok_closed},
199
- # 阶段A-M4 口径声明(只增):单点 FBA 值非硬结论
199
+ # 口径声明(只增):单点 FBA 值非硬结论
200
200
  "units": "1/h",
201
201
  "point_value_note": "单点 FBA 值,非解空间硬结论;条件对比请用 gem_fluxscan 区间分离判定",
202
202
  }
@@ -312,7 +312,7 @@ class Validator:
312
312
  matched += 1
313
313
  results.append({"substrate": sub, "published": int(pub), "predicted": int(pred),
314
314
  "growth": round(g, 6), "exchange": exid or None, "match": bool(ok),
315
- # 阶段A-M4 口径声明(只增):每底物 growth 为单点 FBA 值
315
+ # 口径声明(只增):每底物 growth 为单点 FBA 值
316
316
  "units": "1/h",
317
317
  "point_value_note": "单点 FBA 值,非解空间硬结论;条件对比请用 gem_fluxscan 区间分离判定"})
318
318
  rep = {
package/src/jobs.js CHANGED
@@ -84,7 +84,7 @@ export function readProgress(jobId) {
84
84
  }
85
85
  }
86
86
 
87
- // 阶段D-P2:失败透明化——job 目录内容摘要 + stderr 尾部(成功路径不计算不返回)
87
+ // 失败透明化——job 目录内容摘要 + stderr 尾部(成功路径不计算不返回)
88
88
  function jobDetail(jobDir) {
89
89
  let files = []
90
90
  try {
package/src/tools.js CHANGED
@@ -1,4 +1,4 @@
1
- // dsh-bio-gem — 工具层(defineTool 注册,23 语义化工具,2026-08-30 阶段C-C4 起)
1
+ // dsh-bio-gem — 工具层(defineTool 注册,23 语义化工具)
2
2
  // 全部执行走 python/gem_ops.py(JSON stdin 协议)或 build.py CLI(gem_build 长任务)。
3
3
  // op 与工具对照:23 op + gem_build(build CLI 直调);详见 docs/ARCHITECTURE.md §3。
4
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -324,7 +324,7 @@ export function registerTools(ctx) {
324
324
  },
325
325
  })))
326
326
 
327
- // gem_fluxscan:通量区间制(阶段A-M1:FVA 区间 + pFBA 点值 + 条件对区间分离判定)
327
+ // gem_fluxscan:通量区间制(FVA 区间 + pFBA 点值 + 条件对区间分离判定)
328
328
  disposers.push(ctx.tools.register(gemTool({
329
329
  name: 'gem_fluxscan',
330
330
  description:
@@ -353,7 +353,7 @@ export function registerTools(ctx) {
353
353
  timeoutMs: 900_000,
354
354
  })))
355
355
 
356
- // gem_sensitivity:结构性灵敏度(阶段A-M2:GAM×biomass 网格 22 组合 + 稳定性三分类 + 单组分漂移)
356
+ // gem_sensitivity:结构性灵敏度(GAM×biomass 网格 22 组合 + 稳定性三分类 + 单组分漂移)
357
357
  disposers.push(ctx.tools.register(gemTool({
358
358
  name: 'gem_sensitivity',
359
359
  description:
@@ -381,7 +381,7 @@ export function registerTools(ctx) {
381
381
  timeoutMs: 3_600_000,
382
382
  })))
383
383
 
384
- // gem_ledger:prediction ledger 预测账本(阶段A-M3:gem_essentiality/gem_phenotype 自动登记的预测可查询/更新/追踪)
384
+ // gem_ledger:prediction ledger 预测账本(gem_essentiality/gem_phenotype 自动登记的预测可查询/更新/追踪)
385
385
  disposers.push(ctx.tools.register(gemTool({
386
386
  name: 'gem_ledger',
387
387
  description:
@@ -412,7 +412,7 @@ export function registerTools(ctx) {
412
412
  timeoutMs: 60_000,
413
413
  })))
414
414
 
415
- // gem_benchmark:通用基准对比(阶段B-B1:任何两个 GEM 的规范对比表,六关并列+生长+biomass 探针+必需性+表型+账本回填)
415
+ // gem_benchmark:通用基准对比(任何两个 GEM 的规范对比表,六关并列+生长+biomass 探针+必需性+表型+账本回填)
416
416
  disposers.push(ctx.tools.register(gemTool({
417
417
  name: 'gem_benchmark',
418
418
  description:
@@ -440,7 +440,7 @@ export function registerTools(ctx) {
440
440
  timeoutMs: 1_200_000,
441
441
  })))
442
442
 
443
- // gem_secretion:可分泌代谢物谱(阶段C-C1:production envelope 扫描,纯拓扑边界声明内置)
443
+ // gem_secretion:可分泌代谢物谱(production envelope 扫描,纯拓扑边界声明内置)
444
444
  disposers.push(ctx.tools.register(gemTool({
445
445
  name: 'gem_secretion',
446
446
  description:
@@ -467,7 +467,7 @@ export function registerTools(ctx) {
467
467
  timeoutMs: 900_000,
468
468
  })))
469
469
 
470
- // gem_double_knockout:双敲 v1 合成致死(阶段C-C2:GPR 穷尽先验 + FVA 预筛全扫,假设声明内置)
470
+ // gem_double_knockout:双敲 v1 合成致死(GPR 穷尽先验 + FVA 预筛全扫,假设声明内置)
471
471
  disposers.push(ctx.tools.register(gemTool({
472
472
  name: 'gem_double_knockout',
473
473
  description:
@@ -491,7 +491,7 @@ export function registerTools(ctx) {
491
491
  timeoutMs: 1_200_000,
492
492
  })))
493
493
 
494
- // gem_enrichment:必需基因通路富集(阶段C-C3:超几何+BH FDR;通路源=SBML groups[MetaCyc PWY])
494
+ // gem_enrichment:必需基因通路富集(超几何+BH FDR;通路源=SBML groups[MetaCyc PWY])
495
495
  disposers.push(ctx.tools.register(gemTool({
496
496
  name: 'gem_enrichment',
497
497
  description:
@@ -512,7 +512,7 @@ export function registerTools(ctx) {
512
512
  timeoutMs: 300_000,
513
513
  })))
514
514
 
515
- // gem_targets:靶点清单规范导出(阶段C-C4:账本三类预测 -> 锁定 schema,供下游引物/编辑工具直接输入)
515
+ // gem_targets:靶点清单规范导出(账本三类预测 -> 锁定 schema,供下游引物/编辑工具直接输入)
516
516
  disposers.push(ctx.tools.register(gemTool({
517
517
  name: 'gem_targets',
518
518
  description: