feat: include wattHours, tdpMax, and batteryLife in hardware and game stats APIs

This commit is contained in:
2026-05-09 22:25:34 +08:00
parent 666219614b
commit 02bdbe2825
3 changed files with 68 additions and 5 deletions
+4
View File
@@ -31,6 +31,8 @@ export default async function DevicesPage() {
deviceType: hardware.deviceType, deviceType: hardware.deviceType,
image: hardware.image, image: hardware.image,
sortOrder: hardware.sortOrder, sortOrder: hardware.sortOrder,
wattHours: hardware.wattHours,
tdpMax: hardware.tdpMax,
}) })
.from(hardware) .from(hardware)
.orderBy(hardware.sortOrder) .orderBy(hardware.sortOrder)
@@ -102,6 +104,8 @@ export default async function DevicesPage() {
gameCount: stats?.gameCount ?? 0, gameCount: stats?.gameCount ?? 0,
verifiedCount: stats?.verifiedCount ?? 0, verifiedCount: stats?.verifiedCount ?? 0,
bestGame: bestGame ?? null, bestGame: bestGame ?? null,
wattHours: device.wattHours ? Number(device.wattHours) : null,
tdpMax: device.tdpMax ? Number(device.tdpMax) : null,
} }
}) })
+53 -2
View File
@@ -46,6 +46,7 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
userNotes: performanceEntries.userNotes, userNotes: performanceEntries.userNotes,
createdAt: performanceEntries.createdAt, createdAt: performanceEntries.createdAt,
versionId: performanceEntries.versionId, versionId: performanceEntries.versionId,
tdpWatts: performanceEntries.tdpWatts,
}) })
.from(performanceEntries) .from(performanceEntries)
.innerJoin( .innerJoin(
@@ -76,6 +77,7 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
fpsRange: [], fpsRange: [],
deviceBreakdown: [], deviceBreakdown: [],
filterOptions: { protonVersions: [], osVersions: [] }, filterOptions: { protonVersions: [], osVersions: [] },
batteryLife: [],
} }
} }
@@ -245,11 +247,17 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
// ── 8. Device breakdown ─────────────────────────────────────── // ── 8. Device breakdown ───────────────────────────────────────
const deviceBreakdown = Array.from(boxplotMap.entries()).map( const deviceBreakdown = Array.from(boxplotMap.entries()).map(
([slug, { hardwareName, values }]) => ({ ([slug, { hardwareName, values }]) => {
const dev = deviceWattHoursMap.get(slug)
return {
hardwareSlug: slug, hardwareSlug: slug,
hardwareName, hardwareName,
count: values.length, count: values.length,
}), wattHours: dev?.wattHours ? Number(dev.wattHours) : null,
tdpMax: dev?.tdpMax ? Number(dev.tdpMax) : null,
deviceType: dev?.deviceType ?? null,
}
},
) )
// ── 9. Performance tier breakdown per device ──────────────── // ── 9. Performance tier breakdown per device ────────────────
@@ -279,6 +287,48 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
stabilityRatio: e.fpsAvg > 0 ? Math.min(1, e.fpsOnePercentLow! / e.fpsAvg) : 0, stabilityRatio: e.fpsAvg > 0 ? Math.min(1, e.fpsOnePercentLow! / e.fpsAvg) : 0,
})) }))
// ── 12. Battery life estimates ─────────────────────────────
// Fetch hardware wattHours for the relevant devices
const deviceSlugs = [...new Set(entries.map((e) => e.hardwareSlug))]
const deviceData = await db
.select({
slug: hardware.slug,
wattHours: hardware.wattHours,
tdpMax: hardware.tdpMax,
deviceType: hardware.deviceType,
})
.from(hardware)
.where(sql`${hardware.slug} = ANY(${deviceSlugs})`)
const deviceWattHoursMap = new Map(
deviceData.map((d) => [d.slug, d]),
)
const batteryLife = entries
.filter((e) => e.tdpWatts != null && e.tdpWatts > 0)
.map((e) => {
const device = deviceWattHoursMap.get(e.hardwareSlug)
const wh = device?.wattHours ? Number(device.wattHours) : null
const tdpMax = device?.tdpMax ? Number(device.tdpMax) : null
if (!wh) return null
const estimatedBatteryHours = wh / e.tdpWatts!
const estimatedBatteryMin = estimatedBatteryHours * 60
const estimatedAtMaxTdpMin = tdpMax ? (wh / tdpMax) * 60 : null
return {
id: e.id,
hardwareSlug: e.hardwareSlug,
tdpWatts: Number(e.tdpWatts),
estimatedBatteryMin: Math.round(estimatedBatteryMin),
estimatedBatteryHours: Math.round(estimatedBatteryHours * 10) / 10,
wattHours: wh,
tdpMax,
estimatedAtMaxTdpMin: estimatedAtMaxTdpMin
? Math.round(estimatedAtMaxTdpMin)
: null,
}
})
.filter(Boolean)
// ── 11. Filter options ──────────────────────────────────────── // ── 11. Filter options ────────────────────────────────────────
const protonVersions = [ const protonVersions = [
...new Set(entries.map((e) => e.protonVersion).filter(Boolean)), ...new Set(entries.map((e) => e.protonVersion).filter(Boolean)),
@@ -306,6 +356,7 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
deviceBreakdown, deviceBreakdown,
performanceTiers, performanceTiers,
stabilityScatter, stabilityScatter,
batteryLife,
filterOptions: { protonVersions, osVersions }, filterOptions: { protonVersions, osVersions },
} }
}, },
+8
View File
@@ -20,6 +20,8 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
name: hardware.name, name: hardware.name,
deviceType: hardware.deviceType, deviceType: hardware.deviceType,
sortOrder: hardware.sortOrder, sortOrder: hardware.sortOrder,
wattHours: hardware.wattHours,
tdpMax: hardware.tdpMax,
}) })
.from(hardware) .from(hardware)
.orderBy(hardware.sortOrder) .orderBy(hardware.sortOrder)
@@ -79,6 +81,8 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
name: device.name, name: device.name,
deviceType: device.deviceType, deviceType: device.deviceType,
sortOrder: device.sortOrder, sortOrder: device.sortOrder,
wattHours: device.wattHours ? Number(device.wattHours) : null,
tdpMax: device.tdpMax ? Number(device.tdpMax) : null,
totalBenchmarks: stats?.totalBenchmarks ?? 0, totalBenchmarks: stats?.totalBenchmarks ?? 0,
avgFps: stats?.avgFps ? Number(stats.avgFps) : null, avgFps: stats?.avgFps ? Number(stats.avgFps) : null,
gameCount: stats?.gameCount ?? 0, gameCount: stats?.gameCount ?? 0,
@@ -100,6 +104,8 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
slug: hardware.slug, slug: hardware.slug,
name: hardware.name, name: hardware.name,
deviceType: hardware.deviceType, deviceType: hardware.deviceType,
wattHours: hardware.wattHours,
tdpMax: hardware.tdpMax,
}) })
.from(hardware) .from(hardware)
.where(eq(hardware.slug, slug)) .where(eq(hardware.slug, slug))
@@ -144,6 +150,8 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
if (entries.length === 0) { if (entries.length === 0) {
return { return {
...device, ...device,
wattHours: null,
tdpMax: null,
totalBenchmarks: 0, totalBenchmarks: 0,
avgFps: null, avgFps: null,
verifiedCount: 0, verifiedCount: 0,