feat: build vehicle data platform and production pipeline
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MONITOR_REFRESH, monitorMapQueryParams, monitorQueryParams } from './useMonitorData';
|
||||
|
||||
describe('monitor query params', () => {
|
||||
it('keeps server-owned status filters and bounded list size', () => {
|
||||
const params = monitorQueryParams({ keyword: ' 沪A ', protocol: 'JT808', status: 'driving' }, 200);
|
||||
expect(params.get('keyword')).toBe('沪A');
|
||||
expect(params.get('protocol')).toBe('JT808');
|
||||
expect(params.get('status')).toBe('driving');
|
||||
expect(params.get('online')).toBeNull();
|
||||
expect(params.get('limit')).toBe('200');
|
||||
});
|
||||
|
||||
it('retains compatibility online filter for online and offline states', () => {
|
||||
expect(monitorQueryParams({ keyword: '', protocol: '', status: 'offline' }, 200).get('online')).toBe('offline');
|
||||
});
|
||||
|
||||
it('drops stale viewport bounds for a direct vehicle search', () => {
|
||||
const viewport = { zoom: 13, bounds: '103,29,105,31' };
|
||||
expect(monitorMapQueryParams({ keyword: '粤A1', protocol: '', status: '' }, viewport).has('bounds')).toBe(false);
|
||||
expect(monitorMapQueryParams({ keyword: '', protocol: '', status: '' }, viewport).get('bounds')).toBe(viewport.bounds);
|
||||
});
|
||||
});
|
||||
|
||||
describe('monitor refresh cadence', () => {
|
||||
it('prioritizes one selected vehicle without polling the whole fleet too aggressively', () => {
|
||||
expect(MONITOR_REFRESH).toEqual({ summary: 30_000, fleet: 15_000, selected: 10_000, alerts: 15_000 });
|
||||
expect(MONITOR_REFRESH.selected).toBeLessThan(MONITOR_REFRESH.fleet);
|
||||
expect(MONITOR_REFRESH.fleet).toBeLessThan(MONITOR_REFRESH.summary);
|
||||
});
|
||||
});
|
||||
104
vehicle-data-platform/apps/web/src/v2/hooks/useMonitorData.ts
Normal file
104
vehicle-data-platform/apps/web/src/v2/hooks/useMonitorData.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '../../api/client';
|
||||
import type { VehicleRealtimeRow } from '../../api/types';
|
||||
|
||||
export type MonitorFilters = {
|
||||
keyword: string;
|
||||
protocol: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type MonitorViewport = {
|
||||
zoom: number;
|
||||
bounds: string;
|
||||
};
|
||||
|
||||
export const MONITOR_REFRESH = {
|
||||
summary: 30_000,
|
||||
fleet: 15_000,
|
||||
selected: 10_000,
|
||||
alerts: 15_000
|
||||
} as const;
|
||||
|
||||
export function monitorQueryParams(filters: MonitorFilters, limit: number) {
|
||||
const params = new URLSearchParams({ limit: String(limit) });
|
||||
if (filters.keyword.trim()) params.set('keyword', filters.keyword.trim());
|
||||
if (filters.protocol) params.set('protocol', filters.protocol);
|
||||
if (filters.status) params.set('status', filters.status);
|
||||
if (filters.status === 'online' || filters.status === 'offline') params.set('online', filters.status);
|
||||
return params;
|
||||
}
|
||||
|
||||
export function monitorMapQueryParams(filters: MonitorFilters, viewport: MonitorViewport) {
|
||||
const params = monitorQueryParams(filters, 10_000);
|
||||
params.set('zoom', String(viewport.zoom));
|
||||
if (viewport.bounds && !filters.keyword.trim()) params.set('bounds', viewport.bounds);
|
||||
return params;
|
||||
}
|
||||
|
||||
export function useMonitorData(filters: MonitorFilters, viewport: MonitorViewport, selectedVin: string) {
|
||||
const params = monitorQueryParams(filters, 200);
|
||||
const mapParams = monitorMapQueryParams(filters, viewport);
|
||||
|
||||
const summary = useQuery({
|
||||
queryKey: ['monitor', 'summary', params.toString()],
|
||||
queryFn: () => api.monitorSummary(params),
|
||||
refetchInterval: MONITOR_REFRESH.summary
|
||||
});
|
||||
const vehicles = useQuery({
|
||||
queryKey: ['monitor', 'vehicles', params.toString()],
|
||||
queryFn: () => api.vehicleRealtime(params),
|
||||
refetchInterval: MONITOR_REFRESH.fleet
|
||||
});
|
||||
|
||||
const map = useQuery({
|
||||
queryKey: ['monitor', 'map', mapParams.toString()],
|
||||
queryFn: () => api.monitorMap(mapParams),
|
||||
placeholderData: (previous) => previous,
|
||||
staleTime: 5_000,
|
||||
refetchInterval: MONITOR_REFRESH.fleet
|
||||
});
|
||||
|
||||
const selectedVehicle = useQuery({
|
||||
queryKey: ['monitor', 'selected-vehicle', selectedVin],
|
||||
queryFn: () => api.vehicleRealtime(new URLSearchParams({ keyword: selectedVin, limit: '1', offset: '0' })),
|
||||
enabled: Boolean(selectedVin),
|
||||
staleTime: 5_000,
|
||||
refetchInterval: selectedVin ? MONITOR_REFRESH.selected : false
|
||||
});
|
||||
|
||||
return { summary, vehicles, map, selectedVehicle };
|
||||
}
|
||||
|
||||
export function useMonitorVehicleCard(vin: string, vehicle?: VehicleRealtimeRow, activelyTracked = false) {
|
||||
const enabled = Boolean(vin);
|
||||
const longitude = vehicle?.longitude;
|
||||
const latitude = vehicle?.latitude;
|
||||
const hasCoordinate = Number.isFinite(longitude) && Number.isFinite(latitude)
|
||||
&& Math.abs(longitude ?? 0) <= 180 && Math.abs(latitude ?? 0) <= 90;
|
||||
|
||||
const detail = useQuery({
|
||||
queryKey: ['monitor', 'vehicle-card', 'detail', vin],
|
||||
queryFn: () => api.vehicleDetail(new URLSearchParams({ keyword: vin })),
|
||||
enabled,
|
||||
staleTime: 30_000
|
||||
});
|
||||
const activeAlerts = useQuery({
|
||||
queryKey: ['monitor', 'vehicle-card', 'active-alerts', vin],
|
||||
queryFn: () => api.alertEventsV2({ keyword: vin, status: 'active', limit: 20, offset: 0 }),
|
||||
enabled,
|
||||
staleTime: 10_000,
|
||||
refetchInterval: enabled && activelyTracked ? MONITOR_REFRESH.alerts : false
|
||||
});
|
||||
const address = useQuery({
|
||||
queryKey: ['monitor', 'vehicle-card', 'address', longitude, latitude],
|
||||
queryFn: () => api.reverseGeocode(new URLSearchParams({
|
||||
longitude: longitude!.toFixed(6),
|
||||
latitude: latitude!.toFixed(6)
|
||||
})),
|
||||
enabled: enabled && hasCoordinate,
|
||||
staleTime: 60 * 60_000
|
||||
});
|
||||
|
||||
return { detail, activeAlerts, address };
|
||||
}
|
||||
Reference in New Issue
Block a user