Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 39 additions & 6 deletions app/Classes/VATUSAMoodle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down
7 changes: 3 additions & 4 deletions app/Console/Commands/SendAcademyRatingExamEmails.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading