Files
ln-bi/src/modules/mileage/attribution.test.ts
T

89 lines
3.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildMileageAttribution,
filterAttributionVehicles,
reconcileMileageAttribution,
} from './attribution.js';
import type { TargetVehicle } from './types.js';
function vehicle(overrides: Partial<TargetVehicle>): TargetVehicle {
return {
plateNumber: '粤A00000',
todayMileage: 0,
totalMileage: 0,
completionRate: 0,
isQualified: false,
currentYearIsQualified: false,
dailyRequiredMileage: 100,
rentStatus: null,
department: null,
customer: null,
isOnline: false,
...overrides,
};
}
test('groups vehicles and calculates additive attribution metrics', () => {
const result = buildMileageAttribution([
vehicle({ plateNumber: '粤A1', department: '业务一部', todayMileage: 120, totalMileage: 1000, completionRate: 0.8, isOnline: true }),
vehicle({ plateNumber: '粤A2', department: '业务一部', todayMileage: 40, totalMileage: 500, completionRate: 0.4 }),
vehicle({ plateNumber: '粤A3', department: '业务二部', todayMileage: 40, totalMileage: 400, completionRate: 0.2 }),
], 'department', 1000);
const first = result.find(group => group.key === '业务一部');
assert.deepEqual(first, {
key: '业务一部',
label: '业务一部',
vehicleCount: 2,
onlineCount: 1,
zeroMileageCount: 0,
behindCount: 1,
todayMileage: 160,
totalMileage: 1500,
targetMileage: 2000,
completedMileage: 1500,
shortfallMileage: 500,
completionRate: 75,
contributionRate: 80,
});
});
test('keeps unassigned vehicles visible and sorts largest shortfall first', () => {
const result = buildMileageAttribution([
vehicle({ plateNumber: '粤A1', department: null, todayMileage: 0 }),
vehicle({ plateNumber: '粤A2', department: '业务一部', todayMileage: 80, totalMileage: 100 }),
], 'department', 100);
assert.equal(result[0].label, '未分配部门');
assert.equal(result[0].shortfallMileage, 100);
assert.equal(result[0].zeroMileageCount, 1);
});
test('reconciles additive group shortfall with the target summary at display precision', () => {
const groups = buildMileageAttribution([
vehicle({ plateNumber: '粤A1', department: '业务一部', totalMileage: 1200 }),
vehicle({ plateNumber: '粤A2', department: '业务二部', totalMileage: 499.96 }),
], 'department', 1000);
assert.deepEqual(reconcileMileageAttribution(groups, 500), {
matches: true,
attributedShortfall: 500,
expectedShortfall: 500,
difference: 0,
});
assert.equal(reconcileMileageAttribution(groups, 450).difference, 50);
});
test('filters a group using the same empty-value semantics as aggregation', () => {
const vehicles = [
vehicle({ plateNumber: '粤A1', customer: null }),
vehicle({ plateNumber: '粤A2', customer: '客户甲' }),
];
assert.deepEqual(
filterAttributionVehicles(vehicles, 'customer', '未绑定客户').map(item => item.plateNumber),
['粤A1'],
);
});