Files
bibdle/tests/stats-digest.test.ts
T
George Powell a562ee3cc9 fix(stats): finalize snapshots on the verse-day window
A verse date is a player's local calendar day, spanning D-1 10:00Z
(UTC+14) through D+1 12:00Z (UTC-12). The old UTC end-of-day cutoff both
dropped late-night finishes in negative timezones and could persist a
partial day.

- Scope snapshot loads by the verse date column; use completedBefore only
  to pin an in-progress point-in-time build.
- Gate persistence on isDayClosed (D+1 12:00Z); open days are computed
  live and never written.
- Build the cron snapshot for latestClosedDate instead of EST yesterday,
  and read the same date for the community page.
- Add all-time best single day to snapshot totals.
- Bump PAYLOAD_SCHEMA_VERSION to 4 and extend snapshot/window tests.
2026-09-28 20:21:47 -04:00

77 lines
2.1 KiB
TypeScript

import { describe, test, expect } from 'bun:test';
import { composeDigest } from '$lib/server/stats/digest';
import type { SnapshotPayload } from '$lib/types/stats';
const payload: SnapshotPayload = {
asOfDate: '2026-09-14',
activity: {
dau: 102,
wau: 363,
mau: 852,
core: 61,
returningShare: 38,
newPlayers: 20,
resurrected: 4,
churned: 25,
netGrowth: -1,
corePrevious: 60,
previous: null
},
dailySeries: [],
kpis: {
d1Return: 12.2,
return7: 18,
overallReturnRate: 22.8,
completionRate: null,
shareRate: null,
firstTryRate: 33.7,
avgGuesses: 4.1
},
retention: { cohorts: [], curves: { d1: [], d7: [], d30: [] }, timeToSecondPlay: [], segmentMix: [] },
verseMix: {
coverage: [],
testamentMix: [],
entropy: [],
repeatIntervals: [],
firstTryByBook: [],
retentionByBook: [],
retentionByGuessBucket: [],
achievability: []
},
habit: { streakSurvival: [], streakLengths: [], calendar: [], weekdayHour: [] },
growth: { lifecycleFunnel: [], signups: [] },
totals: { completions: 31775, players: 6589, yesterdayCompletions: 102, yesterdayAvgGuesses: 4.1, bestDay: null },
signals: [
{
id: 'd1_return',
severity: 'bad',
title: 'D1 return',
detail: 'D1 return is 12.2%, below the 20% target.',
action: 'Test a "come back tomorrow" prompt.'
}
],
meta: { hasStartedData: false, hasShareData: false, firstEventDate: null }
};
describe('composeDigest', () => {
test('includes the core numbers in plain language', () => {
const message = composeDigest(payload);
expect(message).toContain('2026-09-14');
expect(message).toContain('363');
expect(message).toContain('12.2');
expect(message).toContain('Top signal: D1 return');
expect(message).toContain('/admin/stats?date=2026-09-14');
});
test('is at most a handful of lines', () => {
expect(composeDigest(payload).split('\n').length).toBeLessThanOrEqual(6);
});
test('shows an em-dash for missing metrics', () => {
const message = composeDigest({
...payload,
kpis: { ...payload.kpis, d1Return: null, return7: null }
});
expect(message).toContain('D1 return —%');
});
});