Changes committed

This commit is contained in:
2026-06-01 21:49:53 +05:30
parent 8537c653c1
commit 3163bb213e
387 changed files with 21940 additions and 107 deletions

View File

@@ -0,0 +1,244 @@
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, String, Text, func
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
from app.models.base import TimestampMixin, UUIDPrimaryKeyMixin
class Document(Base, UUIDPrimaryKeyMixin, TimestampMixin):
"""Uploaded document record."""
__tablename__ = "documents"
filename: Mapped[str] = mapped_column(String(500), nullable=False)
original_filename: Mapped[str] = mapped_column(String(500), nullable=False)
content_type: Mapped[str] = mapped_column(String(100), nullable=False)
file_size: Mapped[int] = mapped_column(BigInteger, nullable=False)
checksum: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
storage_path: Mapped[str] = mapped_column(String(1024), nullable=False)
status: Mapped[str] = mapped_column(
String(50),
default="pending",
nullable=False,
index=True,
)
page_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
is_scanned: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
document_metadata: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
uploaded_by: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
pages: Mapped[list[DocumentPage]] = relationship(
"DocumentPage",
back_populates="document",
cascade="all, delete-orphan",
order_by="DocumentPage.page_number",
lazy="selectin",
)
template_matches: Mapped[list[TemplateMatch]] = relationship(
"TemplateMatch",
back_populates="document",
cascade="all, delete-orphan",
lazy="dynamic",
)
def __repr__(self) -> str:
return f"<Document(id={self.id}, filename={self.original_filename}, status={self.status})>"
class DocumentPage(Base, UUIDPrimaryKeyMixin):
"""Individual page within a document."""
__tablename__ = "document_pages"
document_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("documents.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
page_number: Mapped[int] = mapped_column(Integer, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
image_path: Mapped[str | None] = mapped_column(String(1024), nullable=True)
text_content: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
document: Mapped[Document] = relationship("Document", back_populates="pages")
text_blocks: Mapped[list[DocumentTextBlock]] = relationship(
"DocumentTextBlock",
back_populates="page",
cascade="all, delete-orphan",
order_by="DocumentTextBlock.sequence",
lazy="selectin",
)
images: Mapped[list[DocumentImage]] = relationship(
"DocumentImage",
back_populates="page",
cascade="all, delete-orphan",
lazy="selectin",
)
tables: Mapped[list[DocumentTable]] = relationship(
"DocumentTable",
back_populates="page",
cascade="all, delete-orphan",
lazy="selectin",
)
def __repr__(self) -> str:
return f"<DocumentPage(id={self.id}, document_id={self.document_id}, page={self.page_number})>"
class DocumentTextBlock(Base, UUIDPrimaryKeyMixin):
"""Extracted text block from a document page."""
__tablename__ = "document_text_blocks"
page_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_pages.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
text: Mapped[str] = mapped_column(Text, nullable=False)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
confidence: Mapped[float | None] = mapped_column(Float, nullable=True)
font_family: Mapped[str | None] = mapped_column(String(255), nullable=True)
font_size: Mapped[float | None] = mapped_column(Float, nullable=True)
font_color: Mapped[str | None] = mapped_column(String(50), nullable=True)
font_style: Mapped[str | None] = mapped_column(String(50), nullable=True)
block_type: Mapped[str] = mapped_column(
String(50),
default="text",
nullable=False,
)
sequence: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
page: Mapped[DocumentPage] = relationship("DocumentPage", back_populates="text_blocks")
def __repr__(self) -> str:
return f"<DocumentTextBlock(id={self.id}, type={self.block_type}, text={self.text[:50]})>"
class DocumentImage(Base, UUIDPrimaryKeyMixin):
"""Extracted image from a document page."""
__tablename__ = "document_images"
page_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_pages.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
image_path: Mapped[str] = mapped_column(String(1024), nullable=False)
image_type: Mapped[str] = mapped_column(
String(50),
default="figure",
nullable=False,
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
page: Mapped[DocumentPage] = relationship("DocumentPage", back_populates="images")
def __repr__(self) -> str:
return f"<DocumentImage(id={self.id}, type={self.image_type})>"
class DocumentTable(Base, UUIDPrimaryKeyMixin):
"""Extracted table from a document page."""
__tablename__ = "document_tables"
page_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_pages.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
rows: Mapped[int] = mapped_column(Integer, nullable=False)
columns: Mapped[int] = mapped_column(Integer, nullable=False)
data: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
page: Mapped[DocumentPage] = relationship("DocumentPage", back_populates="tables")
def __repr__(self) -> str:
return f"<DocumentTable(id={self.id}, rows={self.rows}, cols={self.columns})>"
class TemplateMatch(Base, UUIDPrimaryKeyMixin):
"""Template matching result for a document."""
__tablename__ = "template_matches"
document_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("documents.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
confidence_score: Mapped[float] = mapped_column(Float, nullable=False)
match_details: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
selected: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
document: Mapped[Document] = relationship("Document", back_populates="template_matches")
template: Mapped[DocumentFormat] = relationship("DocumentFormat")
def __repr__(self) -> str:
return f"<TemplateMatch(id={self.id}, doc={self.document_id}, score={self.confidence_score})>"