Updates to tabulation controller. Status page more efficient
This commit is contained in:
parent
e4f16bbdd3
commit
72566739b1
|
|
@ -40,11 +40,11 @@ class RoomController extends Controller
|
|||
|
||||
if($request->isMethod('post')) {
|
||||
// attach judge on post
|
||||
$room->judges()->attach($judge->id);
|
||||
$room->addJudge($judge->id);
|
||||
$message = "Assigned " . $judge->full_name() . " to " . $room->name;
|
||||
} elseif ($request->isMethod('delete')) {
|
||||
// detach judge on delete
|
||||
$room->judges()->detach($judge->id);
|
||||
$room->removeJudge($judge->id);
|
||||
$message = "Removed " . $judge->full_name() . " from " . $room->name;
|
||||
} else {
|
||||
return redirect('/admin/rooms/judging_assignments')->with('error', 'Invalid request method.');
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class Audition extends Model
|
|||
protected $rankedEntries = null;
|
||||
protected static $completeAuditions = null;
|
||||
protected $fully_scored; // Set by TabulationService
|
||||
protected $scored_entries_count; //Set by TabulationService
|
||||
|
||||
public static function getCompleteAuditions()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\AuditionChange;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
|
@ -40,4 +41,16 @@ class Room extends Model
|
|||
{
|
||||
return $this->belongsToMany(User::class,'room_user','room_id','user_id');
|
||||
}
|
||||
|
||||
public function addJudge($userId): void
|
||||
{
|
||||
$this->judges()->attach($userId);
|
||||
AuditionChange::dispatch();
|
||||
}
|
||||
|
||||
public function removeJudge($userId): void
|
||||
{
|
||||
$this->judges()->detach($userId);
|
||||
AuditionChange::dispatch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ class AppServiceProvider extends ServiceProvider
|
|||
});
|
||||
|
||||
$this->app->singleton(TabulationService::class, function($app) {
|
||||
return new TabulationService($app->make(AuditionCacheService::class));
|
||||
return new TabulationService(
|
||||
$app->make(AuditionCacheService::class),
|
||||
$app->make(ScoreService::class),
|
||||
$app->make(EntryCacheService::class));
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class AuditionCacheService
|
|||
return $this->getAuditions()->firstWhere('id',$id);
|
||||
}
|
||||
|
||||
|
||||
public function refreshCache()
|
||||
{
|
||||
Cache::forget($this->cacheKey);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class EntryCacheService
|
|||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function getEntriesForAudition($auditionId) {
|
||||
|
||||
// TODO this invokes a lot of lazy loading. Perhaps cache the data for all entries then draw from that for each audition
|
||||
$cacheKey = 'audition' . $auditionId . 'entries';
|
||||
return Cache::remember($cacheKey, 3600, function () use ($auditionId) {
|
||||
return Entry::where('audition_id',$auditionId)
|
||||
|
|
@ -53,8 +53,8 @@ class EntryCacheService
|
|||
public function getAllEntries()
|
||||
{
|
||||
$cacheKey = 'allEntries';
|
||||
return Cache::remember($cacheKey, 3600, function() {
|
||||
return Entry::with(['student.school'])->get()->keyBy('id');
|
||||
return Cache::remember($cacheKey, 5, function() {
|
||||
return Entry::all();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
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
|
||||
|
|
@ -50,6 +52,30 @@ class ScoreService
|
|||
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
|
||||
$entryScoreSheetCounts = ScoreSheet::select('entry_id', DB::raw('count(*) as count'))
|
||||
->groupBy('entry_id')
|
||||
->get()
|
||||
->pluck('count','entry_id');
|
||||
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
|
||||
|
|
@ -82,7 +108,7 @@ class ScoreService
|
|||
}
|
||||
|
||||
/**
|
||||
* Calculate final score using teh provided scoring guide and score sheets. Returns an array of scores
|
||||
* 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.
|
||||
* @param $scoringGuideId
|
||||
|
|
@ -120,4 +146,28 @@ class ScoreService
|
|||
array_unshift($finalScoresArray,$totalScore);
|
||||
return $finalScoresArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the judge on the score sheet is actually assigned to judge
|
||||
* then entry
|
||||
* @param ScoreSheet $sheet
|
||||
* @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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,31 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Audition;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Services\AuditionCacheService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
|
||||
class TabulationService
|
||||
{
|
||||
protected $cacheKey = 'entries';
|
||||
protected $auditionCacheService;
|
||||
protected AuditionCacheService $auditionCacheService;
|
||||
protected EntryCacheService $entryCacheService;
|
||||
protected ScoreService $scoreService;
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(AuditionCacheService $scoringGuideCacheService)
|
||||
public function __construct(
|
||||
AuditionCacheService $scoringGuideCacheService,
|
||||
ScoreService $scoreService,
|
||||
EntryCacheService $entryCacheService)
|
||||
{
|
||||
$this->auditionCacheService = $scoringGuideCacheService;
|
||||
$this->scoreService = $scoreService;
|
||||
$this->entryCacheService = $entryCacheService;
|
||||
}
|
||||
|
||||
public function entryRank(Entry $entry) {
|
||||
|
|
@ -39,7 +43,8 @@ class TabulationService
|
|||
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$entry->final_score_array = $this->entryFinalScores($entry);
|
||||
// $entry->final_score_array = $this->entryFinalScores($entry);
|
||||
$entry->final_score_array = $this->scoreService->entryTotalScores($entry);
|
||||
$entry->scoring_complete = ($entry->score_sheets_count == $audition->judges_count) ? true:false;
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +54,7 @@ class TabulationService
|
|||
return $entry['final_score_array'][$n];
|
||||
});
|
||||
}
|
||||
//TODO verify this actually sorts by subscores correcty
|
||||
//TODO verify this actually sorts by subscores correctly
|
||||
$n = 1;
|
||||
foreach ($entries as $entry) {
|
||||
$entry->rank = $n;
|
||||
|
|
@ -59,40 +64,9 @@ class TabulationService
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TabulationException
|
||||
*/
|
||||
public function entryFinalScores(Entry $entry) {
|
||||
$audition = $this->auditionCacheService->getAudition($entry->audition_id);
|
||||
if (! $this->entryScoreSheetsAreValid($entry)) return null;
|
||||
|
||||
$subscores = $audition->scoringGuide->subscores->sortBy('tiebreak_order');
|
||||
$finalScoresArray = [];
|
||||
foreach($subscores as $subscore) {
|
||||
$finalScoresArray[$subscore->id] = 0;
|
||||
|
||||
}
|
||||
|
||||
foreach ($entry->scoreSheets as $sheet) {
|
||||
foreach ($sheet->subscores as $ss) {
|
||||
$finalScoresArray[$ss['subscore_id']] += $ss['score'];
|
||||
}
|
||||
}
|
||||
|
||||
// calculate weighted final score
|
||||
$totalScore = 0;
|
||||
$totalWeight = 0;
|
||||
foreach ($subscores as $subscore) {
|
||||
$totalScore += ($finalScoresArray[$subscore->id] * $subscore->weight);
|
||||
$totalWeight += $subscore->weight;
|
||||
}
|
||||
$totalScore = ($totalScore / $totalWeight);
|
||||
array_unshift($finalScoresArray,$totalScore);
|
||||
return $finalScoresArray;
|
||||
}
|
||||
|
||||
public function entryScoreSheetsAreValid(Entry $entry): bool {
|
||||
//TODO consider making this move the invalid score to another database for further investication
|
||||
//TODO consider making this move the invalid score to another database for further investigation
|
||||
$validJudges = $this->auditionCacheService->getAudition($entry->audition_id)->judges;
|
||||
foreach ($entry->scoreSheets as $sheet) {
|
||||
if (! $validJudges->contains($sheet->user_id)) {
|
||||
|
|
@ -110,38 +84,31 @@ class TabulationService
|
|||
return $audition->entries_count - $audition->scored_entries_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of all auditions from the cache. For each one, set a property
|
||||
* scored_entries_count that indicates the number of entries for that audition that
|
||||
* have a number of score sheets equal to the number of judges for that audition.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuditionsWithStatus()
|
||||
{
|
||||
return Cache::remember('auditionsWithStatus',30,function() {
|
||||
// Create an array with the number of scores for each entry
|
||||
$scoreCountByEntry = ScoreSheet::select('entry_id', DB::raw('count(*) as count'))
|
||||
->groupBy('entry_id')
|
||||
->get()
|
||||
->pluck('count','entry_id');
|
||||
|
||||
// Retrieve auditions from the cache and load entry IDs
|
||||
$auditions = $this->auditionCacheService->getAuditions();
|
||||
$auditions->load('entries');
|
||||
|
||||
// Eager load the count of related models
|
||||
$auditions->loadCount(['judges', 'entries']);
|
||||
|
||||
// Iterate over the auditions and calculate the scored_entries_count
|
||||
return $auditions->map(function ($audition) use ($scoreCountByEntry) {
|
||||
$audition->scored_entries_count = $audition->entries->reduce(function ($carry, $entry) use ($audition, $scoreCountByEntry) {
|
||||
$entry->fully_scored = $audition->judges_count == $scoreCountByEntry->has($entry->id) ? $scoreCountByEntry[$entry->id] : false;
|
||||
return $carry + ($entry->fully_scored ? 1 : 0);
|
||||
}, 0);
|
||||
return $audition;
|
||||
});
|
||||
});
|
||||
foreach($auditions as $audition) {
|
||||
$scored_entries_count = 0;
|
||||
foreach ($this->entryCacheService->getEntriesForAudition($audition->id) as $entry) {
|
||||
if ($this->scoreService->entryScoreSheetCounts()[$entry->id] ?? 0 == $audition->judges_count) {
|
||||
$scored_entries_count++;
|
||||
}
|
||||
}
|
||||
|
||||
public function allResults() {
|
||||
$auditions = $this->getAuditionsWithStatus();
|
||||
foreach ($auditions as $audition) {
|
||||
$audition->entries = $this->auditionEntries($audition->id);
|
||||
$audition->scored_entries_count = $scored_entries_count;
|
||||
}
|
||||
return $auditions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ return [
|
|||
'show_copy' => false, // Show copy button next to the query,
|
||||
'slow_threshold' => false, // Only track queries that last longer than this time in ms
|
||||
'memory_usage' => false, // Show queries memory usage
|
||||
'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured
|
||||
'soft_limit' => 200, // After the soft limit, no parameters/backtrace are captured
|
||||
'hard_limit' => 500, // After the hard limit, queries are ignored
|
||||
],
|
||||
'mail' => [
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@
|
|||
use Illuminate\Support\Facades\Session;
|
||||
@endphp
|
||||
@inject('scoreservice','App\Services\ScoreService');
|
||||
@inject('auditionService','App\Services\AuditionCacheService');
|
||||
@inject('entryService','App\Services\EntryCacheService')
|
||||
<x-layout.app>
|
||||
<x-slot:page_title>Test Page</x-slot:page_title>
|
||||
@php
|
||||
$entry = Entry::first();
|
||||
$scoreservice->calculateScoresForAudition(6);
|
||||
$testedSheet = ScoreSheet::find(37);
|
||||
@endphp
|
||||
|
||||
Entry 392 scores: {{ $scoreservice->entryTotalScores(Entry::find(392))[0] }}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue