perf: reuse mileage target snapshots

This commit is contained in:
kkfluous
2026-08-07 17:46:51 +08:00
parent 2de417e109
commit 8a7ce7eccf
4 changed files with 113 additions and 5 deletions
+1 -1
View File
@@ -173,7 +173,7 @@ SUM(fee) / NULLIF(SUM(kwh), 0)
- 已完成电能数据截至时间和实际趋势月展示。 - 已完成电能数据截至时间和实际趋势月展示。
- 电能日期下钻自动核对日汇总与订单全量合计的日期、车辆范围、电量和费用,并显式展示通过或差异状态。 - 电能日期下钻自动核对日汇总与订单全量合计的日期、车辆范围、电量和费用,并显式展示通过或差异状态。
- 已完成氢能结构化故障状态与重试体验。 - 已完成氢能结构化故障状态与重试体验。
- 已完成里程今日仪表快照复用:普通查询命中分钟级快照,手动刷新生成一致的分页快照。 - 已完成里程今日仪表快照复用:监控查询和考核车辆当前日明细命中同一分钟级快照,手动刷新生成一致的分页快照;历史日仍使用独立日期缓存
- 已在各 BI 模块头部提供当前数据源的真实请求状态、失败率和最后成功/失败时间。 - 已在各 BI 模块头部提供当前数据源的真实请求状态、失败率和最后成功/失败时间。
- 待恢复氢能数据库后进行总览、站点、客户三层对账。 - 待恢复氢能数据库后进行总览、站点、客户三层对账。
- 待 ETC 同步后进行通行记录、账单和收款三层对账。 - 待 ETC 同步后进行通行记录、账单和收款三层对账。
@@ -0,0 +1,70 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { targetVehicleMileageFromMonitoringCache } from './target-vehicle-cache.js';
import type { CachedVehicle, MonitoringCache } from './types.js';
function vehicle(plate: string, overrides: Partial<CachedVehicle> = {}): CachedVehicle {
return {
plate,
vin: '',
dailyKm: 12.3,
totalKm: 456.7,
source: 'ONEOS_API',
sourceProtocol: 'GB32960',
sourceCategory: 'INSTRUMENT',
dataTime: '2026-08-07T10:00:00+08:00',
calculatedAt: null,
updatedAt: null,
isOnline: true,
isDataSynced: true,
customer: null,
department: null,
manager: null,
managerId: null,
rentStatus: null,
entity: null,
project: null,
region: null,
brand: null,
targetNames: [],
yesterdayKm: 0,
...overrides,
};
}
function cache(vehicles: CachedVehicle[]): MonitoringCache {
return {
vehicles,
stats: { totalToday: 0, totalAll: 0, vehicleCount: vehicles.length },
filters: {
departments: [], customers: [], plates: [], projects: [], entities: [],
rentStatuses: [], platePrefixes: [], targetNames: [], regions: [], brands: [],
},
targetPlatesMap: new Map(),
updatedAt: '2026-08-07T10:00:00+08:00',
};
}
test('reuses only the current-day monitoring snapshot and filters target plates', () => {
const monitoringCache = cache([
vehicle('粤A1'),
vehicle('粤A2', { dailyKm: -1, totalKm: null, isOnline: false }),
]);
const result = targetVehicleMileageFromMonitoringCache(
monitoringCache,
'2026-08-07',
'2026-08-07',
['粤A2'],
);
assert.deepEqual(Array.from(result || []), [['粤A2', {
dailyKm: 0,
totalKm: null,
isOnline: false,
}]]);
assert.equal(
targetVehicleMileageFromMonitoringCache(monitoringCache, '2026-08-06', '2026-08-07', ['粤A2']),
null,
);
assert.equal(targetVehicleMileageFromMonitoringCache(null, '2026-08-07', '2026-08-07', ['粤A2']), null);
});
@@ -0,0 +1,27 @@
import type { MonitoringCache } from './types.js';
export interface TargetVehicleMileageSnapshot {
dailyKm: number;
totalKm: number | null;
isOnline: boolean;
}
export function targetVehicleMileageFromMonitoringCache(
cache: MonitoringCache | null,
requestedDate: string,
today: string,
plates: string[],
): Map<string, TargetVehicleMileageSnapshot> | null {
if (!cache || !requestedDate || requestedDate !== today) return null;
const selected = new Set(plates);
return new Map(
cache.vehicles
.filter(vehicle => selected.has(vehicle.plate))
.map(vehicle => [vehicle.plate, {
dailyKm: Math.max(0, Number(vehicle.dailyKm) || 0),
totalKm: vehicle.totalKm == null ? null : Math.max(0, Number(vehicle.totalKm) || 0),
isOnline: vehicle.isOnline,
}]),
);
}
+15 -4
View File
@@ -4,6 +4,8 @@ import { getCache } from './cache.js';
import { fetchOneOsDailyMileage } from './oneos-api.js'; import { fetchOneOsDailyMileage } from './oneos-api.js';
import { fetchVehicleInfoByPlates } from './vehicle-info.js'; import { fetchVehicleInfoByPlates } from './vehicle-info.js';
import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js'; import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js';
import { shanghaiDate } from './monitoring-cache-policy.js';
import { targetVehicleMileageFromMonitoringCache } from './target-vehicle-cache.js';
const app = new Hono(); const app = new Hono();
@@ -252,11 +254,20 @@ app.get('/:id/vehicles', async (c) => {
) as [any[], unknown]; ) as [any[], unknown];
const plates: string[] = rows.map((r: any) => r.plate_number); const plates: string[] = rows.map((r: any) => r.plate_number);
const infoMap = await fetchVehicleInfoByPlates(plates); const cachedDateMileageMap = targetVehicleMileageFromMonitoringCache(
getCache(),
date,
shanghaiDate(),
plates,
);
const [infoMap, mileageRows] = await Promise.all([
fetchVehicleInfoByPlates(plates),
date && !cachedDateMileageMap ? fetchOneOsDailyMileage(date, plates) : Promise.resolve([]),
]);
const dateMileageMap = new Map<string, { dailyKm: number; totalKm: number | null; isOnline: boolean }>(); const dateMileageMap = cachedDateMileageMap
if (date && plates.length > 0) { || new Map<string, { dailyKm: number; totalKm: number | null; isOnline: boolean }>();
const mileageRows = await fetchOneOsDailyMileage(date, plates); if (!cachedDateMileageMap && date && plates.length > 0) {
for (const m of mileageRows) { for (const m of mileageRows) {
const existing = dateMileageMap.get(m.plateNumber); const existing = dateMileageMap.get(m.plateNumber);
const dailyKm = m.status === 'NORMAL' ? (Number(m.dailyMileageKm) || 0) : 0; const dailyKm = m.status === 'NORMAL' ? (Number(m.dailyMileageKm) || 0) : 0;