Source code for buildcompiler.planning.domestication
"""Deterministic domestication planning helpers."""
from __future__ import annotations
from dataclasses import dataclass, field
import sbol2
from buildcompiler.domain import BuildWarning
from buildcompiler.planning.validation import classify_part_role
[docs]
@dataclass
class SequenceEditProposal:
source_identity: str
enzyme_name: str
site_sequence: str
position: int
original_sequence: str
proposed_sequence: str
reason: str
approved: bool = False
[docs]
@dataclass
class DomesticationPlan:
part_identity: str
part_display_id: str | None
part_role: str
backbone_identity: str | None = None
restriction_enzyme_name: str = "BsaI"
ligase_name: str = "T4_DNA_ligase"
sequence_edit_proposals: list[SequenceEditProposal] = field(default_factory=list)
warnings: list[BuildWarning] = field(default_factory=list)
[docs]
class DomesticationPlanner:
"""Pure planner for domestication requirements for a single part."""
_BSAI_SITES = ("GGTCTC", "GAGACC")
[docs]
def plan(self, part_component: sbol2.ComponentDefinition) -> DomesticationPlan:
part_role = classify_part_role(part_component)
if part_role is None:
raise ValueError(
"Unsupported domestication role for part "
f"{part_component.identity}; expected promoter/rbs/cds/terminator"
)
sequence = self._resolve_sequence(part_component)
proposals: list[SequenceEditProposal] = []
for site in self._BSAI_SITES:
start = 0
while True:
index = sequence.find(site, start)
if index < 0:
break
proposals.append(
SequenceEditProposal(
source_identity=part_component.identity,
enzyme_name="BsaI",
site_sequence=site,
position=index,
original_sequence=site,
proposed_sequence=f"{site[:-1]}A",
reason=(
"Internal BsaI recognition site detected; "
"human-reviewed edit required."
),
)
)
start = index + 1
return DomesticationPlan(
part_identity=part_component.identity,
part_display_id=part_component.displayId,
part_role=part_role,
sequence_edit_proposals=proposals,
)
def _resolve_sequence(self, part_component: sbol2.ComponentDefinition) -> str:
for sequence_ref in part_component.sequences:
sequence_obj = (
part_component.doc.find(sequence_ref) if part_component.doc else None
)
elements = getattr(sequence_obj, "elements", None)
if isinstance(elements, str) and elements:
return elements.upper()
raise ValueError(
f"Part {part_component.identity} is missing a usable DNA sequence"
)
[docs]
def select_deterministic_flanking_sequence(
*,
source_sequence: str,
flank_length: int,
forbidden_sites: tuple[str, ...] = ("GGTCTC", "GAGACC"),
) -> str:
"""TODO: choose deterministic domestication flanks that avoid forbidden sites."""
raise NotImplementedError(
"Deterministic domestication flanking sequence selection is a planned TODO."
)