"""Exon-aware annotation — alignment + exon mapping + NMD logic. NO NETWORK. We never call Ensembl: a GeneStructure is built by hand and the pure mapping functions are exercised directly. """ import random import pytest from dee.core.exon import ( Exon, GeneStructure, annotate_guides_with_exons, _align_input_to_cds, _exon_at_cds_position, ) # A deterministic, NON-repeating 300-nt CDS so 40-mer seeds align # unambiguously (a repeating sequence would make find() return the # wrong offset). Seed the RNG ONCE — instantiating Random(42) per # iteration would re-seed every draw and yield a constant base. _RNG = random.Random(42) CDS = "".join(_RNG.choice("ACGT") for _ in range(300)) GENE = GeneStructure( organism="human", gene_symbol="TESTG", transcript_id="ENST00000TEST", strand=1, cds_sequence=CDS, exons=[ Exon(number=1, chrom_start=1000, chrom_end=1099, cds_start=0, cds_end=100), Exon(number=2, chrom_start=2000, chrom_end=2099, cds_start=100, cds_end=200), Exon(number=3, chrom_start=3000, chrom_end=3099, cds_start=200, cds_end=300), ], last_junction_cds_pos=200, ) def test_align_exact_and_offset(): assert _align_input_to_cds(CDS, CDS) == 0 assert _align_input_to_cds(CDS[30:], CDS) == 30 def test_align_returns_none_for_short_or_unmatched(): assert _align_input_to_cds("ACGT", CDS) is None # < 30 nt assert _align_input_to_cds("", CDS) is None assert _align_input_to_cds("Z" * 50, CDS) is None # nothing valid def test_exon_at_cds_position(): assert _exon_at_cds_position(GENE, 50).number == 1 assert _exon_at_cds_position(GENE, 150).number == 2 assert _exon_at_cds_position(GENE, 250).number == 3 assert _exon_at_cds_position(GENE, 350) is None # past CDS end def _annotate_single(cut): return annotate_guides_with_exons(CDS, [(cut, "+", cut)], GENE)[0] def test_annotate_maps_cut_to_exon(): ctx = _annotate_single(50) assert ctx.in_cds is True assert ctx.exon_number == 1 assert ctx.distance_to_splice_5p == 50 # 50 - cds_start(0) assert ctx.distance_to_splice_3p == 49 # cds_end(100) - 50 - 1 assert "exon 1 of 3" in ctx.summary def test_nmd_zone_rule(): # PTC > 50 nt upstream of the last junction (cds pos 200) → NMD likely. assert _annotate_single(50).in_nmd_zone is True # 50 < 150 assert _annotate_single(250).in_nmd_zone is False # last exon, escapes NMD def test_cut_outside_cds_marked_not_in_cds(): ctx = _annotate_single(500) # beyond CDS length assert ctx.in_cds is False assert ctx.exon_number == 0 def test_annotation_order_matches_input(): cuts = [(50, "+", 50), (150, "+", 150), (250, "+", 250)] out = annotate_guides_with_exons(CDS, cuts, GENE) assert [c.exon_number for c in out] == [1, 2, 3]