feat(mileage): add OneOS source priority controls

This commit is contained in:
kkfluous
2026-07-23 14:11:52 +08:00
parent becacecc67
commit d8773ff0a0
12 changed files with 448 additions and 44 deletions
+53 -9
View File
@@ -4,6 +4,11 @@ import { dirname, join } from 'node:path';
import pool from '../../db.js';
import { fetchVehicleInfoMap } from './vehicle-info.js';
import { fetchOneOsDailyMileage, fetchOneOsMileageDates, type OneOsDailyMileage } from './oneos-api.js';
import {
sourceCategoryFromProtocol,
type MileageSourceCategory,
type OneOsProtocol,
} from './source-policy.js';
import type { CachedVehicle, MonitoringCache, MonitoringFilters, PlatePrefix, VehicleInfoRow } from './types.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -64,6 +69,7 @@ interface MileageRow {
daily_km: string;
total_km: string | null;
source: string;
source_protocol: OneOsProtocol | null;
data_time: string | null;
calculated_at: string | null;
updated_at: string | null;
@@ -75,6 +81,7 @@ interface DailyMileageRow {
date: string;
daily_km: string | number | null;
source: string | null;
source_protocol: OneOsProtocol | null;
data_time: string | null;
calculated_at: string | null;
updated_at: string | null;
@@ -87,6 +94,7 @@ function toMileageRows(rows: OneOsDailyMileage[]): MileageRow[] {
daily_km: String(row.dailyMileageKm ?? 0),
total_km: row.totalMileageKm == null ? null : String(row.totalMileageKm),
source: row.status === 'NORMAL' ? 'ONEOS_API' : 'NONE',
source_protocol: row.sourceProtocol,
data_time: row.dataTime,
calculated_at: row.calculatedAt,
updated_at: row.updatedAt,
@@ -190,6 +198,8 @@ function mergeVehicles(
// Never backfill it from another mileage source outside the assessment page.
totalKm: gpsTotal,
source,
sourceProtocol: m?.source_protocol || null,
sourceCategory: sourceCategoryFromProtocol(m?.source_protocol || null),
dataTime: m?.data_time || null,
calculatedAt: m?.calculated_at || null,
updatedAt: m?.updated_at || null,
@@ -248,10 +258,13 @@ export async function refreshMonitoringCache(): Promise<void> {
}
}
export async function queryDateMileage(dateStr: string): Promise<CachedVehicle[]> {
export async function queryDateMileage(
dateStr: string,
protocolPriority?: OneOsProtocol[],
): Promise<CachedVehicle[]> {
const [apiRows, yesterdayRows, infoMap, targetRows] = await Promise.all([
fetchOneOsDailyMileage(dateStr),
fetchOneOsDailyMileage(previousDate(dateStr)),
fetchOneOsDailyMileage(dateStr, undefined, protocolPriority),
fetchOneOsDailyMileage(previousDate(dateStr), undefined, protocolPriority),
fetchVehicleInfoMap(),
fetchTargetRows(),
]);
@@ -282,11 +295,16 @@ function datesBetween(start: string, end: string): string[] {
return result;
}
export async function queryRangeMileage(startDate: string, endDate: string): Promise<RangeMileageResult> {
export async function queryRangeMileage(
startDate: string,
endDate: string,
protocolPriority?: OneOsProtocol[],
): Promise<RangeMileageResult> {
if (startDate === endDate) {
const vehicles = (await queryDateMileage(startDate)).map(vehicle => ({
const vehicles = (await queryDateMileage(startDate, protocolPriority)).map(vehicle => ({
...vehicle,
dailyMileage: { [startDate]: vehicle.dailyKm },
dailySourceProtocols: { [startDate]: vehicle.sourceProtocol },
}));
return {
vehicles,
@@ -301,9 +319,9 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
const days = datesBetween(startDate, endDate);
const [apiRowsByDate, endDateRows, yesterdayRows, infoMap, targetRows] = await Promise.all([
fetchOneOsMileageDates(days),
fetchOneOsDailyMileage(endDate),
fetchOneOsDailyMileage(previousDate(startDate)),
fetchOneOsMileageDates(days, undefined, protocolPriority),
fetchOneOsDailyMileage(endDate, undefined, protocolPriority),
fetchOneOsDailyMileage(previousDate(startDate), undefined, protocolPriority),
fetchVehicleInfoMap(),
fetchTargetRows(),
]);
@@ -316,6 +334,7 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
date,
daily_km: row.dailyMileageKm,
source: row.status === 'NORMAL' ? 'ONEOS_API' : 'NONE',
source_protocol: row.sourceProtocol,
data_time: row.dataTime,
calculated_at: row.calculatedAt,
updated_at: row.updatedAt,
@@ -324,12 +343,15 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
}
const perVehicleDaily = new Map<string, Record<string, number>>();
const perVehicleDailySources = new Map<string, Record<string, OneOsProtocol | null>>();
const perVehicleSourceCategories = new Map<string, Set<MileageSourceCategory>>();
const perVehicleSum = new Map<string, {
plate: string;
vin: string;
daily_km: string;
total_km: string | null;
source: string;
source_protocol: OneOsProtocol | null;
data_time: string | null;
calculated_at: string | null;
updated_at: string | null;
@@ -357,6 +379,15 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
const daily = perVehicleDaily.get(plate) || {};
daily[date] = km;
perVehicleDaily.set(plate, daily);
const dailySources = perVehicleDailySources.get(plate) || {};
dailySources[date] = row.source_protocol;
perVehicleDailySources.set(plate, dailySources);
const category = sourceCategoryFromProtocol(row.source_protocol);
if (category) {
const categories = perVehicleSourceCategories.get(plate) || new Set<MileageSourceCategory>();
categories.add(category);
perVehicleSourceCategories.set(plate, categories);
}
const existing = perVehicleSum.get(plate);
perVehicleSum.set(plate, {
@@ -365,6 +396,7 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
daily_km: String((Number(existing?.daily_km) || 0) + km),
total_km: null,
source: existing?.source !== 'NONE' && existing?.source ? existing.source : (row.source || 'NONE'),
source_protocol: row.source_protocol || existing?.source_protocol || null,
data_time: row.data_time || existing?.data_time || null,
calculated_at: row.calculated_at || existing?.calculated_at || null,
updated_at: row.updated_at || existing?.updated_at || null,
@@ -379,6 +411,7 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
...aggregate,
vin: endDateRow.vin || aggregate.vin,
total_km: endDateRow.totalMileageKm == null ? null : String(endDateRow.totalMileageKm),
source_protocol: endDateRow.sourceProtocol || aggregate.source_protocol,
data_time: endDateRow.dataTime || aggregate.data_time,
updated_at: endDateRow.updatedAt || aggregate.updated_at,
});
@@ -393,9 +426,20 @@ export async function queryRangeMileage(startDate: string, endDate: string): Pro
buildPlateTargetNamesMap(targetRows),
).map(vehicle => {
const dailyMileage = perVehicleDaily.get(vehicle.plate) || {};
const dailySources = perVehicleDailySources.get(vehicle.plate) || {};
const categories = perVehicleSourceCategories.get(vehicle.plate);
const completedDailyMileage: Record<string, number> = {};
const completedDailySources: Record<string, OneOsProtocol | null> = {};
for (const day of days) completedDailyMileage[day] = dailyMileage[day] || 0;
return { ...vehicle, dailyMileage: completedDailyMileage };
for (const day of days) completedDailySources[day] = dailySources[day] || null;
return {
...vehicle,
dailyMileage: completedDailyMileage,
dailySourceProtocols: completedDailySources,
sourceCategory: categories && categories.size > 1
? 'MIXED' as const
: categories?.values().next().value || vehicle.sourceCategory,
};
});
return {