# app/models.py from sqlalchemy import Column, Integer, String, Float, DateTime, Text, ForeignKey, JSON, Boolean from sqlalchemy.orm import relationship from datetime import datetime from app.database import Base class Project(Base): """Проект — один загруженный PDF.""" __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True) pdf_filename = Column(String, nullable=False) status = Column(String, default="uploaded") # uploaded / processing / completed / error created_at = Column(DateTime, default=datetime.utcnow) completed_at = Column(DateTime, nullable=True) error_message = Column(Text, nullable=True) # Пути к файлам output_folder = Column(String, nullable=True) pages = relationship("Page", back_populates="project", cascade="all, delete-orphan") issues = relationship("Issue", back_populates="project", cascade="all, delete-orphan") class Page(Base): """Страница PDF.""" __tablename__ = "pages" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id")) page_number = Column(Integer, nullable=False) png_path = Column(String, nullable=True) dzi_path = Column(String, nullable=True) ocr_data = Column(JSON, nullable=True) # full_ocr_results для этой страницы vlm_description = Column(Text, nullable=True) width = Column(Integer, nullable=True) height = Column(Integer, nullable=True) project = relationship("Project", back_populates="pages") issues = relationship("Issue", back_populates="page") class Issue(Base): """Замечание QC — одна проблема на чертеже.""" __tablename__ = "issues" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id")) page_id = Column(Integer, ForeignKey("pages.id")) issue_type = Column(String, nullable=False) # DIMENSION_OVERLAP, LOW_CONFIDENCE, etc. severity = Column(String, nullable=False) # error / warning / info message = Column(Text, nullable=False) # Координаты bbox на PNG (в пикселях) bbox_x1 = Column(Float, nullable=True) bbox_y1 = Column(Float, nullable=True) bbox_x2 = Column(Float, nullable=True) bbox_y2 = Column(Float, nullable=True) # Дополнительные данные dimension_text = Column(String, nullable=True) # Текст размера (если применимо) confidence = Column(Float, nullable=True) # OCR confidence extra_data = Column(JSON, nullable=True) # Всё остальное source = Column(String, nullable=True) # "rules" или "vlm" created_at = Column(DateTime, default=datetime.utcnow) project = relationship("Project", back_populates="issues") page = relationship("Page", back_populates="issues") feedback = relationship("Feedback", back_populates="issue", uselist=False) class Feedback(Base): """Feedback проектировщика — правда ли это замечание.""" __tablename__ = "feedback" id = Column(Integer, primary_key=True, index=True) issue_id = Column(Integer, ForeignKey("issues.id"), unique=True) # True = реальная проблема, False = ложное срабатывание, None = не размечено is_true_positive = Column(Boolean, nullable=True) # Почему (опционально) comment = Column(Text, nullable=True) # Что сделал проектировщик action_taken = Column(String, nullable=True) # fixed / ignored / not_sure created_at = Column(DateTime, default=datetime.utcnow) user_id = Column(String, nullable=True) # Для многопользовательского режима issue = relationship("Issue", back_populates="feedback")