feat(patterns): GET /users/patterns — agrégation erreurs récurrentes + exercices long terme + indice de préparation (Sprint 3.6c)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a394ce8429
commit
c48ae8d443
6 changed files with 1055 additions and 0 deletions
|
|
@ -609,6 +609,149 @@ export async function generateExercices(input: ExercicesInput): Promise<Exercice
|
|||
}))
|
||||
}
|
||||
|
||||
// ── Sprint 3.6c — Exercices long terme (patterns Premium) ──────────────
|
||||
|
||||
export interface PatternInput {
|
||||
code: string
|
||||
critere: Critere
|
||||
frequency: number
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export interface PatternExerciceItem {
|
||||
code: string
|
||||
critere: Critere
|
||||
diagnostic: string
|
||||
exercice: {
|
||||
consigne: string
|
||||
exemple: string
|
||||
correction: string
|
||||
astuce: string
|
||||
}
|
||||
}
|
||||
|
||||
const PATTERN_EXERCICES_SYSTEM = `Tu es un coach spécialisé dans la préparation au TCF Canada (Test de connaissance du français).
|
||||
|
||||
Un candidat commet systématiquement les mêmes erreurs sur ses 5 dernières productions écrites. Tu dois produire UN exercice ciblé par pattern d'erreur récurrent identifié.
|
||||
|
||||
CONTEXTE :
|
||||
- Ces exercices sont des exercices LONG TERME destinés à corriger des faiblesses structurelles récurrentes.
|
||||
- Ils sont DISTINCTS des exercices individuels générés après chaque correction (qui ciblent une production spécifique).
|
||||
- Tu n'as PAS accès au texte du candidat. Tes exemples doivent être génériques et représentatifs de l'erreur.
|
||||
|
||||
RÈGLES :
|
||||
1. Un exercice par pattern en entrée, dans le même ordre.
|
||||
2. Le diagnostic explique en 1-2 phrases POURQUOI cette erreur est problématique pour le TCF Canada.
|
||||
3. La consigne demande au candidat de corriger ou reformuler une phrase.
|
||||
4. L'exemple est une phrase incorrecte illustrant le pattern (inventée, pas tirée du candidat).
|
||||
5. La correction est la version correcte de l'exemple.
|
||||
6. L'astuce est un procédé mnémotechnique, une règle pratique ou un réflexe de relecture que le candidat doit appliquer APRÈS avoir rédigé son texte pour détecter et corriger cette erreur lui-même. Formulée comme un conseil direct et actionnable.
|
||||
Exemples d'astuces :
|
||||
- Subjonctif : "Après 'bien que', 'pourvu que', 'avant que' → le verbe qui suit est TOUJOURS au subjonctif. Relisez votre texte en cherchant ces expressions."
|
||||
- Accords : "Relisez chaque phrase en pointant du doigt le sujet et son verbe. S'ils sont éloignés, vérifiez l'accord."
|
||||
- Connecteurs : "Après rédaction, surlignez tous vos connecteurs. Si le même revient plus de 2 fois, remplacez-en un."
|
||||
7. Niveau de langue : NCLC 7-9 (ni trop simple, ni trop littéraire).
|
||||
8. Les exemples doivent être en contexte TCF Canada : courriels, lettres formelles, essais argumentatifs, situations professionnelles canadiennes.
|
||||
|
||||
FORMAT DE SORTIE — JSON strict, aucun texte avant ni après :
|
||||
{
|
||||
"exercises": [
|
||||
{
|
||||
"code": "<code_taxonomie>",
|
||||
"critere": "<critere>",
|
||||
"diagnostic": "<1-2 phrases>",
|
||||
"exercice": {
|
||||
"consigne": "<instruction au candidat>",
|
||||
"exemple": "<phrase incorrecte>",
|
||||
"correction": "<phrase corrigée>",
|
||||
"astuce": "<procédé mnémotechnique ou réflexe de relecture>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
function buildPatternExercicesUserPrompt(patterns: PatternInput[]): string {
|
||||
const lines = patterns.map((p) => {
|
||||
const desc = p.description ? ` — « ${p.description} »` : ''
|
||||
return `- ${p.code} (${p.critere}) — apparu ${p.frequency}/5 fois${desc}`
|
||||
})
|
||||
return `Voici les patterns d'erreurs récurrents détectés sur les 5 dernières productions du candidat :
|
||||
|
||||
${lines.join('\n')}
|
||||
|
||||
Produis un exercice ciblé par pattern. JSON strict uniquement.`
|
||||
}
|
||||
|
||||
export async function generatePatternExercices(
|
||||
patterns: PatternInput[],
|
||||
): Promise<PatternExerciceItem[]> {
|
||||
if (patterns.length === 0) return []
|
||||
|
||||
const response = await fetch(`${DEEPSEEK_BASE_URL}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${DEEPSEEK_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [
|
||||
{ role: 'system', content: PATTERN_EXERCICES_SYSTEM },
|
||||
{ role: 'user', content: buildPatternExercicesUserPrompt(patterns) },
|
||||
],
|
||||
temperature: 0.4,
|
||||
response_format: { type: 'json_object' },
|
||||
}),
|
||||
signal: AbortSignal.timeout(20_000),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DeepSeek API error: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const data = (await response.json()) as {
|
||||
choices?: { message?: { content?: string } }[]
|
||||
}
|
||||
const content = data.choices?.[0]?.message?.content
|
||||
if (!content) throw new Error('DeepSeek API: réponse vide')
|
||||
|
||||
const parsed = JSON.parse(content) as { exercises?: unknown }
|
||||
if (!Array.isArray(parsed.exercises)) {
|
||||
throw new Error('Réponse DeepSeek invalide : exercises doit être un tableau')
|
||||
}
|
||||
|
||||
const out: PatternExerciceItem[] = []
|
||||
for (const raw of parsed.exercises as unknown[]) {
|
||||
const item = raw as Record<string, unknown>
|
||||
const ex = item.exercice as Record<string, unknown> | undefined
|
||||
if (
|
||||
typeof item.code !== 'string' ||
|
||||
typeof item.critere !== 'string' ||
|
||||
typeof item.diagnostic !== 'string' ||
|
||||
!ex ||
|
||||
typeof ex.consigne !== 'string' ||
|
||||
typeof ex.exemple !== 'string' ||
|
||||
typeof ex.correction !== 'string' ||
|
||||
typeof ex.astuce !== 'string'
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (!isValidCritere(item.critere)) continue
|
||||
out.push({
|
||||
code: item.code,
|
||||
critere: item.critere,
|
||||
diagnostic: item.diagnostic,
|
||||
exercice: {
|
||||
consigne: ex.consigne,
|
||||
exemple: ex.exemple,
|
||||
correction: ex.correction,
|
||||
astuce: ex.astuce,
|
||||
},
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ── EO (Expression Orale) — inchangé par Sprint 3.6a ────────────────────
|
||||
|
||||
export interface EOCritere {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue