from sqlalchemy import Column, Integer, String, Text, Float, ForeignKey, DateTime, JSON from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.orm import relationship, declarative_base from pgvector.sqlalchemy import Vector import datetime Base = declarative_base() class UploadJob(Base): __tablename__ = "upload_jobs" id = Column(Integer, primary_key=True) filename = Column(String) newspaper = Column(String) edition = Column(String) date = Column(String) celery_task_id = Column(String) status = Column(String, default="PENDING") error_message = Column(Text) created_time = Column(DateTime, default=datetime.datetime.utcnow) articles = relationship("Article", back_populates="upload_job") class Article(Base): __tablename__ = "articles" id = Column(Integer, primary_key=True) upload_job_id = Column(Integer, ForeignKey("upload_jobs.id"), nullable=True) date = Column(String, index=True) newspaper = Column(String, index=True) edition = Column(String) page = Column(Integer, index=True) headline = Column(Text, index=True) subheadline = Column(Text) category = Column(String, index=True) subcategory = Column(String, index=True) language = Column(String) author = Column(String) text = Column(Text) summary = Column(Text) keywords = Column(ARRAY(String)) persons = Column(ARRAY(String)) locations = Column(ARRAY(String)) organizations = Column(ARRAY(String)) sentiment = Column(String) image_path = Column(String) bounding_box = Column(JSON) confidence = Column(Float) embedding = Column(Vector(384)) # pgvector for semantic search content_hash = Column(String, index=True) # for dedup created_time = Column(DateTime, default=datetime.datetime.utcnow) images = relationship("ArticleImage", back_populates="article") upload_job = relationship("UploadJob", back_populates="articles") class ArticleImage(Base): __tablename__ = "article_images" id = Column(Integer, primary_key=True) article_id = Column(Integer, ForeignKey("articles.id")) image_path = Column(String) caption = Column(Text) width = Column(Integer) height = Column(Integer) article = relationship("Article", back_populates="images")