feat(platform): expose source consistency
This commit is contained in:
@@ -90,6 +90,7 @@ export interface VehicleDetail {
|
||||
realtimeSummary?: VehicleRealtimeRow;
|
||||
serviceStatus?: VehicleServiceStatus;
|
||||
serviceOverview?: VehicleServiceOverview;
|
||||
sourceConsistency?: VehicleSourceConsistency;
|
||||
sources: string[];
|
||||
sourceStatus: VehicleSourceStatus[];
|
||||
realtime: RealtimeLocationRow[];
|
||||
@@ -99,6 +100,15 @@ export interface VehicleDetail {
|
||||
quality: Page<QualityIssueRow>;
|
||||
}
|
||||
|
||||
export interface VehicleSourceConsistency {
|
||||
sourceCount: number;
|
||||
onlineSourceCount: number;
|
||||
locatedSourceCount: number;
|
||||
mileageDeltaKm: number;
|
||||
sourceTimeDeltaSeconds: number;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface VehicleServiceOverview {
|
||||
vin: string;
|
||||
plate: string;
|
||||
|
||||
@@ -130,6 +130,12 @@ export function VehicleDetail({
|
||||
...(detail?.sourceStatus ?? []).map((source) => parseTimeMs(source.lastSeen))
|
||||
].filter(isFiniteNumber);
|
||||
const sourceTimeDeltaSeconds = timeValues.length > 1 ? (Math.max(...timeValues) - Math.min(...timeValues)) / 1000 : undefined;
|
||||
const consistency = detail?.sourceConsistency;
|
||||
const consistencySourceCount = consistency?.sourceCount ?? sourceCount;
|
||||
const consistencyOnlineSourceCount = consistency?.onlineSourceCount ?? onlineSourceCount;
|
||||
const consistencyLocatedSourceCount = consistency?.locatedSourceCount ?? locatedSourceCount;
|
||||
const consistencyMileageDelta = consistency?.mileageDeltaKm ?? mileageDelta;
|
||||
const consistencyTimeDeltaSeconds = consistency?.sourceTimeDeltaSeconds ?? sourceTimeDeltaSeconds;
|
||||
const activeProtocol = query.protocol?.trim() ?? '';
|
||||
const scopeText = activeProtocol ? `单一来源:${activeProtocol}` : '全部来源聚合';
|
||||
const formKey = `${query.keyword}-${query.protocol ?? ''}`;
|
||||
@@ -322,11 +328,11 @@ export function VehicleDetail({
|
||||
<Descriptions
|
||||
row
|
||||
data={[
|
||||
{ key: '来源覆盖', value: sourceCount > 0 ? `${sourceCount} 个来源` : '-' },
|
||||
{ key: '在线来源', value: sourceCount > 0 ? `${onlineSourceCount}/${sourceCount} 在线` : '-' },
|
||||
{ key: '位置覆盖', value: locatedSourceCount > 0 ? `${locatedSourceCount} 个来源有位置` : '暂无位置' },
|
||||
{ key: '里程差异', value: formatCompactNumber(mileageDelta, ' km') },
|
||||
{ key: '来源时间差', value: formatSeconds(sourceTimeDeltaSeconds) },
|
||||
{ key: '来源覆盖', value: consistencySourceCount > 0 ? `${consistencySourceCount} 个来源` : '-' },
|
||||
{ key: '在线来源', value: consistencySourceCount > 0 ? `${consistencyOnlineSourceCount}/${consistencySourceCount} 在线` : '-' },
|
||||
{ key: '位置覆盖', value: consistencyLocatedSourceCount > 0 ? `${consistencyLocatedSourceCount} 个来源有位置` : '暂无位置' },
|
||||
{ key: '里程差异', value: formatCompactNumber(consistencyMileageDelta, ' km') },
|
||||
{ key: '来源时间差', value: formatSeconds(consistencyTimeDeltaSeconds) },
|
||||
{ key: '车辆服务视角', value: activeProtocol ? `当前只看 ${activeProtocol}` : '三类来源合并为同一车辆服务' }
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1762,6 +1762,14 @@ test('shows cross-source consistency for one vehicle service', async () => {
|
||||
totalMileageKm: 1000.5,
|
||||
lastSeen: '2026-07-03T20:12:10+08:00'
|
||||
},
|
||||
sourceConsistency: {
|
||||
sourceCount: 3,
|
||||
onlineSourceCount: 2,
|
||||
locatedSourceCount: 3,
|
||||
mileageDeltaKm: 0.4,
|
||||
sourceTimeDeltaSeconds: 35,
|
||||
scope: 'all_sources'
|
||||
},
|
||||
sources: ['GB32960', 'JT808', 'YUTONG_MQTT'],
|
||||
sourceStatus: [
|
||||
{ protocol: 'GB32960', online: true, lastSeen: '2026-07-03T20:12:10+08:00', hasRealtime: true, hasHistory: true, hasRaw: true, hasMileage: true },
|
||||
@@ -1801,6 +1809,6 @@ test('shows cross-source consistency for one vehicle service', async () => {
|
||||
expect(screen.getByText('3 个来源')).toBeInTheDocument();
|
||||
expect(screen.getByText('2/3 在线')).toBeInTheDocument();
|
||||
expect(screen.getByText('3 个来源有位置')).toBeInTheDocument();
|
||||
expect(screen.getByText('0.3 km')).toBeInTheDocument();
|
||||
expect(screen.getByText('30 秒')).toBeInTheDocument();
|
||||
expect(screen.getByText('0.4 km')).toBeInTheDocument();
|
||||
expect(screen.getByText('35 秒')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user