Boost Azure Demo

This commit is contained in:
MangoPig
2026-05-25 17:05:06 +01:00
parent 675285e99d
commit 4f79137d89
230 changed files with 43275 additions and 2644 deletions

View File

@@ -0,0 +1,391 @@
-- name: CreateAssignment :one
INSERT INTO assignments (
classroom_id,
teacher_id,
title,
instructions,
status,
due_at,
published_at,
pass_threshold
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8
)
RETURNING *;
-- name: AssignStudentToAssignment :exec
INSERT INTO assignment_assignees (
assignment_id,
student_id
) VALUES (
$1,
$2
)
ON CONFLICT (assignment_id, student_id) DO NOTHING;
-- name: DeleteAssignmentAssignee :exec
DELETE FROM assignment_assignees
WHERE assignment_id = $1
AND student_id = $2;
-- name: GetAssignmentAssignee :one
SELECT *
FROM assignment_assignees
WHERE assignment_id = $1
AND student_id = $2;
-- name: UpdateAssignmentAIReview :one
UPDATE assignment_assignees
SET ai_feedback = $3,
next_step_outcome = NULLIF($4::text, '')::assignment_next_step_outcome
WHERE assignment_id = $1
AND student_id = $2
RETURNING *;
-- name: UpdateAssignmentRedoPlan :one
UPDATE assignment_assignees
SET redo_plan = NULLIF($3::text, ''),
redo_plan_generated_at = CASE
WHEN NULLIF($3::text, '') IS NULL THEN NULL
ELSE NOW()
END
WHERE assignment_id = $1
AND student_id = $2
RETURNING *;
-- name: GetAssignmentRedoPlan :one
SELECT
assignment_id,
student_id,
redo_plan,
redo_plan_generated_at
FROM assignment_assignees
WHERE assignment_id = $1
AND student_id = $2;
-- name: UpdateAssignmentTeacherFeedback :one
WITH student_question_set AS (
SELECT asq.assignment_id, asq.question_id, asq.position
FROM assignment_student_questions asq
WHERE asq.assignment_id = $1
AND asq.student_id = $2
), selected_questions AS (
SELECT assignment_id, question_id, position
FROM student_question_set
UNION ALL
SELECT aq.assignment_id, aq.question_id, aq.position
FROM assignment_questions aq
WHERE aq.assignment_id = $1
AND NOT EXISTS (SELECT 1 FROM student_question_set)
), score_summary AS (
SELECT CASE
WHEN COUNT(sa.id) = 0 THEN NULL
ELSE ROUND((AVG(
CASE
WHEN sa.is_correct IS NULL THEN COALESCE(sa.review_understanding_score, 0)::NUMERIC
ELSE (
((CASE WHEN sa.is_correct THEN 1 ELSE 0 END)::NUMERIC) + COALESCE(sa.review_understanding_score, 0)::NUMERIC
) / 2
END
) * 10)::NUMERIC, 2)
END AS overall_score
FROM selected_questions aq
LEFT JOIN student_answers sa
ON sa.assignment_id = aq.assignment_id
AND sa.question_id = aq.question_id
AND sa.student_id = $2
WHERE aq.assignment_id = $1
), updated AS (
UPDATE assignment_assignees aa
SET teacher_feedback = $3,
pass_status_override = NULLIF($4::text, '')::assignment_pass_status,
next_step_outcome = NULLIF($5::text, '')::assignment_next_step_outcome,
overall_score = (SELECT overall_score FROM score_summary),
pass_status = COALESCE(
NULLIF($4::text, '')::assignment_pass_status,
CASE
WHEN (SELECT overall_score FROM score_summary) IS NULL THEN 'pending'::assignment_pass_status
WHEN (SELECT overall_score FROM score_summary) >= a.pass_threshold THEN 'pass'::assignment_pass_status
ELSE 'no_pass'::assignment_pass_status
END
)
FROM assignments a
WHERE aa.assignment_id = $1
AND aa.student_id = $2
AND a.id = aa.assignment_id
RETURNING aa.*
)
SELECT *
FROM updated;
-- name: AddQuestionToAssignment :exec
INSERT INTO assignment_questions (
assignment_id,
question_id,
position
) VALUES (
$1,
$2,
$3
)
ON CONFLICT (assignment_id, question_id) DO UPDATE
SET position = EXCLUDED.position;
-- name: DeleteAssignmentStudentQuestions :exec
DELETE FROM assignment_student_questions
WHERE assignment_id = $1
AND student_id = $2;
-- name: AddAssignmentStudentQuestion :one
INSERT INTO assignment_student_questions (
assignment_id,
student_id,
question_id,
position,
source_bucket,
source_topic,
source_difficulty,
generator_seed
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8
)
RETURNING *;
-- name: ListAssignmentStudentQuestions :many
SELECT *
FROM assignment_student_questions
WHERE assignment_id = $1
AND student_id = $2
ORDER BY position ASC, id ASC;
-- name: ListGeneratedQuestionsForAssignmentStudent :many
SELECT
asq.id,
asq.assignment_id,
asq.student_id,
asq.question_id,
asq.position,
asq.source_bucket,
asq.source_topic,
asq.source_difficulty,
asq.generator_seed,
asq.created_at,
q.author_teacher_id,
q.title,
q.prompt,
q.subject,
q.source,
q.status,
q.created_at AS question_created_at,
q.updated_at AS question_updated_at,
q.correct_answer,
q.topic,
q.difficulty
FROM assignment_student_questions asq
JOIN questions q ON q.id = asq.question_id
WHERE asq.assignment_id = $1
AND asq.student_id = $2
ORDER BY asq.position ASC, asq.id ASC;
-- name: ListAssignmentsByTeacher :many
SELECT *
FROM assignments
WHERE teacher_id = $1
ORDER BY created_at DESC;
-- name: ListAssignmentsForStudent :many
SELECT a.*
FROM assignment_assignees aa
JOIN assignments a ON a.id = aa.assignment_id
WHERE aa.student_id = $1
ORDER BY a.created_at DESC;
-- name: GetAssignmentByID :one
SELECT *
FROM assignments
WHERE id = $1;
-- name: UpdateAssignmentDraft :one
UPDATE assignments
SET classroom_id = $2,
title = $3,
instructions = $4,
due_at = $5,
pass_threshold = $6,
updated_at = NOW()
WHERE id = $1
RETURNING *;
-- name: CloseAssignment :one
UPDATE assignments
SET status = 'closed'::assignment_status,
updated_at = NOW()
WHERE id = $1
RETURNING *;
-- name: ListQuestionsForAssignment :many
SELECT
aq.assignment_id,
aq.question_id,
aq.position,
q.author_teacher_id,
q.title,
q.prompt,
q.subject,
q.source,
q.status,
q.created_at,
q.updated_at
FROM assignment_questions aq
JOIN questions q ON q.id = aq.question_id
WHERE aq.assignment_id = $1
ORDER BY aq.position ASC, aq.question_id ASC;
-- name: GetAssignmentReviewSummary :one
WITH student_question_set AS (
SELECT asq.student_id, asq.question_id, asq.position
FROM assignment_student_questions asq
WHERE asq.assignment_id = $1
),
students_with_personalized AS (
SELECT DISTINCT student_id
FROM student_question_set
),
selected_questions AS (
SELECT student_id, question_id, position
FROM student_question_set
UNION ALL
SELECT aa.student_id, aq.question_id, aq.position
FROM assignment_assignees aa
JOIN assignment_questions aq ON aq.assignment_id = aa.assignment_id
WHERE aa.assignment_id = $1
AND NOT EXISTS (
SELECT 1
FROM students_with_personalized swp
WHERE swp.student_id = aa.student_id
)
),
student_states AS (
SELECT
aa.student_id,
COUNT(sq.question_id)::BIGINT AS total_questions,
CASE
WHEN COUNT(sa.id) = 0 THEN 'not_started'::answer_status
WHEN COUNT(sq.question_id) > 0
AND COUNT(sa.id) FILTER (WHERE sa.status = 'reviewed') = COUNT(sq.question_id)
THEN 'reviewed'::answer_status
WHEN COUNT(sa.id) FILTER (WHERE sa.status = 'submitted') > 0 THEN 'submitted'::answer_status
WHEN COUNT(sa.id) FILTER (WHERE sa.status = 'in_progress') > 0 THEN 'in_progress'::answer_status
WHEN COUNT(sa.id) FILTER (WHERE sa.status = 'reviewed') > 0 THEN 'in_progress'::answer_status
ELSE 'not_started'::answer_status
END AS review_status
FROM assignment_assignees aa
LEFT JOIN selected_questions sq
ON sq.student_id = aa.student_id
LEFT JOIN student_answers sa
ON sa.assignment_id = aa.assignment_id
AND sa.question_id = sq.question_id
AND sa.student_id = aa.student_id
WHERE aa.assignment_id = $1
GROUP BY aa.student_id
)
SELECT
$1::BIGINT AS assignment_id,
COALESCE(MAX(student_states.total_questions), 0)::BIGINT AS total_questions,
COUNT(*)::BIGINT AS total_assigned,
COUNT(*) FILTER (WHERE review_status = 'not_started')::BIGINT AS not_started,
COUNT(*) FILTER (WHERE review_status = 'in_progress')::BIGINT AS in_progress,
COUNT(*) FILTER (WHERE review_status = 'submitted')::BIGINT AS submitted,
COUNT(*) FILTER (WHERE review_status = 'reviewed')::BIGINT AS reviewed
FROM student_states;
-- name: ListAssignmentReviewQueue :many
WITH student_question_set AS (
SELECT asq.student_id, asq.question_id, asq.position
FROM assignment_student_questions asq
WHERE asq.assignment_id = $1
),
students_with_personalized AS (
SELECT DISTINCT student_id
FROM student_question_set
),
selected_questions AS (
SELECT student_id, question_id, position
FROM student_question_set
UNION ALL
SELECT aa.student_id, aq.question_id, aq.position
FROM assignment_assignees aa
JOIN assignment_questions aq ON aq.assignment_id = aa.assignment_id
WHERE aa.assignment_id = $1
AND NOT EXISTS (
SELECT 1
FROM students_with_personalized swp
WHERE swp.student_id = aa.student_id
)
),
student_states AS (
SELECT
aa.assignment_id,
aa.student_id,
aa.next_step_outcome,
u.full_name AS student_name,
u.email AS student_email,
COUNT(sq.question_id)::BIGINT AS total_questions,
COUNT(sa.id)::BIGINT AS answered_questions,
COUNT(sa.id) FILTER (WHERE sa.status = 'reviewed')::BIGINT AS reviewed_questions,
COUNT(sa.id) FILTER (WHERE sa.status = 'submitted')::BIGINT AS submitted_questions,
COUNT(sa.id) FILTER (WHERE sa.status = 'in_progress')::BIGINT AS in_progress_questions,
MAX(sa.submitted_at)::timestamptz AS latest_submitted_at,
MAX(sa.reviewed_at)::timestamptz AS latest_reviewed_at,
CASE
WHEN COUNT(sa.id) = 0 THEN 'not_started'::answer_status
WHEN COUNT(sq.question_id) > 0
AND COUNT(sa.id) FILTER (WHERE sa.status = 'reviewed') = COUNT(sq.question_id)
THEN 'reviewed'::answer_status
WHEN COUNT(sa.id) FILTER (WHERE sa.status = 'submitted') > 0 THEN 'submitted'::answer_status
WHEN COUNT(sa.id) FILTER (WHERE sa.status = 'in_progress') > 0 THEN 'in_progress'::answer_status
WHEN COUNT(sa.id) FILTER (WHERE sa.status = 'reviewed') > 0 THEN 'in_progress'::answer_status
ELSE 'not_started'::answer_status
END AS review_status
FROM assignment_assignees aa
JOIN users u ON u.id = aa.student_id
LEFT JOIN selected_questions sq
ON sq.student_id = aa.student_id
LEFT JOIN student_answers sa
ON sa.assignment_id = aa.assignment_id
AND sa.question_id = sq.question_id
AND sa.student_id = aa.student_id
WHERE aa.assignment_id = $1
GROUP BY aa.assignment_id, aa.student_id, aa.next_step_outcome, u.full_name, u.email
)
SELECT
student_states.assignment_id,
student_states.student_id,
student_states.next_step_outcome,
student_states.student_name,
student_states.student_email,
student_states.total_questions,
student_states.answered_questions,
student_states.reviewed_questions,
student_states.submitted_questions,
student_states.in_progress_questions,
student_states.review_status,
student_states.latest_submitted_at,
student_states.latest_reviewed_at
FROM student_states
WHERE ($2::text = '' OR review_status::text = $2::text)
ORDER BY student_states.student_name ASC, student_states.student_id ASC;

View File

@@ -0,0 +1,36 @@
-- name: CreateClassroom :one
INSERT INTO classrooms (
teacher_id,
name,
code,
description
) VALUES (
$1,
$2,
$3,
$4
)
RETURNING *;
-- name: ListClassroomsByTeacher :many
SELECT *
FROM classrooms
WHERE teacher_id = $1
ORDER BY created_at DESC;
-- name: AddStudentToClassroom :exec
INSERT INTO classroom_students (
classroom_id,
student_id
) VALUES (
$1,
$2
)
ON CONFLICT (classroom_id, student_id) DO NOTHING;
-- name: ListStudentsForClassroom :many
SELECT u.*
FROM classroom_students cs
JOIN users u ON u.id = cs.student_id
WHERE cs.classroom_id = $1
ORDER BY u.full_name ASC;

View File

@@ -0,0 +1,266 @@
-- name: ListMessageRecipientsForUser :many
SELECT
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
p.preferred_name,
p.profile_icon_url,
p.headline
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.id <> $1
AND u.is_active = TRUE
AND (
EXISTS (
SELECT 1
FROM classrooms c
JOIN classroom_students cs ON cs.classroom_id = c.id
WHERE c.teacher_id = u.id
AND cs.student_id = $1
)
OR EXISTS (
SELECT 1
FROM classrooms c
JOIN classroom_students cs ON cs.classroom_id = c.id
WHERE c.teacher_id = $1
AND cs.student_id = u.id
)
)
ORDER BY COALESCE(NULLIF(p.preferred_name, ''), u.full_name) ASC, u.id ASC;
-- name: GetMessageRecipientByIDForUser :one
SELECT
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
p.preferred_name,
p.profile_icon_url,
p.headline
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.id = $2
AND u.id <> $1
AND u.is_active = TRUE
AND (
EXISTS (
SELECT 1
FROM classrooms c
JOIN classroom_students cs ON cs.classroom_id = c.id
WHERE c.teacher_id = u.id
AND cs.student_id = $1
)
OR EXISTS (
SELECT 1
FROM classrooms c
JOIN classroom_students cs ON cs.classroom_id = c.id
WHERE c.teacher_id = $1
AND cs.student_id = u.id
)
)
LIMIT 1;
-- name: ListMessageThreadsForUser :many
SELECT
t.id AS thread_id,
t.subject,
t.created_by_user_id,
t.created_at AS thread_created_at,
t.updated_at AS thread_updated_at,
COALESCE(last_message.id, 0)::bigint AS last_message_id,
COALESCE(last_message.body, '') AS last_message_body,
last_message.created_at AS last_message_created_at,
COALESCE(last_message.sender_user_id, 0)::bigint AS last_message_sender_user_id,
sender.full_name AS last_message_sender_full_name,
sender_profile.preferred_name AS last_message_sender_preferred_name,
sender_profile.profile_icon_url AS last_message_sender_profile_icon_url,
COALESCE((
SELECT COUNT(*)::bigint
FROM messages unread
WHERE unread.thread_id = t.id
AND unread.sender_user_id <> $1
AND (participant.last_read_at IS NULL OR unread.created_at > participant.last_read_at)
), 0)::bigint AS unread_count
FROM message_thread_participants participant
JOIN message_threads t ON t.id = participant.thread_id
LEFT JOIN LATERAL (
SELECT m.id, m.body, m.created_at, m.sender_user_id
FROM messages m
WHERE m.thread_id = t.id
ORDER BY m.created_at DESC, m.id DESC
LIMIT 1
) AS last_message ON TRUE
LEFT JOIN users sender ON sender.id = last_message.sender_user_id
LEFT JOIN profiles sender_profile ON sender_profile.user_id = sender.id
WHERE participant.user_id = $1
AND participant.archived_at IS NULL
ORDER BY COALESCE(last_message.created_at, t.updated_at) DESC, t.id DESC;
-- name: ListMessageThreadParticipantsForUser :many
SELECT
mtp.thread_id,
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
p.preferred_name,
p.profile_icon_url,
p.headline,
mtp.joined_at,
mtp.last_read_at,
mtp.archived_at
FROM message_thread_participants mtp
JOIN users u ON u.id = mtp.user_id
LEFT JOIN profiles p ON p.user_id = u.id
WHERE mtp.thread_id IN (
SELECT participant.thread_id
FROM message_thread_participants participant
WHERE participant.user_id = $1
AND participant.archived_at IS NULL
)
ORDER BY mtp.thread_id ASC, COALESCE(NULLIF(p.preferred_name, ''), u.full_name) ASC, u.id ASC;
-- name: GetMessageThreadForUser :one
SELECT
t.id,
t.subject,
t.created_by_user_id,
t.created_at,
t.updated_at,
participant.last_read_at,
COALESCE((
SELECT COUNT(*)::bigint
FROM messages unread
WHERE unread.thread_id = t.id
AND unread.sender_user_id <> $2
AND (participant.last_read_at IS NULL OR unread.created_at > participant.last_read_at)
), 0)::bigint AS unread_count
FROM message_threads t
JOIN message_thread_participants participant ON participant.thread_id = t.id
WHERE t.id = $1
AND participant.user_id = $2
AND participant.archived_at IS NULL;
-- name: ListMessagesForThreadForUser :many
SELECT
m.id,
m.thread_id,
m.sender_user_id,
m.body,
m.created_at,
m.updated_at,
sender.email AS sender_email,
sender.role AS sender_role,
sender.full_name AS sender_full_name,
sender_profile.preferred_name AS sender_preferred_name,
sender_profile.profile_icon_url AS sender_profile_icon_url,
sender_profile.headline AS sender_headline
FROM messages m
JOIN message_thread_participants participant ON participant.thread_id = m.thread_id
JOIN users sender ON sender.id = m.sender_user_id
LEFT JOIN profiles sender_profile ON sender_profile.user_id = sender.id
WHERE m.thread_id = $1
AND participant.user_id = $2
AND participant.archived_at IS NULL
ORDER BY m.created_at ASC, m.id ASC;
-- name: ListParticipantsForThreadForUser :many
SELECT
mtp.thread_id,
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
p.preferred_name,
p.profile_icon_url,
p.headline,
mtp.joined_at,
mtp.last_read_at,
mtp.archived_at
FROM message_thread_participants mtp
JOIN users u ON u.id = mtp.user_id
LEFT JOIN profiles p ON p.user_id = u.id
WHERE mtp.thread_id = $1
AND EXISTS (
SELECT 1
FROM message_thread_participants participant
WHERE participant.thread_id = mtp.thread_id
AND participant.user_id = $2
AND participant.archived_at IS NULL
)
ORDER BY COALESCE(NULLIF(p.preferred_name, ''), u.full_name) ASC, u.id ASC;
-- name: CreateMessageThread :one
INSERT INTO message_threads (
created_by_user_id,
subject
) VALUES (
$1,
$2
)
RETURNING *;
-- name: AddMessageThreadParticipant :exec
INSERT INTO message_thread_participants (
thread_id,
user_id,
last_read_at
) VALUES (
$1,
$2,
$3
)
ON CONFLICT (thread_id, user_id) DO NOTHING;
-- name: CreateThreadMessage :one
INSERT INTO messages (
thread_id,
sender_user_id,
body
) VALUES (
$1,
$2,
$3
)
RETURNING *;
-- name: TouchMessageThread :exec
UPDATE message_threads
SET updated_at = NOW()
WHERE id = $1;
-- name: UpdateMessageThreadSubject :one
UPDATE message_threads
SET subject = sqlc.arg(subject),
updated_at = NOW()
WHERE id = sqlc.arg(thread_id)
RETURNING *;
-- name: UpdateThreadMessageBody :one
UPDATE messages
SET body = sqlc.arg(body),
updated_at = NOW()
WHERE id = sqlc.arg(message_id)
AND thread_id = sqlc.arg(thread_id)
AND sender_user_id = sqlc.arg(user_id)
RETURNING *;
-- name: DeleteThreadMessage :one
DELETE FROM messages
WHERE id = sqlc.arg(message_id)
AND thread_id = sqlc.arg(thread_id)
AND sender_user_id = sqlc.arg(user_id)
RETURNING *;
-- name: DeleteMessageThread :one
DELETE FROM message_threads
WHERE id = sqlc.arg(thread_id)
RETURNING *;
-- name: MarkMessageThreadRead :one
UPDATE message_thread_participants
SET last_read_at = COALESCE((SELECT MAX(m.created_at) FROM messages m WHERE m.thread_id = $1), NOW())
WHERE message_thread_participants.thread_id = $1
AND message_thread_participants.user_id = $2
RETURNING *;

View File

@@ -0,0 +1,55 @@
-- name: CreateQuestion :one
INSERT INTO questions (
author_teacher_id,
title,
prompt,
topic,
subject,
difficulty,
source,
status,
correct_answer
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9
)
RETURNING *;
-- name: ListQuestionsByTeacher :many
SELECT *
FROM questions
WHERE author_teacher_id = $1
ORDER BY created_at DESC;
-- name: GetQuestionByID :one
SELECT *
FROM questions
WHERE id = $1;
-- name: CreateTag :one
INSERT INTO tags (name)
VALUES ($1)
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
RETURNING *;
-- name: AttachTagToQuestion :exec
INSERT INTO question_tags (
question_id,
tag_id
) VALUES (
$1,
$2
)
ON CONFLICT (question_id, tag_id) DO NOTHING;
-- name: ListTags :many
SELECT *
FROM tags
ORDER BY name ASC;

View File

@@ -0,0 +1,228 @@
-- name: UpsertStudentAnswer :one
INSERT INTO student_answers (
assignment_id,
question_id,
student_id,
answer_text,
solve_mode,
working_steps,
ai_feedback,
teacher_feedback,
status,
submitted_at,
reviewed_at,
is_correct
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12
)
ON CONFLICT (assignment_id, question_id, student_id) DO UPDATE
SET
answer_text = EXCLUDED.answer_text,
solve_mode = EXCLUDED.solve_mode,
working_steps = EXCLUDED.working_steps,
ai_feedback = EXCLUDED.ai_feedback,
teacher_feedback = EXCLUDED.teacher_feedback,
status = EXCLUDED.status,
submitted_at = EXCLUDED.submitted_at,
reviewed_at = EXCLUDED.reviewed_at,
is_correct = EXCLUDED.is_correct,
updated_at = NOW()
RETURNING *;
-- name: UpdateAnswerAIReview :one
UPDATE student_answers
SET
ai_feedback = $2,
review_needs_attention = $3,
review_issue_reason = $4,
review_correctness_score = $5,
review_understanding_score = $6,
review_question_score = $7,
review_confidence = $8,
updated_at = NOW()
WHERE id = $1
RETURNING *;
-- name: ListAnswersForAssignment :many
SELECT *
FROM student_answers
WHERE assignment_id = $1
ORDER BY created_at ASC;
-- name: ListAnswersForStudent :many
SELECT *
FROM student_answers
WHERE student_id = $1
ORDER BY created_at DESC;
-- name: ListQuestionDetailsForAssignmentStudent :many
WITH student_question_set AS (
SELECT
asq.assignment_id,
asq.question_id,
asq.position
FROM assignment_student_questions asq
WHERE asq.assignment_id = $1
AND asq.student_id = $2
),
selected_questions AS (
SELECT
sq.assignment_id,
sq.question_id,
sq.position
FROM student_question_set sq
UNION ALL
SELECT
aq.assignment_id,
aq.question_id,
aq.position
FROM assignment_questions aq
WHERE aq.assignment_id = $1
AND NOT EXISTS (SELECT 1 FROM student_question_set)
)
SELECT
aq.assignment_id,
aq.question_id,
aq.position,
q.title,
q.prompt,
q.subject,
q.source,
COALESCE(
ARRAY(
SELECT t.name
FROM question_tags qt
JOIN tags t ON t.id = qt.tag_id
WHERE qt.question_id = aq.question_id
ORDER BY t.name ASC
),
ARRAY[]::TEXT[]
)::TEXT[] AS question_tags,
q.status AS question_status,
q.correct_answer,
aa.ai_feedback AS assignment_ai_feedback,
aa.teacher_feedback AS assignment_teacher_feedback,
review_summary.overall_score,
a.pass_threshold,
aa.next_step_outcome,
aa.pass_status_override,
COALESCE(
aa.pass_status_override,
CASE
WHEN review_summary.overall_score IS NULL THEN 'pending'::assignment_pass_status
WHEN review_summary.overall_score >= a.pass_threshold THEN 'pass'::assignment_pass_status
ELSE 'no_pass'::assignment_pass_status
END
) AS pass_status,
sa.id AS answer_id,
sa.student_id,
sa.answer_text,
sa.solve_mode,
sa.working_steps,
sa.is_correct,
sa.ai_feedback,
sa.teacher_feedback,
sa.status AS answer_status,
sa.review_needs_attention,
sa.review_issue_reason,
sa.review_correctness_score,
sa.review_understanding_score,
sa.review_question_score,
sa.review_confidence,
sa.review_tags,
sa.submitted_at,
sa.reviewed_at,
sa.created_at AS answer_created_at,
sa.updated_at AS answer_updated_at
FROM selected_questions aq
JOIN assignments a ON a.id = aq.assignment_id
JOIN questions q ON q.id = aq.question_id
LEFT JOIN assignment_assignees aa
ON aa.assignment_id = aq.assignment_id
AND aa.student_id = $2
LEFT JOIN LATERAL (
SELECT CASE
WHEN COUNT(sa2.id) = 0 THEN NULL::NUMERIC(5,2)
ELSE ROUND((AVG(
CASE
WHEN sa2.is_correct IS NULL THEN COALESCE(sa2.review_understanding_score, 0)::NUMERIC
ELSE (
((CASE WHEN sa2.is_correct THEN 1 ELSE 0 END)::NUMERIC) + COALESCE(sa2.review_understanding_score, 0)::NUMERIC
) / 2
END
) * 10)::NUMERIC, 2)::NUMERIC(5,2)
END AS overall_score
FROM selected_questions aq2
LEFT JOIN student_answers sa2
ON sa2.assignment_id = aq2.assignment_id
AND sa2.question_id = aq2.question_id
AND sa2.student_id = $2
WHERE aq2.assignment_id = aq.assignment_id
) review_summary ON TRUE
LEFT JOIN student_answers sa
ON sa.assignment_id = aq.assignment_id
AND sa.question_id = aq.question_id
AND sa.student_id = $2
WHERE aq.assignment_id = $1
ORDER BY aq.position ASC, aq.question_id ASC;
-- name: UpdateAnswerReview :one
UPDATE student_answers
SET
status = $2,
review_needs_attention = $3,
review_issue_reason = $4,
review_correctness_score = $5,
review_understanding_score = $6,
review_question_score = $7,
review_confidence = $8,
review_tags = $9,
reviewed_at = CASE
WHEN $2::answer_status = 'reviewed' THEN NOW()
ELSE NULL
END,
updated_at = NOW()
WHERE id = $1
RETURNING *;
-- name: ListStudentPlanningPerformance :many
SELECT
sa.assignment_id,
sa.question_id,
q.topic,
q.subject,
q.difficulty,
COALESCE(
ARRAY(
SELECT t.name
FROM question_tags qt
JOIN tags t ON t.id = qt.tag_id
WHERE qt.question_id = sa.question_id
ORDER BY t.name ASC
),
ARRAY[]::TEXT[]
)::TEXT[] AS question_tags,
sa.is_correct,
sa.review_understanding_score,
sa.review_needs_attention,
sa.review_issue_reason,
sa.status,
sa.submitted_at,
sa.reviewed_at,
sa.updated_at
FROM student_answers sa
JOIN questions q ON q.id = sa.question_id
WHERE sa.student_id = $1
AND sa.status IN ('submitted'::answer_status, 'reviewed'::answer_status)
ORDER BY COALESCE(sa.reviewed_at, sa.submitted_at, sa.updated_at) DESC, sa.id DESC;

View File

@@ -0,0 +1,178 @@
-- name: CreateUser :one
INSERT INTO users (
email,
password_hash,
role,
full_name
) VALUES (
$1,
$2,
$3,
$4
)
RETURNING *;
-- name: GetUserByID :one
SELECT *
FROM users
WHERE id = $1;
-- name: GetAuthUserByID :one
SELECT
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
u.is_active AS user_is_active,
u.created_at AS user_created_at,
u.updated_at AS user_updated_at,
p.user_id AS profile_user_id,
p.preferred_name,
p.profile_icon_url,
p.headline,
p.bio,
p.timezone,
p.locale,
p.grade_level,
p.learning_goal,
p.created_at AS profile_created_at,
p.updated_at AS profile_updated_at
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.id = $1;
-- name: GetUserByEmail :one
SELECT *
FROM users
WHERE email = $1;
-- name: GetAuthUserByEmail :one
SELECT
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
u.is_active AS user_is_active,
u.password_hash AS user_password_hash,
u.created_at AS user_created_at,
u.updated_at AS user_updated_at,
p.user_id AS profile_user_id,
p.preferred_name,
p.profile_icon_url,
p.headline,
p.bio,
p.timezone,
p.locale,
p.grade_level,
p.learning_goal,
p.created_at AS profile_created_at,
p.updated_at AS profile_updated_at
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.email = $1;
-- name: ListUsersByRole :many
SELECT *
FROM users
WHERE role = $1
ORDER BY full_name ASC;
-- name: ListUsersWithProfileByRole :many
SELECT
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
u.is_active AS user_is_active,
u.created_at AS user_created_at,
u.updated_at AS user_updated_at,
p.user_id AS profile_user_id,
p.preferred_name,
p.profile_icon_url,
p.headline,
p.bio,
p.timezone,
p.locale,
p.grade_level,
p.learning_goal,
p.created_at AS profile_created_at,
p.updated_at AS profile_updated_at
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.role = $1
ORDER BY u.full_name ASC;
-- name: GetUserWithProfileByID :one
SELECT
u.id AS user_id,
u.email AS user_email,
u.role AS user_role,
u.full_name AS user_full_name,
u.is_active AS user_is_active,
u.created_at AS user_created_at,
u.updated_at AS user_updated_at,
p.user_id AS profile_user_id,
p.preferred_name,
p.profile_icon_url,
p.headline,
p.bio,
p.timezone,
p.locale,
p.grade_level,
p.learning_goal,
p.created_at AS profile_created_at,
p.updated_at AS profile_updated_at
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.id = $1;
-- name: UpdateUserActiveStatus :one
UPDATE users
SET
is_active = $2,
updated_at = NOW()
WHERE id = $1
RETURNING *;
-- name: UpdateUserFullName :one
UPDATE users
SET
full_name = $2,
updated_at = NOW()
WHERE id = $1
RETURNING *;
-- name: UpsertUserProfile :one
INSERT INTO profiles (
user_id,
preferred_name,
profile_icon_url,
headline,
bio,
timezone,
locale,
grade_level,
learning_goal
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9
)
ON CONFLICT (user_id) DO UPDATE
SET
preferred_name = EXCLUDED.preferred_name,
profile_icon_url = EXCLUDED.profile_icon_url,
headline = EXCLUDED.headline,
bio = EXCLUDED.bio,
timezone = EXCLUDED.timezone,
locale = EXCLUDED.locale,
grade_level = EXCLUDED.grade_level,
learning_goal = EXCLUDED.learning_goal,
updated_at = NOW()
RETURNING *;