feat(gamification): Phase 3 — 38 new achievements + triggers + 'exam' group

Adds achievement coverage for every feature shipped since the original
seed: exam-prep (math9), textbooks, classroom/board, biochemistry,
live-quiz, flashcards, hangman/crossword, pet, plus a new 'social' group
for class & leaderboard wins and 'consistency' extensions (streak_100,
goal_30, early_bird, night_owl).

74 achievements now (was 36), grouped into 7 sections:
  onboarding (3) → volume (8) → mastery (16) → consistency (7) →
  exam (9) → exploration (21) → social (10)

A new top-level group 'exam' slots between consistency and exploration
in the profile UI.

What's wired in service.checkPhase3Achievements (called from
checkAchievements):
  • streak_100 — extends the existing streak track
  • goal_30 — 30 days with daily_goals fully met (SUM check)
  • early_bird / night_owl — strftime('%H', xp_log.created_at)
  • exam_first / 25 / 100 — exam_attempts where is_correct=1
  • exam_variant_clear / 5_variants — perfect mock-variant sessions
  • exam_topic_master — ≥10 attempts at ≥90% on a single subtopic
  • exam_mock_done / pass / perfect — exam_mock_sessions.score
  • tb_first_para — textbook_progress
  • fc_first_deck / 100_cards / 1000_cards — flashcard_reviews
  • bc_first_molecule / 5_challenges / 20_challenges — bio_user_*
  • game_win_5 / 25 — xp_log reason IN (hangman_win, crossword_win)
  • pet_streak_7 / 30 — users.pet_petting_streak
  • lq_first / 3_quizzes — live_answers grouped by session
  • cr_first_join / 5 / 25_lessons — classroom_attendance
  • class_5_members / 25 — teacher's biggest class
  • parent_link — parent_links presence
  • lb_top10 / lb_top1 — weekly XP rank among students

What's deferred (catalog entry only, no trigger yet):
  • tb_chapter_done / tb_book_done / tb_3_books — need to parse
    textbook_progress.paragraphs_read JSON against textbook structure

Every block is wrapped in its own try/catch so a missing table on a
legacy install can't take down the whole achievement sweep.

Verified end-to-end: admin user picked up 7 new unlocks on first
checkAchievements call after seed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 20:26:59 +03:00
parent 90c8464356
commit b005226e2c
4 changed files with 324 additions and 1 deletions
@@ -0,0 +1,68 @@
-- ═══════════════════════════════════════════════════════════════
-- 031: Phase 3 — new achievement catalog
--
-- Adds 38 achievements covering features that landed since the original
-- 36 were seeded: exam-prep (exam9), textbooks, classroom/board,
-- biochemistry, live-quiz, flashcards, hangman/crossword, plus a new
-- 'social' group for class & leaderboard wins and a few stretch goals
-- in 'consistency' (streak_100, goal_30, early-bird / night-owl).
--
-- Inserts use INSERT OR IGNORE so re-runs are safe; backend
-- seedAchievements() will keep title/icon/desc/sort_order in sync on
-- every boot via UPDATE.
--
-- A new top-level group 'exam' slots between 'consistency' (400) and
-- 'exploration' (500). Group display metadata is hard-coded in both
-- backend (gamification/_shared.ACHIEVEMENT_GROUPS) and frontend
-- (profile.html ACH_GROUPS); keep them in sync.
-- ═══════════════════════════════════════════════════════════════
INSERT OR IGNORE INTO achievements
(slug, title, icon, category, description, group_slug, track, tier, sort_order)
VALUES
-- ── consistency extensions ─────────────────────────────────
('streak_100', 'Сто дней подряд', 'flame', 'streak', 'Невероятная серия — 100 дней активности подряд', 'consistency', 'streak', 4, 440),
('goal_30', 'Цель: 30 дней', 'target', 'streak', '30 дней подряд выполнять цель дня', 'consistency', 'goal_streak', 1, 450),
('early_bird', 'Ранняя пташка', 'sunrise', 'streak', 'Активность до 9 утра', 'consistency', 'time', 1, 460),
('night_owl', 'Ночная сова', 'moon', 'streak', 'Активность после 23:00', 'consistency', 'time', 2, 470),
-- ── exam-prep (math9) ──────────────────────────────────────
('exam_first', 'Старт экзамена', 'flag', 'exam', 'Решена первая задача экзамена 9 класса', 'exam', 'exam_attempts', 1, 700),
('exam_25_attempts', '25 задач экзамена', 'list-checks', 'exam', '25 решённых попыток в банке экзамена', 'exam', 'exam_attempts', 2, 710),
('exam_100_attempts', '100 задач экзамена', 'check-square', 'exam', '100 решённых попыток в банке экзамена', 'exam', 'exam_attempts', 3, 720),
('exam_variant_clear', 'Чистый вариант', 'crown', 'exam', 'Все 10 задач варианта решены верно', 'exam', 'variants', 1, 730),
('exam_5_variants', '5 вариантов', 'layers', 'exam', '5 разных вариантов закрыто целиком', 'exam', 'variants', 2, 740),
('exam_topic_master', 'Мастер темы', 'target', 'exam', '≥90% точности на одной из подтем (≥10 попыток)', 'exam', 'topic', 1, 750),
('exam_mock_done', 'Первый пробник', 'timer', 'exam', 'Завершён первый пробный экзамен', 'exam', 'mock', 1, 760),
('exam_mock_pass', 'Пробник сдан', 'badge-check', 'exam', 'Балл ≥ 7 на пробном экзамене', 'exam', 'mock', 2, 770),
('exam_mock_perfect', 'Пробник на 10', 'trophy', 'exam', 'Идеальные 10 баллов на пробном экзамене', 'exam', 'mock', 3, 780),
-- ── mastery extensions (textbooks + flashcards) ────────────
('tb_first_para', 'Открыл параграф', 'book-marked', 'theory', 'Прочитан первый параграф учебника', 'mastery', 'tb_progress', 1, 800),
('tb_chapter_done','Глава закрыта', 'book-check', 'theory', 'Все параграфы одной главы пройдены', 'mastery', 'tb_progress', 2, 810),
('tb_book_done', 'Учебник целиком', 'book-up', 'theory', 'Все параграфы учебника пройдены', 'mastery', 'tb_progress', 3, 820),
('tb_3_books', 'Полка из трёх', 'library-big', 'theory', '3 учебника закрыто полностью', 'mastery', 'tb_progress', 4, 830),
('fc_first_deck', 'Первая колода', 'layers', 'theory', 'Все карточки одной колоды разок повторены', 'mastery', 'flashcards', 1, 840),
('fc_100_cards', '100 карточек', 'rotate-cw', 'theory', '100 повторов карточек в сумме', 'mastery', 'flashcards', 2, 850),
('fc_1000_cards', '1000 карточек', 'sparkles', 'theory', '1000 повторов карточек в сумме', 'mastery', 'flashcards', 3, 860),
-- ── exploration extensions (biochem / games / pet) ─────────
('bc_first_molecule', 'Первая молекула', 'flask-round', 'lab', 'Собрана первая молекула в био-конструкторе', 'exploration', 'biochem', 1, 870),
('bc_5_challenges', '5 био-задач', 'beaker', 'lab', '5 биохимических задач решено', 'exploration', 'biochem', 2, 880),
('bc_20_challenges', '20 био-задач', 'atom', 'lab', '20 биохимических задач решено', 'exploration', 'biochem', 3, 890),
('game_win_5', '5 побед в играх', 'gamepad-2', 'volume','5 побед в Виселице или Кроссворде суммарно', 'exploration', 'minigames', 1, 900),
('game_win_25', '25 побед в играх', 'gamepad', 'volume','25 побед в Виселице или Кроссворде суммарно', 'exploration', 'minigames', 2, 910),
('pet_streak_7', 'Заботливый', 'heart', 'volume','7 дней подряд гладить питомца', 'exploration', 'pet', 1, 920),
('pet_streak_30', 'Лучший друг', 'paw-print', 'volume','30 дней подряд гладить питомца', 'exploration', 'pet', 2, 930),
-- ── social (NEW group) ─────────────────────────────────────
('lb_top10', 'Топ-10 недели', 'trending-up', 'volume', 'Попасть в топ-10 лидерборда недели', 'social', 'leaderboard', 1, 1000),
('lb_top1', 'Первое место', 'medal', 'volume', '№1 в лидерборде недели', 'social', 'leaderboard', 2, 1010),
('lq_first', 'Первый live-quiz', 'radio', 'volume', 'Принять участие в первом live-квизе', 'social', 'live_quiz', 1, 1020),
('lq_3_quizzes', '3 live-квиза', 'megaphone', 'volume', 'Принять участие в 3 live-квизах', 'social', 'live_quiz', 2, 1030),
('cr_first_join', 'Первое подключение','door-open', 'volume', 'Первое подключение к уроку в classroom', 'social', 'classroom', 1, 1040),
('cr_5_lessons', '5 уроков', 'presentation', 'volume', 'Посещено 5 уроков classroom', 'social', 'classroom', 2, 1050),
('cr_25_lessons', '25 уроков', 'school', 'volume', 'Посещено 25 уроков classroom', 'social', 'classroom', 3, 1060),
('class_5_members', 'Учитель: 5 учеников','users', 'start', 'Учитель: класс из ≥5 учеников', 'social', 'teacher', 1, 1070),
('class_25_members', 'Учитель: 25 учеников','users-2', 'start', 'Учитель: класс из ≥25 учеников', 'social', 'teacher', 2, 1080),
('parent_link', 'Связан с родителем','user-plus', 'start', 'Связь с родителем установлена', 'social', 'family', 1, 1090);