locate-the-news / backend / app / models.py
models.py
Raw
from sqlalchemy import Column, Integer, String, DateTime, Float, ForeignKey, Boolean, Enum as SQLEnum
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declared_attr
from datetime import datetime
import enum
from .database import Base

# --- Priority Enum ---

class Priority(enum.Enum):
    """News priority levels"""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"  # Deprem, katliam gibi önemli haberler

# --- Article Model ---

class Article(Base):
    """
    Represents a single news article fetched from an RSS feed.
    """
    __tablename__ = "articles"

    # Primary fields from RSS feed
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    link = Column(String, unique=True, index=True) # Unique link to prevent duplicates
    summary = Column(String)
    published_date = Column(DateTime, default=datetime.utcnow)
    source_name = Column(String) # e.g., "BBC News World"

    # Priority field for critical news (deprem, katliam, etc.)
    priority = Column(SQLEnum(Priority), default=Priority.MEDIUM, index=True)
    
    # Sentiment Analysis fields
    sentiment_score = Column(Float, nullable=True, index=True)  # -1 to 1
    sentiment_label = Column(String, nullable=True)  # positive, neutral, negative
    sentiment_analyzer = Column(String, nullable=True)  # textblob, vader, hybrid
    analyzed_at = Column(DateTime, nullable=True)

    # Status fields for processing pipeline
    is_processed = Column(Boolean, default=False) # Has NLP and Geocoding been run?
    processing_error = Column(String, nullable=True) # To store error if processing fails

    # Relationship to Locations: An article can be related to multiple locations
    locations = relationship("LocationBase", back_populates="article")

    def __repr__(self):
        return f"<Article(title='{self.title[:30]}...', link='{self.link}')>"

# --- Location Base Class (Abstract) ---

class LocationBase(Base):
    """
    Base class for all location types (City, Country, Continent).
    Uses single table inheritance pattern.
    """
    __tablename__ = "locations"

    id = Column(Integer, primary_key=True, index=True)
    
    # Foreign key link to the article
    article_id = Column(Integer, ForeignKey("articles.id"), nullable=True)
    article = relationship("Article", back_populates="locations")

    # Common fields for all location types
    entity_name = Column(String, index=True) # The text entity (e.g., "Paris", "Ukraine", "Europe")
    entity_type = Column(String) # The spaCy NER label (e.g., 'GPE', 'LOC')
    latitude = Column(Float, index=True)
    longitude = Column(Float, index=True)
    geocoder_source = Column(String, default="Nominatim")
    
    # Discriminator column for inheritance
    location_type = Column(String(20), nullable=False)  # 'city', 'country', 'continent'

    __mapper_args__ = {
        'polymorphic_identity': 'location',
        'polymorphic_on': location_type
    }

    def __repr__(self):
        return f"<{self.__class__.__name__}(entity='{self.entity_name}', coords=({self.latitude}, {self.longitude}))>"

# --- City Model ---

class City(LocationBase):
    """
    Represents a city location.
    """
    __mapper_args__ = {
        'polymorphic_identity': 'city',
    }
    
    country_id = Column(Integer, ForeignKey("locations.id"), nullable=True)
    population = Column(Integer, nullable=True)

# --- Country Model ---

class Country(LocationBase):
    """
    Represents a country location.
    """
    __mapper_args__ = {
        'polymorphic_identity': 'country',
    }
    
    continent_id = Column(Integer, ForeignKey("locations.id"), nullable=True)
    country_code = Column(String(3), nullable=True)  # ISO country code

# --- Continent Model ---

class Continent(LocationBase):
    """
    Represents a continent location.
    """
    __mapper_args__ = {
        'polymorphic_identity': 'continent',
    }