feat: build vehicle data platform and production pipeline

This commit is contained in:
lingniu
2026-07-14 12:35:33 +08:00
parent b452be3b94
commit bb59303a4b
270 changed files with 88016 additions and 1975 deletions

View File

@@ -1,10 +1,33 @@
import type {
ApiEnvelope,
AccessQuery,
AccessSummary,
AccessThresholdConfig,
AccessThresholdUpdate,
AccessUnresolvedIdentity,
AccessUnresolvedIdentityQuery,
AccessVehicleRow,
AlertAction,
AlertEvent,
AlertNotification,
AlertQuery,
AlertRule,
AlertRuleInput,
AlertSummary,
DailyMileageRow,
DashboardSummary,
HistoryLocationRow,
HistoryDataResponse,
HistorySeriesResponse,
HistoryExportJob,
HistoryExportRequest,
HistoryMetricCatalog,
MetricCatalog,
LatestTelemetryResponse,
MileageSummary,
MapReverseGeocode,
MonitorMapResponse,
MonitorSummary,
OnlineStatisticsSummary,
OnlineVehicleStatusRow,
OpsHealth,
@@ -15,15 +38,22 @@ import type {
RawFrameRow,
RealtimeLocationRow,
SourceReadinessPlan,
SessionInfo,
TrackPlaybackResponse,
VehicleRealtimeRow,
VehicleCoverageRow,
VehicleCoverageSummary,
VehicleDetail,
VehicleProfile,
VehicleProfileInput,
VehicleProfileSyncRequest,
VehicleProfileSyncResult,
VehicleIdentityResolution,
VehicleServiceOverview,
VehicleServiceSummary,
VehicleRow
} from './types';
import { getAccessToken } from '../v2/auth/session';
export type RawFrameQuery = {
keyword?: string;
@@ -54,7 +84,9 @@ type ApiErrorEnvelope = {
};
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(path, init);
const token = getAccessToken();
const requestInit = token ? { ...init, headers: withAuthorization(init?.headers, token) } : init;
const response = await fetch(path, requestInit);
if (!response.ok) {
throw new Error(await responseErrorMessage(response));
}
@@ -62,6 +94,12 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
return envelope.data;
}
function withAuthorization(headers: HeadersInit | undefined, token: string) {
const authorized = new Headers(headers);
authorized.set('Authorization', `Bearer ${token}`);
return authorized;
}
async function responseErrorMessage(response: Response) {
try {
const envelope = (await response.json()) as ApiErrorEnvelope;
@@ -85,12 +123,66 @@ function withTraceID(message: string, traceID?: string) {
}
export const api = {
session: () => request<SessionInfo>('/api/v2/session'),
monitorSummary: (params = new URLSearchParams()) => request<MonitorSummary>(`/api/v2/monitor/summary?${params.toString()}`),
monitorMap: (params = new URLSearchParams()) => request<MonitorMapResponse>(`/api/v2/monitor/map?${params.toString()}`),
trackPlayback: (params = new URLSearchParams()) => request<TrackPlaybackResponse>(`/api/v2/tracks?${params.toString()}`),
metricCatalog: () => request<MetricCatalog>('/api/v2/metrics'),
historyMetricCatalog: () => request<HistoryMetricCatalog>('/api/v2/history/metrics'),
historyData: (params = new URLSearchParams()) => request<HistoryDataResponse>(`/api/v2/history/query?${params.toString()}`),
historySeries: (params = new URLSearchParams()) => request<HistorySeriesResponse>(`/api/v2/history/series?${params.toString()}`),
historyExports: () => request<HistoryExportJob[]>('/api/v2/exports'),
createHistoryExport: (query: HistoryExportRequest) => request<HistoryExportJob>('/api/v2/exports', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
}),
accessSummary: (query: AccessQuery) => request<AccessSummary>('/api/v2/access/summary', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
}),
accessVehicles: (query: AccessQuery) => request<Page<AccessVehicleRow>>('/api/v2/access/vehicles', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
}),
accessUnresolvedIdentities: (query: AccessUnresolvedIdentityQuery) => request<Page<AccessUnresolvedIdentity>>('/api/v2/access/unresolved-identities', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
}),
accessThresholds: () => request<AccessThresholdConfig>('/api/v2/access/thresholds'),
updateAccessThresholds: (update: AccessThresholdUpdate) => request<AccessThresholdConfig>('/api/v2/access/thresholds', {
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(update)
}),
alertSummaryV2: (query: AlertQuery) => request<AlertSummary>('/api/v2/alerts/summary', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
}),
alertEventsV2: (query: AlertQuery) => request<Page<AlertEvent>>('/api/v2/alerts/events', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
}),
alertEventV2: (id: string) => request<AlertEvent>(`/api/v2/alerts/events/${encodeURIComponent(id)}`),
actOnAlertV2: (id: string, action: Pick<AlertAction, 'action' | 'note'> & { version: number; actor?: string }) => request<AlertEvent>(`/api/v2/alerts/events/${encodeURIComponent(id)}/actions`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(action)
}),
alertRulesV2: () => request<AlertRule[]>('/api/v2/alerts/rules'),
saveAlertRuleV2: (input: AlertRuleInput) => request<AlertRule>(input.version > 0 ? `/api/v2/alerts/rules/${encodeURIComponent(input.id)}` : '/api/v2/alerts/rules', {
method: input.version > 0 ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
}),
setAlertRuleEnabledV2: (id: string, update: { version: number; enabled: boolean; actor?: string }) => request<AlertRule>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/enabled`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(update)
}),
alertNotificationsV2: (params = new URLSearchParams()) => request<Page<AlertNotification>>(`/api/v2/alerts/notifications?${params.toString()}`),
readAlertNotificationsV2: (ids: number[]) => request<{ updated: number }>('/api/v2/alerts/notifications/read', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids })
}),
dashboardSummary: () => request<DashboardSummary>('/api/dashboard/summary'),
vehicles: (params = new URLSearchParams()) => request<Page<VehicleRow>>(`/api/vehicles?${params.toString()}`),
vehicleResolve: (params = new URLSearchParams()) => request<VehicleIdentityResolution>(`/api/vehicles/resolve?${params.toString()}`),
vehicleCoverage: (params = new URLSearchParams()) => request<Page<VehicleCoverageRow>>(`/api/vehicles/coverage?${params.toString()}`),
vehicleCoverageSummary: (params = new URLSearchParams()) => request<VehicleCoverageSummary>(`/api/vehicles/coverage/summary?${params.toString()}`),
vehicleDetail: (params = new URLSearchParams()) => request<VehicleDetail>(`/api/vehicle-service?${params.toString()}`),
vehicleProfile: (vin: string) => request<VehicleProfile>(`/api/v2/vehicles/${encodeURIComponent(vin)}/profile`),
latestTelemetry: (vin: string) => request<LatestTelemetryResponse>(`/api/v2/vehicles/${encodeURIComponent(vin)}/telemetry/latest`),
updateVehicleProfile: (vin: string, input: VehicleProfileInput) => request<VehicleProfile>(`/api/v2/vehicles/${encodeURIComponent(vin)}/profile`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
}),
syncVehicleProfiles: (input: VehicleProfileSyncRequest) => request<VehicleProfileSyncResult>('/api/v2/vehicle-profiles/sync', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
}),
vehicleServiceSummary: () => request<VehicleServiceSummary>('/api/vehicle-service/summary'),
vehicleServiceOverview: (params = new URLSearchParams()) => request<VehicleServiceOverview>(`/api/vehicle-service/overview?${params.toString()}`),
vehicleServiceOverviews: (query: VehicleOverviewBatchQuery) => request<Page<VehicleServiceOverview>>('/api/vehicle-service/overviews', {