mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-10-22 05:54:10 -04:00
first pass analytics redo
This commit is contained in:
1 parent
b656fd04c0
commit
45ffa39f2a
69 files changed
+6400
-1138
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { deriveSegments, countSegments } from '$lib/server/stats/segments';
|
||||
import { SETTINGS_DEFAULTS, type PlayerState, type StatsSettings } from '$lib/types/stats';
|
||||
import { addDays } from '$lib/server/stats/dates';
|
||||
|
||||
const AS_OF = '2026-09-15';
|
||||
|
||||
function settings(overrides: Partial<StatsSettings> = {}): StatsSettings {
|
||||
return { ...SETTINGS_DEFAULTS, ...overrides };
|
||||
}
|
||||
|
||||
function player(id: string, overrides: Partial<PlayerState>): PlayerState {
|
||||
return {
|
||||
id,
|
||||
firstDate: addDays(AS_OF, -100),
|
||||
lastDate: AS_OF,
|
||||
totalPlays: 10,
|
||||
plays7: 2,
|
||||
plays30: 10,
|
||||
streak: 2,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
const players: PlayerState[] = [
|
||||
player('new', { firstDate: addDays(AS_OF, -1), lastDate: AS_OF, plays7: 1 }),
|
||||
player('core', { lastDate: AS_OF, plays7: 5 }),
|
||||
player('resurrected', { lastDate: AS_OF, plays7: 2, plays30: 2 }),
|
||||
player('returning', { lastDate: AS_OF, plays7: 2, plays30: 10 }),
|
||||
player('atRisk', { lastDate: addDays(AS_OF, -10) }),
|
||||
player('churned', { lastDate: addDays(AS_OF, -40) })
|
||||
];
|
||||
|
||||
describe('deriveSegments', () => {
|
||||
test('classifies each lifecycle segment', () => {
|
||||
const sets = deriveSegments(players, AS_OF, settings());
|
||||
expect(sets.new).toEqual(['new']);
|
||||
expect(sets.core).toEqual(['core']);
|
||||
expect(sets.resurrected).toEqual(['resurrected']);
|
||||
expect(sets.returning).toEqual(['returning']);
|
||||
expect(sets.atRisk).toEqual(['atRisk']);
|
||||
expect(sets.churned).toEqual(['churned']);
|
||||
expect(sets.active).toEqual(['new', 'core', 'resurrected', 'returning']);
|
||||
});
|
||||
|
||||
test('changing the core threshold re-segments without new data', () => {
|
||||
const strict = deriveSegments(players, AS_OF, settings({ core_plays_per_week: 6 }));
|
||||
expect(strict.core).toEqual([]);
|
||||
expect(strict.returning).toContain('core');
|
||||
|
||||
const lenient = deriveSegments(players, AS_OF, settings({ core_plays_per_week: 2 }));
|
||||
expect(lenient.core).toContain('core');
|
||||
expect(lenient.core).toContain('returning');
|
||||
});
|
||||
|
||||
test('counts include every player exactly once across mutually exclusive sets', () => {
|
||||
const counts = countSegments(deriveSegments(players, AS_OF, settings()));
|
||||
const activeTotal = counts.new + counts.core + counts.returning + counts.resurrected;
|
||||
expect(activeTotal).toBe(counts.active);
|
||||
expect(counts.total).toBe(players.length);
|
||||
});
|
||||
|
||||
test('at-risk and churned boundaries follow settings', () => {
|
||||
const sets = deriveSegments(players, AS_OF, settings({ at_risk_days: 5, churn_days: 20 }));
|
||||
expect(sets.atRisk).toContain('atRisk'); // inactive 10 >= 5, < 20
|
||||
expect(sets.churned).toContain('churned'); // inactive 40 >= 20
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user