Skip to content

feat: model-backed, evidence-cited quiz generation — no stubs left - #28

Merged
mikim merged 1 commit into
mainfrom
feat/quiz-engine-model
Jul 14, 2026
Merged

feat: model-backed, evidence-cited quiz generation — no stubs left#28
mikim merged 1 commit into
mainfrom
feat/quiz-engine-model

Conversation

@mikim

@mikim mikim commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Replaces the last stub in the codebase. The quiz engine now generates evidence-cited items (Claude-backed or deterministic mock) and the attempt loop is wired end to end: upload → quiz → graded attempt → auto-opened ErrorEpisode → review queue, all over HTTP.

The grounding design

Models are unreliable at character offsets, so the model never emits offsets — it copies verbatim evidence quotes, and resolveQuotesToCitations() anchors them deterministically (indexOf). A paraphrased or missing quote fails the whole batch. That turns the repo's "citations resolve 100%" gate into an invariant enforced in code, not an aspiration.

Persistence completes the chain: unit-relative offsets are mapped onto the immutable SourceRevision (unit.citationStart + offset), so QuizItemCitation → SourceSpan → SourceRevision.rawText always resolves — verified by integration test against real Postgres.

Contents

  • @study-os/quiz-engine: QuizProvider contract + provenance; AnthropicQuizProvider (claude-opus-4-8, adaptive thinking, structured outputs; <자료> treated as untrusted data; ambiguous items forbidden; every failure path fail-closed) + MockQuizProvider (deterministic, offline, all 3 types); type-specific validation (MCQ 3–5 choices/exactly 1 correct, ____ blanks, difficulty 1–5).
  • Korean-aware grading replaces the exact lowercased match: NFC normalization (NFD-composed Hangul now matches), whitespace collapse, trailing punctuation, acceptedAnswers; gradingMethod recorded on every raw Attempt.
  • persistQuizDraft (one transaction) + POST /api/units/:id/quiz + POST /api/quiz-items/:id/attempts — wrong answers auto-open ErrorEpisodes that appear in the existing review queue.

Verified

20 new quiz-engine unit tests (quote anchoring incl. paraphrase rejection, per-type contracts, determinism, NFC/NFD grading) + 6 CI integration tests walking the full loop against real Postgres. Local: lint/typecheck/build/smoke green, 100+ tests.

🤖 Generated with Claude Code

Replaces the last stub in the codebase. The quiz engine now generates
citation-carrying items and wires the full attempt loop.

@study-os/quiz-engine rework:
- QuizProvider contract: unit in -> evidence-cited items out, with
  GenerationRun provenance (provider/model/promptVersion/inputSha256/
  tokens)
- Quote-anchored citations: the model NEVER emits character offsets
  (unreliable) — it copies evidence quotes verbatim and
  resolveQuotesToCitations() anchors them deterministically via
  indexOf; a paraphrased or missing quote fails the whole batch.
  This makes 'citations resolve 100%' an invariant, not an aspiration.
- AnthropicQuizProvider: claude-opus-4-8, adaptive thinking, structured
  outputs; Korean prompt treats <자료> as untrusted data and forbids
  ambiguous multi-answer items; evidence_sufficient=false, refusal,
  truncation, malformed JSON all fail closed
- MockQuizProvider: deterministic, offline, all three quiz types,
  citations resolve by construction; default without ANTHROPIC_API_KEY
- Type-specific validation: MCQ 3-5 choices with exactly 1 correct,
  fill-in-the-blank requires a ____ marker, difficulty 1..5
- Korean-aware grading replaces exact lowercased match: NFC
  normalization, whitespace collapse, trailing-punctuation strip,
  acceptedAnswers support; gradingMethod recorded on every Attempt

Persistence + API (loop closure):
- persistQuizDraft(): GenerationRun + QuizSet + items + choices +
  SourceSpans in ONE transaction, with unit-relative citation offsets
  mapped onto the immutable SourceRevision (unit.citationStart + start)
  so QuizItemCitation -> SourceSpan -> SourceRevision.rawText resolves
- POST /api/units/:unitId/quiz: generate + persist + return cited items
  (422 if the unit lacks citation offsets; 502 fail-closed generation)
- POST /api/quiz-items/:quizItemId/attempts: normalized grading, raw
  Attempt (latency/confidence/gradingMethod), and a wrong answer
  auto-opens an ErrorEpisode(open) — upload -> quiz -> attempt ->
  episode -> review queue now runs end to end over HTTP

Tests: 20 quiz-engine unit tests (quote anchoring incl. paraphrase
rejection, per-type contracts, determinism, NFC/NFD grading) + 6 CI
integration tests walking the whole loop against real Postgres,
including the revision-mapped span resolution check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikim
mikim merged commit a580614 into main Jul 14, 2026
1 check passed
mikim added a commit that referenced this pull request Jul 14, 2026
#29)

- PR range #18#26#18#28
- 'application wiring lands with M2' → first half of the loop is live
  (attempt → auto ErrorEpisode → review queue); cause suggest/confirm
  + interventions are the next slice
- Remediation-model row updated to match
- Local-development endpoint list gains the quiz endpoints

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant