diff --git a/app/Actions/Tabulation/TotalEntryScores.php b/app/Actions/Tabulation/TotalEntryScores.php index d7c0a4b..e24497c 100644 --- a/app/Actions/Tabulation/TotalEntryScores.php +++ b/app/Actions/Tabulation/TotalEntryScores.php @@ -19,7 +19,6 @@ class TotalEntryScores public function __invoke(Entry $entry, bool $force_recalculation = false): void { - // TODO Verify accuracy of calculations, particularly for olympic scoring if ($force_recalculation) { EntryTotalScore::where('entry_id', $entry->id)->delete(); } @@ -34,17 +33,19 @@ class TotalEntryScores // deal with seating scores // TODO: Consider a rewrite to pull the scoreSheets from the entry model so they may be preloaded $scoreSheets = ScoreSheet::where('entry_id', $entry->id)->orderBy('seating_total', 'desc')->get(); + // bail out if there are not enough score sheets $assignedJudges = $entry->audition->judges()->count(); if ($scoreSheets->count() == 0 || $scoreSheets->count() < $assignedJudges) { return; } - if (auditionSetting('olympic_scoring' && $scoreSheets->count() > 2)) { + + if (auditionSetting('olympic_scoring') && $scoreSheets->count() > 2) { // under olympic scoring, drop the first and last element $scoreSheets->shift(); $scoreSheets->pop(); } - $newTotaledScore->seating_total = $scoreSheets->avg('seating_total'); + $newTotaledScore->seating_total = round($scoreSheets->avg('seating_total'), 6); $seatingSubscores = $requiredSubscores ->filter(fn ($subscore) => $subscore->for_seating == true) ->sortBy('tiebreak_order'); @@ -54,18 +55,18 @@ class TotalEntryScores foreach ($scoreSheets as $scoreSheet) { $runningTotal += $scoreSheet->subscores[$subscore->id]['score']; } - $total_seating_subscores[] = $runningTotal / $scoreSheets->count(); + $total_seating_subscores[] = round($runningTotal / $scoreSheets->count(), 4); } $newTotaledScore->seating_subscore_totals = $total_seating_subscores; // deal with advancement scores $scoreSheets = ScoreSheet::where('entry_id', $entry->id)->orderBy('advancement_total', 'desc')->get(); - if (auditionSetting('olympic_scoring' && $scoreSheets->count() > 2)) { + if (auditionSetting('olympic_scoring') && $scoreSheets->count() > 2) { // under olympic scoring, drop the first and last element $scoreSheets->shift(); $scoreSheets->pop(); } - $newTotaledScore->advancement_total = $scoreSheets->avg('advancement_total'); + $newTotaledScore->advancement_total = round($scoreSheets->avg('advancement_total'), 6); $advancement_subscores = $requiredSubscores ->filter(fn ($subscore) => $subscore->for_advance == true) ->sortBy('tiebreak_order'); @@ -75,7 +76,7 @@ class TotalEntryScores foreach ($scoreSheets as $scoreSheet) { $runningTotal += $scoreSheet->subscores[$subscore->id]['score']; } - $total_advancement_subscores[] = $runningTotal / $scoreSheets->count(); + $total_advancement_subscores[] = round($runningTotal / $scoreSheets->count(), 4); } $newTotaledScore->advancement_subscore_totals = $total_advancement_subscores; diff --git a/tests/Feature/app/Actions/Tabulation/TotalEntryScoresTest.php b/tests/Feature/app/Actions/Tabulation/TotalEntryScoresTest.php new file mode 100644 index 0000000..5ff9c9f --- /dev/null +++ b/tests/Feature/app/Actions/Tabulation/TotalEntryScoresTest.php @@ -0,0 +1,206 @@ +run(); + SubscoreDefinition::where('id', '<', 900)->delete(); + $this->audition = Audition::first(); + $this->judge1 = User::factory()->create(); + $this->judge2 = User::factory()->create(); + $this->judge3 = User::factory()->create(); + $this->judge4 = User::factory()->create(); + $this->judge5 = User::factory()->create(); + $this->audition->judges()->attach([ + $this->judge1->id, + $this->judge2->id, + $this->judge3->id, + $this->judge4->id, + $this->judge5->id, + ]); + $this->entry = Entry::factory()->create(['audition_id' => $this->audition->id]); + $this->judge1Subscores = [ + 1001 => 45, + 1002 => 87, + 1003 => 34, + 1004 => 86, + 1005 => 75, + ]; + $this->judge2Subscores = [ + 1001 => 75, + 1002 => 69, + 1003 => 56, + 1004 => 89, + 1005 => 45, + ]; + $this->judge3Subscores = [ + 1001 => 78, + 1002 => 56, + 1003 => 98, + 1004 => 34, + 1005 => 56, + ]; + $this->judge4Subscores = [ + 1001 => 67, + 1002 => 45, + 1003 => 98, + 1004 => 43, + 1005 => 89, + ]; + $this->judge5Subscores = [ + 1001 => 45, + 1002 => 97, + 1003 => 34, + 1004 => 97, + 1005 => 78, + ]; + $this->scribe = app(EnterScore::class); + $this->calculator = app(TotalEntryScores::class); +}); + +test('scores correctly for non-olympic scoring', function () { + Settings::set('olympic_scoring', false); + ($this->scribe)($this->judge1, $this->entry, $this->judge1Subscores); + ($this->scribe)($this->judge2, $this->entry, $this->judge2Subscores); + ($this->scribe)($this->judge3, $this->entry, $this->judge3Subscores); + ($this->scribe)($this->judge4, $this->entry, $this->judge4Subscores); + ($this->scribe)($this->judge5, $this->entry, $this->judge5Subscores); + $judges = [ + $this->judge1, + $this->judge2, + $this->judge3, + $this->judge4, + $this->judge5, + ]; + $expectedSeatingTotals = [ + 1 => 68.125, + 2 => 74, + 3 => 61, + 4 => 60.25, + 5 => 74.75, + ]; + $expectedAdvancementTotals = [ + 1 => 71.875, + 2 => 70.25, + 3 => 58.25, + 4 => 63, + 5 => 78.875, + ]; + foreach ($judges as $judge) { + $scoreSheet = ScoreSheet::where('entry_id', $this->entry->id)->where('user_id', $judge->id)->first(); + expect($scoreSheet->seating_total)->toBe($expectedSeatingTotals[$judge->id]) + ->and($scoreSheet->advancement_total)->toBe($expectedAdvancementTotals[$judge->id]); + } + ($this->calculator)($this->entry); + $totalScore = EntryTotalScore::where('entry_id', $this->entry->id)->first(); + expect($totalScore->seating_subscore_totals)->toBe([69.8, 70.8, 64, 62]) + ->and($totalScore->advancement_subscore_totals)->toBe([68.6, 69.8, 70.8, 64]) + ->and($totalScore->seating_total)->toBe(67.625) + ->and($totalScore->advancement_total)->toBe(68.45); +}); + +test('scores correctly for olympic scoring', function () { + Settings::set('olympic_scoring', true); + ($this->scribe)($this->judge1, $this->entry, $this->judge1Subscores); + ($this->scribe)($this->judge2, $this->entry, $this->judge2Subscores); + ($this->scribe)($this->judge3, $this->entry, $this->judge3Subscores); + ($this->scribe)($this->judge4, $this->entry, $this->judge4Subscores); + ($this->scribe)($this->judge5, $this->entry, $this->judge5Subscores); + $judges = [ + $this->judge1, + $this->judge2, + $this->judge3, + $this->judge4, + $this->judge5, + ]; + $expectedSeatingTotals = [ + 1 => 68.125, + 2 => 74, + 3 => 61, + 4 => 60.25, + 5 => 74.75, + ]; + $expectedAdvancementTotals = [ + 1 => 71.875, + 2 => 70.25, + 3 => 58.25, + 4 => 63, + 5 => 78.875, + ]; + foreach ($judges as $judge) { + $scoreSheet = ScoreSheet::where('entry_id', $this->entry->id)->where('user_id', $judge->id)->first(); + expect($scoreSheet->seating_total)->toBe($expectedSeatingTotals[$judge->id]) + ->and($scoreSheet->advancement_total)->toBe($expectedAdvancementTotals[$judge->id]); + } + ($this->calculator)($this->entry); + $totalScore = EntryTotalScore::where('entry_id', $this->entry->id)->first(); + expect($totalScore->seating_subscore_totals)->toBe([69.6667, 70.6667, 62.6667, 66]) + ->and($totalScore->advancement_subscore_totals)->toBe([69.6667, 72.6667, 67, 62.6667]) + ->and($totalScore->seating_total)->toBe(67.708333) + ->and($totalScore->advancement_total)->toBe(68.375); +}); + +test('it correctly brings in bonus scores', function () { + Settings::set('olympic_scoring', false); + ($this->scribe)($this->judge1, $this->entry, $this->judge1Subscores); + ($this->scribe)($this->judge2, $this->entry, $this->judge2Subscores); + ($this->scribe)($this->judge3, $this->entry, $this->judge3Subscores); + ($this->scribe)($this->judge4, $this->entry, $this->judge4Subscores); + ($this->scribe)($this->judge5, $this->entry, $this->judge5Subscores); + $judges = [ + $this->judge1, + $this->judge2, + $this->judge3, + $this->judge4, + $this->judge5, + ]; + $expectedSeatingTotals = [ + 1 => 68.125, + 2 => 74, + 3 => 61, + 4 => 60.25, + 5 => 74.75, + ]; + $expectedAdvancementTotals = [ + 1 => 71.875, + 2 => 70.25, + 3 => 58.25, + 4 => 63, + 5 => 78.875, + ]; + foreach ($judges as $judge) { + $scoreSheet = ScoreSheet::where('entry_id', $this->entry->id)->where('user_id', $judge->id)->first(); + expect($scoreSheet->seating_total)->toBe($expectedSeatingTotals[$judge->id]) + ->and($scoreSheet->advancement_total)->toBe($expectedAdvancementTotals[$judge->id]); + } + $bonusScore = BonusScore::create([ + 'entry_id' => $this->entry->id, + 'user_id' => $this->judge1, + 'originally_scored_entry' => $this->entry->id, + 'score' => 6, + ]); + ($this->calculator)($this->entry); + $totalScore = EntryTotalScore::where('entry_id', $this->entry->id)->first(); + + expect($totalScore->seating_subscore_totals)->toBe([69.8, 70.8, 64, 62]) + ->and($totalScore->advancement_subscore_totals)->toBe([68.6, 69.8, 70.8, 64]) + ->and($totalScore->seating_total)->toBe(67.625) + ->and($totalScore->advancement_total)->toBe(68.45) + ->and($totalScore->bonus_total)->toBe(6) + ->and($totalScore->seating_total_with_bonus)->toBe(73.625); +}); diff --git a/tests/Pest.php b/tests/Pest.php index a07cc90..54d4edf 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -13,7 +13,7 @@ use App\Models\User; use App\Settings; -use Illuminate\Foundation\Testing\TestCase; + use function Pest\Laravel\actingAs; use function Pest\Laravel\artisan; @@ -80,5 +80,5 @@ uses()->beforeEach(function () { Settings::set('payment_city', 'Washington'); Settings::set('payment_state', 'DC'); Settings::set('payment_zip', '20500'); + Settings::set('olympic_scoring', 1); })->in('Feature'); -