@dsh-bio/dsh-bio-gem 0.1.15 → 0.1.16

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
@@ -17,7 +17,7 @@ Genome-scale metabolic model builder for dsh: genome in, validated SBML out.
17
17
  | Python | 3.10+,且装 **`cobra`** | 分析/验证/补洞/账本/基准/导出 —— 除 `gem_build` 外的 22 个工具 |
18
18
  | `pyrodigal` | 装在同一个 Python 环境(可选但建议) | 裸基因组自动注释兜底(`gem_annotate` / fna 输入) |
19
19
  | **CarveMe** | 独立 venv `~/.dsh/dsh-bio-gem/venv-carveme`,含 `carve.exe` + **`diamond.exe`** | `gem_build`(carveme 引擎) |
20
- | WSL2 + gapseq | 可选,按本机拓扑(见第 4 节) | `gem_build`(gapseq 引擎) |
20
+ | WSL2 + gapseq | 可选,按 WSL2 环境拓扑(见第 4 节) | `gem_build`(gapseq 引擎) |
21
21
 
22
22
  > **Python 环境从哪来(v0.1.4 起)**:解释器探测顺序为
23
23
  > `GEM_PYTHON` → **宿主 `dsh-bio-genie` 的自举环境** → `CONDA_PREFIX` → `PATH` 中的 `python`,
@@ -38,7 +38,7 @@ npx -y @deepseek-ai/dsh plugin --profile web add @dsh-bio/dsh-bio-gem
38
38
  # 方式二:从 GitHub 安装(拉源码;本插件纯 ESM 无构建步骤,可直接加载)
39
39
  npx -y @deepseek-ai/dsh plugin --profile web add github:moonbowterfly/dsh-bio-gem
40
40
 
41
- # 方式三:从本地目录安装(开发调试)
41
+ # 方式三:从本地目录安装(本地源码)
42
42
  npx -y @deepseek-ai/dsh plugin --profile web add ./dsh-bio-gem
43
43
  ```
44
44
 
@@ -53,10 +53,10 @@ npx -y @deepseek-ai/dsh plugin --profile web add ./dsh-bio-gem
53
53
 
54
54
  3. 重新打开桌面端生效(也可直接在桌面端内 **「插件」页**输入包名安装,无需退出应用)。
55
55
 
56
- - 本机若已全局安装 dsh CLI,把 `npx -y @deepseek-ai/dsh` 换成 `dsh` 即可。
56
+ - 若已全局安装 dsh CLI,把 `npx -y @deepseek-ai/dsh` 换成 `dsh` 即可。
57
57
  - `--profile <name>` 是**必填选项**(不传报 `required option '--profile <name>' not specified`);Web 端固定用 `web`,桌面端固定用 `desktop`。
58
58
  - 安装完**重启对应的 dsh**(web:重新双击启动入口;桌面端:重开应用)。
59
- - **引擎兼容**:0.1.x 侧经 dsh 0.1.5-rc.2 走廊逐卡走查与实机验证;**0.2.0+(含官方桌面端)已于 2026-10-01 实测**(工具全量注册 + `gem_media_resolve` 真实执行)。
59
+ - **引擎兼容**:0.1.x 侧经 dsh 0.1.5-rc.2 完整兼容核验与实机验证;**0.2.0+(含官方桌面端)已于 2026-10-01 实测**(工具全量注册 + `gem_media_resolve` 真实执行)。
60
60
  - 版本刚发布时可能短时间拉不到:registry 首次分发有几分钟延迟,`pnpm` 还可能缓存住 404。遇到 `ERR_PNPM_FETCH_404 ... is not in the npm registry` 时等几分钟重试,或在命令末尾追加 `--registry https://registry.npmjs.org/` 绕过缓存。
61
61
 
62
62
  验证插件层已生效(不用启动服务):
@@ -121,13 +121,15 @@ unzip -o diamond.zip diamond.exe -d "$HOME/.dsh/dsh-bio-gem/venv-carveme/Scripts
121
121
 
122
122
  ### 4.(可选)gapseq 引擎(WSL2)
123
123
 
124
- `gem_build` 的 `engine=gapseq` 走 WSL2 桥(`gem_gapseq` 原子四步:setup / launch / status / fetch),质量档耗时 30-60 分钟/模型,非必需——默认的 `engine=carveme` 已能出可验证模型。桥按本机拓扑实现(WSL2 + `/opt/miniforge3` conda 环境 `gapseq` + 本地序列库),换机器需改 `python/gapseq_wsl.py` 顶部常量,故目前**视为实验性可选能力**。没有 WSL2 不影响其余 22 个工具与 carveme 构建。
124
+ `gem_build` 的 `engine=gapseq` 走 WSL2 桥(`gem_gapseq` 原子四步:setup / launch / status / fetch),质量档耗时 30-60 分钟/模型,非必需——默认的 `engine=carveme` 已能出可验证模型。桥按 WSL2 + conda 环境拓扑实现(默认路径见 `python/gapseq_wsl.py` 顶部常量,换机器需相应调整),故目前**视为实验性可选能力**。没有 WSL2 不影响其余 22 个工具与 carveme 构建。
125
+
126
+ 能力探针只读检查 `doall` 使用的环境内 `seq/Bacteria` 元数据和 `rev/rxn/unrev` 文件,不请求 Zenodo。检查路径与当前固定的 conda 环境路径一致。
125
127
 
126
128
  ### 5. 自检(仓库源码目录内)
127
129
 
128
130
  ```sh
129
131
  # 冒烟:不依赖 dsh,直测 Python 层 + 工具注册表
130
- # 断言锚定本机 C58 夹具路径(见 test/smoke.js 顶部常量),换机器先改路径
132
+ # 断言锚定固定夹具路径(见 test/smoke.js 顶部常量),换机器先改路径
131
133
  GEM_PYTHON=<你的-cobra-python> node test/smoke.js --skip-build # 跳过 ~70s 的 build 单测
132
134
  GEM_PYTHON=<你的-cobra-python> node test/smoke.js # 含 build 单测
133
135
  # 托管领域扩展的只读 integration 协议(无需 dsh 实例)
@@ -210,7 +212,7 @@ CarveMe + diamond are required only by `gem_build`; the other 20 tools need noth
210
212
  | `gem_essentiality` | 全量必需基因扫描(FVA 预筛 + 手工敲除)| ✅ C58: 必需 155 |
211
213
  | `gem_annotate` | 基因组→蛋白(官方优先 + pyrodigal 兜底,纯 Windows)| ✅ pyrodigal 5330 |
212
214
  | `gem_build` | CarveMe/gapseq 双引擎构建(fna/faa;后台 job + 进度;M9 或目标介质验证闭环)| ✅ carveme 70s / fna 全链 63.5s / gapseq 实测中 |
213
- | `gem_gapseq` | gapseq 原子四步(WSL 可选:setup/launch/status/fetch)| ✅ 本机全通 |
215
+ | `gem_gapseq` | gapseq 原子四步(WSL 可选:setup/launch/status/fetch)| ✅ 实测全通 |
214
216
  | `gem_media_resolve` | 跨引擎介质解析 RPC(自然名→EX ID;消费侧统一入口)| ✅ AB→20 EX |
215
217
  | `gem_fluxscan` | 通量区间制(FVA 区间+pFBA 点值;条件对比区间分离判定,overlap=伪影禁止引用)| ✅ C58 AB 0.519981 / 蔗糖 supplement 0.97077 |
216
218
  | `gem_sensitivity` | 结构性灵敏度(GAM×biomass 22 组合全量+稳定性三分类+单组分漂移+模型卡鲁棒性 v3)| ✅ C58 基准复现 155 |
@@ -224,7 +226,7 @@ CarveMe + diamond are required only by `gem_build`; the other 20 tools need noth
224
226
  | `gem_quality` | 模型质量报告(gem-qi-v1:blocked/环路(fastcc 方向锥)/元素平衡/孤儿与死端/覆盖/连通性 + 启发式聚合分;分项与 failed_checks 为准)| ✅ C58+AB: qi 67.23 / blocked 1032 / cyclic 289 |
225
227
  | `gem_sample` | 通量空间采样(ACHR 默认 / OptGP 大样本;growth_floor_fraction 受限空间;全空间 vs 受限边界声明)| ✅ C58+AB: 全空间 median 0.019 / floor-0.9 min 0.468 |
226
228
 
227
- 架构/决策见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)、[docs/DECISIONS-2026-08-29.md](docs/DECISIONS-2026-08-29.md)。
229
+ 架构见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
228
230
 
229
231
  ## 开发速查
230
232
 
@@ -4,22 +4,22 @@
4
4
 
5
5
  dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质粒/多染色体),自动构建→验证→补洞→出报告(标准 SBML + 模型卡),产出后可被 dsh-bio-genie 现有消费工具(FBA/必需性/生产包络线/模型面板)直接加载使用。
6
6
 
7
- 硬性原则(沿袭 bio-genie):**用户零手动安装、零自愈、通用化(不可本机特化)、结论可溯源**。
7
+ 硬性原则(沿袭 bio-genie):**用户零手动安装、零自愈、通用化(不针对特定机器特化)、结论可溯源**。
8
8
 
9
9
  ## 2. 决策记录(为什么这么设计)
10
10
 
11
11
  | 日期 | 决策 | 依据 |
12
12
  |---|---|---|
13
13
  | 08-28 | 插件名 dsh-bio-gem;资产盘点:消费侧已就绪、补构建侧闭环 | 用户拍板 |
14
- | 08-29 | 引擎路线:**任务门槛路由**(不是简单 auto);落地节奏 **M1 CarveMe+补洞 → M2 gapseq WSL 桥 → M3 双引擎交叉** | 第三方 GLM 独立评估 + 本机实测(CarveMe AB 不生长=补洞是生存线;WSL 桥显著降级交付风险;Docker 非 WSL 替代)|
14
+ | 08-29 | 引擎路线:**任务门槛路由**(不是简单 auto);落地节奏 **M1 CarveMe+补洞 → M2 gapseq WSL 桥 → M3 双引擎交叉** | 独立设计评估 + 实测(CarveMe AB 不生长=补洞是生存线;WSL 桥显著降级交付风险;Docker 非 WSL 替代)|
15
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 | 修正 GLM 建议:弃 μ 判据用 FBA 通量判据;pyrodigal 注释前端降 backlog;测试矩阵首版收敛 C58+2 公开株 | 本机输出口径为 objective_value;默认输入是带注释基因组 |
16
+ | 08-29 | 修正建议:弃 μ 判据用 FBA 通量判据;pyrodigal 注释前端降 backlog;测试矩阵首版收敛 C58+2 公开株 | 输出口径为 objective_value;默认输入是带注释基因组 |
17
17
 
18
- **裁决原则**:GLM 分析质量高但缺本机上下文(输出单位、输入形态、部署面=本机为主的现实),凡冲突处以本机实测与产品原则为准。
18
+ **采纳原则**:外部分析缺实际环境上下文时,凡冲突处以实测与产品原则为准。
19
19
 
20
20
  ## 3. 工具契约(21 工具 ↔ Python 层;21 op + build CLI)
21
21
 
22
- | 工具 | Python 层 | 阶段 |
22
+ | 工具 | Python 层 | 状态 |
23
23
  |---|---|---|
24
24
  | gem_build | build.py CLI(CarveMe M9 gapfill;fna 自动注释)| ✅ M1+模块 DONE(C58 63-70s)|
25
25
  | gem_validate | op validate(G1-G6 + GATE_REGISTRY)| ✅ M1 DONE |
@@ -33,15 +33,15 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
33
33
  | gem_report | op model_info(+ ledger_summary 基率摘要)| ✅ DONE |
34
34
  | gem_media_resolve | op media_resolve(介质解析 RPC,消费侧统一入口)| ✅ DONE |
35
35
  | gem_biomass | op biomass_inspect / biomass_apply(inspect 组分+对照参考;apply 覆盖表+三联对照+原文件不动回滚)| ✅ Q2 DONE(复位 delta 0.0)|
36
- | gem_fluxscan | op fluxscan(通量区间制:FVA 区间+pFBA 点值+条件对区间分离判定,overlap=伪影禁止引用)| ✅ 阶段A-M1 DONE(C58 AB 0.519981 / 蔗糖 supplement 0.97077)|
37
- | gem_sensitivity | op sensitivity(GAM×biomass 22 组合全量+稳定性三分类+单组分漂移;模型卡 robustness v3)| ✅ 阶段A-M2 DONE(基准复现 155)|
38
- | gem_ledger | op ledger(prediction ledger:list/query/update;幂等追加式账本)| ✅ 阶段A-M3 DONE(C58 155+19 条幂等复跑)|
39
- | gem_benchmark | op benchmark(通用基准对比:六关并列/生长[介质层两级策略]/biomass 探针/必需性对比含退化护栏/表型/账本回填/md 落盘;model 参数支持 bigg:&lt;id&gt; 下载)| ✅ 阶段B-B1/B2/B3 DONE |
40
- | gem_secretion | op secretion(可分泌谱:production envelope;边界声明内置;wt<=EPS 退化护栏不登记)| ✅ 阶段C-C1 DONE(C58 85 可分泌)|
41
- | gem_double_knockout | op double_knockout(双敲 v1:GPR 穷尽先验+全扫 max_pairs 预算;假设声明内置)| ✅ 阶段C-C2 DONE(Atu3364↔Atu4682 对应命中)|
42
- | gem_enrichment | op enrichment(必需基因通路富集:超几何+BH FDR;无注释 annotation_unavailable 兜底)| ✅ 阶段C-C3 DONE(C58 55 条 FDR 显著)|
43
- | gem_targets | op targets(靶点规范导出:11 字段锁定 schema;账本计数闭合;引物设计不做)| ✅ 阶段C-C4 DONE(258 行三类闭合)|
44
- | gem_precursor_scan | op precursor_scan(阻塞前体分析:基线通量→可生长即返「无阻塞」;不生长则逐前体移除测试定位阻塞点)| ✅ 2026-09-11(E2E 绕道归因产出)|
36
+ | gem_fluxscan | op fluxscan(通量区间制:FVA 区间+pFBA 点值+条件对区间分离判定,overlap=伪影禁止引用)| ✅ 已完成(C58 AB 0.519981 / 蔗糖 supplement 0.97077)|
37
+ | gem_sensitivity | op sensitivity(GAM×biomass 22 组合全量+稳定性三分类+单组分漂移;模型卡 robustness v3)| ✅ 已完成(基准复现 155)|
38
+ | gem_ledger | op ledger(prediction ledger:list/query/update;幂等追加式账本)| ✅ 已完成(C58 155+19 条幂等复跑)|
39
+ | gem_benchmark | op benchmark(通用基准对比:六关并列/生长[介质层两级策略]/biomass 探针/必需性对比含退化护栏/表型/账本回填/md 落盘;model 参数支持 bigg:&lt;id&gt; 下载)| ✅ 已完成 |
40
+ | gem_secretion | op secretion(可分泌谱:production envelope;边界声明内置;wt<=EPS 退化护栏不登记)| ✅ 已完成(C58 85 可分泌)|
41
+ | gem_double_knockout | op double_knockout(双敲 v1:GPR 穷尽先验+全扫 max_pairs 预算;假设声明内置)| ✅ 已完成(Atu3364↔Atu4682 对应命中)|
42
+ | gem_enrichment | op enrichment(必需基因通路富集:超几何+BH FDR;无注释 annotation_unavailable 兜底)| ✅ 已完成(C58 55 条 FDR 显著)|
43
+ | gem_targets | op targets(靶点规范导出:11 字段锁定 schema;账本计数闭合;引物设计不做)| ✅ 已完成(258 行三类闭合)|
44
+ | gem_precursor_scan | op precursor_scan(阻塞前体分析:基线通量→可生长即返「无阻塞」;不生长则逐前体移除测试定位阻塞点)| ✅ 2026-09-11(实测归因产出)|
45
45
 
46
46
  > 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
47
  >
@@ -49,7 +49,7 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
49
49
 
50
50
  > **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
51
 
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)。**生长/通量数值口径(阶段A-M4)**:所有产出生长/通量数值的工具输出均带 `units: mmol/gDW/h` 与单点 FBA 声明;条件间通量对比一律走 gem_fluxscan 区间分离判定(overlap=伪影禁止引用)。
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=伪影禁止引用)。
53
53
 
54
54
  ## 4. 引擎路线(M1→M2→M3)
55
55
 
@@ -69,7 +69,7 @@ dsh 平台的 **GEM 构建侧插件**:输入细菌全基因组(支持多质
69
69
  | G5 | 必需基因抽检(≤30 基因)| 条件 | 有参照集才跑;映射覆盖 <80% 时 SKIP(WARN) |
70
70
  | G6 | ATP 泄漏检测(全关交换后 ATP demand 应≈0)| ✅ | leak ≤0.01 判 PASS;ATP 解析走 id→name→formula 三级回退(跨 ID 体系)|
71
71
 
72
- **G0 的由来(2026-09-10 E2E 实测)**:MetaCyc 风格 id 的公开模型(iNX1344_v3)上,
72
+ **G0 的由来(2026-09-10 实测)**:MetaCyc 风格 id 的公开模型(iNX1344_v3)上,
73
73
  `gem_gapfind` 报 5 个 L3「内部通路缺口」,实为 biomass 前体未映射所致——agent 为逐个
74
74
  证伪手写 cobra 代码 18 次。现 `gem_validate` 在 G1 之前输出 `g0`,`gem_gapfind` 返回
75
75
  `coherence_warning` + `interpretation_guard`,把该结论前置给 agent。
@@ -126,7 +126,7 @@ job 化 + 进度事件(粒度 ≤5s)+ 分步 checkpoint(每步落盘,可
126
126
 
127
127
  零手动干预下:**基因组进 → 四个消费工具(FBA/必需性/包络线/面板)不经修改即可用的 SBML 出**,且模型在声明培养基上生长为正;C58 端到端演示通过(build→面板可见→FBA 可跑→必需性可跑);模型卡齐全(引擎/版本/补洞记录/验证结果,同输入重跑一致);5-6 Mb 基因组 p95 ≤ 20 min。
128
128
 
129
- ## 附录 A:性能基准(阶段 A-M6,2026-08-30 本机实测,独占运行)
129
+ ## 附录 A:性能基准(2026-08-30 实测,独占运行)
130
130
 
131
131
  分析 Python 3.13.13 / cobra 0.32.1 / GLPK;C58=gapseq 2485 反应/1084 基因;iNX1344_v4=1441 反应/1344 基因。
132
132
 
@@ -142,4 +142,4 @@ job 化 + 进度事件(粒度 ≤5s)+ 分步 checkpoint(每步落盘,可
142
142
  | 单组分 ±25% 灵敏度 | 75 组分×2=150 次 FBA,54.4s | 47 组分×2=94 次 FBA,7.9s |
143
143
  | 必需性漂移 top10(含生长探针) | 522.0s(含 7 刚性对跳过探针) | 33.8s(20/20 全部"不生长跳过") |
144
144
 
145
- > 注:FVA 占单条件耗时 ~75%;sensitivity 线性于组合数(每组合 fresh 读模+FVA+敲除循环)。GLPK 对个别扰动 LP 有病态停摆前科,sensitivity 内置 LP_TIMEOUT_S=30 护栏(见 docs/DECISIONS-阶段A.md M2-5)。
145
+ > 注:FVA 占单条件耗时 ~75%;sensitivity 线性于组合数(每组合 fresh 读模+FVA+敲除循环)。GLPK 对个别扰动 LP 有病态停摆前科,sensitivity 内置 LP_TIMEOUT_S=30 护栏。
@@ -17,7 +17,7 @@
17
17
  - `test/smoke.js` 重构:模型资产**多候选解析**(`--assets-root` / `DSH_BIO_GEM_ASSETS` / 新旧位置)——资产迁移后不再硬编码路径崩溃;**缺资产 / 缺账本标记 SKIP 而非失败**(`--require-assets` 供 CI 严格模式);CarveMe 跳过判断移至调用前;子进程非零退出 fail-closed。
18
18
  - `npm test` 接线:smoke + integration + optional-injection(真注册验证,此前未纳入任何 npm 入口)。
19
19
 
20
- **引擎兼容(2026-09-19)**:经 dsh **0.1.5-rc.2** 走廊逐卡走查(55 张变更卡:零适配命中)与实机验证(工具注册 / 代谢建模面板 / 账本与模型数据)。
20
+ **引擎兼容(2026-09-19)**:经 dsh **0.1.5-rc.2** 完整兼容核验(全量变更:零适配命中)与实机验证(工具注册 / 代谢建模面板 / 账本与模型数据)。
21
21
 
22
22
  ## 安装
23
23
 
@@ -38,6 +38,6 @@ dsh plugin --profile web add @dsh-bio/dsh-bio-gem
38
38
 
39
39
  ## 已知边界(诚实清单)
40
40
 
41
- - `gem_build` 的 gapseq 引擎为实验性(WSL2 本机拓扑绑定,30–60 分钟/模型);默认 carveme 引擎已可出可验证模型。
41
+ - `gem_build` 的 gapseq 引擎为实验性(WSL2 拓扑绑定,30–60 分钟/模型);默认 carveme 引擎已可出可验证模型。
42
42
  - CarveMe 运行时需手动准备一次(README 第 3 节)——「装完插件」不等于「构建可用」,分析/验证类能力不受影响。
43
43
  - 注释依赖 SBML groups(gapseq 系模型自带);无注释模型按契约返回 `annotation_unavailable` 兜底,不伪造通路。
@@ -0,0 +1,31 @@
1
+ # @dsh-bio/dsh-bio-gem v0.1.16
2
+
3
+ > **主题**:导出与工作区修复批次 + 公开文档整理。
4
+ > 基因组尺度代谢模型(GEM)构建插件:基因组输入 → 构建 → 验证 → 补洞 → 出报告(标准 SBML + 模型卡)。
5
+
6
+ ## 修复与改进
7
+
8
+ **导出产物规范化**
9
+ - CSV 导出保持**纯数据表**(标准表头 + 行);声明/参数/免责信息移入旁车文件 `<path>.meta.json`(返回体附 `export_csv_meta` 路径)——标准解析器(pandas 等)可直接读取。
10
+ - 导出路径父目录不存在时**自动创建**(mkdir -p 语义),不再报 FileNotFoundError。
11
+
12
+ **工作区与运行环境**
13
+ - 工具相对路径基于会话工作区解析(python 子进程 cwd 对齐会话目录)。
14
+ - gapseq 能力探针改为只读检查本地序列库元数据,不再依赖在线检查(离线环境不再误报)。
15
+
16
+ **公开文档整理**
17
+ - 清理内部流程信息;架构文档措辞规范化。
18
+
19
+ ## 安装
20
+
21
+ ```sh
22
+ npx -y @deepseek-ai/dsh plugin --profile web add @dsh-bio/dsh-bio-gem
23
+ ```
24
+
25
+ 桌面端(0.2.0+):用桌面端自带 CLI(`--profile desktop`)或应用内「插件」页安装。
26
+ 分析用 Python(cobra)优先复用 dsh-bio-genie 自举环境;未装 genie 时见 README 第 2 节。
27
+
28
+ ## 已知边界(诚实清单)
29
+
30
+ - `gem_build` 的 gapseq 引擎为实验性(WSL2 环境绑定,30–60 分钟/模型);默认 carveme 引擎已可出可验证模型。
31
+ - 其余边界见 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.15",
3
+ "version": "0.1.16",
4
4
  "description": "基因组尺度代谢模型(GEM)构建插件:输入细菌全基因组(蛋白FASTA,支持多质粒/多染色体),自动构建+验证+补洞+出报告(SBML + 模型卡),供 dsh-bio-genie 消费工具加载使用 | Genome-scale metabolic model builder for dsh",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "scripts": {
54
54
  "smoke": "node test/smoke.js",
55
- "test": "node test/check-version-source.mjs && node test/smoke.js --skip-build && node test/integration.js && node test/capabilities.js && node --import ./test/register-dsh-tools.mjs test/optional-injection.js && node --import ./test/register-dsh-tools.mjs test/check-capabilities.mjs && node test/check-counts.mjs && node test/capabilities-warn-state.mjs",
55
+ "test": "node test/check-version-source.mjs && node test/smoke.js --skip-build && python -I test/test_gapseq_probe.py && python -I test/test_fsutil.py && node test/integration.js && node test/capabilities.js && node --import ./test/register-dsh-tools.mjs test/optional-injection.js && node --import ./test/register-dsh-tools.mjs test/check-capabilities.mjs && node test/check-counts.mjs && node test/capabilities-warn-state.mjs && node test/workdir.mjs",
56
56
  "test:full": "node test/smoke.js && node test/integration.js && node test/capabilities.js && node --import ./test/register-dsh-tools.mjs test/optional-injection.js && node --import ./test/register-dsh-tools.mjs test/check-capabilities.mjs && node test/check-counts.mjs"
57
57
  },
58
58
  "peerDependencies": {
@@ -63,4 +63,4 @@
63
63
  "optional": true
64
64
  }
65
65
  }
66
- }
66
+ }
@@ -1,5 +1,5 @@
1
1
  # annotate.py — 路线 P0 注释步骤(纯 Windows)
2
- # 策略(GLM 二轮 Q1 采纳 + 验证协议):官方注释优先 + pyrodigal 兜底
2
+ # 策略(设计评审采纳 + 验证协议):官方注释优先 + pyrodigal 兜底
3
3
  # 1) 同目录 *_protein.faa 存在(NCBI dataset 常见)→ 直接用
4
4
  # 2) 同目录 *.gff(含 CDS)→ 解析坐标从 fna 提取 + 翻译(transl_table 11)
5
5
  # 3) 仅 .fna → pyrodigal(多序列模式;总长 <100kb 时 meta 模式)
@@ -125,7 +125,7 @@ def _essential_full_scan(model_path, medium, log, tag):
125
125
 
126
126
  def map_genes(genes, model_b):
127
127
  """基因映射尽力而为(与物种无关):策略1 identity(同 id);策略2 gene.name 匹配。
128
- 反应桥(EC/名字)需两侧注释充分;本机两命名空间注释层不足时不启用,如实报告。"""
128
+ 反应桥(EC/名字)需两侧注释充分;两侧命名空间注释层不足时不启用,如实报告。"""
129
129
  b_by_id = {g.id for g in model_b.genes}
130
130
  b_by_name = {}
131
131
  for g in model_b.genes:
@@ -387,9 +387,9 @@ def write_md(path, out):
387
387
  def fetch_bigg_model(model_id, dest_dir=None):
388
388
  """从 BiGG 下载模型 SBML(B3 最小版)。
389
389
  URL 策略(实测 2026-08-30):静态库 http://bigg.ucsd.edu/static/models/<id>.xml 返回标准 SBML;
390
- 任务书给的 /api/v2/universal/models/<id>/download 实为 404(universal 是 reactions 命名空间),
390
+ `/api/v2/universal/models/<id>/download` 实为 404(universal 是 reactions 命名空间),
391
391
  /api/v2/models/<id>/download 返回 200 但内容是 BiGG JSON(非 SBML)——两者均不采用。
392
- 直连失败走本机代理 127.0.0.1:27890;都失败抛错(调用方如实报告,不阻塞本地对比)。"""
392
+ 直连失败走系统代理;都失败抛错(调用方如实报告,不阻塞本地对比)。"""
393
393
  import urllib.request
394
394
  import shutil
395
395
  urls = [f"http://bigg.ucsd.edu/static/models/{model_id}.xml"]
@@ -3,7 +3,7 @@
3
3
  # 目的:在 G3/G4/G5 与 gapfind 之前,先判断**模型自身数据质量是否允许下结论**,
4
4
  # 避免把「未映射代谢物」这类数据问题,误报成「通路缺口 / 必需基因异常」。
5
5
  #
6
- # 实测来源(2026-09-10 E2E,iNX1344_v3 —— MetaCyc 风格 id 的公开模型):
6
+ # 实测来源(2026-09-10,iNX1344_v3 —— MetaCyc 风格 id 的公开模型):
7
7
  # - gem_gapfind 报 5 个 L3「内部通路缺口」,根因实为 biomass 前体未映射;
8
8
  # - agent 为证伪这些假阳性,手写 cobra 代码 18 次(占该轮调用的一半)。
9
9
  #
@@ -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 在 E2E 中手工探索过)待重新设计后引入。
19
+ # 正确方法(agent 曾手工探索过)待重新设计后引入。
20
20
  #
21
21
  # 保留的判据都经过「问题模型报出真问题 + 标准模型零误报」双向验证:
22
22
  # - id 体系识别(信息性,决定下游名称映射口径)
@@ -18,6 +18,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
18
18
 
19
19
  from silentio import silent_read_sbml
20
20
  from essential_scan import setup_model_medium, scan_essentiality, EPS
21
+ from fsutil import ensure_parent_dir, write_meta_sidecar
21
22
 
22
23
  ASSUMPTION_NOTE = "细菌双敲验证率无大规模实验数据支撑,本结果=假设生成,供实验设计参考非结论"
23
24
 
@@ -174,16 +175,35 @@ def double_knockout(model_path, medium=None, max_pairs=5000, export_csv=None,
174
175
 
175
176
 
176
177
  def _export_csv(path, results, out):
178
+ """纯数据 CSV(标准表头,机器可读)+ 旁车 meta。
179
+
180
+ 2026-10-05 修:此前把 assumption_note 写成首行 `"# assumption", note` 双字段行——
181
+ pandas/自动解析会把它当表头或脏行(实测 agent 需专门跳过 '#' 行)。
182
+ 现改为:CSV 保持纯表;说明与参数写入 <path>.meta.json(返回体 export_csv_meta)。
183
+ """
184
+ path = ensure_parent_dir(path)
177
185
  n = 0
178
186
  with open(path, "w", newline="", encoding="utf-8-sig") as f:
179
187
  w = csv.writer(f)
180
- w.writerow(["# assumption", out["assumption_note"]])
181
188
  w.writerow(["gene_a", "gene_b", "single_a_growth", "single_b_growth",
182
189
  "double_growth", "rationale", "source"])
183
190
  for r in results:
184
191
  w.writerow([r["pair"][0], r["pair"][1], r["single_a_growth"],
185
192
  r["single_b_growth"], r["double_growth"], r["rationale"], r["source"]])
186
193
  n += 1
194
+ meta = write_meta_sidecar(path, {
195
+ "assumption_note": out.get("assumption_note"),
196
+ "model": out.get("model"),
197
+ "medium": out.get("medium"),
198
+ "medium_preset": out.get("medium_preset"),
199
+ "wt_growth": out.get("wt_growth"),
200
+ "units": out.get("units"),
201
+ "eps": out.get("eps"),
202
+ "max_pairs": out.get("max_pairs"),
203
+ "pairs_found": out.get("pairs_found"),
204
+ })
205
+ if meta:
206
+ out["export_csv_meta"] = meta
187
207
  return n
188
208
 
189
209
 
@@ -13,6 +13,7 @@ from math import comb
13
13
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
14
 
15
15
  from silentio import silent_read_sbml
16
+ from fsutil import ensure_parent_dir
16
17
 
17
18
  NOTE_UNAVAILABLE = ("模型无 SBML groups(通路)注释。可补途径:①用 gapseq 重建(自带 MetaCyc pathway "
18
19
  "groups);②从 BiGG API 取模型 subsystem 后注入 groups;③按 ec-code 注释做粗分类。")
@@ -153,6 +154,7 @@ def enrichment(model_path, gene_list=None, pathway_source=None, ledger_path=None
153
154
  "timing_seconds": round(time.time() - t0, 1),
154
155
  }
155
156
  if export_csv:
157
+ export_csv = ensure_parent_dir(export_csv)
156
158
  with open(export_csv, "w", newline="", encoding="utf-8-sig") as f:
157
159
  w = csv.writer(f)
158
160
  w.writerow(["pathway", "genes_hit_count", "genes_hit", "background_hit",
@@ -1,5 +1,5 @@
1
1
  # fluxscan.py — M1 通量区间制(阶段 A 可信度内核第一件)
2
- # 语义(bsp 锁稿,一字不改):每反应输出 fva_min/fva_max/pfba;条件对比消费区间分离判定;
2
+ # 语义(已锁定):每反应输出 fva_min/fva_max/pfba;条件对比消费区间分离判定;
3
3
  # overlap = 点值差异是求解器伪影,禁止引用。
4
4
  # 计算口径:每 condition 独立 silent_read_sbml 重读模型(勿深拷贝);介质 setup 对齐 validate G3
5
5
  # (expand_medium -> 全交换清零 EX_/DM_/SK_/boundary -> resolve_medium 设 bounds);
@@ -21,6 +21,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
21
21
  from silentio import silent_read_sbml
22
22
  from gapfind import expand_medium, resolve_medium, build_ex_index, match_ex
23
23
  from validate import parse_formula
24
+ from fsutil import ensure_parent_dir
24
25
 
25
26
  EX_PREFIX = ("EX_", "DM_", "SK_")
26
27
  DEFAULT_FRACTION = 0.9999
@@ -168,6 +169,7 @@ def _pair_result(a_name, b_name, data_a, data_b, scope, tol, only_diff):
168
169
 
169
170
  def _export_csv(path, model_reactions, pair_results):
170
171
  """全量 CSV:每行 = 条件对 × 反应(双侧区间/点值/判定)。返回 (rows, bytes)。"""
172
+ path = ensure_parent_dir(path)
171
173
  names = {r.id: r for r in model_reactions}
172
174
  rows = 0
173
175
  with open(path, "w", newline="", encoding="utf-8-sig") as f:
@@ -267,15 +269,15 @@ if __name__ == "__main__":
267
269
  # 双协议(历史坑:stdin 与 argv-file 两派并存,新脚本必须都支持)
268
270
  if "--selftest" in sys.argv:
269
271
  cases = [
270
- # (la, ua, lb, ub, tol, direction, hard) —— 任务书 5 组判定样例
272
+ # (la, ua, lb, ub, tol, direction, hard) —— 5 组判定样例
271
273
  (1.0, 2.0, 5.0, 6.0, 1e-6, "b_higher", True), # ① 分离:b 高
272
274
  (5.0, 6.0, 1.0, 2.0, 1e-6, "a_higher", True), # ① 分离:a 高
273
275
  (1.0, 3.0, 2.0, 4.0, 1e-6, None, False), # ② 重叠(伪影)
274
276
  (1.0, 3.0, 3.0, 5.0, 1e-6, None, False), # ② 接触容差内仍 overlap(边界)
275
277
  (-5.0, -2.0, 0.0, 0.0, 1e-6, "b_higher", True), # ③ 零通量/负向
276
278
  (-5.0, -2.0, 0.0, 0.0, 0.0, "b_higher", True), # ④ 精确边界(tol=0)
277
- # ⑤ 容差:锁定公式下 gap=5e-7 < tol=1e-6 -> overlap(任务书该行样例期望"分离",
278
- # 与锁定公式 ua+tol<lb 矛盾:5e-7 不大于 1e-6。按公式实现,差异上报 bsp 裁决)
279
+ # ⑤ 容差:锁定公式下 gap=5e-7 < tol=1e-6 -> overlap(该样例期望"分离",
280
+ # 与锁定公式 ua+tol<lb 矛盾:5e-7 不大于 1e-6。按公式实现)
279
281
  (1.0, 1.0000005, 1.000001, 2.0, 1e-6, None, False),
280
282
  # 同数字、tol(1e-7)<gap(5e-7) -> 分离:证明容差机制本身生效
281
283
  (1.0, 1.0000005, 1.000001, 2.0, 1e-7, "b_higher", True),
@@ -0,0 +1,44 @@
1
+ """fsutil.py — 文件系统小工具(导出路径处理,2026-10-05 增补)。
2
+
3
+ 背景(round1 TC4 观察 GEM-2,round2 复核):export_csv/export_path 传相对路径
4
+ 且父目录不存在时,直接 FileNotFoundError ——对用户是零价值的报错(需手动 mkdir)。
5
+ 统一改为自动创建父目录(mkdir -p 语义),返回绝对路径。
6
+ - 部分导出曾把声明文本写成 CSV 首行 `"# xxx", note` 双字段行——标准 CSV 解析器
7
+ (pandas 默认)会把它当表头或脏行(实测 agent 需专门跳过 '#' 行)。
8
+ 统一改为:CSV 保持纯数据表,声明与参数写入旁车文件 <path>.meta.json。
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import time
15
+
16
+
17
+ def ensure_parent_dir(path: str) -> str:
18
+ """确保导出路径的父目录存在,返回绝对路径。
19
+
20
+ 空 dirname(纯文件名 → 当前目录)时只返回绝对路径;
21
+ 目录已存在时为幂等 no-op。
22
+ """
23
+ abspath = os.path.abspath(path)
24
+ parent = os.path.dirname(abspath)
25
+ if parent:
26
+ os.makedirs(parent, exist_ok=True)
27
+ return abspath
28
+
29
+
30
+ def write_meta_sidecar(path: str, payload: dict) -> str | None:
31
+ """在导出文件旁写 <path>.meta.json(声明/参数/时间戳)。失败仅告警返回 None。
32
+
33
+ 调用方约定:CSV 本体只放纯数据;任何注释性说明(assumption/boundary 等)
34
+ 放进 payload,由本函数落盘。
35
+ """
36
+ meta_path = path + ".meta.json"
37
+ try:
38
+ data = dict(payload)
39
+ data.setdefault("generated_at", time.strftime("%Y-%m-%dT%H:%M:%S%z"))
40
+ with open(meta_path, "w", encoding="utf-8") as fh:
41
+ json.dump(data, fh, ensure_ascii=False, indent=1)
42
+ return meta_path
43
+ except Exception:
44
+ return None
@@ -25,6 +25,7 @@ WSL_DISTRO = os.environ.get("GEM_GAPSEQ_DISTRO", "Ubuntu-22.04")
25
25
  CONDA_SH = "/opt/miniforge3/etc/profile.d/conda.sh"
26
26
  GAPSEQ_ENV = "gapseq"
27
27
  WSL_WORK = "/opt/gem-gapseq-work"
28
+ SEQDB_DIR = f"/opt/miniforge3/envs/{GAPSEQ_ENV}/share/gapseq/dat/seq/Bacteria"
28
29
 
29
30
 
30
31
  def wsl_run(bash_cmd, timeout=300):
@@ -79,13 +80,34 @@ def probe():
79
80
  import re
80
81
  mm = re.search(r"gapseq version:\s*([\d.]+)", out)
81
82
  res["gapseq_version"] = mm.group(1) if mm else out[:80]
82
- seqdb = "/mnt/f/Datasets/gapseq/db"
83
+ # doall 实际读取 conda 环境内的 seq/Bacteria;旧探针却查 F: 备份并调用
84
+ # update-sequences -c,后者依赖 Zenodo 在线记录。网络解析失败会误报后端缺失。
85
+ # 本地元数据 + 三类序列文件同时检查,缺任何一项都不宣称可用。
83
86
  rc, out, err = wsl_run(
84
- f"source {CONDA_SH} && conda activate {GAPSEQ_ENV} && "
85
- f"gapseq update-sequences -t Bacteria -D {seqdb} -q -c 2>&1", 300)
86
- res["checks"]["seqdb"] = "up-to-date" in out.lower()
87
+ f"set -o pipefail; cat {SEQDB_DIR}/version_seqDB.json && "
88
+ f"find {SEQDB_DIR}/rev -type f | wc -l && "
89
+ f"find {SEQDB_DIR}/rxn -type f | wc -l && "
90
+ f"find {SEQDB_DIR}/unrev -type f | wc -l", 120)
91
+ try:
92
+ meta, offset = json.JSONDecoder().raw_decode(out)
93
+ counts = [int(value) for value in out[offset:].split()]
94
+ metadata_ok = (isinstance(meta, dict)
95
+ and isinstance(meta.get("version"), list)
96
+ and len(meta["version"]) == 1
97
+ and isinstance(meta["version"][0], str)
98
+ and bool(meta["version"][0])
99
+ and isinstance(meta.get("zenodoID"), list)
100
+ and len(meta["zenodoID"]) == 1
101
+ and isinstance(meta["zenodoID"][0], int)
102
+ and meta["zenodoID"][0] > 0)
103
+ res["checks"]["seqdb"] = rc == 0 and metadata_ok and len(counts) == 3 and all(n > 0 for n in counts)
104
+ if res["checks"]["seqdb"]:
105
+ res["seqdb_version"] = meta["version"][0]
106
+ res["seqdb_counts"] = dict(zip(("rev", "rxn", "unrev"), counts))
107
+ except (IndexError, ValueError, TypeError, json.JSONDecodeError):
108
+ res["checks"]["seqdb"] = False
87
109
  if not res["checks"]["seqdb"]:
88
- res["detail"] = f"序列库未注册(可能触发在线下载灾难): {out[:400]}"
110
+ res["detail"] = f"gapseq 实际序列库缺失或不完整: {(out or err)[:400]}"
89
111
  res["level"] = "DEGRADED"
90
112
  return res
91
113
  res["capable"] = True
@@ -248,4 +270,4 @@ if __name__ == "__main__":
248
270
  f"source {CONDA_SH} && conda activate {GAPSEQ_ENV} && gapseq -v 2>&1", 120)
249
271
  print(out[:300] or err[:300])
250
272
  else:
251
- print("unknown cmd")
273
+ print("unknown cmd")
package/python/gem_ops.py CHANGED
@@ -264,7 +264,7 @@ def op_l3_fix(args):
264
264
 
265
265
  # ---------------------------------------------------------------------------
266
266
  # op: fluxscan — 阶段A-M1 通量区间制(FVA 区间 + pFBA 点值 + 条件对区间分离判定)
267
- # 语义 bsp 锁稿:overlap = 求解器伪影禁止引用;判定公式见 fluxscan.judge_interval(单测锁定)
267
+ # 语义锁定:overlap = 求解器伪影禁止引用;判定公式见 fluxscan.judge_interval(单测锁定)
268
268
  # ---------------------------------------------------------------------------
269
269
  def op_fluxscan(args):
270
270
  from fluxscan import fluxscan, DEFAULT_FRACTION, DEFAULT_TOL
@@ -533,7 +533,7 @@ OPS["targets"] = op_targets
533
533
  # ---------------------------------------------------------------------------
534
534
  # op: precursor_scan — 阻塞前体分析
535
535
  # 「模型为什么不长」的结构级定位:逐前体做移除测试,找出卡住生长的前体。
536
- # 来源:2026-09-11 E2E 绕道归因(agent 手写该逻辑 6+ 次,无工具可用)。
536
+ # 来源:2026-09-11 实测归因(agent 手写该逻辑 6+ 次,无工具可用)。
537
537
  # 判据刻意用「相对判断」而非绝对可达性 —— 对可生长模型天然零误报。
538
538
  # ---------------------------------------------------------------------------
539
539
  def op_precursor_scan(args):
package/python/l3_fix.py CHANGED
@@ -28,7 +28,7 @@ EX_PREFIX = ("EX_", "DM_", "SK_")
28
28
  SOURCE_TAG = "gem-l3fix"
29
29
  NEW_RXN_SUFFIX = "_l3fix"
30
30
 
31
- # BiGG 基名 -> ModelSEED cpd 号(2026-08-29 本机以 C58 名字+公式+电荷逐一验证;
31
+ # BiGG 基名 -> ModelSEED cpd 号(2026-08-29 以 C58 名字+公式+电荷逐一验证;
32
32
  # 映射时仍做公式/电荷校验,不一致即弃用防静默污染化学计量)
33
33
  COFACTOR_BRIDGE = {
34
34
  "h2o": "00001", "atp": "00002", "nad": "00003", "nadh": "00004",
@@ -42,7 +42,7 @@ COFACTOR_BRIDGE = {
42
42
  COMP_MAP = {"c": "c0", "e": "e0", "p": "p0"} # BiGG 区室后缀 -> gapseq 区室 id
43
43
 
44
44
  WHITELIST_DIR = os.path.join(os.path.expanduser("~"), ".dsh", "dsh-bio-gem", "whitelist")
45
- # 本地白名单数据库(license 守则: 仅本机,不进 git/发布包;GEM_WHITELIST_DB_DIR 可覆盖)
45
+ # 本地白名单数据库(license 守则: 仅本地留存,不进 git/发布包;GEM_WHITELIST_DB_DIR 可覆盖)
46
46
  RXN_DB_DIR = os.environ.get("GEM_WHITELIST_DB_DIR", r"D:\Program\hermes\temp\gem_whitelist")
47
47
  DEFAULT_UNIVERSAL = r"D:\Program\hermes\temp\gem_universal\iML1515.xml"
48
48
 
package/python/ledger.py CHANGED
@@ -7,7 +7,7 @@
7
7
  # 幂等: 同 model+condition+type+content 哈希去重,重复运行不追加。
8
8
  # 完整性: 逐行 JSON 解析校验,损坏行跳过并在返回里报 corrupt_rows + 行号(不阻塞);
9
9
  # 写入失败只 WARN 不使主流程失败。update 重写文件但保留损坏行原样(不删行)。
10
- # 证据分级优先级(任务书锁定): EVIDENCE_literature > EVIDENCE_sequence > EVIDENCE_rule > EVIDENCE_math
10
+ # 证据分级优先级(口径锁定): EVIDENCE_literature > EVIDENCE_sequence > EVIDENCE_rule > EVIDENCE_math
11
11
  import os
12
12
  import re
13
13
  import sys
@@ -29,7 +29,7 @@ def _now_iso():
29
29
 
30
30
 
31
31
  def _content_hash(model, condition, rtype, content):
32
- # 阶段D-E2E P1:Windows 路径斜杠/大小写差异("F:/a" vs "F:\a")会使同一模型的
32
+ # 实测发现:Windows 路径斜杠/大小写差异("F:/a" vs "F:\a")会使同一模型的
33
33
  # 重复登记漏过去重——hash 前做 normcase+normpath 归一化(首登记的存储格式不变)。
34
34
  m = os.path.normcase(os.path.normpath(model)) if model else ""
35
35
  raw = "\x1f".join([m, condition or "", rtype or "", content or ""])
@@ -4,7 +4,7 @@
4
4
  # 纪律: 各工具完成后**仅当产物模型旁已有 card** 才向后追加;无卡不动(不凭空造卡)。
5
5
  # 兼容: build.py 旧卡(无 schema 字段)读取时即时迁移到 v2(新增字段缺失不报错)。
6
6
  # units: growth_rate 一律 1/h(比生长速率;biomass 反应 gDW 归一化口径,数值 = μ)。
7
- # 2026-09-21 由 mmol/gDW/h 演进为 1/h:数值不变、生物语义更准(外部评审 P0;schema 向后兼容,旧卡照读)。
7
+ # 2026-09-21 由 mmol/gDW/h 演进为 1/h:数值不变、生物语义更准(schema 向后兼容,旧卡照读)。
8
8
  import os
9
9
  import json
10
10
  import time
@@ -1,6 +1,6 @@
1
1
  # precursor_scan.py — dsh-bio-gem 阻塞前体分析(「模型为什么不长」的结构级定位)
2
2
  #
3
- # 来源(2026-09-11 E2E 绕道归因):agent 在不生长模型上反复手写「逐前体探测」逻辑
3
+ # 来源(2026-09-11 实测归因):agent 在不生长模型上反复手写「逐前体探测」逻辑
4
4
  # (6+ 次调用),本模块把它固化为工具。
5
5
  #
6
6
  # ⚠️ 判据设计——**刻意不用「绝对可达性」**:
package/python/quality.py CHANGED
@@ -14,6 +14,7 @@ from cobra.flux_analysis import fastcc, find_blocked_reactions
14
14
 
15
15
  from gapfind import expand_medium, resolve_medium
16
16
  from silentio import silent_read_sbml
17
+ from fsutil import ensure_parent_dir
17
18
 
18
19
 
19
20
  EX_PREFIXES = ("EX_", "DM_", "SK_")
@@ -357,6 +358,7 @@ def _connectivity(model):
357
358
 
358
359
  def _write_export_csv(path, records):
359
360
  """Write long-form complete lists; JSON samples remain deliberately capped."""
361
+ path = ensure_parent_dir(path)
360
362
  with open(path, "w", encoding="utf-8", newline="") as handle:
361
363
  writer = csv.DictWriter(handle, fieldnames=("check", "item_id", "detail"))
362
364
  writer.writeheader()
@@ -1,6 +1,6 @@
1
1
  # roundtrip_check.py — SBML 往返保真自检(Q2 工程质量件 A)
2
2
  # cobra 读 → write_sbml_model → 读回:断言反应/代谢物/基因数一致 + GPR 字符串精确一致
3
- # (GLM 经典暗坑:序列化静默丢 GPR——fbc v2 写入路径回归护栏;≥5 个复合 GPR 反应样本必查)
3
+ # (经典暗坑:序列化静默丢 GPR——fbc v2 写入路径回归护栏;≥5 个复合 GPR 反应样本必查)
4
4
  import os
5
5
  import sys
6
6
  import json
@@ -11,6 +11,7 @@ from cobra.util.solver import linear_reaction_coefficients
11
11
 
12
12
  from gapfind import expand_medium, resolve_medium
13
13
  from silentio import silent_read_sbml
14
+ from fsutil import ensure_parent_dir
14
15
 
15
16
 
16
17
  EX_PREFIXES = ("EX_", "DM_", "SK_")
@@ -219,6 +220,7 @@ def sample_fluxes(
219
220
  growth_summary = _describe(samples[growth_reaction.id].to_numpy())
220
221
 
221
222
  if export_csv:
223
+ export_csv = ensure_parent_dir(export_csv)
222
224
  samples.to_csv(export_csv, index=False)
223
225
 
224
226
  if growth_floor_fraction is None:
@@ -18,6 +18,7 @@ from silentio import silent_read_sbml
18
18
  from gapfind import build_ex_index, ex_index_is_boundary
19
19
  from essential_scan import setup_model_medium
20
20
  from sensitivity import find_biomass_gam
21
+ from fsutil import ensure_parent_dir, write_meta_sidecar
21
22
 
22
23
  EPS = 1e-6
23
24
  GROWTH_FRACTIONS = [0.25, 0.5, 0.75, 0.9, 0.99, 1.0]
@@ -151,10 +152,15 @@ def secretion(model_path, medium=None, fractions=None, export_csv=None,
151
152
 
152
153
 
153
154
  def _export_csv(path, rows, out):
155
+ """纯数据 CSV(标准表头)+ 旁车 meta。
156
+
157
+ 2026-10-05 修:boundary_note 移出 CSV 首行(原为 `"# boundary_note", note` 双字段行,
158
+ pandas/自动解析会当表头或脏行)→ 写入 <path>.meta.json(返回体 export_csv_meta)。
159
+ """
160
+ path = ensure_parent_dir(path)
154
161
  n = 0
155
162
  with open(path, "w", newline="", encoding="utf-8-sig") as f:
156
163
  w = csv.writer(f)
157
- w.writerow(["# boundary_note", out["boundary_note"]])
158
164
  w.writerow(["rxn", "met_id", "name", "feasible", "max_prod", "growth_at_max",
159
165
  "fraction", "prod"])
160
166
  for r in rows:
@@ -162,6 +168,17 @@ def _export_csv(path, rows, out):
162
168
  w.writerow([r["rxn"], r["met_id"], r["name"], int(r["feasible"]),
163
169
  r["max_prod"], r["growth_at_max"], e["fraction"], e["prod"]])
164
170
  n += 1
171
+ meta = write_meta_sidecar(path, {
172
+ "boundary_note": out.get("boundary_note"),
173
+ "model": out.get("model"),
174
+ "medium": out.get("medium"),
175
+ "medium_preset": out.get("medium_preset"),
176
+ "units": out.get("units"),
177
+ "growth_fractions": out.get("growth_fractions"),
178
+ "wt_growth": out.get("wt_growth"),
179
+ })
180
+ if meta:
181
+ out["export_csv_meta"] = meta
165
182
  return n
166
183
 
167
184
 
@@ -19,6 +19,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
19
19
  from silentio import silent_read_sbml
20
20
  from validate import parse_formula
21
21
  from essential_scan import setup_model_medium, scan_essentiality
22
+ from fsutil import ensure_parent_dir
22
23
 
23
24
  BIOMASS_SCALES = [0.75, 1.0, 1.25]
24
25
  GAM_GRID = [1, 5, 10, 20, 30, 40, 50]
@@ -216,7 +217,7 @@ def sensitivity(model_path, medium=None, biomass_scales=None, gam_grid=None,
216
217
  run_component_sensitivity=True, run_drift=True, top_n=10,
217
218
  export_csv=None, progress=None, baseline_check=None):
218
219
  """M2 主入口。baseline_check: 可选外部基线必需集(来自 essential_scan 直跑)——
219
- 基准组合与其做集合相等断言(任务书锚点)。返回完整结果 dict。"""
220
+ 基准组合与其做集合相等断言(基准锚点)。返回完整结果 dict。"""
220
221
  log = progress or (lambda s: sys.stderr.write(str(s) + "\n"))
221
222
  biomass_scales = biomass_scales or BIOMASS_SCALES
222
223
  gam_grid = gam_grid or GAM_GRID
@@ -421,6 +422,7 @@ def sensitivity(model_path, medium=None, biomass_scales=None, gam_grid=None,
421
422
 
422
423
 
423
424
  def _export_csv(path, grid, comp_rows, drift, stability):
425
+ path = ensure_parent_dir(path)
424
426
  rows = 0
425
427
  with open(path, "w", newline="", encoding="utf-8-sig") as f:
426
428
  w = csv.writer(f)
package/python/targets.py CHANGED
@@ -13,6 +13,8 @@ import json
13
13
 
14
14
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
15
15
 
16
+ from fsutil import ensure_parent_dir
17
+
16
18
  SCHEMA_FIELDS = ["target_id", "type", "genes", "met_ids", "condition", "rationale",
17
19
  "evidence_tier", "status", "growth_or_maxprod", "source", "exported_at"]
18
20
  TYPE_ORDER = ["essentiality", "synthetic_lethal", "secretion"]
@@ -112,6 +114,7 @@ def targets(model_path=None, types=None, condition=None, ledger_path=None,
112
114
  d = os.path.join(os.path.expanduser("~"), ".dsh", "dsh-bio-gem", "exports")
113
115
  os.makedirs(d, exist_ok=True)
114
116
  export_path = os.path.join(d, f"targets_{time.strftime('%Y%m%d_%H%M%S')}.{export_format}")
117
+ export_path = ensure_parent_dir(export_path)
115
118
  if export_format == "json":
116
119
  with open(export_path, "w", encoding="utf-8") as f:
117
120
  json.dump(out_rows, f, ensure_ascii=False, indent=1)
@@ -16,7 +16,7 @@ CORE_ELEMS = ("C", "N", "P", "S") # 硬核:不平衡必须 = 0
16
16
  REPORT_ELEMS = ("H", "O") # 报告不阻塞
17
17
  ELM_RE = re.compile(r"([A-Z][a-z]?)(\d*)")
18
18
 
19
- # 关卡注册器(GLM 建议 + 2026-08-29 采纳):未来加 G7 不改主流程
19
+ # 关卡注册器(2026-08-29 采纳):未来加 G7 不改主流程
20
20
  GATE_REGISTRY = {}
21
21
 
22
22
 
@@ -107,7 +107,7 @@ class Validator:
107
107
  frac = 1.0 - n_bad / checked if checked else 0.0
108
108
  status = "PASS" if n_bad == 0 else ("WARN" if frac >= 0.85 else "FAIL")
109
109
 
110
- # 2026-09-11 修复(agent 在真实 E2E 中发现并指出):**公式覆盖率是 PASS 结论的
110
+ # 2026-09-11 修复(agent 在真实会话中发现并指出):**公式覆盖率是 PASS 结论的
111
111
  # 作用域上界** —— 覆盖率低时「无反应不平衡」只说明被检查的那部分没问题,不能
112
112
  # 外推为整体 PASS。实测 iNX1344_v3:覆盖率 68.35% 却判 PASS,agent 据此指出
113
113
  # 「g2 的 PASS 是假阳性,因为它只检查了有 formula 的 68.35% 代谢物」。
@@ -210,7 +210,7 @@ class Validator:
210
210
  补洞后必跑(context.post_gapfill 时不再跳过)。
211
211
  P1-5 修复(2026-08-31 LBA9402 会话实测):CarveMe 模型 ATP id 为 M_atp_c,
212
212
  旧匹配只看 atp_c/cpd00002_c0 -> 误 SKIP「未找到 ATP」——扩展命名模式 + SKIP 时列出尝试模式与模型内候选。
213
- P2-9 修复(2026-09-10 iNX1344 E2E 实测):MetaCyc/BioCyc 导出模型 ATP 为 M00002_c
213
+ P2-9 修复(2026-09-10 iNX1344 实测):MetaCyc/BioCyc 导出模型 ATP 为 M00002_c
214
214
  (name='ATP',formula 为去质子化变体),仍不在模式表内 → 二次误 SKIP(G6 直接失效)。
215
215
  改为三级通用解析:id 模式 → name 匹配 → formula 匹配,跨 ID 体系自适应。"""
216
216
  m = self.m
@@ -61,15 +61,15 @@ language: mixed
61
61
 
62
62
  ## 硬规则(实测,违反会拿错结果)
63
63
 
64
- 0. **外部事实断言必须带证据**(2026-08-29 采纳 GLM 教训):凡涉及"某物种/文献/工具是否存在、是否已发表"等**模型外事实断言**,agent 必须真实检索并附来源(如 PubMed/PMC 链接);无法检索时降级表述为"未验证假设 [假设]",不得由记忆下断言。此项与 EVIDENCE 分级同位:**事实断言也有证据分级**(检索+来源=high;仅记忆=unverified)。
64
+ 0. **外部事实断言必须带证据**(2026-08-29 采纳):凡涉及"某物种/文献/工具是否存在、是否已发表"等**模型外事实断言**,agent 必须真实检索并附来源(如 PubMed/PMC 链接);无法检索时降级表述为"未验证假设 [假设]",不得由记忆下断言。此项与 EVIDENCE 分级同位:**事实断言也有证据分级**(检索+来源=high;仅记忆=unverified)。
65
65
  1. **培养基一律用自然名成分**:`{"D-Glucose": -5, "NH3": -10, "O2": -12.5, ...}`。插件跨引擎自动解析(gapseq EX_cpdXXXXXX_e0 与 CarveMe/BiGG EX_glc__D_e 命名空间不同,硬编码 ID 会失配)。
66
66
  2. **数字必须来自工具输出**:agent 报告生长值/基因数/复制子数时引用工具 result,不要凭模型知识猜测(防幻觉铁律)。**生长值单位是 mmol/gDW/h(FBA 通量),不是 h⁻¹/μ**——汇报时不要换算成比生长速率。
67
67
  3. **CarveMe 模型的介质边界**:`gem_build` 默认在 M9 最小培养基验证(生长阳性);**AB 等自定义培养基若 FAIL 且 gapfind 判定 L3(内部路径),规则补洞修不了**——此时如实报告「模型可用介质=M9,目标介质需要 L3 级文献补充」,绝不要假装自愈。
68
68
  4. **缺口 90% 是 L1/L2**(缺交换/转运,非缺分解酶)——先诊断再补,别直接加反应。
69
69
  5. **多复制子正常**:质粒/多染色体基因都在同一模型内(C58 实测 4 复制子:967/434/65/18 基因分布于 ChrⅠ/ChrⅡ/pTi/pAt)。
70
- 6. **通量区间制(阶段A-M4 硬规则)**:无区间不点数——任何条件间通量对比必须消费 `gem_fluxscan` 的区间分离判定;两条件区间分离才是解空间无关硬结论(a_higher/b_higher);overlap 反应的任何点值 diff 必须标注"伪影,禁止引用"。单点 FBA 生长/通量值(gem_validate/gem_phenotype/gem_essentiality 等输出)只是该条件下的一个解,跨条件直接 diff 是求解器伪影。
71
- 7. **模型指代消歧(阶段D-E2E 硬规则)**:用户说"我们的 C58 模型"等未给路径的指代时——①先查预测账本(`gem_ledger query` 按 model 前缀)与项目档案中已登记的 canonical 路径;②同一名字存在多个模型(如 gapseq 原版 vs carveme 重建)时,必需性/富集等锚点敏感分析必须用登记在案的原始版本或向用户确认,并说明选择了哪个文件;③不同引擎重建的同一物种是不同模型(基因数/必需集都不同),不得混用结论。
72
- 8. **文件出处/来源推断必须标注 [推断](阶段D-P2 硬规则)**:凡非本次会话工具 result 直接给出、而是凭文件名/目录布局/常识推断的出处(如"这个 faa 是 gem_annotate 产出的"),汇报时必须标注 [推断];可验证时先验证再陈述。例证:阶段 D 轮 1 agent 称 faa 为"前面流程 gem_annotate 产出"——事实上正确但出自文件名推断而非会话验证。
70
+ 6. **通量区间制(硬规则)**:无区间不点数——任何条件间通量对比必须消费 `gem_fluxscan` 的区间分离判定;两条件区间分离才是解空间无关硬结论(a_higher/b_higher);overlap 反应的任何点值 diff 必须标注"伪影,禁止引用"。单点 FBA 生长/通量值(gem_validate/gem_phenotype/gem_essentiality 等输出)只是该条件下的一个解,跨条件直接 diff 是求解器伪影。
71
+ 7. **模型指代消歧(实测硬规则)**:用户说"我们的 C58 模型"等未给路径的指代时——①先查预测账本(`gem_ledger query` 按 model 前缀)与项目档案中已登记的 canonical 路径;②同一名字存在多个模型(如 gapseq 原版 vs carveme 重建)时,必需性/富集等锚点敏感分析必须用登记在案的原始版本或向用户确认,并说明选择了哪个文件;③不同引擎重建的同一物种是不同模型(基因数/必需集都不同),不得混用结论。
72
+ 8. **文件出处/来源推断必须标注 [推断](硬规则)**:凡非本次会话工具 result 直接给出、而是凭文件名/目录布局/常识推断的出处(如"这个 faa 是 gem_annotate 产出的"),汇报时必须标注 [推断];可验证时先验证再陈述。例证:一次实测中 agent 称 faa 为"前面流程 gem_annotate 产出"——事实上正确但出自文件名推断而非会话验证。
73
73
 
74
74
  ## C58 回归锚(验证工具是否正常)
75
75
 
@@ -1,6 +1,6 @@
1
1
  // dsh-bio-gem — capabilities 单源(single source of truth for tool manifest & capability metadata)
2
2
  //
3
- // 目的(外部评审共识,2026-09-21 裁决 §3.3):
3
+ // 目的(2026-09-21 设计共识):
4
4
  // 1. 工具清单/能力分级/成本与副作用元数据集中一处,供 integration API(/v1/capabilities)
5
5
  // 与 genie 宿主侧动态消费——消灭「工具数变化需同步 ≥12 处」的手工漂移。
6
6
  // 2. 校验脚本 scripts/check-capabilities.mjs 强制本 MANIFEST 与 tools.js 真实注册集合一致;
@@ -225,7 +225,7 @@ async function probeGapseqEnvironment({ isWindows, distro, runner }) {
225
225
  return { available: false, detail: 'gapseq 仅支持 Windows WSL 的只读探测。' }
226
226
  }
227
227
  // 快速预检:先确认目标发行版存在(wsl.exe -l -q 亚秒级),避免发行版缺失时
228
- // 白等一次 bash 长命令直到超时刹车(真实运行时实测:本机 bash 启动即 ~3s)。
228
+ // 白等一次 bash 长命令直到超时刹车(实测:bash 启动即 ~3s)。
229
229
  try {
230
230
  const listed = await runner('wsl.exe', ['-l', '-q'])
231
231
  const names = String(listed?.stdout ?? '')
package/src/python.js CHANGED
@@ -10,7 +10,7 @@ import os from 'node:os'
10
10
  const PYTHON_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'python')
11
11
 
12
12
  /**
13
- * 候选解释器,按优先级(**通用化,不写死任何本机路径**):
13
+ * 候选解释器,按优先级(**通用化,不写死任何机器特定路径**):
14
14
  *
15
15
  * 1. `GEM_PYTHON` — 用户显式指定,最高优先级
16
16
  * 2. 宿主插件自举环境 — `$DSH_HOME/dsh-bio-genie/python-env`
@@ -125,7 +125,7 @@ export function callGem(op, args, opts = {}) {
125
125
  return new Promise((resolve, reject) => {
126
126
  const py = pythonExe()
127
127
  const script = join(PYTHON_DIR, 'gem_ops.py')
128
- const cp = spawn(py, ['-I', script], { cwd: PYTHON_DIR, windowsHide: true })
128
+ const cp = spawn(py, ['-I', script], { cwd: opts.cwd || PYTHON_DIR, windowsHide: true })
129
129
  let out = ''
130
130
  let err = ''
131
131
  cp.stdout.on('data', (d) => { out += d })
package/src/tools.js CHANGED
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'
8
8
  import { spawn } from 'node:child_process'
9
9
  import { callGem, stampProvenance } from './python.js'
10
10
  import { startBuild, jobStatus } from './jobs.js'
11
+ import { resolveWorkdir } from './workdir.js'
11
12
 
12
13
  /** 校验输入存在(绝对路径或用户给定路径)。 */
13
14
  function requirePath(v, label) {
@@ -27,8 +28,8 @@ function gemTool(opts) {
27
28
  schema: { type: 'object', additionalProperties: true },
28
29
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
29
30
  },
30
- async execute(args) {
31
- return callGem(opts.op, args, { timeoutMs: opts.timeoutMs ?? 300_000 })
31
+ async execute(args, exec) {
32
+ return callGem(opts.op, args, { timeoutMs: opts.timeoutMs ?? 300_000, cwd: resolveWorkdir(exec) })
32
33
  },
33
34
  })
34
35
  }
@@ -41,7 +42,7 @@ function buildTool() {
41
42
  '从细菌全基因组构建基因组尺度代谢模型(GEM)。' +
42
43
  'engine=carveme(默认,纯 Windows 快):输入蛋白 FASTA(*.faa),CarveMe -g M9 gapfill → M9 介质验证 → 目标介质 L1/L2 补洞,' +
43
44
  '约 1-2 分钟(C58 实测 70s)。' +
44
- 'engine=gapseq(质量档,需本机 WSL2 gapseq 环境):输入核苷酸 FASTA(*.fna),WSL 桥 gapseq doall → 模型拷回 → 目标介质验证,' +
45
+ 'engine=gapseq(质量档,需 WSL2 gapseq 环境):输入核苷酸 FASTA(*.fna),WSL 桥 gapseq doall → 模型拷回 → 目标介质验证,' +
45
46
  '约 30-60 分钟(后台进度日志旁观,不要误判超时)。' +
46
47
  '输出标准 SBML(fbc v2)+ 模型卡(sidecar JSON:引擎版本/验证结果/补洞记录)。' +
47
48
  '生长值为比生长速率口径(1/h,biomass 归一化);通量为单点 FBA 值(mmol/gDW/h,非硬结论)。条件间通量对比用 gem_fluxscan(区间制)。' +
@@ -317,9 +318,9 @@ export function registerTools(ctx) {
317
318
  schema: { type: 'object', additionalProperties: true },
318
319
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
319
320
  },
320
- async execute(args) {
321
+ async execute(args, exec) {
321
322
  const op = args.action === 'apply' ? 'biomass_apply' : 'biomass_inspect'
322
- return callGem(op, args, { timeoutMs: 900_000 })
323
+ return callGem(op, args, { timeoutMs: 900_000, cwd: resolveWorkdir(exec) })
323
324
  },
324
325
  })))
325
326
 
@@ -416,7 +417,7 @@ export function registerTools(ctx) {
416
417
  name: 'gem_benchmark',
417
418
  description:
418
419
  '通用基准对比(benchmark):任何两个代谢模型跑规范对比表,产出论文级对比。model_a/model_b 支持本地 SBML 绝对路径' +
419
- '或 "bigg:<model_id>" URI(如 bigg:iML1515,BiGG 静态库下载到 ~/.dsh/dsh-bio-gem/models/,直连失败自动走本机代理,下载后缓存)。' +
420
+ '或 "bigg:<model_id>" URI(如 bigg:iML1515,BiGG 静态库下载到 ~/.dsh/dsh-bio-gem/models/,直连失败自动走系统代理,下载后缓存)。' +
420
421
  '输出:ID 体系探测 / 六道关卡 G1-G6 逐项并列 / 声明介质生长(含介质层两级策略——无 EX_ 层的模型自动回退 ' +
421
422
  'boundary 单代谢物反应解析,boundary_style 标注)/ biomass 可行性探针(逐组分净产测试,结构性断供清单)/ ' +
422
423
  '必需性对比(复用 essential_scan;任一侧 wt<=EPS 判退化只报结构信息不做垃圾对比,基因映射尽力而为如实报覆盖率,' +
package/src/workdir.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * dsh-bio-gem — 工作区解析(工具执行的工作目录)
3
+ *
4
+ * 优先级:
5
+ * 1. 会话工作区 `exec.agent.session.header.cwd`(与 dsh 内置 fs 工具一致:
6
+ * 每个会话操作自己的工作区,而不是服务器启动目录)
7
+ * 2. 保底工作区 `~/.dsh/sessions/default`(自动创建)
8
+ *
9
+ * 背景(2026-10-04 实测):python 子进程此前固定 cwd=插件 python 目录,
10
+ * 工具参数里的相对路径(如 gem_fluxscan 的 export_csv)因此解析到插件目录、
11
+ * 写入失败(agent 被迫改用绝对路径自愈)。本模块对齐 dsh-bio-genie 的
12
+ * workdir 语义:相对路径基于会话工作区解析。
13
+ *
14
+ * 说明:dsh 对未显式指定 cwd 的会话会把 header.cwd 填充为服务器启动目录
15
+ * (process.cwd()),此时视为「未指定工作区」,走保底链。
16
+ *
17
+ * @module dsh-bio-gem/workdir
18
+ */
19
+ import { existsSync, mkdirSync } from 'node:fs'
20
+ import { homedir } from 'node:os'
21
+ import { join } from 'node:path'
22
+
23
+ /** 保底工作区目录(自动创建;仅无会话 cwd 时使用)。 */
24
+ export function fallbackWorkspace() {
25
+ return join(homedir(), '.dsh', 'sessions', 'default')
26
+ }
27
+
28
+ /**
29
+ * 会话工作区目录。
30
+ * @param {object} [exec] 工具执行上下文(defineTool execute 的第二参数)。
31
+ * @returns {string|undefined} 会话 cwd;未指定或不可读时返回 undefined。
32
+ */
33
+ export function sessionWorkspace(exec) {
34
+ const cwd = exec?.agent?.session?.header?.cwd
35
+ if (typeof cwd !== 'string' || cwd.length === 0) return undefined
36
+ if (!existsSync(cwd)) return undefined
37
+ return cwd
38
+ }
39
+
40
+ /** 幂等确保目录存在(失败静默——由调用方决定回退)。 */
41
+ function ensureDir(dir) {
42
+ if (existsSync(dir)) return true
43
+ try {
44
+ mkdirSync(dir, { recursive: true })
45
+ return existsSync(dir)
46
+ } catch {
47
+ return false
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 解析一次工具调用的工作目录(供 python 子进程 cwd 使用)。
53
+ * @param {object} [exec] 工具执行上下文。
54
+ * @returns {string} 解析后的绝对路径(保底可用)。
55
+ */
56
+ export function resolveWorkdir(exec) {
57
+ const serverCwd = process.cwd()
58
+ const rawSessionCwd = sessionWorkspace(exec)
59
+ const sessionCwd = rawSessionCwd && rawSessionCwd !== serverCwd ? rawSessionCwd : undefined
60
+ if (sessionCwd) {
61
+ ensureDir(sessionCwd)
62
+ return sessionCwd
63
+ }
64
+ const fb = fallbackWorkspace()
65
+ ensureDir(fb)
66
+ return fb
67
+ }
@@ -1,37 +0,0 @@
1
- # DECISIONS-2026-08-29 — GLM 两轮评审裁决与路线固化
2
-
3
- > 第三方 LLM(GLM)对纯 Windows 路线功能/架构的两轮评审;按"以实测为准、独立裁决"原则的采纳记录。
4
-
5
- ## 第一轮(功能拓展+优化)裁决
6
-
7
- | 建议 | 裁决 | 备注 |
8
- |---|---|---|
9
- | P0 pyrodigal 注释(兑现"基因组→模型"承诺)| ✅ 原则采纳 | **待用户解锁**(用户已说暂不补注释步骤);落地=官方注释优先+pyrodigal 兜底+meta 模式(<100kb 强制)|
10
- | P0 G5 全量必需性扫描(FVA 预筛)| ✅ 采纳 | 排入路线;模型卡加必需基因章节(证据着色)|
11
- | P1 gapseq 白名单 + L3 MILP 组合拳 | ✅ 采纳 | 白名单仅本地基准(license 见下);L3 候选池=自建同源映射(license 干净)|
12
- | P1 biomass 精修(参考对照)| ✅ 采纳但后置 | 级联重置验证基线风险;做成可选 --biomass-profile + before/after 三联对照 |
13
- | P2 accession 下载 / P3 表型 schema / P3 差分 / P4 交叉 / P4 可视化 | ✅ 按序采纳 | 可视化=Escher JSON 导出(不做内嵌渲染)|
14
- | 建模卡 RPC/架构优化 | ✅ 见第二轮 | — |
15
- | 五道关卡→关卡注册器 + G6 ATP 泄漏 | ✅ **已实现** | G6 实测 C58 PASS(flux 0.0)|
16
- | 防过补第五闸门(全局预算 <5%)| ✅ 采纳 | 表型回填/长期迭代防温水煮青蛙 |
17
- | 证据分级枚举(sequence/literature/rule/math)| ✅ 采纳 | 白名单+MILP 落地后填充 model card |
18
- | SBML 往返保真测试 | ✅ 采纳 | 进 smoke |
19
-
20
- ## 第二轮(7 问)裁决
21
-
22
- 1. **注释质量**:✅ 官方注释优先+pyrodigal 兜底;验证协议(预测蛋白覆盖率 85-92%、反应 delta<3%、生长差<5%)待解锁后实施。
23
- 2. **license**:✅ **分离消费**——seqDB 仅本地基准不分发;自建 UniProt→EC→BiGG 作可分发白名单/L3 候选池(license 干净);止损 2 周;GPLv3 环绕注意。
24
- 3. **介质的共享形态**:✅ 单一 Python 来源 + RPC/CLI 子命令(不做双语言双包);schema v2 采纳,**修正 units=mmol/gDW/h(非 1/h)**。
25
- 4. **modelseedpy**:✅ P2 交叉分歧清单主体(非并列引擎);BiGG↔SEED 桥复用 MET_ALIAS 模式。
26
- 5. **G6**:✅ 已实现(ATP demand 全关最大化 >0.01 WARN);补洞后必跑;注册器已内置。
27
- 6. **证据呈现**:✅ 分区+徽标+行着色+`--strict-evidence` 开关(不隐藏不全表高亮)。
28
- 7. **排期冲突**:✅ 衔接前置(schema v2/media_resolve RPC)归入 gem 内部工作流;genie 解锁即消费,gem 主线不阻塞。
29
-
30
- ## 落地状态
31
-
32
- - [x] G6 ATP 泄漏关卡 + 关卡注册器(validate.py;C58 PASS 0.0)
33
- - [ ] schema v2(supported_mediums/verified_phenotypes/model_lineage/evidence_summary)——unit 已定为 mmol/gDW/h
34
- - [ ] G5 全量(FVA 预筛)——路线 P0
35
- - [ ] 白名单 + L3 MILP + 证据分级——路线 P1(license 结论已定:自建映射)
36
- - [ ] 注释步骤(pyrodigal)——**待用户解锁**
37
- - [ ] genie 衔接(RPC 子命令)——待用户解锁
@@ -1,76 +0,0 @@
1
- # DECISIONS — 2026-09-21 升级批次(外部评审裁决后 Phase 1)
2
-
3
- > 本批决策依据:`D:\Program\dsh-plug-develop\21-gem升级-裁决与实施方案.md`(GPT/DS 双评审 + 本机 8 项实测核验)。
4
- > 记录本批次的**语义演进与架构决策**,供下游与后续批次参考。
5
-
6
- ## D1. growth 单位演进:mmol/gDW/h → 1/h(数值不变)
7
-
8
- **决策**:growth / growth_rate / wt_growth / before-after 生长 等**比生长量**字段的单位标注由
9
- `mmol/gDW/h` 改为 `1/h`;反应通量类保持 `mmol/gDW/h`。
10
-
11
- **依据**(实测 + 社区约定):
12
- - C58 的 bio1(Bacterial Gram-negative biomass reaction)产物系数 = 1.0 —— biomass 反应已按
13
- **1 gDW 归一化**;此时其通量数值 = 比生长速率 μ(h⁻¹),这是 COBRA 社区的标准解读
14
- (biomass flux through a normalized biomass reaction equals the specific growth rate)。
15
- - 外部评审(GPT)指出原标注 `mmol/gDW/h` 对 biomass 反应在语义上不准确 → 列为 P0。
16
- - straindesign 官方文档同样以 `growth rates above 0.5/h` 表述(旁证)。
17
-
18
- **边界**:对**未归一化**的 biomass 反应(产物系数 ≠ 1)growth 应以 mmol/gDW/h 解读——见
19
- validate 的 units.note 表述。数值本身在任何情况下不变,本决策只纠标注。
20
-
21
- **影响面**:python/ 12 个模块(23 处)+ src/tools.js(16 处口径文案统一)+ 本文档;
22
- `model_card.GROWTH_UNITS` 同步演进(旧卡兼容:growth_units 字段照读,数值口径不变)。
23
-
24
- ## D2. 能力单源(capabilities.json → /v1/capabilities → 宿主动态消费)
25
-
26
- **决策**:工具清单与能力元数据(cost_class / network / mutability / requires)以
27
- `src/capabilities.js` 的 `TOOLS_MANIFEST` 为**唯一事实源**:
28
- - integration API 新增 `GET /v1/capabilities`(features 声明 `capabilities`);
29
- - 宿主 dsh-bio-genie 的 `handleDomainRequest` 在对方声明该 feature 时拉取并透传
30
- (失败静默降级到静态清单 `GEM_TOOLS`——它已降级为 fallback 视图);
31
- - 机器门:`test/check-capabilities.mjs`(manifest ↔ 真实注册一致)+
32
- `test/check-counts.mjs`(文档计数 ↔ manifest 一致)。
33
-
34
- **背景**:原「工具数变化需同步 ≥12 处」是两评审共同指出的工程债;本决策把它变成
35
- 「单源 + 两道机器门」,文档数字仍手写但有门兜底。
36
-
37
- ## D3. CarveMe 零手动部署(bootstrap_carveme.py)
38
-
39
- **决策**:`gem_build(engine=carveme)` 首次调用自动完成运行时部署(幂等):
40
- uv venv(uv 探测链:`GEM_UV` → genie 自举 uv → PATH)→ `uv pip install carveme` →
41
- 下载 GitHub 官方 diamond 二进制(**固定 v2.2.8**,3.4MB)→ deep smoke → manifest 记录。
42
-
43
- **关键设计**:
44
- - **快速路径**:`carve.exe + diamond.exe + manifest.json` 齐备时秒过(实测 0.34s,不做子进程冒烟);
45
- - 部署与复验才做 deep smoke(实测全量部署 45s:venv 31s + diamond 下载数秒 + 冒烟);
46
- - 下载通道:直连 → 环境代理(HTTPS_PROXY/HTTP_PROXY)→ 失败给可执行指引(含手动放置路径);
47
- - 这就是 P0「装完插件 ≠ 构建可用」的根治:契约要求用户零手动安装。
48
-
49
- ## D4. gem_sample 的边界设计(全空间 vs 受限)
50
-
51
- **决策**:默认采样**全 feasible space**(ACHR),但 `boundary` 字段**强制输出**边界声明;
52
- 近最优生长状态必须显式传 `growth_floor_fraction`(如 0.9)。
53
-
54
- **依据**(本机实测):C58 全空间采样 bio1 max ≈ 0.017 vs FBA 最优 0.7134(差 2 个数量级)——
55
- 全空间均匀分布 ≠ 生物学上有意义的活跃状态;不声明边界就是「做了但没用」的典型。
56
-
57
- **Windows 约束**:默认 ACHR(无多进程依赖,实测 init ~172s、采样秒级);OptGP 在 Windows
58
- 下实验性——不可用时**显式安全拒绝**(不触发多进程陷阱)。
59
-
60
- ## D5. quality_index(gem-qi-v1)不是 MEMOTE 分数
61
-
62
- **决策**:`gem_quality` 输出 `quality_index`(0-100 启发式聚合)+ 分项 raw_metrics +
63
- failed_checks + not_assessable_checks;**禁止**作为单一质量结论引用(notes 内置声明)。
64
-
65
- **依据**:两评审共识——MEMOTE 的价值在其可分解测试体系;聚合分容易被误用。
66
- 评分规则(写死在 `python/quality.py` 常量):见 WEIGHTS / 阈值常量与 note 输出。
67
- **不安装 MEMOTE 整体**(PyPI 0.17.0 仅声明兼容到 Py3.11;本机 Py3.13 风险)。
68
-
69
- ## D6. 本批新增工具(2)
70
-
71
- | 工具 | capability | cost_class | 说明 |
72
- |---|---|---|---|
73
- | `gem_quality` | `gem.validate.quality-report` | medium | 质量审计(blocked/环路/平衡/孤儿/覆盖/连通性)|
74
- | `gem_sample` | `gem.analysis.sampling` | heavy | 通量空间采样(ACHR/OptGP;边界声明)|
75
-
76
- 工具数 21 → 23;op 数 21 → 23。两门(check-capabilities / check-counts)同步更新。
@@ -1,67 +0,0 @@
1
- # DECISIONS — 阶段 A:可信度内核六批次(2026-08-30)
2
-
3
- > 记录阶段 A(M1 fluxscan / M2 sensitivity / M3 ledger / M4 消费方升级 / M5 iNX1344 前哨 / M6 基准与文档)的每个设计决策与依据。背景:L1/L2 已做到行业上游,短板在 L3(通量分布)/L4(定量速率)呈现——单点 FBA 通量取决于求解器顶点,跨条件点值 diff 是伪影。本阶段让 L3 通量一律区间制、L2 必需性变稳定性谱、所有预测进账本可追踪。
4
-
5
- ## M1 gem_fluxscan(通量区间制)
6
-
7
- | # | 决策 | 依据 |
8
- |---|---|---|
9
- | M1-1 | **语义按 bsp 锁稿一字不改**(fva_min/fva_max/pfba 三件套;区间分离判定公式;overlap=伪影禁止引用) | 任务书明确"语义已锁定,建议写报告由 bsp 裁决" |
10
- | M1-2 | **fraction_of_optimum 默认 0.9999 而非 1.0** | 1.0 时数值噪声会使个别区间变空/变窄(求解器在最优面顶点的抖动);0.9999 留 0.01% 最优性余量换区间稳健性,对生长影响 <0.01% |
11
- | M1-3 | **tolerance 默认 1e-6 mmol/gDW/h** | 与必需性判定 EPS 同量级;区间间隙须 > 容差才判分离——防止把求解器噪声当成"硬结论"。判定公式 `ua+tol<lb → b_higher; ub+tol<la → a_higher; 否则 overlap` |
12
- | M1-4 | **判定与输出/CSV 同用 6 位舍入值**(而非 raw 值判定、展示舍入) | 自洽性:用户可从展示值直接复核判定。6 位舍入误差(≤5e-7)< 容差(1e-6),不改变科学结论。若 bsp 偏好 raw 判定,改一行 |
13
- | M1-5 | **每 condition 独立 `silent_read_sbml` 重读模型**,不用 cobra Model 深拷贝 | 深拷贝共享底层求解器状态有交叉污染风险;读模仅 4s,22 组合级成本可忽略(M2 实测 fresh 模型还规避了 GLPK 病态停摆,见 M2-5) |
14
- | M1-6 | **`reactions` 子集仅收窄 comparisons/summary 且两者同口径**;FVA/pFBA 恒全模型 | 任务书明确"禁止 summary 全模型 vs comparisons 子集矛盾";FVA 全模型算一次,子集只是输出过滤,无性能收益可图 |
15
- | M1-7 | **only_diff 只过滤 comparisons,summary 仍全口径计数** | summary 的 total/overlapping 描述整个 scope 才有意义;任务书未明说,按"summary 与 scope 同口径"原则实现并报告 |
16
- | M1-8 | **conditions.name 唯一校验(重复直接报错)** | 任务书锁定要求;pairs 引用条件名,重名会使引用歧义 |
17
- | M1-9 | **任务书容差单测第 5 组与锁定公式矛盾——按公式实现,差异上报** | 公式 `ua+tol<lb` 要求间隙>tol;样例 `[1.0,1.0000005] vs [1.000001,2.0]` 间隙 5e-7 < tol 1e-6,公式判 overlap 而任务书写"分离"。公式是保守科学语义(防噪声造假硬结论),判"分离"需要反语义的 `ua<lb+tol`(会让"接触"样例变 b_higher,与第 2 组矛盾)。已按公式实现并双容差断言(1e-6→overlap;1e-7→b_higher 证明容差机制生效)。**待 bsp 裁决** |
18
-
19
- ## M2 gem_sensitivity(结构性灵敏度)
20
-
21
- | # | 决策 | 依据 |
22
- |---|---|---|
23
- | M2-1 | **GAM 载体探索结论:C58 载体在 biomass 方程 bio1 内部**(81 组分中 ATP 水解 stub 五元组:ATP -40.165476 / H2O -34.922907 / ADP +40.0 / Pi +39.992706 / H+ +40.0,GAM_ORIG=40.0 以 ADP 系数为净水电解量),非独立 ATPM(全模型无 lb>0 维持反应) | 公式级(元素组成)识别跨命名空间通用(ModelSEED/BiGG/MetaCyc 都适用),不依赖 id 约定 |
24
- | M2-2 | **正交化实现**:biomass 组分缩放排除 5 元 GAM stub 与 Biomass 产物(连接 objective 的汇组分);GAM 网格只动 stub(等比缩放 X/GAM_ORIG,保留 stub 内部记账不对称如 ATP-40.165 vs ADP+40) | 任务书要求"biomass 变体与 GAM 网格正交不混淆";等比缩放保持 gapseq stub 的内部比例,可逆、可解释 |
25
- | M2-3 | **biomass 组分缩放语义 = 75 个原料/副产系数 ×f,Biomass 产物系数保持 +1.0** | 若把产物也缩放,growth 恰好等比缩放(平凡结论);保持产物=1 才是真正的"组成不确定→生长响应"问题,且与 GAM 轴正交 |
26
- | M2-4 | **网格 = 3 biomass 档 × 7 GAM 档 = 21 扫点 + 1 基准(不扰动)= 22 组合**;每组合 wt growth + 必需性重扫(复用 essential_scan 拆出的 `setup_model_medium`/`scan_essentiality`) | 任务书锁定;基准与 essential_scan 完全同参数 → **实测精确复现 155(155 vs 155 EXACT MATCH)**;(1.0, gam=40) 扫点与基准互证(0.519981/155 双一致,stub×40/40=1.0 无扰动的数学必然) |
27
- | M2-5 | **GLPK 病态停摆护栏 LP_TIMEOUT_S=30**(optlang configuration.timeout)+ 扰动后不生长的组分跳过必需性漂移 | 实测两次全量扫描卡死于特定扰动 LP(GLPK 单纯形停摆);30s 上限对正常 <0.05s 的 LP 是 600× 余量,永不误伤。刚性组分对(ACP↔apo-ACP 等 7 个)任一 ±25% 使 biomass 方程不可满足 → growth=0 → 必需性判定退化为"全候选必需",漂移无意义 |
28
- | M2-6 | **CSV 导出容错**(跳过行无 essential_count 的 KeyError 曾致 45min 全量在导出步崩溃) | 教训:长任务的所有输出步骤必须 try/except 或缺省键兼容,不能让已完成 95% 的计算死在最后一行 |
29
- | M2-7 | **稳定性三分类**:always(22/22 组合必需)/ conditionally / never;基准 155 ⊆ always∪conditional 硬断言 | 任务书锁定断言(基准在网格内故必成立);C58 实测 always=155 / conditional=0 / never=929——必需集对该网格完全鲁棒 |
30
- | M2-8 | **模型卡 robustness 章节 = schema v3**(v2 + robustness 字段,只增;`_ensure_v2` 接受 v3 不降级);无 card 不造卡 | 向后兼容:读卡方按 JSON 字段访问,v2 消费方忽略 robustness 即可;C58/C58_P1 无卡 → card_robustness_written=false 如实报告 |
31
-
32
- ## M3 gem_ledger(prediction ledger)
33
-
34
- | # | 决策 | 依据 |
35
- |---|---|---|
36
- | M3-1 | **追加式 JSONL**(`~/.dsh/dsh-bio-gem/ledger/predictions.jsonl`,目录不存在则创建)+ 幂等哈希去重(model+condition+type+content 的 sha256) | 任务书锁定;幂等使"每次扫描自动登记"可安全重复执行(实测 155 条复跑 appended=0/skipped=155) |
37
- | M3-2 | **evidence_tier 取支撑反应 evidence 集合中最高者,优先级 literature > sequence > rule > math;无标注默认 EVIDENCE_rule 并注明** | 任务书锁定优先级。注意与插件既有分级的表述差异:l3_fix 语境里 sequence(白名单直接对应)> math;ledger 的序以任务书为准,且 C58 实测全部默认 EVIDENCE_rule(l3_fix 数学连接反应无 GPR,不落入必需基因的支撑集),差异未实际生效 |
38
- | M3-3 | **update 重写文件但逐行保留(坏行原样写回,不删行)** | 任务书"全部只读/追加/更新,不删行"+ 完整性要求;重写时先按行解析,解析失败的行原样保留 |
39
- | M3-4 | **gem_report 增加 ledger_summary{total,by_status,by_type,corrupt_rows} + ledger_context 基率披露** | 基率披露措辞(zcode 建议稿,供裁决):"预测账本共 N 条(其中 M 条 unverified):全部为模型推导预测(essentiality/phenotype 等),实验或文献兑现前不应当作事实引用;状态分布即预测可信度基率,回填后 by_status 向 literature_supported/experimentally_verified 迁移。" |
40
- | M3-5 | **gem_ledger 与 gem_report 均支持 `ledger_path` 覆盖默认路径** | 任务书明确(测试用临时路径不必写用户目录);生产默认路径不变 |
41
-
42
- ## M4 消费方升级(声明式,非自动区间化)
43
-
44
- | # | 决策 | 依据 |
45
- |---|---|---|
46
- | M4-1 | **声明式升级而非自动区间化**:既有工具输出加 `units` + `point_value_note` 声明(指向 gem_fluxscan),而不把每个工具的内部计算改成区间制 | 任务书锁定"严格只增不改";自动区间化会改变所有既有输出的形状/成本(FVA 是 pFBA 的 25 倍耗时),且各工具的定位(验证/补洞/必需性)本就不是条件对比。取舍:声明在输出侧、区间化集中在 fluxscan 一个工具 |
47
- | M4-2 | 声明覆盖面:validate G3/G4 每行、l3_fix before/after/l3a/l3b、biomass apply 三联对照 growth、essentiality wt_growth 输出字段;tools.js 七个产出生长数值的工具 description 追加统一句 | 任务书 6 条清单 + "各工具 description"按产出生长数值者解释(validate/gapfind/build/phenotype/essentiality/l3_fix/biomass;新三工具 M1-M3 出生即带) |
48
- | M4-3 | skill 硬规则 #6「通量区间制:无区间不点数」+ 决策树补 gem_fluxscan/gem_sensitivity/gem_ledger 三行 | 决策树此前 6/12 覆盖的选型断层教训(D2);工具数增至 15 后决策树全量同步 |
49
-
50
- ## M5 iNX1344_v4 前哨(阶段 B 前哨,不做正式基准对比)
51
-
52
- | # | 决策/发现 | 依据 |
53
- |---|---|---|
54
- | M5-1 | **路径确认**:`F:/A_NGJ plan/Zcode/models/` 下修复链 `iNX1344_fixed.xml → v2 → v3 → v4.xml`(终点 1.9MB);原始发布 `iNX1344_raw/mpp13032-sup-0009-DataS1.xml` cobra 直接加载失败(CobraSBMLError) | 任务书要求先 ls 确认真实路径再引用 |
55
- | M5-2 | **v4 状态**:1441 反应 / 1344 基因 / 1106 代谢物;MetaCyc 风格 `RnxatuXXXX`/`M00XXX_c` ID + Atu 基因座;objective=Rnxatu0132(52 组分 biomass,即 biomass 本体);区室 c/e(名字为空);756/1106 代谢物有名 | 任务书要求确认可加载/ID 体系/计数/biomass 状态 |
56
- | M5-3 | **GAM 检测适配**:v4 的 GAM 载体实际在 biomass 内(ATP -25.633 / ADP+Pi+H+ 各 +24.78,GAM_ORIG=24.78),但 H2O(M00001_c)公式缺失 → 公式级检测漏判 → 误落独立载体分支且谷氨酰胺合成酶(含额外底物的 ATP 水解反应)误命中。修复:h2o 量级回退(与 GAM 净水电解量差 <5% 的未分类能量级组分补判)+ 独立载体判据收紧为"反应内全部代谢物均为能量 stub 角色"(纯 ATPM) | 适配进 `find_biomass_gam`(C58 锚点不受影响——其 h2o 公式命中);两个误判模式都记档 |
57
- | M5-4 | **核心发现:v4 交换层与介质机制不兼容**——全部 1441 反应 `Rnx` 前缀、无 EX_/DM_/SK_ 体系,交换由 161 个 boundary 单代谢物反应承担;`build_ex_index`(EX_ 前缀)索引为空 → 任何自然名/预设介质 0 解析 → 全交换清零后 growth 恒 0 | fluxscan AB vs M9 实测:两条件各 20 项全 unresolved、growth 0/0、pair 全 overlap(1441 行)。工具本身跑通无崩溃, medium 契约(EX_ 中心)是跨模型边界 |
58
- | M5-5 | **根因诊断:v4 结构性不生长**——161 个 boundary 全开放(lb=-1000)FBA 仍为 0;逐组分 demand 探针定位 5 个不可净产的 biomass 组分:M02908_c / M01051_c / M00342_c(Sn-Glycero-3-phosphoethanolamine,脂质) / M00336_c / M00155_c(多为无名无公式代谢物,脂质/聚合物家族;与原始模型"脂质 μ_max=0"诊断一致) | 全 boundary 开放 + 逐组分 demand 可行性测试(work/m5_probe_inx4.py 可复核) |
59
- | M5-6 | **必需性退化警示**:v4 essential_scan wt=0 → 1066/1066 候选全判"必需"(判定式 v<EPS 在 wt=0 时恒真)。sensitivity 增加 `baseline_growth_degenerate` 字段 + WARN:基准 wt≤0 时 essential 集无生物学意义,结果仅证明工具跑通 | 前哨发现的工具层固化:防止阶段 B 拿退化基线做对比 |
60
- | M5-7 | **M5 定位 = 前哨不修复**:发现清单进阶段 B 建议(交换层规范化/5 组分补路径/ID 桥),不实现 | 任务书"范围外:正式 iNX1344 基准对比表";避免在共享介质代码上做临时特化 |
61
-
62
- ## M6 基准与文档
63
-
64
- | # | 决策 | 依据 |
65
- |---|---|---|
66
- | M6-1 | 性能基准表口径:stderr/日志分段计时 + 整段 wall time;同机独占运行(避免后台任务竞争 skew) | 见报告性能基准表 |
67
- | M6-2 | 文档纪律校验以 grep 计数为准(ctx.tools.register / OPS 键 / 决策树覆盖 / README 工具表 / smoke 断言数) | 三轮验证方法论:文档计数漂移是常态,逐项点数核对 |
@@ -1,56 +0,0 @@
1
- # DECISIONS-阶段E — genie 接入期(E4 弹性批次)实测结论与决策
2
-
3
- > 2026-08-31 阶段 E「genie 接入 gem + 全面实战验证」弹性批次。执行与证据:`D:/Program/Zcode/gem-verify/work/e/`。
4
-
5
- ## E4a 引擎对照(CarveMe vs gapseq,同基因组 C58)— 正式归因
6
-
7
- benchmark 全量对比(AB,essential_full=true,231.3s,export_md 落盘 `e4a_benchmark_report.md`):
8
-
9
- | 维度 | CarveMe (C58_carveme_test.xml) | gapseq (C58.xml) |
10
- |---|---|---|
11
- | 规模 | 1495 基因 / 3109 反应 / 1981 代谢物 | 1084 基因 / 2485 反应 / 2062 代谢物 |
12
- | AB 生长 | 0.624132 | 0.519981(锚点精确复现) |
13
- | biomass 结构 | `Growth` 显式 57 组分,GAM=53.95(in-biomass),断供 0 | `bio1` 81 组分,GAM=40.0,**断供 7**(cpd00166/01997/03422/11493/12370/15665/15666) |
14
- | 必需性(全量) | 90/1276 | 155/818 |
15
- | 表型(19 底物 sole) | 11/19 = 57.9% | 11/19 = 57.9%(**Arabinose 双双失败**——两引擎独立重建同缺 L3 阿拉伯糖分解路径) |
16
-
17
- 必需性差异分解(identity+gene.name 映射,覆盖 88.89%):intersection 68 / a_only 12 / b_only 87。
18
- **归因**:差异主体不是命名空间映射噪声,而是两引擎的通路覆盖与 biomass 结构差异(gapseq bio1 更大更严——
19
- 81 组分含 7 个模型内不可净产前体,敲除网络对前体供给更敏感→必需集更大)。
20
-
21
- ## E4b 跨命名空间基因映射桥 — 原型结论(不并入插件)
22
-
23
- 前提修正:任务书设定"覆盖率从 0 提升",实测 identity+gene.name 已达 **88.89%**(80/90)——前提过时。
24
- EC 等价类桥原型(`work/e/e4b_ec_bridge_probe.py` + `e4b_bridge_crossref.py`):
25
-
26
- - gapseq 侧 EC 索引 848 个(反应 id/annotation 提取)
27
- - 10 个未映射必需基因中 6 条 EC→gapseq-essential 命中;**唯一靶规则**(多候选拒绝)下可信桥 2 条:
28
- NC_003062_2_847→NC_003063_2_595(4 EC 同靶)、NC_003063_2_888→NC_003062_2_1639
29
- - 覆盖率 0.8889 → **0.9111**(+2.2pp);剩余未映射多为模型内容差(gapseq 无对应基因),非命名空间问题
30
-
31
- **决策**:不并入 benchmark 映射层。理由:收益 ≤2pp、EC→多候选歧义需二次证据消歧、错误映射会污染必需性对比结论。
32
- 原型脚本留存可复跑;若未来接入 Atu 旧 locus 桥(GFF/tbl 资产),应与本 EC 桥合并为"二级映射策略"并带 evidence 标注。
33
-
34
- ## E4c 双敲全扫先验排序 — 已实现(v0.1.1)
35
-
36
- 见 commit 8736690。诚实结论:C58 上全部 18 对 SL 均为 GPR 穷尽型同工酶对(先验区),扫描区排序无可测收益;
37
- 改动是其他模型的期望改进(共享反应数降序=冗余暴露先验),零回归(18/18 集合一致)。
38
-
39
- ## E4d iNX1344_v4 断供 5 组分诊断深化 — 可行性报告(不修模型)
40
-
41
- demand 探针(全交换开放 lb=-10)复现 5 断供前体:**M00155_c / M00336_c / M00342_c / M01051_c / M02908_c**。
42
-
43
- 关键事实(`work/e/e4d_inx_probe.py` + `e4d_inx_neighborhood.py`):
44
-
45
- 1. 仅 M00342(Sn-Glycero-3-phosphoethanolamine,C5H14NO6P)在模型内有 name+formula;
46
- 其余 4 个是**裸 MetaCyc M-ID**(无 name/formula/annotation)——修复前必须先做化合物身份解析(外部证据,不可臆造)。
47
- 2. 邻域分析:5 个断供物由 Rnxatu0461-0470/Rnxatu0577(反应名内嵌 MetaCyc R0xxxxx id)连成**内部互连的孤岛子网络**——
48
- 断供根因是孤岛与核心代谢之间缺上游供给连接,不是 biomass 化学计量错误。
49
- 3. 修复路径可行性 = 中:①MetaCyc 身份解析(4 个裸 ID);②孤岛边界死端代谢物定位;③按 MetaCyc 通路补连接反应(需文献证据分级)。
50
- 属"补路径"而非"补公式";G6 能量泄漏哨兵必须随补随测。
51
-
52
- ## 接入记录(genie 侧)
53
-
54
- genie preset persona.md 增代谢模型能力域路由段(commit e38c561)+ docs/plugin-integration.md 契约 v1(64dae29)+
55
- bio_python 桥 exitCode 校验(06c8c05,genie 0.6.25)。路由生效、契约传导(prediction_id/status 引用与账本 grep 一致)
56
- 经真实 agent 会话验证,详见 `D:/Program/Zcode/gem-verify/phaseE-report.md`。