/** * Promised against happened. * * The rows below are copied from production (tenant 916, 29 August 2026) rather * than invented, because every awkward thing this module exists to handle is * something the live data does and a hand-written fixture would not: a * `starttime` later than `arrivaltime`, an `actualkms` identical to `kms`, a * `riderkms` of 0.0026, and a promise written in twelve-hour time. */ import assert from 'node:assert/strict'; import { test } from 'node:test'; import type { DeliveryRow } from '@/api/types'; import { compare, journeyOf, lateness, parsePromise, span } from './plannedVsActual'; /** One batch of four, assigned together — the rows that revealed the ladder. */ const BATCH: DeliveryRow[] = [ { deliveryid: 1, orderid: '916-1', ridername: 'Varun', orderstatus: 'delivered', assigntime: '2026-08-29 11:22:36', arrivaltime: '2026-08-29 12:37:26', pickuptime: '2026-08-29 12:41:20', starttime: '2026-08-29 13:16:23', deliverytime: '2026-08-29 13:16:57', expecteddeliverytime: '2026-08-29 01:00 PM', kms: '6', actualkms: '6.0', riderkms: '4.2', }, { deliveryid: 2, orderid: '916-2', ridername: 'Varun', orderstatus: 'delivered', assigntime: '2026-08-29 11:22:36', arrivaltime: '2026-08-29 12:37:29', pickuptime: '2026-08-29 12:41:21', starttime: '2026-08-29 13:17:19', deliverytime: '2026-08-29 13:38:26', expecteddeliverytime: '2026-08-29 01:30 PM', kms: '8', actualkms: '8.0', riderkms: '6.1', }, { deliveryid: 3, orderid: '916-3', ridername: 'Varun', orderstatus: 'delivered', assigntime: '2026-08-29 11:22:36', arrivaltime: '2026-08-29 12:37:30', pickuptime: '2026-08-29 12:41:23', starttime: '2026-08-29 13:38:37', deliverytime: '2026-08-29 13:44:14', expecteddeliverytime: '2026-08-29 01:20 PM', kms: '2', actualkms: '2.0', // A failed GPS reading, exactly as production sends it. riderkms: '0.0026', }, { deliveryid: 4, orderid: '916-4', ridername: 'Varun', orderstatus: 'delivered', assigntime: '2026-08-29 11:22:36', arrivaltime: '2026-08-29 12:37:33', pickuptime: '2026-08-29 12:41:26', starttime: '2026-08-29 13:44:56', deliverytime: '2026-08-29 13:59:19', expecteddeliverytime: '2026-08-29 02:10 PM', kms: '6', actualkms: '6.0', riderkms: '5.5', }, ]; /* ── Reading one delivery ────────────────────────────────────────────────── */ // The whole point. Read in column order the ladder gives negative steps; read // as assign → arrive → pickup → start → deliver every step is positive. test('every step of the ladder is a positive duration', () => { for (const row of BATCH) { for (const step of journeyOf(row).steps) { assert.ok(step.ms !== null && step.ms >= 0, `${row.orderid} ${step.key} was ${step.ms}`); } } }); test('the steps mean what the ladder says they mean', () => { const journey = journeyOf(BATCH[1] as DeliveryRow); const of = (key: string) => journey.steps.find((step) => step.key === key)!.ms!; // 11:22:36 → 12:37:29 assert.equal(Math.round(of('toShop') / 60_000), 75); // 12:37:29 → 12:41:21 assert.equal(Math.round(of('counter') / 60_000), 4); // 12:41:21 → 13:17:19, waiting while the first drop was done assert.equal(Math.round(of('inRound') / 60_000), 36); // 13:17:19 → 13:38:26 assert.equal(Math.round(of('onRoad') / 60_000), 21); }); // A round's first drop gets its `starttime` written moments before delivery. test("a round's first drop is flagged rather than believed", () => { assert.equal(journeyOf(BATCH[0] as DeliveryRow).isFirstLeg, true, '34 seconds is not a ride'); assert.equal(journeyOf(BATCH[1] as DeliveryRow).isFirstLeg, false); }); // `actualkms` equals `kms` to the decimal on 498 of 498 production rows. Showing // it as "actual" would draw two identical bars and call the round perfect. test('the measured distance is riderkms, never actualkms', () => { const journey = journeyOf(BATCH[1] as DeliveryRow); assert.equal(journey.plannedKm, 8); assert.equal(journey.riddenKm, 6.1, 'actualkms (8.0) was used as the measurement'); }); test('a GPS reading of three metres is treated as absent, not as a short trip', () => { const journey = journeyOf(BATCH[2] as DeliveryRow); assert.equal(journey.riddenKm, null); assert.equal(journey.plannedKm, 2, 'the plan is still known'); }); test('a stamp written out of order leaves a blank, not a negative bar', () => { const journey = journeyOf({ deliveryid: 9, assigntime: '2026-08-29 13:00:00', arrivaltime: '2026-08-29 12:00:00', pickuptime: '2026-08-29 12:05:00', } as DeliveryRow); assert.equal(journey.steps.find((step) => step.key === 'toShop')!.ms, null); assert.equal(journey.steps.find((step) => step.key === 'counter')!.ms, 5 * 60_000); }); test('a delivery nobody has finished has a blank total, not a running one', () => { const journey = journeyOf({ deliveryid: 9, assigntime: '2026-08-29 13:00:00', orderstatus: 'pending', } as DeliveryRow); assert.equal(journey.totalMs, null); assert.equal(journey.lateMs, null); assert.equal(journey.steps.every((step) => step.ms === null), true); }); /* ── The promise ─────────────────────────────────────────────────────────── */ // The fourth timestamp format Fiesta sends, and the only twelve-hour one. Only // the ISO format is specified; a meridiem string is implementation-defined, so // this is pinned against an explicitly constructed instant rather than against // whatever the running engine happens to do with the same string. test('a twelve-hour promise is read as the instant it names', () => { assert.equal(parsePromise('2026-08-29 07:27 PM'), Date.parse('2026-08-29T19:27:00')); }); test('midnight and noon do not swap', () => { assert.equal(parsePromise('2026-08-29 12:15 AM'), Date.parse('2026-08-29T00:15:00')); assert.equal(parsePromise('2026-08-29 12:15 PM'), Date.parse('2026-08-29T12:15:00')); }); test('a twenty-four-hour promise is read too, so a format change does not blank the panel', () => { assert.equal(parsePromise('2026-08-29 19:27:30'), Date.parse('2026-08-29T19:27:30')); }); test('no promise, or an unreadable one, is null rather than a guess', () => { assert.equal(parsePromise(''), null); assert.equal(parsePromise(undefined), null); assert.equal(parsePromise('soon'), null); }); test('lateness is measured against the promise, either way', () => { // Promised 01:00 PM, delivered 13:16:57. assert.ok(journeyOf(BATCH[0] as DeliveryRow).lateMs! > 16 * 60_000); // Promised 02:10 PM, delivered 13:59:19 — early. assert.ok(journeyOf(BATCH[3] as DeliveryRow).lateMs! < 0); }); /* ── Many journeys ───────────────────────────────────────────────────────── */ test('the summary counts what was promised and what landed on time', () => { const result = compare(BATCH); assert.equal(result.journeys, 4); assert.equal(result.promised, 4); assert.equal(result.onTime, 1, 'only the last drop beat its promise'); }); // Getting to the shop was 75 minutes; everything else was minutes. Naming the // bottleneck is the one thing this panel is for. test('the bottleneck is the step that actually costs the time', () => { assert.equal(compare(BATCH).bottleneck, 'toShop'); }); test("the first drop's artefact leg is kept out of the on-road figure", () => { const result = compare(BATCH); const onRoad = result.steps.find((step) => step.key === 'onRoad')!; assert.equal(result.firstLegs, 1); assert.equal(onRoad.measured, 3, "the 34-second leg was averaged in"); assert.ok( onRoad.medianMs! > 5 * 60_000, `a real ride, not ${Math.round(onRoad.medianMs! / 1000)}s`, ); }); test('shares add up to the whole, so the bars fill the bar', () => { const total = compare(BATCH).steps.reduce((sum, step) => sum + step.share, 0); assert.ok(Math.abs(total - 1) < 0.0001); }); test('a step nobody stamped is measured zero times rather than counted as instant', () => { const result = compare([ { deliveryid: 1, assigntime: '2026-08-29 11:00:00', arrivaltime: '2026-08-29 11:30:00' } as DeliveryRow, ]); const counter = result.steps.find((step) => step.key === 'counter')!; assert.equal(counter.measured, 0); assert.equal(counter.medianMs, null); assert.equal(counter.share, 0); }); test('distance compares plan against measurement, over the rows that have both', () => { const { distance } = compare(BATCH); assert.equal(distance.measured, 3, 'the 0.0026 km row has no measurement'); assert.equal(distance.medianPlannedKm, 6); assert.equal(distance.medianRiddenKm, 5.5); }); test('an empty day summarises to blanks, not zeroes', () => { const result = compare([]); assert.equal(result.journeys, 0); assert.equal(result.medianLateMs, null); assert.equal(result.bottleneck, null); assert.equal(result.distance.medianRiddenKm, null); assert.equal(result.steps.every((step) => step.medianMs === null), true); }); /* ── Wording ─────────────────────────────────────────────────────────────── */ test('spans read at the scale they are', () => { assert.equal(span(null), '—'); assert.equal(span(34_000), '34s'); assert.equal(span(42 * 60_000), '42m'); assert.equal(span(69 * 60_000), '1h 09m'); }); test('lateness reads as a sentence, not a signed number', () => { assert.equal(lateness(null), '—'); assert.equal(lateness(30_000), 'on time'); assert.equal(lateness(48 * 60_000), '48m late'); assert.equal(lateness(-12 * 60_000), '12m early'); });