diff --git a/app/api/instructor/exercises/[id]/recalculate-scores/route.ts b/app/api/instructor/exercises/[id]/recalculate-scores/route.ts index 745b4b6..7a1e0de 100644 --- a/app/api/instructor/exercises/[id]/recalculate-scores/route.ts +++ b/app/api/instructor/exercises/[id]/recalculate-scores/route.ts @@ -42,23 +42,21 @@ export async function POST( const minResponseLength = ex.min_response_length ?? DEFAULT_MIN_LENGTH; const hasConstraints = ex.pass_mark !== null || ex.min_questions_required !== null || ex.flag_fails || ex.max_paste_chars !== null || ex.max_focus_loss !== null; - // 1. Mark submissions as final only if actually attempted + // 1. Mark submissions as final only if actually attempted (not skipped) // (non-empty response OR has edit events — handles both written and code questions) await sql` UPDATE submissions SET is_final = true WHERE session_id IN (SELECT id FROM sessions WHERE exercise_id = ${exerciseId}) AND is_final = false + AND status != 'skipped' AND ( LENGTH(TRIM(COALESCE(response_text, ''))) > 0 OR EXISTS (SELECT 1 FROM edit_events ee WHERE ee.submission_id = submissions.id) ) `; - // 2. Close open sessions - await sql` - UPDATE sessions SET closed_at = now() - WHERE exercise_id = ${exerciseId} AND closed_at IS NULL AND started_at IS NOT NULL - `; + // 2. Close open sessions — intentionally removed. + // Recalculate should only rescore; it must not force-close active sessions. // 3. Re-evaluate paste flags with threshold if (ex.max_paste_chars !== null) { @@ -174,7 +172,23 @@ export async function POST( const sessionData = await sql` SELECT s.id AS session_id, - SUM(CASE WHEN sub.is_final THEN 1 ELSE 0 END)::int AS final_count, + SUM(CASE + WHEN sub.is_final AND sub.status != 'skipped' AND ( + ( + SELECT CASE + WHEN q.test_cases IS NOT NULL THEN (CASE WHEN sub.tests_passed = true THEN 1 ELSE 0 END) + ELSE (CASE WHEN + LENGTH(TRIM(REPLACE(COALESCE(sub.response_text, ''), COALESCE(q.starter, ''), ''))) > 0 + OR EXISTS (SELECT 1 FROM edit_events ee WHERE ee.submission_id = sub.id) + THEN 1 ELSE 0 END) + END + FROM questions q + JOIN sessions s2 ON s2.exercise_id = q.exercise_id + WHERE s2.id = s.id AND q.question_index = sub.question_index + LIMIT 1 + ) = 1 + ) THEN 1 ELSE 0 + END)::int AS final_count, COALESCE(SUM( GREATEST(0, COALESCE(array_length(sub.flag_reasons, 1), 0) diff --git a/lib/scoring.ts b/lib/scoring.ts index 72f9f41..bf10b6c 100644 --- a/lib/scoring.ts +++ b/lib/scoring.ts @@ -51,10 +51,10 @@ export async function recalculateSessionScore(sessionId: string): Promise 0 + LENGTH(TRIM(REPLACE(COALESCE(sub.response_text, ''), COALESCE(q.starter, ''), ''))) > 0 OR EXISTS (SELECT 1 FROM edit_events ee WHERE ee.submission_id = sub.id) )) OR