- Add WordTranslation model for storing multiple translations per word - AI generates translations with example sentences and their translations - Show example usage after answering tasks (learning + interface language) - Save translations to word_translations table when adding words from tasks - Improve word exclusion in new_words mode (stronger prompt + client filtering) - Add migration for word_translations table 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
123 lines
5.8 KiB
Python
123 lines
5.8 KiB
Python
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):
|
||
"""Уровни владения языком (CEFR)"""
|
||
A1 = "A1"
|
||
A2 = "A2"
|
||
B1 = "B1"
|
||
B2 = "B2"
|
||
C1 = "C1"
|
||
C2 = "C2"
|
||
|
||
|
||
class JLPTLevel(str, enum.Enum):
|
||
"""Уровни JLPT для японского языка"""
|
||
N5 = "N5" # Базовый
|
||
N4 = "N4" # Начальный
|
||
N3 = "N3" # Средний
|
||
N2 = "N2" # Продвинутый
|
||
N1 = "N1" # Свободный
|
||
|
||
|
||
# Языки, использующие JLPT вместо CEFR
|
||
JLPT_LANGUAGES = {"ja"}
|
||
|
||
# Дефолтные уровни для разных систем
|
||
DEFAULT_CEFR_LEVEL = "A1"
|
||
DEFAULT_JLPT_LEVEL = "N5"
|
||
|
||
|
||
class WordSource(str, enum.Enum):
|
||
"""Источник добавления слова"""
|
||
MANUAL = "manual" # Ручное добавление
|
||
SUGGESTED = "suggested" # Предложено ботом
|
||
CONTEXT = "context" # Из контекста диалога
|
||
IMPORT = "import" # Импорт из текста
|
||
ERROR = "error" # Из ошибок в заданиях
|
||
AI_TASK = "ai_task" # Из AI-задания
|
||
|
||
|
||
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)
|
||
levels_by_language: Mapped[Optional[dict]] = mapped_column(JSON, default=None) # {"en": "B1", "ja": "N4"}
|
||
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 WordTranslation(Base):
|
||
"""Модель перевода слова с контекстом"""
|
||
__tablename__ = "word_translations"
|
||
|
||
id: Mapped[int] = mapped_column(primary_key=True)
|
||
vocabulary_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||
translation: Mapped[str] = mapped_column(String(255), nullable=False)
|
||
context: Mapped[Optional[str]] = mapped_column(String(500)) # Пример предложения
|
||
context_translation: Mapped[Optional[str]] = mapped_column(String(500)) # Перевод примера
|
||
is_primary: Mapped[bool] = mapped_column(Boolean, default=False) # Основной перевод
|
||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||
|
||
|
||
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)
|