feat: integrate OneOS mileage APIs and release v1.1.10
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Hono } from 'hono';
|
||||
import pool from '../../db.js';
|
||||
import mileagePool from '../../mileage-db.js';
|
||||
import { fetchVehicleInfoMap } from '../mileage/vehicle-info.js';
|
||||
import { fetchOneOsMileageDates } from '../mileage/oneos-api.js';
|
||||
import { mapRegion } from '../vehicles.js';
|
||||
import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js';
|
||||
import { classifyVehicle, generateSuggestions } from './algorithm.js';
|
||||
@@ -42,6 +42,24 @@ function classifyVehicleType(typeName: string, _modelRaw: string): string {
|
||||
return t || '其他';
|
||||
}
|
||||
|
||||
function recentCompletedDates(count: number): string[] {
|
||||
const today = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
}).format(new Date());
|
||||
const [year, month, day] = today.split('-').map(Number);
|
||||
const base = new Date(Date.UTC(year, month - 1, day));
|
||||
const dates: string[] = [];
|
||||
for (let offset = count; offset >= 1; offset -= 1) {
|
||||
const date = new Date(base);
|
||||
date.setUTCDate(base.getUTCDate() - offset);
|
||||
dates.push(date.toISOString().slice(0, 10));
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Route
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -114,29 +132,33 @@ app.get('/', async (c) => {
|
||||
// ---- Collect all plates for Query 6 ----
|
||||
const allPlates = assessmentRows.map((r: any) => r.plate_number as string);
|
||||
|
||||
// ---- Query 6: Customer daily avg (from mileage DB) — 30d baseline + 7d recent ----
|
||||
// ---- Query 6: Customer daily avg (OneOS only) — 30d baseline + 7d recent ----
|
||||
const customerAvgDailyMap = new Map<string, number>();
|
||||
const customerAvgDaily7dMap = new Map<string, number>();
|
||||
if (allPlates.length > 0) {
|
||||
const placeholders = allPlates.map(() => '?').join(',');
|
||||
// Single query returning both windows per plate.
|
||||
const [dailyRows] = await mileagePool.execute(
|
||||
`SELECT plate,
|
||||
AVG(CASE WHEN stat_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN daily_km END) AS avg_30d,
|
||||
AVG(CASE WHEN stat_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) THEN daily_km END) AS avg_7d
|
||||
FROM v_vehicle_daily_stats
|
||||
WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
AND stat_date < CURDATE()
|
||||
AND plate IN (${placeholders})
|
||||
GROUP BY plate`,
|
||||
allPlates,
|
||||
) as [any[], unknown];
|
||||
|
||||
const dates = recentCompletedDates(30);
|
||||
const sevenDayDates = new Set(dates.slice(-7));
|
||||
const allowedPlates = new Set(allPlates);
|
||||
const rowsByDate = await fetchOneOsMileageDates(dates);
|
||||
const aggregates = new Map<string, { sum30: number; count30: number; sum7: number; count7: number }>();
|
||||
for (const [date, rows] of rowsByDate) {
|
||||
for (const row of rows) {
|
||||
if (!allowedPlates.has(row.plateNumber) || row.status !== 'NORMAL' || row.dailyMileageKm === null) continue;
|
||||
const current = aggregates.get(row.plateNumber) || { sum30: 0, count30: 0, sum7: 0, count7: 0 };
|
||||
current.sum30 += row.dailyMileageKm;
|
||||
current.count30 += 1;
|
||||
if (sevenDayDates.has(date)) {
|
||||
current.sum7 += row.dailyMileageKm;
|
||||
current.count7 += 1;
|
||||
}
|
||||
aggregates.set(row.plateNumber, current);
|
||||
}
|
||||
}
|
||||
const plateAvg30Map = new Map<string, number>();
|
||||
const plateAvg7Map = new Map<string, number>();
|
||||
for (const row of dailyRows) {
|
||||
if (row.avg_30d !== null) plateAvg30Map.set(row.plate, Number(row.avg_30d));
|
||||
if (row.avg_7d !== null) plateAvg7Map.set(row.plate, Number(row.avg_7d));
|
||||
for (const [plate, values] of aggregates) {
|
||||
if (values.count30 > 0) plateAvg30Map.set(plate, values.sum30 / values.count30);
|
||||
if (values.count7 > 0) plateAvg7Map.set(plate, values.sum7 / values.count7);
|
||||
}
|
||||
|
||||
const customerPlates30 = new Map<string, number[]>();
|
||||
|
||||
Reference in New Issue
Block a user