Fix Postgres stats key mapping

Before: the optimized Postgres stats query could aggregate 2152 sessions yet still return totalTime=0, items=0, and empty day buckets because the raw rows exposed lowercase keys instead of the camelCase fields the stats helper expected.\n\nAfter: the stats helper accepts both camelCase model fields and lowercase/raw Postgres fields, restoring totals, item aggregation, and day buckets without giving up the faster raw query path. A validation query against the same account now aggregates about 6.7M seconds across 193 items and 630 active days.
This commit is contained in:
Kevin Gatera 2026-03-08 23:08:33 -04:00
parent 7557387189
commit 1914b9d7e7
No known key found for this signature in database
GPG key ID: F0D9F5932458CFB9

View file

@ -560,6 +560,27 @@ class ApiRouter {
const today = date.format(new Date(), 'YYYY-MM-DD')
const includeItems = options.includeItems !== false
const includeRecentSessions = options.includeRecentSessions !== false
const getSessionField = (session, key) => {
if (!session || typeof session !== 'object') return undefined
if (session[key] !== undefined) return session[key]
const lowerKey = key.toLowerCase()
if (session[lowerKey] !== undefined) return session[lowerKey]
return undefined
}
const getSessionObjectField = (session, key) => {
const value = getSessionField(session, key)
if (!value) return null
if (typeof value === 'string') {
try {
return JSON.parse(value)
} catch (error) {
return null
}
}
return value
}
const tasks = [Database.getPlaybackSessionsForStats({ userId })]
if (includeRecentSessions) {
@ -582,21 +603,26 @@ class ApiRouter {
if (includeItems) listeningStats.items = {}
if (includeRecentSessions) listeningStats.recentSessions = recentSessions
listeningSessions.forEach((s) => {
const libraryItemId = s.extraData?.libraryItemId || null
const numericListening = Number(s.timeListening)
const extraData = getSessionObjectField(s, 'extraData')
const libraryItemId = extraData?.libraryItemId || extraData?.libraryitemid || null
const sessionDate = getSessionField(s, 'date')
const sessionDayOfWeek = getSessionField(s, 'dayOfWeek')
const sessionUpdatedAt = getSessionField(s, 'updatedAt')
const sessionMediaMetadata = getSessionObjectField(s, 'mediaMetadata')
const numericListening = Number(getSessionField(s, 'timeListening'))
const sessionTimeListening = Number.isFinite(numericListening)
? numericListening
: 0
if (s.dayOfWeek) {
if (!listeningStats.dayOfWeek[s.dayOfWeek]) listeningStats.dayOfWeek[s.dayOfWeek] = 0
listeningStats.dayOfWeek[s.dayOfWeek] += sessionTimeListening
if (sessionDayOfWeek) {
if (!listeningStats.dayOfWeek[sessionDayOfWeek]) listeningStats.dayOfWeek[sessionDayOfWeek] = 0
listeningStats.dayOfWeek[sessionDayOfWeek] += sessionTimeListening
}
if (s.date && sessionTimeListening > 0) {
if (!listeningStats.days[s.date]) listeningStats.days[s.date] = 0
listeningStats.days[s.date] += sessionTimeListening
if (sessionDate && sessionTimeListening > 0) {
if (!listeningStats.days[sessionDate]) listeningStats.days[sessionDate] = 0
listeningStats.days[sessionDate] += sessionTimeListening
if (s.date === today) {
if (sessionDate === today) {
listeningStats.today += sessionTimeListening
}
}
@ -610,8 +636,8 @@ class ApiRouter {
listeningStats.items[libraryItemId] = {
id: libraryItemId,
timeListening: sessionTimeListening,
mediaMetadata: s.mediaMetadata,
lastUpdate: s.updatedAt
mediaMetadata: sessionMediaMetadata,
lastUpdate: sessionUpdatedAt
}
} else {
listeningStats.items[libraryItemId].timeListening += sessionTimeListening