Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11cf298332 | |||
| 25d7f1c315 |
@@ -56,6 +56,20 @@ describe('battery domain', () => {
|
||||
expect(response.items[0]?.createTime).toBe('2026-05-10T23:00:00.000Z')
|
||||
})
|
||||
|
||||
test('keeps explicit window summaries for limited history slices', () => {
|
||||
const now = new Date('2026-05-11T00:00:00.000Z')
|
||||
const items = rows.map(toBatteryInfo)
|
||||
const response = createBatteriesResponse(items, now, {
|
||||
total: items.length,
|
||||
lowPower: 1,
|
||||
charging: 1,
|
||||
})
|
||||
|
||||
expect(response.total).toBe(2)
|
||||
expect(response.lowPower).toBe(1)
|
||||
expect(response.charging).toBe(1)
|
||||
})
|
||||
|
||||
test('creates dashboard aggregate shape without using power as fake SOH', () => {
|
||||
const now = new Date('2026-05-11T00:00:00.000Z')
|
||||
const snapshot = createDashboardSnapshot(rows.map(toBatteryInfo), now)
|
||||
|
||||
@@ -205,7 +205,7 @@ function BatteriesPage() {
|
||||
search: (prev) => ({
|
||||
...prev,
|
||||
cursor: nextCursor,
|
||||
cursors: [...(prev.cursors || []), prev.cursor || firstPageCursor],
|
||||
cursors: [...(prev.cursors || []), prev.cursor || firstPageCursor].slice(-100),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createBatteriesResponse, createDashboardSnapshot } from '@/domain/battery'
|
||||
import { createBatteriesResponse, createDashboardSnapshot, POWER_STATUS } from '@/domain/battery'
|
||||
import { os } from '@/server/api/server'
|
||||
import {
|
||||
getBatteryHistory,
|
||||
@@ -63,5 +63,10 @@ export const batteries = os.battery.batteries.handler(async ({ input }) => {
|
||||
export const history = os.battery.history.handler(async ({ input }) => {
|
||||
const items = await getBatteryHistory(input.mac)
|
||||
|
||||
return createBatteriesResponse(items)
|
||||
// History returns a limited window, so the counters must describe only this returned slice.
|
||||
return createBatteriesResponse(items, new Date(), {
|
||||
total: items.length,
|
||||
lowPower: items.filter((item) => item.isLowPower).length,
|
||||
charging: items.filter((item) => item.powerStatus === POWER_STATUS.CHARGING).length,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,8 +15,6 @@ import { env } from '@/env'
|
||||
|
||||
const historyLimit = 500
|
||||
const predictionHistoryLimit = 10
|
||||
const dashboardLatestLimit = 100
|
||||
|
||||
type BatteryInfoMysqlRow = RowDataPacket & BatteryInfoSourceRow
|
||||
type CountMysqlRow = RowDataPacket & {
|
||||
total: number
|
||||
@@ -314,16 +312,19 @@ export async function getLatestBatteryPage(input: LatestBatteryPageInput): Promi
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLatestBatteryPerDevice(limit = dashboardLatestLimit): Promise<BatteryInfo[]> {
|
||||
export async function getLatestBatteryPerDevice(limit?: number): Promise<BatteryInfo[]> {
|
||||
const appliedLimit = typeof limit === 'number' && limit > 0 ? limit : undefined
|
||||
const limitSql = appliedLimit ? '\n LIMIT :limit' : ''
|
||||
const queryParams = appliedLimit ? { limit: appliedLimit } : {}
|
||||
|
||||
const [rows] = await getBatteryPool().query<BatteryInfoMysqlRow[]>(
|
||||
`
|
||||
SELECT ${sourceColumns}
|
||||
FROM ls_battery_info AS current_record
|
||||
WHERE ${latestRecordPredicate}
|
||||
ORDER BY current_record.create_time DESC, current_record.id DESC
|
||||
LIMIT :limit
|
||||
ORDER BY current_record.create_time DESC, current_record.id DESC${limitSql}
|
||||
`,
|
||||
{ limit: Math.min(Math.max(limit, 1), dashboardLatestLimit) },
|
||||
queryParams,
|
||||
)
|
||||
|
||||
return rows.map(toBatteryInfo)
|
||||
|
||||
Reference in New Issue
Block a user