Files
tg_bot_language/services/wordofday_service.py

228 lines
8.5 KiB
Python
Raw Normal View History

"""Сервис генерации слова дня"""
import logging
from datetime import datetime, date
from typing import Optional, Dict, List
from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession
from database.db import async_session_maker
from database.models import WordOfDay, LanguageLevel, JLPTLevel, JLPT_LANGUAGES
from services.ai_service import ai_service
logger = logging.getLogger(__name__)
# Уровни для каждого языка
CEFR_LEVELS = [level.value for level in LanguageLevel] # A1-C2
JLPT_LEVELS = [level.value for level in JLPTLevel] # N5-N1
# Языки для генерации
LEARNING_LANGUAGES = ["en", "ja"]
class WordOfDayService:
"""Сервис для генерации и получения слова дня"""
async def generate_all_words_for_today(self) -> Dict[str, int]:
"""
Генерация слов дня для всех языков и уровней.
Вызывается в 00:00 UTC.
Returns:
Dict с количеством сгенерированных слов по языкам
"""
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
results = {"en": 0, "ja": 0, "errors": 0}
async with async_session_maker() as session:
for lang in LEARNING_LANGUAGES:
levels = JLPT_LEVELS if lang in JLPT_LANGUAGES else CEFR_LEVELS
for level in levels:
try:
# Проверяем, не сгенерировано ли уже
existing = await self._get_word_for_date(
session, today, lang, level
)
if existing:
logger.debug(
f"Слово дня уже существует: {lang}/{level}"
)
continue
# Получаем список недавних слов для исключения
excluded = await self._get_recent_words(session, lang, level, days=30)
# Генерируем слово
word_data = await ai_service.generate_word_of_day(
level=level,
learning_lang=lang,
translation_lang="ru", # Базовый перевод на русский
excluded_words=excluded
)
if word_data:
word_of_day = WordOfDay(
word=word_data.get("word", ""),
transcription=word_data.get("transcription"),
translation=word_data.get("translation", ""),
examples=word_data.get("examples"),
synonyms=word_data.get("synonyms"),
etymology=word_data.get("etymology"),
learning_lang=lang,
level=level,
date=today
)
session.add(word_of_day)
await session.commit()
results[lang] += 1
logger.info(
f"Сгенерировано слово дня: {word_data.get('word')} "
f"({lang}/{level})"
)
else:
results["errors"] += 1
logger.warning(
f"Не удалось сгенерировать слово для {lang}/{level}"
)
except Exception as e:
results["errors"] += 1
logger.error(
f"Ошибка генерации слова для {lang}/{level}: {e}"
)
total = results["en"] + results["ja"]
logger.info(
f"Генерация слов дня завершена: всего={total}, "
f"en={results['en']}, ja={results['ja']}, ошибок={results['errors']}"
)
return results
async def get_word_of_day(
self,
learning_lang: str,
level: str,
target_date: Optional[datetime] = None
) -> Optional[WordOfDay]:
"""
Получить слово дня для языка и уровня.
Args:
learning_lang: Язык изучения (en/ja)
level: Уровень (A1-C2 или N5-N1)
target_date: Дата (по умолчанию сегодня)
Returns:
WordOfDay или None
"""
if target_date is None:
target_date = datetime.utcnow().replace(
hour=0, minute=0, second=0, microsecond=0
)
else:
target_date = target_date.replace(
hour=0, minute=0, second=0, microsecond=0
)
async with async_session_maker() as session:
return await self._get_word_for_date(
session, target_date, learning_lang, level
)
async def _get_word_for_date(
self,
session: AsyncSession,
target_date: datetime,
learning_lang: str,
level: str
) -> Optional[WordOfDay]:
"""Получить слово из БД для конкретной даты"""
result = await session.execute(
select(WordOfDay).where(
and_(
WordOfDay.date == target_date,
WordOfDay.learning_lang == learning_lang,
WordOfDay.level == level
)
)
)
return result.scalar_one_or_none()
async def _get_recent_words(
self,
session: AsyncSession,
learning_lang: str,
level: str,
days: int = 30
) -> List[str]:
"""Получить список недавних слов для исключения"""
from datetime import timedelta
cutoff_date = datetime.utcnow() - timedelta(days=days)
result = await session.execute(
select(WordOfDay.word).where(
and_(
WordOfDay.learning_lang == learning_lang,
WordOfDay.level == level,
WordOfDay.date >= cutoff_date
)
)
)
return [row[0] for row in result.fetchall()]
async def format_word_for_user(
self,
word: WordOfDay,
translation_lang: str = "ru",
ui_lang: str = None
) -> str:
"""
Форматировать слово дня для отображения пользователю.
Args:
word: Объект WordOfDay
translation_lang: Язык перевода для пользователя
ui_lang: Язык интерфейса (для локализации заголовков)
Returns:
Отформатированная строка
"""
from utils.i18n import t
lang = ui_lang or translation_lang or "ru"
lines = []
# Заголовок со словом
if word.transcription:
lines.append(f"📚 <b>{word.word}</b> [{word.transcription}]")
else:
lines.append(f"📚 <b>{word.word}</b>")
# Перевод
lines.append(f"📝 {word.translation}")
# Синонимы
if word.synonyms:
lines.append(f"\n🔄 <b>{t(lang, 'wod.synonyms')}:</b> {word.synonyms}")
# Примеры
if word.examples:
lines.append(f"\n📖 <b>{t(lang, 'wod.examples')}:</b>")
for i, example in enumerate(word.examples[:3], 1):
sentence = example.get("sentence", "")
translation = example.get("translation", "")
lines.append(f" {i}. {sentence}")
if translation:
lines.append(f" <i>{translation}</i>")
# Этимология/интересный факт
if word.etymology:
lines.append(f"\n💡 {word.etymology}")
return "\n".join(lines)
# Глобальный экземпляр сервиса
wordofday_service = WordOfDayService()