diff --git a/scripts/seed-fake-completions.sh b/scripts/seed-fake-completions.sh new file mode 100755 index 0000000..0ffff26 --- /dev/null +++ b/scripts/seed-fake-completions.sh @@ -0,0 +1,26 @@ +#!/bin/zsh + +# Seed the database with 10 fake completions with random anonymous_ids +# Useful for testing streak percentile and stats features + +DB_PATH="dev.db" +TODAY=$(date +%Y-%m-%d) +NOW=$(date +%s) + +echo "Seeding 10 fake completions for date: $TODAY" + +for i in {1..50}; do + ID=$(uuidgen | tr '[:upper:]' '[:lower:]') + ANON_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') + GUESS_COUNT=$(( (RANDOM % 6) + 1 )) # 1–6 guesses + + sqlite3 "$DB_PATH" " + INSERT OR IGNORE INTO daily_completions (id, anonymous_id, date, guess_count, completed_at) + VALUES ('$ID', '$ANON_ID', '$TODAY', $GUESS_COUNT, $NOW); + " + + echo " [$i] anon=$ANON_ID guesses=$GUESS_COUNT" +done + +TOTAL=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM daily_completions WHERE date = '$TODAY';") +echo "✓ Done. Total completions for $TODAY: $TOTAL" diff --git a/src/lib/components/ChapterGuess.svelte b/src/lib/components/ChapterGuess.svelte index 992ae0e..8e20f5b 100644 --- a/src/lib/components/ChapterGuess.svelte +++ b/src/lib/components/ChapterGuess.svelte @@ -90,7 +90,7 @@ return chapterCounts[bookId] || 1; } - // Generate 6 random chapter options including the correct one + // Generate 4 random chapter options including the correct one function generateChapterOptions( correctChapter: number, totalChapters: number, @@ -98,14 +98,14 @@ const options = new Set(); options.add(correctChapter); - if (totalChapters >= 6) { - while (options.size < 6) { + if (totalChapters >= 4) { + while (options.size < 4) { const randomChapter = Math.floor(Math.random() * totalChapters) + 1; options.add(randomChapter); } } else { - while (options.size < 6) { + while (options.size < 4) { const randomChapter = Math.floor(Math.random() * 10) + 1; options.add(randomChapter); } @@ -167,18 +167,18 @@
-

Bonus Challenge

-

- Guess the chapter for an even higher grade +

+ Bonus Challenge + — guess the chapter for an even higher grade

-
- {#each chapterOptions as chapter} +
+ {#each chapterOptions as chapter (chapter)}
{/if} diff --git a/src/routes/api/streak-percentile/+server.ts b/src/routes/api/streak-percentile/+server.ts new file mode 100644 index 0000000..f793792 --- /dev/null +++ b/src/routes/api/streak-percentile/+server.ts @@ -0,0 +1,72 @@ +import { json, error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { db } from '$lib/server/db'; +import { dailyCompletions } from '$lib/server/db/schema'; +import { desc } from 'drizzle-orm'; + +export const GET: RequestHandler = async ({ url }) => { + const streakParam = url.searchParams.get('streak'); + const localDate = url.searchParams.get('localDate'); + + if (!streakParam || !localDate) { + error(400, 'Missing streak or localDate'); + } + + const targetStreak = parseInt(streakParam, 10); + if (isNaN(targetStreak) || targetStreak < 1) { + error(400, 'Invalid streak'); + } + + // Fetch all completions ordered by anonymous_id and date desc + // so we can walk each user's history to compute their current streak. + const rows = await db + .select({ + anonymousId: dailyCompletions.anonymousId, + date: dailyCompletions.date, + }) + .from(dailyCompletions) + .orderBy(desc(dailyCompletions.date)); + + // Group dates by user + const byUser = new Map(); + for (const row of rows) { + const list = byUser.get(row.anonymousId); + if (list) { + list.push(row.date); + } else { + byUser.set(row.anonymousId, [row.date]); + } + } + + // Calculate the current streak for each user + const streaks: number[] = []; + for (const [, dates] of byUser) { + // dates are already desc-sorted + const dateSet = new Set(dates); + let streak = 0; + let cursor = new Date(`${localDate}T00:00:00`); + + while (true) { + const dateStr = cursor.toLocaleDateString('en-CA'); + if (!dateSet.has(dateStr)) break; + streak++; + cursor.setDate(cursor.getDate() - 1); + } + + streaks.push(streak); + } + + // Only count users who have an active streak (streak >= 1) + const activeStreaks = streaks.filter((s) => s >= 1); + + if (activeStreaks.length === 0) { + return json({ percentile: 100 }); + } + + // Percentage of active-streak users who have a streak >= targetStreak + const atOrAbove = activeStreaks.filter((s) => s >= targetStreak).length; + const raw = (atOrAbove / activeStreaks.length) * 100; + const percentile = raw < 1 ? Math.round(raw * 100) / 100 : Math.round(raw); + + return json({ percentile }); +};