Skip to main content
Training / chapter / lesson completion + quiz/exam progress + score leaderboard
This file documents UserProgressAnalyticsController — a read-only analytics layer that derives a student’s learning progress and a gamification score from data that already exists in the system.
It does NOT modify the existing quiz/exam progress-tracking endpoints. Those (start_student_quiz, finish_student_quiz, save_student_quiz_result, show_quiz_student_answers, reset_student_attempts) remain the single writer of attempts/answers and are documented in Quizzes & Exams progress tracking. This controller only reads their tables.

Table of Contents

  1. Overview
  2. Base URL and Headers
  3. How progress is computed (sources of truth)
  4. How the gamification score is computed
  5. Authorization & company scoping
  6. Endpoints
    • 6.1 Training Progress (detailed, one training)
    • 6.2 Trainings Progress (summary list)
    • 6.3 Quiz/Exam Progress (per-question breakdown)
    • 6.4 Quizzes/Exams Progress (summary list)
    • 6.5 Gamification Score (recompute + rank)
    • 6.6 Leaderboard (top-N)
    • 6.7 Recompute Scores (bulk, staff)
  7. Per-question correctness model
  8. Configuration
  9. History Tracking Actions
  10. Related Files
  11. Future Suggestions

1. Overview

The system already records two facts elsewhere:
  • Lesson completion — a row in the student_lesson pivot (written by LessonsController@toggle_lesson_validation).
  • Quiz/exam attempts & scores — rows in quiz_student_attempts / quiz_student_answers (written by the progress-tracking controller).
This controller joins those, plus the content hierarchy (trainings → chapters → lessons and quizzes), to produce:
  • Per-training progress — which lessons are completed per chapter, how many quizzes are passed, and overall progress %.
  • Per-quiz/exam progress — best score, pass state, and a per-question right/wrong breakdown for any attempt.
  • A gamification score per user (users.gamification_score) and a top-N leaderboard for the company.
Everything is derived and idempotent — recomputing never double-counts.

2. Base URL and Headers

All endpoints live under the progress prefix and the tenant-scoped middleware stack (auth:sanctum, check_if_user_is_blocked_middleware, company_policy_middleware).

3. How progress is computed (sources of truth)

⚠️ passed_at is NOT used as the pass signal. In the existing tracking controller passed_at is set unconditionally whenever a result is saved, so it does not by itself mean “passed”. The reliable signal is score_percentage >= passing_percentage, which is what this controller uses everywhere.
Training progress percentages (each lesson and each quiz counts as one “item”):
A denominator of 0 yields 0.0 (never divide-by-zero).

4. How the gamification score is computed

  • Idempotent / no double-counting — each completed lesson and each distinct passed quiz/exam is counted exactly once, no matter how many attempts or re-toggles happened.
  • Stored denormalized in users.gamification_score (an unsignedInteger, indexed) purely for fast leaderboard reads. It is rebuilt from the sources of truth by GamificationService, never hand-edited.
  • It is separate from loyalty_points (which is monetary / convertible to coupons). Gamification score is a learning/achievement metric only.
The score is refreshed:
  • On demand for one user via GET /api/progress/score (recomputes that user before returning).
  • In bulk for a whole company via POST /api/progress/recompute-scores (run before showing a fresh leaderboard).

5. Authorization & company scoping

  • Company scoping is manual. Every endpoint requires company_id; CompanyPolicyMiddleware guarantees it matches the caller’s company (super-admin bypasses).
  • Who can view a student’s progress:
    • A student may view only their own progress (student_id defaults to the caller).
    • Staff (super-admin, admin, content-manager, instructor) may view any student in their company by passing student_id.
    • Otherwise → 403.
  • Recompute (bulk) is staff-only → 403 for students.

6. Endpoints


6.1 Training Progress (detailed)

Query params Response 200

6.2 Trainings Progress (summary list)

Query params Response 200 — standard paginator shape:

6.3 Quiz/Exam Progress (per-question breakdown)

Works for both quizzes (training/chapter-linked) and exams (standalone) — they are the same Quiz model. Query params Response 200
reviewed_attempt is null if the student has no attempts on this quiz. passed (in summary/attempts) is derived from score_percentage >= passing_percentage.

6.4 Quizzes/Exams Progress (summary list)

Lists every quiz/exam the student has attempted, within the company. Query params Response 200 — paginator with rows:

6.5 Gamification Score (recompute + rank)

Recomputes the target student’s score from the sources of truth, persists it, and returns the breakdown + the student’s company rank. Query params: company_id (✅), student_id (❌, staff only for others). Response 200
rank is dense (1 = highest score in the company).

6.6 Leaderboard (top-N)

Reads the stored gamification_score (fast). To guarantee freshness, call recompute-scores first. Query params Response 200
me always reflects the caller’s standing, even if they are outside the top-N. Both the leaderboard rows and me.rank are scoped to the resolved role_id, so a staff caller’s me.rank is relative to the same role being listed (student by default).

6.7 Recompute Scores (bulk, staff)

Rebuilds gamification_score for every user in the company. Staff only (403 otherwise). Logged to history. Body: company_id (✅). Response 200

7. Per-question correctness model

The breakdown in §6.3 mirrors exactly how finish_student_quiz stores answers in quiz_student_answers: A question with no submitted answer is counted as incorrect.

8. Configuration

config/custom_config.php:
Override per environment via .env. Changing point values does not retro-edit stored scores — run recompute-scores afterwards to apply new weights. Migration: 2026_06_08_000000_add_gamification_score_to_users_table.php adds the indexed users.gamification_score column (default 0). Run php artisan migrate.

9. History Tracking Actions

Read endpoints do not write history. Per-user score recompute is a lightweight idempotent write and is intentionally not logged.

11. Future Suggestions

These are deliberately not implemented yet — captured here for the planned “gamification award system”:
  1. Real-time score updates. Today the score is recomputed on demand (/score) or in bulk (/recompute-scores). To make the leaderboard live without a bulk job, add a small recompute hook in the two places completion happens:
    • after LessonsController@toggle_lesson_validation attaches/detaches a lesson, call GamificationService::recomputeForUser($student);
    • after save_student_quiz_result persists a passing score, do the same. These are additive (no behavioural change to the existing tracking) but were left out to honour the “do not touch the quiz/exam tracking” constraint. A model observer on QuizStudentAttempt is an alternative that keeps the controller untouched.
  2. A score-event ledger (gamification_score_events: user_id, source_type, source_id, points, awarded_at) for a full audit trail and “how did I earn this” UI, while keeping users.gamification_score as the denormalized cache. Enables streaks, daily caps, and reversals.
  3. Badges / achievements (badges, badge_user pivot) — e.g. “First Course Completed”, “Perfect Exam”, “7-day streak”. The current score is the foundation; badges become rule evaluations over the same sources of truth.
  4. Streaks — consecutive-day activity from student_lesson.created_at and attempt timestamps; bonus points for maintaining streaks.
  5. Cross-company / global leaderboard for super-admins (a scope=global option), and time-windowed leaderboards (weekly/monthly) using the score-event ledger.
  6. Configurable weights per company — move point values into loyalty_points_settings-style per-company rows instead of global config, so each tenant tunes its own gamification.
  7. Caching the leaderboard (e.g. 1–5 min cache) for high-traffic tenants, invalidated on recompute-scores.
  8. Completion certificates trigger — when overall_progress_percentage hits 100, auto-suggest issuing the training certificate (ties into the existing certification module).