feat(platform): share vehicle service verdict logic

This commit is contained in:
lingniu
2026-07-04 10:25:41 +08:00
parent 295a7df396
commit 1f116ccaa8
4 changed files with 158 additions and 81 deletions

View File

@@ -0,0 +1,43 @@
import { describe, expect, test } from 'vitest';
import { summarizeVehicleService } from './vehicleService';
describe('summarizeVehicleService', () => {
test('marks unresolved vehicles as unavailable', () => {
expect(summarizeVehicleService({ resolved: false, sourceCount: 1, onlineSourceCount: 1 })).toMatchObject({
status: '不可服务',
risk: '身份未归并到 VIN'
});
});
test('prioritizes quality issues before missing source warnings', () => {
expect(summarizeVehicleService({
resolved: true,
sourceCount: 3,
onlineSourceCount: 2,
qualityIssueCount: 3,
missingProtocols: ['YUTONG_MQTT']
})).toMatchObject({
status: '降级可服务',
risk: '3 项质量问题影响可信度'
});
});
test('detects missing protocol evidence', () => {
expect(summarizeVehicleService({
resolved: true,
sourceCount: 2,
onlineSourceCount: 2,
missingProtocols: ['JT808', 'YUTONG_MQTT']
})).toMatchObject({
status: '降级可服务',
risk: '缺少 JT808、YUTONG_MQTT 来源'
});
});
test('marks complete online evidence as serviceable', () => {
expect(summarizeVehicleService({ resolved: true, sourceCount: 3, onlineSourceCount: 3 })).toMatchObject({
status: '可服务',
color: 'green'
});
});
});

View File

@@ -0,0 +1,85 @@
export type VehicleServiceVerdict = {
status: string;
color: 'green' | 'orange' | 'red';
risk: string;
nextStep: string;
};
export type VehicleServiceVerdictInput = {
resolved?: boolean;
bindingStatus?: string;
sourceCount?: number;
onlineSourceCount?: number;
qualityIssueCount?: number;
missingProtocols?: string[];
mileageDeltaKm?: number;
};
function isFiniteNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value);
}
function formatCompactNumber(value: number, suffix = '') {
return `${value.toLocaleString(undefined, { maximumFractionDigits: 1 })}${suffix}`;
}
export function summarizeVehicleService(input: VehicleServiceVerdictInput): VehicleServiceVerdict {
if (input.resolved === false || input.bindingStatus === 'unbound') {
return {
status: '不可服务',
color: 'red',
risk: '身份未归并到 VIN',
nextStep: '先维护车辆身份绑定,再使用实时、历史和里程服务。'
};
}
const sourceCount = input.sourceCount ?? 0;
const onlineSourceCount = input.onlineSourceCount ?? 0;
if (sourceCount <= 0) {
return {
status: '不可服务',
color: 'red',
risk: '没有任何来源证据',
nextStep: '优先确认平台转发、端口、订阅和绑定配置。'
};
}
if (onlineSourceCount <= 0) {
return {
status: '不可服务',
color: 'red',
risk: '全部来源离线',
nextStep: '先恢复至少一个实时来源,再判断车辆状态。'
};
}
const qualityIssueCount = input.qualityIssueCount ?? 0;
if (qualityIssueCount > 0) {
return {
status: '降级可服务',
color: 'orange',
risk: `${qualityIssueCount} 项质量问题影响可信度`,
nextStep: '先处理质量问题,再将该车用于 BI、告警或对外查询。'
};
}
const missingProtocols = input.missingProtocols ?? [];
if (missingProtocols.length > 0) {
return {
status: '降级可服务',
color: 'orange',
risk: `缺少 ${missingProtocols.join('、')} 来源`,
nextStep: '车辆仍可查询,但需要补齐缺失来源后再做跨来源校验。'
};
}
if (isFiniteNumber(input.mileageDeltaKm) && input.mileageDeltaKm > 1) {
return {
status: '降级可服务',
color: 'orange',
risk: `跨来源里程差异 ${formatCompactNumber(input.mileageDeltaKm, ' km')}`,
nextStep: '核对各来源总里程口径,确认统计使用的主来源。'
};
}
return {
status: '可服务',
color: 'green',
risk: '身份、来源和核心实时证据可用',
nextStep: '可进入实时、历史、RAW 和里程查询继续分析。'
};
}