feat(api): 支持电池分页和安全预测

This commit is contained in:
2026-05-11 22:39:05 +08:00
parent 29e70fea9a
commit cf6f91651d
4 changed files with 322 additions and 33 deletions
+25 -11
View File
@@ -1,3 +1,4 @@
import { LRUCache } from 'lru-cache'
import { z } from 'zod'
import type { BatteryInfo, BatteryPrediction } from '@/domain/battery'
import { env } from '@/env'
@@ -60,13 +61,12 @@ const predictionResponseSchema = z.object({
updated_at: z.string().nullable().optional(),
})
type CacheEntry = {
expiresAt: number
value: SohPrediction
}
const logger = getLogger(['prediction'])
const cache = new Map<string, CacheEntry>()
const cache = new LRUCache<string, SohPrediction>({
max: 5_000,
ttl: env.SOH_PREDICTION_CACHE_TTL_SECONDS * 1000,
})
const inFlightRequests = new Map<string, Promise<SohPrediction | null>>()
const round2 = (value: number) => Math.round(value * 100) / 100
@@ -155,7 +155,22 @@ export async function predictSoh(battery: BatteryInfo, history: BatteryInfo[]):
const cacheKey = createCacheKey(battery, history)
const cached = cache.get(cacheKey)
if (cached && cached.expiresAt > Date.now()) return cached.value
if (cached) return cached
const pendingRequest = inFlightRequests.get(cacheKey)
if (pendingRequest) return pendingRequest
const requestPromise = requestPrediction(cacheKey, battery, request)
inFlightRequests.set(cacheKey, requestPromise)
return requestPromise
}
async function requestPrediction(
cacheKey: string,
battery: BatteryInfo,
request: PredictionRequest,
): Promise<SohPrediction | null> {
if (!env.SOH_PREDICTION_API_BASE_URL) return null
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), env.SOH_PREDICTION_TIMEOUT_MS)
@@ -179,10 +194,7 @@ export async function predictSoh(battery: BatteryInfo, history: BatteryInfo[]):
const json = await response.json()
const prediction = normalizePrediction(predictionResponseSchema.parse(json))
cache.set(cacheKey, {
expiresAt: Date.now() + env.SOH_PREDICTION_CACHE_TTL_SECONDS * 1000,
value: prediction,
})
cache.set(cacheKey, prediction)
return prediction
} catch (error) {
@@ -190,9 +202,11 @@ export async function predictSoh(battery: BatteryInfo, history: BatteryInfo[]):
return null
} finally {
clearTimeout(timeout)
inFlightRequests.delete(cacheKey)
}
}
export function clearPredictionCache() {
cache.clear()
inFlightRequests.clear()
}