polish access difference workspace

This commit is contained in:
lingniu
2026-07-18 11:40:35 +08:00
parent 0802b1989a
commit 09e16e8d26
6 changed files with 230 additions and 30 deletions

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import { accessRowsToCSV, formatSeconds, thresholdForProtocol, updateProtocolThreshold } from './access';
import type { AccessVehicleRow } from '../../api/types';
import { accessIssueSummary, accessRowsToCSV, formatAccessTime, formatSeconds, thresholdForProtocol, updateProtocolThreshold } from './access';
describe('access domain helpers', () => {
it('formats duration without hiding sign or long offline windows', () => {
@@ -16,6 +17,23 @@ describe('access domain helpers', () => {
expect(updateProtocolThreshold(config.protocols, 'JT808', 120)).toEqual([{ protocol: 'JT808', thresholdSec: 120 }]);
});
it('keeps access timestamps and actionable differences explicit', () => {
expect(formatAccessTime('2026-07-18T02:33:00Z')).toBe('2026-07-18 10:33:00');
const row = {
vin: 'VIN1',
plate: '粤A1',
actualProtocols: ['GB32960', 'JT808'],
masterDataIssues: [],
connectionState: 'degraded',
protocolStatuses: [
{ protocol: 'GB32960', connected: true, onlineState: 'online', delayAbnormal: true },
{ protocol: 'JT808', connected: true, onlineState: 'offline', delayAbnormal: true }
]
} as unknown as AccessVehicleRow;
expect(accessIssueSummary(row)).toBe('JT808 已离线GB32960 数据延迟异常');
expect(accessIssueSummary({ ...row, actualProtocols: [], protocolStatuses: [], connectionState: 'not_connected' })).toBe('尚未发现真实接入来源');
});
it('exports explicit state and evidence fields', () => {
const csv = accessRowsToCSV([{ vin: 'VIN1', plate: '粤A1', oem: '', model: '', company: '示范企业', protocol: 'JT808', provider: '', source: '', firstSeenAt: '', latestEventAt: '', latestReceivedAt: '', reportIntervalSec: null, dataDelaySec: 2, freshnessSec: 3, onlineState: 'online', thresholdSec: 60, latestMessageType: '位置,数据', latestEventId: '', latestError: '', delayAbnormal: false, firstSeenEvidence: '', firstSeenSource: '', reportIntervalEvidence: '', reportSampleCount: 2, expectedProtocols: [], actualProtocols: ['JT808'], missingProtocols: [], masterDataIssues: ['车辆品牌未维护'], protocolStatuses: [{ protocol: 'JT808', expected: false, connected: true, provider: 'G7', firstSeenAt: '2026-07-01T00:00:00+08:00', latestEventAt: '', latestReceivedAt: '2026-07-15T09:00:00+08:00', reportIntervalSec: 10, dataDelaySec: 2, freshnessSec: 3, onlineState: 'online', thresholdSec: 60, delayAbnormal: false, firstSeenEvidence: '网关首次观测', reportIntervalEvidence: '连续样本' }], connectionState: 'incomplete', expectationEvidence: '尚未接入业务应接口径' }]);
expect(csv).toContain('在线');

View File

@@ -1,7 +1,14 @@
import type { AccessProtocolThreshold, AccessThresholdConfig, AccessVehicleRow } from '../../api/types';
const accessTimeFormatter = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'Asia/Shanghai'
});
export const accessStateLabels: Record<AccessVehicleRow['onlineState'], string> = {
@@ -29,6 +36,25 @@ export function formatAccessTime(value: string) {
return accessTimeFormatter.format(parsed).replace(/\//g, '-');
}
export function accessIssueSummary(row: AccessVehicleRow) {
const issues: string[] = [];
const connected = row.protocolStatuses.filter((status) => status.connected);
const protocols = (items: typeof connected) => items.map((status) => status.protocol).join(' / ');
const offline = connected.filter((status) => status.onlineState === 'offline');
const uncertain = connected.filter((status) => status.onlineState === 'unknown' || status.onlineState === 'never_reported');
const delayed = connected.filter((status) => status.delayAbnormal && status.onlineState === 'online');
if (!row.actualProtocols.length) issues.push('尚未发现真实接入来源');
if (offline.length) issues.push(`${protocols(offline)} 已离线`);
if (uncertain.length) issues.push(`${protocols(uncertain)} 状态待确认`);
if (delayed.length) issues.push(`${protocols(delayed)} 数据延迟异常`);
if (row.masterDataIssues.length) issues.push(row.masterDataIssues.slice(0, 2).join('、'));
if (issues.length) return issues.slice(0, 2).join('');
if (row.connectionState === 'healthy') return '已接来源状态正常';
return '接入状态待核对';
}
export function thresholdForProtocol(config: AccessThresholdConfig, protocol: string) {
return config.protocols.find((item) => item.protocol === protocol)?.thresholdSec ?? config.defaultThresholdSec;
}