Initial commit

This commit is contained in:
Zdeněk Burda
2026-01-09 21:26:40 +01:00
parent e83aec6dca
commit 41e3ce6f25
404 changed files with 61250 additions and 28 deletions

141
app/Jobs/FinalizeRunJob.php Normal file
View File

@@ -0,0 +1,141 @@
<?php
namespace App\Jobs;
use App\Models\EvaluationLock;
use App\Models\EvaluationRun;
use App\Services\Evaluation\EvaluationCoordinator;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;
/**
* Job: FinalizeRunJob
*
* Odpovědnost:
* - Finální krok vyhodnocovací pipeline pro jeden EvaluationRun.
* - Přepíná vyhodnocovací běh z technického stavu "rozpracováno" do
* konzistentního, uzavřeného stavu, který je připraven k prezentaci
* nebo publikaci výsledků.
*
* Kontext:
* - Spouští se po úspěšném dokončení agregace výsledků
* (AggregateLogResultsJob / DispatchAggregateResultsJobsJob).
* - Pracuje výhradně s agregovanými daty svázanými s evaluation_run_id.
*
* Co job dělá (typicky):
* - Ověří, že všechny předchozí kroky pipeline byly úspěšně dokončeny.
* - Provede finální validace výsledků (např. konzistence součtů).
* - Označí agregované výsledky jako finální pro daný EvaluationRun:
* - nastaví příznaky typu is_final / is_official
* - případně provede přesun z dočasných (staging) struktur
* do finálních tabulek
* - Aktualizuje stav EvaluationRun:
* - status -> SUCCEEDED (nebo FAILED při chybě)
* - nastaví finished_at
* - Uvolní locky držené pro scope vyhodnocení.
* - Zapíše závěrečné auditní události (EvaluationRunEvent).
*
* Co job NEDĚLÁ:
* - nepočítá body ani skóre
* - nemění výsledky jednotlivých QSO
* - neřeší export ani prezentaci v UI
*
* Zásady návrhu:
* - Tento krok musí být atomický z pohledu stavu vyhodnocení.
* - Při selhání musí být EvaluationRun jednoznačně označen jako FAILED
* a nesmí zůstat v nekonzistentním stavu.
* - Veškerá logika patří do service layer (např. EvaluationFinalizerService).
*
* Queue:
* - Spouští se ve frontě "evaluation".
* - Nemá běžet paralelně nad stejným EvaluationRun.
*/
class FinalizeRunJob implements ShouldQueue
{
use Queueable;
public int $tries = 2;
public array $backoff = [60];
/**
* Create a new job instance.
*/
public function __construct(
protected int $evaluationRunId,
protected ?string $lockKey = null
)
{
//
}
/**
* Finalizuje vyhodnocovací běh.
*
* Metoda handle():
* - provede závěrečné kontroly konzistence dat
* - označí výsledky jako finální / oficiální
* - přepne EvaluationRun do koncového stavu (SUCCEEDED / FAILED)
* - uvolní zdroje a locky držené během vyhodnocení
*
* Poznámky:
* - Tento job je posledním krokem pipeline.
* - Po jeho úspěšném dokončení musí být možné výsledky bezpečně
* zobrazit nebo exportovat.
*/
public function handle(): void
{
$run = EvaluationRun::find($this->evaluationRunId);
if (! $run || $run->isCanceled()) {
return;
}
$coordinator = new EvaluationCoordinator();
try {
$coordinator->eventInfo($run, 'Finalize: krok spuštěn.', [
'step' => 'finalize',
'round_id' => $run->round_id,
]);
// Po ručních zásazích může být potřeba znovu aplikovat override a přepočítat pořadí.
(new ApplyLogOverridesJob($run->id))->handle();
(new RecalculateOfficialRanksJob($run->id))->handle();
$coordinator->eventInfo($run, 'Před finalizací byl znovu aplikován override a přepočítáno pořadí.', [
'step' => 'finalize',
'round_id' => $run->round_id,
]);
$run->update([
'status' => 'SUCCEEDED',
'current_step' => 'finalize',
'progress_total' => 1,
'progress_done' => 1,
'finished_at' => now(),
]);
$coordinator->eventInfo($run, 'Vyhodnocení dokončeno.', [
'step' => 'finalize',
'round_id' => $run->round_id,
'step_progress_done' => 1,
'step_progress_total' => 1,
]);
$coordinator->eventInfo($run, 'Finalize: krok dokončen.', [
'step' => 'finalize',
'round_id' => $run->round_id,
]);
} catch (Throwable $e) {
$run->update([
'status' => 'FAILED',
'error' => $e->getMessage(),
'finished_at' => now(),
]);
$coordinator->eventError($run, "FinalizeRunJob selhal: {$e->getMessage()}", [
'step' => 'finalize',
'round_id' => $run->round_id,
]);
throw $e;
} finally {
// Lock musí být uvolněn vždy, i při chybě jinak zablokuje další běhy.
$lockKey = $this->lockKey ?? "evaluation:round:{$run->round_id}";
EvaluationLock::release($lockKey, $run);
}
}
}