Files
notytex/REWRITE.md

68 KiB

Réécriture Notytex : Flask → FastAPI + Vue.js

Table des Matières

  1. Contexte et Motivations
  2. Objectifs de la Réécriture
  3. Architecture Cible
  4. Stratégie de Compatibilité
  5. Jalons de Validation
  6. Planning et Estimation
  7. Scripts de Validation
  8. Critères de Succès

Contexte et Motivations

État Actuel de l'Application

Notytex est une application Flask de gestion scolaire développée rapidement pour répondre à des besoins immédiats. Bien que fonctionnelle, l'application présente plusieurs problèmes architecturaux :

Points forts existants :

  • Modèle de données mature et bien structuré (SQLAlchemy)
  • Règles métier complexes fonctionnelles (calcul de notes, gestion temporelle des élèves)
  • Services partiellement découplés (Repository Pattern, Services métier)
  • Couverture de tests correcte (100 tests)

Problèmes identifiés :

  • Mélange des responsabilités : Logique métier dans les templates Jinja2 et les routes
  • Couplage fort : Dépendances circulaires nécessitant des imports conditionnels
  • JavaScript fragmenté : Mélange de code legacy et modules ES6
  • Pas d'API REST pure : Endpoints mixtes HTML/JSON
  • Scalabilité limitée : Architecture monolithique sans séparation claire
  • Performance : Requêtes synchrones, pas d'async

Pourquoi Réécrire ?

  1. Maintenabilité : Le code actuel devient difficile à maintenir et à faire évoluer
  2. Séparation des préoccupations : Backend API pur + Frontend SPA moderne
  3. Performance : FastAPI avec support async natif
  4. Documentation API : OpenAPI/Swagger automatique
  5. Typage fort : Pydantic pour la validation des données
  6. Expérience développeur : Hot reload, meilleur debugging, tests plus simples

Objectifs de la Réécriture

Objectifs Techniques

  1. Migrer le backend de Flask vers FastAPI

    • API REST JSON pure
    • Support async/await
    • Validation Pydantic
    • Documentation OpenAPI automatique
  2. Créer un frontend Vue.js 3 séparé

    • Single Page Application (SPA)
    • State management avec Pinia
    • Composants réutilisables
    • Build moderne avec Vite
  3. Conserver le modèle de données intact

    • Même schéma SQLAlchemy
    • Base de données partagée entre v1 et v2
    • Pas de migration de données
  4. Garantir la parité fonctionnelle

    • Mêmes règles métier
    • Mêmes calculs (notes, statistiques)
    • Mêmes vues et workflows

Objectifs de Qualité

  • Tests de comparaison automatisés v1 ↔ v2
  • Couverture de tests > 80%
  • Documentation API complète
  • Performance égale ou supérieure
  • Rollback possible à tout moment

Contraintes

  • Base de données partagée : v1 et v2 doivent pouvoir fonctionner sur la même DB
  • Pas de régression : Tous les calculs doivent produire les mêmes résultats
  • Migration progressive : Possibilité de basculer progressivement

Architecture Cible

Structure du Projet

notytex-v2/
├── backend/
│   ├── api/
│   │   ├── __init__.py
│   │   ├── main.py                 # Application FastAPI
│   │   ├── dependencies.py         # Injection de dépendances
│   │   └── routes/
│   │       ├── __init__.py
│   │       ├── assessments.py      # /api/v2/assessments
│   │       ├── classes.py          # /api/v2/classes
│   │       ├── grading.py          # /api/v2/grading
│   │       ├── students.py         # /api/v2/students
│   │       ├── council.py          # /api/v2/council
│   │       └── config.py           # /api/v2/config
│   │
│   ├── schemas/                    # Modèles Pydantic (validation I/O)
│   │   ├── __init__.py
│   │   ├── assessment.py
│   │   ├── student.py
│   │   ├── grading.py
│   │   ├── class_group.py
│   │   └── common.py
│   │
│   ├── domain/                     # Règles métier pures (sans dépendances framework)
│   │   ├── __init__.py
│   │   ├── services/
│   │   │   ├── __init__.py
│   │   │   ├── grading_calculator.py
│   │   │   ├── statistics_service.py
│   │   │   ├── temporal_service.py
│   │   │   ├── council_service.py
│   │   │   └── config_service.py
│   │   └── value_objects/
│   │       ├── __init__.py
│   │       ├── progress.py
│   │       ├── score.py
│   │       └── statistics.py
│   │
│   ├── infrastructure/
│   │   ├── __init__.py
│   │   ├── database/
│   │   │   ├── __init__.py
│   │   │   ├── models.py           # SQLAlchemy (IDENTIQUE à v1)
│   │   │   ├── session.py          # Async session management
│   │   │   └── repositories/
│   │   │       ├── __init__.py
│   │   │       ├── base.py
│   │   │       ├── assessment_repository.py
│   │   │       ├── student_repository.py
│   │   │       ├── class_repository.py
│   │   │       └── grade_repository.py
│   │   └── external/
│   │       ├── __init__.py
│   │       └── email_service.py
│   │
│   ├── core/
│   │   ├── __init__.py
│   │   ├── config.py               # Settings avec pydantic-settings
│   │   └── logging.py
│   │
│   └── tests/
│       ├── __init__.py
│       ├── conftest.py
│       ├── unit/
│       ├── integration/
│       └── comparison/             # Tests de comparaison v1/v2
│
├── frontend/
│   ├── src/
│   │   ├── main.js
│   │   ├── App.vue
│   │   ├── router/
│   │   │   └── index.js
│   │   ├── stores/                 # Pinia stores
│   │   │   ├── assessments.js
│   │   │   ├── classes.js
│   │   │   ├── students.js
│   │   │   └── config.js
│   │   ├── services/               # Clients API
│   │   │   ├── api.js              # Client HTTP de base
│   │   │   ├── assessments.js
│   │   │   ├── classes.js
│   │   │   └── grading.js
│   │   ├── components/
│   │   │   ├── common/
│   │   │   ├── assessment/
│   │   │   ├── class/
│   │   │   └── grading/
│   │   ├── views/
│   │   │   ├── DashboardView.vue
│   │   │   ├── AssessmentListView.vue
│   │   │   ├── AssessmentDetailView.vue
│   │   │   ├── AssessmentFormView.vue
│   │   │   ├── GradingView.vue
│   │   │   ├── ResultsView.vue
│   │   │   ├── ClassListView.vue
│   │   │   ├── ClassDashboardView.vue
│   │   │   ├── StudentListView.vue
│   │   │   └── ConfigView.vue
│   │   └── utils/
│   ├── public/
│   ├── index.html
│   ├── vite.config.js
│   ├── tailwind.config.js
│   └── package.json
│
├── validation/                     # Scripts de comparaison v1/v2
│   ├── compare_all.py
│   ├── compare_endpoints.py
│   ├── compare_calculations.py
│   └── generate_report.py
│
├── docker-compose.yml
├── pyproject.toml
└── README.md

Stack Technologique

Backend :

  • FastAPI 0.100+
  • SQLAlchemy 2.0+ (async)
  • Pydantic 2.0+
  • Uvicorn (ASGI server)
  • Python 3.11+

Frontend :

  • Vue.js 3.4+
  • Vue Router 4
  • Pinia (state management)
  • Axios (HTTP client)
  • TailwindCSS 3
  • Chart.js 4
  • Vite (build tool)

Tests :

  • pytest + pytest-asyncio
  • httpx (client de test async)
  • Cypress (tests E2E frontend)

Stratégie de Compatibilité

Base de Données Partagée

Le principe fondamental est que v1 et v2 partagent exactement la même base de données sans aucune modification de schéma.

Fichier models.py Identique

# backend/infrastructure/database/models.py
# Ce fichier est une COPIE EXACTE de l'ancien models.py (sans la logique métier)

from sqlalchemy import Column, Integer, String, Float, Date, Text, ForeignKey
from sqlalchemy.orm import relationship, declarative_base

Base = declarative_base()

class ClassGroup(Base):
    __tablename__ = 'class_group'
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    description = Column(Text)
    year = Column(String(20), nullable=False)
    # Relations identiques
    students = relationship('Student', back_populates='class_group')
    assessments = relationship('Assessment', back_populates='class_group')

# Tous les autres modèles IDENTIQUES...

Configuration Partagée

# Les deux versions pointent vers la même base
# v1/.env
DATABASE_URL=sqlite:///school_management.db

# v2/.env
DATABASE_URL=sqlite:///school_management.db

Coexistence v1/v2

┌─────────────────────────────────────────────────────┐
│                   Base de Données                   │
│               school_management.db                  │
└─────────────────────┬───────────────────────────────┘
                      │
         ┌────────────┴────────────┐
         │                         │
         ▼                         ▼
┌─────────────────┐       ┌─────────────────┐
│   Flask v1      │       │  FastAPI v2     │
│   Port 5000     │       │   Port 8000     │
│   (Production)  │       │   (Staging)     │
└─────────────────┘       └────────┬────────┘
                                   │
                                   ▼
                          ┌─────────────────┐
                          │   Vue.js SPA    │
                          │   Port 3000     │
                          └─────────────────┘

Tests de Non-Régression

Chaque donnée écrite par v2 doit être lisible par v1 et vice-versa :

# Exemple de test
def test_cross_version_compatibility():
    # Créer une évaluation via v2
    response = httpx.post("http://localhost:8000/api/v2/assessments", json=data)
    assessment_id = response.json()['id']
    
    # Vérifier que v1 peut la lire
    response = httpx.get(f"http://localhost:5000/assessments/{assessment_id}")
    assert response.status_code == 200
    assert response.json()['title'] == data['title']

Jalons de Validation

Jalon 0 : Infrastructure

Durée estimée : 3-4 jours

Objectif

Mettre en place la structure FastAPI et vérifier la connexion à la base de données existante.

Tâches

  • Créer la structure de répertoires backend
  • Configurer FastAPI avec uvicorn
  • Copier models.py sans la logique métier
  • Configurer la session async SQLAlchemy
  • Créer un endpoint de santé /api/v2/health
  • Vérifier la lecture des données existantes

Livrables

  • Application FastAPI démarrable
  • Connexion à school_management.db fonctionnelle
  • Tests de lecture des tables existantes passants

Critères de Validation

# Démarrer l'API v2
uvicorn backend.api.main:app --port 8000

# Tester la connexion
curl http://localhost:8000/api/v2/health

# Réponse attendue
{
  "status": "healthy",
  "database": "connected",
  "tables": 12,
  "classes": 5,
  "students": 127,
  "assessments": 28
}

Jalon 1 : API Lecture (Read-Only)

Durée estimée : 1 semaine

Objectif

Implémenter tous les endpoints GET et valider qu'ils retournent les mêmes données que v1.

Endpoints à Implémenter

# Classes
GET /api/v2/classes
GET /api/v2/classes/{id}
GET /api/v2/classes/{id}/students
GET /api/v2/classes/{id}/stats
GET /api/v2/classes/{id}/council

# Élèves
GET /api/v2/students
GET /api/v2/students/{id}

# Évaluations
GET /api/v2/assessments
GET /api/v2/assessments/{id}
GET /api/v2/assessments/{id}/results
GET /api/v2/assessments/{id}/statistics
GET /api/v2/assessments/{id}/grades

# Configuration
GET /api/v2/config
GET /api/v2/config/competences
GET /api/v2/config/domains
GET /api/v2/config/scale

Tâches

  • Créer les schemas Pydantic pour chaque réponse
  • Implémenter les repositories de lecture
  • Implémenter chaque route GET
  • Écrire les tests de comparaison

Script de Comparaison

# validation/compare_read_endpoints.py

ENDPOINTS_TO_COMPARE = [
    ("/classes", "/api/v2/classes"),
    ("/classes/1", "/api/v2/classes/1"),
    ("/classes/1/students", "/api/v2/classes/1/students"),
    ("/assessments", "/api/v2/assessments"),
    ("/assessments/5/results", "/api/v2/assessments/5/results"),
]

def compare_responses(v1_url, v2_url):
    v1_response = requests.get(f"http://localhost:5000{v1_url}")
    v2_response = requests.get(f"http://localhost:8000{v2_url}")
    
    return deep_compare(v1_response.json(), v2_response.json())

Critères de Validation

┌─────────────────────────────────────────────────────────────┐
│ COMPARAISON API v1 vs v2 - Lecture                          │
├─────────────────────────────────────────────────────────────┤
│ GET /classes                    │ ✅ IDENTIQUE (5 classes)   │
│ GET /classes/1                  │ ✅ IDENTIQUE               │
│ GET /classes/1/students         │ ✅ IDENTIQUE (28 élèves)   │
│ GET /classes/1/stats            │ ✅ IDENTIQUE               │
│ GET /students                   │ ✅ IDENTIQUE (127 élèves)  │
│ GET /assessments                │ ✅ IDENTIQUE (28 évals)    │
│ GET /assessments/5/results      │ ✅ IDENTIQUE               │
│ GET /assessments/5/statistics   │ ✅ IDENTIQUE               │
│ GET /config/competences         │ ✅ IDENTIQUE (6 items)     │
│ GET /config/domains             │ ✅ IDENTIQUE               │
└─────────────────────────────────────────────────────────────┘
Total: 15/15 endpoints validés ✅

Jalon 2 : Calculs Métier

Durée estimée : 1-2 semaines

Objectif

Migrer les services de calcul et valider que les résultats sont identiques à v1.

Services à Migrer

  1. GradingCalculator (domain/services/grading_calculator.py)

    • calculate_score(value, grading_type, max_points)
    • Gestion des valeurs spéciales (., d, a)
    • Strategy Pattern pour notes/score
  2. StudentScoreCalculator (domain/services/statistics_service.py)

    • calculate_student_scores(assessment_id)
    • Calcul par exercice et total
  3. AssessmentStatisticsService (domain/services/statistics_service.py)

    • get_statistics(assessment_id)
    • Moyenne, médiane, écart-type, min, max
  4. TemporalStudentService (domain/services/temporal_service.py)

    • get_eligible_students(class_id, date)
    • Vérification des inscriptions/départs
  5. ClassStatisticsService (domain/services/statistics_service.py)

    • get_trimester_statistics(class_id, trimester)
    • get_class_results(class_id, trimester)

Tâches

  • Extraire la logique de services/assessment_services.py
  • Créer les value objects (ProgressResult, StudentScore, etc.)
  • Implémenter les services sans dépendances Flask
  • Écrire les tests unitaires
  • Écrire les tests de comparaison avec v1

Tests de Comparaison

# validation/compare_calculations.py

def test_grading_calculator_parity():
    """Vérifie que les calculs de notes sont identiques"""
    test_cases = [
        ("15.5", "notes", 20, 15.5),
        ("2", "score", 3, 2.0),
        (".", "notes", 10, 0),
        ("d", "score", 3, None),
        ("a", "notes", 5, 0),
    ]
    
    for value, g_type, max_pts, expected in test_cases:
        v1_result = call_v1_calculator(value, g_type, max_pts)
        v2_result = call_v2_calculator(value, g_type, max_pts)
        assert v1_result == v2_result == expected

def test_assessment_results_parity():
    """Vérifie que les résultats d'évaluation sont identiques"""
    for assessment_id in [1, 5, 10, 15, 20]:
        v1_results = get_v1_results(assessment_id)
        v2_results = get_v2_results(assessment_id)
        
        for student_id in v1_results['students']:
            v1_score = v1_results['students'][student_id]['total_score']
            v2_score = v2_results['students'][student_id]['total_score']
            assert abs(v1_score - v2_score) < 0.01

def test_statistics_parity():
    """Vérifie que les statistiques sont identiques"""
    for assessment_id in [1, 5, 10, 15, 20]:
        v1_stats = get_v1_statistics(assessment_id)
        v2_stats = get_v2_statistics(assessment_id)
        
        assert v1_stats['mean'] == pytest.approx(v2_stats['mean'], abs=0.01)
        assert v1_stats['median'] == v2_stats['median']
        assert v1_stats['std_dev'] == pytest.approx(v2_stats['std_dev'], abs=0.01)

Critères de Validation

pytest validation/compare_calculations.py -v

# Résultat attendu
test_grading_calculator_parity PASSED
test_assessment_results_parity PASSED
test_statistics_parity PASSED
test_temporal_eligibility_parity PASSED
test_class_statistics_parity PASSED

========================= 5 passed in 3.42s =========================

Jalon 3 : API Écriture (CRUD Complet)

Durée estimée : 1-2 semaines

Objectif

Implémenter tous les endpoints d'écriture et vérifier la compatibilité bidirectionnelle.

Endpoints à Implémenter

# Classes
POST   /api/v2/classes
PUT    /api/v2/classes/{id}
DELETE /api/v2/classes/{id}

# Élèves
POST   /api/v2/students
PUT    /api/v2/students/{id}
DELETE /api/v2/students/{id}
POST   /api/v2/students/enroll
POST   /api/v2/students/transfer
POST   /api/v2/students/departure

# Évaluations
POST   /api/v2/assessments
PUT    /api/v2/assessments/{id}
DELETE /api/v2/assessments/{id}

# Notation
POST   /api/v2/assessments/{id}/grades
PUT    /api/v2/assessments/{id}/grades/{student_id}/{element_id}

# Import/Export
POST   /api/v2/classes/{id}/import-csv
POST   /api/v2/assessments/{id}/send-reports

# Conseil de classe
POST   /api/v2/classes/{id}/council/appreciations/{student_id}

# Configuration
PUT    /api/v2/config
PUT    /api/v2/config/competences/{id}
PUT    /api/v2/config/domains/{id}
PUT    /api/v2/config/scale

Tâches

  • Créer les schemas Pydantic pour les requêtes
  • Implémenter les repositories d'écriture
  • Implémenter chaque route POST/PUT/DELETE
  • Gérer les transactions et rollbacks
  • Écrire les tests de compatibilité croisée

Tests de Compatibilité Croisée

# validation/compare_write_endpoints.py

def test_create_via_v2_read_via_v1():
    """Créer via v2, lire via v1"""
    # Créer une évaluation via v2
    data = {
        "title": "Test Cross-Version",
        "date": "2025-01-15",
        "trimester": 2,
        "class_group_id": 1,
        "coefficient": 1.0,
        "exercises": [...]
    }
    r2 = httpx.post("http://localhost:8000/api/v2/assessments", json=data)
    assessment_id = r2.json()['id']
    
    # Lire via v1
    r1 = requests.get(f"http://localhost:5000/assessments/{assessment_id}")
    assert r1.status_code == 200
    assert r1.json()['title'] == "Test Cross-Version"

def test_create_via_v1_read_via_v2():
    """Créer via v1, lire via v2"""
    # (Tester dans l'autre sens)

def test_grades_cross_version():
    """Sauvegarder des notes via v2, vérifier via v1"""
    grades_data = {
        "grades": [
            {"student_id": 1, "element_id": 10, "value": "15.5"},
            {"student_id": 2, "element_id": 10, "value": "12"},
        ]
    }
    
    # Sauvegarder via v2
    r2 = httpx.post(f"http://localhost:8000/api/v2/assessments/5/grades", json=grades_data)
    assert r2.status_code == 200
    
    # Vérifier que v1 voit les notes
    r1 = requests.get("http://localhost:5000/assessments/5/grading")
    # Parser le HTML et vérifier les valeurs...

Critères de Validation

┌─────────────────────────────────────────────────────────────┐
│ COMPARAISON API v1 vs v2 - Écriture                         │
├─────────────────────────────────────────────────────────────┤
│ POST /assessments               │ ✅ Créé, lisible par v1   │
│ PUT /assessments/{id}           │ ✅ Modifié, lisible par v1│
│ DELETE /assessments/{id}        │ ✅ Supprimé des 2 côtés   │
│ POST /grades                    │ ✅ Notes synchronisées    │
│ POST /students                  │ ✅ Élève visible v1       │
│ POST /students/enroll           │ ✅ Inscription visible v1 │
│ POST /students/transfer         │ ✅ Transfert fonctionnel  │
│ POST /import-csv                │ ✅ Import identique       │
└─────────────────────────────────────────────────────────────┘
Total: 12/12 tests passés ✅

Jalon 4 : Parité Fonctionnelle Complète

Durée estimée : 1 semaine

Objectif

Vérifier que 100% des fonctionnalités v1 sont disponibles en v2.

Checklist Fonctionnelle

## Gestion des Classes
- [ ] Liste des classes avec pagination
- [ ] Création d'une classe
- [ ] Modification d'une classe
- [ ] Suppression d'une classe
- [ ] Dashboard avec statistiques par trimestre
- [ ] Histogramme des moyennes
- [ ] Analyse par domaines
- [ ] Analyse par compétences

## Gestion des Élèves
- [ ] Liste globale avec recherche
- [ ] Liste par classe
- [ ] Création d'un élève
- [ ] Modification d'un élève
- [ ] Inscription dans une classe
- [ ] Transfert entre classes
- [ ] Départ d'une classe
- [ ] Import CSV
- [ ] Historique des mouvements

## Évaluations
- [ ] Liste avec filtres (trimestre, classe, correction)
- [ ] Tri par date, titre, classe
- [ ] Création unifiée (éval + exercices + éléments)
- [ ] Modification d'une évaluation
- [ ] Suppression avec cascade
- [ ] Indicateur de progression de correction
- [ ] Page de notation avec grille
- [ ] Sauvegarde incrémentale des notes
- [ ] Navigation clavier dans la grille
- [ ] Page de résultats avec statistiques
- [ ] Histogramme de distribution
- [ ] Tableau des scores par élève
- [ ] Heatmap par compétences
- [ ] Heatmap par domaines
- [ ] Envoi de bilans par email

## Conseil de Classe
- [ ] Vue de préparation avec données consolidées
- [ ] Statistiques par élève
- [ ] Saisie des appréciations
- [ ] Historique des appréciations

## Configuration
- [ ] Modification de l'année scolaire
- [ ] Gestion de l'échelle de compétences (0-3)
- [ ] Gestion des valeurs spéciales (., d, a)
- [ ] Liste des compétences (CRUD)
- [ ] Liste des domaines (CRUD)
- [ ] Configuration SMTP email

Tests de Parité

# validation/test_feature_parity.py

@pytest.mark.parametrize("feature,test_func", [
    ("class_list", test_class_list_parity),
    ("class_stats", test_class_stats_parity),
    ("student_enrollment", test_enrollment_parity),
    ("assessment_creation", test_assessment_creation_parity),
    ("grading_workflow", test_grading_workflow_parity),
    ("results_display", test_results_parity),
    ("council_preparation", test_council_parity),
    ("config_management", test_config_parity),
])
def test_feature(feature, test_func):
    """Test paramétré pour chaque fonctionnalité"""
    result = test_func()
    assert result.passed, f"{feature}: {result.message}"

Critères de Validation

┌─────────────────────────────────────────────────────────────┐
│              RAPPORT DE PARITÉ v1 ↔ v2                      │
├─────────────────────────────────────────────────────────────┤
│ Module               │ Fonctionnalités │ Statut            │
├─────────────────────────────────────────────────────────────┤
│ Classes              │ 8/8             │ ✅ 100%            │
│ Élèves               │ 9/9             │ ✅ 100%            │
│ Évaluations          │ 15/15           │ ✅ 100%            │
│ Notation             │ 6/6             │ ✅ 100%            │
│ Conseil              │ 4/4             │ ✅ 100%            │
│ Configuration        │ 6/6             │ ✅ 100%            │
├─────────────────────────────────────────────────────────────┤
│ TOTAL                │ 48/48           │ ✅ 100%            │
└─────────────────────────────────────────────────────────────┘

✅ Prêt pour développement frontend

Jalon 5 : Frontend Vue.js

Durée estimée : 3-4 semaines

Objectif

Créer une SPA Vue.js complète qui consomme l'API v2.

Setup Initial

# Créer le projet Vue
npm create vue@latest frontend
cd frontend
npm install

# Dépendances
npm install vue-router@4 pinia axios
npm install -D tailwindcss postcss autoprefixer
npm install chart.js vue-chartjs

Configuration

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:8000'
    }
  }
})

Structure des Composants

src/
├── components/
│   ├── common/
│   │   ├── AppHeader.vue
│   │   ├── AppFooter.vue
│   │   ├── LoadingSpinner.vue
│   │   ├── Modal.vue
│   │   ├── Notification.vue
│   │   ├── Pagination.vue
│   │   └── FilterBar.vue
│   ├── assessment/
│   │   ├── AssessmentCard.vue
│   │   ├── AssessmentForm.vue
│   │   ├── ExerciseBuilder.vue
│   │   ├── ProgressIndicator.vue
│   │   └── StatisticsCard.vue
│   ├── class/
│   │   ├── ClassCard.vue
│   │   ├── TrimesterNav.vue
│   │   ├── StatsOverview.vue
│   │   └── Histogram.vue
│   ├── grading/
│   │   ├── GradingGrid.vue
│   │   ├── GradeInput.vue
│   │   └── StudentRow.vue
│   └── student/
│       ├── StudentList.vue
│       ├── StudentForm.vue
│       └── EnrollmentHistory.vue
└── views/
    ├── DashboardView.vue
    ├── ClassListView.vue
    ├── ClassDashboardView.vue
    ├── ClassStudentsView.vue
    ├── StudentListView.vue
    ├── AssessmentListView.vue
    ├── AssessmentDetailView.vue
    ├── AssessmentFormView.vue
    ├── GradingView.vue
    ├── ResultsView.vue
    ├── CouncilView.vue
    └── ConfigView.vue

Mapping Routes

Route Vue API Endpoints
/ DashboardView GET /stats
/classes ClassListView GET /classes
/classes/:id ClassDashboardView GET /classes/:id/stats
/classes/:id/students ClassStudentsView GET /classes/:id/students
/students StudentListView GET /students
/assessments AssessmentListView GET /assessments
/assessments/new AssessmentFormView POST /assessments
/assessments/:id AssessmentDetailView GET /assessments/:id
/assessments/:id/edit AssessmentFormView PUT /assessments/:id
/assessments/:id/grading GradingView GET/POST /grades
/assessments/:id/results ResultsView GET /results
/classes/:id/council CouncilView GET /council
/config ConfigView GET/PUT /config

Tâches par Semaine

Semaine 1 : Foundation

  • Setup projet Vue + Vite + Tailwind
  • Configuration router et stores Pinia
  • Client API avec intercepteurs
  • Composants communs (Header, Footer, Modal, etc.)
  • Dashboard et navigation

Semaine 2 : Gestion Classes/Élèves

  • Liste des classes
  • Dashboard de classe avec graphiques
  • Liste des élèves
  • Import CSV
  • Formulaires CRUD

Semaine 3 : Évaluations

  • Liste avec filtres
  • Formulaire de création unifié
  • Page de notation (grille interactive)
  • Page de résultats avec graphiques

Semaine 4 : Finitions

  • Conseil de classe
  • Configuration
  • Tests E2E
  • Responsive design
  • Optimisations performance

Tests E2E

// cypress/e2e/assessment.cy.js
describe('Assessment Workflow', () => {
  it('creates assessment and grades students', () => {
    // Créer une évaluation
    cy.visit('/assessments/new')
    cy.get('[data-testid="title"]').type('Test E2E')
    cy.get('[data-testid="submit"]').click()
    
    // Aller à la notation
    cy.get('[data-testid="grade-button"]').click()
    
    // Saisir des notes
    cy.get('[data-testid="grade-input-1-1"]').type('15')
    cy.get('[data-testid="grade-input-1-2"]').type('2')
    
    // Vérifier la sauvegarde
    cy.contains('Notes enregistrées')
    
    // Vérifier les résultats
    cy.get('[data-testid="results-button"]').click()
    cy.contains('Moyenne')
  })
})

Critères de Validation

# Tests unitaires Vue
npm run test:unit

# Tests E2E
npm run test:e2e

# Résultat attendu
┌─────────────────────────────────────────────────────────────┐
│ Tests Frontend Vue.js                                       │
├─────────────────────────────────────────────────────────────┤
│ ✅ Navigation entre pages                                    │
│ ✅ Affichage liste classes                                   │
│ ✅ Dashboard classe avec statistiques                        │
│ ✅ Filtres évaluations fonctionnels                          │
│ ✅ Création évaluation complète                              │
│ ✅ Saisie notes avec sauvegarde auto                         │
│ ✅ Affichage résultats avec graphiques                       │
│ ✅ Import CSV élèves                                         │
│ ✅ Configuration modifiable                                  │
│ ✅ Responsive mobile/tablet/desktop                          │
└─────────────────────────────────────────────────────────────┘

Jalon 6 : Déploiement et Bascule

Durée estimée : 1 semaine

Objectif

Mettre en production la v2 et décommissionner la v1.

Stratégie de Bascule Progressive

Étape 1: Déploiement parallèle (Jour 1-2)
─────────────────────────────────────────
- v1 Flask sur port 5000 (production actuelle)
- v2 FastAPI + Vue sur port 8000/3000 (staging)
- Tests de charge sur v2

Étape 2: Bascule DNS avec rollback (Jour 3)
─────────────────────────────────────────
- notytex.example.com → v2 FastAPI + Vue
- notytex-legacy.example.com → v1 Flask
- Monitoring intensif

Étape 3: Période de stabilisation (Jour 4-7)
─────────────────────────────────────────
- Surveillance des erreurs
- Feedback utilisateurs
- Corrections rapides si nécessaire
- v1 maintenue en standby

Étape 4: Décommissionnement v1 (Après validation)
─────────────────────────────────────────
- Archive du code Flask
- Suppression des routes /api/v1
- Documentation mise à jour

Configuration Docker

# docker-compose.yml
version: '3.8'

services:
  api:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=sqlite:///data/school_management.db
    volumes:
      - ./data:/app/data

  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - api

  # Legacy (pour rollback)
  legacy:
    build: ./notytex-v1
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=sqlite:///data/school_management.db
    volumes:
      - ./data:/app/data
    profiles:
      - legacy

Tests de Production

# validation/test_production.py

def test_performance():
    """v2 ne doit pas être plus lent que v1"""
    endpoints = [
        "/classes/1/stats",
        "/assessments/5/results",
    ]
    
    for endpoint in endpoints:
        v1_time = measure_time(f"http://localhost:5000{endpoint}")
        v2_time = measure_time(f"http://localhost:8000/api/v2{endpoint}")
        
        assert v2_time < v1_time * 1.2, f"{endpoint}: v2 trop lent"

def test_concurrent_load():
    """Test de charge avec 50 requêtes simultanées"""
    with ThreadPoolExecutor(max_workers=50) as executor:
        futures = [executor.submit(request_api) for _ in range(50)]
        results = [f.result() for f in futures]
    
    success_rate = sum(1 for r in results if r.ok) / len(results)
    assert success_rate > 0.99

def test_error_handling():
    """Les erreurs sont bien gérées"""
    # 404
    r = httpx.get("http://localhost:8000/api/v2/assessments/99999")
    assert r.status_code == 404
    assert "detail" in r.json()
    
    # 400 validation
    r = httpx.post("http://localhost:8000/api/v2/assessments", json={})
    assert r.status_code == 422

Critères de Validation Finale

┌─────────────────────────────────────────────────────────────┐
│              VALIDATION PRODUCTION v2                       │
├─────────────────────────────────────────────────────────────┤
│ Tests de performance              │ ✅ v2 aussi rapide      │
│ Tests de charge (50 concurrent)   │ ✅ 100% succès          │
│ Tests d'erreurs                   │ ✅ Bien gérées          │
│ Compatibilité navigateurs         │ ✅ Chrome/Firefox/Safari│
│ Responsive design                 │ ✅ Mobile/Tablet/Desktop│
│ Accessibilité (a11y)              │ ✅ WCAG 2.1 AA          │
│ Documentation API                 │ ✅ OpenAPI complète     │
│ Rollback testé                    │ ✅ Fonctionnel          │
└─────────────────────────────────────────────────────────────┘

✅ Prêt pour production

Planning et Estimation

Vue d'Ensemble

Jalon Durée Semaines Dépendances
Jalon 0 - Infrastructure 3-4 jours S1 -
Jalon 1 - API Lecture 1 semaine S1-S2 Jalon 0
Jalon 2 - Calculs Métier 1-2 semaines S2-S3 Jalon 1
Jalon 3 - API Écriture 1-2 semaines S3-S4 Jalon 2
Jalon 4 - Parité Complète 1 semaine S5 Jalon 3
Jalon 5 - Frontend Vue 3-4 semaines S5-S8 Jalon 4
Jalon 6 - Déploiement 1 semaine S9 Jalon 5

Durée totale estimée : 9-12 semaines

Diagramme de Gantt Simplifié

Semaine:  1    2    3    4    5    6    7    8    9
          ├────┼────┼────┼────┼────┼────┼────┼────┤
Jalon 0   ████
Jalon 1   ░░░░████
Jalon 2        ░░░░████████
Jalon 3             ░░░░░░░░████████
Jalon 4                      ░░░░████
Jalon 5                      ░░░░████████████████
Jalon 6                                      ████

████ = Travail principal
░░░░ = Chevauchement possible

Parallélisation Possible

  • Jalon 5 (Frontend) peut commencer dès que Jalon 1 est terminé
  • Le développement frontend peut avancer en parallèle des jalons 2-4
  • Les tests de comparaison peuvent être écrits en avance

Scripts de Validation

Script Principal

#!/usr/bin/env python3
# validation/compare_all.py
"""
Script de comparaison exhaustive v1 ↔ v2
Usage: python compare_all.py [--jalon N] [--verbose] [--output report.html]
"""

import argparse
import json
import requests
import httpx
from datetime import datetime
from typing import Dict, Any

V1_URL = "http://localhost:5000"
V2_URL = "http://localhost:8000"

class ComparisonResult:
    def __init__(self):
        self.passed = 0
        self.failed = 0
        self.details = []
    
    def add(self, name: str, passed: bool, message: str = ""):
        if passed:
            self.passed += 1
        else:
            self.failed += 1
        self.details.append({
            "name": name,
            "passed": passed,
            "message": message
        })

def compare_json(v1_data: Any, v2_data: Any, path: str = "") -> list:
    """Compare récursivement deux structures JSON"""
    differences = []
    
    if type(v1_data) != type(v2_data):
        differences.append(f"{path}: type différent ({type(v1_data)} vs {type(v2_data)})")
        return differences
    
    if isinstance(v1_data, dict):
        all_keys = set(v1_data.keys()) | set(v2_data.keys())
        for key in all_keys:
            new_path = f"{path}.{key}" if path else key
            if key not in v1_data:
                differences.append(f"{new_path}: absent dans v1")
            elif key not in v2_data:
                differences.append(f"{new_path}: absent dans v2")
            else:
                differences.extend(compare_json(v1_data[key], v2_data[key], new_path))
    
    elif isinstance(v1_data, list):
        if len(v1_data) != len(v2_data):
            differences.append(f"{path}: longueur différente ({len(v1_data)} vs {len(v2_data)})")
        else:
            for i, (v1_item, v2_item) in enumerate(zip(v1_data, v2_data)):
                differences.extend(compare_json(v1_item, v2_item, f"{path}[{i}]"))
    
    elif isinstance(v1_data, float):
        if abs(v1_data - v2_data) > 0.01:
            differences.append(f"{path}: {v1_data} vs {v2_data}")
    
    elif v1_data != v2_data:
        differences.append(f"{path}: {v1_data} vs {v2_data}")
    
    return differences

def test_jalon_0() -> ComparisonResult:
    """Jalon 0: Infrastructure"""
    result = ComparisonResult()
    
    # Test connexion DB
    try:
        r = httpx.get(f"{V2_URL}/api/v2/health")
        data = r.json()
        result.add("Connexion DB", data.get("database") == "connected")
        result.add("Tables détectées", data.get("tables", 0) > 0)
    except Exception as e:
        result.add("Connexion v2", False, str(e))
    
    return result

def test_jalon_1() -> ComparisonResult:
    """Jalon 1: API Lecture"""
    result = ComparisonResult()
    
    endpoints = [
        ("/classes", "/api/v2/classes"),
        ("/classes/1", "/api/v2/classes/1"),
        ("/classes/1/students", "/api/v2/classes/1/students"),
        ("/students", "/api/v2/students"),
        ("/assessments", "/api/v2/assessments"),
        ("/assessments/5/results", "/api/v2/assessments/5/results"),
    ]
    
    for v1_path, v2_path in endpoints:
        try:
            v1_r = requests.get(f"{V1_URL}{v1_path}")
            v2_r = httpx.get(f"{V2_URL}{v2_path}")
            
            differences = compare_json(v1_r.json(), v2_r.json())
            passed = len(differences) == 0
            message = "; ".join(differences[:3]) if differences else ""
            result.add(f"GET {v2_path}", passed, message)
        except Exception as e:
            result.add(f"GET {v2_path}", False, str(e))
    
    return result

def test_jalon_2() -> ComparisonResult:
    """Jalon 2: Calculs Métier"""
    result = ComparisonResult()
    
    # Test des statistiques sur plusieurs évaluations
    for assessment_id in [1, 5, 10]:
        try:
            v1_r = requests.get(f"{V1_URL}/assessments/{assessment_id}/results")
            v2_r = httpx.get(f"{V2_URL}/api/v2/assessments/{assessment_id}/results")
            
            v1_stats = v1_r.json().get("statistics", {})
            v2_stats = v2_r.json().get("statistics", {})
            
            # Comparer les métriques clés
            for key in ["mean", "median", "std_dev"]:
                v1_val = v1_stats.get(key, 0)
                v2_val = v2_stats.get(key, 0)
                passed = abs(v1_val - v2_val) < 0.01
                result.add(
                    f"Assessment {assessment_id} - {key}",
                    passed,
                    f"v1={v1_val}, v2={v2_val}" if not passed else ""
                )
        except Exception as e:
            result.add(f"Assessment {assessment_id}", False, str(e))
    
    return result

def generate_report(results: Dict[str, ComparisonResult], output_format: str = "console"):
    """Génère le rapport de comparaison"""
    
    if output_format == "console":
        print("\n" + "="*60)
        print("         RAPPORT DE COMPARAISON v1 ↔ v2")
        print("="*60 + "\n")
        
        total_passed = 0
        total_failed = 0
        
        for jalon_name, result in results.items():
            status = "✅" if result.failed == 0 else "❌"
            print(f"{jalon_name}: {status} ({result.passed}/{result.passed + result.failed})")
            
            for detail in result.details:
                icon = "  ✅" if detail["passed"] else "  ❌"
                msg = f" - {detail['message']}" if detail["message"] else ""
                print(f"{icon} {detail['name']}{msg}")
            
            total_passed += result.passed
            total_failed += result.failed
            print()
        
        print("="*60)
        final_status = "✅ SUCCÈS" if total_failed == 0 else "❌ ÉCHEC"
        print(f"TOTAL: {total_passed}/{total_passed + total_failed} tests - {final_status}")
        print("="*60)

def main():
    parser = argparse.ArgumentParser(description="Comparaison v1 vs v2")
    parser.add_argument("--jalon", type=int, help="Numéro du jalon à tester (0-6)")
    parser.add_argument("--verbose", action="store_true", help="Affichage détaillé")
    parser.add_argument("--output", help="Fichier de sortie (html ou json)")
    args = parser.parse_args()
    
    results = {}
    
    jalon_tests = {
        0: ("Jalon 0: Infrastructure", test_jalon_0),
        1: ("Jalon 1: API Lecture", test_jalon_1),
        2: ("Jalon 2: Calculs Métier", test_jalon_2),
    }
    
    for jalon_num, (name, test_func) in jalon_tests.items():
        if args.jalon is None or args.jalon == jalon_num:
            print(f"Testing {name}...")
            results[name] = test_func()
    
    generate_report(results, "console")

if __name__ == "__main__":
    main()

Utilisation

# Tester tous les jalons implémentés
python validation/compare_all.py

# Tester un jalon spécifique
python validation/compare_all.py --jalon 2

# Mode verbeux
python validation/compare_all.py --verbose

# Générer un rapport HTML
python validation/compare_all.py --output report.html

Critères de Succès

Critères Techniques

Critère Objectif Validation
Performance API Temps réponse ≤ v1 Tests de charge
Calculs identiques Différence < 0.01 Tests automatisés
Couverture tests > 80% pytest-cov
Documentation API 100% endpoints OpenAPI/Swagger
Zéro régression Toutes fonctionnalités Tests E2E

Critères Fonctionnels

Critère Description Validation
Compatibilité DB v1 et v2 sur même DB Tests croisés
Rollback possible Retour v1 sans perte Test de bascule
Parité UI Mêmes vues et workflows Review manuelle
Accessibilité WCAG 2.1 niveau AA Audit a11y

Critères de Qualité

Critère Description Validation
Code maintenable Séparation des couches Review architecture
Typage complet Pydantic + type hints mypy
Logs structurés JSON avec corrélation Inspection logs
Gestion erreurs Messages clairs Tests erreurs

Commandes de Référence Rapide

# === BACKEND v2 ===

# Démarrer l'API FastAPI
cd backend
uvicorn api.main:app --reload --port 8000

# Lancer les tests
pytest tests/ -v --cov=.

# Générer la doc OpenAPI
# Automatique sur http://localhost:8000/docs

# === FRONTEND ===

# Installer les dépendances
cd frontend
npm install

# Démarrer en dev
npm run dev

# Build production
npm run build

# Tests E2E
npm run test:e2e

# === VALIDATION ===

# Comparer v1 et v2
python validation/compare_all.py

# Test de charge
locust -f validation/load_test.py --host=http://localhost:8000

# === PRODUCTION ===

# Déploiement Docker
docker-compose up -d

# Vérifier les logs
docker-compose logs -f api

# Rollback vers v1
docker-compose --profile legacy up -d legacy

Ressources

Documentation

Outils Recommandés

  • API Testing : httpx, pytest-asyncio
  • Load Testing : locust
  • E2E Testing : Cypress ou Playwright
  • Monitoring : Prometheus + Grafana

Historique des Modifications

Date Version Description
2025-01-20 1.0 Document initial
2025-11-20 1.1 Jalon 0 et Jalon 1 complétés
2025-11-21 1.2 Jalon 2 - Calculs Métier complété
2025-11-21 1.3 Jalon 3 - API Écriture (CRUD principal) complété
2025-11-21 1.4 Jalon 3 - API Écriture 100% (Config CRUD ajouté)
2025-11-21 1.5 Jalon 4 - Dashboard stats classe + Envoi bilans (48 routes)
2025-11-21 1.6 Jalon 4 - EmailService + StudentReportService intégrés (99 tests)
2025-11-21 1.7 Jalon 4 - SMTP config + Heatmaps + Parité complète (45 routes)
2025-11-25 1.8 Jalon 5 - Frontend Vue.js complété (40+ composants, 15 routes)

📊 Progression Actuelle

Vue d'Ensemble

Jalon Statut Progression Date Début Date Fin
Jalon 0 - Infrastructure Complété 100% 2025-11-20 2025-11-20
Jalon 1 - API Lecture Complété 100% 2025-11-20 2025-11-20
Jalon 2 - Calculs Métier Complété 100% 2025-11-21 2025-11-21
Jalon 3 - API Écriture Complété 100% 2025-11-21 2025-11-21
Jalon 4 - Parité Complète Complété 100% 2025-11-21 2025-11-21
Jalon 5 - Frontend Vue Complété 100% 2025-11-25 2025-11-25
Jalon 6 - Déploiement À faire 0% - -

Progression globale : 6/7 jalons (86%)


Détails Jalon 0 : Infrastructure

Statut : COMPLÉTÉ

  • Créer la structure de répertoires backend
  • Configurer FastAPI avec uvicorn
  • Copier models.py sans la logique métier (12 tables)
  • Configurer la session async SQLAlchemy
  • Créer un endpoint de santé /api/v2/health
  • Vérifier la lecture des données existantes

Résultats :

{
  "status": "healthy",
  "database": "connected",
  "tables": 12,
  "classes": 5,
  "students": 155,
  "assessments": 30
}

Détails Jalon 1 : API Lecture

Statut : COMPLÉTÉ

Endpoints Implémentés

Endpoint Description Status
GET /api/v2/classes Liste des classes avec comptage élèves
GET /api/v2/classes/{id} Détails d'une classe
GET /api/v2/classes/{id}/students Élèves d'une classe
GET /api/v2/students Liste des étudiants (recherche, filtres)
GET /api/v2/students/{id} Détails étudiant avec historique inscriptions
GET /api/v2/assessments Liste évaluations (filtres trimestre/classe/tri)
GET /api/v2/assessments/{id} Détails avec exercices et éléments
GET /api/v2/assessments/{id}/results Résultats avec statistiques et histogramme
GET /api/v2/config Configuration complète
GET /api/v2/config/competences Liste des compétences
GET /api/v2/config/domains Liste des domaines
GET /api/v2/config/scale Échelle de notation

Total : 12/12 endpoints

Tests de Comparaison v1/v2

tests/comparison/test_v1_v2_parity.py - 16 tests PASSED ✅

TestDataCounts::test_classes_count                    PASSED
TestDataCounts::test_students_count                   PASSED
TestDataCounts::test_assessments_count                PASSED
TestStudentsPerClass::test_each_class_student_count   PASSED
TestAssessmentProgress::test_completed_assessment_progress PASSED
TestAssessmentResults::test_results_statistics        PASSED
TestAssessmentResults::test_student_scores_sorted_alphabetically PASSED
TestConfigConsistency::test_competences_count         PASSED
TestConfigConsistency::test_domains_count             PASSED
TestConfigConsistency::test_scale_values_count        PASSED
TestFilteringAndSorting::test_filter_assessments_by_trimester PASSED
TestFilteringAndSorting::test_filter_assessments_by_class PASSED
TestFilteringAndSorting::test_search_students         PASSED
TestErrorHandling::test_class_not_found               PASSED
TestErrorHandling::test_student_not_found             PASSED
TestErrorHandling::test_assessment_not_found          PASSED

Fichiers Créés

Routes :

  • api/routes/classes.py - Routes pour les classes
  • api/routes/students.py - Routes pour les étudiants
  • api/routes/assessments.py - Routes pour les évaluations
  • api/routes/config.py - Routes pour la configuration

Schémas Pydantic :

  • schemas/class_group.py - Schémas classes
  • schemas/student.py - Schémas étudiants
  • schemas/assessment.py - Schémas évaluations
  • schemas/config.py - Schémas configuration
  • schemas/common.py - Schémas communs

Tests :

  • tests/comparison/test_v1_v2_parity.py - Tests de comparaison v1/v2

Détails Jalon 2 : Calculs Métier

Statut : COMPLÉTÉ

Services Domain Créés

Service Description Status
GradingCalculator Calcul des scores avec Strategy Pattern (notes/compétences)
StatisticsService Moyenne, médiane, écart-type, histogramme
StudentScoreCalculator Calcul des scores par élève et exercice
ConfigService Gestion de la configuration (valeurs spéciales)
ProgressCalculator Calcul de progression de correction

Value Objects Créés

Value Object Description Status
ProgressResult Résultat du calcul de progression
StudentScore Score complet d'un élève
ExerciseScore Score d'un exercice
StatisticsResult Statistiques descriptives
HistogramData Données d'histogramme
GradeValue Valeur de note avec métadonnées

Tests Créés

tests/unit/test_grading_calculator.py - 46 tests PASSED ✅
tests/unit/test_statistics_service.py - 23 tests PASSED ✅
tests/comparison/test_calculation_parity.py - 37 tests PASSED ✅

Total: 106 tests unitaires + parité

Fichiers Créés

Services Domain :

  • domain/services/grading_calculator.py - GradingCalculator avec Strategy Pattern
  • domain/services/statistics_service.py - StatisticsService
  • domain/services/score_calculator.py - StudentScoreCalculator + ProgressCalculator
  • domain/services/config_service.py - ConfigService

Value Objects :

  • domain/value_objects/progress.py - ProgressResult, ProgressStatus
  • domain/value_objects/score.py - StudentScore, ExerciseScore, GradeValue
  • domain/value_objects/statistics.py - StatisticsResult, HistogramData

Tests :

  • tests/unit/test_grading_calculator.py - Tests unitaires GradingCalculator
  • tests/unit/test_statistics_service.py - Tests unitaires StatisticsService
  • tests/comparison/test_calculation_parity.py - Tests de parité v1/v2

Refactoring Effectué

  • Route GET /api/v2/assessments/{id}/results utilise maintenant GradingCalculator et StatisticsService
  • Logique de calcul centralisée et réutilisable

Détails Jalon 3 : API Écriture

Statut : COMPLÉTÉ (100%)

Toutes les fonctionnalités d'écriture sont implémentées, incluant Import CSV, Appréciations conseil et Configuration CRUD.

Endpoints Implémentés

Endpoint Description Status
POST /api/v2/classes Créer une classe
PUT /api/v2/classes/{id} Modifier une classe
DELETE /api/v2/classes/{id} Supprimer une classe
POST /api/v2/students Créer un étudiant
PUT /api/v2/students/{id} Modifier un étudiant
DELETE /api/v2/students/{id} Supprimer un étudiant
POST /api/v2/students/enroll Inscrire un élève
POST /api/v2/students/transfer Transférer un élève
POST /api/v2/students/departure Enregistrer un départ
POST /api/v2/assessments Créer une évaluation unifiée
PUT /api/v2/assessments/{id} Modifier une évaluation
DELETE /api/v2/assessments/{id} Supprimer une évaluation
POST /api/v2/assessments/{id}/grades Saisir des notes
POST /api/v2/classes/{id}/import-csv Import CSV élèves
POST /api/v2/council/classes/{id}/appreciations/{student_id} Créer/Modifier appréciation
GET /api/v2/council/classes/{id}/appreciations/{student_id} Lire appréciation
PUT /api/v2/council/classes/{id}/appreciations/{student_id}/finalize Finaliser appréciation
GET /api/v2/council/classes/{id} Préparation conseil de classe
POST /api/v2/config/competences Créer compétence
PUT /api/v2/config/competences/{id} Modifier compétence
DELETE /api/v2/config/competences/{id} Supprimer compétence
POST /api/v2/config/domains Créer domaine
PUT /api/v2/config/domains/{id} Modifier domaine
DELETE /api/v2/config/domains/{id} Supprimer domaine
POST /api/v2/config/scale Créer valeur d'échelle
PUT /api/v2/config/scale/{value} Modifier valeur d'échelle
DELETE /api/v2/config/scale/{value} Supprimer valeur d'échelle
PUT /api/v2/config Modifier configuration générale

Total : 25/25 endpoints

Schémas Pydantic Créés

Classes :

  • ClassGroupCreate - Création de classe
  • ClassGroupUpdate - Modification de classe
  • ClassGroupResponse - Réponse CRUD

Étudiants :

  • StudentCreate - Création d'étudiant
  • StudentUpdate - Modification d'étudiant
  • EnrollRequest - Inscription (élève existant ou nouveau)
  • TransferRequest - Transfert entre classes
  • DepartureRequest - Enregistrement de départ
  • EnrollmentResponse, TransferResponse, DepartureResponse - Réponses

Évaluations :

  • GradingElementCreate - Élément de notation
  • ExerciseCreate - Exercice avec éléments
  • AssessmentCreate - Création unifiée (éval + exercices + éléments)
  • AssessmentUpdate - Modification
  • BulkGradeCreate - Saisie de notes en lot
  • BulkGradeResponse - Réponse saisie notes

Tests de Compatibilité

tests/comparison/test_write_parity.py - 16 tests PASSED ✅

TestClassCRUD::test_create_class                       PASSED
TestClassCRUD::test_create_class_duplicate_name        PASSED
TestClassCRUD::test_update_class                       PASSED
TestClassCRUD::test_delete_class                       PASSED
TestStudentCRUD::test_create_student_without_class     PASSED
TestStudentCRUD::test_create_student_with_class        PASSED
TestStudentCRUD::test_enroll_existing_student          PASSED
TestStudentCRUD::test_enroll_new_student               PASSED
TestStudentCRUD::test_transfer_student                 PASSED
TestStudentCRUD::test_student_departure                PASSED
TestAssessmentCRUD::test_create_assessment_unified     PASSED
TestAssessmentCRUD::test_update_assessment             PASSED
TestAssessmentCRUD::test_delete_assessment             PASSED
TestGradesCRUD::test_save_grades                       PASSED
TestGradesCRUD::test_update_grades                     PASSED
TestCrossVersionCompatibility::test_assessment_results_consistency PASSED

Fichiers Modifiés/Créés

Schémas :

  • schemas/class_group.py - Ajout ClassGroupCreate, ClassGroupUpdate
  • schemas/student.py - Ajout StudentCreate, EnrollRequest, etc.
  • schemas/assessment.py - Ajout AssessmentCreate, ExerciseCreate, etc.
  • schemas/grading.py - Ajout BulkGradeResponse
  • schemas/__init__.py - Export de tous les schémas

Routes :

  • api/routes/classes.py - Ajout POST/PUT/DELETE
  • api/routes/students.py - Ajout POST/PUT/DELETE + enroll/transfer/departure
  • api/routes/assessments.py - Ajout POST/PUT/DELETE + grades

Tests :

  • tests/comparison/test_write_parity.py - 16 tests CRUD + compatibilité

Prochaines Étapes

Jalon 4 : Parité Fonctionnelle Complète

Le Jalon 3 étant complété à 100%, voici l'avancement du Jalon 4 :

Endpoints Implémentés

  1. Dashboard statistiques de classe - GET /api/v2/classes/{id}/stats?trimester=1

    • Moyennes par élève
    • Statistiques globales (mean, median, std_dev)
    • Histogramme des moyennes
    • Statistiques par domaine et compétence
  2. Envoi d'emails de bilans - POST /api/v2/assessments/{id}/send-reports

    • EmailService intégré avec support SMTP
    • StudentReportService pour génération de bilans HTML
    • Validation des élèves et emails
    • Gestion des erreurs
  3. Services Infrastructure

    • infrastructure/external/email_service.py - Service d'envoi SMTP
    • domain/services/student_report_service.py - Génération bilans individuels
    • Tests unitaires complets (28 tests)
  4. Configuration SMTP email - GET/PUT /api/v2/config/smtp + test

    • Lecture/écriture configuration SMTP
    • Test d'envoi d'email
    • Validation des paramètres
  5. Heatmaps dans résultats - Inclus dans GET /api/v2/assessments/{id}/results

    • Heatmap par compétences
    • Heatmap par domaines
    • Données structurées pour visualisation frontend

Jalon 4 Complété

Tous les objectifs du Jalon 4 ont été atteints :

  • Parité fonctionnelle : 100% des fonctionnalités v1 disponibles en v2
  • Checklist documentée : docs/PARITY_CHECKLIST.md
  • 45 routes API opérationnelles
  • 99 tests unitaires passants

Prochaine étape : Jalon 6 - Déploiement et Bascule


Détails Jalon 5 : Frontend Vue.js

Statut : COMPLÉTÉ

Setup Réalisé

  • Projet Vue 3 avec Vite configuré
  • TailwindCSS intégré avec configuration personnalisée
  • Vue Router 4 avec lazy loading des routes
  • Pinia pour la gestion d'état
  • Axios pour les appels API
  • Chart.js + vue-chartjs pour les graphiques
  • Configuration du proxy Vite vers l'API backend

Structure Complète Implémentée

14 Vues (Views) Créées :

  • DashboardView.vue - Tableau de bord principal
  • ClassListView.vue - Liste des classes
  • ClassFormView.vue - Création/Modification de classe
  • ClassDashboardView.vue - Dashboard d'une classe avec stats
  • ClassStudentsView.vue - Gestion des élèves d'une classe
  • StudentListView.vue - Liste globale des élèves
  • AssessmentListView.vue - Liste des évaluations avec filtres
  • AssessmentFormView.vue - Création/Modification d'évaluation
  • AssessmentDetailView.vue - Détails d'une évaluation
  • GradingView.vue - Interface de notation
  • ResultsView.vue - Résultats avec statistiques et graphiques
  • CouncilView.vue - Préparation conseil de classe
  • ConfigView.vue - Configuration système
  • NotFoundView.vue - Page 404

Composants Communs (8) :

  • AppHeader.vue - En-tête avec navigation
  • AppFooter.vue - Pied de page
  • LoadingSpinner.vue - Indicateur de chargement
  • Modal.vue - Modal réutilisable
  • NotificationContainer.vue - Système de notifications
  • EmptyState.vue - États vides
  • SkeletonLoader.vue - Skeleton loading
  • ColorPicker.vue - Sélecteur de couleur

Composants Spécialisés :

  • assessment/ProgressIndicator.vue - Indicateur de progression
  • assessment/DomainAutocomplete.vue - Autocomplétion domaines
  • config/ConfigGeneralTab.vue - Onglet configuration générale
  • config/ConfigCompetencesTab.vue - Gestion des compétences
  • config/ConfigDomainsTab.vue - Gestion des domaines
  • config/ConfigScaleTab.vue - Configuration échelle de notation
  • config/ConfigEmailTab.vue - Configuration SMTP

Stores Pinia (4) :

  • stores/classes.js - État global des classes
  • stores/assessments.js - État global des évaluations
  • stores/config.js - Configuration système
  • stores/notifications.js - Gestion des notifications

Services API (5) :

  • services/api.js - Client HTTP de base avec intercepteurs
  • services/classes.js - API classes
  • services/students.js - API étudiants
  • services/assessments.js - API évaluations
  • services/config.js - API configuration

Routes Implémentées (15)

Route Composant Description Status
/ DashboardView Tableau de bord
/classes ClassListView Liste des classes
/classes/new ClassFormView Nouvelle classe
/classes/:id ClassDashboardView Dashboard classe
/classes/:id/edit ClassFormView Modifier classe
/classes/:id/students ClassStudentsView Élèves de la classe
/students StudentListView Liste des élèves
/assessments AssessmentListView Liste évaluations
/assessments/new AssessmentFormView Nouvelle évaluation
/assessments/:id AssessmentDetailView Détails évaluation
/assessments/:id/edit AssessmentFormView Modifier évaluation
/assessments/:id/grading GradingView Notation
/assessments/:id/results ResultsView Résultats
/classes/:id/council CouncilView Conseil de classe
/config ConfigView Configuration

Total : 15/15 routes

Fonctionnalités Frontend Implémentées

Gestion des Classes :

  • Liste avec comptage d'élèves
  • Création/Modification/Suppression
  • Dashboard avec statistiques par trimestre
  • Navigation par trimestre
  • Graphiques (histogramme, heatmaps)

Gestion des Élèves :

  • Liste globale avec recherche
  • Liste par classe
  • Inscription/Transfert/Départ
  • Import CSV (interface prévue)

Évaluations :

  • Liste avec filtres (trimestre, classe, statut correction)
  • Tri dynamique
  • Indicateur de progression de correction
  • Création unifiée (évaluation + exercices + éléments)
  • Modification
  • Suppression avec confirmation

Notation :

  • Grille de saisie interactive
  • Sauvegarde automatique
  • Gestion des valeurs spéciales (., d, a)
  • Navigation clavier

Résultats :

  • Statistiques descriptives
  • Histogramme de distribution
  • Tableau des scores
  • Heatmaps compétences/domaines
  • Export (interface prévue)

Configuration :

  • Interface à onglets
  • Gestion compétences (CRUD)
  • Gestion domaines (CRUD)
  • Échelle de notation (CRUD)
  • Configuration SMTP email
  • Sélecteur de couleurs

UX/UI :

  • Design responsive (mobile/tablet/desktop)
  • TailwindCSS avec design system cohérent
  • Notifications toast
  • États de chargement (spinners, skeletons)
  • États vides
  • Gestion d'erreurs
  • Modals réutilisables

Fichiers Créés

Configuration :

  • package.json - Dépendances et scripts
  • vite.config.js - Configuration Vite avec proxy
  • tailwind.config.js - Configuration TailwindCSS
  • postcss.config.js - Configuration PostCSS

Application :

  • src/main.js - Point d'entrée
  • src/App.vue - Composant racine
  • src/router/index.js - Configuration Vue Router
  • src/assets/main.css - Styles globaux

Total : 40+ fichiers Vue/JS créés

Intégration API Backend

Toutes les vues consomment l'API v2 FastAPI :

  • Authentification des requêtes
  • Gestion des erreurs HTTP
  • Intercepteurs Axios pour logging
  • Retry automatique sur erreurs réseau
  • Transformation des données

Tests Prévus

  • Tests unitaires composants (Vitest)
  • Tests E2E (Cypress/Playwright)
  • Tests d'accessibilité
  • Tests de performance

Critères de Validation

# Démarrer le frontend
cd /home/commun/scripts/notytex/notytex-v2/frontend
npm run dev

# Résultat attendu
✓ Serveur démarré sur http://localhost:5173
✓ Toutes les pages accessibles
✓ Navigation fluide
✓ API backend accessible via proxy
✓ Design responsive fonctionnel

Jalon 5 COMPLÉTÉ - Prêt pour le Jalon 6 (Déploiement)


Commandes pour Continuer

# Démarrer l'API v2
cd /home/commun/scripts/notytex/notytex-v2/backend
uv run uvicorn api.main:app --reload --port 8000

# Exécuter tous les tests unitaires (124 passent)
uv run pytest tests/unit/ tests/comparison/test_calculation_parity.py tests/comparison/test_write_parity.py tests/test_health.py -v

# Exécuter les tests CRUD uniquement (16 tests)
uv run pytest tests/comparison/test_write_parity.py -v

# Exécuter les tests de comparaison v1/v2 (nécessite serveur démarré)
uv run pytest tests/comparison/test_v1_v2_parity.py -v --noconftest

# Accéder à la documentation API
open http://localhost:8000/api/v2/docs

Résumé des Tests

Catégorie Fichier Tests
Health test_health.py 2
Calculs test_grading_calculator.py 46
Statistiques test_statistics_service.py 23
Email Service test_email_service.py 16
Student Reports test_student_report_service.py 12
Parité calculs test_calculation_parity.py 37
CRUD & Écriture test_write_parity.py 16
Total unitaires 99 tests

Résumé API

Module Routes Description
Classes 5 CRUD + stats dashboard
Students 7 CRUD + enroll/transfer/departure + CSV
Assessments 7 CRUD + grades + results + send-reports
Council 4 Préparation + appréciations
Config 22 Compétences + Domaines + Échelle + SMTP
Total 45

Note: Les 37 tests de parité calculs et 16 tests de write_parity nécessitent une base de données de test.


Notes

Ce document doit être mis à jour au fur et à mesure de l'avancement du projet. Chaque jalon validé doit être marqué dans la checklist correspondante.

Pour toute question sur ce plan de migration, consulter l'équipe de développement.