feat(platform): diagnose source consistency

This commit is contained in:
lingniu
2026-07-04 04:12:23 +08:00
parent 7b801b6f71
commit e077d99a5e
9 changed files with 104 additions and 5 deletions

View File

@@ -107,6 +107,10 @@ export interface VehicleSourceConsistency {
mileageDeltaKm: number;
sourceTimeDeltaSeconds: number;
scope: string;
status: string;
severity: string;
title: string;
detail: string;
}
export interface VehicleServiceOverview {

View File

@@ -100,8 +100,8 @@ export function AppShell({
</Tag>
) : null}
{currentVehicleConsistency ? (
<Tag color={currentVehicleConsistency.onlineSourceCount === currentVehicleConsistency.sourceCount ? 'green' : 'orange'}>
{currentVehicleConsistency.onlineSourceCount}/{currentVehicleConsistency.sourceCount}
<Tag color={currentVehicleConsistency.severity === 'ok' ? 'green' : currentVehicleConsistency.severity === 'error' ? 'red' : 'orange'}>
{currentVehicleConsistency.title || `${currentVehicleConsistency.onlineSourceCount}/${currentVehicleConsistency.sourceCount} 来源`}
</Tag>
) : null}
<Tag color="blue"></Tag>

View File

@@ -136,6 +136,8 @@ export function VehicleDetail({
const consistencyLocatedSourceCount = consistency?.locatedSourceCount ?? locatedSourceCount;
const consistencyMileageDelta = consistency?.mileageDeltaKm ?? mileageDelta;
const consistencyTimeDeltaSeconds = consistency?.sourceTimeDeltaSeconds ?? sourceTimeDeltaSeconds;
const consistencyTitle = consistency?.title ?? '-';
const consistencyDetail = consistency?.detail ?? '-';
const activeProtocol = query.protocol?.trim() ?? '';
const scopeText = activeProtocol ? `单一来源:${activeProtocol}` : '全部来源聚合';
const formKey = `${query.keyword}-${query.protocol ?? ''}`;
@@ -328,11 +330,13 @@ export function VehicleDetail({
<Descriptions
row
data={[
{ key: '一致性结论', value: consistencyTitle },
{ 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: consistencyDetail },
{ key: '车辆服务视角', value: activeProtocol ? `当前只看 ${activeProtocol}` : '三类来源合并为同一车辆服务' }
]}
/>

View File

@@ -386,7 +386,11 @@ test('shows resolved vehicle service status after topbar search', async () => {
locatedSourceCount: 2,
mileageDeltaKm: 0,
sourceTimeDeltaSeconds: 0,
scope: 'all_sources'
scope: 'all_sources',
status: 'degraded',
severity: 'warning',
title: '部分来源离线',
detail: '1/2 个来源在线,车辆服务可用但需要关注离线来源。'
}
},
traceId: 'trace-test',
@@ -432,7 +436,7 @@ test('shows resolved vehicle service status after topbar search', async () => {
expect(await screen.findByText('当前车辆:后端判定部分离线')).toBeInTheDocument();
expect(screen.getByText('粤AG18312 / LB9A32A24R0LS1426')).toBeInTheDocument();
expect(screen.getByText('一致性:1/2 来源')).toBeInTheDocument();
expect(screen.getByText('一致性:部分来源离线')).toBeInTheDocument();
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicle-service/overview?keyword=%E7%B2%A4AG18312'), undefined);
expect(fetchMock).not.toHaveBeenCalledWith(expect.stringContaining('/api/vehicles/resolve'), undefined);
});
@@ -1777,7 +1781,11 @@ test('shows cross-source consistency for one vehicle service', async () => {
locatedSourceCount: 3,
mileageDeltaKm: 0.4,
sourceTimeDeltaSeconds: 35,
scope: 'all_sources'
scope: 'all_sources',
status: 'degraded',
severity: 'warning',
title: '部分来源离线',
detail: '2/3 个来源在线,车辆服务可用但需要关注离线来源。'
},
sources: ['GB32960', 'JT808', 'YUTONG_MQTT'],
sourceStatus: [
@@ -1815,6 +1823,8 @@ test('shows cross-source consistency for one vehicle service', async () => {
render(<App />);
expect(await screen.findByText('跨来源一致性')).toBeInTheDocument();
expect(screen.getAllByText('部分来源离线').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('2/3 个来源在线,车辆服务可用但需要关注离线来源。')).toBeInTheDocument();
expect(screen.getByText('3 个来源')).toBeInTheDocument();
expect(screen.getByText('2/3 在线')).toBeInTheDocument();
expect(screen.getByText('3 个来源有位置')).toBeInTheDocument();