@@ -42,23 +42,21 @@ export async function POST(
4242 const minResponseLength = ex . min_response_length ?? DEFAULT_MIN_LENGTH ;
4343 const hasConstraints = ex . pass_mark !== null || ex . min_questions_required !== null || ex . flag_fails || ex . max_paste_chars !== null || ex . max_focus_loss !== null ;
4444
45- // 1. Mark submissions as final only if actually attempted
45+ // 1. Mark submissions as final only if actually attempted (not skipped)
4646 // (non-empty response OR has edit events — handles both written and code questions)
4747 await sql `
4848 UPDATE submissions SET is_final = true
4949 WHERE session_id IN (SELECT id FROM sessions WHERE exercise_id = ${ exerciseId } )
5050 AND is_final = false
51+ AND status != 'skipped'
5152 AND (
5253 LENGTH(TRIM(COALESCE(response_text, ''))) > 0
5354 OR EXISTS (SELECT 1 FROM edit_events ee WHERE ee.submission_id = submissions.id)
5455 )
5556 ` ;
5657
57- // 2. Close open sessions
58- await sql `
59- UPDATE sessions SET closed_at = now()
60- WHERE exercise_id = ${ exerciseId } AND closed_at IS NULL AND started_at IS NOT NULL
61- ` ;
58+ // 2. Close open sessions — intentionally removed.
59+ // Recalculate should only rescore; it must not force-close active sessions.
6260
6361 // 3. Re-evaluate paste flags with threshold
6462 if ( ex . max_paste_chars !== null ) {
@@ -174,7 +172,23 @@ export async function POST(
174172 const sessionData = await sql `
175173 SELECT
176174 s.id AS session_id,
177- SUM(CASE WHEN sub.is_final THEN 1 ELSE 0 END)::int AS final_count,
175+ SUM(CASE
176+ WHEN sub.is_final AND sub.status != 'skipped' AND (
177+ (
178+ SELECT CASE
179+ WHEN q.test_cases IS NOT NULL THEN (CASE WHEN sub.tests_passed = true THEN 1 ELSE 0 END)
180+ ELSE (CASE WHEN
181+ LENGTH(TRIM(REPLACE(COALESCE(sub.response_text, ''), COALESCE(q.starter, ''), ''))) > 0
182+ OR EXISTS (SELECT 1 FROM edit_events ee WHERE ee.submission_id = sub.id)
183+ THEN 1 ELSE 0 END)
184+ END
185+ FROM questions q
186+ JOIN sessions s2 ON s2.exercise_id = q.exercise_id
187+ WHERE s2.id = s.id AND q.question_index = sub.question_index
188+ LIMIT 1
189+ ) = 1
190+ ) THEN 1 ELSE 0
191+ END)::int AS final_count,
178192 COALESCE(SUM(
179193 GREATEST(0,
180194 COALESCE(array_length(sub.flag_reasons, 1), 0)
0 commit comments