phase 14-15: docker build validation and helm deployment

This commit is contained in:
Celes Renata
2026-04-11 11:59:45 -07:00
parent 7394d241c9
commit ce10afa034
179 changed files with 32559 additions and 576 deletions
+165
View File
@@ -0,0 +1,165 @@
"""Tests for contradiction detection and disagreement representation.
Requirements: 6.4, 6.5
"""
from datetime import datetime, timezone
from services.aggregation.contradiction import (
CatalystEntry,
ContradictionResult,
detect_contradictions,
)
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, sentiment: float, impact: float = 0.5) -> WeightedSignal:
"""Helper to build a WeightedSignal with default scoring."""
w = compute_signal_weight(NOW, NOW, "7d", 0.8, extraction_confidence=0.8)
return WeightedSignal(doc_id, w, sentiment_value=sentiment, impact_score=impact)
# ---------------------------------------------------------------------------
# Overall score (backward compat with compute_contradiction_score)
# ---------------------------------------------------------------------------
def test_no_signals_returns_zero():
result = detect_contradictions([])
assert result.score == 0.0
assert result.details == []
def test_all_positive_no_contradiction():
signals = [_sw("d1", 1.0), _sw("d2", 1.0)]
result = detect_contradictions(signals)
assert result.score == 0.0
assert len(result.details) == 0
def test_equal_opposing_gives_half():
signals = [_sw("d1", 1.0, 0.5), _sw("d2", -1.0, 0.5)]
result = detect_contradictions(signals)
assert abs(result.score - 0.5) < 1e-4
def test_neutral_signals_ignored():
signals = [_sw("d1", 0.0), _sw("d2", 0.0)]
result = detect_contradictions(signals)
assert result.score == 0.0
assert result.details == []
# ---------------------------------------------------------------------------
# Sentiment disagreement detail
# ---------------------------------------------------------------------------
def test_sentiment_disagreement_detail_present():
signals = [_sw("d1", 1.0, 0.6), _sw("d2", -1.0, 0.4)]
result = detect_contradictions(signals)
sentiments = [d for d in result.details if d.dimension == "sentiment"]
assert len(sentiments) == 1
detail = sentiments[0]
assert detail.positive_doc_ids == ["d1"]
assert detail.negative_doc_ids == ["d2"]
assert detail.positive_weight > 0
assert detail.negative_weight > 0
assert "positive" in detail.description.lower() or "sentiment" in detail.description.lower()
def test_no_sentiment_detail_when_all_agree():
signals = [_sw("d1", 1.0), _sw("d2", 1.0)]
result = detect_contradictions(signals)
sentiments = [d for d in result.details if d.dimension == "sentiment"]
assert len(sentiments) == 0
# ---------------------------------------------------------------------------
# Catalyst disagreement detail
# ---------------------------------------------------------------------------
def test_catalyst_disagreement_detected():
signals = [_sw("d1", 1.0, 0.7), _sw("d2", -1.0, 0.5)]
entries = [
CatalystEntry("d1", "earnings"),
CatalystEntry("d2", "earnings"),
]
result = detect_contradictions(signals, entries)
catalyst_details = [d for d in result.details if d.dimension.startswith("catalyst:")]
assert len(catalyst_details) == 1
assert catalyst_details[0].dimension == "catalyst:earnings"
assert catalyst_details[0].positive_doc_ids == ["d1"]
assert catalyst_details[0].negative_doc_ids == ["d2"]
def test_no_catalyst_disagreement_when_same_sentiment():
signals = [_sw("d1", 1.0), _sw("d2", 1.0)]
entries = [
CatalystEntry("d1", "earnings"),
CatalystEntry("d2", "earnings"),
]
result = detect_contradictions(signals, entries)
catalyst_details = [d for d in result.details if d.dimension.startswith("catalyst:")]
assert len(catalyst_details) == 0
def test_catalyst_disagreement_across_types():
"""Different catalyst types with internal disagreement each get a detail."""
signals = [
_sw("d1", 1.0, 0.5),
_sw("d2", -1.0, 0.5),
_sw("d3", 1.0, 0.5),
_sw("d4", -1.0, 0.5),
]
entries = [
CatalystEntry("d1", "earnings"),
CatalystEntry("d2", "earnings"),
CatalystEntry("d3", "product"),
CatalystEntry("d4", "product"),
]
result = detect_contradictions(signals, entries)
catalyst_details = [d for d in result.details if d.dimension.startswith("catalyst:")]
dims = {d.dimension for d in catalyst_details}
assert "catalyst:earnings" in dims
assert "catalyst:product" in dims
# ---------------------------------------------------------------------------
# Integration with assemble_trend_summary
# ---------------------------------------------------------------------------
def test_trend_summary_includes_disagreement_details():
"""assemble_trend_summary should populate disagreement_details."""
from datetime import timedelta
from services.aggregation.worker import (
ImpactRow,
assemble_trend_summary,
build_weighted_signals,
)
impacts = [
ImpactRow(
document_id="d1", confidence=0.8, novelty_score=0.5,
source_credibility=0.8, sentiment="positive", impact_score=0.7,
catalyst_type="earnings", key_facts=[], risks=[],
published_at=NOW - timedelta(hours=1),
),
ImpactRow(
document_id="d2", confidence=0.8, novelty_score=0.5,
source_credibility=0.8, sentiment="negative", impact_score=0.7,
catalyst_type="earnings", key_facts=[], risks=[],
published_at=NOW - timedelta(hours=2),
),
]
signals = build_weighted_signals(impacts, NOW, "7d")
summary = assemble_trend_summary("AAPL", "7d", signals, impacts, reference_time=NOW)
assert summary.contradiction_score > 0
assert len(summary.disagreement_details) > 0
dims = {d.dimension for d in summary.disagreement_details}
assert "sentiment" in dims