CN: MF split normalised by per-probe reference BAF noise; genotype-based complete loss (CYTO-041)
ci / test (3.12) (push) Failing after 9s
ci / test (3.13) (push) Failing after 11s

The mosaic BAF split is now estimated over heterozygous calls against each probe's expected noise (the
reference heterozygous spread scaled to the sample), inverting the median of |N(+-d, sigma)|. A mosaic loss
lacking the heterozygous calls its PFB predicts gets MF 1 and is promoted (CYTO-020).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
kemalandClaude Opus 5.5 committed 2026-09-29 10:56:21 +01:00
1 parent 19bc6ca986
commit 662addf5eb
4 files changed
+90 -31

No files matched your search

+9 -6
View File
@@ -12,7 +12,7 @@ from typer.testing import CliRunner
from simurg.cli.main import app
from simurg.cn import hmm
from simurg.cn.events import Event, Thresholds, _mf_from_baf, apply_thresholds, effective_size, extract
from simurg.cn.events import Event, Thresholds, _mf_from_baf, apply_thresholds, complete_loss, effective_size, extract
from simurg.cn.gcwave import gc_correct
from simurg.cn.model import gc_fractions, population_bfreq
from simurg.formats.gtc import Gtc, write_gtc
@@ -237,12 +237,15 @@ def test_mf_refinements() -> None: # CYTO-018 / CYTO-041
b = 0.5 + rng.normal(0, sd, 400)
mf = _mf_from_baf("MDEL", b, sd)
assert mf is not None and mf < 0.08
assert (_mf_from_baf("MDEL", b, 0.0) or 0) > 0.05 # uncorrected, the old estimate was biased upwards
assert (_mf_from_baf("MDEL", b) or 0) > 0.05 # uncorrected, the old estimate was biased upwards
# a true 40 % mosaic loss is still recovered
d = 0.4 / (2 * (2 - 0.4))
b = 0.5 + rng.choice([-d, d], 400) + rng.normal(0, sd, 400)
assert abs((_mf_from_baf("MDEL", b, sd) or 0) - 0.4) < 0.08
# no heterozygotes where ~50 are expected: a complete (constitutional) loss
b = rng.choice([0.0, 1.0], 100) + rng.normal(0, 0.01, 100)
assert _mf_from_baf("MDEL", np.clip(b, 0, 1), sd, np.full(100, 0.3)) == 1.0
assert _mf_from_baf("MDEL", np.clip(b, 0, 1), sd, np.full(100, 0.01)) is None # too few expected
# no heterozygous calls where ~12 are expected: a complete (constitutional) loss
hom = np.full(30, 1, dtype=np.uint8)
assert complete_loss(hom, np.full(30, 0.3))
assert not complete_loss(hom, np.full(30, 0.02)) # too few expected
mixed = hom.copy()
mixed[:8] = 2
assert not complete_loss(mixed, np.full(30, 0.3))