import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import pool from '../../db.js'; import { fetchVehicleInfoMap } from './vehicle-info.js'; import { fetchOneOsDailyMileage } from './oneos-api.js'; import { queryMonitoringRange } from './query-cache.js'; import { DEFAULT_ONEOS_PROTOCOL_PRIORITY, type OneOsProtocol } from './source-policy.js'; import { buildSummaryMileage } from './summary-model.js'; import { buildMonitoringFilters, buildPlateTargetNamesMap, buildTargetPlatesMap, dailyMileageMap, mergeMonitoringVehicles, previousMileageDate, toMileageRows, type TargetRow, } from './cache-model.js'; import type { CachedVehicle, MonitoringCache, MonitoringFilters } from './types.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const regionMap: Record = JSON.parse( readFileSync(join(__dirname, 'region-map.json'), 'utf8') ); let monitoringCache: MonitoringCache | null = null; export function getCache(): MonitoringCache | null { return monitoringCache; } function shanghaiDate(): string { return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', }).format(new Date()); } async function fetchTargetRows(): Promise { return pool.execute( `SELECT t.id, t.target_name, v.plate_number FROM lingniu_prod.tab_mileage_assessment_target t JOIN lingniu_prod.tab_mileage_assessment_vehicle v ON v.target_id = t.id AND v.is_deleted = 0 WHERE t.is_deleted = 0` ).then(([rows]) => rows as TargetRow[]); } export async function refreshMonitoringCache(): Promise { try { console.log('[mileage] refreshing monitoring cache...'); const start = Date.now(); const date = shanghaiDate(); const [apiRows, yesterdayRows, infoMap, targetRows] = await Promise.all([ fetchOneOsDailyMileage(date), fetchOneOsDailyMileage(previousMileageDate(date)), fetchVehicleInfoMap(), fetchTargetRows(), ]); const mileageRows = toMileageRows(apiRows); const yesterdayMap = dailyMileageMap(yesterdayRows); const targetPlatesMap = buildTargetPlatesMap(targetRows); const targetNamesByPlate = buildPlateTargetNamesMap(targetRows); const targetNames = Array.from(targetPlatesMap.keys()); const vehicles = mergeMonitoringVehicles( mileageRows, infoMap, yesterdayMap, targetNamesByPlate, regionMap, ); const totalToday = Math.round(vehicles.reduce((sum, v) => sum + v.dailyKm, 0)); const totalAll = vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0); monitoringCache = { vehicles, stats: { totalToday, totalAll, vehicleCount: vehicles.length }, filters: buildMonitoringFilters(vehicles, targetNames), targetPlatesMap, updatedAt: new Date().toISOString(), }; console.log(`[mileage] cache refreshed: ${vehicles.length} vehicles in ${Date.now() - start}ms`); } catch (e: unknown) { console.error('[mileage] cache refresh error:', e); } } export async function queryDateMileage( dateStr: string, protocolPriority?: OneOsProtocol[], ): Promise { const [apiRows, yesterdayRows, infoMap, targetRows] = await Promise.all([ fetchOneOsDailyMileage(dateStr, undefined, protocolPriority), fetchOneOsDailyMileage(previousMileageDate(dateStr), undefined, protocolPriority), fetchVehicleInfoMap(), fetchTargetRows(), ]); const mileageRows = toMileageRows(apiRows); const yesterdayMap = dailyMileageMap(yesterdayRows); return mergeMonitoringVehicles( mileageRows, infoMap, yesterdayMap, buildPlateTargetNamesMap(targetRows), regionMap, ); } export async function queryRangeMileage( startDate: string, endDate: string, protocolPriority: OneOsProtocol[] = DEFAULT_ONEOS_PROTOCOL_PRIORITY, refresh = false, ) { const [cached, infoMap, targetRows] = await Promise.all([ queryMonitoringRange(startDate, endDate, protocolPriority, refresh), fetchVehicleInfoMap(), fetchTargetRows(), ]); const { summary, yesterdayRows } = cached.result; return { vehicles: buildSummaryMileage(summary, yesterdayRows, infoMap, targetRows, regionMap, cached.result.priorOdometers), summary, start: startDate, end: endDate, queriedAt: cached.queriedAt, cacheStatus: cached.cacheStatus, }; } export function buildDateFilters(vehicles: CachedVehicle[]): MonitoringFilters { return buildMonitoringFilters(vehicles, monitoringCache?.filters.targetNames || []); }