# Changelog — Expria Backend Toutes les modifications notables du backend sont documentées dans ce fichier. Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/). --- ## [Unreleased] — 2026-06-30 — Sprint 7a-patch — T1 Live : suppression de la dépendance au questionnaire ### Changed - `buildT1SystemPrompt` (`geminiLiveT1.ts`) — prompt système T1 désormais **statique** (signature sans argument). La section « CONTEXTE DU CANDIDAT » (5 variables `${reponses.*}`) est retirée et remplacée par une consigne d'écoute : « Écoute attentivement ce que le candidat dit. Quand on te le signale, formule UNE question de relance courte (10-20 mots) liée à ce que le candidat vient de dire. » Les 8 règles sont conservées (silence par défaut, relance sur signal, ton bienveillant, jamais d'évaluation/hors-rôle, règle 5 « DOIS poser des questions »). Règle 4 : retrait de « ou à son contexte ci-dessus ». - `openGeminiLiveT1Session` (`geminiLiveT1.ts`) — option `reponses` retirée de `OpenGeminiLiveT1SessionOptions` et de la signature. Le handler de message client ignore désormais explicitement (log debug + return) tout message non reconnu (ni `audio` ni `end`) — un éventuel `{type:'context'}` d'un ancien front ne provoque ni crash ni close. - `WS /t1/live` (`t1live.ts`) — la session Gemini s'ouvre **immédiatement après l'auth** (calque T2), dans `onOpen`. Le flux client devient `{type:'audio', data}` puis `{type:'end'}`. - `docs/Prompt_t1live.md` — §1 (table « Subject-based »), §2 (règle 3), §3 (prompt statique, retrait des variables et de la section « Variables à substituer »), §4.1 (retrait du message de contexte), §4.3 (retrait du close 4004). - Tests `geminiLiveT1.test.ts` / `t1live.test.ts` — appels `buildT1SystemPrompt()` sans argument, retrait de `reponses` des appels de session ; suppression du test d'intégration des réponses (remplacé par un test « écoute / plus de CONTEXTE DU CANDIDAT ») et du bloc `parseT1Context` ; tests interruption, flush terminal, timeout et correction inchangés. ### Removed - Message client `{type:'context', reponses}` (1er message obligatoire) et close **4004 `CONTEXT_MISSING`** — la route ne lit plus de contexte. - Fonction `parseT1Context` (`t1live.ts`) et ses imports `validateReponses` / `PresentationReponses`. ### Notes - Tests backend : 309/309 verts. `tsc --noEmit` OK. - **Suivi frontend (hors scope)** : le frontend Sprint 7b (non commité) envoie encore `{type:'context'}` et possède un `QuestionnaireT1Page` Live ; ce message est désormais inoffensif côté backend (ignoré). Le questionnaire Live devient sémantiquement obsolète — à retirer dans une session frontend dédiée. --- ## [Unreleased] — 2026-06-28 — Sprint 6d — T2 Live : durcissement prompt + VAD + cleanup SDK ### Changed - `buildT2SystemPrompt` (`geminiLive.ts`) — prompt système T2 durci : 13 règles absolues (Bug 1). Interdiction stricte de poser des questions (aucun point d'interrogation), rôle passif/inerte, silence total après réponse, aucune formule de politesse de fin. Objectif : stopper la relance systématique de l'examinateur IA. Réf TD-22. ⚠ Spécifique T2 — l'interdiction du « ? » ne doit pas être propagée au prompt T1 (Sprint 7). - `buildSetupFrame` (`geminiLive.ts`) — `realtimeInputConfig.automaticActivityDetection` (VAD) réintégré dans le setup frame Gemini (Bug 2), 4 champs : `disabled:false`, `startOfSpeechSensitivity:START_SENSITIVITY_LOW`, `endOfSpeechSensitivity:END_SENSITIVITY_LOW`, `silenceDurationMs:2000`. - `geminiLive.test.ts` — assertion VAD mise à jour (`realtimeInputConfig` : `toBeUndefined` → présence + valeurs des 4 champs). ### Removed - Dépendance `@google/genai` retirée de `package.json` / `package-lock.json` et script de debug `test-gemini-live.js` supprimé (Bug 8) — SDK abandonné au profit du WebSocket brut (`ws`). ### Notes - Tests backend : 292/292 verts (0 échec). - Validé au test manuel Golden Dataset Groupe D — Bug 1 (plus de relance systématique) et Bug 2 (VAD, pauses de réflexion respectées) confirmés en conditions réelles. - **TD-22 (🟡 ouvert)** — comportement T2 garanti uniquement par prompt engineering ; pas de garantie déterministe sur modèle Flash Live. À revisiter si la relance persiste. --- ## [Unreleased] — 2026-04-26 — Sprint 6a — Backend T2 Live ### Added - `buildT2SystemPrompt({role, contexte})` dans `geminiLive.ts` — prompt dynamique conforme `Prompt_t2live.md §3`, remplace la constante `T2_SYSTEM_PROMPT` (agent immobilier). - Accumulation transcripts pendant la session WS : `inputTranscription[]` + `outputTranscription[]` parsés depuis les messages Gemini, reconstruits en transcript chronologique à la fin. - VAD config dans le setup frame Gemini : `endOfSpeechSensitivity: END_SENSITIVITY_LOW`, `startOfSpeechSensitivity: START_SENSITIVITY_LOW`, `silenceDurationMs: 2000`. - Timeout session 210 s (3 min 30) + warning client à 180 s (30 s restantes). - Signal client `{type:'end'}` pour fin anticipée du dialogue. - Close codes : 4005 `GEMINI_CONFIG`, 4006 `GEMINI_DISCONNECTED`. - Orchestration `t2live.ts` : fetch sujet par UUID (`?sujet=`, validation `mode='EO'` + `tache=2`), close 4004 `SUJET_NOT_FOUND` si absent. - Post-session : `runT2LiveCorrection` — insert `productions(tache='EO_T2_LIVE')`, appel `deepseekCorrectEO(transcript, 'EO_T2')`, `PHONOLOGY_STUB` (TD-08), persist rapport + score + nclc, envoi `{type:'report'}` au client, close 1000. - `TacheEO` étendu avec `'EO_T2'` dans `deepseek.ts` + `VALID_TACHES_EO` dans `corrections.ts`. - 10 tests d'intégration `t2live.test.ts` (auth, sujet, pipeline correction nominal + erreurs). - 11 tests `geminiLive.test.ts` (7 réécrits + 4 nouveaux : prompt builder, accumulation, timeout/warning, end signal). ### Changed - `geminiLive.ts` réécrit — setup frame paramétrable, `inputAudioTranscription` + `outputAudioTranscription` activés, callback `onSessionEnd(transcript)`. - `corrections.ts` — `VALID_TACHES_EO` inclut `'EO_T2'`. ### Notes - Tests backend : 292/292 verts (+15 vs baseline 277). - Phonologie T2 Live = 0 (TD-08 — pas d'audio brut pour évaluation phonologique). - Le frontend n'est pas encore connecté — test e2e au Sprint 6c. --- ## [Unreleased] — 2026-04-26 — Sprint 5a — Backend billing cleanup ### Added - `supabase/migrations/007_sprint_5a_stripe_webhook_events.sql` — table `stripe_webhook_events(id TEXT PRIMARY KEY, processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW())` + index sur `processed_at`. Idempotente (`CREATE TABLE IF NOT EXISTS`). - `src/lib/stripeWebhookEvents.ts` — helpers `isEventProcessed` / `markEventProcessed` (insert idempotent, conflit unique `23505` avalé silencieusement). - `src/lib/__tests__/stripeWebhookEvents.test.ts` — 8 tests (lecture, écriture, edge cases vide/erreur DB). - `src/lib/__tests__/createBillingPortalSession.test.ts` — 4 tests (succès, customerId vide, returnUrl vide, URL Stripe vide). - `POST /stripe/customer-portal` — endpoint authentifié qui crée une Stripe Billing Portal Session (gestion abonnement self-service) et redirige l'utilisateur. 400 `NO_ACTIVE_SUBSCRIPTION` si pas de `stripe_customer_id` ; return_url = `${APP_URL}/dashboard`. ### Changed - `POST /stripe/webhook` — déduplication explicite des events Stripe (TD-13 résolu) : check `isEventProcessed(event.id)` avant traitement → early return `200 { received: true, replayed: true }` ; `markEventProcessed` après succès uniquement (pas si exception, pour permettre rejeu Stripe). - `src/lib/stripe.ts` — nouvelle fonction `createBillingPortalSession({ customerId, returnUrl })` (mirror de `createCheckoutSession`). - `src/routes/__tests__/stripe.test.ts` — 5 nouveaux tests (2 idempotency webhook + 3 customer-portal route). - `docs/ARCHITECTURE-backend.md` — §3 commentaire `plans.ts` corrigé (`POST /plans/upgrade-prorata` au lieu de `POST /plans/upgrade` qui n'existait pas) ; §6 retrait de la ligne dupliquée `POST /plans/upgrade` (la création d'abonnement passe par `POST /stripe/checkout`) ; §6 ajout `POST /stripe/customer-portal`. ### Fixed - `src/lib/stripe.ts` — `cancel_url` Stripe Checkout corrigé : `${APP_URL}/tarifs?upgrade=cancelled` → `${APP_URL}/plan?upgrade=cancelled`. La route `/tarifs` n'existe pas côté frontend (route réelle : `/plan`) ; les checkouts annulés aboutissaient sur un 404. Bug détecté lors du Sprint 5c frontend (gestion des retours post-Checkout) et corrigé en cross-repo. ### Resolved - **TD-13 🔴 → Résolu** : Webhook Stripe idempotent (table `stripe_webhook_events` + helpers + wiring route + 10 tests). ### Notes - Tests : 261 → 278 verts (+17). - Aucun changement code frontend dans ce sprint — Sprint 5b/5c/5d (frontend billing) livrés en parallèle. --- ## [Unreleased] — 2026-04-26 — Sprint 4.8 — Phonologie EO ### Added - `src/lib/geminiPhonology.ts` — évaluation phonologique via Gemini 2.5 Flash (audio brut, JSON strict, timeout 45s + 1 retry). `PHONOLOGY_STUB` pour Mode A (transcript sans audio). - `src/lib/__tests__/geminiPhonology.test.ts` — 9 tests (parse, cap 0..4, retry, erreurs HTTP). - `src/controllers/__tests__/correctionEoPhonology.test.ts` — 4 tests (injection 5e critère, stub Mode A, fallback Gemini down, score max 20). ### Changed - `POST /corrections/eo` — passe de 4 critères × /5 à 5 critères × /4 (score total /20 inchangé). Phonologie évaluée par Gemini en parallèle de la transcription (Mode B audio). Fallback stub si Gemini phonologie échoue. - `src/lib/deepseek.ts` — prompt EO : cap critère /5 → /4, libellés officiels TCF Canada, mention phonologie évaluée séparément. Cap EE inchangé /5. - TD-08 partiellement résolu (T1/T3). T2 Live reste à 0 (Sprint 6). ### Notes - ⚠️ Breaking change frontend : criteres.length passe de 4 à 5, échelle /5 → /4. - Tests : 248 → 261 verts (+13). --- ## [Unreleased] — 2026-04-25 — Sprint 4a/4b — Backend EO ### Added - `POST /corrections/eo` aligné format Sprint 3.6a : revelation, diagnostic, criteres enrichis (exemple/suggestion/astuce), conseil_nclc adaptatif (4 niveaux selon score vs cible), erreurs_codes, jobs fire-and-forget modèle + exercices - `POST /presentations/generate` — génération présentation T1 via DeepSeek (220-260 mots, registre oral NCLC 7-8, 5 champs) - `POST /transcriptions/token` — token Deepgram éphémère (600s TTL, dormant côté frontend MVP) - `src/lib/deepgram.ts` — client Deepgram /v1/auth/grant (scope Member requis) - `src/lib/audioStorage.ts` — supprimé (audio non stocké côté serveur) - Migration `006_sprint_4a_eo.sql` — documentation bucket Storage (no-op) ### Changed - `correctEO` : accepte audioBase64+mimeType (Gemini batch) OU transcript texte - MIME normalisé avant validation (audio/webm;codecs=opus → audio/webm) - `sanitizeJsonContent` : gère single-quote JSON DeepSeek - Gemini timeout 30s → 45s, DeepSeek correctEO 55s → 90s - `gemini-2.0-flash` → `gemini-2.5-flash` - conseil_nclc adaptatif EE + EO (4 niveaux : dépassé / atteint / proche / loin) - Tests : 205 → 248 verts (+43) --- ## [Unreleased] — 2026-04-25 — Fix health check keepalive Supabase ### Changed - Route `GET /` : ajout d'un ping Supabase (`profiles.select('id', { head: true }).limit(1)`) à chaque appel. Garde le pool de connexions DB actif via les pings UptimeRobot (toutes les 5 min). Réponse enrichie : `{ message, db: 'connected' | 'error' }`. Toujours 200 (liveness, pas readiness). --- ## [Unreleased] — 2026-04-22 — Sprint 3.6a — Qualité correction Backend ### Added - Nouveaux prompts DeepSeek spécifiés dans `docs/Prompt_maître.md` et `docs/Prompt_production_modèle.md` — builders dynamiques `buildCorrectionPrompt`, `buildModelPrompt`, `buildExercicesPrompt` dans `src/lib/deepseek.ts`. - `expria-frontend/docs/TAXONOMIE_ERREURS.md` — 63 codes d'erreurs TCF Canada sur 4 critères + 4 codes « autre ». Validation runtime via `src/lib/taxonomieErreurs.ts` (`isValidCode`, `isValidCritere`, `buildTaxonomyPromptSection`). Codes invalides retournés par DeepSeek sont filtrés ; le code `autre` sans description est rejeté. - Génération parallèle correction + modèle — option (b) : `generateProductionModele` démarre en même temps que `correctEE` avec `nclcObtenu = nclcCible - 1` comme estimation provisoire, `await` uniquement sur la correction pour répondre à la requête HTTP. - Exercices personnalisés fire-and-forget déclenchés après la résolution de la correction (dépendent de `rapport.erreurs_codes` et `rapport.criteres`). Format aligné sur les captures d'écran : `{difficulte, theme, diagnostic, consigne, extrait, indice, correction, explication}`. - Nouveaux champs dans `productions` : `revelation` (JSONB), `diagnostic` (TEXT), `conseil_nclc` (JSONB), `erreurs_codes` (JSONB), `exercices` (JSONB), `modele` (JSONB), `nclc_cible` (INTEGER), `exercices_status` / `modele_status` (TEXT, 'pending'/'ready'/'error'). - Migration SQL `supabase/migrations/004_sprint_3_6a_qualite_correction.sql` — première migration versionnée du projet (cf. backend TD-06) ; idempotente grâce à `IF NOT EXISTS`. - Paramètre `nclc_cible` optionnel sur `POST /corrections/ee` (défaut 9, valeurs acceptées : 9 ou 10 ; sinon 400 VALIDATION_ERROR). - Index GIN sur `erreurs_codes` pour préparer l'agrégation du Sprint 3.6c (analyse patterns). - Nouveau fichier de tests `src/controllers/__tests__/correctionController.test.ts` — 8 tests (parallélisme option b, statuts ready/error, nclc_cible propagé, simulation introuvable, autre utilisateur). - 2 tests ajoutés à `simulationController.test.ts` — `getById` renvoie `nclc_cible`, `exercices`, `modele` + statuts. - Logs d'erreur détaillés : `callDeepSeek` classifie TIMEOUT / ABORT / JSON_PARSE / NETWORK / OTHER ; `correctionController.correctEE` logue `{simulationId, tache, nclcCible, message, stack}` avant de retourner 500. - FTD-23 🟡 ajoutée dans `expria-frontend/docs/TECH_DEBT.md` — `useAutosave` peut fire un PATCH `/simulations/:id/contenu` après correction, ce qui retourne 400 VALIDATION_ERROR. À corriger dans une session dédiée (préexistant au Sprint 3.6a, détecté lors des tests manuels). ### Changed - `correctEE` dans `src/lib/deepseek.ts` — nouvelle signature `correctEE(CorrectionInput)` (contenu, tache, sujet, sourceDoc1/2, nclcCible) et nouvelle forme de retour `CorrectionRapport` (revelation, diagnostic, criteres avec exemple/suggestion/astuce, conseil_nclc, erreurs_codes). `EERapport` devient alias de `CorrectionRapport`. EO inchangé. - `correctionController.correctEE` — charge le sujet + documents T3 depuis Supabase pour alimenter le prompt maître ; persiste les nouveaux champs (revelation, diagnostic, conseil_nclc, erreurs_codes, nclc_cible) + statuts pending initiaux ; lance `runModeleJob` en parallèle (option b) et `runExercicesJob` après correction. - `simulationController.getById` — retourne désormais `nclc_cible`, `exercices`, `exercices_status`, `modele`, `modele_status` en plus du `rapport` enrichi ; fallback `'pending'` si les colonnes sont absentes (compat avec productions pré-migration). - Timeout DeepSeek côté backend : `callDeepSeek` abort à **55 s** via `AbortSignal.timeout(55_000)` (avant : aucun timeout) ; timeout frontend corrections monte de **30 s à 60 s** — marge de 5 s entre abort backend et abort client. - Routes `/simulations/*` : réorganisation défensive — les `PATCH /:id/contenu` et `PATCH /:id/sujet` sont déclarées avant `GET /:id` pour éviter tout risque de masquage. - `deepseek.test.ts` réécrit (25 tests) — couvre correctEE nouvelle signature, generateProductionModele, generateExercices, helpers post-traitement, EO inchangé. ### Notes - **Option A retenue** pour la compatibilité frontend : backend renvoie uniquement la nouvelle forme. Le Sprint 3.6b (frontend) est immédiatement suivant et corrige l'écran blanc sur `RapportPage`. - **Option (b) retenue** pour le parallélisme : modèle en parallèle avec correction (nclcObtenu estimé), exercices strictement après correction. - Migration SQL à exécuter manuellement via `supabase db push` ou SQL Editor du dashboard (cf. Règle F) — aucune exécution automatique. - Tests : **174 tests verts** (+19 vs baseline 155), 18 fichiers de tests. - TD-15 🟡 ouvert : si le process redémarre pendant un job fire-and-forget (modèle/exercices), le statut reste `pending` indéfiniment. À traiter après observation en production.