feat(exam): batches, scans, marks, results, CSV (S4-6)
Adds routers/exam/batches.py (mounted alongside templates under /api/exam):
- POST/GET /batches — batch creation seeds the cohort from class_students AS
THE USER (cs_read requires caller teaches/admins the class); each active
enrollee becomes a student_submissions row (status='absent') so no student
is ever dropped from results (A7). Display names denormalised via a
documented service-role profiles read (deny-all as-user, E4).
- GET /batches/{id}/queue — submissions + per-submission mark counts + progress.
- GET /batches/{id}/results + /csv — every roster student incl. absent (blank
marks/total); CSV row always present (A7 baked into the contract).
- PUT /marks/{id} — upsert; batch_id derived server-side from the submission
(client never supplies the RLS scoping key).
- POST /batches/{id}/scans — E3 guards: MIME check, hard size ceiling (chunked
read), %PDF magic-byte sniff; owner-only; stores via service-role storage;
manual/ordered matching (QR-decode is a follow-on, no QR fixtures yet).
Unit tests cover batch/roster-seed/list, queue, results+CSV A7, mark upsert
round-trip, and all scan guards + owner check.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
96f9fb2446
commit
5ad9c01cde
@@ -102,3 +102,28 @@ class PatchQuestionRequest(BaseModel):
|
||||
mark_scheme: Optional[Dict[str, Any]] = None
|
||||
is_container: Optional[bool] = None
|
||||
spec_ref: Optional[str] = None
|
||||
|
||||
|
||||
# ─── Marking batches & marks ─────────────────────────────────────────────────
|
||||
|
||||
class CreateBatchRequest(BaseModel):
|
||||
template_id: str
|
||||
# When a class is given, the roster (class_students, status='active') is materialised as
|
||||
# student_submissions(status='absent') so every enrolled student appears in results (A7).
|
||||
class_id: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
|
||||
|
||||
class MarkUpsertRequest(BaseModel):
|
||||
"""Upsert one mark entry (PUT /marks/{id}; id is the mark_entry uuid).
|
||||
|
||||
batch_id is derived server-side from the submission, so the client never sets the RLS
|
||||
scoping key. submission_id + question_id identify what is being marked.
|
||||
"""
|
||||
submission_id: str
|
||||
question_id: str
|
||||
awarded_marks: float = 0
|
||||
mark_scheme_detail: Optional[Dict[str, Any]] = None
|
||||
annotation_shape_ids: Optional[Any] = None
|
||||
comment: Optional[str] = None
|
||||
confirmed: Optional[bool] = None
|
||||
|
||||
Reference in New Issue
Block a user