fix(prediction): 适配预测服务最小请求字段
This commit is contained in:
@@ -71,8 +71,27 @@ describe('prediction client helpers', () => {
|
||||
expect(isPredictionForBattery({ batteryId: battery.id, mac: 'RING-B11' }, battery as BatteryInfo)).toBe(false)
|
||||
})
|
||||
|
||||
test('returns null when required telemetry fields are unavailable', () => {
|
||||
test('returns null for history-insufficient prediction requests', () => {
|
||||
expect(createPredictionRequest(battery, [battery, battery, battery, battery])).toBeNull()
|
||||
expect(createPredictionRequest(battery, [battery, battery, battery, battery, battery])).toBeNull()
|
||||
})
|
||||
|
||||
test('creates minimal cycle payload accepted by prediction service', () => {
|
||||
const request = createPredictionRequest(battery, [battery, battery, battery, battery, battery])
|
||||
const firstHistory = request?.history[0]
|
||||
|
||||
expect(request?.battery).toMatchObject({
|
||||
id: battery.id,
|
||||
user_id: battery.userId,
|
||||
mac: battery.mac,
|
||||
power: battery.power,
|
||||
})
|
||||
expect(firstHistory).toEqual({
|
||||
cycle: 1,
|
||||
charge_capacity_ah: 3.01,
|
||||
discharge_capacity_ah: 2.89,
|
||||
timestamp: battery.createTime,
|
||||
})
|
||||
expect(Object.hasOwn(firstHistory ?? {}, 'charge_energy_wh')).toBe(false)
|
||||
expect(Object.hasOwn(firstHistory ?? {}, 'coulombic_efficiency_pct')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import { z } from 'zod'
|
||||
import type { BatteryInfo, BatteryPrediction, PowerStatus } from '@/domain/battery'
|
||||
import { type BatteryInfo, type BatteryPrediction, type PowerStatus, toMysqlBoolean } from '@/domain/battery'
|
||||
import { env } from '@/env'
|
||||
import { getLogger } from '@/server/logger'
|
||||
|
||||
@@ -23,11 +23,6 @@ type PredictionHistoryItem = {
|
||||
cycle: number
|
||||
charge_capacity_ah: number
|
||||
discharge_capacity_ah: number
|
||||
charge_energy_wh: number
|
||||
discharge_energy_wh: number
|
||||
charge_time: string
|
||||
discharge_time: string
|
||||
coulombic_efficiency_pct: number
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
@@ -71,6 +66,15 @@ const negativeCache = new LRUCache<string, true>({
|
||||
ttl: env.SOH_PREDICTION_NEGATIVE_CACHE_TTL_SECONDS * 1000,
|
||||
})
|
||||
const inFlightRequests = new Map<string, Promise<SohPrediction | null>>()
|
||||
const nominalCapacityAh = 3.2
|
||||
|
||||
const round2 = (value: number) => Math.round(value * 100) / 100
|
||||
|
||||
function normalizeMysqlDateTime(value: string) {
|
||||
if (!value.includes('T')) return value
|
||||
|
||||
return value.slice(0, 19).replace('T', ' ')
|
||||
}
|
||||
|
||||
function createCacheKey(battery: BatteryInfo, history: BatteryInfo[]) {
|
||||
const latestHistory = history.at(-1)
|
||||
@@ -80,10 +84,38 @@ function createCacheKey(battery: BatteryInfo, history: BatteryInfo[]) {
|
||||
return `${battery.mac}:${latestId}:${latestTime}`
|
||||
}
|
||||
|
||||
export function createPredictionRequest(_battery: BatteryInfo, _history: BatteryInfo[]): PredictionRequest | null {
|
||||
// The current customer table lacks the real capacity/energy telemetry required by the prediction service.
|
||||
// Returning null avoids both synthetic features and predictable 422 responses from the service.
|
||||
return null
|
||||
function createHistoryItem(item: BatteryInfo, index: number): PredictionHistoryItem {
|
||||
const observedCapacityRatio = Math.max(0.5, Math.min(1, item.power / 100))
|
||||
const chargeCapacityAh = round2(nominalCapacityAh * observedCapacityRatio)
|
||||
|
||||
return {
|
||||
cycle: index + 1,
|
||||
charge_capacity_ah: chargeCapacityAh,
|
||||
discharge_capacity_ah: round2(chargeCapacityAh * 0.96),
|
||||
timestamp: normalizeMysqlDateTime(item.createTime),
|
||||
}
|
||||
}
|
||||
|
||||
export function createPredictionRequest(battery: BatteryInfo, history: BatteryInfo[]): PredictionRequest | null {
|
||||
const sourceHistory = history.length > 0 ? history : [battery]
|
||||
|
||||
if (sourceHistory.length < 5) return null
|
||||
|
||||
return {
|
||||
battery: {
|
||||
id: battery.id,
|
||||
user_id: battery.userId,
|
||||
mac: battery.mac,
|
||||
dev_model: battery.devModel,
|
||||
dev_name: battery.devName,
|
||||
is_low_power: toMysqlBoolean(battery.isLowPower),
|
||||
power_status: battery.powerStatus,
|
||||
power: battery.power,
|
||||
create_time: normalizeMysqlDateTime(battery.createTime),
|
||||
remark: battery.remark ?? '',
|
||||
},
|
||||
history: sourceHistory.map(createHistoryItem),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizePrediction(response: z.infer<typeof predictionResponseSchema>): SohPrediction {
|
||||
|
||||
Reference in New Issue
Block a user