mrna-design-studio / tests /test_candidate_score.py
offtargeteffect's picture
Replace clustering with Candidate Analysis tab
2038bdc verified
Raw
History Blame Contribute Delete
3.36 kB
"""Tests for candidate objective scoring."""
from types import SimpleNamespace
import pytest
from core.analysis.candidate_score import score_objectives, OBJECTIVE_WEIGHTS
def _report(cai=0.8, kozak="strong", gc=55.0, u_pct=20.0, stretches=0,
enzymes=None, flags=None, structure_stub=True):
return SimpleNamespace(
cai=cai,
kozak=SimpleNamespace(strength=kozak) if kozak else None,
gc_percent_global=gc,
uridine=SimpleNamespace(u_percent=u_pct, high_u_stretches=[(0, 1, 1)] * stretches),
restriction_enzymes_present=enzymes or [],
structure=SimpleNamespace(is_stub=structure_stub, mfe=0.0, sequence=""),
liability=SimpleNamespace(flags=flags or []),
)
def _flag(category, severity):
return SimpleNamespace(category=category, severity=severity)
class TestObjectiveScores:
def test_ideal_candidate_scores_high(self):
s = score_objectives(_report())
assert s.expression > 80
assert s.immunogenicity > 90 # low uridine
assert s.manufacturability == 100 # no liabilities
assert s.overall > 80
def test_weights_sum_to_one(self):
assert abs(sum(OBJECTIVE_WEIGHTS.values()) - 1.0) < 1e-9
def test_overall_is_weighted_mean(self):
s = score_objectives(_report())
expected = (
OBJECTIVE_WEIGHTS["expression"] * s.expression
+ OBJECTIVE_WEIGHTS["stability"] * s.stability
+ OBJECTIVE_WEIGHTS["immunogenicity"] * s.immunogenicity
+ OBJECTIVE_WEIGHTS["manufacturability"] * s.manufacturability
)
assert s.overall == pytest.approx(expected, abs=0.5)
def test_low_cai_and_weak_kozak_drop_expression(self):
hi = score_objectives(_report(cai=0.9, kozak="strong"))
lo = score_objectives(_report(cai=0.3, kozak="weak"))
assert lo.expression < hi.expression
def test_high_uridine_lowers_immunogenicity_score(self):
clean = score_objectives(_report(u_pct=18.0))
hot = score_objectives(_report(u_pct=45.0, stretches=2))
assert hot.immunogenicity < clean.immunogenicity
def test_restriction_sites_lower_manufacturability(self):
s = score_objectives(_report(enzymes=["EcoRI", "BamHI"]))
assert s.manufacturability < 100
def test_homopolymer_flag_hits_stability_and_manufacturability(self):
s = score_objectives(_report(flags=[_flag("Homopolymer", "critical")]))
clean = score_objectives(_report())
assert s.stability < clean.stability
assert s.manufacturability < clean.manufacturability
def test_missing_metrics_are_neutral_not_zero(self):
s = score_objectives(SimpleNamespace()) # empty report
for v in (s.expression, s.stability, s.immunogenicity, s.manufacturability):
assert 0 < v <= 100
def test_scores_bounded_0_100(self):
s = score_objectives(_report(cai=0.0, kozak="weak", gc=10.0, u_pct=90.0,
stretches=10, enzymes=["A", "B", "C", "D"],
flags=[_flag("Homopolymer", "critical"),
_flag("GC", "critical"), _flag("Motif", "warning")]))
for v in (s.expression, s.stability, s.immunogenicity, s.manufacturability, s.overall):
assert 0 <= v <= 100