"""Модели документов, fingerprints и логов использования.""" from datetime import datetime from typing import TYPE_CHECKING from sqlalchemy import JSON, BigInteger, ForeignKey, String, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base if TYPE_CHECKING: from app.models.user import User class Document(Base): """Документ в базе знаний системы (статья, книга, веб-страница).""" __tablename__ = "documents" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) # Источник: openalex, cyberleninka, arxiv, wikipedia_ru, wikipedia_en source: Mapped[str] = mapped_column(String(50), nullable=False, index=True) # Внешний ID (OpenAlex ID, arXiv ID и т.д.) — уникален в рамках источника ext_id: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False) doi: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) title: Mapped[str] = mapped_column(nullable=False) authors: Mapped[list] = mapped_column(JSON, default=list) year: Mapped[int | None] = mapped_column(nullable=True, index=True) lang: Mapped[str | None] = mapped_column(String(10), nullable=True, index=True) journal: Mapped[str | None] = mapped_column(nullable=True) volume: Mapped[str | None] = mapped_column(String(50), nullable=True) issue: Mapped[str | None] = mapped_column(String(50), nullable=True) pages: Mapped[str | None] = mapped_column(String(50), nullable=True) abstract: Mapped[str | None] = mapped_column(nullable=True) url: Mapped[str | None] = mapped_column(nullable=True) # Ключ объекта в MinIO (полный текст) minio_key: Mapped[str | None] = mapped_column(nullable=True) # ID вектора в FAISS индексе faiss_id: Mapped[int | None] = mapped_column(nullable=True, index=True) indexed_at: Mapped[datetime] = mapped_column(server_default=func.now()) # Отпечатки (Winnowing хэши) fingerprints: Mapped[list["Fingerprint"]] = relationship( back_populates="document", lazy="select", ) def __repr__(self) -> str: return f"" class Fingerprint(Base): """Winnowing fingerprint (хэш n-граммы) для быстрого поиска плагиата.""" __tablename__ = "fingerprints" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) doc_id: Mapped[int] = mapped_column(ForeignKey("documents.id"), index=True) hash_value: Mapped[int] = mapped_column(BigInteger, index=True) position: Mapped[int] = mapped_column() # Позиция в документе document: Mapped["Document"] = relationship(back_populates="fingerprints") def __repr__(self) -> str: return f"" class UsageLog(Base): """Лог использования для подсчёта лимитов по тарифу.""" __tablename__ = "usage_logs" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) # Действие: search, plagiarism, summarize, gost action: Mapped[str] = mapped_column(String(50), nullable=False) created_at: Mapped[datetime] = mapped_column(server_default=func.now(), index=True) user: Mapped["User"] = relationship(back_populates="usage") def __repr__(self) -> str: return f""