-
Jonathan Foucher authoredJonathan Foucher authored
GraphDataRepository.php 20.17 KiB
<?php
namespace App\Repository;
use App\Models\Danger;
use App\Models\DangerLevelEvaluation;
use App\Models\Evaluation;
use App\Models\EvaluationMaturityAnswer;
use App\Models\Measure;
use App\Models\MeasureLevel;
use App\Models\Organization;
use App\Models\Scenario;
use App\Models\Territory;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class GraphDataRepository
{
private EvaluationRepository $repository;
public function __construct(EvaluationRepository $repository)
{
$this->repository = $repository;
}
public function getMeasuresDataForEvaluation(Evaluation $evaluation): array
{
$measureLevels = collect($evaluation->measureLevels)->sortBy(function (MeasureLevel $ml) {
return $ml->measure->fundamental;
});
$levels = $measureLevels->map(function (MeasureLevel $level) {
return $level->actual_level;
});
$labels = $measureLevels->map(function (MeasureLevel $level) {
return $level->measure->short_name;
});
return [
'labels' => $labels->values(),
'data' => $levels->values(),
];
}
/**
* For each danger, return the exposition value taking into account the danger level that was selected.
*/
public function getExpositionDataForEvaluation(Evaluation $evaluation): array
{
$measureLevels = collect($evaluation->measureLevels);
$dangerLevels = collect($evaluation->dangerLevels);
// For each danger, add all points of all measures that apply to the danger scenarios
$dangers = Danger::all();
$labels = [];
$values = [];
foreach ($dangers as $danger) {
/*
* @var Danger $danger
*/
$labels[] = $danger->name;
$v = $danger->scenarios->reduce(function ($acc, Scenario $scenario) use ($measureLevels) {
$exposition = $this->getExpositionPointsForScenario($scenario, $measureLevels);
return $acc + $exposition;
}, 0);
// Get selected danger level
$dangerLevel = $dangerLevels->first(function (DangerLevelEvaluation $dl) use ($danger) {
return $dl->danger_id === $danger->id;
});
// the points for this danger are the total points for this danger,
// divided by the number of appllicable scenarios
$values[] = $v / $danger->scenarios->count() * $dangerLevel->level->getPointsAttribute();
}
$total = collect($values)->sum();
$percentages = collect($values)->map(function ($val) use ($total) {
if (0.0 === $val || 0.0 === $total) {
return 10;
}
return max(10, $val / $total * 100);
});
return [
'labels' => $labels,
'data' => $percentages,
];
}
public function getRisksDataForEvaluation(Evaluation $evaluation): array
{
$measureLevels = collect($evaluation->measureLevels);
// For each danger, add all points of all measures that apply to the danger scenarios
$dangers = Danger::all();
$labels = [];
$values = [];
foreach ($dangers as $danger) {
/*
* @var Danger $danger
*/
$labels[] = $danger->name;
$v = $danger->scenarios->reduce(function ($acc, Scenario $scenario) use ($measureLevels) {
$exposition = $this->getExpositionPointsForScenario($scenario, $measureLevels);
return $acc + $exposition;
}, 0);
$values[] = max(10, $v / $danger->scenarios->count());
}
return [
'labels' => $labels,
'data' => $values,
];
}
public function getFutureRisksDataForEvaluation(Evaluation $evaluation): array
{
$measureLevels = collect($evaluation->measureLevels);
// For each danger, add all points of all measures that apply to the danger scenarios
$dangers = Danger::all();
$labels = [];
$values = [];
foreach ($dangers as $danger) {
/*
* @var Danger $danger
*/
$labels[] = $danger->name;
$v = $danger->scenarios->reduce(function ($acc, Scenario $scenario) use ($measureLevels) {
$exposition = $this->getFutureExpositionPointsForScenario($scenario, $measureLevels);
return $acc + $exposition;
}, 0);
$values[] = max(10, $v / $danger->scenarios->count());
}
return [
'labels' => $labels,
'data' => $values,
];
}
public function getAttackDataForEvaluation(Evaluation $evaluation): array
{
$scenarios = Scenario::with('measures')->get();
$measureLevels = collect($evaluation->measureLevels);
// For each scenario, add all points of all selected measure levels and divide by number of measures
// (fundamental measures count double)
$values = $scenarios->map(function (Scenario $scenario) use ($measureLevels) {
return max(10, $this->getExpositionPointsForScenario($scenario, $measureLevels));
});
return [
'labels' => $scenarios->pluck('name'),
'data' => $values,
];
}
public function getMaturityDataForEvaluation(Evaluation $evaluation): array
{
// Calculate automatic answer results
$evaluation->saveAutomaticAnswers();
$evaluation = Evaluation::find($evaluation->id);
$maturityLevels = $evaluation->maturityLevels;
$values = $maturityLevels->map(function (EvaluationMaturityAnswer $ml) {
return ['level' => $ml->answer->level, 'label' => $ml->question->title];
});
return [
'labels' => $values->pluck('label'),
'data' => $values->pluck('level'),
];
}
public function getBestMeasuresForEvaluation(Evaluation $evaluation): array
{
$measureLevels = collect($evaluation->measureLevels);
// For each danger, add all points of all measures that apply to the danger scenarios
$measures = Measure::all();
Log::info('____ Calcul des meilleures mesures pour l\'évaluation ' . $evaluation->id . ' ____');
$bestMeasures = array_map(function ($i) {return 0; }, array_flip($measures->pluck('id')->toArray()));
$res = DB::select('select measures.id, count(*) as qty from measures
join measure_scenario on measures.id=measure_scenario.measure_id
join danger_scenario on danger_scenario.scenario_id=measure_scenario.scenario_id
group by measures.id');
$measureCount = [];
foreach ($res as $row) {
$measureCount[$row->id] = $row->qty;
}
/**
* @var DangerLevelEvaluation $dangerLevel
*/
// Nous regardons chaque niveau de danger choisi à l'étape 1 pour cette évaluation
foreach ($evaluation->dangerLevels as $dangerLevel) {
Log::info('*** Danger ' . $dangerLevel->danger->name . ' ***');
Log::info('Ce danger appartient à ' . count($dangerLevel->danger->scenarios) . ' scenarios');
/**
* @var Scenario $scenario
*/
// Pour chaque niveau de danger, nous regardons les scénarios qui sont liés au danger
foreach ($dangerLevel->danger->scenarios as $scenario) {
Log::info('--- Scenario ' . $scenario->name . ' ---');
Log::info('Ce scenario contient ' . count($scenario->measures) . ' mesures');
// Add points for this measure if there is a difference in exposition
// Pour chaque scenarios nous regardons les mesures qui l'affectent
foreach ($scenario->measures as $measure) {
Log::info('- Mesure ' . $measure->short_name . ($measure->fundamental ? '(fondamentale) -' : ' -'));
/**
* @var MeasureLevel|null $selectedLevel
*/
// Nous regardons quel niveau a été choisit à l'étape 2 pour cette mesure.
$selectedLevel = $measureLevels->first(function (MeasureLevel $level) use ($measure) {
return $measure->id === $level->measure_id;
});
Log::info('Niveau actuel de la mesure : ' . $selectedLevel->actual_level);
if (!$selectedLevel) {
// Si aucun niveau n'a été choisi pour cette mesure, nous lui mettons le niveau 0
// Cela ne devrait pas être possible car le choix d'un niveau est obligatoire à l'étape 2
// Toutefois, mieux vaut s'assurer de sa présence pour éviter un crash
$selectedLevel = new MeasureLevel();
$selectedLevel->actual_level = 0;
}
// La valeur du niveau actuel choisit pour cette mesure
// (les mesures fondamentales sont déja multipliées par deux en base de données)
$l1 = $measure->{'level' . $selectedLevel->actual_level . '_value'};
Log::info('Valeur du niveau actuel : ' . $measure->{'level' . $selectedLevel->actual_level . '_value'});
// La valeur du niveau juste au dessus du niveau actuel choisit pour cette mesure (avec un maximum de niveau 3)
// (les mesures fondamentales sont déja multipliées par deux en base de données)
$l2 = $measure->{'level' . min($selectedLevel->actual_level + 1, 3) . '_value'};
Log::info('Valeur du niveau actuel + 1 : ' . $measure->{'level' . min($selectedLevel->actual_level + 1, 3) . '_value'});
// La différence entre la valeur du niveau N + 1 et du niveau N (progression potentielle)
// est pondérée par la perception du danger
// puis ajouté au score pour cette mesure
$ponderatedIncrement = ($l2 - $l1) * $dangerLevel->level->value / $measureCount[$measure->id];
$bestMeasures[$measure->id] += $ponderatedIncrement;
Log::info('Incrément pondéré : ' . $ponderatedIncrement);
Log::info('Valeur courante de la mesure ' . $measure->id . ' (' . $measure->short_name . ') : ' . $bestMeasures[$measure->id]);
}
}
}
foreach ($bestMeasures as $k => $m) {
Log::info('Valeur finale de la mesure ' . $k . ' : ' . $m);
}
// Les mesures sont mises dans l'ordre du plus grand score au plus petit et renvoyées.
return collect($bestMeasures)->sortDesc()->keys()->toArray();
}
private function getExpositionPointsForScenario(Scenario $scenario, $measureLevels): float
{
// Get prorated count of measures
$count = $scenario->measures->reduce(function ($acc, Measure $measure) {
if ($measure->fundamental) {
return $acc + 2;
}
return $acc + 1;
}, 0);
// for each measure in this scenario, add points for the level that is selected
$points = $scenario->measures->reduce(function ($acc, Measure $measure) use ($measureLevels) {
$selectedLevel = $measureLevels->first(function (MeasureLevel $level) use ($measure) {
return $measure->id === $level->measure_id;
});
if (!$selectedLevel) {
return $acc;
}
// fundamental measures count double
return $acc + $measure->{'level' . $selectedLevel->actual_level . '_value'};
}, 0);
return floor(100 - ($points / $count));
}
private function getFutureExpositionPointsForScenario(Scenario $scenario, $measureLevels): float
{
// Get prorated count of measures
$count = $scenario->measures->reduce(function ($acc, Measure $measure) {
if ($measure->fundamental) {
return $acc + 2;
}
return $acc + 1;
}, 0);
// for each measure in this scenario, add points for the level that is selected
$points = $scenario->measures->reduce(function ($acc, Measure $measure) use ($measureLevels) {
$selectedLevel = $measureLevels->first(function (MeasureLevel $level) use ($measure) {
return $measure->id === $level->measure_id;
});
// fundamental measures count double
$val = $measure->{'level' . $selectedLevel->expected_level . '_value'};
if (!$val) {
$val = $measure->{'level' . $selectedLevel->actual_level . '_value'};
}
return $acc + $val;
}, 0);
return floor(100 - ($points / $count));
}
public function getActionTerritoryGraphData(): array
{
// Pour cq territoire
// trouver les evals des structures
// sort les evals pour recup currentEvaluation & previousEvaluation
// puis plannedMeasures par structure
// puis doneMeasure par structure
// puis count par territoire
$territories = Territory::with([
'organizations',
'organizations.evaluations',
'organizations.evaluations.measureLevels',
])->get();
$labels = [];
$plannedMeasures = [];
$doneMeasures = [];
foreach ($territories as $territory) {
/*
* @var Territory $territory
*/
$labels[] = $territory->name;
$plannedMeasures[] = $territory->organizations->filter(function (Organization $org) {
return $org->active;
})->reduce(function ($acc, Organization $org) {
$doneEvaluations = $org->doneEvaluations;
if ($doneEvaluations->count() >= 2) {
/**
* @var Evaluation $currentEval
*/
$currentEval = $doneEvaluations[0];
// Get the number of planned actions in the current evaluation
return $acc + $currentEval->measureLevels->filter(function (MeasureLevel $ml) {
return null !== $ml->expected_level && 0 !== $ml->expected_level;
})->count();
}
return $acc;
}, 0);
$doneMeasures[] = $territory->organizations->filter(function (Organization $org) {
return $org->active;
})->reduce(function ($acc, Organization $org) {
$doneEvaluations = $org->doneEvaluations;
if ($doneEvaluations->count() >= 2) {
/**
* @var Evaluation $currentEval
*/
$currentEval = $doneEvaluations[0];
/**
* @var Evaluation $previousEval
*/
$previousEval = $doneEvaluations[1];
// Get the number of planned actions in the previous evaluation
// that have reached the expected_level in the current evaluation
return $acc + $currentEval->measureLevels->filter(function (MeasureLevel $ml) use ($previousEval) {
$prevML = $previousEval->measureLevels->first(function (MeasureLevel $pml) use ($ml) {
return $ml->measure_id === $pml->measure_id;
});
return $prevML && $ml->actual_level >= $prevML->expected_level;
})->count();
}
return $acc;
}, 0);
}
return [
'labels' => $labels,
'data' => [$plannedMeasures, $doneMeasures],
];
}
public function getOrganizationData(int|null $territory_id = null)
{
$orgQuery = Organization::where('active', true);
if ($territory_id) {
$orgQuery->where('territory_id', $territory_id);
}
$orgs = $orgQuery->get();
$count = $orgs->count();
if (0 === $count) {
return [
'maturity' => 0, // Nombre de structures évaluées
'count' => 0, // Indice de maturité moyen des structures actives
'top_measures' => [], // Top 3 des niveaux de mesures planifiées (doit retourner le nom court de la mesure, le niveau et le nombre de structures concernées / ou directement le %)
];
}
$meanMaturity = $orgs->reduce(function ($acc, Organization $organization) {
/**
* @var Evaluation|null $doneEvaluation
*/
$doneEvaluation = $organization->doneEvaluations->first();
if (!$doneEvaluation) {
return $acc;
}
return $acc + $doneEvaluation->getMaturityCyberAttribute();
}, 0) / $count;
$topMeasures = [];
foreach ($orgs as $org) {
/**
* @var Evaluation|null $currentEvaluation
*/
$currentEvaluation = $org->doneEvaluations->first();
if (!$currentEvaluation) {
continue;
}
foreach ($currentEvaluation->measureLevels as $level) {
if (!$level->expected_level) {
continue;
}
if (array_key_exists($level->measure->short_name, $topMeasures)) {
++$topMeasures[$level->measure->short_name]['level' . $level->expected_level];
++$topMeasures[$level->measure->short_name]['organizations'];
} else {
$topMeasures[$level->measure->short_name] = [
'short_name' => $level->measure->short_name,
'level1' => 1 === $level->expected_level ? 1 : 0,
'level2' => 2 === $level->expected_level ? 1 : 0,
'level3' => 3 === $level->expected_level ? 1 : 0,
'organizations' => 1,
];
}
}
}
$topMeasures = collect($topMeasures)->map(function ($topMeasure) use ($count) {
$levelValues = [
1 => $topMeasure['level1'],
2 => $topMeasure['level2'],
3 => $topMeasure['level3'],
];
$maxValue = max($levelValues);
$maxLevels = array_keys($levelValues, $maxValue);
return [
'short_name' => $topMeasure['short_name'],
'organizations' => $topMeasure['organizations'],
'max_levels' => collect($maxLevels)->sortDesc()->toArray(),
'max_value' => $maxValue,
'percentage' => 100 * $maxValue / $count,
];
})->values()->sortBy('max_value', SORT_NUMERIC, true)->splice(0, 3)->toArray();
return [
'maturity' => $meanMaturity, // Nombre de structures évaluées
'count' => $count, // Indice de maturité moyen des structures actives
'top_measures' => $topMeasures, // Top 3 des niveaux de mesures planifiées (doit retourner le nom court de la mesure, le niveau et le nombre de structures concernées / ou directement le %)
];
}
public static function hue2rgb($p, $q, $t)
{
if ($t < 0) {
++$t;
}
if ($t > 1) {
--$t;
}
if ($t < 1 / 6) {
return $p + ($q - $p) * 6 * $t;
}
if ($t < 1 / 2) {
return $q;
}
if ($t < 2 / 3) {
return $p + ($q - $p) * (2 / 3 - $t) * 6;
}
return $p;
}
public static function hslToRgb($h, $s, $l)
{
if (0 == $s) {
$r = $l;
$g = $l;
$b = $l; // achromatic
} else {
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
$p = 2 * $l - $q;
$r = self::hue2rgb($p, $q, $h + 1 / 3);
$g = self::hue2rgb($p, $q, $h);
$b = self::hue2rgb($p, $q, $h - 1 / 3);
}
return [round($r * 255), round($g * 255), round($b * 255)];
}
}