From 8751ffd6c8a41a14900bef6ef82fa02c19d0c82c Mon Sep 17 00:00:00 2001 From: sudacode Date: Thu, 9 Apr 2026 22:16:17 -0700 Subject: [PATCH] refactor(stats): drop animePerDay from trends response in favor of librarySummary --- .../services/__tests__/stats-server.test.ts | 24 ++++--- .../immersion-tracker/__tests__/query.test.ts | 2 +- .../immersion-tracker/query-trends.ts | 66 ------------------- 3 files changed, 16 insertions(+), 76 deletions(-) diff --git a/src/core/services/__tests__/stats-server.test.ts b/src/core/services/__tests__/stats-server.test.ts index 729ab6d1..23190614 100644 --- a/src/core/services/__tests__/stats-server.test.ts +++ b/src/core/services/__tests__/stats-server.test.ts @@ -166,14 +166,20 @@ const TRENDS_DASHBOARD = { ratios: { lookupsPerHundred: [{ label: 'Mar 1', value: 5 }], }, - animePerDay: { - episodes: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 1 }], - watchTime: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 25 }], - cards: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 5 }], - words: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 300 }], - lookups: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 15 }], - lookupsPerHundred: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 5 }], - }, + librarySummary: [ + { + title: 'Little Witch Academia', + watchTimeMin: 25, + videos: 1, + sessions: 1, + cards: 5, + words: 300, + lookups: 15, + lookupsPerHundred: 5, + firstWatched: 20_000, + lastWatched: 20_000, + }, + ], animeCumulative: { watchTime: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 25 }], episodes: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 1 }], @@ -598,7 +604,7 @@ describe('stats server API routes', () => { const body = await res.json(); assert.deepEqual(seenArgs, ['90d', 'month']); assert.deepEqual(body.activity.watchTime, TRENDS_DASHBOARD.activity.watchTime); - assert.deepEqual(body.animePerDay.watchTime, TRENDS_DASHBOARD.animePerDay.watchTime); + assert.deepEqual(body.librarySummary, TRENDS_DASHBOARD.librarySummary); }); it('GET /api/stats/trends/dashboard accepts 365d range', async () => { diff --git a/src/core/services/immersion-tracker/__tests__/query.test.ts b/src/core/services/immersion-tracker/__tests__/query.test.ts index ef277cee..2d36ffca 100644 --- a/src/core/services/immersion-tracker/__tests__/query.test.ts +++ b/src/core/services/immersion-tracker/__tests__/query.test.ts @@ -687,7 +687,7 @@ test('getTrendsDashboard returns chart-ready aggregated series', () => { assert.equal(dashboard.progress.watchTime[1]?.value, 75); assert.equal(dashboard.progress.lookups[1]?.value, 18); assert.equal(dashboard.ratios.lookupsPerHundred[0]?.value, +((8 / 120) * 100).toFixed(1)); - assert.equal(dashboard.animePerDay.watchTime[0]?.animeTitle, 'Trend Dashboard Anime'); + assert.equal(dashboard.librarySummary[0]?.title, 'Trend Dashboard Anime'); assert.equal(dashboard.animeCumulative.watchTime[1]?.value, 75); assert.equal( dashboard.patterns.watchTimeByDayOfWeek.reduce((sum, point) => sum + point.value, 0), diff --git a/src/core/services/immersion-tracker/query-trends.ts b/src/core/services/immersion-tracker/query-trends.ts index 8ea7705d..f521886e 100644 --- a/src/core/services/immersion-tracker/query-trends.ts +++ b/src/core/services/immersion-tracker/query-trends.ts @@ -74,14 +74,6 @@ export interface TrendsDashboardQueryResult { ratios: { lookupsPerHundred: TrendChartPoint[]; }; - animePerDay: { - episodes: TrendPerAnimePoint[]; - watchTime: TrendPerAnimePoint[]; - cards: TrendPerAnimePoint[]; - words: TrendPerAnimePoint[]; - lookups: TrendPerAnimePoint[]; - lookupsPerHundred: TrendPerAnimePoint[]; - }; animeCumulative: { watchTime: TrendPerAnimePoint[]; episodes: TrendPerAnimePoint[]; @@ -315,61 +307,6 @@ function buildLookupsPerHundredWords( }); } -function buildPerAnimeFromSessions( - sessions: TrendSessionMetricRow[], - getValue: (session: TrendSessionMetricRow) => number, -): TrendPerAnimePoint[] { - const byAnime = new Map>(); - - for (const session of sessions) { - const animeTitle = resolveTrendAnimeTitle(session); - const epochDay = session.epochDay; - const dayMap = byAnime.get(animeTitle) ?? new Map(); - dayMap.set(epochDay, (dayMap.get(epochDay) ?? 0) + getValue(session)); - byAnime.set(animeTitle, dayMap); - } - - const result: TrendPerAnimePoint[] = []; - for (const [animeTitle, dayMap] of byAnime) { - for (const [epochDay, value] of dayMap) { - result.push({ epochDay, animeTitle, value }); - } - } - return result; -} - -function buildLookupsPerHundredPerAnime(sessions: TrendSessionMetricRow[]): TrendPerAnimePoint[] { - const lookups = new Map>(); - const words = new Map>(); - - for (const session of sessions) { - const animeTitle = resolveTrendAnimeTitle(session); - const epochDay = session.epochDay; - - const lookupMap = lookups.get(animeTitle) ?? new Map(); - lookupMap.set(epochDay, (lookupMap.get(epochDay) ?? 0) + session.yomitanLookupCount); - lookups.set(animeTitle, lookupMap); - - const wordMap = words.get(animeTitle) ?? new Map(); - wordMap.set(epochDay, (wordMap.get(epochDay) ?? 0) + getTrendSessionWordCount(session)); - words.set(animeTitle, wordMap); - } - - const result: TrendPerAnimePoint[] = []; - for (const [animeTitle, dayMap] of lookups) { - const wordMap = words.get(animeTitle) ?? new Map(); - for (const [epochDay, lookupCount] of dayMap) { - const wordCount = wordMap.get(epochDay) ?? 0; - result.push({ - epochDay, - animeTitle, - value: wordCount > 0 ? +((lookupCount / wordCount) * 100).toFixed(1) : 0, - }); - } - } - return result; -} - function buildCumulativePerAnime(points: TrendPerAnimePoint[]): TrendPerAnimePoint[] { const byAnime = new Map>(); const allDays = new Set(); @@ -760,8 +697,6 @@ export function getTrendsDashboard( titlesByVideoId, (rollup) => rollup.totalTokensSeen, ), - lookups: buildPerAnimeFromSessions(sessions, (session) => session.yomitanLookupCount), - lookupsPerHundred: buildLookupsPerHundredPerAnime(sessions), }; return { @@ -788,7 +723,6 @@ export function getTrendsDashboard( ratios: { lookupsPerHundred: buildLookupsPerHundredWords(sessions, groupBy), }, - animePerDay, animeCumulative: { watchTime: buildCumulativePerAnime(animePerDay.watchTime), episodes: buildCumulativePerAnime(animePerDay.episodes),