feat(platform): summarize vehicle service verdicts
This commit is contained in:
@@ -8,7 +8,7 @@ import { PageHeader } from '../components/PageHeader';
|
||||
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||
import { StatusTag } from '../components/StatusTag';
|
||||
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
||||
import { summarizeVehicleService } from '../domain/vehicleService';
|
||||
import { summarizeVehicleService, type VehicleServiceVerdict } from '../domain/vehicleService';
|
||||
|
||||
function vehicleServiceStatus(row: VehicleCoverageRow) {
|
||||
if (row.serviceStatus) {
|
||||
@@ -27,6 +27,16 @@ function vehicleServiceStatus(row: VehicleCoverageRow) {
|
||||
return { label: verdict.status, color: verdict.color };
|
||||
}
|
||||
|
||||
function rowServiceVerdict(row: VehicleCoverageRow): VehicleServiceVerdict {
|
||||
return summarizeVehicleService({
|
||||
bindingStatus: row.bindingStatus,
|
||||
sourceCount: row.sourceCount,
|
||||
onlineSourceCount: row.onlineSourceCount,
|
||||
missingProtocols: row.missingProtocols,
|
||||
mileageDeltaKm: row.sourceConsistency?.mileageDeltaKm
|
||||
});
|
||||
}
|
||||
|
||||
function sourceEvidenceText(row: VehicleCoverageRow) {
|
||||
return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`;
|
||||
}
|
||||
@@ -188,6 +198,14 @@ type VehicleActionRecommendation = {
|
||||
filters: Record<string, string> | null;
|
||||
};
|
||||
|
||||
type PageVerdictStat = {
|
||||
status: string;
|
||||
count: number;
|
||||
color: 'green' | 'orange' | 'red';
|
||||
risk: string;
|
||||
nextStep: string;
|
||||
};
|
||||
|
||||
function vehicleActionRecommendation(row: VehicleCoverageRow): VehicleActionRecommendation {
|
||||
const verdict = summarizeVehicleService({
|
||||
bindingStatus: row.bindingStatus,
|
||||
@@ -211,6 +229,41 @@ function vehicleActionRecommendation(row: VehicleCoverageRow): VehicleActionReco
|
||||
return { label: '持续观察', color: verdict.color, filters: null };
|
||||
}
|
||||
|
||||
function currentPageVerdictStats(rows: VehicleCoverageRow[]): PageVerdictStat[] {
|
||||
const stats = new Map<string, PageVerdictStat>();
|
||||
for (const row of rows) {
|
||||
const verdict = rowServiceVerdict(row);
|
||||
const current = stats.get(verdict.status);
|
||||
if (current) {
|
||||
current.count += 1;
|
||||
continue;
|
||||
}
|
||||
stats.set(verdict.status, {
|
||||
status: verdict.status,
|
||||
count: 1,
|
||||
color: verdict.color,
|
||||
risk: verdict.risk,
|
||||
nextStep: verdict.nextStep
|
||||
});
|
||||
}
|
||||
const order = ['不可服务', '降级可服务', '可服务'];
|
||||
return [...stats.values()].sort((left, right) => order.indexOf(left.status) - order.indexOf(right.status));
|
||||
}
|
||||
|
||||
function currentPageActionStats(rows: VehicleCoverageRow[]) {
|
||||
const stats = new Map<string, { label: string; count: number; color: 'green' | 'orange' | 'red'; filters: Record<string, string> | null }>();
|
||||
for (const row of rows) {
|
||||
const action = vehicleActionRecommendation(row);
|
||||
const current = stats.get(action.label);
|
||||
if (current) {
|
||||
current.count += 1;
|
||||
continue;
|
||||
}
|
||||
stats.set(action.label, { label: action.label, count: 1, color: action.color, filters: action.filters });
|
||||
}
|
||||
return [...stats.values()].sort((left, right) => right.count - left.count);
|
||||
}
|
||||
|
||||
function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record<string, string>) => void) {
|
||||
const consistency = row.sourceConsistency;
|
||||
if (!consistency) {
|
||||
@@ -354,6 +407,8 @@ export function Vehicles({
|
||||
return items;
|
||||
}, [summary]);
|
||||
const filterSummary = vehicleFilterSummary(filters);
|
||||
const pageVerdictStats = useMemo(() => currentPageVerdictStats(rows), [rows]);
|
||||
const pageActionStats = useMemo(() => currentPageActionStats(rows), [rows]);
|
||||
|
||||
const load = (values: Record<string, string> = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
@@ -577,6 +632,45 @@ export function Vehicles({
|
||||
)}
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
{rows.length > 0 ? (
|
||||
<div className="vp-current-service-board">
|
||||
<div>
|
||||
<div className="vp-current-service-title">当前页服务判读</div>
|
||||
<div className="vp-current-service-grid">
|
||||
{pageVerdictStats.map((item) => (
|
||||
<div key={item.status} className="vp-current-service-item">
|
||||
<Space spacing={6} wrap>
|
||||
<Tag color={item.color}>{item.status}</Tag>
|
||||
<Tag color="grey">{item.count.toLocaleString()} 辆</Tag>
|
||||
</Space>
|
||||
<div className="vp-current-service-risk">{item.risk}</div>
|
||||
<div className="vp-current-service-next">{item.nextStep}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="vp-current-service-title">建议下一步</div>
|
||||
<div className="vp-current-action-list">
|
||||
{pageActionStats.map((item) => (
|
||||
item.filters ? (
|
||||
<Button
|
||||
key={item.label}
|
||||
size="small"
|
||||
theme="light"
|
||||
type={item.color === 'red' ? 'danger' : 'warning'}
|
||||
onClick={() => applyFilters({ ...filters, ...item.filters })}
|
||||
>
|
||||
{item.label} {item.count.toLocaleString()}
|
||||
</Button>
|
||||
) : (
|
||||
<Tag key={item.label} color={item.color}>{item.label} {item.count.toLocaleString()}</Tag>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="vp-result-summary-grid">
|
||||
{resultSummary.map((item) => (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user