Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -9254,6 +9254,16 @@ def build_block_summary_keyboard(user_data: dict) -> InlineKeyboardMarkup:
translate("block_retry_errors", locale),
callback_data=f"bretry:{session_id}",
)
elif user_data.get("block_mode") == "flash":
primary = InlineKeyboardButton(
translate("block_reinforce_quiz", locale),
callback_data=f"bmode:{session_id}:quiz",
)
elif user_data.get("block_mode") == "quiz":
primary = InlineKeyboardButton(
translate("block_reinforce_written", locale),
callback_data=f"bmode:{session_id}:type",
Comment on lines +9262 to +9265

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep immediate reinforcement from advancing SRS stages

When a learner follows the new primary actions after perfect cards and quiz attempts, both callbacks start fresh, normally scored attempts over block_all_indices; each correct answer therefore runs through block_advance/bot_learning.rate and increments the same word's correct_count. A brand-new word can consequently go from 0 to 3 and receive a seven-day interval during one uninterrupted lesson, immediately satisfying the canonical “durable word” metric and postponing its first spaced review. These same-session reinforcement attempts need separate/non-SRS scoring, or otherwise must not advance the interval as independent reviews.

AGENTS.md reference: AGENTS.md:L10-L12

Useful? React with 👍 / 👎.

)
else:
primary = InlineKeyboardButton(
translate("block_another_lesson", locale),
Expand Down
16 changes: 16 additions & 0 deletions mydictionary/localization.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,8 @@
"block_intro": "📖 *{topic}*\nMemorize {count} words:\n\n{study}",
"block_quiz_mode": "Quiz · 4 choices",
"block_written_mode": "Written",
"block_reinforce_quiz": "🧠 Reinforce with a quiz",
"block_reinforce_written": "✍️ Answer in writing",
"block_ai_tutor": "AI tutor",
"block_voice_practice": "🎤 Pronounce 10 words",
"block_topics_study": "Topics 📚",
Expand All @@ -1005,6 +1007,8 @@
"block_intro": "📖 *{topic}*\nMémorisez {count} mots :\n\n{study}",
"block_quiz_mode": "Quiz · 4 choix",
"block_written_mode": "Par écrit",
"block_reinforce_quiz": "🧠 Consolider avec un quiz",
"block_reinforce_written": "✍️ Répondre par écrit",
"block_ai_tutor": "Tuteur IA",
"block_voice_practice": "🎤 Prononcer 10 mots",
"block_topics_study": "Thèmes 📚",
Expand All @@ -1031,6 +1035,8 @@
"block_intro": "📖 *{topic}*\nPräge dir {count} Wörter ein:\n\n{study}",
"block_quiz_mode": "Quiz · 4 Antworten",
"block_written_mode": "Schriftlich",
"block_reinforce_quiz": "🧠 Mit einem Quiz festigen",
"block_reinforce_written": "✍️ Schriftlich antworten",
"block_ai_tutor": "KI-Tutor",
"block_voice_practice": "🎤 10 Wörter aussprechen",
"block_topics_study": "Themen 📚",
Expand All @@ -1057,6 +1063,8 @@
"block_intro": "📖 *{topic}*\n{count}語を覚えましょう:\n\n{study}",
"block_quiz_mode": "クイズ · 4択",
"block_written_mode": "入力",
"block_reinforce_quiz": "🧠 クイズで定着",
"block_reinforce_written": "✍️ 入力して答える",
"block_ai_tutor": "AIチューター",
"block_voice_practice": "🎤 10語を発音",
"block_topics_study": "トピック 📚",
Expand All @@ -1083,6 +1091,8 @@
"block_intro": "📖 *{topic}*\nاحفظ {count} كلمات:\n\n{study}",
"block_quiz_mode": "اختبار · 4 خيارات",
"block_written_mode": "كتابة",
"block_reinforce_quiz": "🧠 ثبّت التعلم باختبار",
"block_reinforce_written": "✍️ أجب كتابةً",
"block_ai_tutor": "معلّم AI",
"block_voice_practice": "🎤 انطق 10 كلمات",
"block_topics_study": "الموضوعات 📚",
Expand All @@ -1109,6 +1119,8 @@
"block_intro": "📖 *{topic}*\n请记住 {count} 个单词:\n\n{study}",
"block_quiz_mode": "测验 · 4 个选项",
"block_written_mode": "书写",
"block_reinforce_quiz": "🧠 用测验巩固",
"block_reinforce_written": "✍️ 书面作答",
"block_ai_tutor": "AI 导师",
"block_voice_practice": "🎤 朗读 10 个单词",
"block_topics_study": "主题 📚",
Expand All @@ -1135,6 +1147,8 @@
"block_intro": "📖 *{topic}*\nЗапомни {count} слов:\n\n{study}",
"block_quiz_mode": "Тест · 4 варианта",
"block_written_mode": "Письменно",
"block_reinforce_quiz": "🧠 Закрепить тестом",
"block_reinforce_written": "✍️ Ответить письменно",
"block_ai_tutor": "AI-репетитор",
"block_voice_practice": "🎤 Произнести 10 слов",
"block_topics_study": "Темы 📚",
Expand All @@ -1161,6 +1175,8 @@
"block_intro": "📖 *{topic}*\nMemoriza {count} palabras:\n\n{study}",
"block_quiz_mode": "Test · 4 opciones",
"block_written_mode": "Por escrito",
"block_reinforce_quiz": "🧠 Reforzar con un test",
"block_reinforce_written": "✍️ Responder por escrito",
"block_ai_tutor": "Tutor de IA",
"block_voice_practice": "🎤 Pronunciar 10 palabras",
"block_topics_study": "Temas 📚",
Expand Down
89 changes: 89 additions & 0 deletions tests/test_post_lesson_reinforcement_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Post-lesson reinforcement keeps learners moving without another chooser."""

import os
import unittest

os.environ.setdefault("BOT_TOKEN", "123456:TESTTOKEN_ABCDEFGHIJKLMNOP")
os.environ.setdefault("ALLOWED_USER_ID", "1")
os.environ.setdefault("ALLOW_SQLITE_DEV", "true")

import bot
from mydictionary.localization import INTERFACE_LOCALES, translate


def buttons(markup):
return [button for row in markup.inline_keyboard for button in row]


class PostLessonReinforcementTest(unittest.TestCase):
def completed(self, mode: str, *, wrong: list[int] | None = None, locale: str = "en"):
state = {"interface_locale": locale}
bot.reset_block_state(
state,
[0, 1, 2],
"ja",
None,
"ja-basics-100",
lesson_kind="daily",
)
bot.start_block_attempt(state, mode)
mistakes = list(wrong or [])
state.update(
block_pos=3,
block_correct=3 - len(mistakes),
block_wrong=mistakes,
)
return state

def test_perfect_cards_offer_quiz_on_the_same_words(self):
state = self.completed("flash")

inline = buttons(bot.build_block_summary_keyboard(state))

self.assertLessEqual(len(inline), 3)
self.assertEqual(
inline[0].callback_data,
f"bmode:{state['block_session']}:quiz",
)
self.assertEqual(inline[0].text, translate("block_reinforce_quiz", "en"))

def test_perfect_quiz_offers_written_recall_on_the_same_words(self):
state = self.completed("quiz", locale="ru")

inline = buttons(bot.build_block_summary_keyboard(state))

self.assertLessEqual(len(inline), 3)
self.assertEqual(
inline[0].callback_data,
f"bmode:{state['block_session']}:type",
)
self.assertEqual(inline[0].text, translate("block_reinforce_written", "ru"))

def test_perfect_written_practice_still_offers_another_lesson(self):
state = self.completed("type")

inline = buttons(bot.build_block_summary_keyboard(state))

self.assertEqual(inline[0].callback_data, "start:daily")
self.assertEqual(inline[0].text, translate("block_another_lesson", "en"))

def test_errors_keep_retry_first_in_every_mode(self):
for mode in ("flash", "quiz", "type"):
with self.subTest(mode=mode):
state = self.completed(mode, wrong=[1])
inline = buttons(bot.build_block_summary_keyboard(state))
self.assertLessEqual(len(inline), 3)
self.assertEqual(
inline[0].callback_data,
f"bretry:{state['block_session']}",
)

def test_reinforcement_copy_exists_for_every_interface_locale(self):
for locale in INTERFACE_LOCALES:
with self.subTest(locale=locale):
self.assertTrue(translate("block_reinforce_quiz", locale).strip())
self.assertTrue(translate("block_reinforce_written", locale).strip())


if __name__ == "__main__":
unittest.main()
Loading