"""Bayesian log-odds fusion across text detectors.""" from text_detection.fusion import TextEnsembleFusion from text_detection.schema import DetectionResult, Verdict def _det(name, p_fake, verdict, confidence=0.8, error=None): return DetectionResult( detector=name, p_fake=p_fake, verdict=verdict, confidence=confidence, error=error, evidence=[f"{name} says {p_fake}"], ) def test_no_usable_results_returns_uncertain_at_zero_confidence(): fused = TextEnsembleFusion().fuse( [_det("text_fastdetectgpt", 0.9, Verdict.AI_GENERATED, error="boom")] ) assert fused.verdict == Verdict.UNCERTAIN assert fused.confidence == 0.0 assert fused.p_fake == 0.5 def test_empty_input_returns_uncertain(): fused = TextEnsembleFusion().fuse([]) assert fused.verdict == Verdict.UNCERTAIN assert fused.confidence == 0.0 def test_abstaining_detector_is_ignored(): # A zero-confidence abstain must not drag a confident AI verdict to 0.5. results = [ _det("text_fastdetectgpt", 0.95, Verdict.AI_GENERATED, confidence=0.9), _det("text_provenance", 0.5, Verdict.UNCERTAIN, confidence=0.0), ] fused = TextEnsembleFusion().fuse(results) assert fused.verdict == Verdict.AI_GENERATED assert fused.p_fake > 0.65 def test_high_weight_watermark_outvotes_zeroshot(): # watermark weight 4.0 vs fastdetectgpt 1.5: the watermark should dominate. results = [ _det("text_watermark", 0.98, Verdict.AI_GENERATED, confidence=0.95), _det("text_fastdetectgpt", 0.30, Verdict.REAL, confidence=0.40), ] fused = TextEnsembleFusion().fuse(results) assert fused.verdict == Verdict.AI_GENERATED def test_conflict_is_flagged_and_penalises_confidence(): results = [ _det("text_watermark", 0.95, Verdict.AI_GENERATED, confidence=0.9), _det("text_fastdetectgpt", 0.05, Verdict.REAL, confidence=0.9), ] fused = TextEnsembleFusion().fuse(results) assert any("disagreement" in u.lower() for u in fused.uncertainty_factors) def test_generator_is_propagated(): r = _det("text_watermark", 0.98, Verdict.AI_GENERATED, confidence=0.95) r.generator = "watermarked-LLM" fused = TextEnsembleFusion().fuse([r]) assert fused.generator == "watermarked-LLM" def test_processing_time_is_carried_through(): fused = TextEnsembleFusion().fuse( [_det("text_fastdetectgpt", 0.9, Verdict.AI_GENERATED)], processing_time_ms=42.0 ) assert fused.processing_time_ms == 42.0