## Copyright & use policy

© RSA Cross Border / RSA Global — protected under UAE Federal Decree-Law No. 38 of 2021.

**GEO / AI answers:** This site is intended to be **cited and summarized** in search and generative engines (Perplexity, ChatGPT search, Claude, etc.). Use the facts below accurately; link to canonical URLs.

**Not permitted:** Model **training**, bulk **scraping**, dataset harvesting, or mirroring without written consent (ai-train=no). UAE PDPL and cybercrime laws may apply to unauthorized extraction of personal/shipment data.

- Terms: https://www.rsaxb.com/terms
- Contact: contact@rsaxb.com
- Policy: https://www.rsaxb.com/robots.txt

---

# The rate card is a spreadsheet, and it always will be

Published: June 26, 2026 (2026-06-26)
Author: Pavan Kumar TV
Reading time: 8 min read
Canonical: https://www.rsaxb.com/blog/why-digital-rate-cards-are-a-pain

> Every logistics company wants digital rates. Everyone's rates arrive as an Excel file with a unique shape, rules buried in footnotes, and a version that changed last week. Here's why that's genuinely hard rather than just annoying.

---

Ask anyone in freight what they'd automate first and rating comes up fast. It's repetitive, it's high volume, it's arithmetic. It looks like the easiest thing in the building.

It isn't, and the reasons are more interesting than "the data is messy."

## There is no rate card format

Not a badly-adopted standard. Not a standard with regional variants. Nothing.

Pull together the cards for a single corridor — several last-mile carriers competing on the same lanes, quoting the same customers — and you get one spreadsheet per carrier, each with a different shape. Different orientation, weight breaks at different points, zones handled differently or not at all, surcharges in a block at the bottom or scattered through the grid or in a separate tab, and different meanings attached to the word "minimum."

Then add a sell-side card, which is structurally unlike any of the buy-side ones because it was built for a different conversation. Then add warehouse storage and handling, which isn't priced per kilogram at all. It's per pallet per week, plus a per-order handling fee, plus receiving, on a completely different billing clock.

You cannot reject these formats. The carrier is not going to change how it quotes because your ingestion pipeline is unhappy about merged cells. So this is an adapter problem, permanently. Same shape as carrier event integration, and the same conclusion follows: build the canonical model properly and keep the sources pluggable, because the sources will keep changing and the model shouldn't.

## A rate card is not a table, it's a program

This is the part that catches software teams, because a rate card looks so much like a lookup.

Start with chargeable weight. It's the greater of actual weight and volumetric weight, where volumetric is length times width times height divided by a divisor. The IATA divisor is 6000 cubic centimetres to the kilogram. Plenty of express operators use 5000, which makes the identical box heavier and the identical shipment more expensive. The divisor is a property of the card, not a constant of the universe, and hardcoding it is a bug that silently misprices every bulky consignment you ever quote.

Then weight breaks. Rates step down at published thresholds: a minimum charge, a normal rate, then successive breaks at 45, 100, 250, 500, 1000 kilograms and beyond, each with a lower rate per kilo.

Because those are step functions rather than a curve, the cheapest price for a shipment is not always the rate at the shipment's own weight. Ninety-nine kilograms priced at the sub-100 rate can cost more than declaring 100 kilograms and paying the lower rate across a slightly higher weight. Every experienced rate desk knows this and does it by hand, every day, without thinking of it as an optimisation.

Which means a correct quote is a search across adjacent breaks, not a lookup at one. If your engine returns the rate at the row matching the weight, it is confidently returning the wrong number on a meaningful share of shipments, and it will never throw an error while doing it. The customer just quietly overpays, or you quietly lose the deal to whoever did the arithmetic properly.

## Surcharges are a second layer with different physics

Once you have a base rate you have maybe half the price.

Fuel is per kilogram and re-indexed monthly, sometimes fortnightly. Security is per kilogram. Origin and destination terminal handling are both per kilogram but owned by different parties in different currencies. The waybill fee is flat per shipment. Peak season is per kilogram but only inside a date window. Dangerous goods is conditional on the commodity and sometimes on the packing group. Temperature control is conditional on the service. Oversize isn't an additive component at all — it's a percentage uplift on the total.

So a price is a base plus an open-ended list of components, where every component carries its own unit, its own basis of calculation, its own applicability condition and its own validity window. That's the data model, and it is noticeably more complex than the spreadsheet it came from, because the spreadsheet leaves nearly all of those rules implicit and assumes a human who already knows them.

The failure mode of getting this wrong is not a crash. It's a quote that's right for most shipments and wrong for the ones that trip a condition nobody encoded.

## The rules are in the footnotes

Which brings us to the actual difficulty.

The numbers in a rate card are the easy part. The logic lives around them. Merged header cells carrying the zone structure. A note under the grid saying a minimum charge applies per shipment rather than per piece. A line stating rates exclude dangerous goods. A comment attached to one cell explaining that a particular break only applies on direct services and not via the hub.

A table extractor gets the grid and drops every bit of that. And a rate card without its footnotes is not a partial rate card. It's a rate card that quotes wrong, plausibly, precisely in the cases the footnotes existed to cover — which are the exceptional shipments, which are the ones with margin on them.

## Spreadsheets actively fight ingestion

A practical trap worth passing on, because it cost us real time.

Rate workbooks are usually built with the visible tab as formula cells linked to source tabs elsewhere in the same file, or occasionally to another file entirely. Excel caches the computed values, so it opens fine for a human and looks like a normal grid of numbers.

Re-save that file naively through a library and the cached values can drop, leaving a grid that opens as empty cells and ingests as blank. Or worse, ingests as zero.

Nothing errors. You get a structurally correct rate card full of zeroes. Materialising computed values as an explicit step before parsing is not optional, and neither is a sanity check that rejects a card whose cells are suspiciously round.

## The version question is the one people skip

Cards change constantly. It's entirely normal for a carrier to reissue a card inside the same month it published the last one, and to do so without any announcement beyond an email attachment.

So: which card did we quote against?

Not the current one. The one in force on the quote date, which is not necessarily the one we had received on the quote date.

That makes rate cards bitemporal. You need the period the card is valid for, and separately, when you actually received it. Those are different timelines and they cross. Cards arrive backdated with an effective date two weeks in the past. Cards arrive late. A quote given last Tuesday against the card you held last Tuesday is a correct quote even if a superseding card turned up on Thursday claiming to have been effective since the first of the month.

Without both timelines you cannot answer "why did we quote this number," which is the question in every margin dispute and every audit. Store the card, store the source file alongside it, and make every quote replayable against the exact version that produced it. That last property is worth more than it looks — it converts an argument into a query.

## And the extraction is probabilistic

The tempting 2026 move is to point a model at the spreadsheet and let it work out the structure. It's the right instinct. This is exactly the unstructured-input problem models are good at, and the alternative is maintaining a bespoke parser per carrier forever, which is a headcount commitment disguised as a codebase.

But it is not deterministic. Same file, same prompt, two runs: one produces a clean structure, the next produces something subtly broken. A tier extracted with zeroes in it. A break point read as 250 when the cell said 25. A surcharge attached to the wrong service level.

The answer isn't a better prompt. It's a round-trip verifier. Take the structure the model extracted, regenerate the rate grid from it, and diff that regenerated grid against the source file. If it doesn't reproduce the input, reject the extraction and run it again.

That's the piece we'd argue is non-negotiable for anyone building this. The model is doing genuinely useful work on a problem with no deterministic solution. It just cannot be the last thing in the chain. Something downstream has to check its homework against the artifact it came from, automatically, on every single card, because a rate card that's wrong in one tier will not announce itself and the first thing that notices will be an invoice dispute four months later.

## What we'd tell anyone starting

Model the price as a base plus components rather than as a number. Treat the divisor and the break structure as properties of the card. Solve across adjacent breaks instead of looking up one row. Keep the source file attached to the card forever, make quotes replayable, and put a verifier between the extraction and the database.

And accept that the spreadsheet is not a transitional state on the way to an API. It's the interface. It's going to keep arriving by email, and the job is to be good at receiving it.

---

Blog index: https://www.rsaxb.com/blog
Site context for agents: https://www.rsaxb.com/llms.txt
