from __future__ import annotations from dataclasses import dataclass, field, asdict from typing import Any, Optional @dataclass class LeadExample: example_id: str linear_sequence: str target_id: Optional[str] = None target_context: Optional[dict[str, Any]] = None protected_positions: list[int] = field(default_factory=list) preferred_property_direction: dict[str, str] = field(default_factory=dict) thresholds: dict[str, float] = field(default_factory=dict) known_active_motif_positions: Optional[list[int]] = None def to_dict(self) -> dict[str, Any]: return asdict(self) @dataclass class BuildingBlock: """Stapling building block. The geometry / motif fields drive CP-Composer-style feasibility: - `motif`: sequence-level pattern; for K↔D/E lactam stapling this is {"i_aa": ["K"], "j_aa": ["D","E"], "spacings": [3, 4]}. - `ca_window`: allowed Cα(i)-Cα(j) distance window in Å. `chemistry_class` is now restricted to {"stapled"} (head_to_tail / disulfide / bicycle were removed; the previous hydrocarbon i,i+4 / i,i+7 blocks were also removed because they fall outside CP-Composer's scope). """ block_id: str name: str chemistry_class: str synthetic_accessibility_score: float cost_score: float spps_score: float motif: Optional[dict[str, Any]] = None ca_window: tuple[float, float] = (4.0, 6.5) def to_dict(self) -> dict[str, Any]: d = asdict(self) d["ca_window"] = list(self.ca_window) return d @classmethod def from_dict(cls, d: dict[str, Any]) -> "BuildingBlock": d = dict(d) if "ca_window" in d and isinstance(d["ca_window"], list): d["ca_window"] = tuple(d["ca_window"]) # tolerate legacy catalogs by dropping retired fields for legacy in ("allowed_anchor_spacings", "compatible_residue_types", "token_substitution"): d.pop(legacy, None) return cls(**d)