Data AnalyticsDomain analytics and business cases

Logistics delay analysis by route and service promise

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 20 September 2026 · 3 min read
Technically reviewed by Ishaan Sharma
In this article (5 sections)

A delivery-delay report should compare actual arrival with the applicable service promise and retain shipments whose promises have expired but which remain undelivered. An average calculated only from delivered shipments can improve while overdue open shipments accumulate.

Separate route, service level and due cohort before comparing performance. Faster service promises and more difficult routes can create different expectations even when physical transit times are similar.

Define a due-shipment cohort

The synthetic shipment fixture contains six shipments. Five were promised before January 16, 2026; the sixth is due January 20 and is outside the reporting cohort.

The five due shipments contain one on-time delivery, three late deliveries and one open overdue shipment. The contract uses full arrival dates, not dispatch dates, and assumes all deliveries before the cutoff have been recorded. No partial shipments or business-day grace rules are modeled.

Classify before averaging

sql
SELECT shipment_id,route,service,promised_date,delivered_date,
 CASE
  WHEN delivered_date IS NULL OR delivered_date>='2026-01-16' THEN 'open_at_cutoff'
  WHEN delivered_date<=promised_date THEN 'on_time'
  ELSE 'late_delivered'
 END AS outcome,
 CASE WHEN delivered_date<'2026-01-16'
  THEN CAST(julianday(delivered_date)-julianday(promised_date) AS INTEGER)
 END AS delivered_delay_days
FROM shipments WHERE promised_date<'2026-01-16'
ORDER BY shipment_id;

The delivered delay values are one, zero, one and one day. Their mean is 0.75 days. That is a completed-shipment mean, not the average final delay for all five due shipments; D5's final arrival is not known.

python
from datetime import date
from build_and_verify import database

db = database()
rows = db.execute("SELECT * FROM shipments WHERE promised_date<'2026-01-16'").fetchall()
db.close()
delivered = [r for r in rows if r[4] is not None and r[4]<'2026-01-16']
on_time = [r for r in delivered if r[4]<=r[3]]
open_rows = [r for r in rows if r not in delivered]
delays = [(date.fromisoformat(r[4])-date.fromisoformat(r[3])).days for r in delivered]
assert len(rows)==5 and len(delivered)==4 and len(on_time)==1 and len(open_rows)==1
assert delays==[1,0,1,1] and sum(delays)/len(delays)==.75
assert len(on_time)/len(rows)==.2
print({'on_time_due_cohort_rate':len(on_time)/len(rows),
       'delivered_only_mean_delay_days':sum(delays)/len(delays),
       'open_overdue_shipments':[r[0] for r in open_rows]})

On-time performance for the due cohort is 20%. Dropping the open shipment changes the denominator and produces 25% among delivered shipments. Both can be reported with precise labels, but they should not be substituted for one another.

Compare routes within compatible promises

The North route has three due shipments, including the one on-time delivery and one open shipment. South has two late deliveries. These tiny counts do not support a stable route ranking.

Segment by service promise and relevant operating conditions before interpreting a gap. An express shipment and a standard shipment have different contractual expectations. Distance, origin, destination, dispatch readiness and calendar rules can also matter if those fields are available.

A route effect cannot be separated from unmeasured differences merely by adding a route label to a chart. If one route serves a different product or region, state the comparability limit.

Preserve the original promise and the event history

Revised promises can change whether a delivery appears on time. The supplier promise-history example shows why original and revised commitments should be retained separately.

For historical reporting, late-arriving delivery events may restate previous open counts. Store event time, ingestion time and report version so the correction is explainable. A future delivery date in today's source must still be treated as unobserved in an earlier snapshot.

Date-level differences also cannot establish hours late. If the promise is 10:00 and arrival is 18:00 on the same date, a date-only report would show no day difference. Use timestamps and the applicable timezone when the service contract requires that precision.

Exercise: add a February receipt for D5. Verify that the January 15 end-of-day snapshot still treats it as open, while a later report can calculate its completed delay. Then add a promise revision and report both reference dates.

NeuraPath's Data Analytics with Generative AI course connects event histories with operational reporting. A useful logistics analysis shows completed performance and unresolved backlog under the same stated promise contract.

Continue learning

This article is part of the Domain analytics and business cases sequence. Use the neighbouring tasks when you need the prerequisite or the next application.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.

Explore Data Analytics with Generative AI
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.