mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-10 04:19:25 -07:00
refactor(stats): drop animePerDay from trends response in favor of librarySummary
This commit is contained in:
@@ -166,14 +166,20 @@ const TRENDS_DASHBOARD = {
|
|||||||
ratios: {
|
ratios: {
|
||||||
lookupsPerHundred: [{ label: 'Mar 1', value: 5 }],
|
lookupsPerHundred: [{ label: 'Mar 1', value: 5 }],
|
||||||
},
|
},
|
||||||
animePerDay: {
|
librarySummary: [
|
||||||
episodes: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 1 }],
|
{
|
||||||
watchTime: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 25 }],
|
title: 'Little Witch Academia',
|
||||||
cards: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 5 }],
|
watchTimeMin: 25,
|
||||||
words: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 300 }],
|
videos: 1,
|
||||||
lookups: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 15 }],
|
sessions: 1,
|
||||||
lookupsPerHundred: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 5 }],
|
cards: 5,
|
||||||
|
words: 300,
|
||||||
|
lookups: 15,
|
||||||
|
lookupsPerHundred: 5,
|
||||||
|
firstWatched: 20_000,
|
||||||
|
lastWatched: 20_000,
|
||||||
},
|
},
|
||||||
|
],
|
||||||
animeCumulative: {
|
animeCumulative: {
|
||||||
watchTime: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 25 }],
|
watchTime: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 25 }],
|
||||||
episodes: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 1 }],
|
episodes: [{ epochDay: 20_000, animeTitle: 'Little Witch Academia', value: 1 }],
|
||||||
@@ -598,7 +604,7 @@ describe('stats server API routes', () => {
|
|||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
assert.deepEqual(seenArgs, ['90d', 'month']);
|
assert.deepEqual(seenArgs, ['90d', 'month']);
|
||||||
assert.deepEqual(body.activity.watchTime, TRENDS_DASHBOARD.activity.watchTime);
|
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 () => {
|
it('GET /api/stats/trends/dashboard accepts 365d range', async () => {
|
||||||
|
|||||||
@@ -687,7 +687,7 @@ test('getTrendsDashboard returns chart-ready aggregated series', () => {
|
|||||||
assert.equal(dashboard.progress.watchTime[1]?.value, 75);
|
assert.equal(dashboard.progress.watchTime[1]?.value, 75);
|
||||||
assert.equal(dashboard.progress.lookups[1]?.value, 18);
|
assert.equal(dashboard.progress.lookups[1]?.value, 18);
|
||||||
assert.equal(dashboard.ratios.lookupsPerHundred[0]?.value, +((8 / 120) * 100).toFixed(1));
|
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.animeCumulative.watchTime[1]?.value, 75);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
dashboard.patterns.watchTimeByDayOfWeek.reduce((sum, point) => sum + point.value, 0),
|
dashboard.patterns.watchTimeByDayOfWeek.reduce((sum, point) => sum + point.value, 0),
|
||||||
|
|||||||
@@ -74,14 +74,6 @@ export interface TrendsDashboardQueryResult {
|
|||||||
ratios: {
|
ratios: {
|
||||||
lookupsPerHundred: TrendChartPoint[];
|
lookupsPerHundred: TrendChartPoint[];
|
||||||
};
|
};
|
||||||
animePerDay: {
|
|
||||||
episodes: TrendPerAnimePoint[];
|
|
||||||
watchTime: TrendPerAnimePoint[];
|
|
||||||
cards: TrendPerAnimePoint[];
|
|
||||||
words: TrendPerAnimePoint[];
|
|
||||||
lookups: TrendPerAnimePoint[];
|
|
||||||
lookupsPerHundred: TrendPerAnimePoint[];
|
|
||||||
};
|
|
||||||
animeCumulative: {
|
animeCumulative: {
|
||||||
watchTime: TrendPerAnimePoint[];
|
watchTime: TrendPerAnimePoint[];
|
||||||
episodes: TrendPerAnimePoint[];
|
episodes: TrendPerAnimePoint[];
|
||||||
@@ -315,61 +307,6 @@ function buildLookupsPerHundredWords(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPerAnimeFromSessions(
|
|
||||||
sessions: TrendSessionMetricRow[],
|
|
||||||
getValue: (session: TrendSessionMetricRow) => number,
|
|
||||||
): TrendPerAnimePoint[] {
|
|
||||||
const byAnime = new Map<string, Map<number, number>>();
|
|
||||||
|
|
||||||
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<string, Map<number, number>>();
|
|
||||||
const words = new Map<string, Map<number, number>>();
|
|
||||||
|
|
||||||
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[] {
|
function buildCumulativePerAnime(points: TrendPerAnimePoint[]): TrendPerAnimePoint[] {
|
||||||
const byAnime = new Map<string, Map<number, number>>();
|
const byAnime = new Map<string, Map<number, number>>();
|
||||||
const allDays = new Set<number>();
|
const allDays = new Set<number>();
|
||||||
@@ -760,8 +697,6 @@ export function getTrendsDashboard(
|
|||||||
titlesByVideoId,
|
titlesByVideoId,
|
||||||
(rollup) => rollup.totalTokensSeen,
|
(rollup) => rollup.totalTokensSeen,
|
||||||
),
|
),
|
||||||
lookups: buildPerAnimeFromSessions(sessions, (session) => session.yomitanLookupCount),
|
|
||||||
lookupsPerHundred: buildLookupsPerHundredPerAnime(sessions),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -788,7 +723,6 @@ export function getTrendsDashboard(
|
|||||||
ratios: {
|
ratios: {
|
||||||
lookupsPerHundred: buildLookupsPerHundredWords(sessions, groupBy),
|
lookupsPerHundred: buildLookupsPerHundredWords(sessions, groupBy),
|
||||||
},
|
},
|
||||||
animePerDay,
|
|
||||||
animeCumulative: {
|
animeCumulative: {
|
||||||
watchTime: buildCumulativePerAnime(animePerDay.watchTime),
|
watchTime: buildCumulativePerAnime(animePerDay.watchTime),
|
||||||
episodes: buildCumulativePerAnime(animePerDay.episodes),
|
episodes: buildCumulativePerAnime(animePerDay.episodes),
|
||||||
|
|||||||
Reference in New Issue
Block a user