"""News service — queries market_news from PostgreSQL via shared pool.""" from __future__ import annotations import asyncpg from typing import Any, Dict, List, Optional from server.db import get_pool def _row_to_dict(row: asyncpg.Record) -> Dict[str, Any]: """Convert an asyncpg Record to a plain dictionary with JSON-safe values.""" d: Dict[str, Any] = dict(row) if "published_at" in d and d["published_at"] is not None: d["published_at"] = d["published_at"].isoformat() return d async def get_news( limit: int = 20, offset: int = 0, category: Optional[str] = None, max_age_hours: int = 48, ) -> List[Dict[str, Any]]: """Fetch recent news articles from the market_news table.""" pool = await get_pool() params: List[Any] = [] param_idx = 1 base_query = ( "SELECT id, source, title, url, category, published_at " "FROM market_news " f"WHERE published_at > NOW() - INTERVAL '{int(max_age_hours)} hours'" ) if category is not None: base_query += f" AND category = ${param_idx}" params.append(category) param_idx += 1 base_query += f" ORDER BY published_at DESC LIMIT ${param_idx} OFFSET ${param_idx + 1}" params.append(limit) params.append(offset) async with pool.acquire() as conn: rows = await conn.fetch(base_query, *params) return [_row_to_dict(row) for row in rows] async def get_news_count( max_age_hours: int = 48, category: Optional[str] = None, ) -> int: """Return the total count of recent news articles.""" pool = await get_pool() params: List[Any] = [] param_idx = 1 query = ( "SELECT COUNT(*) AS cnt " "FROM market_news " f"WHERE published_at > NOW() - INTERVAL '{int(max_age_hours)} hours'" ) if category is not None: query += f" AND category = ${param_idx}" params.append(category) async with pool.acquire() as conn: row = await conn.fetchrow(query, *params) return int(row["cnt"]) if row else 0 async def get_categories(max_age_hours: int = 48) -> List[str]: """Return distinct categories from recent news articles.""" pool = await get_pool() query = ( "SELECT DISTINCT category " "FROM market_news " f"WHERE published_at > NOW() - INTERVAL '{int(max_age_hours)} hours' " "AND category IS NOT NULL " "ORDER BY category" ) async with pool.acquire() as conn: rows = await conn.fetch(query) return [row["category"] for row in rows]