feat(stats): add episodes completed and anime completed to tracking snapshot

- Query watched videos count and anime with all episodes watched
- Display in overview tracking snapshot
- Remove efficiency section from trends
This commit is contained in:
2026-03-14 22:31:17 -07:00
parent 249a7cada8
commit ff2d9141bc
6 changed files with 39 additions and 6 deletions

View File

@@ -124,6 +124,8 @@ export function getQueryHints(db: DatabaseSync): {
activeSessions: number;
episodesToday: number;
activeAnimeCount: number;
totalEpisodesWatched: number;
totalAnimeCompleted: number;
} {
const sessions = db.prepare('SELECT COUNT(*) AS total FROM imm_sessions');
const active = db.prepare('SELECT COUNT(*) AS total FROM imm_sessions WHERE ended_at_ms IS NULL');
@@ -147,7 +149,23 @@ export function getQueryHints(db: DatabaseSync): {
AND s.started_at_ms >= ?
`).get(thirtyDaysAgoMs) as { count: number })?.count ?? 0;
return { totalSessions, activeSessions, episodesToday, activeAnimeCount };
const totalEpisodesWatched = (db.prepare(`
SELECT COUNT(*) AS count FROM imm_videos WHERE watched = 1
`).get() as { count: number })?.count ?? 0;
const totalAnimeCompleted = (db.prepare(`
SELECT COUNT(*) AS count FROM (
SELECT a.anime_id
FROM imm_anime a
JOIN imm_videos v ON v.anime_id = a.anime_id
JOIN imm_media_art m ON m.video_id = v.video_id
WHERE m.episodes_total IS NOT NULL AND m.episodes_total > 0
GROUP BY a.anime_id
HAVING COUNT(DISTINCT CASE WHEN v.watched = 1 THEN v.video_id END) >= MAX(m.episodes_total)
)
`).get() as { count: number })?.count ?? 0;
return { totalSessions, activeSessions, episodesToday, activeAnimeCount, totalEpisodesWatched, totalAnimeCompleted };
}
export function getDailyRollups(db: DatabaseSync, limit = 60): ImmersionSessionRollupRow[] {

View File

@@ -40,7 +40,7 @@ export function OverviewTab() {
No tracked card-add events in the current immersion DB yet. New cards mined after this fix will show here.
</div>
)}
<div className="grid grid-cols-2 sm:grid-cols-5 gap-3 text-sm">
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-7 gap-3 text-sm">
<div className="rounded-lg bg-ctp-surface1/60 p-3">
<div className="text-xs uppercase tracking-wide text-ctp-overlay2">Total Sessions</div>
<div className="mt-1 text-xl font-semibold text-ctp-lavender">
@@ -71,6 +71,18 @@ export function OverviewTab() {
{formatNumber(summary.totalTrackedCards)}
</div>
</div>
<div className="rounded-lg bg-ctp-surface1/60 p-3">
<div className="text-xs uppercase tracking-wide text-ctp-overlay2">Episodes Completed</div>
<div className="mt-1 text-xl font-semibold text-ctp-blue">
{formatNumber(summary.totalEpisodesWatched)}
</div>
</div>
<div className="rounded-lg bg-ctp-surface1/60 p-3">
<div className="text-xs uppercase tracking-wide text-ctp-overlay2">Anime Completed</div>
<div className="mt-1 text-xl font-semibold text-ctp-sapphire">
{formatNumber(summary.totalAnimeCompleted)}
</div>
</div>
</div>
</div>

View File

@@ -141,9 +141,6 @@ export function TrendsTab() {
type="line"
/>
<SectionHeader>Efficiency</SectionHeader>
<TrendChart title="Cards per Hour" data={dashboard.cardsPerHour} color="#f5a97f" type="line" />
<SectionHeader>Anime</SectionHeader>
<StackedTrendChart title="Anime Progress (episodes)" data={animeProgress} />
<StackedTrendChart title="Watch Time per Anime (min)" data={watchTimePerAnime} />

View File

@@ -49,7 +49,7 @@ test('buildOverviewSummary aggregates tracked totals and recent windows', () =>
const overview: OverviewData = {
sessions,
rollups,
hints: { totalSessions: 1, activeSessions: 0, episodesToday: 2, activeAnimeCount: 3 },
hints: { totalSessions: 1, activeSessions: 0, episodesToday: 2, activeAnimeCount: 3, totalEpisodesWatched: 5, totalAnimeCompleted: 1 },
};
const summary = buildOverviewSummary(overview, now);

View File

@@ -14,6 +14,8 @@ export interface OverviewSummary {
totalTrackedCards: number;
episodesToday: number;
activeAnimeCount: number;
totalEpisodesWatched: number;
totalAnimeCompleted: number;
averageSessionMinutes: number;
totalSessions: number;
activeDays: number;
@@ -146,6 +148,8 @@ export function buildOverviewSummary(
totalTrackedCards: Math.max(sessionCards, rollupCards),
episodesToday: overview.hints.episodesToday ?? 0,
activeAnimeCount: overview.hints.activeAnimeCount ?? 0,
totalEpisodesWatched: overview.hints.totalEpisodesWatched ?? 0,
totalAnimeCompleted: overview.hints.totalAnimeCompleted ?? 0,
averageSessionMinutes:
overview.sessions.length > 0
? Math.round(sumBy(overview.sessions, (session) => session.activeWatchedMs) / overview.sessions.length / 60_000)

View File

@@ -91,6 +91,8 @@ export interface OverviewData {
activeSessions: number;
episodesToday: number;
activeAnimeCount: number;
totalEpisodesWatched: number;
totalAnimeCompleted: number;
};
}