Files
ln-bi/src/modules/mileage/statistics/model.ts
T

186 lines
6.2 KiB
TypeScript

import type { TargetSummary, TargetVehicle, TargetYearlyAssessment, TrendPoint } from '../types';
export type ChartType = 'bar' | 'line' | 'area';
export type VehicleSort = 'asc' | 'desc';
export type PressureLevel = '健康' | '关注' | '高压';
export interface SelectedTargetMetrics {
assessment: TargetYearlyAssessment | null;
completion: number;
remaining: number;
daysLeft: number;
dailyTarget: number;
qualifiedRate: number;
trendDelta: number;
pressureLevel: PressureLevel;
}
export interface TargetTableRow {
assessment: TargetYearlyAssessment | null;
completion: number;
qualified: number;
halfQualified: number;
goal: number;
completed: number;
remainingMileage: number;
days: number;
daily: number;
taskPerVehicle: number;
}
export interface TargetTotals {
today: number;
cumulative: number;
vehicleCount: number;
averageCompletion: number;
}
export function getDefaultDate(now = new Date()): string {
const date = new Date(now);
if (date.getHours() < 5) date.setDate(date.getDate() - 1);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}
export function getCurrentDateLabel(now = new Date()): string {
return `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`;
}
export function fmtKm(value: number): string {
if (value >= 10000) return (value / 10000).toFixed(2) + '万';
return value.toLocaleString();
}
export function fmtPercent(value: number): string {
return `${value.toFixed(1)}%`;
}
export function getTargetAssessment(
target: TargetSummary,
selectedYear?: number,
): TargetYearlyAssessment | null {
if (target.yearlyAssessments.length === 0) return null;
return target.yearlyAssessments.find(item => item.yearNumber === selectedYear) || target.yearlyAssessments[0];
}
export function fmtDateLabel(date: string | null): string {
if (!date) return '';
const [year, month, day] = date.split('-');
return `${year}.${Number(month)}.${Number(day)}`;
}
export function shortTargetName(name: string): string {
const match = name.match(/(\d+)[辆台](.+)/);
if (!match) return name;
const count = match[1];
let desc = match[2];
desc = desc.replace('4.5T普货', '普货');
desc = desc.replace('4.5T冷链车', '冷藏车');
desc = desc.replace('4.5T冷链', '冷藏车');
desc = desc.replace('18T', '18T');
return `${count}${desc}`;
}
export function trendDateLabel(value: string): string {
const [month, day] = value.split('-');
return `${parseInt(month)}.${parseInt(day)}`;
}
export function orderTargets(targets: TargetSummary[]): TargetSummary[] {
const focused = targets.find(item => item.targetName.includes('羚牛136')) || targets[0];
return focused
? [focused, ...targets.filter(item => item.id !== focused.id)]
: targets;
}
export function initialAssessmentYears(targets: TargetSummary[]): Record<number, number> {
return Object.fromEntries(
targets.map(item => [item.id, item.yearlyAssessments[0]?.yearNumber || 1]),
);
}
export function deriveSelectedTargetMetrics(
target: TargetSummary | undefined,
selectedYear: number | undefined,
trendData: TrendPoint[],
): SelectedTargetMetrics {
const assessment = target ? getTargetAssessment(target, selectedYear) : null;
const completion = assessment?.completionRate ?? target?.avgCompletion ?? 0;
const remaining = assessment?.remaining ?? 0;
const daysLeft = assessment?.daysLeft ?? target?.daysLeft ?? 0;
const dailyTarget = assessment?.dailyTarget ?? (daysLeft > 0 ? remaining / daysLeft : 0);
const qualifiedRate = assessment?.qualifiedRate
?? (target?.vehicleCount ? target.yearQualifiedCount / target.vehicleCount * 100 : 0);
const latestTrend = trendData[trendData.length - 1];
const previousTrend = trendData[trendData.length - 2];
const trendDelta = latestTrend && previousTrend ? latestTrend.mileage - previousTrend.mileage : 0;
const pressureLevel = completion >= 90 ? '健康' : completion >= 70 ? '关注' : '高压';
return {
assessment,
completion,
remaining,
daysLeft,
dailyTarget,
qualifiedRate,
trendDelta,
pressureLevel,
};
}
export function deriveTargetTableRow(
target: TargetSummary,
selectedYear?: number,
): TargetTableRow {
const assessment = getTargetAssessment(target, selectedYear);
return {
assessment,
completion: assessment?.completionRate ?? target.avgCompletion,
qualified: assessment?.qualifiedCount ?? target.yearQualifiedCount,
halfQualified: assessment?.halfQualifiedCount ?? target.halfQualifiedCount,
goal: assessment?.target ?? target.currentYearTarget,
completed: assessment?.completed ?? target.currentYearCompleted,
remainingMileage: assessment?.remaining ?? target.remaining,
days: assessment?.daysLeft ?? target.daysLeft,
daily: assessment?.dailyTarget ?? target.dailyTarget,
taskPerVehicle: target.annualMileagePerVehicle * (assessment?.yearNumber || 1),
};
}
export function summarizeTargets(
targets: TargetSummary[],
assessmentYearMap: Record<number, number>,
): TargetTotals {
const totals = targets.reduce((result, target) => ({
today: result.today + target.todayTotal,
cumulative: result.cumulative + target.cumulativeTotal,
vehicleCount: result.vehicleCount + target.vehicleCount,
completion: result.completion
+ (getTargetAssessment(target, assessmentYearMap[target.id])?.completionRate ?? target.avgCompletion),
}), { today: 0, cumulative: 0, vehicleCount: 0, completion: 0 });
return {
today: totals.today,
cumulative: totals.cumulative,
vehicleCount: totals.vehicleCount,
averageCompletion: targets.length > 0 ? totals.completion / targets.length : 0,
};
}
export function filterAndSortTargetVehicles(
vehicles: TargetVehicle[],
search: string,
sort: VehicleSort,
): TargetVehicle[] {
return vehicles
.filter(vehicle => vehicle.plateNumber.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => {
const valueA = a.todayMileage || 0;
const valueB = b.todayMileage || 0;
return sort === 'desc' ? valueB - valueA : valueA - valueB;
});
}
export function totalTodayMileage(vehicles: TargetVehicle[]): number {
return vehicles.reduce((sum, vehicle) => sum + (vehicle.todayMileage || 0), 0);
}