From 349da644b7cb5d90c08bc7451a6d2f72ce7eafa2 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 15 Jun 2025 15:44:05 -0500 Subject: [PATCH] Rewrite RankAuditionEntries action and use it in the new seat audition form controller. --- .../Tabulation/RankAuditionEntries.php | 151 ++++++++---------- .../Tabulation/SeatAuditionFormController.php | 21 +-- 2 files changed, 67 insertions(+), 105 deletions(-) diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index cd3586e..6a3e7d8 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -4,105 +4,82 @@ namespace App\Actions\Tabulation; -use App\Exceptions\TabulationException; +use App\Exceptions\AuditionAdminException; use App\Models\Audition; -use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\Cache; - -use function is_numeric; +use Illuminate\Support\Collection; class RankAuditionEntries { - protected CalculateEntryScore $calculator; - - public function __construct(CalculateEntryScore $calculator) - { - $this->calculator = $calculator; - } - - public function rank(string $mode, Audition $audition): Collection - { - $cacheKey = 'audition'.$audition->id.$mode; - - return Cache::remember($cacheKey, 300, function () use ($mode, $audition) { - return $this->calculateRank($mode, $audition); - }); - - } - /** - * For a given audition, return a collection of entries ranked by total score. Each entry will have a - * property rank that either is their rank or a flag reflecting no-show, declined, or failed-prelim status + * Get ranked entries for the provided audition for either seating or advancement. * - * @throws TabulationException + * If the rank_type is seating, the ranked entries are returned in descending order of seating total. + * If the rank_type is advancement, the ranked entries are returned in descending order of advancement total. + * + * The ranked entries are returned as a Collection of Entry objects. + * + * @param string $rank_type advancement|seating + * @return Collection|void + * + * @throws AuditionAdminException */ - public function calculateRank(string $mode, Audition $audition): Collection + public function __invoke(Audition $audition, string $rank_type) { - $this->basicValidation($mode, $audition); - $entries = match ($mode) { - 'seating' => $audition->entries()->forSeating()->with('scoreSheets')->withCount('bonusScores')->get(), - 'advancement' => $audition->entries()->forAdvancement()->with('scoreSheets')->get(), - }; - - foreach ($entries as $entry) { - $entry->setRelation('audition', $audition); - try { - $entry->score_totals = $this->calculator->calculate($mode, $entry); - } catch (TabulationException $ex) { - $entry->score_totals = [-1]; - $entry->score_message = $ex->getMessage(); - } - } - // Sort entries based on their total score, then by subscores in tiebreak order - $entries = $entries->sort(function ($a, $b) { - for ($i = 0; $i < count($a->score_totals); $i++) { - if (! array_key_exists($i, $a->score_totals)) { - return -1; - } - if (! array_key_exists($i, $b->score_totals)) { - return -1; - } - if ($a->score_totals[$i] > $b->score_totals[$i]) { - return -1; - } elseif ($a->score_totals[$i] < $b->score_totals[$i]) { - return 1; - } - } - - return 0; - }); - $rank = 1; - $rawRank = 1; - foreach ($entries as $entry) { - $entry->rank = $rank; - $entry->raw_rank = $rawRank; - // We don't really get a rank for seating if we have certain flags - if ($mode === 'seating') { - if ($entry->hasFlag('failed_prelim')) { - $entry->rank = 'Failed Prelim'; - } elseif ($entry->hasFlag('declined')) { - $entry->rank = 'Declined'; - } elseif ($entry->hasFlag('no_show')) { - $entry->rank = 'No Show'; - } - } - - if (is_numeric($entry->rank)) { - $rank++; - } - $rawRank++; + if ($rank_type !== 'seating' && $rank_type !== 'advancement') { + throw new AuditionAdminException('Invalid rank type: '.$rank_type.' (must be seating or advancement)'); + } + + $cache_duration = 15; + + if ($rank_type === 'seating') { + return cache()->remember('rank_seating_'.$audition->id, $cache_duration, function () use ($audition) { + return $this->get_seating_ranks($audition); + }); } - return $entries; } - protected function basicValidation($mode, Audition $audition): void + private function get_seating_ranks(Audition $audition): Collection { - if ($mode !== 'seating' && $mode !== 'advancement') { - throw new TabulationException('Mode must be seating or advancement'); - } - if (! $audition->exists()) { - throw new TabulationException('Invalid audition provided'); - } + return $audition->entries() + ->whereHas('totalScore') + ->with('totalScore') + ->with('student.school') + ->join('entry_total_scores', 'entries.id', '=', 'entry_total_scores.entry_id') + ->orderBy('entry_total_scores.seating_total', 'desc') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[0]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[1]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[2]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[3]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[4]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[5]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[6]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[7]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[8]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[9]"), -999999) DESC') + ->select('entries.*') + ->get(); + } + + private function get_advancement_ranks(Audition $audition): Collection + { + return $audition->entries() + ->whereHas('totalScore') + ->with('totalScore') + ->with('student.school') + ->join('entry_total_scores', 'entries.id', '=', 'entry_total_scores.entry_id') + ->orderBy('entry_total_scores.advancement_total', 'desc') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[0]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[1]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[2]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[3]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[4]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[5]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[6]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[7]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[8]"), -999999) DESC') + ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.advancement_subscore_totals, "$[9]"), -999999) DESC') + ->select('entries.*') + ->get(); } } diff --git a/app/Http/Controllers/Tabulation/SeatAuditionFormController.php b/app/Http/Controllers/Tabulation/SeatAuditionFormController.php index f0c0307..b6f0ccb 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionFormController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionFormController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Tabulation; use App\Actions\Tabulation\GetAuditionSeats; +use App\Actions\Tabulation\RankAuditionEntries; use App\Http\Controllers\Controller; use App\Models\Audition; use Illuminate\Http\Request; @@ -11,25 +12,9 @@ class SeatAuditionFormController extends Controller { public function __invoke(Request $request, Audition $audition) { + $ranker = app(RankAuditionEntries::class); // Get scored entries in order - $scored_entries = $audition->entries() - ->whereHas('totalScore') - ->with('totalScore') - ->with('student.school') - ->join('entry_total_scores', 'entries.id', '=', 'entry_total_scores.entry_id') - ->orderBy('entry_total_scores.seating_total', 'desc') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[0]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[1]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[2]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[3]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[4]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[5]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[6]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[7]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[8]"), -999999) DESC') - ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[9]"), -999999) DESC') - ->select('entries.*') - ->get(); + $scored_entries = $ranker($audition, 'seating'); // Get unscored entries sorted by draw number $unscored_entries = $audition->entries()