# `macro_split` 重构为可安装包 `macro_lactone_toolkit` 的实施计划

## Summary

采用“渐进式重构，不推倒重写”的方案：保留仓库里已经可用的 12-20 元环识别、通用编号、侧链 BFS 提取和 dummy 原子思路，删除或降级 16 元环专用路径，把核心能力统一收敛到一个正式可安装、可测试、可 CLI 调用的包。

本次重构完成后，项目要满足这几个结果：
- 可以作为正式 Python 包安装和导入，主包名为 `macro_lactone_toolkit`
- 默认自动识别 12-20 元有效大环内酯；也允许显式传 `ring_size`
- 支持环编号和侧链裂解
- 裂解结果同时提供“带位置编号 dummy”与“普通 `*` dummy”两套 SMILES，且都能被 RDKit 读取保存
- 用 `pixi` 管理环境和测试
- 先执行 `git pull --ff-only` 同步远端，再开始任何代码改动

## Key Changes

### 1. 仓库与包结构正规化

- 第一动作不是改代码，而是在仓库根目录执行：
  - `git status --short`
  - `git pull --ff-only`
- 将当前直接放在顶层 `src/` 下的运行时代码，改成标准包布局：`src/macro_lactone_toolkit/`
- 不保留 `src.*` 兼容层；README、测试、脚本、入口点全部切到新导入路径
- `pyproject.toml` 改为标准 setuptools `src` layout 配置，修正：
  - 包发现
  - 项目名与版本元数据
  - console scripts
  - pytest 配置
- `pixi.toml` 统一为 Python 3.12，并支持至少：
  - `osx-arm64`
  - `linux-64`
- `pixi.toml` 补齐测试依赖和常用任务：
  - `test`
  - `lint`（只做检查，不自动改写文件）
  - `smoke-import`
- README 中所有 `from src...` 示例全部替换为 `from macro_lactone_toolkit...`

### 2. 统一核心领域模型与 API

以 `ring_visualization.py` 的通用逻辑和参考脚本的编号思路为基础，重构出清晰边界的核心模块，避免“16 元环专用”和“12-20 元环通用”并存。

公开 API 统一为：

- `macro_lactone_toolkit.MacroLactoneAnalyzer`
- `macro_lactone_toolkit.MacrolactoneFragmenter`
- `macro_lactone_toolkit.RingNumberingResult`
- `macro_lactone_toolkit.SideChainFragment`
- `macro_lactone_toolkit.FragmentationResult`

行为约定固定如下：

- `MacroLactoneAnalyzer`
  - 负责识别 12-20 元环
  - 验证环上是否存在有效内酯酯键
  - 返回所有命中的有效环尺寸
- `MacrolactoneFragmenter`
  - 默认自动识别有效环尺寸
  - 若检测到 0 个有效环，抛出明确异常
  - 若检测到多个有效环尺寸，默认抛出“歧义异常”，要求调用者显式传 `ring_size`
  - 若调用者传了 `ring_size`，则只处理该环
- `RingNumberingResult`
  - 至少包含：`ring_size`、`ring_atoms`、`ordered_atoms`、`carbonyl_carbon_idx`、`ester_oxygen_idx`、`atom_to_position`、`position_to_atom`
- `SideChainFragment`
  - 至少包含：`parent_id`、`cleavage_position`、`attachment_atom_idx`、`fragment_smiles_labeled`、`fragment_smiles_plain`、`atom_count`、`molecular_weight`
- `FragmentationResult`
  - 至少包含：母分子信息、选定环尺寸、编号结果、所有碎片、错误/警告信息

异常策略固定为：

- `MacrolactoneError`
- `MacrolactoneDetectionError`
- `AmbiguousMacrolactoneError`
- `RingNumberingError`
- `FragmentationError`

### 3. 编号与裂解算法收敛

编号逻辑以“通用 12-20 元环”作为唯一正式实现，不再让旧的 16 元环 `ring_numbering.py` 充当主入口。

- 编号规则固定：
  - 位置 1 = 环上的内酯羰基碳
  - 位置 2 = 环上的酯键氧
  - 位置 3-N = 沿确定方向依次编号剩余环原子
- 编号实现使用统一入口，不能再出现批处理脚本内部偷偷调用 `[r16]` 专用函数的情况
- 自动识别时，先找所有 12-20 元环，再验证该环是否为有效大环内酯
- 侧链识别统一用“环原子邻居中不在环内的原子即侧链起点”
- 侧链提取统一用 BFS，只沿非环原子扩展
- dummy 原子输出固定两套：
  - `fragment_smiles_labeled`：带位置编号 dummy，例如 `[5*]`
  - `fragment_smiles_plain`：普通 `*`
- dummy 与连接原子的键型必须保留原始键型，不能强制都变成单键
- 所有裂解结果必须通过 RDKit round-trip：
  - `Chem.MolFromSmiles(...)` 成功
  - `Chem.MolToSmiles(...)` 可再次序列化

### 4. CLI、批处理与现有脚本归并

交付范围包含正式 CLI，不再依赖散乱脚本作为主要入口。

CLI 设计固定为 3 个命令：

- `macro-lactone-toolkit analyze`
  - 输入单个 SMILES 或 CSV
  - 输出有效环尺寸、是否歧义、选中的环尺寸
- `macro-lactone-toolkit number`
  - 输入单个 SMILES
  - 输出编号结果 JSON
- `macro-lactone-toolkit fragment`
  - 输入单个 SMILES 或 CSV
  - 输出碎片 JSON/CSV，包含 labeled/plain 两套 SMILES

批处理行为固定为：

- CSV 默认读取 `smiles` 列
- 可选 `id` 列；未提供则自动生成
- 自动识别歧义分子时：
  - 默认跳过并记录错误
  - 显式传 `--ring-size` 时按指定环处理
- 旧 `scripts/` 中保留价值的逻辑迁入正式 CLI；剩余脚本改成薄封装或标记为 legacy，不再承担核心实现

## Test Plan

必须用 `pixi` 跑完整验证，至少覆盖下面这些场景：

- 包安装与导入
  - `pixi run python -c "import macro_lactone_toolkit"`
  - `pixi run pytest`
- 环识别
  - 单一有效环尺寸的 12、14、16、20 元环样例
  - 无效分子
  - 无内酯键分子
  - 多有效环尺寸分子，确认抛出 `AmbiguousMacrolactoneError`
- 编号
  - 位置 1 必须是羰基碳
  - 位置 2 必须是酯键氧
  - 编号范围必须连续覆盖 `1..N`
  - 同一分子多次运行结果一致
- 裂解
  - 无侧链位置返回空列表
  - 有侧链位置返回碎片
  - `fragment_smiles_labeled` 与 `fragment_smiles_plain` 都能被 RDKit 成功读取
  - labeled dummy 保留位置号
  - dummy 键型与原键型一致
- CLI
  - `analyze`、`number`、`fragment` 的基本 smoke test
  - CSV 批处理
  - 歧义分子跳过与错误日志行为
- 回归
  - 现有 splicing engine 若继续保留，至少保留一个 dummy 拼接回组装测试，确保 labeled dummy 方案可持续扩展

## Assumptions

- `git pull` 使用 `--ff-only`，避免在重构开始前引入不透明 merge commit
- 本次不保留 `src.*` 导入兼容层，全面切换到 `macro_lactone_toolkit`
- 本次把“正式库 API + 正式 CLI + pixi 测试”作为主交付；notebooks 只做参考，不作为正式接口
- 发现多个有效大环尺寸时，默认视为歧义并失败，不自动猜测
- 继续使用 RDKit 作为唯一结构解析与序列化基础
