feat(dashboard): 接入预测结果聚合

This commit is contained in:
2026-05-11 22:21:57 +08:00
parent b11d37e9d8
commit 1a2ff19cf4
4 changed files with 125 additions and 12 deletions
+62 -10
View File
@@ -90,6 +90,19 @@ export const batteriesResponseSchema = z.object({
})
export type BatteriesResponse = z.infer<typeof batteriesResponseSchema>
export type BatteryPrediction = {
mac: string
nowSoh: number
monthSoh: number
trmonthSoh: number
riskScore: number | null
riskLevel: string | null
status: string | null
modelName: string | null
cyclesUsed: number | null
updatedAt: string | null
}
const round1 = (value: number) => Math.round(value * 10) / 10
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value))
@@ -115,6 +128,34 @@ export function getDeviceStatus(soh: number): DeviceStatus {
return '健康'
}
function getDeviceStatusByRisk(prediction: BatteryPrediction): DeviceStatus {
const riskText = `${prediction.riskLevel ?? ''} ${prediction.status ?? ''}`.toLowerCase()
if (
riskText.includes('high') ||
riskText.includes('danger') ||
riskText.includes('危险') ||
riskText.includes('高')
) {
return '预警'
}
if (
riskText.includes('medium') ||
riskText.includes('warning') ||
riskText.includes('关注') ||
riskText.includes('中')
) {
return '关注'
}
if (prediction.riskScore !== null) {
if (prediction.riskScore >= 70) return '预警'
if (prediction.riskScore >= 40) return '关注'
}
return getDeviceStatus(prediction.nowSoh)
}
export function normalizePowerStatus(value: number): PowerStatus {
if (value === 1 || value === 2) return value
return 0
@@ -163,28 +204,35 @@ export function createBatteriesResponse(items: BatteryInfo[], now = new Date()):
}
}
function toFleetUnit(item: BatteryInfo, index: number): FleetUnit {
const soh = item.power
const status = getDeviceStatus(soh)
function toFleetUnit(item: BatteryInfo, index: number, prediction?: BatteryPrediction): FleetUnit {
const soh = prediction?.nowSoh ?? item.power
const status = prediction ? getDeviceStatusByRisk(prediction) : getDeviceStatus(soh)
const riskFactors: string[] = []
if (item.isLowPower || item.power <= 20) riskFactors.push('低电量')
if (item.powerStatus === 1) riskFactors.push('充电中')
if (status === '预警') riskFactors.push('衰减加速')
if (item.remark?.includes('v3.7')) riskFactors.push('旧固件')
if (prediction?.riskLevel) riskFactors.push(`AI风险:${prediction.riskLevel}`)
const thermalPressure = index % 3
const soh30d = round1(clamp(soh - 0.8 - thermalPressure * 0.25, 0, 100))
const soh60d = round1(clamp(soh - 1.7 - thermalPressure * 0.35, 0, 100))
const soh90d = round1(clamp(soh - 2.8 - thermalPressure * 0.45, 0, 100))
const soh30d = prediction
? round1(clamp(prediction.monthSoh, 0, 100))
: round1(clamp(soh - 0.8 - thermalPressure * 0.25, 0, 100))
const soh90d = prediction
? round1(clamp(prediction.trmonthSoh, 0, 100))
: round1(clamp(soh - 2.8 - thermalPressure * 0.45, 0, 100))
const soh60d = prediction ? round1((soh30d + soh90d) / 2) : round1(clamp(soh - 1.7 - thermalPressure * 0.35, 0, 100))
const temperature = round1(29.5 + thermalPressure * 2.1 + (item.isLowPower ? 1.4 : 0))
const chargeEfficiency = round1(clamp(91 + item.power / 12 - riskFactors.length * 1.8, 80, 98))
const riskScore = Math.round(clamp(12 + (100 - soh) * 1.45 + riskFactors.length * 8 + thermalPressure * 4, 8, 96))
const riskScore = Math.round(
clamp(prediction?.riskScore ?? 12 + (100 - soh) * 1.45 + riskFactors.length * 8 + thermalPressure * 4, 8, 96),
)
return {
id: item.devName || item.mac,
batch: item.devModel,
firmware: item.remark ?? 'unknown',
firmware: prediction?.modelName ?? item.remark ?? 'unknown',
cycles: 120 + index * 17 + Math.round((100 - soh) * 2.2),
soh,
soh30d,
@@ -341,8 +389,12 @@ function createStrategies(devices: FleetUnit[]) {
] satisfies DashboardSnapshot['strategies']
}
export function createDashboardSnapshot(items: BatteryInfo[], now = new Date()): DashboardSnapshot {
const devices = items.map(toFleetUnit)
export function createDashboardSnapshot(
items: BatteryInfo[],
now = new Date(),
predictions: ReadonlyMap<string, BatteryPrediction> = new Map(),
): DashboardSnapshot {
const devices = items.map((item, index) => toFleetUnit(item, index, predictions.get(item.mac)))
return {
devices,