# Gamification — Points, Streaks & Badges

> **Status**: Phase 1 COMPLETE — deployed to production **March 6, 2026** (commit `b0163dc0`)

## TL;DR
Lightweight gamification layer: activity points for contributions, login streaks for daily engagement, and badges for milestones. Leverages existing activity data (read history, Q&A, communities) to create visible progress that motivates researchers to return.

---

## What Was Implemented (Phase 1)

### Entities ✅
| Entity | Table | File |
|--------|-------|------|
| `UserActivity` | `user_activity` | `src/Entity/UserActivity.php` |
| `UserBadge` | `user_badge` | `src/Entity/UserBadge.php` |
| `UserStreak` | `user_streak` | `src/Entity/UserStreak.php` |

- Migration: `migrations/Version20260306100000.php` — creates all 3 tables with indexes and FK to `fos_user`
- All 3 repositories: `src/Repository/UserActivityRepository.php`, `UserBadgeRepository.php`, `UserStreakRepository.php`

### GamificationService ✅
`src/Service/GamificationService.php` — core orchestrator with:
- `awardPoints(User, type, referenceId, customPoints)` — dedup, daily caps, activity creation, streak update, badge check
- `updateStreak(User)` — extends/resets streak, awards streak bonus (streak_days × 1pt)
- `checkBadges(User)` — iterates all 13 badge definitions, awards newly qualified
- `getUserProfile(User)` — totalPoints, monthPoints, monthRank, streak, badges
- `getLeaderboard()` — top 50 by monthly points
- `getNewBadgesSince(User, since)` — for celebration modal polling

### Point Values ✅

| Action | Type Constant | Points | Daily Cap |
|--------|---------------|--------|-----------|
| Read a paper | `read_paper` | 1 | 10/day |
| Rate a paper | `rate_paper` | 1 | 5/day |
| Save to favorites | `save_favorite` | 1 | 5/day |
| Ask a question | `ask_question` | 5 | 2/day |
| Answer a question | `answer_question` | 5 | 5/day |
| Upload a reference | `upload_reference` | 3 | 10/day |
| Join a community | `community_join` | 3 | 3/day |
| Post in community | `community_post` | 3 | 5/day |
| Login streak bonus | `login_streak` | streak_days × 1 | — |
| Use AI tool | `ai_usage` | 2 | 20/day |

### Badge Definitions ✅ (13 badges)

| Badge ID | Name (ar) | Name (en) | Threshold |
|----------|-----------|-----------|-----------|
| `reader_10` | قارئ | Reader | 10 papers read |
| `reader_50` | باحث نهم | Avid Reader | 50 papers read |
| `reader_100` | عالم | Scholar | 100 papers read |
| `contributor_1` | مساهم | Contributor | 1st Q&A answer |
| `contributor_10` | خبير | Expert | 10 Q&A answers |
| `contributor_50` | مرشد أكاديمي | Academic Mentor | 50 Q&A answers |
| `streak_7` | ملتزم | Consistent | 7-day streak |
| `streak_30` | مثابر | Dedicated | 30-day streak |
| `streak_100` | مخلص | Devoted | 100-day streak |
| `library_10` | مكتبة صغيرة | Mini Library | 10 uploads |
| `library_50` | مكتبة متكاملة | Full Library | 50 uploads |
| `social_5` | اجتماعي | Social | 5 communities joined |
| `ai_pioneer` | رائد الذكاء الاصطناعي | AI Pioneer | 1st AI tool use |

### Controller Wiring

| Controller | Action | Activity Type | Status |
|------------|--------|---------------|--------|
| `ResearchController::showAction` | Read paper | `read_paper` (1pt, dedup by slug) | ✅ Works for MySQL papers |
| `ResearchController::rateResearchAction` | Rate paper | `rate_paper` (1pt) | ⚠️ MySQL papers only — see below |
| `ResearchController::addToFavouriteAction` | Save favorite | `save_favorite` (1pt) | ⚠️ MySQL papers only — see below |
| `QuestionController::newQuestionAction` | Ask question | `ask_question` (5pt) | ✅ |
| `QuestionController::answerQuestionAction` | Answer question | `answer_question` (5pt) | ✅ |
| `UserReferenceController::upload` | Upload reference | `upload_reference` (3pt) | ✅ |
| `CommunityMemberController::follow` | Join community | `community_join` (3pt) | ✅ |
| `CommunityQuestionController::new` | Community post | `community_post` (3pt) | ✅ |

> **Note**: `rate_paper` and `save_favorite` are wired in `ResearchController`, not `HomepageController`.

#### ⚠️ ES-Only Papers Gap (INCOMPLETE)

The platform has two paper templates:
- **`show.html.twig`** — for papers stored in MySQL. Has rating stars, favourite button, and full JS.
- **`show_elastic.html.twig`** — for papers that exist ONLY in Elasticsearch (not in MySQL). **This template is MISSING both the rating UI and the favourite/save button entirely.**

Since a large portion of English papers (97k in ES vs ~12k in MySQL) are ES-only, the `rate_paper` and `save_favorite` gamification actions are effectively **unavailable for most English papers**.

**To fix**: Add rating stars + favourite button to `show_elastic.html.twig`, along with the required JS and AJAX handlers. The gamification `awardPoints()` calls are already in the backend controller actions — the missing piece is purely frontend.

### Login Streak Listener ✅
`src/EventListener/GamificationLoginListener.php` — listens to `LoginSuccessEvent` at priority -10, calls `updateStreak()`.

### Profile Display ✅
`templates/Profile/show/personal_info.html.twig` — sidebar cards showing:
- Points & Achievements (total points, monthly points, monthly rank, streak fire indicator)
- Badges grid (earned = colored with icon, locked = grayed out)
- Link to leaderboard

### Monthly Leaderboard ✅
- Controller: `src/Controller/LeaderboardController.php`
- Template: `templates/leaderboard/index.html.twig`
- Routes: `/leaderboard` (ar), `/en/leaderboard` (en)
- Top 50 users by monthly points, with avatar, name, points, badges
- "My Stats" card at the top for logged-in users

### Badge Celebration Modal ✅
- Added to `templates/base.html.twig`
- JS polls `/api/gamification/new-badges` every 60 seconds
- Shows pop-animation celebration overlay with badge name/icon when earned
- Stores `lastBadgeCheck` timestamp in localStorage

### API Endpoints ✅
| Route | Method | Purpose |
|-------|--------|---------|
| `/api/gamification/new-badges` | GET | Poll for newly earned badges (used by celebration modal) |
| `/api/gamification/stats` | GET | User's current gamification stats (JSON) |

### Translations ✅
- `translations/Gamification.ar.yml` — all badge names, descriptions, activity labels, leaderboard UI
- `translations/Gamification.en.yml`

---

## What Is NOT Yet Wired / Incomplete

| Item | Status | Priority | Notes |
|------|--------|----------|-------|
| Rate paper on ES-only pages | **INCOMPLETE** | **HIGH** | `show_elastic.html.twig` has no rating stars UI. Backend action + gamification wiring exist — need frontend only. |
| Save to favorites on ES-only pages | **INCOMPLETE** | **HIGH** | `show_elastic.html.twig` has no favourite button. Backend action + gamification wiring exist — need frontend only. |
| AI tool usage (`ai_usage`) | **✅ WIRED** | — | Wired in `UsageMonitorService::logApiCall()` — single chokepoint for all 13+ AI operations. Awards 2pts per successful AI call, capped 20/day. |
| Leaderboard filter by academic field | **Not implemented** | Low | Current leaderboard is global, no field/department filter |
| Social media sharing from badge modal | **✅ IMPLEMENTED** | — | Share buttons for Twitter, LinkedIn, Facebook + Web Share API for mobile. Shares localized badge name with link to site. |
| Historical backfill | **Not done** | Low | Users with existing activity (papers read, questions answered) start from zero |

### Implementation Notes for ES-Only Paper Features

**Template**: `src/syndex/AcademicBundle/Resources/views/Research/show_elastic.html.twig`

What needs to be added:
1. **Rating stars** — copy the rating UI from `show.html.twig` (lines ~1073-1078), 5 clickable stars with `.rate` class
2. **Favourite button** — copy the favourite button from `show.html.twig` (lines ~701-712), behind `{% if app.user %}` check
3. **JavaScript** — include `show-research.js` (or extract the rate + favourite AJAX handlers into the elastic template)
4. **Controller data** — `showAction` needs to pass `rate`, `raters`, `added` (isFavourite) variables to `show_elastic.html.twig` (currently only passed to `show.html.twig`)
5. **Gamification** — already wired in `rateResearchAction` and `addToFavouriteAction`, no backend changes needed

**Key concern**: ES-only papers don't have a MySQL `Research` entity, so the favourite system (which likely stores a FK to the research table) may need adaptation to work with ES document IDs instead. Rating may have the same FK issue. This needs investigation before implementation.

---

## Decisions Made

- **Points are non-monetary**: No credit, no real-world value. Purely motivational.
- **Daily caps prevent gaming**: Users can't farm points by clicking papers all day.
- **Append-only activity log**: `UserActivity` is write-heavy; points computed via SUM queries.
- **No levels/XP system**: Kept simple — points + badges + streaks. No progression tiers.
- **Graceful failure**: All `awardPoints()` calls are wrapped in try/catch — gamification errors never break the main user flow.
- **Deduplication**: Reference-based activities (read_paper, rate_paper, etc.) are deduplicated per slug per day.

---

## Phase 2 — Future Enhancements

### 2A. Weekly Digest Integration
- Include user's weekly point summary + new badges in the existing weekly digest email
- "You earned 47 points this week — you're in the top 15%!"
- Streak reminder: "You're on a 12-day streak — don't break it!"
- Low effort, high retention impact

### 2B. Achievement Milestones with Rewards
- Unlock real platform perks at point thresholds:
  - **500 points** → Custom profile banner/frame
  - **1,000 points** → "Verified Contributor" badge shown on all their Q&A answers
  - **2,500 points** → 50 bonus playground credits (one-time)
  - **5,000 points** → Early access to new AI features
- Creates tangible motivation beyond vanity points

### 2C. Research Challenges / Quests
- Time-limited challenges that appear on a user's dashboard:
  - "Read 5 papers in your field this week" → 25 bonus points
  - "Ask your first question" → 15 bonus points
  - "Rate 3 papers today" → 10 bonus points
- Rotating weekly/daily challenges keep the experience fresh
- Entity: `UserChallenge` (challenge_id, user_id, progress, target, expires_at, completed_at)
- Great for onboarding new users — guided first-week quests

### 2D. Collaborative Challenges
- Community-wide goals: "This community needs to rate 100 papers this month"
- Progress bar visible to all community members
- Reward: Special community badge for all participants
- Builds collective engagement and community identity

### 2E. Expertise Levels by Field
- Track points per academic field/tag (not just global)
- "Level 3 in Machine Learning" / "Level 5 in Arabic Linguistics"
- Badge: "Top Contributor in [Field]" — monthly, auto-awarded
- Visible on profile as expertise tags
- Drives niche engagement — users become known for their specialties

### 2F. Social Features — Follow Researchers
- Users follow other researchers (already planned in `futures/06-follow-researchers.md`)
- Gamify it: "Influencer" badge at 10/50/100 followers
- "Rising Star" badge — most points gained in a single week (new user)
- Activity feed: "Dr. Ahmed earned the Scholar badge"
- Creates social proof and peer motivation

### 2G. Streak Shields & Freeze
- Allow users to "freeze" their streak for 1 day (costs 50 points)
- Or earn "Streak Shields" as rare badge rewards — protects streak during vacation/illness
- Addresses the main frustration with streaks (one missed day resets everything)
- Maximum 2 freezes per month to prevent abuse

### 2H. Seasonal Leaderboards & Events
- Quarterly competitions: "Spring Research Sprint" — double points for 2 weeks
- Semester-aligned: "End of Semester Challenge" — bonus points for paper uploads
- Ramadan special: reading challenges aligned with cultural context
- Winners get permanent seasonal badge (e.g., "Spring 2026 Champion")
- Creates urgency and event-driven engagement

### 2I. Paper Quality Scores (Peer Rewards)
- Users can award "Helpful" points to other users' Q&A answers
- "Best Answer" upvote gives the answerer 10 bonus points
- "This paper summary helped me" → reward the reviewer
- Community-driven quality curation that rewards good contributors
- Entity: `UserReward` (from_user, to_user, type, points, reference_id)

### 2J. Onboarding Gamification
- First-time user sees a "Getting Started" checklist:
  - [ ] Complete your profile (10 pts)
  - [ ] Read your first paper (5 pts)
  - [ ] Save a paper to favorites (5 pts)
  - [ ] Ask your first question (15 pts)
  - [ ] Upload a reference (10 pts)
- Progress bar: "3/5 complete — 25 more points to unlock!"
- Special "Welcome Badge" for completing all onboarding tasks
- Dramatically improves activation rate for new signups

### 2K. Research Reading Lists & Goals
- Users set personal goals: "Read 20 papers this month"
- Progress tracked automatically via existing `read_paper` activity
- Achievement badge: "Goal Setter" / "Goal Crusher"
- Shareable: "I've read 45 papers this semester on Shamra"
- Taps into self-improvement motivation

### 2L. Notification Triggers for Gamification
- Push/email when user is close to a badge: "You've read 8/10 papers — 2 more for the Reader badge!"
- Streak danger alert: "You haven't visited today — your 15-day streak is at risk!"
- Milestone celebrations: "You just crossed 1,000 total points!"
- Uses existing Firebase push notification infrastructure

### 2M. Admin Analytics Dashboard Extension
- Extend existing admin dashboard (`/jim19ud83/playground/dashboard`) with gamification tab
- Metrics: total active users with points, badge distribution, streak distribution
- Engagement correlation: do users with streaks have higher retention?
- Top earners by period, most common badge, average points per user
- A/B test readiness: measure if gamification users engage more

### 2N. Points Decay / Activity Freshness
- Optional: Points from older activities gradually decay (e.g., 10% per quarter)
- Keeps leaderboard competitive — you can't just coast on past activity
- Encourages continuous engagement over one-time bursts
- Only apply to leaderboard ranking, not to total lifetime points or badges

---

## Priority Matrix

| Enhancement | Impact | Effort | Priority |
|-------------|--------|--------|----------|
| 2A. Weekly Digest Integration | High | Low | **P1 — Do next** |
| 2J. Onboarding Gamification | High | Medium | **P1 — Do next** |
| 2L. Notification Triggers | High | Medium | **P1 — Do next** |
| 2C. Research Challenges/Quests | High | Medium | P2 |
| 2E. Expertise Levels by Field | High | Medium | P2 |
| 2G. Streak Shields & Freeze | Medium | Low | P2 |
| 2B. Achievement Milestones | Medium | Low | P2 |
| 2K. Reading Lists & Goals | Medium | Medium | P3 |
| 2I. Peer Rewards | Medium | Medium | P3 |
| 2F. Social / Follow Researchers | High | High | P3 (depends on futures/06) |
| 2H. Seasonal Events | Medium | Medium | P3 |
| 2D. Collaborative Challenges | Medium | High | P4 |
| 2M. Admin Analytics | Low | Medium | P4 |
| 2N. Points Decay | Low | Low | P4 |

---

## Files Reference

| Category | Files |
|----------|-------|
| Entities | `src/Entity/UserActivity.php`, `UserBadge.php`, `UserStreak.php` |
| Repositories | `src/Repository/UserActivityRepository.php`, `UserBadgeRepository.php`, `UserStreakRepository.php` |
| Service | `src/Service/GamificationService.php` |
| Migration | `migrations/Version20260306100000.php` |
| Event Listener | `src/EventListener/GamificationLoginListener.php` |
| Controllers | `src/Controller/LeaderboardController.php`, `ProfileController.php` |
| Templates | `templates/leaderboard/index.html.twig`, `templates/Profile/show/personal_info.html.twig`, `templates/base.html.twig` (celebration modal) |
| Translations | `translations/Gamification.ar.yml`, `translations/Gamification.en.yml` |
| Wired Controllers | `ResearchController` (3 actions), `QuestionController` (2 actions), `UserReferenceController`, `CommunityMemberController`, `CommunityQuestionController` |

---

## Implementation Notes (DAU/MAU Stickiness — April 2026)

DAU/MAU is currently 0%. The gamification system is deployed but not driving daily returns yet. Priority actions for Phase 2:

### Immediate (highest impact on stickiness)

1. **Daily email digest** — Cron job sending 3-5 papers matching user interests/followed tags each morning. This is the external trigger that pulls users back daily. See `futures/01-automated-weekly-digest.md` — adapt to daily cadence.

2. **Streak visibility everywhere** — The streak counter exists but is only visible on profile pages. Add a small fire icon + streak count to the header/navbar so users see it on every page load. Losing a streak creates urgency to return.

3. **Push notifications for followed tags** — When new papers match followed tags, send browser push notifications. Real-time pull-back trigger. See `futures/10-push-notifications-revamp.md`.

### Short-term (1-2 weeks)

4. **Streak freeze / shield** (item 2G above) — Let users "freeze" their streak once per week (costs points). Reduces frustration from broken streaks and increases long-term retention.

5. **Homepage streak + points widget** — Show streak, today's points, and "X more points to next badge" on the homepage hero. Makes progress visible immediately on landing.

6. **Wire gamification to more actions** — Currently ES-only papers (majority of English papers) don't trigger `rate_paper` or `save_favorite`. Adding rating/favourite UI to `show_elastic.html.twig` would unlock gamification for ~97k more papers.

### Key insight
The Duolingo formula: **daily email/push (external trigger) + streak counter (internal motivation) + loss aversion (streak freeze)**. All three pieces need to work together. The streak system is built — we just need the daily trigger (email/push) and the visible streak in the navbar.
