offtargeteffect's picture
Deploy mRNA Design Studio (Docker SDK)
99f834c verified
Raw
History Blame Contribute Delete
5.1 kB
"""
Sequence part models for the parts library.
SequencePart is the base class; specialized part types inherit to add
bespoke fields (e.g., CDS has protein_sequence, codon_usage; UTRs have
stability metrics).
Parts are auto-extracted when sequences are analyzed or imported.
"""
from __future__ import annotations
import hashlib
import uuid
from dataclasses import dataclass, field
from typing import Any, Dict, Literal, Optional
@dataclass
class SequencePart:
"""
Base class for reusable sequence parts.
Each part can be mixed and matched in the Parts Workshop to
assemble new mRNA sequences.
"""
name: str
sequence: str
part_type: Literal["5_utr", "kozak", "cds", "3_utr", "polya"]
source: str # "database_import", "generated", "manual", "extracted"
id: str = field(default_factory=lambda: str(uuid.uuid4()))
origin_sequence_id: Optional[str] = None # ID of parent mRNASequence
metadata: Dict[str, Any] = field(default_factory=dict)
@property
def length(self) -> int:
return len(self.sequence)
@property
def sequence_hash(self) -> str:
"""SHA256 hash for deduplication."""
return hashlib.sha256(self.sequence.encode()).hexdigest()[:16]
def to_dict(self) -> Dict[str, Any]:
return {
"id": self.id,
"name": self.name,
"sequence": self.sequence,
"part_type": self.part_type,
"source": self.source,
"origin_sequence_id": self.origin_sequence_id,
"metadata": self.metadata,
}
@dataclass
class UTR5Part(SequencePart):
"""5' UTR part with stability and structure metrics."""
part_type: Literal["5_utr"] = field(default="5_utr", init=False)
# Predicted secondary structure stability
stability_score: Optional[float] = None
mfe: Optional[float] = None # Minimum free energy from ViennaRNA
secondary_structure_dot: Optional[str] = None # Dot-bracket notation
@dataclass
class KozakPart(SequencePart):
"""Kozak consensus sequence part."""
part_type: Literal["kozak"] = field(default="kozak", init=False)
# Consensus score (0-1) based on optimal Kozak pattern
consensus_score: Optional[float] = None
# Canonical Kozak is: gcc(A/G)ccATGG
matches_canonical: Optional[bool] = None
@dataclass
class CDSPart(SequencePart):
"""Coding sequence part with translation and codon metrics."""
part_type: Literal["cds"] = field(default="cds", init=False)
# Translated protein sequence
protein_sequence: Optional[str] = None
# Codon usage metrics
codon_usage: Optional[Dict[str, float]] = None # {codon: frequency}
cai: Optional[float] = None # Codon Adaptation Index
# Quality checks
has_start_codon: Optional[bool] = None
has_stop_codon: Optional[bool] = None
in_frame: Optional[bool] = None
@dataclass
class UTR3Part(SequencePart):
"""3' UTR part with stability and regulatory element annotations."""
part_type: Literal["3_utr"] = field(default="3_utr", init=False)
# Predicted stability
stability_score: Optional[float] = None
mfe: Optional[float] = None
# Regulatory elements (AU-rich elements, miRNA binding sites, etc.)
regulatory_elements: Optional[Dict[str, Any]] = None
@dataclass
class PolyAPart(SequencePart):
"""Poly(A) tail part."""
part_type: Literal["polya"] = field(default="polya", init=False)
# Length of the poly(A) stretch
tail_length: Optional[int] = None
# Purity (percentage of A nucleotides)
purity: Optional[float] = None
# Type alias for all part types
AnyPart = UTR5Part | KozakPart | CDSPart | UTR3Part | PolyAPart
def create_part_from_component(
sequence: str,
part_type: Literal["5_utr", "kozak", "cds", "3_utr", "polya"],
name: str,
source: str,
origin_sequence_id: Optional[str] = None,
) -> AnyPart:
"""
Factory function to create the appropriate Part subclass.
Used during auto-extraction from mRNASequence objects.
"""
if part_type == "5_utr":
return UTR5Part(
name=name,
sequence=sequence,
source=source,
origin_sequence_id=origin_sequence_id,
)
elif part_type == "kozak":
return KozakPart(
name=name,
sequence=sequence,
source=source,
origin_sequence_id=origin_sequence_id,
)
elif part_type == "cds":
return CDSPart(
name=name,
sequence=sequence,
source=source,
origin_sequence_id=origin_sequence_id,
)
elif part_type == "3_utr":
return UTR3Part(
name=name,
sequence=sequence,
source=source,
origin_sequence_id=origin_sequence_id,
)
elif part_type == "polya":
return PolyAPart(
name=name,
sequence=sequence,
source=source,
origin_sequence_id=origin_sequence_id,
)
else:
raise ValueError(f"Unknown part type: {part_type}")