Label created is not a scan: the tracking state most systems collapse too early
If you surface carrier tracking to customers, there is one modelling decision that quietly produces a whole category of support tickets: tre…
핵심 요약
자동 요약- 1If you surface carrier tracking to customers, there is one modelling decision that quietly…
- 2Pull the history for a parcel that was ordered an hour ago and you tend to get exactly one…
- 3Depending on the carrier it reads as Info Received, Shipping label created, Pre-advice rec…
원문 본문
출처 · dev.toIf you surface carrier tracking to customers, there is one modelling decision that quietly produces a whole category of support tickets: treating the first row a carrier returns as if the carrier had seen the parcel.
It usually has not.
What that first row actually is
Pull the history for a parcel that was ordered an hour ago and you tend to get exactly one line. Depending on the carrier it reads as Info Received, Shipping label created, Pre-advice received, or - one of the more honest phrasings, from a Canadian last-mile network - haven't yet received information from the shipper.
All of those mean the same operational thing:
- the shipper called the carrier API (or uploaded a manifest) and a number was allocated
- the carrier now knows a parcel is expected
- the parcel is still on the shipper's side
No scan has happened, because nothing has been scanned. A courier cannot scan a box it does not have.
Why collapsing it hurts
The common shortcut is to map everything that is not delivered or exception into one bucket called in_transit. It is tempting because carrier status vocabularies are a mess and you want one normalised enum.
But label_created and in_transit have different truth conditions, and three things break when you merge them:
1. Your transit-time metric measures the wrong interval. If you compute delivered_at - first_event_at, and the first event is the label, you are measuring warehouse dwell plus carrier transit and reporting it as carrier performance. Your carrier looks worse than it is, and your own fulfilment delay is invisible.
2. Your "stuck parcel" alert fires on the wrong parties. A record sitting on a label row for three days is a warehouse problem. A record sitting on arrived at sorting centre for three days might be a carrier problem. Same elapsed time, completely different owner. If both are in_transit, your alert cannot tell you who to page.
3. Your customer-facing copy has nothing true to say. "In transit" on a parcel nobody has collected is a statement you will be contradicted on.
A minimal model that holds up
Three fields rather than one enum:
status - your normalised bucket has_carrier_scan - boolean: has any row been written by the carrier's own network? first_scan_at - timestamp of that row, null until it exists has_carrier_scan is the one that earns its keep. Derive it once, at ingest, by asking whether the row came from a physical handling point rather than from the pre-advice. Then:
- transit time =
delivered_at - first_scan_at, not- created_at - "stuck" alerts split cleanly:
has_carrier_scan = falseescalates to fulfilment,trueescalates to the carrier - the UI can say "the carrier has been notified and is waiting to collect this", which is both true and reassuring
Normalising the wording
The phrasings vary more than the meaning does. A non-exhaustive set worth matching:
info received shipment information received electronic information submitted / pre-advice shipping label created awaiting collection / awaiting item has not yet received information from the shipper order data received Match on meaning, not on carrier. Writing one rule per carrier is how these tables get to four figures and start contradicting each other.
One caution from measuring this: do not infer label_created purely from "this is the only row". Some carriers genuinely publish a single consolidated row late in the journey, and treating that as a label will hide a parcel that is nearly delivered. Key off the wording, and let row count be a tiebreak at most.
What the numbers look like once you split them
With the two intervals separated, the shape of the wait becomes obvious. On the parcels we see, once a domestic last-mile network has physically taken the item, half are delivered in about a day and the slowest fifth inside about four. A cross-border air route is nearer nine days median, with the slowest fifth around fourteen - and almost all of that extra sits in two stages, the flight and the destination customs queue, neither of which produces scans.
So when a customer says "it has not moved in a week", the honest answer is usually that the week has not started yet.
I work on 24hTrack, a free package tracker that covers 3,200+ carriers - paste any tracking number, the carrier is detected from the number itself, no sign-up. It has a REST API and an MCP server if you want this in your own stack. Written with AI assistance; the measurements and the modelling argument are ours.
이 글은 dev.to 의 원문을 정제해 보여드립니다. 저작권은 원저작자에게 있습니다.
전체 내용이 궁금하다면
dev.to 원문에서 이어 읽기





