Files
Sauci/apps/api/src/domains/quiz/service.ts
T
jjbruntonandClaude Fable 5 6523ddeb1c feat(quiz): couples quiz with shareable watermarked results card
Replaces the notify-me quiz stub with a full loop: quiz sessions per
couple, answer-and-guess flow, dual-direction scoring, and a score-only
share card (no question or answer content) captured via ViewShot with
Sauci branding.

API: migration 0019 (quiz_questions, quiz_sessions, quiz_answers, 12
seeded questions), new quiz domain with session start / current /
answers / result routes, couple isolation, route + integration +
scoring unit tests.

Mobile: src/features/quiz feature (intro, per-question self+guess flow,
waiting-for-partner nudge, results with breakdown), quizStore with
sign-out clearing, QuizShareModal following SharePreviewModal, discovery
QuizTile, analytics events, and tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 18:09:19 +01:00

42 lines
1.3 KiB
TypeScript

/**
* One partner's guess of the other's self-answer, for a single question. Scoring
* always runs both directions: A guessing B and B guessing A each count separately,
* so a couple who only ever gets half the guesses right lands at 50%, not 100%.
*/
export interface QuizAnswerPair {
questionId: string;
aSelfIndex: number;
aGuessIndex: number;
bSelfIndex: number;
bGuessIndex: number;
}
export interface QuizScoreQuestionResult extends QuizAnswerPair {
aGuessedRight: boolean;
bGuessedRight: boolean;
}
export interface QuizScoreResult {
scorePercent: number;
perQuestion: QuizScoreQuestionResult[];
}
/**
* Pure scoring function for a completed quiz session. Each question contributes
* up to two hits (partner A's guess about B, and B's guess about A), so the
* denominator is twice the question count.
*/
export function computeQuizScore(pairs: QuizAnswerPair[]): QuizScoreResult {
const perQuestion = pairs.map((pair) => ({
...pair,
aGuessedRight: pair.aGuessIndex === pair.bSelfIndex,
bGuessedRight: pair.bGuessIndex === pair.aSelfIndex,
}));
const hits = perQuestion.reduce(
(sum, question) => sum + (question.aGuessedRight ? 1 : 0) + (question.bGuessedRight ? 1 : 0),
0,
);
const scorePercent = pairs.length === 0 ? 0 : Math.round((100 * hits) / (2 * pairs.length));
return { scorePercent, perQuestion };
}