35 KiB
News Domain Completion - Task Implementation Guide
Overview
Complete the final 5% of the news domain by implementing Dagster orchestration, OpenRouter-powered LLM sentiment analysis, vector embeddings, and RAG-powered semantic search. This builds on 95% complete infrastructure with PostgreSQL + TimescaleDB + pgvectorscale stack.
Total Estimated Time: 15-20 hours with AI assistance Target Completion: 4-5 days Test Coverage Requirement: Maintain >85% Architecture Pattern: Entity → Repository → Service → Dagster Op → Dagster Job
Implementation Phases
Phase 1: Entity Layer (2-3 hours)
Database and entity layer enhancements for LLM integration
Phase 2: Repository Layer (2-3 hours)
RAG-powered vector similarity search methods
Phase 3: LLM Integration (4-5 hours)
OpenRouter clients for sentiment and embeddings
Phase 4: Service Enhancement (2-3 hours)
Integrate LLM clients into NewsService workflow
Phase 5: Dagster Orchestration (3-4 hours)
Jobs, ops, schedules, and sensors for automated collection
Phase 6: Testing & Documentation (2-3 hours)
Integration tests, performance validation, and documentation updates
Task Breakdown
Phase 1: Entity Layer
T001: Enhance NewsArticle Dataclass - Sentiment Fields
Priority: Critical | Duration: 1-2 hours | Dependencies: None
Description: Add LLM sentiment fields to existing NewsArticle dataclass
Acceptance Criteria:
- Add
sentiment_confidence: Optional[float]field (0.0-1.0 range) - Add
sentiment_label: Optional[str]field ("positive", "negative", "neutral") - Update
to_entity()method to include new sentiment fields - Update
from_entity()method to populate new sentiment fields - Add
has_reliable_sentiment()helper method (confidence >= 0.6)
Implementation Details:
@dataclass
class NewsArticle:
# Existing fields...
sentiment_score: Optional[float] = None # Already exists
# New LLM sentiment fields
sentiment_confidence: Optional[float] = None # 0.0 to 1.0
sentiment_label: Optional[str] = None # "positive", "negative", "neutral"
# Vector fields already exist from 95% complete infrastructure
title_embedding: Optional[List[float]] = None
content_embedding: Optional[List[float]] = None
def has_reliable_sentiment(self) -> bool:
"""Check if sentiment analysis is reliable."""
return bool(
self.sentiment_score is not None
and self.sentiment_confidence is not None
and self.sentiment_confidence >= 0.6
)
Files to Modify:
/Users/martinrichards/code/TradingAgents/tradingagents/domains/news/news_repository.py(NewsArticle dataclass section)
Test Requirements:
- Dataclass instantiation with new fields
to_entity()andfrom_entity()roundtrip conversionhas_reliable_sentiment()validation logic- Edge cases (None values, boundary conditions)
T002: Database Migration - Sentiment Fields
Priority: Critical | Duration: 1 hour | Dependencies: T001
Description: Create Alembic migration to add sentiment fields to news_articles table
Acceptance Criteria:
- Create Alembic migration script
add_sentiment_fields.py - Add
sentiment_confidence FLOATcolumn (nullable) - Add
sentiment_label VARCHAR(20)column (nullable) - Add index on
sentiment_labelfor filtering - Migration tested with upgrade and downgrade
- Rollback capability verified
Implementation Details:
# alembic/versions/20250111_add_sentiment_fields.py
def upgrade():
op.add_column('news_articles', sa.Column('sentiment_confidence', sa.Float(), nullable=True))
op.add_column('news_articles', sa.Column('sentiment_label', sa.String(20), nullable=True))
op.create_index('idx_news_sentiment_label', 'news_articles', ['sentiment_label'])
def downgrade():
op.drop_index('idx_news_sentiment_label', table_name='news_articles')
op.drop_column('news_articles', 'sentiment_label')
op.drop_column('news_articles', 'sentiment_confidence')
Files to Create:
/Users/martinrichards/code/TradingAgents/alembic/versions/20250111_add_sentiment_fields.py
Test Requirements:
- Migration upgrade succeeds
- Migration downgrade succeeds
- Index is created properly
- Existing data remains intact
Phase 2: Repository Layer
T003: NewsRepository - Vector Similarity Search
Priority: Critical | Duration: 2-3 hours | Dependencies: T001, T002
Description: Add RAG-powered vector similarity search using pgvectorscale
Acceptance Criteria:
- Implement
find_similar_articles()method with cosine distance - Support similarity threshold filtering (0.0-1.0)
- Support optional symbol filtering
- Results ordered by similarity descending
- Proper async/await with session management
- Logging for debugging and monitoring
Implementation Details:
async def find_similar_articles(
self,
embedding: List[float],
limit: int = 10,
threshold: float = 0.7,
symbol: Optional[str] = None
) -> List[NewsArticle]:
"""
Find articles similar to given embedding using pgvectorscale cosine distance.
pgvectorscale operator: <=> for cosine distance
Cosine similarity = 1 - cosine_distance
"""
async with self.db_manager.get_session() as session:
# Build query with vector similarity
query = select(
NewsArticleEntity,
(1 - NewsArticleEntity.title_embedding.cosine_distance(embedding)).label('similarity')
).filter(
NewsArticleEntity.title_embedding.is_not(None)
)
# Optional symbol filter
if symbol:
query = query.filter(NewsArticleEntity.symbol == symbol)
# Filter by similarity threshold and order by distance
query = query.filter(
(1 - NewsArticleEntity.title_embedding.cosine_distance(embedding)) >= threshold
).order_by(
NewsArticleEntity.title_embedding.cosine_distance(embedding)
).limit(limit)
result = await session.execute(query)
rows = result.all()
articles = [NewsArticle.from_entity(row[0]) for row in rows]
logger.info(f"Found {len(articles)} similar articles (threshold={threshold})")
return articles
Files to Modify:
/Users/martinrichards/code/TradingAgents/tradingagents/domains/news/news_repository.py(add method to NewsRepository class)
Test Requirements:
- Vector similarity returns correct results with test data
- Similarity threshold filtering works correctly
- Symbol filtering works correctly
- Empty result handling
- Performance test (<1s for typical queries)
T004: NewsRepository - Batch Embedding Updates
Priority: Medium | Duration: 1 hour | Dependencies: T003
Description: Add efficient batch embedding update method
Acceptance Criteria:
- Implement
batch_update_embeddings()method - Use PostgreSQL bulk update operations
- Support title and content embeddings
- Update timestamp on modification
- Return count of updated articles
Implementation Details:
async def batch_update_embeddings(
self,
article_embeddings: List[Tuple[UUID, List[float], List[float]]]
) -> int:
"""Efficiently batch update embeddings for multiple articles."""
if not article_embeddings:
return 0
async with self.db_manager.get_session() as session:
stmt = update(NewsArticleEntity).where(
NewsArticleEntity.id == bindparam('article_id')
).values(
title_embedding=bindparam('title_emb'),
content_embedding=bindparam('content_emb'),
updated_at=func.now()
)
batch_data = [
{
'article_id': article_id,
'title_emb': title_emb,
'content_emb': content_emb
}
for article_id, title_emb, content_emb in article_embeddings
]
await session.execute(stmt, batch_data)
logger.info(f"Batch updated embeddings for {len(article_embeddings)} articles")
return len(article_embeddings)
Files to Modify:
/Users/martinrichards/code/TradingAgents/tradingagents/domains/news/news_repository.py
Test Requirements:
- Batch update modifies correct articles
- Performance test (sub-second for 50 articles)
- Empty list handling
- Database rollback on errors
Phase 3: LLM Integration
T005: OpenRouter Sentiment Client
Priority: Critical | Duration: 2-3 hours | Dependencies: T001
Description: Implement OpenRouter client for LLM sentiment analysis
Acceptance Criteria:
- OpenRouter API integration using
quick_think_llm(claude-3.5-haiku) - Structured JSON output: score, confidence, label, reasoning
- Financial news-focused prompts
- Exponential backoff retry logic (3 attempts)
- Keyword-based fallback on API failures
- Proper error handling and logging
Implementation Details:
@dataclass
class SentimentResult:
"""Result from sentiment analysis."""
score: float # -1.0 to 1.0
confidence: float # 0.0 to 1.0
label: str # "positive", "negative", "neutral"
reasoning: str
class OpenRouterSentimentClient:
"""Client for sentiment analysis via OpenRouter."""
def __init__(self, config: TradingAgentsConfig):
self.api_key = config.openrouter_api_key
self.model = config.quick_think_llm # claude-3.5-haiku
self.base_url = "https://openrouter.ai/api/v1/chat/completions"
async def analyze_sentiment(self, title: str, content: str) -> SentimentResult:
"""Analyze sentiment with fallback to keyword-based analysis."""
try:
prompt = self._build_sentiment_prompt(title, content)
response = await self._call_openrouter(prompt)
return self._parse_sentiment_response(response)
except Exception as e:
logger.warning(f"OpenRouter sentiment failed: {e}, using fallback")
return self._fallback_sentiment(title, content)
def _fallback_sentiment(self, title: str, content: str) -> SentimentResult:
"""Keyword-based fallback for sentiment analysis."""
text = f"{title} {content}".lower()
positive_keywords = ['gain', 'up', 'rise', 'growth', 'profit', 'beat']
negative_keywords = ['loss', 'down', 'fall', 'decline', 'miss', 'concern']
pos_count = sum(1 for kw in positive_keywords if kw in text)
neg_count = sum(1 for kw in negative_keywords if kw in text)
if pos_count > neg_count:
return SentimentResult(0.3, 0.5, "positive", "Keyword-based fallback")
elif neg_count > pos_count:
return SentimentResult(-0.3, 0.5, "negative", "Keyword-based fallback")
else:
return SentimentResult(0.0, 0.5, "neutral", "Keyword-based fallback")
Files to Create:
/Users/martinrichards/code/TradingAgents/tradingagents/domains/news/clients/openrouter_sentiment_client.py
Test Requirements:
- API response parsing tests with VCR
- Retry logic tests
- Fallback mechanism tests
- Error handling tests
- Integration test with real API (optional)
T006: OpenRouter Embeddings Client
Priority: Critical | Duration: 1-2 hours | Dependencies: T001
Description: Implement OpenRouter client for vector embeddings generation
Acceptance Criteria:
- OpenRouter embeddings API integration (text-embedding-ada-002)
- Text preprocessing (8000 char limit)
- Batch processing support for multiple texts
- 1536-dimensional vector validation
- Zero-vector fallback on API failures
- Proper error handling and logging
Implementation Details:
class OpenRouterEmbeddingsClient:
"""Client for generating embeddings via OpenRouter."""
def __init__(self, config: TradingAgentsConfig):
self.api_key = config.openrouter_api_key
self.model = "openai/text-embedding-ada-002" # Via OpenRouter
self.base_url = "https://openrouter.ai/api/v1/embeddings"
async def generate_embeddings(self, texts: List[str]) -> List[List[float]]:
"""Generate 1536-dim embeddings for multiple texts."""
if not texts:
return []
try:
processed_texts = [self._preprocess_text(text) for text in texts]
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {"model": self.model, "input": processed_texts}
async with aiohttp.ClientSession() as session:
async with session.post(
self.base_url,
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=60)
) as response:
response.raise_for_status()
data = await response.json()
embeddings = [item['embedding'] for item in data['data']]
# Validate dimensions
for i, emb in enumerate(embeddings):
if len(emb) != 1536:
raise ValueError(f"Invalid embedding dimension: {len(emb)}")
return embeddings
except Exception as e:
logger.error(f"Embeddings generation failed: {e}, using zero vectors")
return [[0.0] * 1536 for _ in texts]
async def generate_article_embeddings(
self,
article: NewsArticle
) -> Tuple[List[float], List[float]]:
"""Generate embeddings for article title and content."""
texts = []
if article.headline:
texts.append(article.headline)
if article.summary:
combined = f"{article.headline} {article.summary}"
texts.append(combined)
if not texts:
return [0.0] * 1536, [0.0] * 1536
embeddings = await self.generate_embeddings(texts)
title_embedding = embeddings[0] if len(embeddings) > 0 else [0.0] * 1536
content_embedding = embeddings[1] if len(embeddings) > 1 else [0.0] * 1536
return title_embedding, content_embedding
def _preprocess_text(self, text: str) -> str:
"""Preprocess text for optimal embedding generation."""
cleaned = " ".join(text.split())
return cleaned[:8000] # OpenAI embedding limit
Files to Create:
/Users/martinrichards/code/TradingAgents/tradingagents/domains/news/clients/openrouter_embeddings_client.py
Test Requirements:
- API response parsing tests with VCR
- Batch processing tests
- Vector dimension validation tests
- Text preprocessing tests
- Zero-vector fallback tests
T007: Enhance NewsService - LLM Integration
Priority: Critical | Duration: 2-3 hours | Dependencies: T005, T006
Description: Integrate OpenRouter LLM clients into NewsService workflow
Acceptance Criteria:
- Add LLM clients to NewsService
__init__() - Implement
_enrich_articles()method for LLM processing - Update
update_company_news()to call enrichment - Implement
find_similar_news()for RAG queries - Best-effort processing (failures don't block storage)
- Proper error handling and logging
Implementation Details:
class NewsService:
def __init__(
self,
google_client: GoogleNewsClient,
repository: NewsRepository,
article_scraper: ArticleScraperClient,
sentiment_client: OpenRouterSentimentClient,
embeddings_client: OpenRouterEmbeddingsClient,
):
self.google_client = google_client
self.repository = repository
self.article_scraper = article_scraper
self.sentiment_client = sentiment_client
self.embeddings_client = embeddings_client
async def update_company_news(self, symbol: str) -> NewsUpdateResult:
"""
Update company news with full LLM enrichment pipeline.
Flow: RSS → Scrape → LLM Sentiment → Embeddings → Store
"""
# 1. Get RSS feed
google_articles = self.google_client.get_company_news(symbol)
# 2. Scrape content
scraped_articles = await self._scrape_articles(google_articles)
# 3. Enrich with LLM (sentiment + embeddings)
enriched_articles = await self._enrich_articles(scraped_articles)
# 4. Store in repository
stored_articles = await self.repository.upsert_batch(enriched_articles, symbol)
return NewsUpdateResult(...)
async def _enrich_articles(
self,
articles: List[NewsArticle]
) -> List[NewsArticle]:
"""Enrich articles with LLM sentiment and vector embeddings."""
enriched = []
for article in articles:
try:
# Generate sentiment
sentiment_result = await self.sentiment_client.analyze_sentiment(
article.headline,
article.summary or ""
)
article.sentiment_score = sentiment_result.score
article.sentiment_confidence = sentiment_result.confidence
article.sentiment_label = sentiment_result.label
# Generate embeddings
title_emb, content_emb = await self.embeddings_client.generate_article_embeddings(article)
article.title_embedding = title_emb
article.content_embedding = content_emb
enriched.append(article)
except Exception as e:
logger.warning(f"Failed to enrich article {article.url}: {e}")
enriched.append(article) # Store without enrichment
return enriched
async def find_similar_news(
self,
query_text: str,
symbol: Optional[str] = None,
limit: int = 5
) -> List[NewsArticle]:
"""Find news articles similar to query text using RAG vector search."""
# Generate embedding for query
query_embeddings = await self.embeddings_client.generate_embeddings([query_text])
query_embedding = query_embeddings[0]
# Search for similar articles
similar_articles = await self.repository.find_similar_articles(
embedding=query_embedding,
limit=limit,
threshold=0.7,
symbol=symbol
)
return similar_articles
Files to Modify:
/Users/martinrichards/code/TradingAgents/tradingagents/domains/news/news_service.py
Test Requirements:
- Mock LLM clients for unit tests
- Integration test with real services
- Error handling and fallback tests
- Performance test for batch enrichment
Phase 4: Dagster Orchestration
T008: Dagster Directory Structure
Priority: High | Duration: 30 minutes | Dependencies: None
Description: Create directory structure for Dagster jobs, ops, and schedules
Acceptance Criteria:
- Create
tradingagents/data/directory - Create subdirectories:
jobs/,ops/,schedules/,sensors/ - Create
__init__.pyfiles for all directories - Import structure allows clean imports
Implementation Details:
tradingagents/data/
├── __init__.py
├── jobs/
│ ├── __init__.py
│ └── news_collection.py
├── ops/
│ ├── __init__.py
│ └── news_ops.py
├── schedules/
│ ├── __init__.py
│ └── news_schedules.py
└── sensors/
├── __init__.py
└── news_sensors.py
Files to Create:
- All directory and
__init__.pyfiles above
Test Requirements:
- Import tests for all modules
- Directory structure validation
T009: Dagster Ops - News Collection
Priority: High | Duration: 2-3 hours | Dependencies: T007, T008
Description: Implement Dagster op for news collection per symbol
Acceptance Criteria:
collect_news_for_symbolop implemented- Proper resource management (database_manager)
- Error handling and logging
- Output metadata (articles_found, articles_scraped, etc.)
- Retry policy configured
- Op tested with build_op_context
Implementation Details:
# tradingagents/data/ops/news_ops.py
from dagster import op, OpExecutionContext, Out, RetryPolicy
@op(
required_resource_keys={"database_manager"},
out=Out(dict),
tags={"kind": "news", "domain": "news"},
retry_policy=RetryPolicy(max_retries=3, delay=10, backoff=BackoffPolicy.EXPONENTIAL),
)
def collect_news_for_symbol(context: OpExecutionContext, symbol: str) -> dict:
"""
Collect and process news for a single stock symbol.
Returns dict with collection statistics.
"""
context.log.info(f"Starting news collection for {symbol}")
try:
config = TradingAgentsConfig.from_env()
db_manager = context.resources.database_manager
news_service = NewsService.build(db_manager, config)
result = await news_service.update_company_news(symbol)
context.log.info(f"Completed: {result.articles_scraped} articles for {symbol}")
return {
"symbol": symbol,
"articles_found": result.articles_found,
"articles_scraped": result.articles_scraped,
"articles_failed": result.articles_failed,
"status": result.status,
}
except Exception as e:
context.log.error(f"News collection failed for {symbol}: {e}")
raise
Files to Create:
/Users/martinrichards/code/TradingAgents/tradingagents/data/ops/news_ops.py
Test Requirements:
- Op execution tests with mock resources
- Error handling tests
- Retry logic tests
- Metadata validation tests
T010: Dagster Job - Daily News Collection
Priority: High | Duration: 1-2 hours | Dependencies: T009
Description: Implement Dagster job that orchestrates news collection across symbols
Acceptance Criteria:
news_collection_dailyjob implemented- Dynamic op mapping for parallel symbol processing
- Proper job tags and metadata
- Configuration for symbol list
- Job tested with execute_in_process
Implementation Details:
# tradingagents/data/jobs/news_collection.py
from dagster import job, DynamicOut, DynamicOutput, OpExecutionContext, op
from tradingagents.data.ops.news_ops import collect_news_for_symbol
@op(out=DynamicOut())
def get_symbols_to_collect(context: OpExecutionContext) -> Generator[DynamicOutput, None, None]:
"""Get list of symbols to collect news for from config."""
symbols = context.op_config.get("symbols", ["AAPL", "GOOGL", "MSFT", "TSLA"])
context.log.info(f"Collecting news for {len(symbols)} symbols: {symbols}")
for symbol in symbols:
yield DynamicOutput(symbol, mapping_key=symbol)
@job(tags={"dagster/priority": "high", "domain": "news"})
def news_collection_daily():
"""
Daily news collection job for all configured symbols.
Workflow:
1. Get symbols to collect
2. Fan out: collect news for each symbol in parallel
3. Aggregate results
"""
get_symbols_to_collect().map(collect_news_for_symbol)
Files to Create:
/Users/martinrichards/code/TradingAgents/tradingagents/data/jobs/news_collection.py
Test Requirements:
- Job execution tests
- Dynamic mapping tests
- Configuration tests
- Parallel execution validation
T011: Dagster Schedule - Daily Trigger
Priority: High | Duration: 1 hour | Dependencies: T010
Description: Implement Dagster schedule for daily news collection at 6 AM UTC
Acceptance Criteria:
news_collection_daily_scheduleschedule implemented- Cron expression:
0 6 * * *(daily at 6 AM UTC) - RunRequest configuration with symbol list
- Proper tags and metadata
- Schedule tested with evaluate_tick
Implementation Details:
# tradingagents/data/schedules/news_schedules.py
from dagster import schedule, ScheduleEvaluationContext, RunRequest
from tradingagents.data.jobs.news_collection import news_collection_daily
@schedule(
job=news_collection_daily,
cron_schedule="0 6 * * *", # Daily at 6 AM UTC
execution_timezone="UTC",
)
def news_collection_daily_schedule(context: ScheduleEvaluationContext):
"""Schedule for daily news collection at 6 AM UTC."""
return RunRequest(
run_key=f"news_collection_{context.scheduled_execution_time.isoformat()}",
run_config={
"ops": {
"get_symbols_to_collect": {
"config": {
"symbols": ["AAPL", "GOOGL", "MSFT", "TSLA", "AMZN", "META", "NVDA"]
}
}
}
},
tags={
"scheduled_time": context.scheduled_execution_time.isoformat(),
"job_type": "news_collection",
},
)
Files to Create:
/Users/martinrichards/code/TradingAgents/tradingagents/data/schedules/news_schedules.py
Test Requirements:
- Schedule evaluation tests
- Cron schedule validation
- RunRequest configuration tests
- Timezone handling tests
T012: Dagster Sensor - Failure Alerting
Priority: Medium | Duration: 1 hour | Dependencies: T010
Description: Implement Dagster sensor for job failure alerting
Acceptance Criteria:
news_collection_failure_sensorrun failure sensor implemented- Monitors
news_collection_dailyjob - Logs failure details
- Placeholder for external alerting (Slack, PagerDuty, etc.)
- Sensor tested with run failure events
Implementation Details:
# tradingagents/data/sensors/news_sensors.py
from dagster import run_failure_sensor, RunFailureSensorContext
from tradingagents.data.jobs.news_collection import news_collection_daily
@run_failure_sensor(
name="news_collection_failure_sensor",
monitored_jobs=[news_collection_daily],
)
def news_collection_failure_alert(context: RunFailureSensorContext):
"""Alert when news collection job fails."""
context.log.error(
f"News collection job failed!\n"
f"Run ID: {context.dagster_run.run_id}\n"
f"Failure: {context.failure_event.event_specific_data}"
)
# TODO: Implement external alerting
# send_slack_alert(...)
# send_pagerduty_alert(...)
Files to Create:
/Users/martinrichards/code/TradingAgents/tradingagents/data/sensors/news_sensors.py
Test Requirements:
- Sensor evaluation tests
- Failure detection tests
- Logging validation tests
Phase 5: Testing & Documentation
T013: Integration Tests - End-to-End Workflow
Priority: High | Duration: 2-3 hours | Dependencies: T007, T010
Description: Comprehensive integration tests for complete news domain workflow
Acceptance Criteria:
- End-to-end workflow test: RSS → Scrape → LLM → Vector → Store
- RAG query test: Vector similarity search with semantic matching
- AgentToolkit integration test
- Performance tests (< 2s queries, < 1s vector search)
- Error recovery and fallback tests
- Test coverage maintained above 85%
Implementation Details:
# tests/domains/news/integration/test_news_workflow.py
@pytest.mark.asyncio
async def test_complete_news_pipeline_end_to_end(test_db_manager):
"""Test complete pipeline: RSS → Scrape → LLM → Vector → Store."""
config = TradingAgentsConfig.from_test_env()
service = NewsService.build(test_db_manager, config)
# Execute full pipeline
result = await service.update_company_news("AAPL")
# Verify results
assert result.status == "completed"
assert result.articles_scraped > 0
# Verify database storage
articles = await service.repository.list_by_date_range(
symbol="AAPL",
start_date=date.today(),
end_date=date.today()
)
assert len(articles) > 0
# Verify LLM enrichment
for article in articles:
assert article.sentiment_score is not None
assert article.sentiment_confidence is not None
assert article.title_embedding is not None
assert len(article.title_embedding) == 1536
@pytest.mark.asyncio
async def test_rag_vector_similarity_search(test_db_manager):
"""Test RAG vector similarity search functionality."""
service = NewsService.build(test_db_manager, TradingAgentsConfig.from_test_env())
# Find similar articles
similar_articles = await service.find_similar_news(
query_text="Apple earnings beat expectations",
symbol="AAPL",
limit=5
)
assert len(similar_articles) <= 5
# Verify articles are relevant (high similarity scores)
@pytest.mark.asyncio
async def test_performance_benchmarks(test_db_manager):
"""Test performance meets requirements."""
repository = NewsRepository(test_db_manager)
# Test query performance (< 2s requirement)
start_time = time.time()
articles = await repository.list_by_date_range(
symbol="AAPL",
start_date=date.today() - timedelta(days=30),
end_date=date.today()
)
query_time = time.time() - start_time
assert query_time < 2.0, f"Query took {query_time}s, should be < 2s"
# Test vector similarity performance (< 1s requirement)
test_embedding = [0.1] * 1536
start_time = time.time()
similar = await repository.find_similar_articles(test_embedding, limit=10)
vector_time = time.time() - start_time
assert vector_time < 1.0, f"Vector search took {vector_time}s, should be < 1s"
Files to Create:
/Users/martinrichards/code/TradingAgents/tests/domains/news/integration/test_news_workflow.py
Test Requirements:
- All integration tests pass
- Performance benchmarks met
- Test coverage > 85%
T014: Dagster Tests
Priority: Medium | Duration: 1 hour | Dependencies: T010, T011
Description: Unit tests for Dagster ops, jobs, and schedules
Acceptance Criteria:
- Op execution tests with mocked resources
- Job execution tests
- Schedule evaluation tests
- Error handling tests
- All Dagster components tested
Implementation Details:
# tests/data/ops/test_news_ops.py
from dagster import build_op_context
from tradingagents.data.ops.news_ops import collect_news_for_symbol
def test_collect_news_for_symbol_op():
"""Test Dagster op for news collection."""
context = build_op_context(
resources={"database_manager": mock_database_manager}
)
result = collect_news_for_symbol(context, "AAPL")
assert result["symbol"] == "AAPL"
assert result["status"] == "completed"
assert result["articles_found"] >= 0
# tests/data/jobs/test_news_collection.py
from dagster import execute_in_process
from tradingagents.data.jobs.news_collection import news_collection_daily
def test_news_collection_daily_job():
"""Test Dagster job execution."""
result = execute_in_process(
news_collection_daily,
run_config={
"ops": {
"get_symbols_to_collect": {
"config": {"symbols": ["AAPL"]}
}
}
}
)
assert result.success
Files to Create:
/Users/martinrichards/code/TradingAgents/tests/data/ops/test_news_ops.py/Users/martinrichards/code/TradingAgents/tests/data/jobs/test_news_collection.py/Users/martinrichards/code/TradingAgents/tests/data/schedules/test_news_schedules.py
Test Requirements:
- All Dagster tests pass
- Coverage > 85% for Dagster code
T015: Documentation Updates
Priority: Medium | Duration: 1-2 hours | Dependencies: T013, T014
Description: Update documentation and monitoring for new functionality
Acceptance Criteria:
- Update API documentation for new methods
- Dagster job configuration examples
- Performance monitoring queries
- Troubleshooting guide for common issues
- AgentToolkit integration documentation
- README updates
Files to Modify:
/Users/martinrichards/code/TradingAgents/docs/domains/news.md/Users/martinrichards/code/TradingAgents/docs/api-reference.md/Users/martinrichards/code/TradingAgents/README.md
Test Requirements:
- Documentation accuracy validation
- Configuration example testing
- Link validation
Parallel Development Opportunities
AI Agent Collaboration Points
Tasks T005 & T006 can be developed in parallel:
- Both are independent OpenRouter client implementations
- Different LLM capabilities (sentiment vs embeddings)
- Can be tested independently with pytest-vcr
Tasks T009, T010, T011 can be developed in parallel after T008:
- Ops, jobs, and schedules are independent components
- Can be tested separately
- Integration testing happens in T014
Critical Path Analysis
Critical Path: T001 → T002 → T003 → T007 → T009 → T010 → T013
Parallel Branches:
- LLM Clients: T005 + T006 (parallel with T003-T004)
- Dagster Components: T009 + T010 + T011 (after T008)
- Testing: Unit tests alongside implementation
Success Metrics
Technical Metrics:
- Test coverage >85% maintained
- Query performance <2s for 30-day lookback
- Vector search performance <1s for top-10 results
- Zero breaking changes to AgentToolkit
- Dagster jobs execute successfully
Functional Metrics:
- OpenRouter LLM sentiment analysis operational
- Vector embeddings enable semantic search
- Dagster schedules running daily without failures
- Agent context enriched with sentiment and similarity
Quality Metrics:
- All acceptance criteria met for each task
- Comprehensive error handling and fallbacks
- Production-ready monitoring via Dagster UI
- Complete documentation for all new features
Implementation Guidelines
TDD Approach
Every task follows: Write test → Write code → Refactor
Layered Architecture Pattern
Strict adherence to: Entity → Repository → Service → Dagster Op → Dagster Job
Error Handling Strategy
Graceful fallbacks for all LLM API dependencies (keyword sentiment, zero vectors)
Performance Requirements
Async operations with proper connection pooling throughout
Testing Strategy
Unit tests + Integration tests + pytest-vcr for external API calls
Risk Mitigation Strategies
LLM API Dependencies
- Implement comprehensive fallback strategies
- Use pytest-vcr for deterministic testing
- Mock clients for unit tests
- Monitor API costs and rate limits
Database Performance
- Test with realistic data volumes
- Monitor query performance during development
- Use proper indexes for vector operations
- Regular performance profiling
Dagster Integration
- Start with simple ops and jobs
- Test incrementally before full integration
- Use Dagster UI for debugging
- Implement comprehensive logging
This comprehensive task breakdown provides clear implementation guidance for completing the final 5% of the news domain while maintaining architectural consistency with Dagster orchestration and leveraging AI-assisted development patterns.