Files
vkv/app/Jobs/FinalizeRunJob.php
Zdeněk Burda 41e3ce6f25 Initial commit
2026-01-09 21:26:40 +01:00

142 lines
5.0 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}
}