From 4e823bc4038173d301b671cb3927876a35707132 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Sat, 2 May 2026 19:52:48 +0200 Subject: [PATCH] Cards control --- backend/app/services.py | 4 ++ .../Fiche1Card1.md | 9 +++ .../Fiche1Card4.md | 11 ++++ .../Fiche1Card5.md | 10 ++++ .../Fiche1Card6.md | 9 +++ frontend/src/App.jsx | 56 +++++++++++++++++-- frontend/src/styles.css | 29 ++++++++++ 7 files changed, 123 insertions(+), 5 deletions(-) diff --git a/backend/app/services.py b/backend/app/services.py index 906f489..6ade526 100644 --- a/backend/app/services.py +++ b/backend/app/services.py @@ -180,6 +180,10 @@ def build_lesson_reply(db: Session, student_id: int, user_message: str, lesson_c "content": ( "Tu reçois un contexte courant de leçon lié à la card affichée. " "Tu dois parler uniquement de cette card, sauf si l'élève pose une question de clarification immédiate. " + "N'utilise que les nombres, mots de vocabulaire et actions explicitement autorisés dans le contexte de la card. " + "Si un nombre n'est pas affiché ou autorisé dans ce contexte, tu n'en parles pas. " + "Ne propose jamais 'un autre nombre' si le contexte ne donne pas cet autre nombre. " + "Si l'élève veut continuer mais que la card courante est terminée, mets `advance_lesson_step` à true. " "N'utilise pas l'historique des séances précédentes pour choisir le thème de ta réponse. " "Ta réponse visible doit être très courte: maximum 24 mots, 1 phrase d'explication " "puis éventuellement 1 question courte. Évite les phrases longues avec plusieurs propositions. " diff --git a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card1.md b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card1.md index 9bc5269..da6574a 100644 --- a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card1.md +++ b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card1.md @@ -8,6 +8,14 @@ Idée à enseigner : - Le groupe `208` est le groupe des unités simples. - Ce découpage aide à lire le nombre sans se perdre. +Nombre autorisé sur cette card : +- `345 208` uniquement. + +Vocabulaire autorisé : +- groupe des milliers ; +- groupe des unités simples ; +- couper entre `345` et `208`. + Consigne orale courte pour Professeur TOP : Explique que pour lire un grand nombre, on le sépare en deux paquets de trois chiffres : d'abord les milliers, puis les unités simples. Prends uniquement l'exemple affiché `345 208`. Demande ensuite à l'élève s'il comprend pourquoi on coupe entre `345` et `208`. @@ -16,6 +24,7 @@ Limites : - Ne donne pas les autres exemples de la fiche. - Ne passe pas à la lecture complète du nombre sauf si l'élève demande ou semble prêt. - Reste sur le thème : découper un nombre de 6 chiffres en deux groupes. +- N'utilise pas l'expression `groupe d'unité`; dis toujours `groupe des unités simples`. Critère pour passer à la card suivante : L'élève peut passer à la suite s'il dit clairement qu'il a compris, ou s'il explique avec ses mots que `345` représente les milliers et `208` les unités simples. diff --git a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card4.md b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card4.md index 77059f4..9e04a2a 100644 --- a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card4.md +++ b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card4.md @@ -7,12 +7,23 @@ Idée à enseigner : - `425` est le groupe des unités simples. - Le nombre se lit `sept-mille-quatre-cent-vingt-cinq`. +Nombre autorisé sur cette card : +- `7 425` uniquement. + +Vocabulaire autorisé : +- milliers ; +- unités simples ; +- lire à voix haute ; +- `sept-mille`, puis `quatre-cent-vingt-cinq`. + Consigne orale courte pour Professeur TOP : Demande à l'élève de lire `7 425` à voix haute. S'il hésite, aide-le en deux morceaux : `sept-mille`, puis `quatre-cent-vingt-cinq`. Limites : - Ne donne pas les deux autres exemples. - Ne transforme pas encore en dictée. +- Ne propose pas de lire un autre nombre sur cette card. +- Si l'élève dit oui pour continuer après avoir travaillé `7 425`, passe à la card suivante au lieu de redonner `7 425`. Critère pour passer à la card suivante : L'élève peut passer à la suite s'il lit `7 425` correctement ou avec une petite correction guidée. diff --git a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card5.md b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card5.md index 8aa9ad7..773870c 100644 --- a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card5.md +++ b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card5.md @@ -7,12 +7,22 @@ Idée à enseigner : - `090` se lit comme `90`, mais le zéro aide à garder la place. - Le nombre se lit `trente-huit-mille-quatre-vingt-dix`. +Nombre autorisé sur cette card : +- `38 090` uniquement. + +Vocabulaire autorisé : +- milliers ; +- zéro qui garde la place ; +- `090` se lit `quatre-vingt-dix`. + Consigne orale courte pour Professeur TOP : Demande à l'élève de lire `38 090`. S'il oublie le zéro ou dit mal la fin, rappelle que `090` se lit `quatre-vingt-dix`. Limites : - Ne parle pas de tous les zéros en général. - Reste sur le nombre affiché. +- Ne propose pas de lire un autre nombre sur cette card. +- Si l'élève veut continuer après `38 090`, passe à la card suivante au lieu de répéter `38 090`. Critère pour passer à la card suivante : L'élève peut passer à la suite s'il lit `38 090` en gardant bien `trente-huit-mille`. diff --git a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card6.md b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card6.md index 5b484c9..98f720d 100644 --- a/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card6.md +++ b/backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01A_nombres_entiers/card_contexts/01_lecture_ecriture_nombres_entiers/Fiche1Card6.md @@ -7,12 +7,21 @@ Idée à enseigner : - `012` se lit comme `12`, mais les zéros gardent la structure du groupe. - Le nombre se lit `cinq-cent-six-mille-douze`. +Nombre autorisé sur cette card : +- `506 012` uniquement. + +Vocabulaire autorisé : +- milliers ; +- zéros qui gardent la structure ; +- `012` se lit `douze`. + Consigne orale courte pour Professeur TOP : Demande à l'élève de lire `506 012`. S'il hésite, aide-le à dire `cinq-cent-six-mille`, puis `douze`. Limites : - Ne parle pas d'autres nombres. - Ne donne pas une leçon complète sur les zéros. +- Ne propose pas de lire un autre nombre sur cette card. Critère pour passer à la card suivante : L'élève a réussi cette fiche s'il lit `506 012` correctement ou s'il corrige sa lecture après aide. diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b8c64db..7f2fd99 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -217,6 +217,7 @@ function TutorApp({ currentUser, onLogout }) { const [cardProgress, setCardProgress] = useState([]) const [sessionCardStates, setSessionCardStates] = useState({}) const [sessionLogs, setSessionLogs] = useState([]) + const [selectedSessionLog, setSelectedSessionLog] = useState(null) const [progressPanelOpen, setProgressPanelOpen] = useState(false) const [settingsPanelOpen, setSettingsPanelOpen] = useState(false) const mediaRecorderRef = useRef(null) @@ -309,6 +310,7 @@ function TutorApp({ currentUser, onLogout }) { setCardProgress([]) setSessionCardStates({}) setSessionLogs([]) + setSelectedSessionLog(null) if (selectedStudentId) { loadProgress(selectedStudentId) loadMessages(selectedStudentId) @@ -520,6 +522,7 @@ function TutorApp({ currentUser, onLogout }) { async function saveLessonSession() { if (!selectedStudentId || !studentProgram) return + const currentCardState = getActiveCardState(40, false) const conversation = messages.map((message) => ({ id: message.id, role: message.role, @@ -542,10 +545,11 @@ function TutorApp({ currentUser, onLogout }) { lesson_id: studentProgram.id, lesson_title: studentProgram.title, conversation, - card_states: getMergedCardStates(), + card_states: getMergedCardStates(currentCardState), }), }) await loadCardProgress(selectedStudentId, studentProgram.id) + if (!isStudentUser) await loadSessionLogs(selectedStudentId) } async function endSession() { @@ -697,12 +701,22 @@ function TutorApp({ currentUser, onLogout }) { async function deleteSessionLog(logId) { try { await apiFetch(`/admin/session-logs/${logId}`, { method: 'DELETE' }) + setSelectedSessionLog((current) => (current?.id === logId ? null : current)) await loadSessionLogs(selectedStudentId) } catch (error) { setErrorMessage(error.message || 'Impossible d’effacer ce log.') } } + function parseLogJson(value) { + try { + const parsed = JSON.parse(value || '[]') + return Array.isArray(parsed) ? parsed : [] + } catch { + return [] + } + } + function goToPreviousLessonStep() { if (activeSectionIndex > 0) { setActiveSectionIndex((index) => index - 1) @@ -764,7 +778,7 @@ function TutorApp({ currentUser, onLogout }) { }) } - function getMergedCardStates() { + function getMergedCardStates(extraState = null) { const fromProgress = cardProgress .filter((item) => item.lesson_id === studentProgram?.id) .map((item) => ({ @@ -780,6 +794,7 @@ function TutorApp({ currentUser, onLogout }) { const merged = {} for (const item of fromProgress) merged[item.section_key] = item for (const item of Object.values(sessionCardStates)) merged[item.section_key] = item + if (extraState) merged[extraState.section_key] = extraState return Object.values(merged) } @@ -1590,9 +1605,14 @@ function TutorApp({ currentUser, onLogout }) { {log.lesson_title} {new Date(log.ended_at).toLocaleString('fr-FR')} - +
+ + +
))} @@ -1602,6 +1622,32 @@ function TutorApp({ currentUser, onLogout }) { )} + {selectedSessionLog && ( +
+
+

Détail de séance

+ +
+
+

Résultats des cards

+ {parseLogJson(selectedSessionLog.card_results).map((item) => ( +
+ {item.section_title} + {Math.round(item.comprehension_score)}% · {item.validated ? 'validée' : 'vue/tentée'} +
+ ))} +

Conversation

+ {parseLogJson(selectedSessionLog.conversation).map((message, index) => ( +

+ {message.role === 'assistant' ? 'Professeur TOP' : 'Élève'}: {message.content} +

+ ))} +
+
+ )} + {errorMessage &&

{errorMessage}

} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index dee726e..2fad567 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -217,6 +217,35 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } color: #64748b; font-size: 0.9rem; } +.admin-log-actions { + display: flex; + gap: 0.5rem; +} +.admin-log-detail-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +} +.admin-log-detail { + display: flex; + flex-direction: column; + gap: 0.65rem; + border: 1px solid #e5e7eb; + border-radius: 12px; + background: #f8fafc; + padding: 1rem; +} +.admin-log-detail h3 { + margin: 0.6rem 0 0; +} +.admin-log-detail h3:first-child { + margin-top: 0; +} +.admin-log-message { + margin: 0; + line-height: 1.45; +} .session-user { margin-left: auto; display: flex;