- Add NewsHeadline and NewsResponse schemas
- Add NewsService for parsing news from Qdrant volatile collection
- Add GET /tools/news endpoint to tools controller
- News fetched from 'news' namespace in volatile_{user} collection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
114 lines
3.3 KiB
Python
114 lines
3.3 KiB
Python
"""
|
|
News data service for Tools domain.
|
|
|
|
Fetches news headlines from the Qdrant volatile collection.
|
|
"""
|
|
from datetime import datetime
|
|
from typing import Optional, Dict, Any, List
|
|
|
|
from src.shared.logging import get_logger
|
|
from src.shared.clients.qdrant_client import get_qdrant_client
|
|
from src.domains.tools.news.schemas import (
|
|
NewsHeadline,
|
|
NewsResponse,
|
|
)
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class NewsService:
|
|
"""
|
|
Service for fetching news data from Qdrant volatile collection.
|
|
|
|
Retrieves news headlines for a specific user.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize news service with Qdrant client."""
|
|
self.qdrant = get_qdrant_client()
|
|
|
|
def _parse_headlines(self, raw_data: Any) -> List[NewsHeadline]:
|
|
"""
|
|
Parse raw news data into list of NewsHeadline schemas.
|
|
|
|
Handles various formats from different news sources.
|
|
"""
|
|
if not raw_data:
|
|
return []
|
|
|
|
try:
|
|
# Handle dict with nested headlines list
|
|
headlines_list = raw_data
|
|
if isinstance(raw_data, dict):
|
|
headlines_list = raw_data.get("headlines") or raw_data.get("articles") or []
|
|
|
|
if not isinstance(headlines_list, list):
|
|
return []
|
|
|
|
headlines = []
|
|
for item in headlines_list:
|
|
if isinstance(item, dict):
|
|
headlines.append(NewsHeadline(
|
|
title=item.get("title", ""),
|
|
description=item.get("description") or item.get("summary"),
|
|
source=item.get("source") or item.get("provider"),
|
|
url=item.get("url") or item.get("link"),
|
|
))
|
|
elif isinstance(item, str):
|
|
# Simple string headlines
|
|
headlines.append(NewsHeadline(title=item))
|
|
|
|
return headlines
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Failed to parse news headlines: {e}")
|
|
return []
|
|
|
|
async def get_news(self, user: str = "default") -> NewsResponse:
|
|
"""
|
|
Get news headlines for a user.
|
|
|
|
Fetches news from the user's volatile collection.
|
|
|
|
Args:
|
|
user: User identifier (default: 'default')
|
|
|
|
Returns:
|
|
NewsResponse with headlines
|
|
"""
|
|
logger.info(f"Fetching news data for user: {user}")
|
|
|
|
# Get raw data from Qdrant
|
|
news_records = await self.qdrant.get_by_namespace(user, "news")
|
|
|
|
headlines = []
|
|
category = None
|
|
sources = None
|
|
|
|
if news_records:
|
|
raw_data = news_records[0].get("raw_data", {})
|
|
headlines = self._parse_headlines(raw_data)
|
|
if isinstance(raw_data, dict):
|
|
category = raw_data.get("category")
|
|
sources = raw_data.get("sources")
|
|
|
|
return NewsResponse(
|
|
headlines=headlines,
|
|
category=category,
|
|
sources=sources,
|
|
updated_at=datetime.utcnow(),
|
|
user=user,
|
|
)
|
|
|
|
|
|
# Singleton instance
|
|
_news_service: Optional[NewsService] = None
|
|
|
|
|
|
def get_news_service() -> NewsService:
|
|
"""Get or create singleton news service instance."""
|
|
global _news_service
|
|
if _news_service is None:
|
|
_news_service = NewsService()
|
|
return _news_service
|