Spaces:
Sleeping
Sleeping
| """ | |
| SmartEyeSsen Backend - SQLAlchemy ORM Models (v2) | |
| ================================================ | |
| ERD v2 ๊ธฐ์ค 12๊ฐ ํ ์ด๋ธ SQLAlchemy ๋ชจ๋ธ ์ ์ | |
| ํ ์ด๋ธ ๋ชฉ๋ก: | |
| 1. users - ์ฌ์ฉ์ ์ ๋ณด | |
| 2. document_types - ๋ฌธ์ ํ์ ์ ์ (worksheet/document) | |
| 3. projects - ํ๋ก์ ํธ (๋ฌธ์ ๋จ์) | |
| 4. pages - ํ์ด์ง ์ ๋ณด | |
| 5. layout_elements - ๋ ์ด์์ ์์ | |
| 6. text_contents - OCR ๊ฒฐ๊ณผ | |
| 7. ai_descriptions - AI ์์ฑ ์ค๋ช | |
| 8. question_groups - ๋ฌธ์ ๊ทธ๋ฃน (v2: ์ต์ปค ์์ ๊ธฐ๋ฐ) | |
| 9. question_elements - ๋ฌธ์ -์์ ๋งคํ | |
| 10. text_versions - ํ ์คํธ ๋ฒ์ ๊ด๋ฆฌ | |
| 11. formatting_rules - ํฌ๋งทํ ๊ท์น | |
| 12. combined_results - ํตํฉ ๋ฌธ์ ์บ์ | |
| ์ต์ข ์์ ์ผ: 2025-01-22 (v2) | |
| ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ: ERD v2 ๊ธฐ์ค ์์ ์ฌ์์ฑ (์ต์ปค/์์ ๊ฐ๋ ๋ฐ์) | |
| """ | |
| from sqlalchemy import ( | |
| Column, Integer, String, Text, DateTime, Enum, Float, | |
| ForeignKey, Boolean, JSON, Index, UniqueConstraint, Computed | |
| ) | |
| from sqlalchemy.orm import relationship | |
| from sqlalchemy.sql import func | |
| from datetime import datetime | |
| from .database import Base | |
| import enum | |
| # ============================================================================ | |
| # Enums - ์ด๊ฑฐํ ์ ์ | |
| # ============================================================================ | |
| class SortingMethodEnum(str, enum.Enum): | |
| """์ ๋ ฌ ๋ฐฉ์""" | |
| QUESTION_BASED = "question_based" # ๋ฌธ์ ์ง: ์ต์ปค-์์ ์ฌ๊ท | |
| READING_ORDER = "reading_order" # ์ผ๋ฐ๋ฌธ์: Y/X ์ขํ | |
| class AnalysisModeEnum(str, enum.Enum): | |
| """๋ถ์ ๋ชจ๋""" | |
| AUTO = "auto" | |
| MANUAL = "manual" | |
| HYBRID = "hybrid" | |
| class ProjectStatusEnum(str, enum.Enum): | |
| """ํ๋ก์ ํธ ์ํ""" | |
| CREATED = "created" | |
| IN_PROGRESS = "in_progress" | |
| COMPLETED = "completed" | |
| ERROR = "error" | |
| class AnalysisStatusEnum(str, enum.Enum): | |
| """๋ถ์ ์ํ""" | |
| PENDING = "pending" | |
| PROCESSING = "processing" | |
| COMPLETED = "completed" | |
| ERROR = "error" | |
| class VersionTypeEnum(str, enum.Enum): | |
| """๋ฒ์ ์ ํ""" | |
| ORIGINAL = "original" | |
| AUTO_FORMATTED = "auto_formatted" | |
| USER_EDITED = "user_edited" | |
| # ============================================================================ | |
| # 1. Users - ์ฌ์ฉ์ ์ ๋ณด | |
| # ============================================================================ | |
| class User(Base): | |
| """์ฌ์ฉ์ ์ ๋ณด ํ ์ด๋ธ""" | |
| __tablename__ = "users" | |
| user_id = Column(Integer, primary_key=True, autoincrement=True, comment="์ฌ์ฉ์ ๊ณ ์ ID") | |
| email = Column(String(255), unique=True, nullable=False, comment="์ด๋ฉ์ผ (๋ก๊ทธ์ธ ID)") | |
| name = Column(String(100), nullable=False, comment="์ฌ์ฉ์ ์ด๋ฆ") | |
| role = Column(String(50), nullable=False, default="user", comment="์ญํ (admin/teacher/student/user)") | |
| password_hash = Column(String(255), nullable=False, comment="bcrypt ํด์๋ ๋น๋ฐ๋ฒํธ") | |
| api_key = Column(String(255), nullable=True, comment="OpenAI API ํค (AES-256 ์ํธํ ์ ์ฅ)") | |
| created_at = Column(DateTime, default=func.now(), comment="๊ณ์ ์์ฑ์ผ") | |
| updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="๋ง์ง๋ง ์์ ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| projects = relationship("Project", back_populates="user", cascade="all, delete-orphan") | |
| text_versions = relationship("TextVersion", back_populates="user") | |
| # ์ธ๋ฑ์ค | |
| __table_args__ = ( | |
| Index("idx_email", "email"), | |
| Index("idx_role", "role"), | |
| ) | |
| def __repr__(self): | |
| return f"<User(user_id={self.user_id}, email='{self.email}', role='{self.role}')>" | |
| # ============================================================================ | |
| # 2. Document Types - ๋ฌธ์ ํ์ ์ ์ | |
| # ============================================================================ | |
| class DocumentType(Base): | |
| """๋ฌธ์ ํ์ ์ ์ ํ ์ด๋ธ (worksheet/document)""" | |
| __tablename__ = "document_types" | |
| doc_type_id = Column(Integer, primary_key=True, autoincrement=True, comment="๋ฌธ์ ํ์ ๊ณ ์ ID") | |
| type_name = Column(String(100), unique=True, nullable=False, comment="ํ์ ๋ช (worksheet/document/form)") | |
| model_name = Column(String(100), nullable=False, comment="AI ๋ชจ๋ธ๋ช (SmartEyeSsen/DocLayout-YOLO)") | |
| sorting_method = Column( | |
| Enum(SortingMethodEnum), | |
| nullable=False, | |
| comment="์ ๋ ฌ ๋ฐฉ์: question_based(๋ฌธ์ ์ง), reading_order(์ผ๋ฐ๋ฌธ์)" | |
| ) | |
| description = Column(Text, nullable=True, comment="ํ์ ์ค๋ช ") | |
| created_at = Column(DateTime, default=func.now(), comment="์์ฑ์ผ") | |
| updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="์์ ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| projects = relationship("Project", back_populates="document_type") | |
| formatting_rules = relationship("FormattingRule", back_populates="document_type", cascade="all, delete-orphan") | |
| # ์ธ๋ฑ์ค | |
| __table_args__ = ( | |
| Index("idx_type_name", "type_name"), | |
| ) | |
| def __repr__(self): | |
| return f"<DocumentType(doc_type_id={self.doc_type_id}, type_name='{self.type_name}', sorting='{self.sorting_method.value}')>" | |
| # ============================================================================ | |
| # 3. Projects - ํ๋ก์ ํธ (๋ฌธ์ ๋จ์) | |
| # ============================================================================ | |
| class Project(Base): | |
| """ํ๋ก์ ํธ ํ ์ด๋ธ (๋ค์ค ํ์ด์ง ๋ฌธ์)""" | |
| __tablename__ = "projects" | |
| project_id = Column(Integer, primary_key=True, autoincrement=True, comment="ํ๋ก์ ํธ ๊ณ ์ ID") | |
| user_id = Column(Integer, ForeignKey("users.user_id", ondelete="CASCADE"), nullable=False, comment="์์ ์ ID") | |
| doc_type_id = Column(Integer, ForeignKey("document_types.doc_type_id", ondelete="RESTRICT"), nullable=False, comment="๋ฌธ์ ํ์ ID") | |
| project_name = Column(String(255), nullable=False, comment="ํ๋ก์ ํธ ์ด๋ฆ") | |
| total_pages = Column(Integer, default=0, comment="์ด ํ์ด์ง ์ (ํธ๋ฆฌ๊ฑฐ๋ก ์๋ ๊ณ์ฐ)") | |
| analysis_mode = Column( | |
| Enum(AnalysisModeEnum), | |
| default=AnalysisModeEnum.AUTO, | |
| comment="๋ถ์ ๋ชจ๋: auto/manual/hybrid" | |
| ) | |
| status = Column( | |
| Enum(ProjectStatusEnum), | |
| default=ProjectStatusEnum.CREATED, | |
| comment="ํ๋ก์ ํธ ์ํ" | |
| ) | |
| created_at = Column(DateTime, default=func.now(), comment="ํ๋ก์ ํธ ์์ฑ์ผ") | |
| updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="๋ง์ง๋ง ์์ ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| user = relationship("User", back_populates="projects") | |
| document_type = relationship("DocumentType", back_populates="projects") | |
| pages = relationship("Page", back_populates="project", cascade="all, delete-orphan") | |
| combined_result = relationship("CombinedResult", back_populates="project", uselist=False, cascade="all, delete-orphan") | |
| # ์ธ๋ฑ์ค | |
| __table_args__ = ( | |
| Index("idx_user_id", "user_id"), | |
| Index("idx_doc_type_id", "doc_type_id"), | |
| Index("idx_status", "status"), | |
| ) | |
| def __repr__(self): | |
| return f"<Project(project_id={self.project_id}, name='{self.project_name}', status='{self.status.value}')>" | |
| # ============================================================================ | |
| # 4. Pages - ํ์ด์ง ์ ๋ณด | |
| # ============================================================================ | |
| class Page(Base): | |
| """ํ์ด์ง ์ ๋ณด ํ ์ด๋ธ""" | |
| __tablename__ = "pages" | |
| page_id = Column(Integer, primary_key=True, autoincrement=True, comment="ํ์ด์ง ๊ณ ์ ID") | |
| project_id = Column(Integer, ForeignKey("projects.project_id", ondelete="CASCADE"), nullable=False, comment="์์ ํ๋ก์ ํธ ID") | |
| page_number = Column(Integer, nullable=False, comment="ํ์ด์ง ๋ฒํธ (1๋ถํฐ ์์)") | |
| image_path = Column(String(500), nullable=False, comment="์ด๋ฏธ์ง ํ์ผ ๊ฒฝ๋ก") | |
| image_width = Column(Integer, nullable=True, comment="์ด๋ฏธ์ง ๋๋น (ํฝ์ )") | |
| image_height = Column(Integer, nullable=True, comment="์ด๋ฏธ์ง ๋์ด (ํฝ์ )") | |
| analysis_status = Column( | |
| Enum(AnalysisStatusEnum), | |
| default=AnalysisStatusEnum.PENDING, | |
| comment="๋ถ์ ์ํ" | |
| ) | |
| processing_time = Column(Float, nullable=True, comment="์ฒ๋ฆฌ ์๊ฐ (์ด)") | |
| created_at = Column(DateTime, default=func.now(), comment="ํ์ด์ง ์ถ๊ฐ์ผ") | |
| analyzed_at = Column(DateTime, nullable=True, comment="๋ถ์ ์๋ฃ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| project = relationship("Project", back_populates="pages") | |
| layout_elements = relationship("LayoutElement", back_populates="page", cascade="all, delete-orphan") | |
| question_groups = relationship("QuestionGroup", back_populates="page", cascade="all, delete-orphan") | |
| text_versions = relationship("TextVersion", back_populates="page", cascade="all, delete-orphan") | |
| # ์ธ๋ฑ์ค ๋ฐ ์ ์ฝ์กฐ๊ฑด | |
| __table_args__ = ( | |
| UniqueConstraint("project_id", "page_number", name="uk_project_page"), | |
| Index("idx_project_id", "project_id"), | |
| Index("idx_analysis_status", "analysis_status"), | |
| ) | |
| def __repr__(self): | |
| return f"<Page(page_id={self.page_id}, project={self.project_id}, page_num={self.page_number}, status='{self.analysis_status.value}')>" | |
| # ============================================================================ | |
| # 5. Layout Elements - ๋ ์ด์์ ์์ (v2: order_index ์ญ์ ) | |
| # ============================================================================ | |
| class LayoutElement(Base): | |
| """๋ ์ด์์ ์์ ํ ์ด๋ธ (AI ๋ชจ๋ธ ๊ฒ์ถ ๊ฒฐ๊ณผ)""" | |
| __tablename__ = "layout_elements" | |
| element_id = Column(Integer, primary_key=True, autoincrement=True, comment="์์ ๊ณ ์ ID") | |
| page_id = Column(Integer, ForeignKey("pages.page_id", ondelete="CASCADE"), nullable=False, comment="์์ ํ์ด์ง ID") | |
| class_name = Column(String(100), nullable=False, comment="ํด๋์ค๋ช (question_number/figure/table/text ๋ฑ)") | |
| confidence = Column(Float, nullable=False, comment="์ ๋ขฐ๋ (0.0~1.0)") | |
| # ๋ฐ์ด๋ฉ ๋ฐ์ค ์ขํ | |
| bbox_x = Column(Integer, nullable=False, comment="X ์ขํ (์ผ์ชฝ ์๋จ)") | |
| bbox_y = Column(Integer, nullable=False, comment="Y ์ขํ (์ผ์ชฝ ์๋จ)") | |
| bbox_width = Column(Integer, nullable=False, comment="๋๋น (ํฝ์ )") | |
| bbox_height = Column(Integer, nullable=False, comment="๋์ด (ํฝ์ )") | |
| # ์๋ ๊ณ์ฐ ์ปฌ๋ผ (GENERATED COLUMN) - SQLAlchemy์์๋ Computed ์ฌ์ฉ | |
| area = Column(Integer, Computed("bbox_width * bbox_height"), comment="๋ฉด์ (์๋ ๊ณ์ฐ)") | |
| y_position = Column(Integer, Computed("bbox_y"), comment="Y ์ ๋ ฌ์ฉ ์ขํ (์๋ ๊ณ์ฐ)") | |
| x_position = Column(Integer, Computed("bbox_x"), comment="X ์ ๋ ฌ์ฉ ์ขํ (์๋ ๊ณ์ฐ)") | |
| created_at = Column(DateTime, default=func.now(), comment="์์ฑ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| page = relationship("Page", back_populates="layout_elements") | |
| text_content = relationship("TextContent", back_populates="layout_element", uselist=False, cascade="all, delete-orphan") | |
| ai_description = relationship("AIDescription", back_populates="layout_element", uselist=False, cascade="all, delete-orphan") | |
| question_group = relationship("QuestionGroup", back_populates="anchor_element", uselist=False, cascade="all, delete-orphan") | |
| question_elements = relationship("QuestionElement", back_populates="layout_element", cascade="all, delete-orphan") | |
| # ์ธ๋ฑ์ค | |
| __table_args__ = ( | |
| Index("idx_page_id", "page_id"), | |
| Index("idx_class_name", "class_name"), | |
| Index("idx_position", "page_id", "y_position", "x_position"), # ๋ณตํฉ ์ธ๋ฑ์ค | |
| ) | |
| def __repr__(self): | |
| return f"<LayoutElement(element_id={self.element_id}, class='{self.class_name}', conf={self.confidence:.3f})>" | |
| # ============================================================================ | |
| # 6. Text Contents - OCR ๊ฒฐ๊ณผ | |
| # ============================================================================ | |
| class TextContent(Base): | |
| """OCR ๊ฒฐ๊ณผ ํ ์ด๋ธ""" | |
| __tablename__ = "text_contents" | |
| text_id = Column(Integer, primary_key=True, autoincrement=True, comment="OCR ๊ฒฐ๊ณผ ๊ณ ์ ID") | |
| element_id = Column(Integer, ForeignKey("layout_elements.element_id", ondelete="CASCADE"), unique=True, nullable=False, comment="๋ ์ด์์ ์์ ID (1:1 ๋งคํ)") | |
| ocr_text = Column(Text, nullable=False, comment="OCR ์ถ์ถ ํ ์คํธ") | |
| ocr_engine = Column(String(50), default="PaddleOCR", comment="์ฌ์ฉํ OCR ์์ง") | |
| ocr_confidence = Column(Float, nullable=True, comment="OCR ์ ๋ขฐ๋ (0.0~1.0)") | |
| language = Column(String(10), default="ko", comment="์ธ์ด ์ฝ๋ (ko/en/ja/zh)") | |
| created_at = Column(DateTime, default=func.now(), comment="์์ฑ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| layout_element = relationship("LayoutElement", back_populates="text_content") | |
| # ์ธ๋ฑ์ค | |
| __table_args__ = ( | |
| UniqueConstraint("element_id", name="uk_element"), | |
| Index("idx_language", "language"), | |
| # FULLTEXT ์ธ๋ฑ์ค๋ MySQL ํน์ ๊ธฐ๋ฅ์ด๋ฏ๋ก ์๋ต (ํ์์ Raw SQL๋ก ์ถ๊ฐ) | |
| ) | |
| def __repr__(self): | |
| return f"<TextContent(text_id={self.text_id}, element={self.element_id}, engine='{self.ocr_engine}')>" | |
| # ============================================================================ | |
| # 7. AI Descriptions - AI ์์ฑ ์ค๋ช | |
| # ============================================================================ | |
| class AIDescription(Base): | |
| """AI ์์ฑ ์ค๋ช ํ ์ด๋ธ (figure/table ์ค๋ช )""" | |
| __tablename__ = "ai_descriptions" | |
| ai_desc_id = Column(Integer, primary_key=True, autoincrement=True, comment="AI ์ค๋ช ๊ณ ์ ID") | |
| element_id = Column(Integer, ForeignKey("layout_elements.element_id", ondelete="CASCADE"), unique=True, nullable=False, comment="๋ ์ด์์ ์์ ID (1:1 ๋งคํ)") | |
| description = Column(Text, nullable=False, comment="AI๊ฐ ์์ฑํ ์ค๋ช ํ ์คํธ") | |
| ai_model = Column(String(100), default="gpt-4o-mini", comment="์ฌ์ฉํ AI ๋ชจ๋ธ๋ช ") | |
| prompt_used = Column(Text, nullable=True, comment="์ฌ์ฉํ ํ๋กฌํํธ (๋๋ฒ๊น ์ฉ)") | |
| created_at = Column(DateTime, default=func.now(), comment="์์ฑ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| layout_element = relationship("LayoutElement", back_populates="ai_description") | |
| # ์ธ๋ฑ์ค | |
| __table_args__ = ( | |
| UniqueConstraint("element_id", name="uk_element"), | |
| Index("idx_ai_model", "ai_model"), | |
| ) | |
| def __repr__(self): | |
| return f"<AIDescription(ai_desc_id={self.ai_desc_id}, element={self.element_id}, model='{self.ai_model}')>" | |
| # ============================================================================ | |
| # 8. Question Groups - ๋ฌธ์ ๊ทธ๋ฃน (v2: ์ต์ปค ์์ ๊ธฐ๋ฐ) | |
| # ============================================================================ | |
| class QuestionGroup(Base): | |
| """๋ฌธ์ ๊ทธ๋ฃน ํ ์ด๋ธ (์ต์ปค ์์ ๊ธฐ์ค)""" | |
| __tablename__ = "question_groups" | |
| question_group_id = Column(Integer, primary_key=True, autoincrement=True, comment="๋ฌธ์ ๊ทธ๋ฃน ๊ณ ์ ID") | |
| page_id = Column(Integer, ForeignKey("pages.page_id", ondelete="CASCADE"), nullable=False, comment="์์ ํ์ด์ง ID") | |
| anchor_element_id = Column(Integer, ForeignKey("layout_elements.element_id", ondelete="CASCADE"), unique=True, nullable=False, comment="์ต์ปค ์์ ID (FK: layout_elements)") | |
| # Y์ขํ ๋ฒ์ | |
| start_y = Column(Integer, nullable=False, comment="๋ฌธ์ ์์ Y์ขํ") | |
| end_y = Column(Integer, nullable=False, comment="๋ฌธ์ ์ข ๋ฃ Y์ขํ") | |
| # ํต๊ณ ์ ๋ณด | |
| element_count = Column(Integer, default=0, comment="๋ฌธ์ ์ ์ํ ์์ ๊ฐ์ (์์ ์์ ์)") | |
| created_at = Column(DateTime, default=func.now(), comment="์์ฑ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| page = relationship("Page", back_populates="question_groups") | |
| anchor_element = relationship("LayoutElement", back_populates="question_group") | |
| question_elements = relationship("QuestionElement", back_populates="question_group", cascade="all, delete-orphan") | |
| # ์ธ๋ฑ์ค ๋ฐ ์ ์ฝ์กฐ๊ฑด | |
| __table_args__ = ( | |
| UniqueConstraint("anchor_element_id", name="uk_anchor_element"), | |
| Index("idx_page_id", "page_id"), | |
| ) | |
| def __repr__(self): | |
| return f"<QuestionGroup(group_id={self.question_group_id}, anchor={self.anchor_element_id}, Y={self.start_y}-{self.end_y})>" | |
| # ============================================================================ | |
| # 9. Question Elements - ๋ฌธ์ -์์ ๋งคํ | |
| # ============================================================================ | |
| class QuestionElement(Base): | |
| """๋ฌธ์ -์์ ๋งคํ ํ ์ด๋ธ (์์ ์์ ๊ด๋ฆฌ)""" | |
| __tablename__ = "question_elements" | |
| qe_id = Column(Integer, primary_key=True, autoincrement=True, comment="๋งคํ ๋ ์ฝ๋ ๊ณ ์ ID") | |
| question_group_id = Column(Integer, ForeignKey("question_groups.question_group_id", ondelete="CASCADE"), nullable=False, comment="๋ฌธ์ ๊ทธ๋ฃน ID") | |
| element_id = Column(Integer, ForeignKey("layout_elements.element_id", ondelete="CASCADE"), nullable=False, comment="์์ ์์ ID") | |
| order_in_question = Column(Integer, nullable=False, comment="๋ฌธ์ ๋ด ์์ ์์ (1, 2, 3, ...) - Y์ขํ ๊ธฐ์ค") | |
| created_at = Column(DateTime, default=func.now(), comment="์์ฑ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| question_group = relationship("QuestionGroup", back_populates="question_elements") | |
| layout_element = relationship("LayoutElement", back_populates="question_elements") | |
| # ์ธ๋ฑ์ค ๋ฐ ์ ์ฝ์กฐ๊ฑด | |
| __table_args__ = ( | |
| UniqueConstraint("question_group_id", "element_id", name="uk_question_element"), | |
| Index("idx_order", "question_group_id", "order_in_question"), | |
| ) | |
| def __repr__(self): | |
| return f"<QuestionElement(qe_id={self.qe_id}, group={self.question_group_id}, element={self.element_id}, order={self.order_in_question})>" | |
| # ============================================================================ | |
| # 10. Text Versions - ํ ์คํธ ๋ฒ์ ๊ด๋ฆฌ | |
| # ============================================================================ | |
| class TextVersion(Base): | |
| """ํ ์คํธ ๋ฒ์ ๊ด๋ฆฌ ํ ์ด๋ธ""" | |
| __tablename__ = "text_versions" | |
| version_id = Column(Integer, primary_key=True, autoincrement=True, comment="๋ฒ์ ๊ณ ์ ID") | |
| page_id = Column(Integer, ForeignKey("pages.page_id", ondelete="CASCADE"), nullable=False, comment="์์ ํ์ด์ง ID") | |
| user_id = Column(Integer, ForeignKey("users.user_id", ondelete="SET NULL"), nullable=True, comment="์์ ํ ์ฌ์ฉ์ ID (์ฌ์ฉ์ ์์ ์)") | |
| content = Column(Text, nullable=False, comment="ํ ์คํธ ๋ด์ฉ") | |
| version_number = Column(Integer, nullable=False, comment="๋ฒ์ ๋ฒํธ (1, 2, 3, ...)") | |
| version_type = Column( | |
| Enum(VersionTypeEnum), | |
| nullable=False, | |
| comment="๋ฒ์ ์ ํ: original/auto_formatted/user_edited" | |
| ) | |
| is_current = Column(Boolean, default=False, comment="ํ์ฌ ๋ฒ์ ์ฌ๋ถ") | |
| created_at = Column(DateTime, default=func.now(), comment="๋ฒ์ ์์ฑ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| page = relationship("Page", back_populates="text_versions") | |
| user = relationship("User", back_populates="text_versions") | |
| # ์ธ๋ฑ์ค ๋ฐ ์ ์ฝ์กฐ๊ฑด | |
| __table_args__ = ( | |
| UniqueConstraint("page_id", "version_number", name="uk_page_version"), | |
| Index("idx_page_id", "page_id"), | |
| Index("idx_is_current", "is_current"), | |
| ) | |
| def __repr__(self): | |
| return f"<TextVersion(version_id={self.version_id}, page={self.page_id}, v={self.version_number}, type='{self.version_type.value}')>" | |
| # ============================================================================ | |
| # 11. Formatting Rules - ํฌ๋งทํ ๊ท์น (v2: ์ต์ปค/์์ ํด๋์ค ๊ท์น) | |
| # ============================================================================ | |
| class FormattingRule(Base): | |
| """ํฌ๋งทํ ๊ท์น ํ ์ด๋ธ""" | |
| __tablename__ = "formatting_rules" | |
| rule_id = Column(Integer, primary_key=True, autoincrement=True, comment="๊ท์น ๊ณ ์ ID") | |
| doc_type_id = Column(Integer, ForeignKey("document_types.doc_type_id", ondelete="CASCADE"), nullable=False, comment="๋ฌธ์ ํ์ ID") | |
| class_name = Column(String(100), nullable=False, comment="์ ์ฉ ํด๋์ค๋ช (question_number/figure/text ๋ฑ)") | |
| # ํฌ๋งทํ ์ค์ | |
| prefix = Column(String(50), default="", comment="์ ๋์ฌ (์: '\\n\\n', ' ')") | |
| suffix = Column(String(50), default="", comment="์ ๋ฏธ์ฌ (์: '. ', '\\n')") | |
| indent_level = Column(Integer, default=0, comment="๋ค์ฌ์ฐ๊ธฐ ๋ ๋ฒจ (0~10)") | |
| # ์คํ์ผ ์ค์ (์ ํ ์ฌํญ) | |
| font_size = Column(String(20), nullable=True, comment="ํฐํธ ํฌ๊ธฐ (์: '14pt')") | |
| font_weight = Column(String(20), nullable=True, comment="ํฐํธ ๋๊ป (์: 'bold')") | |
| created_at = Column(DateTime, default=func.now(), comment="๊ท์น ์์ฑ์ผ") | |
| updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="๊ท์น ์์ ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| document_type = relationship("DocumentType", back_populates="formatting_rules") | |
| # ์ธ๋ฑ์ค ๋ฐ ์ ์ฝ์กฐ๊ฑด | |
| __table_args__ = ( | |
| UniqueConstraint("doc_type_id", "class_name", name="uk_type_class"), | |
| Index("idx_doc_type_id", "doc_type_id"), | |
| ) | |
| def __repr__(self): | |
| return f"<FormattingRule(rule_id={self.rule_id}, doc_type={self.doc_type_id}, class='{self.class_name}')>" | |
| # ============================================================================ | |
| # 12. Combined Results - ํตํฉ ๋ฌธ์ ์บ์ | |
| # ============================================================================ | |
| class CombinedResult(Base): | |
| """ํตํฉ ๋ฌธ์ ์บ์ ํ ์ด๋ธ""" | |
| __tablename__ = "combined_results" | |
| combined_id = Column(Integer, primary_key=True, autoincrement=True, comment="ํตํฉ ๊ฒฐ๊ณผ ๊ณ ์ ID") | |
| project_id = Column(Integer, ForeignKey("projects.project_id", ondelete="CASCADE"), unique=True, nullable=False, comment="ํ๋ก์ ํธ ID (1:1 ๋งคํ)") | |
| combined_text = Column(Text(4294967295), nullable=False, comment="ํตํฉ๋ ์ ์ฒด ํ ์คํธ (ํ์ด์ง๋ณ ๊ฒฐ๊ณผ ํฉ์นจ) - LONGTEXT") | |
| combined_stats = Column(JSON, nullable=True, comment="ํต๊ณ ์ ๋ณด (JSON ํ์: ํ์ด์ง์, ๋จ์ด์, ๋ฌธ์ ์ ๋ฑ)") | |
| generated_at = Column(DateTime, default=func.now(), comment="์ต์ด ์์ฑ์ผ") | |
| updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="๋ง์ง๋ง ์ ๋ฐ์ดํธ์ผ") | |
| # ๊ด๊ณ ์ค์ | |
| project = relationship("Project", back_populates="combined_result") | |
| # ์ธ๋ฑ์ค ๋ฐ ์ ์ฝ์กฐ๊ฑด | |
| __table_args__ = ( | |
| UniqueConstraint("project_id", name="uk_project"), | |
| Index("idx_project_id", "project_id"), | |
| ) | |
| def __repr__(self): | |
| return f"<CombinedResult(combined_id={self.combined_id}, project={self.project_id})>" | |
| # ============================================================================ | |
| # ๋ชจ๋ธ ์ด๊ธฐํ ์์ (์ฐธ๊ณ ์ฉ) | |
| # ============================================================================ | |
| """ | |
| ์ธ๋ ํค ์์กด์ฑ ์์: | |
| 1. User (๋ ๋ฆฝ) | |
| 2. DocumentType (๋ ๋ฆฝ) | |
| 3. Project (User, DocumentType ์์กด) | |
| 4. Page (Project ์์กด) | |
| 5. LayoutElement (Page ์์กด) | |
| 6. TextContent (LayoutElement ์์กด) | |
| 7. AIDescription (LayoutElement ์์กด) | |
| 8. QuestionGroup (Page, LayoutElement ์์กด) - ์ต์ปค ๊ด๊ณ | |
| 9. QuestionElement (QuestionGroup, LayoutElement ์์กด) | |
| 10. TextVersion (Page, User ์์กด) | |
| 11. FormattingRule (DocumentType ์์กด) | |
| 12. CombinedResult (Project ์์กด) | |
| """ | |