Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions app/api/instructor/exercises/[id]/recalculate-scores/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions lib/scoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export async function recalculateSessionScore(sessionId: string): Promise<ScoreR
// - code with test cases: is_final AND tests_passed = true
// - code without test cases: is_final AND has edit events (unchanged behavior)
const finalRows = await sql`
SELECT SUM(CASE WHEN sub.is_final AND (
-- Written question or code with no test cases: presence check
SELECT SUM(CASE WHEN sub.is_final AND sub.status != 'skipped' AND (
-- Written question or code with no test cases: must have content beyond the starter
(q.test_cases IS NULL AND (
LENGTH(TRIM(COALESCE(sub.response_text, ''))) > 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
Expand Down
Loading