mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
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>
42 lines
1.3 KiB
TypeScript
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 };
|
|
}
|