Seating page lists entries in score order

This commit is contained in:
Matt Young 2024-07-10 23:22:37 -05:00
parent 6939a09eef
commit 6f0a4ac9bc
12 changed files with 246 additions and 52 deletions

View File

@ -21,6 +21,7 @@ class AllJudgesCount implements CalculateEntryScore
$this->basicValidation($mode, $entry);
$this->areAllJudgesIn($entry);
$this->areAllJudgesValid($entry);
return $this->getJudgeTotals($mode, $entry);
}
@ -30,8 +31,14 @@ class AllJudgesCount implements CalculateEntryScore
foreach ($entry->audition->judges as $judge) {
$scores[] = $this->calculator->__invoke($mode, $entry, $judge);
}
for ($i = 0; $i < count($scores[0]); $i++) {
$sums[] = $scores[0][$i] + $scores[1][$i];
// Sum each subscore from the judges
foreach ($scores as $score) {
$index = 0;
foreach ($score as $value) {
$sums[$index] = $sums[$index] ?? 0;
$sums[$index] += $value;
$index++;
}
}
return $sums;

View File

@ -6,5 +6,6 @@ use App\Models\Entry;
interface CalculateEntryScore
{
public function calculate(string $mode, Entry $entry): array;
}

View File

@ -0,0 +1,71 @@
<?php
/** @noinspection PhpUnhandledExceptionInspection */
namespace App\Actions\Tabulation;
use App\Exceptions\TabulationException;
use App\Models\Audition;
use Illuminate\Database\Eloquent\Collection;
class RankAuditionEntries
{
protected CalculateEntryScore $calculator;
public function __construct(CalculateEntryScore $calculator)
{
$this->calculator = $calculator;
}
public function booo()
{
return 'blah';
}
public function rank(string $mode, Audition $audition): Collection
{
$this->basicValidation($mode, $audition);
$entries = match ($mode) {
'seating' => $audition->entries()->forSeating()->get(),
'advancement' => $audition->entries()->forAdvancement()->get(),
};
foreach ($entries as $entry) {
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 ($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;
foreach ($entries as $entry) {
$entry->rank = $rank;
$rank++;
}
return $entries;
}
protected function basicValidation($mode, Audition $audition): void
{
if ($mode !== 'seating' && $mode !== 'advancement') {
throw new TabulationException('Mode must be seating or advancement');
}
if (! $audition->exists()) {
throw new TabulationException('Invalid audition provided');
}
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Tabulation;
use App\Actions\Tabulation\CalculateEntryScore;
use App\Actions\Tabulation\RankAuditionEntries;
use App\Exceptions\TabulationException;
use App\Http\Controllers\Controller;
use App\Models\Audition;
@ -12,30 +13,31 @@ use Illuminate\Http\Request;
class SeatAuditionController extends Controller
{
protected CalculateEntryScore $calc;
protected RankAuditionEntries $ranker;
public function __construct(CalculateEntryScore $calc)
public function __construct(CalculateEntryScore $calc, RankAuditionEntries $ranker)
{
$this->calc = $calc;
$this->ranker = $ranker;
}
public function __invoke(Request $request, Audition $audition)
{
$entryData = [];
$entries = Entry::forSeating()->with('student.school')->where('audition_id', $audition->id)->get();
#$entries = Entry::forSeating()->with('student.school')->where('audition_id', $audition->id)->get();
$entries = $this->ranker->rank('seating', $audition);
$entries->load('student.school');
foreach ($entries as $entry) {
try {
$totalScore = $this->calc->calculate('seating', $entry);
} catch (TabulationException $ex) {
$totalScore[0] = $ex->getMessage();
}
$totalScoreColumn = $entry->score_totals[0] >= 0 ?
$entry->score_totals[0] : $entry->score_message;
$entryData[] = [
'rank' => 'not implemented',
'rank' => $entry->rank,
'id' => $entry->id,
'studentName' => $entry->student->full_name(),
'schoolName' => $entry->student->school->name,
'drawNumber' => $entry->draw_number,
'totalScore' => $totalScore[0],
'fullyScored' => is_numeric($totalScore[0]),
'totalScore' => $totalScoreColumn,
'fullyScored' => $entry->score_totals[0] >= 0,
];
}

View File

@ -3,45 +3,20 @@
namespace App\Http\Controllers;
use App\Actions\Tabulation\CalculateEntryScore;
use App\Actions\Tabulation\CalculateScoreSheetTotal;
use App\Exceptions\TabulationException;
use App\Models\Entry;
use App\Models\User;
use App\Actions\Tabulation\RankAuditionEntries;
class TestController extends Controller
{
protected CalculateEntryScore $bigCalc;
public function __construct(CalculateEntryScore $bigCalc)
protected RankAuditionEntries $rankomatic;
public function __construct(RankAuditionEntries $rankomatic)
{
$this->bigCalc = $bigCalc;
$this->rankomatic = $rankomatic;
}
public function flashTest()
{
$entries = Entry::forSeating()->with('student')->where('audition_id', 17)->get();
$rows = [];
foreach ($entries as $entry) {
try {
$totalScore = $this->bigCalc->calculate('seating', $entry)[0];
} catch (TabulationException $ex){
$totalScore = '--';
}
$rows[] = [
'name' => $entry->student->full_name(),
'totalScore' => $totalScore,
];
}
$scoreCalc = new CalculateScoreSheetTotal;
$bam = $scoreCalc('seating', Entry::find(916), User::find(65))[0];
// try {
// $test = $this->bigCalc->calculate('seating', Entry::find(1061))[0];
// } catch (TabulationException $ex) {
// dd($ex);
// }
return view('test', compact('rows', 'bam'));
dd($this->rankomatic->booo());
return view('test');
}
}

View File

@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Staudenmeir\BelongsToThrough;
use App\Models\ScoreSheet;
class Entry extends Model
@ -38,6 +39,7 @@ class Entry extends Model
return $this->belongsTo(Audition::class);
}
public function school(): HasOneThrough
{
return $this->hasOneThrough(

View File

@ -21,4 +21,9 @@ class Event extends Model
return $this->hasMany(Ensemble::class)
->orderBy('rank');
}
public function entries()
{
return $this->hasManyThrough(Entry::class, Audition::class);
}
}

View File

@ -2,6 +2,9 @@
namespace App\Providers;
use App\Actions\Tabulation\AllJudgesCount;
use App\Actions\Tabulation\CalculateEntryScore;
use App\Actions\Tabulation\CalculateScoreSheetTotal;
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Room;
@ -40,9 +43,9 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
// $this->app->singleton(DrawService::class, function () {
// return new DrawService();
// });
$this->app->singleton(DrawService::class, function () {
return new DrawService();
});
//
// $this->app->singleton(AuditionService::class, function () {
// return new AuditionService();

View File

@ -12,6 +12,7 @@
"laravel/pail": "^1.1",
"laravel/tinker": "^2.9",
"predis/predis": "^2.2",
"staudenmeir/belongs-to-through": "^2.5",
"symfony/http-client": "^7.1",
"symfony/mailgun-mailer": "^7.1"
},

67
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7aab57ef52f0152526434decd76ef1e1",
"content-hash": "cd8959ab9db27e12c6fce8cf87c52d90",
"packages": [
{
"name": "bacon/bacon-qr-code",
@ -3601,6 +3601,71 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "staudenmeir/belongs-to-through",
"version": "v2.16",
"source": {
"type": "git",
"url": "https://github.com/staudenmeir/belongs-to-through.git",
"reference": "79667db6660fa0065b24415bab29a5f85a0128c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/79667db6660fa0065b24415bab29a5f85a0128c7",
"reference": "79667db6660fa0065b24415bab29a5f85a0128c7",
"shasum": ""
},
"require": {
"illuminate/database": "^11.0",
"php": "^8.2"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.0",
"orchestra/testbench": "^9.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Staudenmeir\\BelongsToThrough\\IdeHelperServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Znck\\Eloquent\\": "src/",
"Staudenmeir\\BelongsToThrough\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rahul Kadyan",
"email": "hi@znck.me"
},
{
"name": "Jonas Staudenmeir",
"email": "mail@jonas-staudenmeir.de"
}
],
"description": "Laravel Eloquent BelongsToThrough relationships",
"support": {
"issues": "https://github.com/staudenmeir/belongs-to-through/issues",
"source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16"
},
"funding": [
{
"url": "https://paypal.me/JonasStaudenmeir",
"type": "custom"
}
],
"time": "2024-03-09T09:53:11+00:00"
},
{
"name": "symfony/clock",
"version": "v7.0.7",

View File

@ -1,4 +1,4 @@
@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\User; @endphp
@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\Event;use App\Models\User; @endphp
@php @endphp
@inject('scoreservice','App\Services\ScoreService');
@inject('auditionService','App\Services\AuditionService');
@ -7,8 +7,9 @@
@inject('drawService', 'App\Services\DrawService')
<x-layout.app>
<x-slot:page_title>Test Page - {{ $bam ?? '' }}</x-slot:page_title>
@foreach($rows as $row)
{{ $row['name'] }} - {{ $row['totalScore'] }}<hr>
@endforeach
@foreach(Event::first()->entries->groupBy('student_id') as $student)
Name: {{ $student[0]->student->full_name() }} <br>
Entry Count: {{ $student->count() }}<hr>
@endforeach
</x-layout.app>

View File

@ -0,0 +1,61 @@
<?php
use App\Actions\Tabulation\AllJudgesCount;
use App\Actions\Tabulation\RankAuditionEntries;
use App\Exceptions\TabulationException;
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Room;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('throws an exception if an invalid mode is specified', function () {
$ranker = new RankAuditionEntries(new AllJudgesCount());
$ranker->rank('wrong', Audition::factory()->create());
})->throws(TabulationException::class, 'Mode must be seating or advancement');
it('throws an exception if an invalid audition is provided', function () {
// Arrange
$ranker = new RankAuditionEntries(new AllJudgesCount());
// Act & Assert
$ranker->rank('seating', Audition::factory()->make());
})->throws(TabulationException::class, 'Invalid audition provided');
it('includes all entries of the given mode in the return', function () {
$audition = Audition::factory()->create();
$entries = Entry::factory()->seatingOnly()->count(10)->create(['audition_id' => $audition->id]);
$otherEntries = Entry::factory()->advanceOnly()->count(10)->create(['audition_id' => $audition->id]);
$ranker = new RankAuditionEntries(new AllJudgesCount());
// Act
$return = $ranker->rank('seating', $audition);
// Assert
foreach ($entries as $entry) {
expect($return->pluck('id')->toArray())->toContain($entry->id);
}
foreach ($otherEntries as $entry) {
expect($return->pluck('id')->toArray())->not()->toContain($entry->id);
}
});
it('places entries in the proper order', function () {
// Arrange
loadSampleAudition();
$judge = User::factory()->create();
Room::find(1000)->addJudge($judge);
$entries = Entry::factory()->count(5)->create(['audition_id' => 1000]);
$scoreArray1 = [1001 => 90, 1002 => 90, 1003 => 90, 1004 => 90, 1005 => 90];
$scoreArray2 = [1001 => 60, 1002 => 60, 1003 => 60, 1004 => 60, 1005 => 60];
$scoreArray3 = [1001 => 80, 1002 => 80, 1003 => 80, 1004 => 80, 1005 => 80];
$scoreArray4 = [1001 => 100, 1002 => 100, 1003 => 100, 1004 => 100, 1005 => 100];
$scoreArray5 = [1001 => 70, 1002 => 70, 1003 => 70, 1004 => 70, 1005 => 70];
enterScore($judge, $entries[0], $scoreArray1);
enterScore($judge, $entries[1], $scoreArray2);
enterScore($judge, $entries[2], $scoreArray3);
enterScore($judge, $entries[3], $scoreArray4);
enterScore($judge, $entries[4], $scoreArray5);
$ranker = new RankAuditionEntries(new AllJudgesCount());
$expectedOrder = [4, 1, 3, 5, 2];
// Act
$return = $ranker->rank('seating', Audition::find(1000));
// Assert
expect($return->pluck('id')->toArray())->toBe($expectedOrder);
});