This commit is contained in:
2026-05-06 19:55:37 +02:00
parent 7a7994e8b6
commit b41993c455
43 changed files with 1563 additions and 14 deletions

View File

@@ -4,9 +4,9 @@ Ce fichier sert de memoire de travail persistante. Il doit etre mis a jour a cha
## Phase courante
- Phase : `6A - SVG exercices CM1 mathematiques / 01B_fractions`
- Statut : `termine - a relire`
- Derniere mise a jour : `2026-05-05`
- Phase : `8D - Utilisation du profil adaptatif / 01B_fractions`
- Statut : `integre - a valider`
- Derniere mise a jour : `2026-05-06`
## Phase 0 - Index et classification
@@ -230,9 +230,143 @@ Etat d'avancement :
## Prochaine etape
- Phase suivante : `6B - Validation SVG exercices 01B_fractions`
- Statut : `a valider apres Phase 6A`
- Objectif : relire les 8 SVG d'exercices avant de lancer le kit adaptatif.
- Phase suivante : `7B/8B - Validation SVG exercices et kit adaptatif 01B_fractions`
- Statut : `a valider dans l'interface`
- Objectif : relire les SVG d'exercices, le diagnostic, les profils, les parcours et les SVG du kit depuis le dashboard admin.
## Phase 6B - Validation SVG exercices CM1 mathematiques / 01B_fractions
Objectif : relire les 8 SVG d'exercices avant de lancer le kit adaptatif.
Statut : validation utilisateur reportee dans l'interface admin.
Note :
- L'utilisateur a demande le `2026-05-06` de poursuivre la production sans bloquer sur cette validation manuelle.
- Les SVG d'exercices restent donc `a relire` dans l'interface.
- La Phase 7A a ete produite en avance pour preparer la validation du lendemain matin.
## Phase 7A - Kit adaptatif CM1 mathematiques / 01B_fractions
Objectif : produire un kit adaptatif pour le sous-theme fractions afin d'orienter Professeur TOP apres quelques reponses courtes de diagnostic.
Livrables attendus :
- `kit_adaptatif/README.md`
- `kit_adaptatif/00_principe_adaptatif.md`
- `kit_adaptatif/routage.md`
- `kit_adaptatif/test_diagnostic/README.md`
- `kit_adaptatif/test_diagnostic/01_lecture_vocabulaire.md`
- `kit_adaptatif/test_diagnostic/02_partage_unite.md`
- `kit_adaptatif/test_diagnostic/03_demi_droite.md`
- `kit_adaptatif/test_diagnostic/04_comparer_expliquer.md`
- `kit_adaptatif/parcours_profils/README.md`
- `kit_adaptatif/parcours_profils/01_visuel_partage.md`
- `kit_adaptatif/parcours_profils/02_verbal_vocabulaire.md`
- `kit_adaptatif/parcours_profils/03_manipulation_concret.md`
- `kit_adaptatif/parcours_profils/04_reperes_demi_droite.md`
- `kit_adaptatif/parcours_profils/05_progressif_charge_reduite.md`
- `kit_adaptatif/svg/README.md`
- `kit_adaptatif/svg/test_diagnostic_fraction.svg`
- `kit_adaptatif/svg/parcours_fraction_support.svg`
Etat d'avancement :
- Kit adaptatif : genere.
- Tests diagnostics : `4` fichiers.
- Parcours par profils : `5` fichiers.
- SVG de support : `2` fichiers, XML valide.
- Synchronisation backend : effectuee dans `backend/contenus_pedagogiques/.../01B_fractions/kit_adaptatif`.
- Integration applicative fine : non lancee.
Statut : genere - a valider dans l'interface.
## Phase 8A - Exposition admin du kit adaptatif / 01B_fractions
Objectif : rendre le kit adaptatif visible et validable depuis l'interface admin, sans attendre une integration fine dans le deroulement eleve.
Livrables produits :
- `backend/app/program_content.py` : ajout des compteurs `adaptive_tests`, `adaptive_profiles`, `adaptive_svg`.
- `backend/app/program_content.py` : ajout des noeuds `kit`, `diagnostic`, `parcours` et `support` dans l'arbre admin.
- `frontend/src/App.jsx` : la file `Validation contenus` inclut maintenant les elements du kit adaptatif.
- `frontend/src/App.jsx` : les panneaux de statut affichent les tests, parcours et SVG du kit.
- `frontend/src/App.jsx` : les noeuds Markdown de diagnostic et de parcours affichent un apercu texte validable.
Etat d'avancement :
- Exposition dans l'arbre admin : effectuee.
- Exposition dans la file de validation : effectuee.
- Revue plein ecran des SVG du kit : disponible via les noeuds `support`.
- Revue des textes Markdown du kit : disponible dans le panneau de validation.
- Integration du choix adaptatif dans une session eleve : non lancee.
Statut : integre - a valider dans l'interface.
## Phase 8B - Contexte adaptatif dans Professeur TOP / 01B_fractions
Objectif : rendre le kit adaptatif exploitable par le moteur de dialogue sans encore construire une UI eleve dediee au diagnostic.
Livrables produits :
- `backend/app/program_content.py` : ajout de `adaptive_kit` dans les objets lecon.
- `backend/app/program_content.py` : ajout de `format_adaptive_kit_context`.
- `backend/app/schemas.py` : ajout de `adaptive_kit` a `ProgramLesson`.
- `backend/app/schemas.py` : ajout de `lesson_id` dans `ChatRequest`.
- `backend/app/main.py` : ajout du contexte adaptatif au demarrage de seance quand la lecon a un kit.
- `backend/app/main.py` : ajout du contexte adaptatif aux appels `/chat` quand `lesson_id` est fourni.
- `frontend/src/App.jsx` : envoi de `lesson_id` dans le contexte de lecon.
- `frontend/src/App.jsx` : affichage du kit adaptatif dans la fiche eleve admin.
Etat d'avancement :
- Kit accessible via les reponses programme : effectue.
- Contexte adaptatif transmis a Professeur TOP : effectue.
- Interface eleve dediee au diagnostic : non lancee.
- Selection automatique persistante d'un parcours adaptatif par eleve : non lancee.
Statut : integre - a valider dans l'interface.
## Phase 8C - Suivi adaptatif par eleve / 01B_fractions
Objectif : permettre au professeur de conserver un parcours adaptatif recommande pour un eleve et une lecon.
Livrables produits :
- `backend/app/models.py` : nouvelle table `student_adaptive_profiles`.
- `backend/app/schemas.py` : schemas `StudentAdaptiveProfileRequest` et `StudentAdaptiveProfileRead`.
- `backend/app/main.py` : endpoint `GET /admin/students/{student_id}/adaptive-profile`.
- `backend/app/main.py` : endpoint `PUT /admin/students/{student_id}/adaptive-profile`.
- `frontend/src/App.jsx` : chargement du profil adaptatif de l'eleve pour la lecon active.
- `frontend/src/App.jsx` : formulaire admin pour choisir le test diagnostic de reference, le parcours recommande et une note de suivi.
- `frontend/src/styles.css` : styles du panneau de suivi adaptatif.
Etat d'avancement :
- Stockage du profil adaptatif par eleve/lecon : effectue.
- Affichage dans la fiche eleve admin : effectue.
- Sauvegarde du parcours recommande : effectuee.
- Utilisation automatique de ce profil dans le deroulement eleve : non lancee.
Statut : integre - a valider dans l'interface.
## Phase 8D - Utilisation du profil adaptatif / 01B_fractions
Objectif : faire en sorte qu'un profil adaptatif enregistre par le professeur influence effectivement les reponses de Professeur TOP.
Livrables produits :
- `backend/app/main.py` : ajout de `format_student_adaptive_profile_context`.
- `backend/app/main.py` : injection du profil adaptatif au demarrage de seance.
- `backend/app/main.py` : injection du profil adaptatif dans les appels `/chat`.
Etat d'avancement :
- Si un profil est enregistre pour l'eleve et la lecon active, il est transmis comme aide prioritaire au LLM.
- Le kit adaptatif generique reste transmis meme sans profil enregistre.
- Le frontend eleve ne montre pas encore explicitement le diagnostic adaptatif.
Statut : integre - a valider dans l'interface.
## Phases prevues

View File

@@ -129,3 +129,17 @@ Organisation :
- `exercices_svg/08_fraction_d_une_quantite.svg`
Statut : SVG exercices initiaux prets pour relecture Phase 6B.
## Kit adaptatif genere
Phase 7A : un kit adaptatif de sous-theme est genere dans `kit_adaptatif/`.
Organisation :
- `kit_adaptatif/00_principe_adaptatif.md` : profils observables et erreurs a reperer.
- `kit_adaptatif/routage.md` : choix du parcours apres diagnostic.
- `kit_adaptatif/test_diagnostic/` : 4 tests courts.
- `kit_adaptatif/parcours_profils/` : 5 parcours de reprise.
- `kit_adaptatif/svg/` : 2 supports visuels XML valides.
Statut : kit adaptatif initial pret pour relecture Phase 7B.

View File

@@ -0,0 +1,45 @@
# Principe adaptatif - fractions CM1
## Compétence cible
Comprendre une fraction simple comme un nombre qui exprime des parts égales d'une unité, puis utiliser cette compréhension pour lire, représenter, comparer et calculer avec des fractions simples.
## Faiblesses à détecter
- l'élève inverse numérateur et dénominateur ;
- l'élève oublie que les parts doivent être égales ;
- l'élève lit une fraction chiffre par chiffre sans utiliser les mots `demi`, `tiers`, `quart`, `cinquième` ;
- l'élève ne sait pas relier fraction et dessin ;
- l'élève place une fraction sur une demi-droite comme un entier isolé ;
- l'élève compare seulement les nombres du haut ou seulement les nombres du bas ;
- l'élève ne distingue pas fraction inférieure, égale ou supérieure à `1` ;
- l'élève réussit un exercice visuel mais se bloque dès que la consigne devient verbale.
## Profils pédagogiques observables
### 1. Visuel et partage
L'élève comprend mieux avec une unité dessinée, des parts colorées, des bandes ou des disques fractionnés.
### 2. Verbal et vocabulaire
L'élève a besoin de dire les fractions, d'entendre les mots et de stabiliser le vocabulaire : numérateur, dénominateur, unité, parts égales.
### 3. Manipulation et concret
L'élève progresse avec des situations de partage : gâteau, bande, tablette, collection d'objets, recette.
### 4. Repères et demi-droite
L'élève doit travailler le passage de la fraction dessinée à la fraction placée sur une ligne graduée.
### 5. Progressif et charge réduite
L'élève sait parfois faire, mais se trompe quand il y a trop d'informations à traiter en même temps.
## Règle générale
Le diagnostic ne doit pas coller une étiquette à l'élève. Il sert seulement à choisir la meilleure première aide.
Professeur TOP doit pouvoir combiner deux profils si les réponses le justifient, par exemple `visuel et partage` puis `verbal et vocabulaire`.

View File

@@ -0,0 +1,29 @@
# Kit adaptatif - 01B fractions
## Rôle du dossier
Ce kit transforme le sous-thème `01B_fractions` en parcours adaptatif pour Professeur TOP.
Il sert à choisir rapidement une entrée de travail adaptée à l'élève après quelques réponses courtes :
- lecture et écriture des fractions simples ;
- représentation d'un partage ;
- placement sur demi-droite graduée ;
- comparaison et encadrement ;
- petites opérations sur fractions de même dénominateur ;
- fraction d'une quantité.
## Structure
- `00_principe_adaptatif.md` : logique générale, erreurs à repérer et profils observables.
- `routage.md` : règles d'orientation après diagnostic.
- `test_diagnostic/` : tests courts à utiliser avant de choisir un parcours.
- `parcours_profils/` : exercices et reprises classés par profil pédagogique.
- `svg/` : supports visuels du diagnostic et des parcours.
## Statut
Phase `7A` : kit adaptatif généré pour revue.
Les contenus restent éditoriaux : ils devront être validés dans l'interface avant assimilation complète au programme actif.

View File

@@ -0,0 +1,28 @@
# Parcours 01 - Visuel et partage
## Pour quel élève ?
L'élève se trompe sur les parts égales, le sens du numérateur ou la comparaison de fractions représentées.
## Reprise courte
Une fraction parle toujours d'une unité. On partage cette unité en parts égales. Le dénominateur dit combien il y a de parts égales. Le numérateur dit combien de parts on prend.
## Exercices
1. Dessine une bande en `5` parts égales et colorie `2` parts. Écris la fraction.
2. Parmi deux dessins, choisis celui qui représente vraiment `3/4`.
3. Colorie `4/6` d'une bande déjà partagée.
4. Explique pourquoi un dessin avec des parts inégales ne convient pas.
## Réponses attendues
- `2/5`
- dessin avec quatre parts égales dont trois coloriées ;
- quatre parts coloriées sur six ;
- les parts doivent avoir la même taille pour compter correctement.
## Retour programme
Reprendre la fiche `02_representer_fraction_partage`.

View File

@@ -0,0 +1,30 @@
# Parcours 02 - Verbal et vocabulaire
## Pour quel élève ?
L'élève inverse numérateur et dénominateur, lit mal les fractions ou manque de mots pour expliquer.
## Reprise courte
Dans `3/4`, je lis d'abord le nombre du haut : `3`. Il dit combien de parts sont prises. Puis je lis le nombre du bas : `4`. Il dit en combien de parts égales l'unité est partagée.
## Exercices
1. Lis `1/2`, `2/3`, `3/4`, `5/8`.
2. Dans `6/10`, entoure le numérateur.
3. Dans `6/10`, souligne le dénominateur.
4. Écris en fraction : `sept dixièmes`.
5. Complète : le dénominateur indique `...`.
## Réponses attendues
- `un demi`, `deux tiers`, `trois quarts`, `cinq huitièmes` ;
- numérateur `6` ;
- dénominateur `10` ;
- `7/10` ;
- le nombre de parts égales dans l'unité.
## Retour programme
Reprendre la fiche `01_lire_ecrire_fraction_simple`.

View File

@@ -0,0 +1,28 @@
# Parcours 03 - Manipulation et concret
## Pour quel élève ?
L'élève comprend mieux quand la fraction est reliée à une situation réelle.
## Reprise courte
Une fraction peut dire une part de gâteau, une longueur de ruban, une partie d'une tablette ou une part d'une collection.
## Exercices
1. Une tablette a `8` carrés. On mange `3` carrés. Quelle fraction de la tablette est mangée ?
2. Une bande mesure une unité. Elle est partagée en `6` morceaux égaux. On garde `5` morceaux. Quelle fraction est gardée ?
3. Il y a `12` jetons. On prend le quart. Combien de jetons prend-on ?
4. Explique `2/3` avec une situation de partage.
## Réponses attendues
- `3/8`
- `5/6`
- `3` jetons
- situation où une unité ou une collection est partagée en trois parts égales et deux parts sont prises.
## Retour programme
Reprendre les fiches `02_representer_fraction_partage` et `08_fraction_d_une_quantite`.

View File

@@ -0,0 +1,28 @@
# Parcours 04 - Repères et demi-droite
## Pour quel élève ?
L'élève a besoin de relier fractions, graduations et position entre deux entiers.
## Reprise courte
Sur une demi-droite, le dénominateur dit en combien d'intervalles égaux on partage l'unité. Le numérateur dit combien d'intervalles on avance depuis `0`.
## Exercices
1. Partage l'intervalle de `0` à `1` en `4` parties égales.
2. Place `1/4`, `2/4`, `3/4`.
3. Place `5/4` et explique pourquoi il est après `1`.
4. Encadre `9/4` entre deux entiers.
## Réponses attendues
- quatre intervalles égaux entre `0` et `1` ;
- fractions placées dans l'ordre ;
- `5/4` est une unité complète plus `1/4` ;
- `9/4` est entre `2` et `3`.
## Retour programme
Reprendre les fiches `03_fraction_sur_demi_droite_graduee`, `04_fraction_superieure_a_un` et `06_encadrer_fraction_entre_entiers`.

View File

@@ -0,0 +1,44 @@
# Parcours 05 - Progressif et charge réduite
## Pour quel élève ?
L'élève comprend une notion isolée mais se trompe quand la page contient trop d'informations.
## Reprise courte
On traite une seule idée à la fois : lire, puis représenter, puis comparer.
## Exercices
### Étape 1 - Lire
Lis seulement `3/4`.
Réponse : `trois quarts`.
### Étape 2 - Nommer
Dans `3/4`, indique seulement le dénominateur.
Réponse : `4`.
### Étape 3 - Dessiner
Dessine une bande en `4` parts égales.
Réponse : quatre parts de même taille.
### Étape 4 - Colorier
Colorie `3` parts.
Réponse : trois parts coloriées sur quatre.
### Étape 5 - Résumer
Écris la phrase : `3/4 signifie 3 parts prises sur 4 parts égales`.
## Retour programme
Reprendre la fiche correspondant à l'étape qui a bloqué.

View File

@@ -0,0 +1,6 @@
# Parcours par profils observables
Chaque dossier propose une reprise ciblée après diagnostic.
Les parcours peuvent être combinés. Par exemple, un élève peut commencer par `01_visuel_partage`, puis passer à `02_verbal_vocabulaire` pour stabiliser les mots.

View File

@@ -0,0 +1,33 @@
# Routage adaptatif - fractions CM1
## Entrées observables
| Observation | Orientation principale | Orientation secondaire |
| --- | --- | --- |
| Inverse haut et bas dans `3/4` | `02_verbal_vocabulaire` | `01_visuel_partage` |
| Colorie des parts inégales | `01_visuel_partage` | `03_manipulation_concret` |
| Lit `5/8` comme `cinq barre huit` | `02_verbal_vocabulaire` | `05_progressif_charge_reduite` |
| Place `3/4` après `3` sur la demi-droite | `04_reperes_demi_droite` | `01_visuel_partage` |
| Compare `2/5` et `4/5` en regardant le dénominateur | `01_visuel_partage` | `02_verbal_vocabulaire` |
| Se perd dans les énoncés longs | `05_progressif_charge_reduite` | profil dominant repéré |
| Comprend avec objets mais pas avec symboles | `03_manipulation_concret` | `02_verbal_vocabulaire` |
## Décision rapide
1. Faire passer les quatre tests courts.
2. Noter les erreurs, mais aussi les hésitations et reformulations spontanées.
3. Choisir un parcours principal.
4. Ajouter un parcours secondaire si deux difficultés sont nettes.
5. Revenir ensuite à la fiche ou card du programme correspondant à la difficulté.
## Retour au programme
- Erreur sur lecture/écriture : reprendre `01_lire_ecrire_fraction_simple`.
- Erreur sur dessin : reprendre `02_representer_fraction_partage`.
- Erreur sur ligne graduée : reprendre `03_fraction_sur_demi_droite_graduee`.
- Erreur sur `> 1` : reprendre `04_fraction_superieure_a_un`.
- Erreur de comparaison : reprendre `05_comparer_fractions_simples`.
- Erreur d'encadrement : reprendre `06_encadrer_fraction_entre_entiers`.
- Erreur d'addition/soustraction : reprendre `07_additionner_soustraire_fractions_simples`.
- Erreur sur collection : reprendre `08_fraction_d_une_quantite`.

View File

@@ -0,0 +1,6 @@
# SVG du kit adaptatif fractions
Ces visuels servent de supports simples pour les tests et les parcours.
Ils sont volontairement moins denses que les fiches de leçon : chaque SVG porte une seule décision pédagogique.

View File

@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="760" viewBox="0 0 1200 760">
<rect width="1200" height="760" fill="#f7fafc"/>
<rect x="60" y="50" width="1080" height="660" rx="28" fill="#ffffff" stroke="#d7e3df" stroke-width="6"/>
<text x="100" y="120" font-family="Inter, Arial, sans-serif" font-size="44" font-weight="700" fill="#0f3d5e">Aide fractions</text>
<text x="100" y="170" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">Une unité, des parts égales, des parts prises.</text>
<rect x="120" y="230" width="420" height="160" rx="20" fill="#eef6ff" stroke="#bfd7f2"/>
<text x="150" y="280" font-family="Inter, Arial, sans-serif" font-size="30" font-weight="700" fill="#0f3d5e">3/4</text>
<text x="230" y="270" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">3 parts prises</text>
<text x="230" y="325" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">4 parts égales</text>
<line x1="150" y1="296" x2="205" y2="296" stroke="#1f2937" stroke-width="4"/>
<rect x="660" y="230" width="360" height="160" rx="20" fill="#fff7ed" stroke="#fed7aa"/>
<rect x="720" y="292" width="240" height="58" fill="#ffffff" stroke="#1f2937" stroke-width="3"/>
<line x1="780" y1="292" x2="780" y2="350" stroke="#1f2937" stroke-width="2"/>
<line x1="840" y1="292" x2="840" y2="350" stroke="#1f2937" stroke-width="2"/>
<line x1="900" y1="292" x2="900" y2="350" stroke="#1f2937" stroke-width="2"/>
<rect x="720" y="292" width="180" height="58" fill="#f97316" opacity="0.45"/>
<text x="716" y="380" font-family="Inter, Arial, sans-serif" font-size="22" fill="#415466">3 parts sur 4</text>
<rect x="120" y="460" width="900" height="120" rx="20" fill="#f0fdf4" stroke="#bbf7d0"/>
<line x1="200" y1="530" x2="900" y2="530" stroke="#1f2937" stroke-width="4"/>
<line x1="200" y1="515" x2="200" y2="545" stroke="#1f2937" stroke-width="3"/>
<line x1="900" y1="515" x2="900" y2="545" stroke="#1f2937" stroke-width="3"/>
<line x1="375" y1="520" x2="375" y2="540" stroke="#64748b" stroke-width="2"/>
<line x1="550" y1="520" x2="550" y2="540" stroke="#64748b" stroke-width="2"/>
<line x1="725" y1="520" x2="725" y2="540" stroke="#64748b" stroke-width="2"/>
<circle cx="725" cy="530" r="10" fill="#16a34a"/>
<text x="190" y="570" font-family="Inter, Arial, sans-serif" font-size="20" fill="#1f2937">0</text>
<text x="893" y="570" font-family="Inter, Arial, sans-serif" font-size="20" fill="#1f2937">1</text>
<text x="700" y="500" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#166534">3/4</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="760" viewBox="0 0 1200 760">
<rect width="1200" height="760" fill="#f7fafc"/>
<rect x="60" y="50" width="1080" height="660" rx="28" fill="#ffffff" stroke="#d7e3df" stroke-width="6"/>
<text x="100" y="120" font-family="Inter, Arial, sans-serif" font-size="44" font-weight="700" fill="#0f3d5e">Diagnostic fractions</text>
<text x="100" y="170" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">Lis, dessine, place, puis compare.</text>
<rect x="100" y="220" width="230" height="160" rx="18" fill="#eef6ff" stroke="#bfd7f2"/>
<text x="130" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#0f3d5e">1. Lire</text>
<text x="175" y="340" font-family="Inter, Arial, sans-serif" font-size="54" font-weight="700" fill="#1f2937">3/4</text>
<rect x="365" y="220" width="230" height="160" rx="18" fill="#fff7ed" stroke="#fed7aa"/>
<text x="395" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#9a3412">2. Partager</text>
<rect x="410" y="310" width="140" height="44" fill="#ffffff" stroke="#1f2937" stroke-width="3"/>
<line x1="445" y1="310" x2="445" y2="354" stroke="#1f2937" stroke-width="2"/>
<line x1="480" y1="310" x2="480" y2="354" stroke="#1f2937" stroke-width="2"/>
<line x1="515" y1="310" x2="515" y2="354" stroke="#1f2937" stroke-width="2"/>
<rect x="410" y="310" width="105" height="44" fill="#f97316" opacity="0.45"/>
<rect x="630" y="220" width="230" height="160" rx="18" fill="#f0fdf4" stroke="#bbf7d0"/>
<text x="660" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#166534">3. Placer</text>
<line x1="680" y1="335" x2="815" y2="335" stroke="#1f2937" stroke-width="4"/>
<line x1="680" y1="325" x2="680" y2="345" stroke="#1f2937" stroke-width="3"/>
<line x1="815" y1="325" x2="815" y2="345" stroke="#1f2937" stroke-width="3"/>
<circle cx="781" cy="335" r="9" fill="#16a34a"/>
<text x="670" y="370" font-family="Inter, Arial, sans-serif" font-size="18" fill="#1f2937">0</text>
<text x="808" y="370" font-family="Inter, Arial, sans-serif" font-size="18" fill="#1f2937">1</text>
<rect x="895" y="220" width="205" height="160" rx="18" fill="#fef2f2" stroke="#fecaca"/>
<text x="925" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#991b1b">4. Comparer</text>
<text x="940" y="340" font-family="Inter, Arial, sans-serif" font-size="42" font-weight="700" fill="#1f2937">2/5 ? 4/5</text>
<rect x="120" y="460" width="960" height="140" rx="20" fill="#f8fafc" stroke="#dbe3ef"/>
<text x="160" y="520" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#0f3d5e">Observer surtout les erreurs, les hésitations et les mots utilisés.</text>
<text x="160" y="565" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">Le diagnostic choisit une aide. Il ne note pas l'élève.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,26 @@
# Test 01 - Lecture et vocabulaire
## Objectif
Repérer si l'élève sait lire une fraction et identifier numérateur et dénominateur.
## Questions
1. Lis `3/4`.
2. Dans `3/4`, quel nombre indique le nombre de parts prises ?
3. Dans `3/4`, quel nombre indique le nombre de parts égales dans l'unité ?
4. Écris en fraction : `deux cinquièmes`.
## Réponses attendues
1. `trois quarts`.
2. `3`, le numérateur.
3. `4`, le dénominateur.
4. `2/5`.
## Indices d'orientation
- Inversion haut/bas : parcours `02_verbal_vocabulaire`.
- Lecture chiffre par chiffre : parcours `02_verbal_vocabulaire`.
- Réussite orale mais erreur écrite : parcours `05_progressif_charge_reduite`.

View File

@@ -0,0 +1,26 @@
# Test 02 - Partage de l'unité
## Objectif
Vérifier que l'élève relie une fraction à des parts égales d'une unité.
## Questions
1. Dessine une bande partagée en `4` parts égales.
2. Colorie `3` parts.
3. Écris la fraction coloriée.
4. Explique pourquoi les parts doivent être égales.
## Réponses attendues
- Bande partagée en quatre parts de même taille.
- Trois parts coloriées.
- Fraction `3/4`.
- Réponse attendue : sinon la fraction ne décrit pas correctement le partage de l'unité.
## Indices d'orientation
- Parts inégales : parcours `01_visuel_partage`.
- Bon dessin mais explication faible : parcours `02_verbal_vocabulaire`.
- Besoin d'une situation concrète : parcours `03_manipulation_concret`.

View File

@@ -0,0 +1,25 @@
# Test 03 - Demi-droite graduée
## Objectif
Voir si l'élève comprend qu'une fraction peut être placée entre deux nombres entiers.
## Questions
1. On partage le segment entre `0` et `1` en `4` parts égales.
2. Place `1/4`, `2/4` et `3/4`.
3. Quelle fraction est la plus proche de `1` ?
4. `5/4` est-il avant ou après `1` ?
## Réponses attendues
- `1/4`, `2/4`, `3/4` placés dans l'ordre entre `0` et `1`.
- `3/4` est la plus proche de `1`.
- `5/4` est après `1`.
## Indices d'orientation
- Place `3/4` après `3` : parcours `04_reperes_demi_droite`.
- Confond `4` graduations et `4` intervalles : parcours `04_reperes_demi_droite`.
- Comprend avec bande mais pas avec ligne : combiner `01_visuel_partage` puis `04_reperes_demi_droite`.

View File

@@ -0,0 +1,26 @@
# Test 04 - Comparer et expliquer
## Objectif
Repérer si l'élève compare les fractions avec le bon critère.
## Questions
1. Compare `2/5` et `4/5`.
2. Compare `1/2` et `1/4`.
3. Complète : `7/4` est entre `...` et `...`.
4. Explique avec tes mots pourquoi `4/5` est plus grand que `2/5`.
## Réponses attendues
- `4/5 > 2/5`.
- `1/2 > 1/4`.
- `7/4` est entre `1` et `2`.
- Pour le même dénominateur, plus le numérateur est grand, plus on prend de parts.
## Indices d'orientation
- Compare seulement les dénominateurs : parcours `01_visuel_partage`.
- Ne sait pas encadrer `7/4` : reprendre `04_reperes_demi_droite`.
- Bonne comparaison mais justification fragile : parcours `02_verbal_vocabulaire`.

View File

@@ -0,0 +1,13 @@
# Tests diagnostics - fractions CM1
Ces tests sont courts et doivent être utilisés avant de choisir un parcours adaptatif.
Ils ne servent pas à noter l'élève. Ils servent à comprendre quelle aide sera la plus efficace.
Ordre conseillé :
1. `01_lecture_vocabulaire.md`
2. `02_partage_unite.md`
3. `03_demi_droite.md`
4. `04_comparer_expliquer.md`

View File

@@ -21,6 +21,7 @@ from .curriculum import QUESTIONS
from .program_content import (
build_program_tree,
decode_asset_token,
format_adaptive_kit_context,
get_content_status,
get_lesson,
list_lessons_for_grade,
@@ -102,6 +103,18 @@ def build_student_program_response(student: models.Student, assignment: models.S
)
def format_student_adaptive_profile_context(profile: models.StudentAdaptiveProfile | None) -> str:
if not profile:
return ""
return (
"Profil adaptatif recommandé pour cet élève:\n"
f"- Parcours: {profile.profile_title}\n"
f"- Source: {profile.source}\n"
f"- Notes professeur: {profile.notes or 'aucune'}\n"
"Règle: utilise ce parcours comme aide prioritaire si l'élève hésite ou se trompe sur cette leçon.\n"
)
def clamp_lesson_position(lesson: dict, asset_index: int, section_index: int) -> tuple[int, int]:
assets = lesson.get("assets") or []
if not assets:
@@ -427,6 +440,55 @@ def update_student_program_start(
return build_student_program_response(student, assignment)
@app.get("/admin/students/{student_id}/adaptive-profile", response_model=schemas.StudentAdaptiveProfileRead | None)
def get_student_adaptive_profile(
student_id: int,
lesson_id: str,
db: Session = Depends(get_db),
current_user: models.User = Depends(require_roles("teacher", "maintenance")),
):
student = db.query(models.Student).filter_by(id=student_id).first()
if not student:
raise HTTPException(status_code=404, detail="Élève introuvable")
return (
db.query(models.StudentAdaptiveProfile)
.filter_by(student_id=student_id, lesson_id=lesson_id)
.first()
)
@app.put("/admin/students/{student_id}/adaptive-profile", response_model=schemas.StudentAdaptiveProfileRead)
def upsert_student_adaptive_profile(
student_id: int,
payload: schemas.StudentAdaptiveProfileRequest,
db: Session = Depends(get_db),
current_user: models.User = Depends(require_roles("teacher", "maintenance")),
):
student = db.query(models.Student).filter_by(id=student_id).first()
if not student:
raise HTTPException(status_code=404, detail="Élève introuvable")
lesson = get_lesson(payload.lesson_id)
if not lesson:
raise HTTPException(status_code=404, detail="Leçon introuvable")
row = (
db.query(models.StudentAdaptiveProfile)
.filter_by(student_id=student_id, lesson_id=payload.lesson_id)
.first()
)
if not row:
row = models.StudentAdaptiveProfile(student_id=student_id, lesson_id=payload.lesson_id)
row.diagnostic_id = payload.diagnostic_id
row.profile_id = payload.profile_id
row.profile_title = payload.profile_title
row.notes = payload.notes
row.source = payload.source
db.add(row)
db.commit()
db.refresh(row)
return row
@app.get("/students/{student_id}/card-progress", response_model=list[schemas.LessonCardProgressRead])
def list_card_progress(
student_id: int,
@@ -544,6 +606,17 @@ def start_session(
)
if first_section and first_section.get("context"):
lesson_context += f"\nTexte pédagogique de la card courante:\n{first_section['context'].strip()}"
adaptive_context = format_adaptive_kit_context(assigned_lesson)
if adaptive_context:
lesson_context += f"\n\n{adaptive_context}"
adaptive_profile = (
db.query(models.StudentAdaptiveProfile)
.filter_by(student_id=student.id, lesson_id=assigned_lesson["id"])
.first()
)
profile_context = format_student_adaptive_profile_context(adaptive_profile)
if profile_context:
lesson_context += f"\n\n{profile_context}"
lesson_reply = build_lesson_reply(
db,
student.id,
@@ -581,6 +654,7 @@ def chat(
lesson_context = ""
if payload.lesson_title or payload.section_title or payload.asset_title:
current_lesson = get_lesson(payload.lesson_id) if payload.lesson_id else None
lesson_context = (
f"Leçon courante: {payload.lesson_title or 'non précisée'}\n"
f"Fiche courante: {payload.asset_title or 'non précisée'}\n"
@@ -591,6 +665,18 @@ def chat(
)
if payload.section_context:
lesson_context += f"\nTexte pédagogique de la card courante:\n{payload.section_context.strip()}"
adaptive_context = format_adaptive_kit_context(current_lesson)
if adaptive_context:
lesson_context += f"\n\n{adaptive_context}"
if current_lesson:
adaptive_profile = (
db.query(models.StudentAdaptiveProfile)
.filter_by(student_id=student.id, lesson_id=current_lesson["id"])
.first()
)
profile_context = format_student_adaptive_profile_context(adaptive_profile)
if profile_context:
lesson_context += f"\n\n{profile_context}"
if lesson_context:
lesson_reply = build_lesson_reply(db, payload.student_id, payload.message, lesson_context)

View File

@@ -121,6 +121,25 @@ class LessonCardProgress(Base):
student = relationship("Student")
class StudentAdaptiveProfile(Base):
__tablename__ = "student_adaptive_profiles"
__table_args__ = (
UniqueConstraint("student_id", "lesson_id", name="uq_student_adaptive_lesson"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
student_id: Mapped[int] = mapped_column(ForeignKey("students.id"), index=True)
lesson_id: Mapped[str] = mapped_column(String(500), index=True)
diagnostic_id: Mapped[str | None] = mapped_column(String(500), nullable=True)
profile_id: Mapped[str] = mapped_column(String(500))
profile_title: Mapped[str] = mapped_column(String(255))
notes: Mapped[str] = mapped_column(Text, default="")
source: Mapped[str] = mapped_column(String(50), default="teacher")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
student = relationship("Student")
class AssessmentAttempt(Base):
__tablename__ = "assessment_attempts"

View File

@@ -250,6 +250,7 @@ def list_lessons() -> list[dict]:
"cycle": cycle,
"asset_count": len(assets),
"assets": assets,
"adaptive_kit": collect_adaptive_kit(lesson_dir),
}
)
@@ -297,6 +298,7 @@ def save_review_state(state: dict) -> None:
def collect_lesson_status(lesson: dict) -> dict:
lesson_dir = CONTENT_ROOT / lesson["id"]
kit_dir = lesson_dir / "kit_adaptatif"
return {
"micro_fiches": len([path for path in lesson_dir.glob("*.md") if path.name.startswith(tuple("0123456789"))]),
"lesson_svg": len(lesson.get("assets") or []),
@@ -304,6 +306,9 @@ def collect_lesson_status(lesson: dict) -> dict:
"contexts": len(list((lesson_dir / "card_contexts").rglob("Fiche*Card*.md"))),
"text_exercises": len(list((lesson_dir / "exercices").rglob("exercices.md"))),
"exercise_svg": len(list((lesson_dir / "exercices_svg").glob("*.svg"))),
"adaptive_tests": len([path for path in (kit_dir / "test_diagnostic").glob("*.md") if path.name != "README.md"]) if kit_dir.exists() else 0,
"adaptive_profiles": len([path for path in (kit_dir / "parcours_profils").glob("*.md") if path.name != "README.md"]) if kit_dir.exists() else 0,
"adaptive_svg": len(list((kit_dir / "svg").glob("*.svg"))) if kit_dir.exists() else 0,
"validations": sorted(path.stem for path in lesson_dir.glob("VALIDATION_PHASE_*.md")),
}
@@ -318,6 +323,69 @@ def append_tree_child(parent: dict, key: str, label: str, node_type: str, path:
return child
def read_review_text(path: Path, max_chars: int = 6000) -> str:
text = path.read_text(encoding="utf-8", errors="ignore").strip()
return text[:max_chars]
def collect_adaptive_kit(lesson_dir: Path) -> dict | None:
kit_dir = lesson_dir / "kit_adaptatif"
if not kit_dir.exists():
return None
def markdown_items(folder: str) -> list[dict]:
base = kit_dir / folder
if not base.exists():
return []
return [
{
"id": path.relative_to(CONTENT_ROOT).as_posix(),
"title": title_from_slug(path.stem),
"content": read_review_text(path),
}
for path in sorted(base.glob("*.md"))
if path.name != "README.md"
]
supports = []
svg_dir = kit_dir / "svg"
if svg_dir.exists():
for path in sorted(svg_dir.glob("*.svg")):
relative = path.relative_to(CONTENT_ROOT).as_posix()
supports.append(
{
"id": relative,
"title": title_from_slug(path.stem),
"url": build_asset_url(relative),
}
)
return {
"id": kit_dir.relative_to(CONTENT_ROOT).as_posix(),
"title": "Kit adaptatif",
"diagnostics": markdown_items("test_diagnostic"),
"profiles": markdown_items("parcours_profils"),
"supports": supports,
}
def format_adaptive_kit_context(lesson: dict | None) -> str:
kit = (lesson or {}).get("adaptive_kit")
if not kit:
return ""
diagnostics = ", ".join(item["title"] for item in kit.get("diagnostics") or [])
profiles = ", ".join(item["title"] for item in kit.get("profiles") or [])
return (
"Kit adaptatif disponible pour cette leçon.\n"
f"Tests diagnostics: {diagnostics or 'aucun'}.\n"
f"Parcours de reprise: {profiles or 'aucun'}.\n"
"Règle adaptative: si l'élève se trompe nettement, semble bloqué, inverse les notions ou demande de l'aide, "
"ne change pas de card tout de suite. Propose une micro-question de diagnostic liée à la difficulté observée, "
"puis oriente vers le parcours le plus probable en une phrase courte.\n"
)
def build_program_tree() -> dict:
reviews = load_review_state()
root = {
@@ -365,6 +433,37 @@ def build_program_tree() -> dict:
)
section_node["url"] = section.get("url")
section_node["review"] = reviews.get(section_key, {})
lesson_dir = CONTENT_ROOT / lesson["id"]
kit_dir = lesson_dir / "kit_adaptatif"
if kit_dir.exists():
kit_key = f"{lesson['id']}/kit_adaptatif"
kit_node = append_tree_child(lesson_node, kit_key, "Kit adaptatif", "kit", kit_key)
kit_node["review"] = reviews.get(kit_key, {})
tests_node = append_tree_child(kit_node, f"{kit_key}/test_diagnostic", "Tests diagnostics", "group", f"{kit_key}/test_diagnostic")
for test_path in sorted((kit_dir / "test_diagnostic").glob("*.md")):
if test_path.name == "README.md":
continue
relative = test_path.relative_to(CONTENT_ROOT).as_posix()
test_node = append_tree_child(tests_node, relative, title_from_slug(test_path.stem), "diagnostic", relative)
test_node["content"] = read_review_text(test_path)
test_node["review"] = reviews.get(relative, {})
profiles_node = append_tree_child(kit_node, f"{kit_key}/parcours_profils", "Parcours profils", "group", f"{kit_key}/parcours_profils")
for profile_path in sorted((kit_dir / "parcours_profils").glob("*.md")):
if profile_path.name == "README.md":
continue
relative = profile_path.relative_to(CONTENT_ROOT).as_posix()
profile_node = append_tree_child(profiles_node, relative, title_from_slug(profile_path.stem), "parcours", relative)
profile_node["content"] = read_review_text(profile_path)
profile_node["review"] = reviews.get(relative, {})
svg_node = append_tree_child(kit_node, f"{kit_key}/svg", "Supports visuels", "group", f"{kit_key}/svg")
for svg_path in sorted((kit_dir / "svg").glob("*.svg")):
relative = svg_path.relative_to(CONTENT_ROOT).as_posix()
support_node = append_tree_child(svg_node, relative, title_from_slug(svg_path.stem), "support", relative)
support_node["url"] = build_asset_url(relative)
support_node["review"] = reviews.get(relative, {})
return root

View File

@@ -54,6 +54,7 @@ class StudentAccountResponse(BaseModel):
class ChatRequest(BaseModel):
student_id: int
message: str
lesson_id: str | None = None
lesson_title: str | None = None
asset_title: str | None = None
section_title: str | None = None
@@ -124,6 +125,7 @@ class ProgramLesson(BaseModel):
cycle: str
asset_count: int
assets: List[ProgramAsset] = []
adaptive_kit: dict[str, Any] | None = None
class ProgramLessonListResponse(BaseModel):
@@ -152,6 +154,30 @@ class StudentProgramStartRequest(BaseModel):
section_index: int = Field(..., ge=0)
class StudentAdaptiveProfileRequest(BaseModel):
lesson_id: str = Field(..., min_length=1)
diagnostic_id: str | None = None
profile_id: str = Field(..., min_length=1)
profile_title: str = Field(..., min_length=1)
notes: str = ""
source: str = "teacher"
class StudentAdaptiveProfileRead(BaseModel):
id: int
student_id: int
lesson_id: str
diagnostic_id: str | None = None
profile_id: str
profile_title: str
notes: str
source: str
updated_at: datetime
class Config:
from_attributes = True
class LessonCardState(BaseModel):
lesson_id: str
asset_path: str

View File

@@ -129,3 +129,17 @@ Organisation :
- `exercices_svg/08_fraction_d_une_quantite.svg`
Statut : SVG exercices initiaux prets pour relecture Phase 6B.
## Kit adaptatif genere
Phase 7A : un kit adaptatif de sous-theme est genere dans `kit_adaptatif/`.
Organisation :
- `kit_adaptatif/00_principe_adaptatif.md` : profils observables et erreurs a reperer.
- `kit_adaptatif/routage.md` : choix du parcours apres diagnostic.
- `kit_adaptatif/test_diagnostic/` : 4 tests courts.
- `kit_adaptatif/parcours_profils/` : 5 parcours de reprise.
- `kit_adaptatif/svg/` : 2 supports visuels XML valides.
Statut : kit adaptatif initial pret pour relecture Phase 7B.

View File

@@ -0,0 +1,45 @@
# Principe adaptatif - fractions CM1
## Compétence cible
Comprendre une fraction simple comme un nombre qui exprime des parts égales d'une unité, puis utiliser cette compréhension pour lire, représenter, comparer et calculer avec des fractions simples.
## Faiblesses à détecter
- l'élève inverse numérateur et dénominateur ;
- l'élève oublie que les parts doivent être égales ;
- l'élève lit une fraction chiffre par chiffre sans utiliser les mots `demi`, `tiers`, `quart`, `cinquième` ;
- l'élève ne sait pas relier fraction et dessin ;
- l'élève place une fraction sur une demi-droite comme un entier isolé ;
- l'élève compare seulement les nombres du haut ou seulement les nombres du bas ;
- l'élève ne distingue pas fraction inférieure, égale ou supérieure à `1` ;
- l'élève réussit un exercice visuel mais se bloque dès que la consigne devient verbale.
## Profils pédagogiques observables
### 1. Visuel et partage
L'élève comprend mieux avec une unité dessinée, des parts colorées, des bandes ou des disques fractionnés.
### 2. Verbal et vocabulaire
L'élève a besoin de dire les fractions, d'entendre les mots et de stabiliser le vocabulaire : numérateur, dénominateur, unité, parts égales.
### 3. Manipulation et concret
L'élève progresse avec des situations de partage : gâteau, bande, tablette, collection d'objets, recette.
### 4. Repères et demi-droite
L'élève doit travailler le passage de la fraction dessinée à la fraction placée sur une ligne graduée.
### 5. Progressif et charge réduite
L'élève sait parfois faire, mais se trompe quand il y a trop d'informations à traiter en même temps.
## Règle générale
Le diagnostic ne doit pas coller une étiquette à l'élève. Il sert seulement à choisir la meilleure première aide.
Professeur TOP doit pouvoir combiner deux profils si les réponses le justifient, par exemple `visuel et partage` puis `verbal et vocabulaire`.

View File

@@ -0,0 +1,29 @@
# Kit adaptatif - 01B fractions
## Rôle du dossier
Ce kit transforme le sous-thème `01B_fractions` en parcours adaptatif pour Professeur TOP.
Il sert à choisir rapidement une entrée de travail adaptée à l'élève après quelques réponses courtes :
- lecture et écriture des fractions simples ;
- représentation d'un partage ;
- placement sur demi-droite graduée ;
- comparaison et encadrement ;
- petites opérations sur fractions de même dénominateur ;
- fraction d'une quantité.
## Structure
- `00_principe_adaptatif.md` : logique générale, erreurs à repérer et profils observables.
- `routage.md` : règles d'orientation après diagnostic.
- `test_diagnostic/` : tests courts à utiliser avant de choisir un parcours.
- `parcours_profils/` : exercices et reprises classés par profil pédagogique.
- `svg/` : supports visuels du diagnostic et des parcours.
## Statut
Phase `7A` : kit adaptatif généré pour revue.
Les contenus restent éditoriaux : ils devront être validés dans l'interface avant assimilation complète au programme actif.

View File

@@ -0,0 +1,28 @@
# Parcours 01 - Visuel et partage
## Pour quel élève ?
L'élève se trompe sur les parts égales, le sens du numérateur ou la comparaison de fractions représentées.
## Reprise courte
Une fraction parle toujours d'une unité. On partage cette unité en parts égales. Le dénominateur dit combien il y a de parts égales. Le numérateur dit combien de parts on prend.
## Exercices
1. Dessine une bande en `5` parts égales et colorie `2` parts. Écris la fraction.
2. Parmi deux dessins, choisis celui qui représente vraiment `3/4`.
3. Colorie `4/6` d'une bande déjà partagée.
4. Explique pourquoi un dessin avec des parts inégales ne convient pas.
## Réponses attendues
- `2/5`
- dessin avec quatre parts égales dont trois coloriées ;
- quatre parts coloriées sur six ;
- les parts doivent avoir la même taille pour compter correctement.
## Retour programme
Reprendre la fiche `02_representer_fraction_partage`.

View File

@@ -0,0 +1,30 @@
# Parcours 02 - Verbal et vocabulaire
## Pour quel élève ?
L'élève inverse numérateur et dénominateur, lit mal les fractions ou manque de mots pour expliquer.
## Reprise courte
Dans `3/4`, je lis d'abord le nombre du haut : `3`. Il dit combien de parts sont prises. Puis je lis le nombre du bas : `4`. Il dit en combien de parts égales l'unité est partagée.
## Exercices
1. Lis `1/2`, `2/3`, `3/4`, `5/8`.
2. Dans `6/10`, entoure le numérateur.
3. Dans `6/10`, souligne le dénominateur.
4. Écris en fraction : `sept dixièmes`.
5. Complète : le dénominateur indique `...`.
## Réponses attendues
- `un demi`, `deux tiers`, `trois quarts`, `cinq huitièmes` ;
- numérateur `6` ;
- dénominateur `10` ;
- `7/10` ;
- le nombre de parts égales dans l'unité.
## Retour programme
Reprendre la fiche `01_lire_ecrire_fraction_simple`.

View File

@@ -0,0 +1,28 @@
# Parcours 03 - Manipulation et concret
## Pour quel élève ?
L'élève comprend mieux quand la fraction est reliée à une situation réelle.
## Reprise courte
Une fraction peut dire une part de gâteau, une longueur de ruban, une partie d'une tablette ou une part d'une collection.
## Exercices
1. Une tablette a `8` carrés. On mange `3` carrés. Quelle fraction de la tablette est mangée ?
2. Une bande mesure une unité. Elle est partagée en `6` morceaux égaux. On garde `5` morceaux. Quelle fraction est gardée ?
3. Il y a `12` jetons. On prend le quart. Combien de jetons prend-on ?
4. Explique `2/3` avec une situation de partage.
## Réponses attendues
- `3/8`
- `5/6`
- `3` jetons
- situation où une unité ou une collection est partagée en trois parts égales et deux parts sont prises.
## Retour programme
Reprendre les fiches `02_representer_fraction_partage` et `08_fraction_d_une_quantite`.

View File

@@ -0,0 +1,28 @@
# Parcours 04 - Repères et demi-droite
## Pour quel élève ?
L'élève a besoin de relier fractions, graduations et position entre deux entiers.
## Reprise courte
Sur une demi-droite, le dénominateur dit en combien d'intervalles égaux on partage l'unité. Le numérateur dit combien d'intervalles on avance depuis `0`.
## Exercices
1. Partage l'intervalle de `0` à `1` en `4` parties égales.
2. Place `1/4`, `2/4`, `3/4`.
3. Place `5/4` et explique pourquoi il est après `1`.
4. Encadre `9/4` entre deux entiers.
## Réponses attendues
- quatre intervalles égaux entre `0` et `1` ;
- fractions placées dans l'ordre ;
- `5/4` est une unité complète plus `1/4` ;
- `9/4` est entre `2` et `3`.
## Retour programme
Reprendre les fiches `03_fraction_sur_demi_droite_graduee`, `04_fraction_superieure_a_un` et `06_encadrer_fraction_entre_entiers`.

View File

@@ -0,0 +1,44 @@
# Parcours 05 - Progressif et charge réduite
## Pour quel élève ?
L'élève comprend une notion isolée mais se trompe quand la page contient trop d'informations.
## Reprise courte
On traite une seule idée à la fois : lire, puis représenter, puis comparer.
## Exercices
### Étape 1 - Lire
Lis seulement `3/4`.
Réponse : `trois quarts`.
### Étape 2 - Nommer
Dans `3/4`, indique seulement le dénominateur.
Réponse : `4`.
### Étape 3 - Dessiner
Dessine une bande en `4` parts égales.
Réponse : quatre parts de même taille.
### Étape 4 - Colorier
Colorie `3` parts.
Réponse : trois parts coloriées sur quatre.
### Étape 5 - Résumer
Écris la phrase : `3/4 signifie 3 parts prises sur 4 parts égales`.
## Retour programme
Reprendre la fiche correspondant à l'étape qui a bloqué.

View File

@@ -0,0 +1,6 @@
# Parcours par profils observables
Chaque dossier propose une reprise ciblée après diagnostic.
Les parcours peuvent être combinés. Par exemple, un élève peut commencer par `01_visuel_partage`, puis passer à `02_verbal_vocabulaire` pour stabiliser les mots.

View File

@@ -0,0 +1,33 @@
# Routage adaptatif - fractions CM1
## Entrées observables
| Observation | Orientation principale | Orientation secondaire |
| --- | --- | --- |
| Inverse haut et bas dans `3/4` | `02_verbal_vocabulaire` | `01_visuel_partage` |
| Colorie des parts inégales | `01_visuel_partage` | `03_manipulation_concret` |
| Lit `5/8` comme `cinq barre huit` | `02_verbal_vocabulaire` | `05_progressif_charge_reduite` |
| Place `3/4` après `3` sur la demi-droite | `04_reperes_demi_droite` | `01_visuel_partage` |
| Compare `2/5` et `4/5` en regardant le dénominateur | `01_visuel_partage` | `02_verbal_vocabulaire` |
| Se perd dans les énoncés longs | `05_progressif_charge_reduite` | profil dominant repéré |
| Comprend avec objets mais pas avec symboles | `03_manipulation_concret` | `02_verbal_vocabulaire` |
## Décision rapide
1. Faire passer les quatre tests courts.
2. Noter les erreurs, mais aussi les hésitations et reformulations spontanées.
3. Choisir un parcours principal.
4. Ajouter un parcours secondaire si deux difficultés sont nettes.
5. Revenir ensuite à la fiche ou card du programme correspondant à la difficulté.
## Retour au programme
- Erreur sur lecture/écriture : reprendre `01_lire_ecrire_fraction_simple`.
- Erreur sur dessin : reprendre `02_representer_fraction_partage`.
- Erreur sur ligne graduée : reprendre `03_fraction_sur_demi_droite_graduee`.
- Erreur sur `> 1` : reprendre `04_fraction_superieure_a_un`.
- Erreur de comparaison : reprendre `05_comparer_fractions_simples`.
- Erreur d'encadrement : reprendre `06_encadrer_fraction_entre_entiers`.
- Erreur d'addition/soustraction : reprendre `07_additionner_soustraire_fractions_simples`.
- Erreur sur collection : reprendre `08_fraction_d_une_quantite`.

View File

@@ -0,0 +1,6 @@
# SVG du kit adaptatif fractions
Ces visuels servent de supports simples pour les tests et les parcours.
Ils sont volontairement moins denses que les fiches de leçon : chaque SVG porte une seule décision pédagogique.

View File

@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="760" viewBox="0 0 1200 760">
<rect width="1200" height="760" fill="#f7fafc"/>
<rect x="60" y="50" width="1080" height="660" rx="28" fill="#ffffff" stroke="#d7e3df" stroke-width="6"/>
<text x="100" y="120" font-family="Inter, Arial, sans-serif" font-size="44" font-weight="700" fill="#0f3d5e">Aide fractions</text>
<text x="100" y="170" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">Une unité, des parts égales, des parts prises.</text>
<rect x="120" y="230" width="420" height="160" rx="20" fill="#eef6ff" stroke="#bfd7f2"/>
<text x="150" y="280" font-family="Inter, Arial, sans-serif" font-size="30" font-weight="700" fill="#0f3d5e">3/4</text>
<text x="230" y="270" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">3 parts prises</text>
<text x="230" y="325" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">4 parts égales</text>
<line x1="150" y1="296" x2="205" y2="296" stroke="#1f2937" stroke-width="4"/>
<rect x="660" y="230" width="360" height="160" rx="20" fill="#fff7ed" stroke="#fed7aa"/>
<rect x="720" y="292" width="240" height="58" fill="#ffffff" stroke="#1f2937" stroke-width="3"/>
<line x1="780" y1="292" x2="780" y2="350" stroke="#1f2937" stroke-width="2"/>
<line x1="840" y1="292" x2="840" y2="350" stroke="#1f2937" stroke-width="2"/>
<line x1="900" y1="292" x2="900" y2="350" stroke="#1f2937" stroke-width="2"/>
<rect x="720" y="292" width="180" height="58" fill="#f97316" opacity="0.45"/>
<text x="716" y="380" font-family="Inter, Arial, sans-serif" font-size="22" fill="#415466">3 parts sur 4</text>
<rect x="120" y="460" width="900" height="120" rx="20" fill="#f0fdf4" stroke="#bbf7d0"/>
<line x1="200" y1="530" x2="900" y2="530" stroke="#1f2937" stroke-width="4"/>
<line x1="200" y1="515" x2="200" y2="545" stroke="#1f2937" stroke-width="3"/>
<line x1="900" y1="515" x2="900" y2="545" stroke="#1f2937" stroke-width="3"/>
<line x1="375" y1="520" x2="375" y2="540" stroke="#64748b" stroke-width="2"/>
<line x1="550" y1="520" x2="550" y2="540" stroke="#64748b" stroke-width="2"/>
<line x1="725" y1="520" x2="725" y2="540" stroke="#64748b" stroke-width="2"/>
<circle cx="725" cy="530" r="10" fill="#16a34a"/>
<text x="190" y="570" font-family="Inter, Arial, sans-serif" font-size="20" fill="#1f2937">0</text>
<text x="893" y="570" font-family="Inter, Arial, sans-serif" font-size="20" fill="#1f2937">1</text>
<text x="700" y="500" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#166534">3/4</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="760" viewBox="0 0 1200 760">
<rect width="1200" height="760" fill="#f7fafc"/>
<rect x="60" y="50" width="1080" height="660" rx="28" fill="#ffffff" stroke="#d7e3df" stroke-width="6"/>
<text x="100" y="120" font-family="Inter, Arial, sans-serif" font-size="44" font-weight="700" fill="#0f3d5e">Diagnostic fractions</text>
<text x="100" y="170" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">Lis, dessine, place, puis compare.</text>
<rect x="100" y="220" width="230" height="160" rx="18" fill="#eef6ff" stroke="#bfd7f2"/>
<text x="130" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#0f3d5e">1. Lire</text>
<text x="175" y="340" font-family="Inter, Arial, sans-serif" font-size="54" font-weight="700" fill="#1f2937">3/4</text>
<rect x="365" y="220" width="230" height="160" rx="18" fill="#fff7ed" stroke="#fed7aa"/>
<text x="395" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#9a3412">2. Partager</text>
<rect x="410" y="310" width="140" height="44" fill="#ffffff" stroke="#1f2937" stroke-width="3"/>
<line x1="445" y1="310" x2="445" y2="354" stroke="#1f2937" stroke-width="2"/>
<line x1="480" y1="310" x2="480" y2="354" stroke="#1f2937" stroke-width="2"/>
<line x1="515" y1="310" x2="515" y2="354" stroke="#1f2937" stroke-width="2"/>
<rect x="410" y="310" width="105" height="44" fill="#f97316" opacity="0.45"/>
<rect x="630" y="220" width="230" height="160" rx="18" fill="#f0fdf4" stroke="#bbf7d0"/>
<text x="660" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#166534">3. Placer</text>
<line x1="680" y1="335" x2="815" y2="335" stroke="#1f2937" stroke-width="4"/>
<line x1="680" y1="325" x2="680" y2="345" stroke="#1f2937" stroke-width="3"/>
<line x1="815" y1="325" x2="815" y2="345" stroke="#1f2937" stroke-width="3"/>
<circle cx="781" cy="335" r="9" fill="#16a34a"/>
<text x="670" y="370" font-family="Inter, Arial, sans-serif" font-size="18" fill="#1f2937">0</text>
<text x="808" y="370" font-family="Inter, Arial, sans-serif" font-size="18" fill="#1f2937">1</text>
<rect x="895" y="220" width="205" height="160" rx="18" fill="#fef2f2" stroke="#fecaca"/>
<text x="925" y="270" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#991b1b">4. Comparer</text>
<text x="940" y="340" font-family="Inter, Arial, sans-serif" font-size="42" font-weight="700" fill="#1f2937">2/5 ? 4/5</text>
<rect x="120" y="460" width="960" height="140" rx="20" fill="#f8fafc" stroke="#dbe3ef"/>
<text x="160" y="520" font-family="Inter, Arial, sans-serif" font-size="28" font-weight="700" fill="#0f3d5e">Observer surtout les erreurs, les hésitations et les mots utilisés.</text>
<text x="160" y="565" font-family="Inter, Arial, sans-serif" font-size="24" fill="#415466">Le diagnostic choisit une aide. Il ne note pas l'élève.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,26 @@
# Test 01 - Lecture et vocabulaire
## Objectif
Repérer si l'élève sait lire une fraction et identifier numérateur et dénominateur.
## Questions
1. Lis `3/4`.
2. Dans `3/4`, quel nombre indique le nombre de parts prises ?
3. Dans `3/4`, quel nombre indique le nombre de parts égales dans l'unité ?
4. Écris en fraction : `deux cinquièmes`.
## Réponses attendues
1. `trois quarts`.
2. `3`, le numérateur.
3. `4`, le dénominateur.
4. `2/5`.
## Indices d'orientation
- Inversion haut/bas : parcours `02_verbal_vocabulaire`.
- Lecture chiffre par chiffre : parcours `02_verbal_vocabulaire`.
- Réussite orale mais erreur écrite : parcours `05_progressif_charge_reduite`.

View File

@@ -0,0 +1,26 @@
# Test 02 - Partage de l'unité
## Objectif
Vérifier que l'élève relie une fraction à des parts égales d'une unité.
## Questions
1. Dessine une bande partagée en `4` parts égales.
2. Colorie `3` parts.
3. Écris la fraction coloriée.
4. Explique pourquoi les parts doivent être égales.
## Réponses attendues
- Bande partagée en quatre parts de même taille.
- Trois parts coloriées.
- Fraction `3/4`.
- Réponse attendue : sinon la fraction ne décrit pas correctement le partage de l'unité.
## Indices d'orientation
- Parts inégales : parcours `01_visuel_partage`.
- Bon dessin mais explication faible : parcours `02_verbal_vocabulaire`.
- Besoin d'une situation concrète : parcours `03_manipulation_concret`.

View File

@@ -0,0 +1,25 @@
# Test 03 - Demi-droite graduée
## Objectif
Voir si l'élève comprend qu'une fraction peut être placée entre deux nombres entiers.
## Questions
1. On partage le segment entre `0` et `1` en `4` parts égales.
2. Place `1/4`, `2/4` et `3/4`.
3. Quelle fraction est la plus proche de `1` ?
4. `5/4` est-il avant ou après `1` ?
## Réponses attendues
- `1/4`, `2/4`, `3/4` placés dans l'ordre entre `0` et `1`.
- `3/4` est la plus proche de `1`.
- `5/4` est après `1`.
## Indices d'orientation
- Place `3/4` après `3` : parcours `04_reperes_demi_droite`.
- Confond `4` graduations et `4` intervalles : parcours `04_reperes_demi_droite`.
- Comprend avec bande mais pas avec ligne : combiner `01_visuel_partage` puis `04_reperes_demi_droite`.

View File

@@ -0,0 +1,26 @@
# Test 04 - Comparer et expliquer
## Objectif
Repérer si l'élève compare les fractions avec le bon critère.
## Questions
1. Compare `2/5` et `4/5`.
2. Compare `1/2` et `1/4`.
3. Complète : `7/4` est entre `...` et `...`.
4. Explique avec tes mots pourquoi `4/5` est plus grand que `2/5`.
## Réponses attendues
- `4/5 > 2/5`.
- `1/2 > 1/4`.
- `7/4` est entre `1` et `2`.
- Pour le même dénominateur, plus le numérateur est grand, plus on prend de parts.
## Indices d'orientation
- Compare seulement les dénominateurs : parcours `01_visuel_partage`.
- Ne sait pas encadrer `7/4` : reprendre `04_reperes_demi_droite`.
- Bonne comparaison mais justification fragile : parcours `02_verbal_vocabulaire`.

View File

@@ -0,0 +1,13 @@
# Tests diagnostics - fractions CM1
Ces tests sont courts et doivent être utilisés avant de choisir un parcours adaptatif.
Ils ne servent pas à noter l'élève. Ils servent à comprendre quelle aide sera la plus efficace.
Ordre conseillé :
1. `01_lecture_vocabulaire.md`
2. `02_partage_unite.md`
3. `03_demi_droite.md`
4. `04_comparer_expliquer.md`

View File

@@ -150,6 +150,9 @@ function ProgramReviewPanel({ selectedNode, reviewForm, onReviewChange, onSaveRe
<span>Contextes: {status.contexts ?? 0}</span>
<span>Exercices: {status.text_exercises ?? 0}</span>
<span>SVG exercices: {status.exercise_svg ?? 0}</span>
<span>Tests adaptatifs: {status.adaptive_tests ?? 0}</span>
<span>Parcours: {status.adaptive_profiles ?? 0}</span>
<span>SVG kit: {status.adaptive_svg ?? 0}</span>
</div>
)}
@@ -160,6 +163,12 @@ function ProgramReviewPanel({ selectedNode, reviewForm, onReviewChange, onSaveRe
</button>
)}
{selectedNode.content && !selectedNode.url && (
<div className="program-node-text-preview">
<pre>{selectedNode.content}</pre>
</div>
)}
{review.comment && (
<div className="admin-review-note">
<strong>Dernier commentaire</strong>
@@ -387,6 +396,12 @@ function TutorApp({ currentUser, onLogout }) {
const [activeSectionIndex, setActiveSectionIndex] = useState(0)
const [programStart, setProgramStart] = useState({ asset_index: 0, section_index: 0 })
const [cardProgress, setCardProgress] = useState([])
const [adaptiveProfile, setAdaptiveProfile] = useState(null)
const [adaptiveProfileForm, setAdaptiveProfileForm] = useState({
diagnostic_id: '',
profile_id: '',
notes: '',
})
const [sessionCardStates, setSessionCardStates] = useState({})
const [sessionLogs, setSessionLogs] = useState([])
const [selectedSessionLog, setSelectedSessionLog] = useState(null)
@@ -540,6 +555,8 @@ function TutorApp({ currentUser, onLogout }) {
setProgramLessons([])
setProgramStart({ asset_index: 0, section_index: 0 })
setCardProgress([])
setAdaptiveProfile(null)
setAdaptiveProfileForm({ diagnostic_id: '', profile_id: '', notes: '' })
setSessionCardStates({})
setSessionLogs([])
setSelectedSessionLog(null)
@@ -576,6 +593,9 @@ function TutorApp({ currentUser, onLogout }) {
useEffect(() => {
if (selectedStudentId && studentProgram?.id) {
loadCardProgress(selectedStudentId, studentProgram.id)
if (!isStudentUser) {
loadAdaptiveProfile(selectedStudentId, studentProgram.id)
}
}
}, [selectedStudentId, studentProgram?.id])
@@ -913,6 +933,46 @@ function TutorApp({ currentUser, onLogout }) {
}
}
async function loadAdaptiveProfile(studentId, lessonId) {
if (!studentId || !lessonId || isStudentUser) return
try {
const data = await apiFetch(`/admin/students/${studentId}/adaptive-profile?lesson_id=${encodeURIComponent(lessonId)}`)
setAdaptiveProfile(data || null)
setAdaptiveProfileForm({
diagnostic_id: data?.diagnostic_id || '',
profile_id: data?.profile_id || '',
notes: data?.notes || '',
})
} catch {
setAdaptiveProfile(null)
setAdaptiveProfileForm({ diagnostic_id: '', profile_id: '', notes: '' })
}
}
async function saveAdaptiveProfile(event) {
event.preventDefault()
if (!selectedStudentId || !studentProgram?.id || !adaptiveProfileForm.profile_id) return
const profile = (studentProgram.adaptive_kit?.profiles || []).find((item) => item.id === adaptiveProfileForm.profile_id)
if (!profile) return
try {
const data = await apiFetch(`/admin/students/${selectedStudentId}/adaptive-profile`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
lesson_id: studentProgram.id,
diagnostic_id: adaptiveProfileForm.diagnostic_id || null,
profile_id: profile.id,
profile_title: profile.title,
notes: adaptiveProfileForm.notes,
source: 'teacher',
}),
})
setAdaptiveProfile(data)
} catch (error) {
setErrorMessage(error.message || 'Impossible denregistrer le profil adaptatif.')
}
}
async function loadSessionLogs(studentId) {
if (!studentId || isStudentUser) return
try {
@@ -1151,6 +1211,7 @@ function TutorApp({ currentUser, onLogout }) {
function getLessonContextPayload() {
const { asset, section } = getLessonPosition()
return {
lesson_id: studentProgram?.id || null,
lesson_title: activeLessonTitle,
asset_title: asset?.title || null,
section_title: section?.title || asset?.title || null,
@@ -1553,6 +1614,7 @@ function TutorApp({ currentUser, onLogout }) {
const lastUserMessage = [...messages].reverse().find((message) => message.role === 'user')
const activeLessonTitle = studentProgram?.title || lessonFocus?.label || 'Cours du jour'
const lessonAssets = studentProgram?.assets || []
const adaptiveKit = studentProgram?.adaptive_kit || null
const activeAsset = lessonAssets[activeAssetIndex] || lessonAssets[0] || null
const activeSections = activeAsset?.sections || []
const activeSection = activeSections[activeSectionIndex] || null
@@ -1578,7 +1640,9 @@ function TutorApp({ currentUser, onLogout }) {
const lessonSummary = lastAssistantMessage?.content || 'Aucune ancienne séance trouvée pour le moment. Professeur TOP commencera par une reprise courte.'
const lastStudentWork = lastUserMessage?.content || 'Aucune réponse récente enregistrée.'
const nextLessonAdvice = studentProgram
? `Conseil: démarrer la leçon ${studentProgram.title} avec les fiches choisies par le professeur.`
? adaptiveKit
? `Conseil: si lélève hésite, lancer le diagnostic adaptatif ${adaptiveKit.title.toLowerCase()} avant de poursuivre.`
: `Conseil: démarrer la leçon ${studentProgram.title} avec les fiches choisies par le professeur.`
: lessonFocus
? `Conseil: reprendre ${lessonFocus.label}, puis lancer un exercice court pour équilibrer la progression.`
: 'Conseil: démarrer une séance pour laisser Professeur TOP choisir la prochaine leçon.'
@@ -1792,7 +1856,9 @@ function TutorApp({ currentUser, onLogout }) {
const averageComprehension = cardProgress.length
? Math.round(cardProgress.reduce((sum, item) => sum + item.comprehension_score, 0) / cardProgress.length)
: 0
const reviewRows = programRows.filter(({ node }) => node.type === 'lesson' || node.type === 'fiche' || node.type === 'card')
const reviewRows = programRows.filter(({ node }) =>
['lesson', 'fiche', 'card', 'kit', 'diagnostic', 'parcours', 'support'].includes(node.type)
)
const reviewQueue = reviewRows.filter(({ node }) => ['needs_changes', 'blocked', 'draft', undefined].includes(node.review?.status))
const selectedNodeStatus = selectedProgramNode?.status || {}
const totalLessonCards = studentProgram?.assets?.reduce((sum, asset) => sum + (asset.sections?.length || 0), 0) || 0
@@ -1967,6 +2033,17 @@ function TutorApp({ currentUser, onLogout }) {
<strong>{validatedCards}</strong>
<p>{remainingCards} card(s) encore à travailler</p>
</article>
<article>
<span>Kit adaptatif</span>
<strong>{adaptiveProfile?.profile_title || (adaptiveKit ? 'Disponible' : 'Absent')}</strong>
<p>
{adaptiveProfile
? 'Profil de reprise enregistré'
: adaptiveKit
? `${adaptiveKit.diagnostics?.length || 0} test(s), ${adaptiveKit.profiles?.length || 0} parcours`
: 'Aucun diagnostic relié à cette leçon.'}
</p>
</article>
</div>
<div className="student-settings-grid">
@@ -2013,6 +2090,77 @@ function TutorApp({ currentUser, onLogout }) {
</label>
</div>
{adaptiveKit && (
<section className="adaptive-kit-panel">
<div className="admin-panel-head">
<div>
<span className="stage-label">Adaptatif</span>
<h2>Diagnostic et parcours disponibles</h2>
</div>
<button type="button" className="secondary-button" onClick={() => setActiveAdminTab('validation')}>
Valider le kit
</button>
</div>
<div className="adaptive-kit-grid">
<article>
<strong>Tests diagnostics</strong>
{(adaptiveKit.diagnostics || []).map((item) => (
<span key={item.id}>{item.title}</span>
))}
</article>
<article>
<strong>Parcours profils</strong>
{(adaptiveKit.profiles || []).map((item) => (
<span key={item.id}>{item.title}</span>
))}
</article>
<article>
<strong>Supports visuels</strong>
{(adaptiveKit.supports || []).map((item) => (
<span key={item.id}>{item.title}</span>
))}
</article>
</div>
<form className="adaptive-profile-form" onSubmit={saveAdaptiveProfile}>
<label>
<span>Test diagnostic de référence</span>
<select
value={adaptiveProfileForm.diagnostic_id}
onChange={(event) => setAdaptiveProfileForm({ ...adaptiveProfileForm, diagnostic_id: event.target.value })}
>
<option value="">Non précisé</option>
{(adaptiveKit.diagnostics || []).map((item) => (
<option key={item.id} value={item.id}>{item.title}</option>
))}
</select>
</label>
<label>
<span>Parcours recommandé</span>
<select
value={adaptiveProfileForm.profile_id}
onChange={(event) => setAdaptiveProfileForm({ ...adaptiveProfileForm, profile_id: event.target.value })}
>
<option value="">Choisir un parcours</option>
{(adaptiveKit.profiles || []).map((item) => (
<option key={item.id} value={item.id}>{item.title}</option>
))}
</select>
</label>
<label className="wide-field">
<span>Note de suivi</span>
<textarea
value={adaptiveProfileForm.notes}
onChange={(event) => setAdaptiveProfileForm({ ...adaptiveProfileForm, notes: event.target.value })}
placeholder="Exemple: inverse encore numérateur/dénominateur, commencer par le parcours verbal."
/>
</label>
<button type="submit" disabled={!adaptiveProfileForm.profile_id}>
Enregistrer le profil adaptatif
</button>
</form>
</section>
)}
<div className="admin-dashboard-grid">
<section className="admin-preview">
<h2>Compréhension des cards</h2>
@@ -2150,11 +2298,13 @@ function TutorApp({ currentUser, onLogout }) {
{selectedProgramNode?.review?.status && <span className={`review-pill ${selectedProgramNode.review.status}`}>{selectedProgramNode.review.status}</span>}
</div>
<div className="validation-main-preview">
{selectedProgramNode?.url ? (
<img src={selectedProgramNode.url} alt={selectedProgramNode.label} />
) : (
<div className="empty-preview">Aucun visuel direct pour cet élément.</div>
)}
{selectedProgramNode?.url ? (
<img src={selectedProgramNode.url} alt={selectedProgramNode.label} />
) : selectedProgramNode?.content ? (
<pre className="validation-text-preview">{selectedProgramNode.content}</pre>
) : (
<div className="empty-preview">Aucun visuel direct pour cet élément.</div>
)}
</div>
</section>
@@ -2167,6 +2317,9 @@ function TutorApp({ currentUser, onLogout }) {
<span>Contextes: {selectedNodeStatus.contexts ?? '-'}</span>
<span>Exercices: {selectedNodeStatus.text_exercises ?? '-'}</span>
<span>SVG ex.: {selectedNodeStatus.exercise_svg ?? '-'}</span>
<span>Tests: {selectedNodeStatus.adaptive_tests ?? '-'}</span>
<span>Parcours: {selectedNodeStatus.adaptive_profiles ?? '-'}</span>
<span>SVG kit: {selectedNodeStatus.adaptive_svg ?? '-'}</span>
</div>
<form className="admin-review-form" onSubmit={saveProgramReview}>
<label>

View File

@@ -216,7 +216,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
}
.student-report-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.student-report-grid article {
@@ -350,6 +350,67 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
align-items: flex-start;
}
.adaptive-kit-panel {
display: flex;
flex-direction: column;
gap: 1rem;
border: 1px solid #dbeafe;
border-radius: 16px;
background: #f8fbff;
padding: 1rem;
}
.adaptive-kit-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.75rem;
}
.adaptive-kit-grid article {
display: flex;
flex-direction: column;
gap: 0.4rem;
border: 1px solid #e2e8f0;
border-radius: 14px;
background: white;
padding: 0.85rem;
}
.adaptive-kit-grid span {
color: #475569;
font-size: 0.9rem;
line-height: 1.35;
}
.adaptive-profile-form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.75rem;
border-top: 1px solid #dbeafe;
padding-top: 1rem;
}
.adaptive-profile-form label {
display: flex;
flex-direction: column;
gap: 0.35rem;
font-weight: 800;
}
.adaptive-profile-form .wide-field,
.adaptive-profile-form button {
grid-column: 1 / -1;
}
.adaptive-profile-form textarea {
min-height: 96px;
resize: vertical;
border: 1px solid #cfd7e6;
border-radius: 12px;
padding: 0.8rem 1rem;
font: inherit;
}
.review-fullscreen {
position: fixed;
inset: 0;
@@ -687,6 +748,23 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
font-size: 0.82rem;
font-weight: 800;
}
.program-node-text-preview,
.validation-text-preview {
min-height: 260px;
max-height: 520px;
overflow: auto;
border: 1px solid #e5e7eb;
border-radius: 12px;
background: white;
padding: 1rem;
}
.program-node-text-preview pre,
.validation-text-preview {
margin: 0;
color: #14213d;
font: 0.95rem/1.55 Inter, system-ui, sans-serif;
white-space: pre-wrap;
}
.admin-review-note p {
margin: 0.35rem 0;
line-height: 1.45;
@@ -1218,6 +1296,8 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.admin-students-layout,
.student-report-grid,
.student-settings-grid,
.adaptive-kit-grid,
.adaptive-profile-form,
.validation-layout,
.admin-program-browser {
grid-template-columns: 1fr;