feat: Calculer automatiquement les moyennes après chaque saisie de notes
Les enseignants ont besoin de moyennes à jour immédiatement après la publication ou modification des notes, sans attendre un batch nocturne. Le système recalcule via Domain Events synchrones : statistiques d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées (normalisation /20), et moyenne générale par élève. Les résultats sont stockés dans des tables dénormalisées avec cache Redis (TTL 5 min). Trois endpoints API exposent les données avec contrôle d'accès par rôle. Une commande console permet le backfill des données historiques au déploiement.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use App\Scolarite\Domain\Model\Grade\AppreciationTemplate;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\CreateAppreciationTemplateProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\DeleteAppreciationTemplateProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\UpdateAppreciationTemplateProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\AppreciationTemplateCollectionProvider;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'AppreciationTemplate',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/me/appreciation-templates',
|
||||
provider: AppreciationTemplateCollectionProvider::class,
|
||||
name: 'get_appreciation_templates',
|
||||
),
|
||||
new Post(
|
||||
uriTemplate: '/me/appreciation-templates',
|
||||
read: false,
|
||||
processor: CreateAppreciationTemplateProcessor::class,
|
||||
validationContext: ['groups' => ['Default', 'create']],
|
||||
name: 'create_appreciation_template',
|
||||
),
|
||||
new Put(
|
||||
uriTemplate: '/me/appreciation-templates/{id}',
|
||||
read: false,
|
||||
processor: UpdateAppreciationTemplateProcessor::class,
|
||||
validationContext: ['groups' => ['Default', 'update']],
|
||||
name: 'update_appreciation_template',
|
||||
),
|
||||
new Delete(
|
||||
uriTemplate: '/me/appreciation-templates/{id}',
|
||||
read: false,
|
||||
processor: DeleteAppreciationTemplateProcessor::class,
|
||||
name: 'delete_appreciation_template',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class AppreciationTemplateResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $id = null;
|
||||
|
||||
#[Assert\NotBlank(groups: ['create', 'update'])]
|
||||
#[Assert\Length(max: 100, groups: ['create', 'update'])]
|
||||
public ?string $title = null;
|
||||
|
||||
#[Assert\NotBlank(groups: ['create', 'update'])]
|
||||
#[Assert\Length(max: 500, groups: ['create', 'update'])]
|
||||
public ?string $content = null;
|
||||
|
||||
public ?string $category = null;
|
||||
|
||||
public ?int $usageCount = null;
|
||||
|
||||
public ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
public ?DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public static function fromDomain(AppreciationTemplate $template): self
|
||||
{
|
||||
$resource = new self();
|
||||
$resource->id = (string) $template->id;
|
||||
$resource->title = $template->title;
|
||||
$resource->content = $template->content;
|
||||
$resource->category = $template->category?->value;
|
||||
$resource->usageCount = $template->usageCount;
|
||||
$resource->createdAt = $template->createdAt;
|
||||
$resource->updatedAt = $template->updatedAt;
|
||||
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\ClassStatisticsProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ClassStatistics',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/classes/{classId}/statistics',
|
||||
provider: ClassStatisticsProvider::class,
|
||||
name: 'get_class_statistics',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class ClassStatisticsResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $classId = null;
|
||||
|
||||
public ?string $periodId = null;
|
||||
|
||||
/** @var list<array{evaluationId: string, title: string, average: float|null, min: float|null, max: float|null, median: float|null, gradedCount: int}> */
|
||||
public array $evaluations = [];
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\LinkCompetenciesToEvaluationProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\CompetencyEvaluationCollectionProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'CompetencyEvaluation',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/evaluations/{evaluationId}/competencies',
|
||||
uriVariables: ['evaluationId'],
|
||||
provider: CompetencyEvaluationCollectionProvider::class,
|
||||
name: 'get_evaluation_competencies',
|
||||
),
|
||||
new Post(
|
||||
uriTemplate: '/evaluations/{evaluationId}/competencies',
|
||||
uriVariables: ['evaluationId'],
|
||||
read: false,
|
||||
processor: LinkCompetenciesToEvaluationProcessor::class,
|
||||
validationContext: ['groups' => ['Default', 'create']],
|
||||
name: 'link_competencies_to_evaluation',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class CompetencyEvaluationResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $id = null;
|
||||
|
||||
public ?string $evaluationId = null;
|
||||
|
||||
public ?string $competencyId = null;
|
||||
|
||||
public ?string $competencyCode = null;
|
||||
|
||||
public ?string $competencyName = null;
|
||||
|
||||
/** @var array<string>|null */
|
||||
#[Assert\NotBlank(message: 'Les compétences sont requises.', groups: ['create'])]
|
||||
public ?array $competencyIds = null;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\CompetencyLevelCollectionProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'CompetencyLevel',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/competency-levels',
|
||||
provider: CompetencyLevelCollectionProvider::class,
|
||||
name: 'get_competency_level_list',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class CompetencyLevelResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $code = null;
|
||||
|
||||
public ?string $name = null;
|
||||
|
||||
public ?string $color = null;
|
||||
|
||||
public int $sortOrder = 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\CompetencyCollectionProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Competency',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/competencies',
|
||||
provider: CompetencyCollectionProvider::class,
|
||||
name: 'get_competency_list',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class CompetencyResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $id = null;
|
||||
|
||||
public ?string $frameworkId = null;
|
||||
|
||||
public ?string $frameworkName = null;
|
||||
|
||||
public ?string $code = null;
|
||||
|
||||
public ?string $name = null;
|
||||
|
||||
public ?string $description = null;
|
||||
|
||||
public ?string $parentId = null;
|
||||
|
||||
public int $sortOrder = 0;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\SaveCompetencyResultsProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\CompetencyResultCollectionProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'CompetencyResult',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/evaluations/{evaluationId}/competency-results',
|
||||
uriVariables: ['evaluationId'],
|
||||
provider: CompetencyResultCollectionProvider::class,
|
||||
name: 'get_competency_result_list',
|
||||
),
|
||||
new Put(
|
||||
uriTemplate: '/evaluations/{evaluationId}/competency-results',
|
||||
uriVariables: ['evaluationId'],
|
||||
read: false,
|
||||
processor: SaveCompetencyResultsProcessor::class,
|
||||
name: 'save_competency_results',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class CompetencyResultResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $id = null;
|
||||
|
||||
public ?string $evaluationId = null;
|
||||
|
||||
public ?string $competencyEvaluationId = null;
|
||||
|
||||
public ?string $competencyId = null;
|
||||
|
||||
public ?string $competencyCode = null;
|
||||
|
||||
public ?string $competencyName = null;
|
||||
|
||||
public ?string $studentId = null;
|
||||
|
||||
public ?string $studentName = null;
|
||||
|
||||
public ?string $levelCode = null;
|
||||
|
||||
public ?string $levelName = null;
|
||||
|
||||
/** @var array<array{studentId: string, competencyEvaluationId: string, levelCode: string}>|null */
|
||||
public ?array $results = null;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Scolarite\Domain\Model\Evaluation\ClassStatistics;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\EvaluationStatisticsProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'EvaluationStatistics',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/evaluations/{evaluationId}/statistics',
|
||||
provider: EvaluationStatisticsProvider::class,
|
||||
name: 'get_evaluation_statistics',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class EvaluationStatisticsResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $evaluationId = null;
|
||||
|
||||
public ?float $average = null;
|
||||
|
||||
public ?float $min = null;
|
||||
|
||||
public ?float $max = null;
|
||||
|
||||
public ?float $median = null;
|
||||
|
||||
public int $gradedCount = 0;
|
||||
|
||||
public static function fromStatistics(string $evaluationId, ClassStatistics $statistics): self
|
||||
{
|
||||
$resource = new self();
|
||||
$resource->evaluationId = $evaluationId;
|
||||
$resource->average = $statistics->average;
|
||||
$resource->min = $statistics->min;
|
||||
$resource->max = $statistics->max;
|
||||
$resource->median = $statistics->median;
|
||||
$resource->gradedCount = $statistics->gradedCount;
|
||||
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use App\Scolarite\Domain\Model\Grade\Grade;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\PublishGradesProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\SaveAppreciationProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\SaveGradesProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\GradeCollectionProvider;
|
||||
use DateTimeImmutable;
|
||||
@@ -32,6 +33,12 @@ use DateTimeImmutable;
|
||||
processor: SaveGradesProcessor::class,
|
||||
name: 'save_evaluation_grades',
|
||||
),
|
||||
new Put(
|
||||
uriTemplate: '/grades/{id}/appreciation',
|
||||
read: false,
|
||||
processor: SaveAppreciationProcessor::class,
|
||||
name: 'save_grade_appreciation',
|
||||
),
|
||||
new Post(
|
||||
uriTemplate: '/evaluations/{evaluationId}/publish',
|
||||
uriVariables: ['evaluationId'],
|
||||
@@ -56,6 +63,8 @@ final class GradeResource
|
||||
|
||||
public ?string $status = null;
|
||||
|
||||
public ?string $appreciation = null;
|
||||
|
||||
public ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
public ?DateTimeImmutable $updatedAt = null;
|
||||
@@ -76,6 +85,7 @@ final class GradeResource
|
||||
$resource->studentName = $studentName;
|
||||
$resource->value = $grade->value?->value;
|
||||
$resource->status = $grade->status->value;
|
||||
$resource->appreciation = $grade->appreciation;
|
||||
$resource->createdAt = $grade->createdAt;
|
||||
$resource->updatedAt = $grade->updatedAt;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\StudentAveragesProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'StudentAverages',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/students/{studentId}/averages',
|
||||
provider: StudentAveragesProvider::class,
|
||||
name: 'get_student_averages',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class StudentAveragesResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $studentId = null;
|
||||
|
||||
public ?string $periodId = null;
|
||||
|
||||
/** @var list<array{subjectId: string, subjectName: string|null, average: float, gradeCount: int}> */
|
||||
public array $subjectAverages = [];
|
||||
|
||||
public ?float $generalAverage = null;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\StudentCompetencyProgressProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'StudentCompetencyProgress',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/students/{studentId}/competency-progress',
|
||||
uriVariables: ['studentId'],
|
||||
provider: StudentCompetencyProgressProvider::class,
|
||||
name: 'get_student_competency_progress',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class StudentCompetencyProgressResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $competencyId = null;
|
||||
|
||||
public ?string $competencyCode = null;
|
||||
|
||||
public ?string $competencyName = null;
|
||||
|
||||
public ?string $currentLevelCode = null;
|
||||
|
||||
public ?string $currentLevelName = null;
|
||||
|
||||
/** @var array<array{date: string, levelCode: string, evaluationTitle: string}> */
|
||||
public array $history = [];
|
||||
}
|
||||
Reference in New Issue
Block a user