copper-mind / tests /test_sentiment_aggregation.py
ifieryarrows's picture
Sync from GitHub
f64b002 verified
"""
Tests for sentiment aggregation logic.
"""
import pytest
import numpy as np
from datetime import datetime, timezone
def calculate_recency_weights(hours_list: list[float], tau: float = 12.0) -> list[float]:
"""
Calculate recency weights for testing.
Mirrors the logic in ai_engine.py
"""
weights = [np.exp(h / tau) for h in hours_list]
total = sum(weights)
return [w / total for w in weights]
class TestRecencyWeighting:
def test_later_gets_higher_weight(self):
"""Articles later in the day should get higher weight."""
hours = [9.0, 12.0, 16.0] # 9am, noon, 4pm
weights = calculate_recency_weights(hours)
# Later hour should have higher weight
assert weights[2] > weights[1] > weights[0]
def test_weights_sum_to_one(self):
"""Normalized weights should sum to 1."""
hours = [9.0, 12.0, 15.0, 18.0]
weights = calculate_recency_weights(hours)
assert abs(sum(weights) - 1.0) < 0.0001
def test_single_article_weight_one(self):
"""Single article should have weight of 1."""
weights = calculate_recency_weights([12.0])
assert weights[0] == 1.0
def test_tau_affects_spread(self):
"""Lower tau should increase weight spread."""
hours = [9.0, 16.0]
weights_high_tau = calculate_recency_weights(hours, tau=24.0)
weights_low_tau = calculate_recency_weights(hours, tau=6.0)
# With lower tau, the difference should be larger
spread_high = weights_high_tau[1] - weights_high_tau[0]
spread_low = weights_low_tau[1] - weights_low_tau[0]
assert spread_low > spread_high
class TestSentimentIndex:
def test_weighted_average(self):
"""Test weighted average calculation."""
scores = [0.5, -0.2, 0.3]
hours = [9.0, 14.0, 16.0]
weights = calculate_recency_weights(hours)
weighted_avg = sum(s * w for s, w in zip(scores, weights))
# Should be between min and max scores
assert min(scores) <= weighted_avg <= max(scores)
def test_all_positive_yields_positive(self):
"""All positive scores should yield positive index."""
scores = [0.3, 0.5, 0.4]
hours = [9.0, 12.0, 15.0]
weights = calculate_recency_weights(hours)
weighted_avg = sum(s * w for s, w in zip(scores, weights))
assert weighted_avg > 0
def test_all_negative_yields_negative(self):
"""All negative scores should yield negative index."""
scores = [-0.3, -0.5, -0.4]
hours = [9.0, 12.0, 15.0]
weights = calculate_recency_weights(hours)
weighted_avg = sum(s * w for s, w in zip(scores, weights))
assert weighted_avg < 0
def test_equal_positive_negative_near_zero(self):
"""Equal positive and negative with same timing should be near zero."""
scores = [0.5, -0.5]
hours = [12.0, 12.0] # Same time
weights = calculate_recency_weights(hours)
weighted_avg = sum(s * w for s, w in zip(scores, weights))
assert abs(weighted_avg) < 0.1
class TestSentimentLabel:
def test_bullish_threshold(self):
"""Positive sentiment above 0.1 should be Bullish."""
from app.inference import get_sentiment_label
assert get_sentiment_label(0.15) == "Bullish"
assert get_sentiment_label(0.5) == "Bullish"
def test_bearish_threshold(self):
"""Negative sentiment below -0.1 should be Bearish."""
from app.inference import get_sentiment_label
assert get_sentiment_label(-0.15) == "Bearish"
assert get_sentiment_label(-0.5) == "Bearish"
def test_neutral_range(self):
"""Sentiment between -0.1 and 0.1 should be Neutral."""
from app.inference import get_sentiment_label
assert get_sentiment_label(0.0) == "Neutral"
assert get_sentiment_label(0.05) == "Neutral"
assert get_sentiment_label(-0.05) == "Neutral"