Files
notytex/CLAUDE.md
2026-02-09 17:57:02 +01:00

398 lines
14 KiB
Markdown

# Notytex v2 - Système de Gestion Scolaire
**Notytex** est une application web moderne pour la gestion complète des évaluations scolaires. Version 2.0 entièrement réécrite avec **FastAPI** (backend) et **Vue.js 3** (frontend).
## Objectif Principal
Simplifier et digitaliser le processus d'évaluation scolaire, de la création des contrôles à la saisie des notes, en offrant une interface moderne, réactive et une API REST documentée.
---
## Architecture Technique
| Couche | Technologie |
|--------|-------------|
| **Backend** | FastAPI 0.115+ (Python 3.11-3.13) |
| **ORM** | SQLAlchemy 2.0.36+ avec aiosqlite (async) |
| **Validation** | Pydantic 2.10+ / pydantic-settings |
| **Serveur** | Uvicorn 0.32+ (ASGI) |
| **Frontend** | Vue.js 3.5+ (Composition API) + Vite 6.0+ |
| **State** | Pinia 2.2+ |
| **CSS** | TailwindCSS 3.4+ |
| **Graphiques** | Chart.js 4.4+ (vue-chartjs) |
| **HTTP client** | Axios 1.7+ |
| **Base de données** | SQLite (dev/prod), PostgreSQL possible |
| **Déploiement** | Docker / Podman avec Nginx |
| **Tests** | pytest + pytest-asyncio (99/99 tests) |
| **Qualité** | Black, Ruff, ESLint |
---
## Modèle de Données (12 tables)
### Entités principales (7)
```
ClassGroup (6ème A, 5ème B...)
↓ StudentEnrollment (inscription temporelle : arrivée/départ)
Student (élèves)
Assessment (évaluation, rattachée à un trimestre 1/2/3)
Exercise (exercices ordonnés)
GradingElement (question/compétence, type "notes" ou "score", domaine optionnel)
Grade (note individuelle par élève : valeur string + commentaire)
```
- **CouncilAppreciation** : appréciation de conseil de classe (élève + classe + trimestre, statut draft/finalized)
### Tables de configuration (5)
- **AppConfig** : clé-valeur (ex: `context.school_year`, `grading.special_values`)
- **CompetenceScaleValue** : échelle de notation (0=Non acquis → 3=Expert, couleurs, `.`, `d`, `a`)
- **Competence** : compétences pédagogiques (Calculer, Raisonner...) avec couleur et icône
- **Domain** : domaines/tags pour les éléments de notation
Tous les modèles sont dans `backend/infrastructure/database/models.py`.
---
## Fonctionnalités Clés
### Gestion des Classes et Élèves
- CRUD classes avec année scolaire
- Inscription temporelle (bitemporal) : dates d'arrivée/départ, transferts, historique
- Import CSV en masse (séparateur `;`, format "NOM Prénoms", gestion doublons)
- Statistiques par trimestre et par classe
### Système d'Évaluation
- Création unifiée (évaluation + exercices + barème en une fois)
- Organisation par trimestre (1, 2, 3)
- Filtrage avancé (trimestre, classe, statut de correction, tri)
- Indicateurs de progression : rouge (0%), orange (en cours), vert (100%)
### Notation Dual Configurable
**2 types de notation (par GradingElement) :**
1. **`notes`** : valeurs numériques décimales (ex: 15.5/20) — calcul direct
2. **`score`** : échelle fixe 0-3 — converti en points : `(value / 3) * max_points`
**Valeurs spéciales (configurables via interface) :**
- `.` = pas de réponse (compte comme 0 dans le total)
- `d` = dispensé (exclu des calculs)
- `a` = absent (compte comme 0)
### Analyse et Statistiques
- Statistiques descriptives (moyenne, médiane, écart-type, min/max, quartiles)
- Histogrammes de distribution (bins de 1 point, Chart.js)
- Heatmaps par compétences et par domaines
- Classement alphabétique avec scores par exercice
### Conseil de Classe
- Préparation automatique avec données consolidées par trimestre
- Saisie et historique des appréciations (brouillon → finalisé)
- Vue complète des performances par élève
### Envoi de Bilans par Email
- Génération automatique de bilans individualisés (HTML Jinja2)
- SMTP configurable (Gmail, Outlook, serveur perso)
- Envoi en lot avec rapport détaillé succès/erreurs
- Prévisualisation avant envoi
---
## Structure du Code
```
notytex/
├── backend/ # API FastAPI
│ ├── api/
│ │ ├── main.py # App FastAPI + CORS + lifespan
│ │ ├── dependencies.py # DI (AsyncSessionDep)
│ │ └── routes/
│ │ ├── assessments.py # CRUD évaluations + notation + résultats + email
│ │ ├── classes.py # CRUD classes + import CSV + stats dashboard
│ │ ├── students.py # CRUD élèves + inscriptions
│ │ ├── config.py # Configuration système + compétences + domaines
│ │ └── council.py # Appréciations de conseil de classe
│ │
│ ├── schemas/ # Modèles Pydantic (validation I/O)
│ │ ├── common.py # BaseSchema, PaginationParams
│ │ ├── assessment.py # AssessmentRead, AssessmentCreate...
│ │ ├── student.py # StudentRead, StudentCreate...
│ │ ├── grading.py # GradeRead, BulkGradeCreate
│ │ ├── class_group.py # ClassGroupRead, ClassDashboardStats
│ │ ├── config.py # ConfigRead, CompetenceCreate
│ │ ├── council.py # CouncilAppreciationRead
│ │ └── csv_import.py # CSVImportResponse
│ │
│ ├── domain/ # Logique métier pure (aucune dépendance framework)
│ │ ├── services/
│ │ │ ├── grading_calculator.py # Strategy Pattern (Notes/Score)
│ │ │ ├── statistics_service.py # Statistiques descriptives
│ │ │ ├── score_calculator.py # Calcul scores élèves
│ │ │ ├── config_service.py # Valeurs spéciales, signification scores
│ │ │ ├── student_report_service.py # Génération bilans email
│ │ │ └── class_statistics_service.py # Stats par classe
│ │ └── value_objects/
│ │ ├── progress.py # ProgressResult, ProgressStatus
│ │ ├── score.py # GradeValue, ExerciseScore, StudentScore
│ │ └── statistics.py # StatisticsResult, HistogramBin
│ │
│ ├── infrastructure/
│ │ ├── database/
│ │ │ ├── models.py # 12 modèles SQLAlchemy
│ │ │ └── session.py # AsyncSession factory
│ │ └── external/
│ │ └── email_service.py # Service SMTP
│ │
│ ├── core/
│ │ └── config.py # pydantic-settings (Settings)
│ │
│ ├── tests/ # 99 tests
│ │ ├── conftest.py # Fixtures (SQLite in-memory, AsyncClient)
│ │ ├── unit/ # Tests services domaine
│ │ ├── integration/ # Tests endpoints API
│ │ └── comparison/ # Tests parité v1 ↔ v2
│ │
│ ├── pyproject.toml # Dépendances + config Black/Ruff/pytest
│ └── uv.lock
├── frontend/ # SPA Vue.js 3
│ ├── src/
│ │ ├── main.js # Initialisation app
│ │ ├── App.vue # Composant racine
│ │ ├── router/index.js # 14 routes Vue Router
│ │ ├── stores/ # Pinia (classes, assessments, config, notifications)
│ │ ├── services/api.js # Instance Axios (/api/v2)
│ │ ├── views/ # 14 pages (Dashboard, Grading, Results...)
│ │ ├── components/ # 16+ composants réutilisables
│ │ └── assets/
│ ├── vite.config.js # Proxy dev /api → localhost:8000
│ ├── tailwind.config.js
│ └── package.json
├── docker/
│ ├── docker-compose.yaml # Production (ports 8080/8081)
│ ├── docker-compose.dev.yaml # Développement avec hot reload
│ └── .env.example # Template variables Docker
├── school_management.db # Base SQLite partagée
├── CLAUDE.md # Ce fichier
└── README.md # Documentation utilisateur
```
---
## Installation et Lancement
### Prérequis
- Python 3.11-3.13 avec [uv](https://docs.astral.sh/uv/)
- Node.js 22 LTS avec npm
- Git
### Backend
```bash
cd backend
uv sync --all-extras
uv run python -m uvicorn api.main:app --reload --port 8000
```
- API : http://localhost:8000/api/v2/docs (Swagger)
- ReDoc : http://localhost:8000/api/v2/redoc
### Frontend
```bash
cd frontend
npm install
npm run dev
```
- Application : http://localhost:3000 (proxy automatique `/api` vers le backend)
### Docker / Podman
```bash
cd docker
cp .env.example .env
# Modifier SECRET_KEY dans .env
docker compose up -d # ou podman-compose up -d
```
- Frontend : http://localhost:8081
- API : http://localhost:8080/api/v2/docs
---
## Tests
```bash
cd backend
# Tous les tests
uv run pytest tests/ -v
# Tests unitaires
uv run pytest tests/unit/ -v
# Tests de parité v1 ↔ v2
uv run pytest tests/comparison/ -v
# Avec couverture
uv run pytest tests/ --cov=. --cov-report=html
```
**Résultat actuel : 99/99 tests**
Les tests utilisent une base SQLite in-memory avec `AsyncClient` + `ASGITransport` pour tester les endpoints sans serveur.
---
## Configuration
### pydantic-settings (`backend/core/config.py`)
```python
class Settings(BaseSettings):
# Base de données
database_url: Optional[str] # URL complète SQLAlchemy async
database_path: Optional[str] # Chemin simple vers le .db
# API
api_v2_prefix: str = "/api/v2"
# CORS
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:5173"]
# Logging
log_level: str = "INFO"
# Email (optionnel)
smtp_server, smtp_port, smtp_username, smtp_password, email_from
model_config = SettingsConfigDict(env_file=".env")
```
Toute la config est lue depuis `backend/.env` et validée par Pydantic au démarrage.
### Configuration dynamique (table AppConfig)
Paramètres modifiables à chaud via l'interface web :
- `context.school_year` — année scolaire en cours
- `grading.special_values` — valeurs spéciales (`.`, `d`, `a`)
- `grading.score_meanings` — signification des scores 0-3
- Configuration SMTP pour l'envoi d'emails
---
## Patterns Architecturaux
### Strategy Pattern — Calcul de notation
`domain/services/grading_calculator.py` : `GradingStrategy` (ABC) avec `NotesStrategy` et `ScoreStrategy`, sélectionnées par `GradingStrategyFactory` selon le `grading_type` du GradingElement.
### Value Objects — Objets immuables du domaine
`domain/value_objects/` : `ProgressResult`, `GradeValue`, `StudentScore`, `StatisticsResult`, `HistogramBin`. Représentent des résultats de calcul sans identité propre.
### Dependency Injection — FastAPI natif
```python
AsyncSessionDep = Annotated[AsyncSession, Depends(get_async_session)]
```
Injection de la session DB dans chaque route, facilitant le remplacement en tests.
### Service Layer — Logique métier découplée
Les services dans `domain/services/` n'ont aucune dépendance sur FastAPI ou SQLAlchemy. Ils reçoivent des données brutes et retournent des Value Objects.
### Clean Architecture — Séparation en couches
`api/` (routes) → `schemas/` (validation) → `domain/` (métier) → `infrastructure/` (DB, email). Les dépendances pointent toujours vers l'intérieur.
---
## Conventions de Code
### Backend (Python)
- **Formatage** : `black` (line-length 88, target py311)
- **Linting** : `ruff` (pycodestyle, pyflakes, isort, bugbear, comprehensions)
- **Types** : `mypy` recommandé pour les nouvelles fonctions
- **Async** : toutes les routes et accès DB sont `async/await`
- **Noms** : snake_case, noms explicites (`calculate_student_scores`, pas `calc`)
### Frontend (JavaScript/Vue)
- **Composition API** avec `<script setup>`
- **Pinia** stores en style composition (pas Options API)
- **TailwindCSS** pour le styling (pas de CSS custom sauf nécessité)
- **Axios** via `services/api.js` pour tous les appels API
### Base de données
- Noms de tables : pluriel anglais (`students`, `assessments`)
- Relations avec `relationship()` et cascade explicite
- Modèles identiques à v1 pour compatibilité de la base partagée
---
## Volumétrie (milieu d'année)
- 5 classes de 25 à 35 élèves
- 10 à 20 éléments de notation par évaluation
- 4 évaluations corrigées par classe pour le 1er trimestre
- 2 évaluations non corrigées ou partiellement corrigées par classe
---
## Public Cible
- Enseignants du secondaire (collège/lycée)
- Établissements souhaitant digitaliser leurs évaluations
- Contexte de coexistence notation classique et évaluation par compétences
---
## Workflow Développeur
### Ajouter un endpoint API
1. Créer le schéma Pydantic dans `backend/schemas/`
2. Ajouter la route dans `backend/api/routes/`
3. Utiliser `AsyncSessionDep` pour l'accès DB
4. Écrire les tests dans `backend/tests/`
### Ajouter une page frontend
1. Créer le composant Vue dans `frontend/src/views/`
2. Ajouter la route dans `frontend/src/router/index.js`
3. Créer un store Pinia si nécessaire dans `frontend/src/stores/`
4. Réutiliser les composants existants de `frontend/src/components/`
### Modifier la logique de notation
1. Modifier `backend/domain/services/grading_calculator.py`
2. Mettre à jour les tests dans `backend/tests/unit/test_grading_calculator.py`
3. Lancer les tests de parité (`tests/comparison/`) pour vérifier la compatibilité v1
### Débogage
```bash
# Backend avec rechargement auto
cd backend && uv run python -m uvicorn api.main:app --reload --port 8000
# Inspecter la base
sqlite3 school_management.db ".schema assessment"
# Console interactive
cd backend && uv run python -c "from infrastructure.database.models import *; print('OK')"
```