diff --git a/vehicle-data-platform/apps/api/internal/platform/access.go b/vehicle-data-platform/apps/api/internal/platform/access.go index 1070fc8a..9f243138 100644 --- a/vehicle-data-platform/apps/api/internal/platform/access.go +++ b/vehicle-data-platform/apps/api/internal/platform/access.go @@ -132,6 +132,9 @@ func (s *Service) AccessSummary(ctx context.Context, query AccessQuery) (AccessS case "degraded": result.DegradedVehicles++ } + if len(row.MasterDataIssues) > 0 { + result.MasterDataIncompleteVehicles++ + } switch row.OnlineState { case "online": result.OnlineVehicles++ @@ -502,7 +505,11 @@ func keepAccessRow(row AccessVehicleRow, query AccessQuery) bool { return false } if value := strings.TrimSpace(query.ConnectionState); value != "" && value != "all" { - if value == "attention" { + if value == "master_data" { + if len(row.MasterDataIssues) == 0 { + return false + } + } else if value == "attention" { if row.ConnectionState == "healthy" { return false } diff --git a/vehicle-data-platform/apps/api/internal/platform/access_test.go b/vehicle-data-platform/apps/api/internal/platform/access_test.go index bff195a5..8fc1961c 100644 --- a/vehicle-data-platform/apps/api/internal/platform/access_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/access_test.go @@ -26,6 +26,9 @@ func TestAccessSummaryUsesDynamicFreshnessAndDelay(t *testing.T) { if summary.HealthyVehicles != 1 || summary.IncompleteVehicles != 0 || summary.DegradedVehicles != 2 { t.Fatalf("connection-state counters must be mutually exclusive: %+v", summary) } + if summary.MasterDataIncompleteVehicles != 1 { + t.Fatalf("master-data maintenance must be counted independently from connection health: %+v", summary) + } } func TestAccessVehiclesFiltersAndKeepsEvidenceGapsExplicit(t *testing.T) { @@ -50,6 +53,10 @@ func TestAccessVehiclesFiltersAndKeepsEvidenceGapsExplicit(t *testing.T) { if err != nil || attention.Total != 5 { t.Fatalf("attention filter should return every vehicle requiring attention: page=%+v err=%v", attention, err) } + masterData, err := service.AccessVehicles(context.Background(), AccessQuery{ConnectionState: "master_data", Limit: 20}) + if err != nil || masterData.Total != 1 || len(masterData.Items[0].MasterDataIssues) == 0 { + t.Fatalf("master-data queue should include issues regardless of connection state: page=%+v err=%v", masterData, err) + } } func TestAccessVehiclesSupportsModelProviderAndTimeFilters(t *testing.T) { diff --git a/vehicle-data-platform/apps/api/internal/platform/model.go b/vehicle-data-platform/apps/api/internal/platform/model.go index 98ae82a7..cbefd295 100644 --- a/vehicle-data-platform/apps/api/internal/platform/model.go +++ b/vehicle-data-platform/apps/api/internal/platform/model.go @@ -520,22 +520,23 @@ type AccessDistribution struct { } type AccessSummary struct { - TotalVehicles int `json:"totalVehicles"` - OnlineVehicles int `json:"onlineVehicles"` - OfflineVehicles int `json:"offlineVehicles"` - LongOfflineVehicles int `json:"longOfflineVehicles"` - NeverReported int `json:"neverReported"` - UnknownVehicles int `json:"unknownVehicles"` - DelayAbnormal int `json:"delayAbnormal"` - ReportedToday int `json:"reportedToday"` - HealthyVehicles int `json:"healthyVehicles"` - IncompleteVehicles int `json:"incompleteVehicles"` - DegradedVehicles int `json:"degradedVehicles"` - OnlineRate float64 `json:"onlineRate"` - Protocols []AccessDistribution `json:"protocols"` - OEMs []AccessDistribution `json:"oems"` - AsOf string `json:"asOf"` - ThresholdVersion int `json:"thresholdVersion"` + TotalVehicles int `json:"totalVehicles"` + OnlineVehicles int `json:"onlineVehicles"` + OfflineVehicles int `json:"offlineVehicles"` + LongOfflineVehicles int `json:"longOfflineVehicles"` + NeverReported int `json:"neverReported"` + UnknownVehicles int `json:"unknownVehicles"` + DelayAbnormal int `json:"delayAbnormal"` + ReportedToday int `json:"reportedToday"` + HealthyVehicles int `json:"healthyVehicles"` + IncompleteVehicles int `json:"incompleteVehicles"` + DegradedVehicles int `json:"degradedVehicles"` + MasterDataIncompleteVehicles int `json:"masterDataIncompleteVehicles"` + OnlineRate float64 `json:"onlineRate"` + Protocols []AccessDistribution `json:"protocols"` + OEMs []AccessDistribution `json:"oems"` + AsOf string `json:"asOf"` + ThresholdVersion int `json:"thresholdVersion"` } type AccessProtocolThreshold struct { diff --git a/vehicle-data-platform/apps/web/src/api/types.ts b/vehicle-data-platform/apps/web/src/api/types.ts index 25940cc7..e62ba748 100644 --- a/vehicle-data-platform/apps/web/src/api/types.ts +++ b/vehicle-data-platform/apps/web/src/api/types.ts @@ -300,7 +300,7 @@ export interface AccessDistribution { name: string; total: number; online: numbe export interface AccessSummary { totalVehicles: number; onlineVehicles: number; offlineVehicles: number; longOfflineVehicles: number; neverReported: number; unknownVehicles: number; delayAbnormal: number; reportedToday: number; onlineRate: number; - healthyVehicles: number; incompleteVehicles: number; degradedVehicles: number; + healthyVehicles: number; incompleteVehicles: number; degradedVehicles: number; masterDataIncompleteVehicles: number; protocols: AccessDistribution[]; oems: AccessDistribution[]; asOf: string; thresholdVersion: number; } export interface AccessProtocolThreshold { protocol: string; thresholdSec: number; } diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx index 13d19f9b..2ea42640 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx @@ -32,7 +32,7 @@ function accessRow(vin: string, plate: string): AccessVehicleRow { } function prepareBaseData() { - mocks.accessSummary.mockResolvedValue({ totalVehicles: 2, onlineVehicles: 2, offlineVehicles: 0, longOfflineVehicles: 0, neverReported: 0, unknownVehicles: 0, delayAbnormal: 0, reportedToday: 2, onlineRate: 100, healthyVehicles: 2, incompleteVehicles: 0, degradedVehicles: 0, protocols: [], oems: [{ name: '测试品牌', total: 2, online: 2, onlineRate: 100 }], asOf: '2026-07-16T04:00:00Z', thresholdVersion: 1 }); + mocks.accessSummary.mockResolvedValue({ totalVehicles: 2, onlineVehicles: 2, offlineVehicles: 0, longOfflineVehicles: 0, neverReported: 0, unknownVehicles: 0, delayAbnormal: 0, reportedToday: 2, onlineRate: 100, healthyVehicles: 2, incompleteVehicles: 0, degradedVehicles: 0, masterDataIncompleteVehicles: 0, protocols: [], oems: [{ name: '测试品牌', total: 2, online: 2, onlineRate: 100 }], asOf: '2026-07-16T04:00:00Z', thresholdVersion: 1 }); mocks.accessUnresolvedIdentities.mockResolvedValue({ items: [], total: 0, limit: 20, offset: 0 }); mocks.accessThresholds.mockResolvedValue({ version: 1, defaultThresholdSec: 300, delayThresholdSec: 60, longOfflineSec: 86_400, protocols: [], updatedBy: 'test', updatedAt: '2026-07-16T04:00:00Z', audit: [] }); } diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx index c2026658..73fc51da 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx @@ -120,10 +120,10 @@ export default function AccessPage() { return
只展示车辆真实存在的来源、在线健康和待维护资料;未接业务系统前不推断应接协议
| 车辆 | 品牌 / 车型 | 真实来源 | {PROTOCOLS.map((item) =>{item} | )}综合状态 |
|---|---|---|---|---|
| {row.plate || '未绑定车牌'}{row.vin} | {row.oem || '品牌未维护'}{row.model || row.company || '车型未维护'} | {row.actualProtocols.length} 个{row.actualProtocols.join(' / ') || '尚无来源'} | {PROTOCOLS.map((protocol) =>