auditionadmin-19 Implement Olympic scoring
This commit is contained in:
parent
772115099f
commit
5ac72c2301
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use function auditionSetting;
|
||||
|
||||
class AllowForOlympicScoring implements CalculateEntryScore
|
||||
{
|
||||
protected CalculateScoreSheetTotal $calculator;
|
||||
protected AuditionService $auditionService;
|
||||
protected EntryService $entryService;
|
||||
|
||||
public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService, EntryService $entryService)
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
$this->auditionService = $auditionService;
|
||||
$this->entryService = $entryService;
|
||||
}
|
||||
|
||||
public function calculate(string $mode, Entry $entry): array
|
||||
{
|
||||
|
||||
$cacheKey = 'entryScore-'.$entry->id.'-'.$mode;
|
||||
return Cache::remember($cacheKey, 10, function () use ($mode, $entry) {
|
||||
$this->basicValidation($mode, $entry);
|
||||
$this->areAllJudgesIn($entry);
|
||||
$this->areAllJudgesValid($entry);
|
||||
|
||||
return $this->getJudgeTotals($mode, $entry);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected function getJudgeTotals($mode, Entry $entry)
|
||||
{
|
||||
|
||||
$scores = [];
|
||||
foreach ($this->auditionService->getJudges($entry->audition) as $judge) {
|
||||
$scores[] = $this->calculator->__invoke($mode, $entry, $judge);
|
||||
}
|
||||
// sort the scores array by the total score
|
||||
usort($scores, function ($a, $b) {
|
||||
return $a[0] <=> $b[0];
|
||||
});
|
||||
|
||||
// we can only really do olympic scoring if there are at least 3 scores
|
||||
if (count($scores) >= 3 && auditionSetting('olympic_scoring')) {
|
||||
// remove the highest and lowest scores
|
||||
array_pop($scores);
|
||||
array_shift($scores);
|
||||
}
|
||||
$sums = [];
|
||||
// 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;
|
||||
}
|
||||
|
||||
protected function basicValidation($mode, $entry): void
|
||||
{
|
||||
if ($mode !== 'seating' && $mode !== 'advancement') {
|
||||
throw new TabulationException('Mode must be seating or advancement');
|
||||
}
|
||||
|
||||
if (! $this->entryService->entryExists($entry)) {
|
||||
throw new TabulationException('Invalid entry specified');
|
||||
}
|
||||
}
|
||||
|
||||
protected function areAllJudgesIn(Entry $entry): void
|
||||
{
|
||||
$assignedJudgeCount = $this->auditionService->getJudges($entry->audition)->count();
|
||||
if ($entry->scoreSheets->count() !== $assignedJudgeCount) {
|
||||
throw new TabulationException('Not all score sheets are in');
|
||||
}
|
||||
}
|
||||
|
||||
protected function areAllJudgesValid(Entry $entry): void
|
||||
{
|
||||
$validJudgeIds = $this->auditionService->getJudges($entry->audition)->sort()->pluck('id')->toArray();
|
||||
$existingJudgeIds = $entry->scoreSheets->sort()->pluck('user_id')->toArray();
|
||||
if ($validJudgeIds !== $existingJudgeIds) {
|
||||
throw new TabulationException('Score exists from a judge not assigned to this audition');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Actions\Tabulation\CalculateEntryScore;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
|
|
@ -109,7 +110,7 @@ class EntryController extends Controller
|
|||
return redirect('/admin/entries');
|
||||
}
|
||||
|
||||
public function edit(Entry $entry)
|
||||
public function edit(Entry $entry, CalculateEntryScore $calculator)
|
||||
{
|
||||
if ($entry->audition->hasFlag('seats_published')) {
|
||||
return to_route('admin.entries.index')->with('error',
|
||||
|
|
@ -123,8 +124,8 @@ class EntryController extends Controller
|
|||
|
||||
$students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get();
|
||||
$auditions = Audition::orderBy('score_order')->get();
|
||||
$scores = $entry->scoreSheets()->get();
|
||||
|
||||
$scores = $entry->scoreSheets()->with('audition', 'judge')->get();
|
||||
$scores->each(fn ($score) => $score->entry = $entry);
|
||||
// return view('admin.entries.edit', ['entry' => $entry, 'students' => $students, 'auditions' => $auditions]);
|
||||
return view('admin.entries.edit', compact('entry', 'students', 'auditions', 'scores'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class ScoreSheet extends Model
|
||||
{
|
||||
|
|
@ -50,4 +52,11 @@ class ScoreSheet extends Model
|
|||
|
||||
return $judges->contains('id', $this->judge->id);
|
||||
}
|
||||
|
||||
public function totalScore($mode)
|
||||
{
|
||||
$calculator = App::make(CalculateScoreSheetTotal::class);
|
||||
|
||||
return $calculator($mode, $this->entry, $this->judge);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\AllowForOlympicScoring;
|
||||
use App\Actions\Tabulation\CalculateEntryScore;
|
||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||
use App\Models\Audition;
|
||||
|
|
@ -45,7 +45,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(CalculateScoreSheetTotal::class, CalculateScoreSheetTotal::class);
|
||||
$this->app->singleton(CalculateEntryScore::class, AllJudgesCount::class);
|
||||
$this->app->singleton(CalculateEntryScore::class, AllowForOlympicScoring::class);
|
||||
$this->app->singleton(DrawService::class, DrawService::class);
|
||||
$this->app->singleton(AuditionService::class, AuditionService::class);
|
||||
$this->app->singleton(EntryService::class, EntryService::class);
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\CalculateEntryScore;
|
||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class CalculateEntryScoreProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(CalculateScoreSheetTotal::class, CalculateScoreSheetTotal::class);
|
||||
$this->app->singleton(CalculateEntryScore::class, AllJudgesCount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\CalculateEntryScoreProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
App\Providers\InvoiceDataServiceProvider::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@
|
|||
<x-form.toggle-checkbox name="judging_enabled"/><span>Enable score entry by judges</span>
|
||||
</div>
|
||||
<div class="col-span-6 flex space-x-3">
|
||||
<x-form.toggle-checkbox name="olympic_scoring"/><span>Olympic scoring</span>
|
||||
<x-form.toggle-checkbox
|
||||
checked="{{ auditionSetting('olympic_scoring') }}"
|
||||
name="olympic_scoring"/>
|
||||
<span>Olympic scoring</span>
|
||||
</div>
|
||||
</x-form.body-grid>
|
||||
</x-layout.page-section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@php use App\Models\Seat; @endphp
|
||||
<x-layout.app>
|
||||
<x-card.card class="mx-auto max-w-2xl">
|
||||
<x-card.card class="mx-auto max-w-3xl">
|
||||
<x-card.heading>
|
||||
Edit Entry #{{ $entry->id }}
|
||||
@if($entry->scoreSheets()->count() === 0)
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
</x-card.card>
|
||||
|
||||
|
||||
<x-card.card class="mx-auto max-w-2xl mt-6">
|
||||
<x-card.card class="mx-auto max-w-3xl mt-6">
|
||||
<x-card.heading>Scores</x-card.heading>
|
||||
<x-card.list.body>
|
||||
<div class="grid sm:grid-cols-3 space-3 m-3">
|
||||
|
|
@ -80,6 +80,17 @@
|
|||
<span class="text-right">{{$subscore['score']}}</span>
|
||||
</p>
|
||||
@endforeach
|
||||
<p class="grid grid-cols-2 border-b">
|
||||
<span class="font-semibold">{{ auditionSetting('auditionAbbreviation') }} Total</span>
|
||||
<span class="text-right font-semibold">{{ $score->totalScore('seating')[0] }}</span>
|
||||
</p>
|
||||
|
||||
@if( auditionSetting('advanceTo'))
|
||||
<p class="grid grid-cols-2 border-b">
|
||||
<span class="font-semibold">{{ auditionSetting('advanceTo') }} Total</span>
|
||||
<span class="text-right font-semibold">{{ $score->totalScore('advancement')[0] }}</span>
|
||||
</p>
|
||||
@endif
|
||||
@if(! $score->isValid())
|
||||
<form method="POST" action="{{ route('scores.destroy',['score'=>$score->id]) }}">
|
||||
@csrf
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\AllowForOlympicScoring;
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
|
|
@ -14,14 +14,14 @@ uses(RefreshDatabase::class);
|
|||
|
||||
it('throws an exception if mode is not seating or advancement', function () {
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator = App::make(AllowForOlympicScoring::class);
|
||||
$calculator->calculate('WRONG', Entry::factory()->create());
|
||||
})->throws(TabulationException::class, 'Mode must be seating or advancement');
|
||||
|
||||
it('throws an exception if entry is not valid', function () {
|
||||
// Arrange
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator = App::make(AllowForOlympicScoring::class);
|
||||
// Act
|
||||
$calculator->calculate('seating', Entry::factory()->make());
|
||||
// Assert
|
||||
|
|
@ -42,7 +42,7 @@ it('throws an exception if entry is missing judge scores', function () {
|
|||
1005 => 90,
|
||||
];
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator = App::make(AllowForOlympicScoring::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
// Act
|
||||
$calculator->calculate('seating', $entry);
|
||||
|
|
@ -66,7 +66,7 @@ it('throws an exception if a score exists from an invalid judge', function () {
|
|||
1005 => 90,
|
||||
];
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator = App::make(AllowForOlympicScoring::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
$scoreSheetToSpoof = enterScore($judge2, $entry, $scores);
|
||||
$scoreSheetToSpoof->update(['user_id' => $judge3->id]);
|
||||
|
|
@ -98,7 +98,7 @@ it('correctly calculates scores for seating', function () {
|
|||
1005 => 95,
|
||||
];
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator = App::make(AllowForOlympicScoring::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
enterScore($judge2, $entry, $scores2);
|
||||
// Act
|
||||
|
|
@ -130,7 +130,7 @@ it('correctly calculates scores for advancement', function () {
|
|||
1004 => 85,
|
||||
1005 => 95,
|
||||
];
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator = App::make(AllowForOlympicScoring::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
enterScore($judge2, $entry, $scores2);
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\AllowForOlympicScoring;
|
||||
use App\Actions\Tabulation\RankAuditionEntries;
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Audition;
|
||||
|
|
|
|||
Loading…
Reference in New Issue