fix(corrections): race condition modele_status + logs diagnostiques
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
63bc43ddcf
commit
14d8d73991
2 changed files with 65 additions and 18 deletions
|
|
@ -138,7 +138,9 @@ describe('correctionController.correctEE — Sprint 3.6a', () => {
|
|||
expect(result.data.score).toBe(14)
|
||||
}
|
||||
|
||||
// La persistance de la correction inclut les nouveaux champs + statuts pending
|
||||
// La persistance de la correction inclut les nouveaux champs.
|
||||
// Les statuts ne sont PAS dans l'update principal (race condition — les
|
||||
// jobs async les pilotent exclusivement). DEFAULT 'pending' côté migration.
|
||||
const persisted = supabaseMock.updates.find(
|
||||
(u) => u.table === 'productions' && u.data.score !== undefined,
|
||||
)
|
||||
|
|
@ -147,13 +149,14 @@ describe('correctionController.correctEE — Sprint 3.6a', () => {
|
|||
score: 14,
|
||||
nclc: 9,
|
||||
nclc_cible: 9,
|
||||
exercices_status: 'pending',
|
||||
modele_status: 'pending',
|
||||
})
|
||||
expect(persisted!.data.revelation).toBeDefined()
|
||||
expect(persisted!.data.diagnostic).toBeDefined()
|
||||
expect(persisted!.data.conseil_nclc).toBeDefined()
|
||||
expect(persisted!.data.erreurs_codes).toBeDefined()
|
||||
// Race condition : ces champs ne doivent PAS être touchés par l'update principal.
|
||||
expect(persisted!.data.modele_status).toBeUndefined()
|
||||
expect(persisted!.data.exercices_status).toBeUndefined()
|
||||
})
|
||||
|
||||
it('modele_status passe à "ready" quand le job réussit', async () => {
|
||||
|
|
|
|||
|
|
@ -137,7 +137,13 @@ export async function correctEE(
|
|||
}
|
||||
}
|
||||
|
||||
// 4. Persister la correction (statuts modèle/exercices : pending — les jobs sont lancés juste après)
|
||||
// 4. Persister la correction.
|
||||
// ⚠️ RACE CONDITION — ne PAS inclure `modele_status` ni `exercices_status`
|
||||
// ici : runModeleJob (lancé en parallèle option b) peut avoir déjà terminé
|
||||
// et écrit 'ready' avant que cet update ne s'exécute. Écraser avec 'pending'
|
||||
// perdrait le résultat.
|
||||
// Les colonnes *_status sont initialisées à 'pending' par la migration
|
||||
// (DEFAULT) et gérées exclusivement par runModeleJob / runExercicesJob.
|
||||
const { error: updateError } = await supabase
|
||||
.from('productions')
|
||||
.update({
|
||||
|
|
@ -149,8 +155,6 @@ export async function correctEE(
|
|||
conseil_nclc: rapport.conseil_nclc,
|
||||
erreurs_codes: rapport.erreurs_codes,
|
||||
rapport: JSON.stringify(rapport),
|
||||
exercices_status: 'pending',
|
||||
modele_status: 'pending',
|
||||
})
|
||||
.eq('id', simulationId)
|
||||
|
||||
|
|
@ -190,17 +194,35 @@ interface ModeleJobInput {
|
|||
|
||||
async function runModeleJob(input: ModeleJobInput): Promise<void> {
|
||||
const { simulationId, tache, sujet, texte, nclcObtenu } = input
|
||||
console.log('[runModeleJob] START', { simulationId, tache, nclcObtenu })
|
||||
try {
|
||||
const modele = await generateProductionModele({ tache, sujet, texte, nclcObtenu })
|
||||
await supabase
|
||||
console.log('[runModeleJob] DeepSeek OK, updating productions', {
|
||||
simulationId,
|
||||
modeleWordCount: modele.tcf_word_count,
|
||||
})
|
||||
const { error: updateErr, data: updateData } = await supabase
|
||||
.from('productions')
|
||||
.update({ modele, modele_status: 'ready' })
|
||||
.eq('id', simulationId)
|
||||
} catch {
|
||||
await supabase
|
||||
.from('productions')
|
||||
.update({ modele_status: 'error' })
|
||||
.eq('id', simulationId)
|
||||
.select('id, modele_status')
|
||||
console.log('[runModeleJob] update result', { simulationId, updateErr, updateData })
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
const stack = err instanceof Error ? err.stack : undefined
|
||||
console.error('[runModeleJob] CAUGHT ERROR', { simulationId, message, stack })
|
||||
try {
|
||||
const { error: fallbackErr } = await supabase
|
||||
.from('productions')
|
||||
.update({ modele_status: 'error' })
|
||||
.eq('id', simulationId)
|
||||
console.log('[runModeleJob] fallback update result', { simulationId, fallbackErr })
|
||||
} catch (fallbackExc) {
|
||||
console.error('[runModeleJob] FALLBACK UPDATE THREW', {
|
||||
simulationId,
|
||||
message: fallbackExc instanceof Error ? fallbackExc.message : String(fallbackExc),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,21 +234,43 @@ interface ExercicesJobInput {
|
|||
|
||||
async function runExercicesJob(input: ExercicesJobInput): Promise<void> {
|
||||
const { simulationId, tache, rapport } = input
|
||||
console.log('[runExercicesJob] START', {
|
||||
simulationId,
|
||||
tache,
|
||||
erreursCodesCount: rapport.erreurs_codes.length,
|
||||
})
|
||||
try {
|
||||
const exercices = await generateExercices({
|
||||
tache,
|
||||
erreursCodes: rapport.erreurs_codes,
|
||||
criteres: rapport.criteres,
|
||||
})
|
||||
await supabase
|
||||
console.log('[runExercicesJob] DeepSeek OK, updating productions', {
|
||||
simulationId,
|
||||
exercicesCount: exercices.length,
|
||||
})
|
||||
const { error: updateErr, data: updateData } = await supabase
|
||||
.from('productions')
|
||||
.update({ exercices, exercices_status: 'ready' })
|
||||
.eq('id', simulationId)
|
||||
} catch {
|
||||
await supabase
|
||||
.from('productions')
|
||||
.update({ exercices_status: 'error' })
|
||||
.eq('id', simulationId)
|
||||
.select('id, exercices_status')
|
||||
console.log('[runExercicesJob] update result', { simulationId, updateErr, updateData })
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
const stack = err instanceof Error ? err.stack : undefined
|
||||
console.error('[runExercicesJob] CAUGHT ERROR', { simulationId, message, stack })
|
||||
try {
|
||||
const { error: fallbackErr } = await supabase
|
||||
.from('productions')
|
||||
.update({ exercices_status: 'error' })
|
||||
.eq('id', simulationId)
|
||||
console.log('[runExercicesJob] fallback update result', { simulationId, fallbackErr })
|
||||
} catch (fallbackExc) {
|
||||
console.error('[runExercicesJob] FALLBACK UPDATE THREW', {
|
||||
simulationId,
|
||||
message: fallbackExc instanceof Error ? fallbackExc.message : String(fallbackExc),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue