from datetime import datetime from typing import Optional from sqlalchemy import String, BigInteger, DateTime, Integer, Boolean, JSON, Enum as SQLEnum, UniqueConstraint from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column import enum class Base(DeclarativeBase): """Базовая модель""" pass class LanguageLevel(str, enum.Enum): """Уровни владения языком""" A1 = "A1" A2 = "A2" B1 = "B1" B2 = "B2" C1 = "C1" C2 = "C2" class WordSource(str, enum.Enum): """Источник добавления слова""" MANUAL = "manual" # Ручное добавление SUGGESTED = "suggested" # Предложено ботом CONTEXT = "context" # Из контекста диалога IMPORT = "import" # Импорт из текста ERROR = "error" # Из ошибок в заданиях class User(Base): """Модель пользователя""" __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) telegram_id: Mapped[int] = mapped_column(BigInteger, unique=True, nullable=False) username: Mapped[Optional[str]] = mapped_column(String(255)) language_interface: Mapped[str] = mapped_column(String(2), default="ru") # ru/en learning_language: Mapped[str] = mapped_column(String(2), default="en") # en level: Mapped[LanguageLevel] = mapped_column(SQLEnum(LanguageLevel), default=LanguageLevel.A1) timezone: Mapped[str] = mapped_column(String(50), default="UTC") daily_task_time: Mapped[Optional[str]] = mapped_column(String(5)) # HH:MM reminders_enabled: Mapped[bool] = mapped_column(Boolean, default=False) last_reminder_sent: Mapped[Optional[datetime]] = mapped_column(DateTime) streak_days: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) last_active: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class Vocabulary(Base): """Модель словарного запаса""" __tablename__ = "vocabulary" __table_args__ = ( UniqueConstraint("user_id", "source_lang", "word_original", name="uq_vocab_user_lang_word"), ) id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) word_original: Mapped[str] = mapped_column(String(255), nullable=False) word_translation: Mapped[str] = mapped_column(String(255), nullable=False) source_lang: Mapped[Optional[str]] = mapped_column(String(5)) # ISO2 языка слова (язык изучения) translation_lang: Mapped[Optional[str]] = mapped_column(String(5)) # ISO2 языка перевода (обычно язык интерфейса) transcription: Mapped[Optional[str]] = mapped_column(String(255)) examples: Mapped[Optional[dict]] = mapped_column(JSON) # JSON массив примеров category: Mapped[Optional[str]] = mapped_column(String(100)) difficulty_level: Mapped[Optional[LanguageLevel]] = mapped_column(SQLEnum(LanguageLevel)) source: Mapped[WordSource] = mapped_column(SQLEnum(WordSource), default=WordSource.MANUAL) times_reviewed: Mapped[int] = mapped_column(Integer, default=0) correct_answers: Mapped[int] = mapped_column(Integer, default=0) last_reviewed: Mapped[Optional[datetime]] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) notes: Mapped[Optional[str]] = mapped_column(String(500)) # Заметки пользователя class Task(Base): """Модель задания""" __tablename__ = "tasks" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) task_type: Mapped[str] = mapped_column(String(50), nullable=False) # translate, sentence, fill, etc. content: Mapped[dict] = mapped_column(JSON, nullable=False) # Содержание задания correct_answer: Mapped[Optional[str]] = mapped_column(String(500)) user_answer: Mapped[Optional[str]] = mapped_column(String(500)) is_correct: Mapped[Optional[bool]] = mapped_column(Boolean) ai_feedback: Mapped[Optional[str]] = mapped_column(String(1000)) completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)