locate-the-news / backend / app / routers / events.py
events.py
Raw
"""
Events router - provides news events with locations for the map
"""
from fastapi import APIRouter, Depends, Query, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime, timedelta

from ..database import get_db
from ..schemas import Event, EventLocation
from ..models import Article, LocationBase, Priority
from ..services.event_processor import EventProcessor
from ..utils.logger import setup_logger

logger = setup_logger(__name__)

router = APIRouter()


@router.get("/events", response_model=List[Event])
def get_events(
    min_lat: Optional[float] = Query(None, description="Minimum latitude for bounding box"),
    max_lat: Optional[float] = Query(None, description="Maximum latitude for bounding box"),
    min_lng: Optional[float] = Query(None, description="Minimum longitude for bounding box"),
    max_lng: Optional[float] = Query(None, description="Maximum longitude for bounding box"),
    priority: Optional[str] = Query(None, description="Filter by priority (low, medium, high, critical)"),
    sentiment: Optional[str] = Query(None, description="Filter by sentiment (positive, neutral, negative)"),
    limit: Optional[int] = Query(100, description="Maximum number of events to return"),
    db: Session = Depends(get_db)
):
    """
    Get news events with locations for display on the map.
    Supports filtering by bounding box, priority, and sentiment.
    """
    try:
        processor = EventProcessor(db)
        
        # Get events from database
        events = processor.get_processed_events(
            limit=limit,
            min_lat=min_lat,
            max_lat=max_lat,
            min_lng=min_lng,
            max_lng=max_lng
        )
        
        # Filter by priority if provided
        if priority:
            events = [e for e in events if e.priority == priority.lower()]
        
        # Filter by sentiment if provided
        if sentiment:
            events = [e for e in events if e.sentiment_label == sentiment.lower()]
        
        logger.info(f"Returning {len(events)} events")
        return events
        
    except Exception as e:
        logger.error(f"Error fetching events: {e}")
        raise HTTPException(status_code=500, detail=f"Error fetching events: {str(e)}")


@router.get("/events/{event_id}", response_model=Event)
def get_event(event_id: int, db: Session = Depends(get_db)):
    """
    Get a single event by ID.
    """
    try:
        article = db.query(Article).filter(
            Article.id == event_id,
            Article.is_processed == True
        ).first()
        
        if not article:
            raise HTTPException(status_code=404, detail="Event not found")
        
        if not article.locations:
            raise HTTPException(status_code=404, detail="Event has no location data")
        
        location = article.locations[0]
        
        return Event(
            id=article.id,
            title=article.title,
            link=article.link,
            summary=article.summary,
            published_date=article.published_date,
            source_name=article.source_name,
            priority=article.priority.value,
            sentiment_score=article.sentiment_score,
            sentiment_label=article.sentiment_label,
            location=EventLocation(
                latitude=location.latitude,
                longitude=location.longitude,
                entity_name=location.entity_name
            )
        )
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error fetching event {event_id}: {e}")
        raise HTTPException(status_code=500, detail=f"Error fetching event: {str(e)}")


@router.post("/refresh")
def refresh_news(
    background_tasks: BackgroundTasks,
    page_size: Optional[int] = Query(50, description="Number of articles to fetch"),
    db: Session = Depends(get_db)
):
    """
    Manually trigger news refresh from NewsAPI.
    Processes articles in the background.
    """
    def process_news():
        try:
            processor = EventProcessor(db)
            articles = processor.fetch_and_process_news(page_size=page_size)
            logger.info(f"Background task: Processed {len(articles)} articles")
        except Exception as e:
            logger.error(f"Background task error: {e}")
    
    background_tasks.add_task(process_news)
    
    return {
        "status": "success",
        "message": f"News refresh started. Processing up to {page_size} articles in the background."
    }


@router.get("/stats")
def get_stats(db: Session = Depends(get_db)):
    """
    Get statistics about the news database.
    """
    try:
        total_articles = db.query(Article).count()
        processed_articles = db.query(Article).filter(Article.is_processed == True).count()
        total_locations = db.query(LocationBase).count()
        
        # Sentiment distribution
        positive = db.query(Article).filter(Article.sentiment_label == 'positive').count()
        neutral = db.query(Article).filter(Article.sentiment_label == 'neutral').count()
        negative = db.query(Article).filter(Article.sentiment_label == 'negative').count()
        
        # Priority distribution
        critical = db.query(Article).filter(Article.priority == Priority.CRITICAL).count()
        high = db.query(Article).filter(Article.priority == Priority.HIGH).count()
        medium = db.query(Article).filter(Article.priority == Priority.MEDIUM).count()
        low = db.query(Article).filter(Article.priority == Priority.LOW).count()
        
        return {
            "total_articles": total_articles,
            "processed_articles": processed_articles,
            "total_locations": total_locations,
            "sentiment": {
                "positive": positive,
                "neutral": neutral,
                "negative": negative
            },
            "priority": {
                "critical": critical,
                "high": high,
                "medium": medium,
                "low": low
            }
        }
        
    except Exception as e:
        logger.error(f"Error fetching stats: {e}")
        raise HTTPException(status_code=500, detail=f"Error fetching stats: {str(e)}")