diff --git a/CLAUDE.md b/CLAUDE.md index 2445346..71d154b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,6 +36,15 @@ Two paths (see `README.md` for full detail): `.env.example` is the authoritative, sectioned list of env vars (DB connections, JWT, VATSIM SSO/OAuth, AWS, Moodle, Swagger). +**`compose.dev.yml`'s Redis does not work out of the box.** `config/database.php`'s +`redis.default` connection hardcodes `'scheme' => 'tls'` (for the real, TLS-only managed +Redis used in staging/prod), but the compose file's `redis:7-alpine` service is plain, +non-TLS Redis — so anything that touches the default cache store (e.g. +`CloudflareServiceProvider::boot()`, which runs on every request/artisan invocation) fails +with a Predis TLS handshake error. Workaround for local runs: override +`CACHE_DRIVER=array` (and `SESSION_DRIVER=array` if needed) rather than editing +`config/database.php`. + ### Testing & CI - Tests are **smoke tests only** right now (in-memory SQLite, no external services). Real diff --git a/app/Classes/VATUSAMoodle.php b/app/Classes/VATUSAMoodle.php index 9882316..7989872 100644 --- a/app/Classes/VATUSAMoodle.php +++ b/app/Classes/VATUSAMoodle.php @@ -62,6 +62,9 @@ class VATUSAMoodle extends MoodleRest /** @var array|null Memoized cohort idnumber => id map for the lifetime of this instance */ private $cohortIdMapCache = null; + /** @var array Memoized quiz grade/sumgrades rows, keyed by quiz id */ + private $quizMetaCache = []; + /** @var int Seconds to bound each Moodle HTTP request to */ private const HTTP_TIMEOUT_SECONDS = 30; @@ -961,9 +964,33 @@ function unenrolUser( ["enrolments" => [0 => ["roleid" => $rid, "userid" => $uid, "courseid" => $cid]]]); } + /** + * Get quiz grade/sumgrades, memoized per quiz id for the lifetime of this instance. + * + * @param int $quizId The Quiz ID + * + * @return object|null + */ + public + function getQuizMeta( + int $quizId + ) { + if (!array_key_exists($quizId, $this->quizMetaCache)) { + $this->quizMetaCache[$quizId] = DB::connection('moodle')->table('quiz') + ->where('id', $quizId) + ->first(['grade', 'sumgrades']); + } + + return $this->quizMetaCache[$quizId]; + } + /** * Get quiz attempts * + * Grades are computed from the Moodle database (quiz.grade/sumgrades and + * quiz_attempts.sumgrades) rather than via the mod_quiz_get_attempt_review web service, + * which returns the entire rendered attempt review (~2 MB) to read a single number. + * * @param int $quizid The Quiz ID * @param int|null $cid The user's CID * @param int|null $uid @@ -985,14 +1012,20 @@ function getQuizAttempts( $attempts = $this->request("mod_quiz_get_user_attempts", ["quizid" => $quizid, "userid" => $userid])['attempts'] ?? []; - for ($i = 0; $i < count($attempts); $i++) { - $review = $this->request("mod_quiz_get_attempt_review", - ["attemptid" => $attempts[$i]['id']]) ?? []; - if (!empty($review)) { - $attempts[$i]['grade'] = round(floatval($review['grade'])); - } else { + $quiz = $this->getQuizMeta($quizid); + if (!$quiz || !$quiz->sumgrades) { + return []; + } + + $sumgrades = DB::connection('moodle')->table('quiz_attempts') + ->whereIn('id', array_column($attempts, 'id')) + ->pluck('sumgrades', 'id'); + + foreach ($attempts as $i => $attempt) { + if (!isset($sumgrades[$attempt['id']])) { return []; } + $attempts[$i]['grade'] = round($sumgrades[$attempt['id']] / $quiz->sumgrades * $quiz->grade); } return $attempts; diff --git a/app/Console/Commands/SendAcademyRatingExamEmails.php b/app/Console/Commands/SendAcademyRatingExamEmails.php index 8264d28..6395c3a 100644 --- a/app/Console/Commands/SendAcademyRatingExamEmails.php +++ b/app/Console/Commands/SendAcademyRatingExamEmails.php @@ -144,12 +144,11 @@ public function handle() $passingGrade = config('exams.BASIC.passingPercent'); $testName = "Basic ATC/S1 Exam"; - $review = $this->moodle->request("mod_quiz_get_attempt_review", - ["attemptid" => $attemptId]) ?? []; - if (empty($review)) { + $quiz = $this->moodle->getQuizMeta(config('exams.BASIC.id')); + if (!$quiz || !$quiz->sumgrades || !isset($attempt->sumgrades)) { continue; } - $grade = round(floatval($review['grade'])); + $grade = round($attempt->sumgrades / $quiz->sumgrades * $quiz->grade); $passed = $grade >= $passingGrade; $result = compact('testName', 'studentName', 'attemptNum', 'grade',