220 lines
7.0 KiB
PHP
220 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Entry;
|
|
use App\Models\ScoreSheet;
|
|
use App\Models\ScoringGuide;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
use function array_unshift;
|
|
|
|
class ScoreService
|
|
{
|
|
protected $auditionCache;
|
|
|
|
protected $entryCache;
|
|
|
|
/**
|
|
* Create a new class instance.
|
|
*/
|
|
public function __construct(AuditionService $auditionCache, EntryService $entryCache)
|
|
{
|
|
$this->auditionCache = $auditionCache;
|
|
$this->entryCache = $entryCache;
|
|
}
|
|
|
|
/**
|
|
* Cache all scoring guides
|
|
*/
|
|
public function getScoringGuides(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
$cacheKey = 'scoringGuides';
|
|
|
|
return Cache::remember($cacheKey, 3600, fn () => ScoringGuide::with('subscores')->withCount('subscores')->get());
|
|
}
|
|
|
|
/**
|
|
* Retrieve a single scoring guide from the cache
|
|
*/
|
|
public function getScoringGuide($id): ScoringGuide
|
|
{
|
|
return $this->getScoringGuides()->find($id);
|
|
}
|
|
|
|
/**
|
|
* Clear the scoring guide cache
|
|
*/
|
|
public function clearScoringGuideCache(): void
|
|
{
|
|
Cache::forget('scoringGuides');
|
|
}
|
|
|
|
/**
|
|
* Returns an array where each key is an entry id and the value is the number
|
|
* of score sheets assigned to that entry.
|
|
*
|
|
* @return Collection
|
|
*/
|
|
public function entryScoreSheetCounts()
|
|
{
|
|
$cacheKey = 'entryScoreSheetCounts';
|
|
|
|
return Cache::remember($cacheKey, 10, function () {
|
|
// For each Entry get the number of ScoreSheets associated with it
|
|
$scoreSheetCountsByEntry = ScoreSheet::select('entry_id', DB::raw('count(*) as count'))
|
|
->groupBy('entry_id')
|
|
->get()
|
|
->pluck('count', 'entry_id');
|
|
|
|
$entryScoreSheetCounts = [];
|
|
$entries = $this->entryCache->getAllEntries();
|
|
foreach ($entries as $entry) {
|
|
$entryScoreSheetCounts[$entry->id] = $scoreSheetCountsByEntry[$entry->id] ?? 0;
|
|
}
|
|
|
|
return $entryScoreSheetCounts;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Get final scores array for the requested entry. The first element is the total score. The following elements are sums
|
|
* of each subscore in tiebreaker order
|
|
*
|
|
* @return array
|
|
*/
|
|
public function entryTotalScores(Entry $entry)
|
|
{
|
|
$cacheKey = 'entry'.$entry->id.'totalScores';
|
|
|
|
return Cache::remember($cacheKey, 3600, function () use ($entry) {
|
|
return $this->calculateFinalScoreArray($entry->audition->scoring_guide_id, $entry->scoreSheets);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Calculate and cache scores for all entries for the provided audition ID
|
|
*
|
|
* @return void
|
|
*/
|
|
public function calculateScoresForAudition($auditionId, $mode= 'seating')
|
|
{
|
|
static $alreadyChecked = [];
|
|
// if $auditionId is in the array $alreadyChecked return
|
|
if (in_array($auditionId, $alreadyChecked)) {
|
|
return;
|
|
}
|
|
$alreadyChecked[] = $auditionId;
|
|
$audition = $this->auditionCache->getAudition($auditionId);
|
|
$scoringGuideId = $audition->scoring_guide_id;
|
|
$entries = $this->entryCache->getEntriesForAudition($auditionId, $mode);
|
|
$entries->load('scoreSheets'); // TODO Cache this somehow, it's expensive and repetitive on the seating page
|
|
|
|
foreach ($entries as $entry) {
|
|
$cacheKey = 'entry'.$entry->id.'totalScores';
|
|
if (Cache::has($cacheKey)) {
|
|
continue;
|
|
}
|
|
$thisTotalScore = $this->calculateFinalScoreArray($scoringGuideId, $entry->scoreSheets);
|
|
Cache::put($cacheKey, $thisTotalScore, 3600);
|
|
}
|
|
}
|
|
|
|
public function clearScoreSheetCountCache()
|
|
{
|
|
$cacheKey = 'entryScoreSheetCounts';
|
|
Cache::forget($cacheKey);
|
|
}
|
|
|
|
public function clearEntryTotalScoresCache($entryId)
|
|
{
|
|
$cacheKey = 'entry'.$entryId.'totalScores';
|
|
Cache::forget($cacheKey);
|
|
}
|
|
|
|
public function clearAllCachedTotalScores()
|
|
{
|
|
foreach ($this->entryCache->getAllEntries() as $entry) {
|
|
$cacheKey = 'entry'.$entry->id.'totalScores';
|
|
Cache::forget($cacheKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate final score using the provided scoring guide and score sheets. Returns an array of scores
|
|
* The first element is the total score. The following elements are the sum of each subscore
|
|
* in tiebreaker order.
|
|
*/
|
|
public function calculateFinalScoreArray($scoringGuideId, array|Collection $scoreSheets): array
|
|
{
|
|
|
|
$sg = $this->getScoringGuide($scoringGuideId);
|
|
|
|
// TODO cache the scoring guides with their subscores
|
|
$subscores = $sg->subscores->sortBy('tiebreak_order');
|
|
|
|
$ignoredSubscores = []; // This will be subscores not used for seating
|
|
|
|
// Init final scores array
|
|
$finalScoresArray = [];
|
|
foreach ($subscores as $subscore) {
|
|
if (! $subscore->for_seating) { // Ignore scores that are not for seating
|
|
$ignoredSubscores[] = $subscore->id;
|
|
|
|
continue;
|
|
}
|
|
$finalScoresArray[$subscore->id] = 0;
|
|
}
|
|
|
|
foreach ($scoreSheets as $sheet) {
|
|
foreach ($sheet->subscores as $ss) {
|
|
if (in_array($ss['subscore_id'], $ignoredSubscores)) { // Ignore scores that are not for seating
|
|
continue;
|
|
}
|
|
$finalScoresArray[$ss['subscore_id']] += $ss['score'];
|
|
}
|
|
}
|
|
|
|
// calculate weighted final score
|
|
$totalScore = 0;
|
|
$totalWeight = 0;
|
|
foreach ($subscores as $subscore) {
|
|
if (in_array($subscore->id, $ignoredSubscores)) { // Ignore scores that are not for seating
|
|
continue;
|
|
}
|
|
$totalScore += ($finalScoresArray[$subscore->id] * $subscore->weight);
|
|
$totalWeight += $subscore->weight;
|
|
}
|
|
$totalScore = ($totalScore / $totalWeight);
|
|
array_unshift($finalScoresArray, $totalScore);
|
|
|
|
return $finalScoresArray;
|
|
}
|
|
|
|
/**
|
|
* Validate that the judge on the score sheet is actually assigned to judge
|
|
* then entry
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function validateScoreSheet(ScoreSheet $sheet)
|
|
{
|
|
// TODO use this when calculating scores
|
|
$entry = $this->entryCache->getAllEntries()->find($sheet->entry_id);
|
|
$audition = $this->auditionCache->getAudition($entry->audition_id);
|
|
$validJudges = $audition->judges;
|
|
// send a laravel flash message with an error if the $sheet->user_id is not in the collection $validJudges
|
|
if (! $validJudges->contains('id', $sheet->user_id)) {
|
|
session()->flash('error', 'Entry ID '.$sheet->entry_id.' has an invalid score entered by '.$sheet->judge->full_name());
|
|
}
|
|
|
|
// check if $sheet->user_id is in the collection $validJudges, return false if not, true if it is
|
|
return $validJudges->contains('id', $sheet->user_id);
|
|
|
|
}
|
|
}
|