Files
Learn_System/backend/src/db/migrations/030_achievements_grouping.sql
T
Maxim Dolgolyov 90c8464356 feat(gamification): Phase 2 — taxonomy + grouped UI for achievements
Achievements gain four new columns: group_slug, track, tier, sort_order.
Existing 36 are backfilled into 5 groups (onboarding/volume/mastery/
consistency/exploration) by migration 030; 'social' stays empty until
Phase 3 adds class/leaderboard/live-quiz tracks.

Tracks bundle escalating thresholds into one progression (tests_10/50/
100 → track='tests', tiers 1-3), so the UI can show '★★★' on the top
tier and the user understands the relationship. sort_order is reserved
in blocks of 10 inside groups of 100, leaving room for inserts without
renumbering.

Backend:
  • migration 030 adds the columns + index + backfill UPDATEs
  • _shared.ACHIEVEMENT_DEFS gains group/track/tier/sort_order per row
  • _shared exports new ACHIEVEMENT_GROUPS metadata for the UI
  • service.seedAchievements writes the new fields on insert AND
    backfills them via UPDATE on existing rows (fresh installs +
    pre-migration installs both end up consistent)
  • _shared.stmts.getAllAchs SELECT updated, ORDER BY sort_order
  • gamification/api.getAchievements forwards the new fields

Frontend:
  • profile.html groups achievements by group_slug with a per-section
    header (icon + title + 'unlocked / total' chip) and a tier-star
    badge (★★ etc.) on tier ≥ 2 items
  • Hard-coded ACH_GROUPS mirror of the backend list (small, stable)
  • New CSS for .ach-group / .ach-group-head / .ach-tier

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:19:46 +03:00

93 lines
7.0 KiB
SQL

-- ═══════════════════════════════════════════════════════════════
-- 030: Two-level taxonomy for achievements
--
-- The existing `category` field stays for backward compatibility, but
-- the UI now groups achievements by `group_slug` (six top-level
-- buckets) and orders them by `(group_slug, sort_order)`. Inside a
-- bucket related achievements with escalating thresholds share a
-- `track` (e.g. tests_10/50/100 → track='tests') and a `tier`
-- (1..5). Singletons leave tier NULL.
--
-- New groups:
-- onboarding — first steps in the app
-- volume — quantity (tests, assignments, XP totals)
-- mastery — quality: streaks/levels were folded into consistency
-- and mastery respectively; pure mastery = score/speed
-- /perfect/course completion
-- consistency — streaks, daily goals, plan adherence
-- exploration — feature discovery: lab, biochem, red book
-- social — class membership, leaderboard (filled in Phase 3)
--
-- Sort order convention: groups are spaced by 100s (onboarding=100,
-- volume=200, ...), achievements inside use offsets so we can insert
-- new ones later without renumbering.
-- ═══════════════════════════════════════════════════════════════
ALTER TABLE achievements ADD COLUMN group_slug TEXT;
ALTER TABLE achievements ADD COLUMN track TEXT;
ALTER TABLE achievements ADD COLUMN tier INTEGER;
ALTER TABLE achievements ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0;
CREATE INDEX IF NOT EXISTS idx_ach_group_sort ON achievements(group_slug, sort_order);
-- ── onboarding (group=100) ─────────────────────────────────────
UPDATE achievements SET group_slug='onboarding', track='first_test', tier=1, sort_order=110 WHERE slug='first_test';
UPDATE achievements SET group_slug='onboarding', track='first_perfect', tier=1, sort_order=120 WHERE slug='first_perfect';
UPDATE achievements SET group_slug='onboarding', track='first_class', tier=1, sort_order=130 WHERE slug='first_class';
-- ── volume (group=200): quantity of things done ────────────────
-- track=tests (10/50/100)
UPDATE achievements SET group_slug='volume', track='tests', tier=1, sort_order=210 WHERE slug='tests_10';
UPDATE achievements SET group_slug='volume', track='tests', tier=2, sort_order=220 WHERE slug='tests_50';
UPDATE achievements SET group_slug='volume', track='tests', tier=3, sort_order=230 WHERE slug='tests_100';
-- track=assign (first/10)
UPDATE achievements SET group_slug='volume', track='assign', tier=1, sort_order=240 WHERE slug='assign_first';
UPDATE achievements SET group_slug='volume', track='assign', tier=2, sort_order=250 WHERE slug='assign_10';
-- track=xp_total (1000/5000/10000)
UPDATE achievements SET group_slug='volume', track='xp_total', tier=1, sort_order=260 WHERE slug='xp_1000';
UPDATE achievements SET group_slug='volume', track='xp_total', tier=2, sort_order=270 WHERE slug='xp_5000';
UPDATE achievements SET group_slug='volume', track='xp_total', tier=3, sort_order=280 WHERE slug='xp_10000';
-- ── mastery (group=300): quality / completion ──────────────────
-- track=level (3/5/10/20)
UPDATE achievements SET group_slug='mastery', track='level', tier=1, sort_order=310 WHERE slug='level_3';
UPDATE achievements SET group_slug='mastery', track='level', tier=2, sort_order=320 WHERE slug='level_5';
UPDATE achievements SET group_slug='mastery', track='level', tier=3, sort_order=330 WHERE slug='level_10';
UPDATE achievements SET group_slug='mastery', track='level', tier=4, sort_order=340 WHERE slug='level_20';
-- track=score (90% streak, perfect, speed)
UPDATE achievements SET group_slug='mastery', track='score', tier=1, sort_order=350 WHERE slug='score_90';
UPDATE achievements SET group_slug='mastery', track='speed', tier=1, sort_order=360 WHERE slug='speed_demon';
-- track=theory_progress (first/10/course)
UPDATE achievements SET group_slug='mastery', track='theory', tier=1, sort_order=370 WHERE slug='theory_first';
UPDATE achievements SET group_slug='mastery', track='theory', tier=2, sort_order=380 WHERE slug='theory_10';
UPDATE achievements SET group_slug='mastery', track='theory', tier=3, sort_order=390 WHERE slug='theory_course';
-- ── consistency (group=400): streaks, plan, goals ──────────────
UPDATE achievements SET group_slug='consistency', track='streak', tier=1, sort_order=410 WHERE slug='streak_3';
UPDATE achievements SET group_slug='consistency', track='streak', tier=2, sort_order=420 WHERE slug='streak_7';
UPDATE achievements SET group_slug='consistency', track='streak', tier=3, sort_order=430 WHERE slug='streak_30';
-- ── exploration (group=500): feature discovery ─────────────────
-- track=lab_experiments (first/5/20/50)
UPDATE achievements SET group_slug='exploration', track='lab', tier=1, sort_order=510 WHERE slug='lab_first';
UPDATE achievements SET group_slug='exploration', track='lab', tier=2, sort_order=520 WHERE slug='lab_5';
UPDATE achievements SET group_slug='exploration', track='lab', tier=3, sort_order=530 WHERE slug='lab_20';
UPDATE achievements SET group_slug='exploration', track='lab', tier=4, sort_order=540 WHERE slug='lab_50';
-- track=lab_reactions (10/30)
UPDATE achievements SET group_slug='exploration', track='lab_reactions', tier=1, sort_order=550 WHERE slug='lab_reactions_10';
UPDATE achievements SET group_slug='exploration', track='lab_reactions', tier=2, sort_order=560 WHERE slug='lab_reactions_30';
-- track=red_book (first/10/25/50/all)
UPDATE achievements SET group_slug='exploration', track='red_book', tier=1, sort_order=570 WHERE slug='rb_first';
UPDATE achievements SET group_slug='exploration', track='red_book', tier=2, sort_order=580 WHERE slug='rb_10';
UPDATE achievements SET group_slug='exploration', track='red_book', tier=3, sort_order=590 WHERE slug='rb_25';
UPDATE achievements SET group_slug='exploration', track='red_book', tier=4, sort_order=600 WHERE slug='rb_50';
UPDATE achievements SET group_slug='exploration', track='red_book', tier=5, sort_order=610 WHERE slug='rb_all_cr';
-- track=red_book_quests (first/5)
UPDATE achievements SET group_slug='exploration', track='red_book_quest',tier=1, sort_order=620 WHERE slug='rb_quest_first';
UPDATE achievements SET group_slug='exploration', track='red_book_quest',tier=2, sort_order=630 WHERE slug='rb_quest_5';
-- singletons
UPDATE achievements SET group_slug='exploration', track='red_book_obs', tier=1, sort_order=640 WHERE slug='rb_sighting';
-- ── social (group=600): empty for now, filled in Phase 3 ───────
-- (no UPDATEs here — placeholder for class/leaderboard/live-quiz tracks)