feat(analytics): comprehension-check accuracy event — the primary AI metric (#866) - #889
Conversation
…metric (#866) Instruments first-attempt accuracy on the comprehension check that gates applying an AI-proposed patch. The check itself is unchanged; this adds the event that was missing (no trackEvent fired anywhere in the AI partner flow). - `comprehension_check_answered` { lessonId, correct, attempt, courseId? } - never deduped — every answer is signal; later attempts carry the running counter so remediation depth is measurable, and attempt=1 is the metric - `attempt` is per CHECK INSTANCE, keyed by the seal token (one seal per proposed patch), so a new proposal restarts at 1 - a failed verify round trip is marked `failed` and fires nothing: a dropped request is not a wrong answer Closes #866
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @thomgabriel's task in 2m 58s —— View job Claude Code review — comprehension-check accuracy event (#866 → #889)Todo
Verdict: no blocking findingsDone-when check (#866, epic #838 item 32b)Cross-referenced against Trust boundary / correctness
ScopeNo RLS/ TestsRead all three test files in full. The red-proof claim checks out from the diff structure (the four positive assertions require code that doesn't exist at Non-blocking
Verification noteI could not execute |
Closes #866 (epic #838, item 32b).
What
The comprehension check that gates applying an AI-proposed patch already exists (
components/editor/ai-partner/diff-card.tsx, sealed bylib/ai/check-seal.ts, unlimited free retries). Its first-attempt accuracy — THE primary AI harm metric per the research — was not instrumented; notrackEventfired anywhere in the AI partner flow. This adds the event only. No behavior change to the check.New event
comprehension_check_answered— payload{ lessonId, correct, attempt, courseId? }:attempt = 1for the headline first-attempt-accuracy metric.attemptis per CHECK INSTANCE, not per lesson lifetime. The counter is keyed bycheckToken— one seal is minted per proposed patch, so the token is the identity of a check instance and a new proposal restarts at 1. Per-lesson-lifetime numbering would makeattempt=1mean "the first check the learner ever saw in this lesson" and silently drop every later patch out of the first-attempt denominator. The keying is documented in-code at the ref.correctis the server's sealed verdict (/api/ai/partner/verify), never a client compare — the browser never holds the answer key, so the metric is not client-forgeable.verifyCheckfails SAFE by returningcorrect:false, which is indistinguishable from a wrong answer at the card — so this PR adds an explicitVerifyOutcome.failedmarker. The UI still fails safe (Accept stays locked); analytics skips it and does not consume the attempt, so a dropped request can't inflate the primary harm metric's error rate with network noise.eventCtx(the existingChallengeEventContextalready threaded to the AI pane) now reaches the diff card viaMessageList; without it the event simply doesn't fire — never a render difference.Red-proof
The new tests fail at
main. Checking outmain'sdiff-card.tsxunder this PR's test file: 4 failed | 9 passed — the four positive assertions (attempt 1wrong→attempt 2retry, correct first answer, new-seal reset, failed-verify not counted) all fail because no event exists. The fifth new case ("no event when no check is rendered") passes trivially at main, as expected for a negative.Tests
components/editor/ai-partner/__tests__/diff-card.test.tsx— asserted through the analytics facade (trackEvent), so the payload shape is covered end to end:attempt: 1withcorrect: false; the retry firesattempt: 2withcorrect: trueattempt: 1, correct: trueattempt: 1failedverify fires nothing and doesn't consume the attempt — the next real answer is still attempt 1Plus helper-level coverage in
lib/analytics/__tests__/events.test.ts(never deduped;courseIdomitted when unknown), and the twouse-ai-partnerverify-fails-SAFE tests updated for thefailedmarker.Verify
pnpm typecheckcleanorigin/main: typecheck clean, 90 tests greenInventory row added to
lib/analytics/README.mdper house convention.