Metadata-Version: 2.4
Name: lc-ms-group-advisor-ldxs
Version: 0.1.0b0
Summary: lc-ms-group-advisor — AI Agent
Home-page: https://github.com/Ldxs001/maby_agent
Author: Ldxs (wUwproject)
Author-email: wuwofc@yeah.net
Project-URL: GitHub, https://github.com/Ldxs001/maby_agent
Project-URL: Gitee, https://gitee.com/wUwproject/maby_agent
Project-URL: Documentation, https://github.com/Ldxs001/maby_agent#readme
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: project-url
Dynamic: requires-python
Dynamic: summary

# LC-MS 分组顾问 — 色谱出峰预测 + 质谱分组优化

> 给定一批化合物（名称 + 化学式 + 母离子 m/z），预测其在液相色谱上的**出峰顺序**，据此给出 MRM 分组扫描建议——哪些化合物可以放进同一个采集窗口，哪些必须分时段，避免 cycle 过长导致点数不足而漏检。
>
> **定位是实验前的「作战地图」，不是精确的色谱分离模拟**：知道谁先谁后、怎么分段，方便分批做分离测试，而不是一针针实测、或 50 个化合物一起测搞得焦头烂额。
>
> 版本：0.1.0b0 | 作者：wUwproject | 许可证：Apache 2.0

---

## 目录

- [一、它是什么](#一它是什么)
- [二、环境要求与依赖](#二环境要求与依赖)
- [三、搭建与启动](#三搭建与启动)
- [四、命令行参数](#四命令行参数)
- [五、使用流程](#五使用流程)
- [六、计算逻辑](#六计算逻辑)
- [七、近似值清单](#七近似值清单)
- [八、观察对象](#八观察对象)
- [九、LLM 抽取：只出指针，值由 Python 取](#九llm-抽取只出指针值由-python-取)
- [十、界面](#十界面)
- [十一、目录结构](#十一目录结构)
- [十二、缺陷与不适用情况](#十二缺陷与不适用情况)
- [十三、常见问题](#十三常见问题)
- [十四、协议](#十四协议)

---

## 一、它是什么

一个本地运行的单文件 Web 工具，把「化合物清单」翻译成「质谱采集策略」。

它回答三个问题：

| 问题 | 输出 |
|---|---|
| 这批化合物在柱子上谁先出、谁后出 | 归一化出峰位置（0%–100%）+ 顺序表 |
| 哪些可以放进同一个采集窗口 | 分组方案 + **每个分组边界的原因** |
| 每组放这么多个，质谱采得过来吗 | 每组 cycle 时间、点数、达标判定 |

与同类工具的差别在于：分组不只是「按出峰时间切段」，同时受**质量可分辨**这条硬约束约束——同组内任意两个母离子的质量差若小于仪器峰宽（Δm < k×FWHM），同一采集窗口里两个质量峰在物理上就是糊在一起的，无论读数精确到小数点后几位。这类冲突必须拆组，且冲突记录会附带两者的出峰位置差，供使用者判断该冲突能否靠时间调度解决。

三条设计原则：

1. **不静默**——配置与数据域矛盾（分子式缺失、排序键超出适用域、母离子多电荷）一律产出显式告警；每个分组边界都回显「这个组是为什么开的」。
2. **不做假功能**——没有实现的模型不摆出来（例如按表面电荷保留的离子交换柱，本工具不支持，不会假装能算）。
3. **LLM 只做它擅长的**——见[第九节](#九llm-抽取只出指针值由-python-取)。

## 二、环境要求与依赖

- **Python 3.11+**（`setup.bat` 启动时自动检测）
- **仅标准库**（`http.server` / `urllib` / `json` / `csv` / `threading` / `math`），**无第三方 pip 依赖**（见 `requirements.txt`）
- **LLM 由外部后端服务提供**：LM Studio（默认 `http://localhost:1234`）/ Ollama（`http://localhost:11434`）/ 任意 OpenAI 兼容 API

LLM 仅在**化合物抽取**这一步被调用。分组预测、判据计算、质谱核算全部是纯 Python 确定性计算，**离线可用，不依赖模型**。

## 三、搭建与启动

**方式一：Windows 一键启动**

双击 `setup.bat`。脚本会依次检测解释器（`py -3.11` 优先，回退 `python`）、清理占用端口的旧进程、启动服务并打开浏览器（端口 8810）。

**方式二：手动启动**

```bash
python main.py                 # 启动 Web UI，默认端口 8810
python main.py --port 8820     # 指定端口
python main.py --check         # 仅检测 LLM 后端连接，不启动服务
python main.py --backend ollama --model qwen2.5
```

脚本文件说明：

| 文件 | 用途 |
|---|---|
| `setup.bat` | 一键启动（检测环境 → 清端口 → 起服务 → 开浏览器），关闭窗口即停服 |
| `_run.bat` | 前台运行（控制台直接看到日志，Ctrl+C 停止） |

两个 `.bat` 均为**纯 ASCII 编码 + CRLF 行尾**——Windows 批处理对非 ASCII 编码与裸 LF 敏感（`goto` / `:label` / `if errorlevel 1 (` 多行块在裸 LF 下会解析错乱），改动时须保持这两个约束。

## 四、命令行参数

| 参数 | 说明 |
|---|---|
| `--port PORT` | Web UI 端口（默认 8810，可传 `auto` 自动选空闲端口） |
| `--host HOST` | 监听地址（默认 `0.0.0.0`） |
| `--pidfile PATH` | PID 文件路径（`setup.bat` 用它做停服） |
| `--check` | 仅检测后端连接并退出 |
| `--backend {lm-studio,ollama,custom}` | LLM 后端（覆盖 config.json） |
| `--base-url URL` | API 地址（覆盖 config.json） |
| `--api-key KEY` | API Key（覆盖 config.json） |
| `--model NAME` / `-m NAME` | 模型名称（覆盖 config.json） |

## 五、使用流程

```
1. 分组预测 Tab
   粘贴化合物文本 / 上传文件  →  LLM 锚点指针抽取  →  可手工修改化合物表
   选柱型 + 分组模式 + 质谱参数  →  运行
   结果：配置自检告警 + 出峰顺序图 + 顺序表 + 分组详情 + 每组 cycle/点数/判定

2. 时间参数 Tab
   按设备（QqQ 正式 / Q-TOF、离子阱、QTRAP 实验性）单机模拟 cycle 构成，
   拖参数看 cycle 与点数如何变化，甘特图展开每一个时间段

3. 通量核算 Tab
   逐行录入离子对（母离子 + 子离子），检查同一采集窗口内的
   母离子间、子离子间是否质量邻近（邻近即互相干扰）
```

## 六、计算逻辑

### 1. 排序键与前处理

柱子类型决定排序键与方向：

| 柱型 | 排序键 | 方向 | 依据 |
|---|---|---|---|
| 反相 C18 / C8 / C4 | 极性指数 | 降序（极性大先出） | 疏水分配 |
| HILIC | 极性指数 | 升序（极性小先出） | 分配 + 吸附 |
| 正相（硅胶/氨基） | 极性指数 | 升序 | 吸附 |
| SEC（GPC/GFC） | 分子量 | 降序（大分子先出） | 体积排阻 |

C4 / C8 / C18 **合并为同一模式**——三者是同一保留机制（疏水分配），只是烷基链长短不同导致疏水性强弱递变，排序方向完全一致。只有机制不同的柱型（SEC 的体积排阻、IEX 的离子交换）才单列。

### 2. 极性指数（PI）

$$\text{hetero} = 2O + 3N + 1.5S + 2P + F + Cl + 0.8Br + 0.5I$$

$$\text{DBE} = C - \frac{H}{2} + \frac{N}{2} + 1 \qquad \text{backbone} = C + 0.1H$$

$$\text{PI} = \frac{\text{hetero}}{\text{backbone} + 0.3\max(\text{DBE}, 0) + 0.1}$$

设计意图：分子（hetero）贡献极性，碳氢骨架（backbone）贡献疏水性，不饱和度（DBE）做折减——芳香/环状结构在同碳数下疏水性更强。分母的 `+0.1` 是防止纯烃（hetero=0）除零并把 PI 压到 0。

### 3. 归一化出峰位置

批次内 min–max 线性映射到 0–100：

$$\text{pos}_i = \frac{v_{\max} - v_i}{v_{\max} - v_{\min}} \times 100\ (\text{降序柱})$$

**这一步是相对位置，不是保留时间。** 它把排序键的差距等距化，使「位置差」可以当作分组阈值的输入。批次内全部 PI 相同时（`vmax == vmin`）位置统一记为 50%。

### 4. 分组策略与两条硬约束

两种模式：

| 模式 | 规则 | 位置约束 |
|---|---|---|
| `threshold` 百分比阈值 | 相邻位置差 < 阈值 → 同组 | `threshold_pct` 位置差阈值，1–20%，默认 5 |
| `capacity` 质谱能力反推 | 由 cycle 上限反推每组最多几个化合物，贪心填充 | `max_span_pct` 组内跨度上限，0.5–100%，默认 100（不干预） |

两种模式都受**两条硬约束**，触发即强制开新组，并记录原因：

| 优先级 | 约束 | 触发条件 | 记录为 |
|---|---|---|---|
| 1 | **质量可分辨** | 同组内任一母离子与候选项 Δm < k×FWHM | `mass` |
| 2 | **容量上限** | 当前组已达每组合法上限（仅 `capacity` 模式） | `capacity` |
| 3 | **出峰位置** | 两种模式约束的几何对象不同，见下 | `span`（capacity）/ `gap`（threshold） |

#### 两种位置约束的对象不同

**`threshold` 模式约束「相邻差」** —— 逐对比较，任一相邻对达到阈值即开新组。位置差在此**就是分组定义本身**，1–20% 是它的正常量纲。

**`capacity` 模式约束「组内首尾跨度」** —— 只看一组从第一个铺到最后一个有多宽。跨度是整体量，相邻差是局部量：局部量容得下「两端各站一个、中间空着」的组，整体量才是「出峰隔太远不该塞进同一采集窗口」的本意。同一批数据（位置 `[0, 22.8, 72.2, 73.3, 74.2, 83.5, 88, 100]`）：

| 判据 | 上限 50% 的结果 |
|---|---|
| 相邻差 | **1 组**（所有相邻差 ≤ 49.45%，合格） |
| 组内跨度 | **2 组**（第一组跨到 72.2%，已超 50%） |

**跨度上限的取值区间是推导出来的，不是选的。** 位置经批次内 min-max 归一化，全批跨度恒为 100%，一组是批次的子集，所以跨度上限**不可能超过 100%**；上限取 100 恰好等于「不干预」，此时分组完全由容量与质量判据决定。

**临界值 `span_critical_pct`** 由该批次实际分组中各组的最大跨度算出（不取均匀分布估计——真实峰位是聚集的，估计值偏乐观），随 `run_separation` 一并回显。跨度上限低于它，这条判据就会抢在容量之前拆组，「质谱能力反推」名不副实。实测 16 个化合物、容量上限 9 时临界值为 68.5%：设 68.0% 时跨度判据拆 1 次，设 68.5% 时归零。上限低于临界值时，界面会在分组详情里显式告警。

**两个位置约束参数都是一等配置项**（`threshold_pct` / `max_span_pct`），在「色谱参数」区随分组模式切换显示，写入 `config.json` 持久化；`capacity` 模式的分组详情抬头常驻回显当前上限值与临界值——不需要等它触发拆组才知道它存在。滑杆的 `min`/`max`/`step` 也统一由配置推动链下发，不在页面里另写一份（见[十二节 C](#c-参数与实现层面的已知约束)）。

每个边界记录含：前一化合物、后一化合物、原因代码、明细（质量冲突带 Δm / 判据值 / 双方名称；跨度冲突带实际跨度、上限与首尾化合物；阈值冲突带实际差值、阈值与运算符 `op`）。

### 5. 质量可分辨判据

**质量准确度 ≠ 质量分辨率。** 仪器能「称准」不代表能「分开」。两个等高等宽高斯峰中心相差 Δm，合成谱的谷值（相对合成峰顶）为：

$$v(\Delta m) = \frac{2e^{-t^2/8}}{1 + e^{-t^2/2}}, \qquad t = \frac{\Delta m}{\sigma}, \qquad \sigma = \frac{\text{FWHM}}{2.354820}$$

反解出判据系数 `k = Δm / FWHM`：

| 谷值标准 | k（本工具用的精确解） | k（闭式近似） |
|---|---|---|
| 50% 谷 | 1.41219 | 1.41421 |
| **10% 谷（质谱经典，默认）** | **2.07892** | 2.07892 |
| 1% 谷（严格） | 2.76475 | 2.76475 |

表中 k 由二分法求解（`valley_to_k`），并已用 60 位精度独立复核。闭式近似 `√(8ln(2/v))/2.3548` 忽略了合成峰顶比单峰高出的一小截，在 10% / 1% 锚点上与精确解相差小于 0.001%，在 50% 锚点上差 0.14%。

**判据：Δm ≥ k × FWHM**（比较时带 1e-9 相对容差，避免浮点让「Δm 恰好等于判据」自相矛盾）。不满足 → 质量混峰 → 强制拆组。

k 取 1 时谷值高达 **94%**——凹陷只有峰高的 6%，肉眼完全看不出是两个峰。所以 k 不能取 1；本工具默认取文献经典的 10% 谷。

峰宽来源三选一，用户输入的实测值始终优先于设备标称值：

| 模式 | 含义 |
|---|---|
| `auto` 跟随设备标称 | 单位分辨四极杆取固定 0.7 Da；高分辨 TOF 取 `m/z × ppm` |
| `constant` 恒定峰宽 | 用户填实测 FWHM（Da），不随 m/z 变 |
| `proportional` 按 m/z 等比 | 用户填相对峰宽（ppm），FWHM = m/z × ppm × 1e-6 |

### 6. MRM cycle 与点数

**设备支持度**：四台设备中仅**三重四极杆 QqQ** 为正式支持，其余三台标为**实验性**，显示名统一带「（实验性）」后缀，并随 label 传播到所有展示面（设备下拉框、核算结果 `device`、时间参数页 `device_label`、通量核算），不只在某一处标注。

| 设备 key | 显示名 | 支持度 | 标称峰宽 | dwell 下限 | 点数下限 |
|---|---|---|---|---|---|
| `qqq` | 三重四极杆 QqQ | 正式 | 0.7 Da 恒定 | 2 ms | 15 |
| `qtrap` | Q离子阱 QTRAP（实验性） | 实验性 | 0.7 Da 恒定 | 2 ms | 15 |
| `it` | （线性）离子阱（实验性） | 实验性 | 0.7 Da 恒定 | 10 ms | 15 |
| `qtof` | 四极杆-飞行时间 Q-TOF（实验性） | 实验性 | 20 ppm × m/z | 0 ms | 15 |

「实验性」的含义严格限定为：该设备的**标称参数（峰宽、dwell 下限）与界面标注**尚未经充分实测验证，按标称值使用可能偏离真实仪器。它**不改变任何计算**——cycle 公式、点数下限判定、质量可分辨判据对四台设备完全同一套逻辑，代码中没有任何一处用 label 或设备身份做分支。要得到可信结果，请用**实测峰宽**覆盖标称值（见 4.2 的 `constant` / `proportional`）。

$$T_{\text{cycle}} = N_1 N_3 \cdot \text{dwell} + N_1 (N_3-1) \cdot \text{delay}_{q3} + (N_1-1)\max(\text{delay}_{q1}, \text{delay}_{q3}) + \text{overhead}$$

其中 `N₁` = 母离子数（化合物数），`N₃` = 每个母离子对应的子离子通道数，总通道数 = N₁×N₃。

三项的物理含义：

| 项 | 含义 |
|---|---|
| `N₁N₃ · dwell` | 每个离子对的实际驻留时间 |
| `N₁(N₃−1) · delay_q3` | **同化合物内**子离子之间切 Q3（按母离子分别加总） |
| `(N₁−1)·max(delay_q1, delay_q3)` | **跨化合物**时 Q1 与 Q3 同时动作、并行稳定，取较慢者 |
| `overhead` | 每 cycle 固定开销 |

点数与达标判定：

$$n_{\text{points}} = \frac{W \times 1000}{T_{\text{cycle}}} \quad (W = \text{色谱峰宽，秒})$$

- `n_points ≥ 设备点数下限` → 点数达标
- `dwell ≥ 设备 dwell 下限` → 仪器做得到
- 两者都满足 → `pass`；否则给出「建议每组不超过 N 个化合物」的具体可执行建议

每组合法上限的反解（供 `capacity` 模式使用）：

$$N_1 \le \frac{T_{\text{cycle}}^{\max} - \text{overhead} + \max(\text{delay}_{q1}, \text{delay}_{q3})}{N_3 \cdot \text{dwell} + (N_3-1)\cdot\text{delay}_{q3} + \max(\text{delay}_{q1}, \text{delay}_{q3})}, \qquad T_{\text{cycle}}^{\max} = \frac{W \times 1000}{n_{\min}}$$

**通道数异构时的等效平均**：同一组内各化合物的子离子通道数不相等时（如 [3,3,3,2,4]），滑杆只能填一个值。此时填算术平均：

$$\bar{N}_3 = \frac{\sum n_i}{N_1}$$

在 cycle 总量上这是**精确等效，不是近似**——推导中 `dwell` 与 `delay_q3` 整项对消，两边只差 `N₁·N̄₃ = Σnᵢ` 这一个恒等式。所以等效值可以在滑杆上填小数（步长 0.1），计算用浮点、甘特图渲染时才取整。

**注意它的能力边界**：等效平均只保证「这一组放不放得进这个 cycle」的总量判断准确，**不保证单个化合物的分配准确**——第 5 个化合物（4 通道那个）够不够点数，它答不了。

### 7. 分子式 ↔ m/z 一致性校验（可选）

`mass_calc` 模块提供独立的校验能力（分组计算**不依赖**它）：

- 元素单同位素质量表（33 种元素）+ 加合物表（阳性 13 项 / 阴性 7 项）
- 由化学式算中性单同位素质量 M，按候选加合物与电荷数算理论 m/z，与实测 m/z 比对
- 容差按设备分流：高分辨走 ppm（qtof 默认 20 ppm），标称分辨走 Da（QQQ/IT/QTRAP 默认 0.5 Da）

三档结果，**不猜**：

| 结果 | 含义与用途 |
|---|---|
| 唯一命中 | 化学式与 m/z 互证，两条数据同时可信 |
| 多候选 | 列出候选交由使用者判断，不自动挑 |
| 零命中 | 两者至少有一个错——例如把子离子 m/z 填成了母离子（差约 40 Da），在进计算前拦住 |

## 七、近似值清单

本工具的输出是**工程近似**，不是物理仿真。以下每一项都明确列出近似形式、引入的偏差方向、以及何时可接受。

| # | 近似项 | 具体形式 | 引入的偏差 | 何时可接受 |
|---|---|---|---|---|
| 1 | **极性由元素计数粗估** | PI 只用各元素原子的**个数**，不含任何结构/官能团信息 | 同分异构体 PI 完全相同；官能团位置差异（邻/间/对）无法区分 | 做「大概分组」时；异构体需靠实测 |
| 2 | **位置归一化把非线性压成线性** | 批次内 min–max 线性映射 | 真实保留与 PI 不是线性关系（反相保留近似服从 log k 与有机相比例的关系），位置差被等距化 | 只用于切段阈值判断；**不同批次的归一化位置不可直接比较** |
| 3 | **位置是相对次序，不是保留时间** | 0%–100% 无物理量纲 | 无法换算成分钟；无法给出分离度 Rs | 排序与分段足够；算分离度需实测 |
| 4 | **高斯峰形假设** | 判据推导基于两个**等高、等宽**的高斯峰 | 实际峰常有拖尾/前沿、两峰高度不等；真实峰形下所需 Δm 与计算结果有偏差 | 峰形对称性尚可时；强拖尾峰需实测复核 |
| 5 | **FWHM 恒定假设** | `auto` 模式下单位分辨设备恒取 0.7 Da | 实际 FWHM 随 m/z 略有变化（通常缓慢增大） | 0.7 Da 是标称值；**有实测值时应改用 `constant` / `proportional` 覆盖** |
| 6 | **单电荷假设** | 质量可分辨判据按实测 m/z 之差直接计算 | z>1 时 Δm = ΔM/z 被压缩，单电荷阈值不再适用（程序会在 `m/z < M−2` 时显式告警） | LC-MS 小分子（<1000 Da）ESI 下几乎恒为 z=1 |
| 7 | **dwell 全局统一** | 同一组内所有离子对共用同一个 dwell | 若实际按化合物分别设 dwell，则 `Σnᵢ·dwellᵢ ≠ N₁·N̄₃·dwell`，单一滑杆模型无法表达 | 方法采用统一 dwell 时（本工具的前提假设） |
| 8 | **N₃ 等效平均** | 异构通道数取算术平均，填小数 | 总量精确（恒等式），但单化合物分配被抹平 | 判断「放不放得进 cycle」；不适用于判断单个化合物的点数 |
| 9 | **cycle 建模为固定延时之和** | 离子对切换用常量 `delay_q1` / `delay_q3` / `overhead` 表示 | 忽略了实际稳定时间的差异、极性切换、透镜调谐等瞬态过程 | 用于横向比较不同分组的相对优劣，不用于预测绝对 cycle |
| 10 | **加合物判定用固定候选表** | 20 项常用加合物 + z∈{1,2,3} | 方法若使用表外加合物（如 [M+Li]⁺）会误报零命中 | 常用 ESI 加合物覆盖范围内；扩表为单点改动 |
| 11 | **元素质量为物理常数** | 单同位素质量取文献值，不设测量误差 | 无（此项为精确值，列出以示区分） | — |
| 12 | **不考虑二次保留** | 模型只含主保留机制 | 硅羟基作用、离子排斥、金属螯合等次级效应未建模 | 中性/常规反相条件 |

**一句话**：本工具的数值可用于**排序、切段、相对比较**，不可用于**预测保留时间、计算分离度、替代实测**。

## 八、观察对象

这套系统观测与输出的量，按层级列出：

| 层级 | 观察量 | 单位/取值 | 来源 |
|---|---|---|---|
| **单个化合物** | 极性指数 PI | 无量纲（典型 0–3） | 化学式 |
| | 分子量 M | Da（单同位素质量） | 化学式 |
| | 归一化出峰位置 | %（0 = 最先出，100 = 最后出） | 排序键归一化 |
| | 排序键有效标志 | 真/假（分子式缺失时为假） | 自检 |
| | 所属组号 | 整数 | 分组 |
| **每对相邻母离子** | 质量差 Δm | Da | 实测 m/z 之差 |
| | 峰宽 FWHM | Da | 设备标称或用户实测 |
| | 判据系数 k | 无量纲（默认 2.07892） | 谷值标准反解 |
| | 所需最小间距 `k×FWHM` | Da（QQQ 默认 1.4552） | 计算 |
| | 合成谱谷值 | %（谷值越低越分得开） | 判据反算 |
| **每个分组边界** | 开新组原因 | `mass` / `capacity` / `gap` | 分组器 |
| | 原因明细 | 质量冲突带 Δm 与判据；位置冲突带差值与阈值 | 分组器 |
| **每个分组** | 母离子数 N₁ / 通道数 N₃ | 个 | 分组 |
| | 总通道数 | N₁×N₃ | 计算 |
| | cycle 时间 | ms | cycle 公式 |
| | 点数 | 个 | 峰宽 ÷ cycle |
| | 达标判定 | `pass` / `fail_points` / `fail_dwell` | 双条件 |
| | 改进建议 | 「每组不超过 N 个化合物」 | 反解 |
| **整体** | 不分组对照 | 总通道数、cycle、点数、是否会漏检 | 反事实计算 |
| | 配置自检告警 | 4 类（见下） | 自检器 |

配置自检的 4 类告警（均为显式，不静默通过）：

| 代码 | 触发条件 | 为什么必须报 |
|---|---|---|
| `missing_formula` | 分子式缺失或非法 | 排序键是占位值，出峰位置不代表真实顺序 |
| `out_of_domain` | PI 模式下 M > 1500 Da | PI 只由元素计数决定，对高分子量无分辨力（可能柱型选错） |
| `sec_under_domain` | SEC 模式下 M < 500 Da | 小分子全进孔、保留体积趋同，几乎没有分离度 |
| `multi_charge` | 母离子 m/z < M − 2 | z>1，单电荷阈值不适用 |

## 九、LLM 抽取：只出指针，值由 Python 取

LLM **不输出任何数值或坐标**，只输出「原文引用 + 槽位」：

```json
{"mode": "anchors", "items": [
  {"g": 1, "name": {"quote": "二甲双胍"}, "formula": {"quote": "C4H11N5"},
   "precursor": {"quote": "130.1"}, "product_quant": null, "product_qual": null}
]}
```

Python 用 `str.find` 反算区间并取值。**三条硬闸门**：引用必须命中原文、位置必须有序（游标只前进）、编号必须连续。任一不过即显式报错，并回灌缺口清单重试一次；二次失败直接报给使用者——不静默丢弃、不降级放行。

**为什么不把坐标或数值交给 LLM**：它的分词器不按字符切，中英混排与全角标点会让计数漂移；而一个错误的坐标仍是一个合法整数，程序会照切不误——错得无声无息。反过来，抄一段原文是 LLM 擅长的，在字符串里找位置是 Python 擅长的。把索引的生成权交给 Python，同时白捡一个自校验：找不到 = 显式失败。

**槽位必需性分层**：

| 槽位 | 缺失后果 | 处理 |
|---|---|---|
| 化学式 | **分组排序的唯一输入缺失**，整批分组不成立 | **阻塞**，要求补全 |
| 母离子 m/z | 质量可分辨约束对该化合物停用 | 告警，明示约束未启用 |
| 化合物名称 | 仅影响显示 | Python 自动赋「化合物N」，继续 |

**语义噪音由提示词正反例处理，不用算法硬分**：`C18`（柱型号）与 `C18H37NO`（化学式）在正则层面无法可靠区分，硬分只会引入误杀。提示词用四段式（名词定义 / 正例 / 反例 / 边界例）引导，并在质量校验层留兜底——`C18` 配 m/z 时会被质量互证拦下。

**大表格走 `columns` 模式**：LLM 只给列语义映射（第几列是什么槽位），行由 Python 全量遍历。输入结构由 Python 判定（非空行 ≥3 且候选分隔符出现次数完全一致 → 表格），不消耗 LLM 调用。

**约束解码**：`llm_client` 支持 `response_format` / `json_schema`，在采样阶段就禁止协议外的输出形状。后端不支持时自动降级为「仅提示词约束」，**校验逻辑不依赖它生效**。

## 十、界面

深色主题单页，三 Tab：

| Tab | 内容 |
|---|---|
| **分组预测** | 化合物输入（文本/文件 → LLM 抽取 → 可编辑表格）+ 色谱参数 + 质谱参数 + 结果区（自检告警、出峰顺序图、顺序表、分组详情含开组原因、每组 cycle/点数/判定、质量冲突清单） |
| **时间参数** | 按设备（QqQ 正式 / Q-TOF、离子阱、QTRAP 实验性）单机模拟：参数滑杆 + cycle 构成公式 + **甘特图**（逐段展开每个 dwell / Q3 切换 / Q1 切换 / 省略段，各段宽度严格正比于耗时，合计恒等于 cycle） |
| **通量核算** | 离子对逐行录入 + 邻近性检查（母离子间、子离子间）+ 三点判定（点数 / dwell / 质量） |

界面全部自包含：无外部 CDN、无 emoji、CSS/JS 内联，仅用系统字体。

**参数跨页共享**：峰宽、点数下限、额外开销、dwell、Q1/Q3 切换延迟、子离子通道数这三个页面各有一组输入，它们描述的是同一个物理量——绑定同一参数键，任一改动即同步其余并写回 `config.json`。因此不存在「在时间参数页调了 dwell、分组预测页仍按旧值反推容量」这类情况。切换 Tab 时会重算本页结果，避免展示别页改动前的旧数字。只属于时间参数页试算的键（N₁ / 推斥频率 / 叠加次数 / 扫描范围 / 扫描速率 / 填充时间 / 模式切换）不进配置文件。

## 十一、目录结构

```
lc-ms-group-advisor/
├── main.py                     # 入口（CLI）
├── setup.bat / _run.bat        # 启动脚本（纯 ASCII + CRLF）
├── config.json                 # 持久化配置
├── requirements.txt            # 仅标准库声明
├── LICENSE / NOTICE            # Apache-2.0 与归属声明
├── README.md / CHANGELOG.md / PROTOCOL.md / llms.txt
├── lc_ms_group_advisor/
│   ├── web_ui.py               # HTTP 服务 + 前端页面 + 路由与计算编排
│   ├── separator.py            # 色谱排序 + 分组策略 + 配置自检
│   ├── polarity.py             # 分子式解析 + 极性指数
│   ├── mass_calc.py            # 元素质量表 + 加合物表 + 一致性校验
│   ├── mass_resolution.py      # 质量可分辨判据（唯一判据入口）
│   ├── ms_engine.py            # 设备参数表 + cycle 核算 + 达标判定
│   ├── extractor.py            # 锚点指针抽取（双模式 + 三闸门）
│   ├── llm_client.py           # 多后端 LLM 客户端（含约束解码）
│   └── config_manager.py       # 配置管理（CLI > config.json > 默认）
└── tests/
    ├── test_mass_calc.py       # 质量计算与加合物判定
    └── test_extractor.py       # 抽取协议与三条闸门
```

## 十二、缺陷与不适用情况

### A. 模型固有缺陷（无法通过调参修复）

| 缺陷 | 后果 |
|---|---|
| **分子式不含结构信息** | 同分异构体的 PI 完全相同，但实际保留可能相差很大（如邻/间/对位异构体）。工具对它们给出的排序是**不可信的同一个值** |
| **PI 只按元素计数，与序列无关** | 对多肽/蛋白质，保留由疏水残基序列与二级结构决定，PI 没有分辨力 |
| **批次内相对化** | 归一化位置是批次内 min–max 的结果，**不同批次的输出不可横向比较**。10 个化合物的批与 50 个化合物的批，同一个化合物的位置百分比可能不同 |
| **没有真实保留时间模型** | 位置 0–100% 无物理量纲，无法换算为分钟，无法计算分离度 Rs |
| **不考虑二次保留** | 硅羟基作用、离子排斥、金属螯合等次级效应未建模 |
| **HILIC 机制复杂** | 水层分配 + 吸附 + 氢键多种机制并存，排序仅供参考 |
| **不做分离度优化** | 工具只回答「能不能放一组」，不回答「怎么改梯度才能分开」 |

### B. 不适用的情况（工具会告警，或根本不该用）

| 场景 | 为什么 | 工具的行为 |
|---|---|---|
| **分子量 > 1500 Da 且用 PI 排序** | PI 对高分子量没有分辨力，排序结果荒谬（实测：泛素 8560 Da 排在缓激肽 1060 Da 之前） | 显式告警 `out_of_domain` |
| **SEC 柱 + 分子量 < 500 Da** | 全部进入孔内，保留体积趋同，几乎没有分离度 | 显式告警 `sec_under_domain` |
| **离子交换（IEX / SCX / SAX）柱** | 保留由表面电荷决定，无法从分子式预测；本工具**不支持**该模式 | 无法选择，不会假装能算 |
| **亲和色谱 / 手性柱** | 保留由生物识别或立体构型决定，与元素计数无关 | 不支持 |
| **多电荷离子（z > 1）** | Δm = ΔM/z 被压缩，单电荷阈值失效 | 显式告警 `multi_charge` |
| **GC-EI 数据** | EI 是硬电离（分子被打碎），色谱是气相分配；本工具的软电离准分子离子模型与极性排序模型**均不成立** | 不适用——这是 LC 工具 |
| **蛋白质 / 寡核苷酸分组** | 保留由序列与二级结构决定；且大分子走 HRMS full-scan / DIA 范式，不做 MRM 通道预算 | 不适用——MRM 分组是小分子范式 |
| **需要真实保留时间或分离度** | 本工具不产出这两个量 | 需要实测 |
| **需要判断单个化合物的点数是否够** | 等效平均只保证总量 | 需逐化合物核算（本工具不做） |

### C. 参数与实现层面的已知约束

| 约束 | 说明 |
|---|---|
| dwell 全局统一 | 若方法按化合物分别设 dwell，单一滑杆模型无法表达（见[近似值 7](#七近似值清单)） |
| 加合物表有限 | 20 项常用加合物；表外加合物会误报零命中 |
| 判据分辨力受限 | 标称分辨（±0.5 Da 容差）只能筛出「差得离谱」的错误（如子离子当母离子，差约 40 Da），查不出 0.05 Da 级的小错——那是高分辨的能力 |
| LLM 召回无保证 | 三条闸门能保证「LLM 标的锚点是对的」，**不能保证「LLM 没漏看」**。原文里有个化学式但 LLM 没标，程序无从发现。缓解手段是界面回显命中位置供人工复核，而非制造虚假保证 |
| 抽取耗时 | 受模型侧 thinking 模式影响，单次抽取可能达数十秒至数分钟，非架构问题 |
| 参数总表 | 界面参数的出厂值、取值范围、步长统一在 `config_manager` 定义：`PARAM_SPEC`（持久化配置）+ `TIME_SPEC`（时间参数页专用输入，不落盘），合并为 `PARAM_SPEC_ALL`；`DEFAULT_CONFIG.params` 仅由前者派生。经 `DEFAULT_CONFIG → config.json → /api/config` 下发，界面控件的 `min`/`max`/`step` 与缺省值均由 `param_spec` 注入，页面里不再写第二份。改值域只需改 `config_manager` 一处 |
| 参数跨页共享 | 峰宽 / 点数下限 / 额外开销各有 3 个控件，dwell / Q1 切换延迟 / Q3 切换延迟 / 子离子通道数各有 3–4 个，分布在三个页面。它们描述的是**同一个物理量**，绑定同一参数键——任一改动即同步其余并写回 `config.json`。时间参数页专用键（N₁ / 推斥频率 / 扫描速率等）不在配置里，只同步不落盘 |
| 旧参数别名 | `delay_ms` / `transitions_per_compound` 已由 `delay_q3_ms` / `channels_per_compound` 取代，代码**保留兼容读取**以兼容旧配置文件。`max_gap_pct` 因判据从「相邻差」改为「组内跨度」而语义已变，**不做兼容读取**——旧配置文件里的该键会被忽略，不会与新键混用 |

## 十三、常见问题

**Q：结果显示「质量可分辨约束未启用」是什么意思？**
A：该批化合物中有条目缺母离子 m/z。分组排序仍成立，但「同组内质量是否分得开」这条判据无法执行——这是显式告知，不是静默跳过。

**Q：为什么同分异构体在结果里位置一样？**
A：PI 只由元素计数决定，不含结构信息。这是模型固有缺陷，需靠实测保留时间区分。

**Q：柱型下拉里为什么没有 C8 / C4？**
A：它们与 C18 是同一保留机制，排序方向一致，已合并进「反相 C18/C8/C4」。分开列不会带来任何排序行为的变化。

**Q：为什么点数是每个化合物都一样？**
A：一个 cycle 会扫过全部母离子，每个母离子每 cycle 出现且仅出现一次，所以点数与化合物自身的通道数无关——被「平均」抹平的只有 cycle 的构成，不是任何化合物的采集机会。

**Q：k 默认 2.08 是怎么来的？**
A：质谱文献的「10% 谷」分辨率定义。两等高等宽高斯峰叠加，谷值降到峰高 10% 时，所需间距为 FWHM 的 2.07892 倍（二分法精确解，已用 60 位精度复核）。详见[第六节第 5 小节](#5-质量可分辨判据)。

**Q：位置百分比在不同批次间能比吗？**
A：不能。归一化是批次内 min–max，换一批化合物，同一个化合物的百分比会变。

## 十四、协议

Apache License 2.0（Apache-2.0）。全文见 [LICENSE](LICENSE)，归属声明见 [NOTICE](NOTICE)。

```text
Copyright 2026 wUwproject

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

依据 Apache-2.0 第 4(d) 条，分发基于本项目的衍生作品时，须携带 [NOTICE](NOTICE) 中的归属声明。
