2025-12-04 11:09:54 +03:00
from datetime import datetime
from typing import Optional
2025-12-04 19:40:01 +03:00
from sqlalchemy import String , BigInteger , DateTime , Integer , Boolean , JSON , Enum as SQLEnum , UniqueConstraint
2025-12-04 11:09:54 +03:00
from sqlalchemy . orm import DeclarativeBase , Mapped , mapped_column
import enum
class Base ( DeclarativeBase ) :
""" Базовая модель """
pass
class LanguageLevel ( str , enum . Enum ) :
2025-12-05 14:30:24 +03:00
""" Уровни владения языком (CEFR) """
2025-12-04 11:09:54 +03:00
A1 = " A1 "
A2 = " A2 "
B1 = " B1 "
B2 = " B2 "
C1 = " C1 "
C2 = " C2 "
2025-12-05 14:30:24 +03:00
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 "
2025-12-04 11:09:54 +03:00
class WordSource ( str , enum . Enum ) :
""" Источник добавления слова """
MANUAL = " manual " # Ручное добавление
SUGGESTED = " suggested " # Предложено ботом
CONTEXT = " context " # Из контекста диалога
IMPORT = " import " # Импорт из текста
ERROR = " error " # Из ошибок в заданиях
2025-12-05 20:15:47 +03:00
AI_TASK = " ai_task " # Из AI-задания
2025-12-04 11:09:54 +03:00
2025-12-08 15:16:24 +03:00
class AIProvider ( str , enum . Enum ) :
""" Провайдеры AI моделей """
openai = " openai "
google = " google "
2025-12-04 11:09:54 +03:00
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 ) )
2025-12-07 16:35:08 +03:00
language_interface : Mapped [ str ] = mapped_column ( String ( 2 ) , default = " ru " ) # ru/en/ja - UI language
learning_language : Mapped [ str ] = mapped_column ( String ( 2 ) , default = " en " ) # en/ja - language being learned
translation_language : Mapped [ Optional [ str ] ] = mapped_column ( String ( 2 ) , default = None ) # ru/en/ja - translation target (defaults to language_interface if None)
2025-12-04 11:09:54 +03:00
level : Mapped [ LanguageLevel ] = mapped_column ( SQLEnum ( LanguageLevel ) , default = LanguageLevel . A1 )
2025-12-05 14:30:24 +03:00
levels_by_language : Mapped [ Optional [ dict ] ] = mapped_column ( JSON , default = None ) # {"en": "B1", "ja": "N4"}
2025-12-04 11:09:54 +03:00
timezone : Mapped [ str ] = mapped_column ( String ( 50 ) , default = " UTC " )
daily_task_time : Mapped [ Optional [ str ] ] = mapped_column ( String ( 5 ) ) # HH:MM
Добавлены основные функции MVP: тематические подборки, импорт слов, диалоговая практика, напоминания и тест уровня
Новые команды:
- /words [тема] - AI-генерация тематических подборок слов (10 слов по теме с учётом уровня)
- /import - извлечение до 15 ключевых слов из текста (книги, статьи, песни)
- /practice - диалоговая практика с AI в 6 сценариях (ресторан, магазин, путешествие, работа, врач, общение)
- /reminder - настройка ежедневных напоминаний по расписанию
- /level_test - тест из 7 вопросов для определения уровня английского (A1-C2)
Основные изменения:
- AI сервис: добавлены методы generate_thematic_words, extract_words_from_text, start_conversation, continue_conversation, generate_level_test
- Диалоговая практика: исправление ошибок в реальном времени, подсказки, перевод реплик
- Напоминания: APScheduler для ежедневной отправки напоминаний в выбранное время
- Тест уровня: автоматическое определение уровня при регистрации, можно пропустить
- База данных: добавлены поля reminders_enabled, last_reminder_sent
- Vocabulary service: метод get_word_by_original для проверки дубликатов
- Зависимости: apscheduler==3.10.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:46:02 +03:00
reminders_enabled : Mapped [ bool ] = mapped_column ( Boolean , default = False )
last_reminder_sent : Mapped [ Optional [ datetime ] ] = mapped_column ( DateTime )
2025-12-04 11:09:54 +03:00
streak_days : Mapped [ int ] = mapped_column ( Integer , default = 0 )
2025-12-08 15:16:24 +03:00
tasks_count : Mapped [ int ] = mapped_column ( Integer , default = 5 ) # Количество заданий (5-15)
2025-12-08 16:43:08 +03:00
ai_model_id : Mapped [ Optional [ int ] ] = mapped_column ( Integer , default = None ) # ID выбранной AI модели (NULL = глобальная)
2025-12-04 11:09:54 +03:00
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 "
2025-12-04 19:40:01 +03:00
__table_args__ = (
UniqueConstraint ( " user_id " , " source_lang " , " word_original " , name = " uq_vocab_user_lang_word " ) ,
)
2025-12-04 11:09:54 +03:00
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 )
2025-12-04 19:40:01 +03:00
source_lang : Mapped [ Optional [ str ] ] = mapped_column ( String ( 5 ) ) # ISO2 языка слова (язык изучения)
translation_lang : Mapped [ Optional [ str ] ] = mapped_column ( String ( 5 ) ) # ISO2 языка перевода (обычно язык интерфейса)
2025-12-04 11:09:54 +03:00
transcription : Mapped [ Optional [ str ] ] = mapped_column ( String ( 255 ) )
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 ) ) # Заметки пользователя
2025-12-06 21:29:41 +03:00
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 )
2025-12-04 11:09:54 +03:00
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 )
2025-12-08 15:16:24 +03:00
class AIModel ( Base ) :
""" Модель AI моделей для генерации """
__tablename__ = " ai_models "
id : Mapped [ int ] = mapped_column ( primary_key = True )
provider : Mapped [ AIProvider ] = mapped_column ( SQLEnum ( AIProvider ) , nullable = False ) # openai / google
model_name : Mapped [ str ] = mapped_column ( String ( 100 ) , nullable = False ) # gpt-4o-mini, gemini-2.5-flash-lite
display_name : Mapped [ str ] = mapped_column ( String ( 100 ) , nullable = False ) # Название для отображения
is_active : Mapped [ bool ] = mapped_column ( Boolean , default = False ) # Только одна модель активна
created_at : Mapped [ datetime ] = mapped_column ( DateTime , default = datetime . utcnow )
2025-12-09 15:05:38 +03:00
class StoryGenre ( str , enum . Enum ) :
""" Жанры мини-историй """
dialogue = " dialogue " # 🗣 Диалоги
news = " news " # 📰 Новости
story = " story " # 🎭 Истории
letter = " letter " # 📧 Письма
recipe = " recipe " # 🍳 Рецепты
class MiniStory ( Base ) :
""" Модель мини-истории для чтения """
__tablename__ = " mini_stories "
id : Mapped [ int ] = mapped_column ( primary_key = True )
user_id : Mapped [ int ] = mapped_column ( Integer , nullable = False , index = True )
title : Mapped [ str ] = mapped_column ( String ( 255 ) , nullable = False )
content : Mapped [ str ] = mapped_column ( String ( 5000 ) , nullable = False ) # Текст истории
translation : Mapped [ Optional [ str ] ] = mapped_column ( String ( 5000 ) , nullable = True ) # Перевод истории
genre : Mapped [ StoryGenre ] = mapped_column ( SQLEnum ( StoryGenre ) , nullable = False )
learning_lang : Mapped [ str ] = mapped_column ( String ( 5 ) , nullable = False ) # en/ja
level : Mapped [ str ] = mapped_column ( String ( 5 ) , nullable = False ) # A1-C2 или N5-N1
word_count : Mapped [ int ] = mapped_column ( Integer , default = 0 ) # Количество слов
vocabulary : Mapped [ Optional [ dict ] ] = mapped_column ( JSON ) # [{word, translation, transcription}]
questions : Mapped [ Optional [ dict ] ] = mapped_column ( JSON ) # [{question, options[], correct}]
is_completed : Mapped [ bool ] = mapped_column ( Boolean , default = False )
correct_answers : Mapped [ int ] = mapped_column ( Integer , default = 0 )
created_at : Mapped [ datetime ] = mapped_column ( DateTime , default = datetime . utcnow )
class WordOfDay ( Base ) :
""" Модель слова дня (глобальная для всех пользователей по уровню) """
__tablename__ = " word_of_day "
__table_args__ = (
UniqueConstraint ( " date " , " learning_lang " , " level " , name = " uq_wod_date_lang_level " ) ,
)
id : Mapped [ int ] = mapped_column ( primary_key = True )
word : Mapped [ str ] = mapped_column ( String ( 255 ) , nullable = False )
transcription : Mapped [ Optional [ str ] ] = mapped_column ( String ( 255 ) )
translation : Mapped [ str ] = mapped_column ( String ( 500 ) , nullable = False )
examples : Mapped [ Optional [ dict ] ] = mapped_column ( JSON ) # [{sentence, translation}]
synonyms : Mapped [ Optional [ str ] ] = mapped_column ( String ( 500 ) ) # Синонимы через запятую
etymology : Mapped [ Optional [ str ] ] = mapped_column ( String ( 500 ) ) # Этимология/интересный факт
learning_lang : Mapped [ str ] = mapped_column ( String ( 5 ) , nullable = False , index = True ) # en/ja
level : Mapped [ str ] = mapped_column ( String ( 5 ) , nullable = False , index = True ) # A1-C2 или N5-N1
date : Mapped [ datetime ] = mapped_column ( DateTime , nullable = False , index = True ) # Дата слова (только дата, без времени)
created_at : Mapped [ datetime ] = mapped_column ( DateTime , default = datetime . utcnow )