DEV Community

24hTrack
24hTrack

Posted on

A carrier can change its name mid-journey, and your tracking model probably can't express that

Disclosure: I work on 24hTrack, a package tracking site, so this comes from watching the data rather than from theory. Written with AI assistance.

Most shipment schemas I have seen store carrier as one scalar on the shipment row. One string, set at ingest, never revisited. It is the obvious design, and it quietly assumes something that is not true: that the company handling your parcel has one name for the whole journey.

Here is a live counterexample from this week.

The same number, two brands

Yodel is one of the UK's larger parcel carriers. Its own site now trades as "Yodel by InPost" and carries a banner telling customers that tracking has moved to InPost. The legal entity behind the site is InPost Delivery Network Limited.

Now look at what that does to a scan list. A typical history on a Yodel number runs something like:

HATFIELD TERMINAL      your parcel is at our national hub
LUTTERWORTH            your parcel is at our national hub
BIRMINGHAM SERVICE CENTRE  your parcel has arrived at your delivery depot
INPOST                 your parcel is on its way to InPost
INPOST                 we have your parcel
INPOST                 your parcel has been dropped off at the locker
Enter fullscreen mode Exit fullscreen mode

One number. One parcel. The brand in the event text changes partway down, because the acquiring company's systems now write the events.

If your model has shipment.carrier = "Yodel", your UI is now lying in one of two directions. Either you render "Yodel" above events that say InPost, and your support inbox fills with who is InPost and why do they have my parcel - or you re-derive the carrier from the latest event and your historical rows silently change identity, which is worse because it is invisible.

Three things worth separating

The bug is that one field is being asked to answer three different questions.

1. Who issued the number. This is a property of the label and it never changes. A 19-character string of three letters and sixteen digits was issued by Yodel's numbering scheme, and it stays issued by it forever, no matter who buys the company.

2. Who is handling it right now. This changes, legitimately, several times on a cross-border parcel: exporter, airline, customs broker, destination post, final-mile courier. The Yodel-to-InPost case is just a rebrand doing the same thing a handover does.

3. What brand to show the customer. This is a presentation decision, not a data one, and it should be allowed to differ from both of the above. "Yodel (now InPost)" is often the right answer and is not expressible if you only stored one string.

Separate those and the rebrand stops being an incident. Store the issuing scheme on the shipment, store the handling party per event, and let the display name be a function of both plus a small alias table.

The alias table is the cheap part

You do not need an ontology. You need a mapping that says these labels are the same operator:

Yodel            -> yodel
Yodel by InPost  -> yodel
INPOST (on a Yodel number) -> yodel
Enter fullscreen mode Exit fullscreen mode

Without it you get a subtler bug than the display one: your per-carrier metrics split. Delivery times, scan counts, exception rates - all of it quietly divides across two labels, and the carrier that looks like it suddenly has half the volume has not changed at all. We hit exactly this and the symptom was a dashboard, not an error log.

Do not derive identity from shape alone

The other half of this problem runs the other way. We have shipments where the number is all digits with no prefix at all, and in that case the number genuinely does not say who issued it. Any code that tries to infer a carrier from a bare numeric string is guessing, and a guess that is stored looks identical to a fact that was confirmed.

The fix that has held up for us: keep carrier_guessed and carrier_confirmed apart, and let the first carrier response that actually returns a history promote one to the other. A nullable confirmed field is more honest than a populated guessed one, and it is the difference between "we do not know yet" and "we told you the wrong thing with confidence".

Practical checklist

  • Carrier is not immutable. Rebrands, acquisitions and final-mile handovers all change it mid-journey.
  • Store issuing scheme, handling party and display name separately.
  • Keep an alias table, or your analytics will split on a name change.
  • Never store an inferred carrier in the same field as a confirmed one.
  • When the event text changes brand, say so in the UI. One line - this carrier now operates as X - removes an entire class of support ticket.

For what it is worth, the customer-facing version of this is simpler than the engineering one: the number did not change, the company did. The write-up of the Yodel case is at https://www.24htrack.com/carriers/yodel if you want the consumer framing.

24hTrack is a free package tracker for 3,200+ carriers - paste any tracking number, the carrier is detected automatically, no sign-up.

FAQ

Does a carrier rebrand invalidate an existing tracking number?
No. The number was issued under the old scheme and stays valid; only the brand writing the later events changes.

Should I re-derive carrier from the most recent event?
Not into the same field. Derive a display name from the latest event if you like, but keep the issuing scheme intact or your history becomes unstable.

How do I tell a rebrand from a genuine handover?
You often cannot from the event text alone, and that is fine - both are "handling party changed", and if you model it that way you do not need to tell them apart.

Top comments (0)

⌂ 换网址