feat(api): 支持电池分页和安全预测
This commit is contained in:
@@ -4,10 +4,23 @@ import { batteriesResponseSchema, dashboardSnapshotSchema } from '@/domain/batte
|
||||
|
||||
export const dashboard = oc.input(z.void()).output(dashboardSnapshotSchema)
|
||||
|
||||
const batteryListInputSchema = z.object({
|
||||
pageSize: z.number().int().min(1).max(100).default(50),
|
||||
cursor: z.string().min(1).optional(),
|
||||
search: z.string().trim().min(1).max(100).optional(),
|
||||
lowPower: z.boolean().optional(),
|
||||
powerStatus: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
|
||||
sort: z.enum(['createdAtDesc', 'createdAtAsc', 'powerDesc', 'powerAsc']).default('createdAtDesc'),
|
||||
})
|
||||
|
||||
export const batteries = oc
|
||||
.input(batteryListInputSchema)
|
||||
.output(batteriesResponseSchema)
|
||||
|
||||
export const history = oc
|
||||
.input(
|
||||
z.object({
|
||||
mac: z.string().min(1).optional(),
|
||||
mac: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.output(batteriesResponseSchema)
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
import { createBatteriesResponse, createDashboardSnapshot } from '@/domain/battery'
|
||||
import { os } from '@/server/api/server'
|
||||
import { getBatteryHistory, getBatteryPredictionHistory, getLatestBatteryPerDevice } from '@/server/battery/mysql'
|
||||
import {
|
||||
getBatteryHistory,
|
||||
getBatteryPredictionHistories,
|
||||
getLatestBatteryPage,
|
||||
getLatestBatteryPerDevice,
|
||||
} from '@/server/battery/mysql'
|
||||
import { isPredictionEnabled, predictSoh } from '@/server/prediction/client'
|
||||
|
||||
const dashboardPredictionConcurrency = 5
|
||||
|
||||
async function mapWithConcurrency<T, R>(items: T[], concurrency: number, handler: (item: T) => Promise<R>): Promise<R[]> {
|
||||
const results: R[] = []
|
||||
let nextIndex = 0
|
||||
|
||||
async function worker() {
|
||||
while (nextIndex < items.length) {
|
||||
const index = nextIndex
|
||||
nextIndex += 1
|
||||
const item = items[index]
|
||||
if (item !== undefined) results[index] = await handler(item)
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker))
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export const dashboard = os.battery.dashboard.handler(async () => {
|
||||
const items = await getLatestBatteryPerDevice()
|
||||
const predictionHistories = isPredictionEnabled()
|
||||
? await getBatteryPredictionHistories(items.map((item) => item.mac))
|
||||
: new Map()
|
||||
const predictionEntries = isPredictionEnabled()
|
||||
? await Promise.all(
|
||||
items.map(async (item) => {
|
||||
const history = await getBatteryPredictionHistory(item.mac)
|
||||
const prediction = await predictSoh(item, history)
|
||||
? await mapWithConcurrency(items, dashboardPredictionConcurrency, async (item) => {
|
||||
const prediction = await predictSoh(item, predictionHistories.get(item.mac) ?? [])
|
||||
|
||||
return prediction ? ([item.mac, prediction] as const) : null
|
||||
}),
|
||||
)
|
||||
return prediction ? ([item.mac, prediction] as const) : null
|
||||
})
|
||||
: []
|
||||
const predictions = new Map(predictionEntries.filter((entry) => entry !== null))
|
||||
|
||||
@@ -21,7 +46,18 @@ export const dashboard = os.battery.dashboard.handler(async () => {
|
||||
})
|
||||
|
||||
export const batteries = os.battery.batteries.handler(async ({ input }) => {
|
||||
const items = input.mac ? await getBatteryHistory(input.mac) : await getLatestBatteryPerDevice()
|
||||
const page = await getLatestBatteryPage(input)
|
||||
|
||||
return createBatteriesResponse(
|
||||
page.items,
|
||||
new Date(),
|
||||
{ total: page.total, lowPower: page.lowPower, charging: page.charging },
|
||||
page.nextCursor,
|
||||
)
|
||||
})
|
||||
|
||||
export const history = os.battery.history.handler(async ({ input }) => {
|
||||
const items = await getBatteryHistory(input.mac)
|
||||
|
||||
return createBatteriesResponse(items)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user