feat(toolkit): ship macro_lactone_toolkit package

Unify macrolactone detection, numbering, fragmentation, and
splicing under the installable macro_lactone_toolkit package.

- replace legacy src.* modules with the new package layout
- add analyze/number/fragment CLI entrypoints and pixi tasks
- migrate tests, README, and scripts to the new package API
This commit is contained in:
2026-03-18 22:06:45 +08:00
parent a768d26e47
commit 5e7b236f31
45 changed files with 1302 additions and 6304 deletions

View File

@@ -0,0 +1,53 @@
from rdkit import Chem
from macro_lactone_toolkit import MacrolactoneFragmenter
from .helpers import build_macrolactone
def test_fragmentation_returns_empty_list_without_sidechains():
built = build_macrolactone(12)
result = MacrolactoneFragmenter().fragment_molecule(built.smiles, parent_id="plain")
assert result.fragments == []
def test_fragmentation_emits_labeled_and_plain_smiles_round_trip():
built = build_macrolactone(16, {5: "ethyl", 8: "methyl"})
result = MacrolactoneFragmenter().fragment_molecule(built.smiles, parent_id="mol_001")
assert result.parent_id == "mol_001"
assert result.ring_size == 16
assert {fragment.cleavage_position for fragment in result.fragments} == {5, 8}
for fragment in result.fragments:
labeled = Chem.MolFromSmiles(fragment.fragment_smiles_labeled)
plain = Chem.MolFromSmiles(fragment.fragment_smiles_plain)
assert labeled is not None
assert plain is not None
assert Chem.MolToSmiles(labeled, isomericSmiles=True)
assert Chem.MolToSmiles(plain, isomericSmiles=True)
assert any(
atom.GetAtomicNum() == 0 and atom.GetIsotope() == fragment.cleavage_position
for atom in labeled.GetAtoms()
)
assert any(
atom.GetAtomicNum() == 0 and atom.GetIsotope() == 0
for atom in plain.GetAtoms()
)
def test_fragmentation_preserves_attachment_bond_type():
built = build_macrolactone(16, {6: "exocyclic_alkene"})
result = MacrolactoneFragmenter().fragment_molecule(built.smiles, parent_id="bond_type")
fragment = next(fragment for fragment in result.fragments if fragment.cleavage_position == 6)
labeled = Chem.MolFromSmiles(fragment.fragment_smiles_labeled)
plain = Chem.MolFromSmiles(fragment.fragment_smiles_plain)
for mol in (labeled, plain):
dummy_atom = next(atom for atom in mol.GetAtoms() if atom.GetAtomicNum() == 0)
neighbor = dummy_atom.GetNeighbors()[0]
bond = mol.GetBondBetweenAtoms(dummy_atom.GetIdx(), neighbor.GetIdx())
assert bond.GetBondType() == Chem.BondType.DOUBLE