feat: Configuration du mode de notation par établissement
Les établissements scolaires utilisent des systèmes d'évaluation variés (notes /20, /10, lettres, compétences, sans notes). Jusqu'ici l'application imposait implicitement le mode notes /20, ce qui ne correspondait pas à la réalité pédagogique de nombreuses écoles. Cette configuration permet à chaque établissement de choisir son mode de notation par année scolaire, avec verrouillage automatique dès que des notes ont été saisies pour éviter les incohérences. Le Score Sérénité adapte ses pondérations selon le mode choisi (les compétences sont converties via un mapping, le mode sans notes exclut la composante notes).
This commit is contained in:
20
backend/src/Scolarite/Domain/Model/GradingMode.php
Normal file
20
backend/src/Scolarite/Domain/Model/GradingMode.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Model;
|
||||
|
||||
/**
|
||||
* Mode de notation vu depuis le contexte Scolarité.
|
||||
*
|
||||
* Représentation locale indépendante de Administration,
|
||||
* utilisée par SerenityScoreWeights pour adapter les pondérations.
|
||||
*/
|
||||
enum GradingMode: string
|
||||
{
|
||||
case NUMERIC_20 = 'numeric_20';
|
||||
case NUMERIC_10 = 'numeric_10';
|
||||
case LETTERS = 'letters';
|
||||
case COMPETENCIES = 'competencies';
|
||||
case NO_GRADES = 'no_grades';
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Service;
|
||||
|
||||
use App\Scolarite\Domain\Model\GradingMode;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value Object contenant les pondérations du Score Sérénité adaptées au mode de notation.
|
||||
*
|
||||
* Standard : Notes×0.4 + Absences×0.3 + Devoirs×0.3
|
||||
* Sans notes : Absences×0.5 + Devoirs×0.5 (composante notes exclue)
|
||||
* Compétences : même pondération, mais les notes sont converties via le mapping Acquis→100, En cours→50, Non acquis→0
|
||||
*/
|
||||
final readonly class SerenityScoreWeights
|
||||
{
|
||||
private const array COMPETENCY_MAPPING = [
|
||||
'acquired' => 100,
|
||||
'in_progress' => 50,
|
||||
'not_acquired' => 0,
|
||||
];
|
||||
|
||||
private function __construct(
|
||||
public float $notesWeight,
|
||||
public float $absencesWeight,
|
||||
public float $devoirsWeight,
|
||||
private bool $usesCompetencyMapping,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function forMode(GradingMode $mode): self
|
||||
{
|
||||
return match ($mode) {
|
||||
GradingMode::NO_GRADES => new self(
|
||||
notesWeight: 0.0,
|
||||
absencesWeight: 0.5,
|
||||
devoirsWeight: 0.5,
|
||||
usesCompetencyMapping: false,
|
||||
),
|
||||
GradingMode::COMPETENCIES => new self(
|
||||
notesWeight: 0.4,
|
||||
absencesWeight: 0.3,
|
||||
devoirsWeight: 0.3,
|
||||
usesCompetencyMapping: true,
|
||||
),
|
||||
default => new self(
|
||||
notesWeight: 0.4,
|
||||
absencesWeight: 0.3,
|
||||
devoirsWeight: 0.3,
|
||||
usesCompetencyMapping: false,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un niveau de compétence en score numérique (0-100).
|
||||
*
|
||||
* @return int|null Score numérique, ou null si le mode n'utilise pas le mapping compétences
|
||||
*/
|
||||
public function competencyToScore(string $competencyLevel): ?int
|
||||
{
|
||||
if (!$this->usesCompetencyMapping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::COMPETENCY_MAPPING[$competencyLevel]
|
||||
?? throw new InvalidArgumentException("Niveau de compétence inconnu : '{$competencyLevel}'");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user