Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
155 lines
3.9 KiB
PHP
155 lines
3.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Scolarite\Domain\Service;
|
||
|
||
use function abs;
|
||
use function array_fill;
|
||
use function array_values;
|
||
use function count;
|
||
use function floor;
|
||
use function max;
|
||
use function min;
|
||
use function round;
|
||
|
||
final class TeacherStatisticsCalculator
|
||
{
|
||
/**
|
||
* Répartition des notes en 8 bins de largeur 2.5 points (sur /20).
|
||
* Bins : [0–2.5[, [2.5–5[, [5–7.5[, [7.5–10[, [10–12.5[, [12.5–15[, [15–17.5[, [17.5–20].
|
||
*
|
||
* @param list<float> $normalizedValues Notes normalisées sur /20
|
||
*
|
||
* @return list<int> 8 éléments
|
||
*/
|
||
public function calculateDistribution(array $normalizedValues): array
|
||
{
|
||
/** @var list<int> $bins */
|
||
$bins = array_fill(0, 8, 0);
|
||
|
||
foreach ($normalizedValues as $value) {
|
||
$binIndex = min(7, max(0, (int) floor($value / 2.5)));
|
||
++$bins[$binIndex];
|
||
}
|
||
|
||
return array_values($bins);
|
||
}
|
||
|
||
/**
|
||
* Taux de réussite : pourcentage de notes >= seuil.
|
||
*
|
||
* @param list<float> $normalizedValues Notes normalisées sur /20
|
||
* @param float $threshold Seuil de réussite (défaut : 10.0)
|
||
*/
|
||
public function calculateSuccessRate(array $normalizedValues, float $threshold = 10.0): float
|
||
{
|
||
if ($normalizedValues === []) {
|
||
return 0.0;
|
||
}
|
||
|
||
$count = count($normalizedValues);
|
||
$above = 0;
|
||
|
||
foreach ($normalizedValues as $value) {
|
||
if ($value >= $threshold) {
|
||
++$above;
|
||
}
|
||
}
|
||
|
||
return round($above * 100.0 / $count, 1);
|
||
}
|
||
|
||
/**
|
||
* Régression linéaire simple (moindres carrés) pour la ligne de tendance.
|
||
*
|
||
* @param list<array{0: int|float, 1: float}> $points [[x, y], ...]
|
||
*/
|
||
public function calculateTrendLine(array $points): ?TrendResult
|
||
{
|
||
$n = count($points);
|
||
|
||
if ($n < 2) {
|
||
return null;
|
||
}
|
||
|
||
$sumX = 0.0;
|
||
$sumY = 0.0;
|
||
$sumXY = 0.0;
|
||
$sumX2 = 0.0;
|
||
|
||
foreach ($points as [$x, $y]) {
|
||
$sumX += $x;
|
||
$sumY += $y;
|
||
$sumXY += $x * $y;
|
||
$sumX2 += $x * $x;
|
||
}
|
||
|
||
$denominator = $n * $sumX2 - $sumX * $sumX;
|
||
|
||
if (abs($denominator) < 1e-10) {
|
||
return new TrendResult(slope: 0.0, intercept: $sumY / $n);
|
||
}
|
||
|
||
$slope = ($n * $sumXY - $sumX * $sumY) / $denominator;
|
||
$intercept = ($sumY - $slope * $sumX) / $n;
|
||
|
||
return new TrendResult(
|
||
slope: round($slope, 4),
|
||
intercept: round($intercept, 4),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Détecte la tendance à partir de moyennes périodiques (trimestres).
|
||
* Compare la dernière moyenne à la première avec un seuil de 1 point.
|
||
*
|
||
* @param list<float> $periodicAverages Moyennes par période chronologique
|
||
*
|
||
* @return 'improving'|'stable'|'declining'
|
||
*/
|
||
public function detectTrend(array $periodicAverages): string
|
||
{
|
||
if (count($periodicAverages) < 2) {
|
||
return 'stable';
|
||
}
|
||
|
||
$first = $periodicAverages[0];
|
||
$last = $periodicAverages[count($periodicAverages) - 1];
|
||
$diff = $last - $first;
|
||
|
||
if ($diff > 1.0) {
|
||
return 'improving';
|
||
}
|
||
|
||
if ($diff < -1.0) {
|
||
return 'declining';
|
||
}
|
||
|
||
return 'stable';
|
||
}
|
||
|
||
/**
|
||
* Calcule le percentile d'une valeur parmi un ensemble d'autres valeurs.
|
||
* Indique le % de valeurs inférieures à la valeur donnée.
|
||
*
|
||
* @param list<float> $otherValues
|
||
*/
|
||
public function calculatePercentile(float $value, array $otherValues): float
|
||
{
|
||
if ($otherValues === []) {
|
||
return 100.0;
|
||
}
|
||
|
||
$below = 0;
|
||
|
||
foreach ($otherValues as $other) {
|
||
if ($other < $value) {
|
||
++$below;
|
||
}
|
||
}
|
||
|
||
return round($below * 100.0 / count($otherValues), 1);
|
||
}
|
||
}
|