"""Tests for evidence ranking — composite scoring for supporting/opposing docs. Requirements: 6.5 """ from datetime import datetime, timedelta, timezone from services.aggregation.evidence import ( EvidenceRankConfig, compute_evidence_rank, rank_evidence, rank_evidence_detailed, ) from services.aggregation.scoring import WeightedSignal, compute_signal_weight NOW = datetime(2026, 4, 11, 12, 0, 0, tzinfo=timezone.utc) def _sw( doc_id: str = "doc-1", sentiment: float = 1.0, impact: float = 0.7, credibility: float = 0.8, confidence: float = 0.8, age_hours: float = 1.0, ) -> WeightedSignal: published = NOW - timedelta(hours=age_hours) weight = compute_signal_weight( published_at=published, reference_time=NOW, window="7d", source_credibility=credibility, extraction_confidence=confidence, ) return WeightedSignal( document_id=doc_id, weight=weight, sentiment_value=sentiment, impact_score=impact, ) # --------------------------------------------------------------------------- # compute_evidence_rank # --------------------------------------------------------------------------- def test_rank_score_positive(): sig = _sw("d1", sentiment=1.0, impact=0.9, credibility=1.0) ranked = compute_evidence_rank(sig) assert ranked.rank_score > 0 assert ranked.document_id == "d1" assert ranked.sentiment_value == 1.0 def test_higher_impact_ranks_higher(): low = _sw("low", impact=0.3) high = _sw("high", impact=0.9) r_low = compute_evidence_rank(low) r_high = compute_evidence_rank(high) assert r_high.rank_score > r_low.rank_score def test_fresher_doc_ranks_higher(): old = _sw("old", age_hours=100.0) fresh = _sw("fresh", age_hours=1.0) r_old = compute_evidence_rank(old) r_fresh = compute_evidence_rank(fresh) assert r_fresh.rank_score > r_old.rank_score def test_higher_credibility_ranks_higher(): low_cred = _sw("low", credibility=0.2) high_cred = _sw("high", credibility=1.0) r_low = compute_evidence_rank(low_cred) r_high = compute_evidence_rank(high_cred) assert r_high.rank_score > r_low.rank_score # --------------------------------------------------------------------------- # rank_evidence # --------------------------------------------------------------------------- def test_rank_evidence_separates_sides(): signals = [ _sw("pos1", sentiment=1.0, impact=0.9), _sw("pos2", sentiment=1.0, impact=0.3), _sw("neg1", sentiment=-1.0, impact=0.7), _sw("neutral", sentiment=0.0, impact=0.5), ] supporting, opposing = rank_evidence(signals) assert "pos1" in supporting assert "pos2" in supporting assert "neg1" in opposing assert "neutral" not in supporting and "neutral" not in opposing def test_rank_evidence_ordered_by_composite(): signals = [ _sw("weak", sentiment=1.0, impact=0.2, credibility=0.3), _sw("strong", sentiment=1.0, impact=0.9, credibility=1.0), ] supporting, _ = rank_evidence(signals) assert supporting[0] == "strong" def test_rank_evidence_respects_max_refs(): signals = [_sw(f"d{i}", sentiment=1.0) for i in range(20)] cfg = EvidenceRankConfig(max_refs=3) supporting, opposing = rank_evidence(signals, config=cfg) assert len(supporting) == 3 assert len(opposing) == 0 def test_rank_evidence_empty(): supporting, opposing = rank_evidence([]) assert supporting == [] assert opposing == [] # --------------------------------------------------------------------------- # rank_evidence_detailed # --------------------------------------------------------------------------- def test_detailed_returns_ranked_evidence_objects(): signals = [ _sw("pos1", sentiment=1.0, impact=0.9), _sw("neg1", sentiment=-1.0, impact=0.7), ] sup, opp = rank_evidence_detailed(signals) assert len(sup) == 1 assert sup[0].document_id == "pos1" assert sup[0].rank_score > 0 assert len(opp) == 1 assert opp[0].document_id == "neg1"