读数来源=Provenance for the numbers below: every figure is either the tool's own built-in
sample data (our source, src/lib/threepl/rules.ts) or a string that our own page rendered in
a real browser after deployment. No customer invoice is quoted here, and no customer result
is claimed.

Every small brand that ships through a third-party logistics warehouse eventually gets that
invoice: a CSV with 400 rows of fees, most of them familiar, a few of them new, and a total
that is 6% higher than last month. Nobody argues with it. Not because the charges are
defensible, but because arguing requires holding a signed rate card, an order export, and
400 invoice lines in your head at the same time.

I built a checker for exactly that gap. It is free, it runs entirely in the browser, and it
does one job: compare what a 3PL billed you, line by line, against what your contracted rate
card says you agreed to pay. This post is about how the comparison works and the four things
that broke while building it.

What "overcharge" actually means in a 3PL invoice

Oversize surcharges, pick-and-pack fees, storage days, fuel add-ons, receiving fees, the
"manual handling" line that appears only some months. Almost all of it is legitimate — when it
matches the card. The expensive part is not fraud, it is drift:

  • the card says $4.10 per unit for a standard pick, the invoice says $4.55 for the same SKU class
  • a fee appears that is not on the card at all (this is the one everyone misses, because the line looks like the other 399 lines)
  • a surcharge is applied to a shipment the contract excludes (e.g. the card says fuel is waived below 2 lb, the invoice applies it to a 1.8 lb order)
  • the quantity billed does not match the quantity shipped

None of those require a lawyer. They require a diff. That is the whole product insight: the
dispute is a join between three tables, and nobody has time to do the join by hand.

The four rules I coded

The checker takes three inputs, all pasted or dropped in as CSV: the invoice lines, the rate
card entries, and your order/shipment export. For each billed line it resolves the applicable
card entry (same service, same weight or size band, effective date in range) and then runs:

  1. Rule A — unlisted fee. The invoice has a fee description that no card entry covers. Verdict: flag, ask for the contractual basis. This one carries no dollar amount to compare against, so it must never be silently priced.
  2. Rule B — unit-rate mismatch. Billed unit rate ≠ card unit rate for the matched band. The delta is (billed_rate - card_rate) × billed_quantity.
  3. Rule C — quantity or band mismatch. The rate is right but the count is not, or the shipment is billed in a band its own weight/size does not fall into. The delta is (billed_qty - shipped_qty) × card_rate, and the second half is checked against your shipment export rather than against the invoice.
  4. Rule D — no card match and no order match. Not an error, a question. These lines go to a "needs confirmation" bucket so the tool never invents a number for something it cannot see.

The output is three lists: clean lines, disputed lines with a dollar delta each, and the
confirmation bucket. The disputed total is just the sum of the deltas — the number you put at
the top of the email.

A worked example (sample data, not a real bill)

The built-in example is four invoice lines against a card where standard pick is $4.10/unit:

line 1 pick-and-pack 100 units billed $410.00 card $4.10 -> clean line 2 pick-and-pack 40 units billed $182.00 card $4.10 -> Rule B, delta $18.00 line 3 oversize surcharge billed $124.65 card none -> Rule A, delta $124.65 line 4 storage 9 days billed $ 72.00 card $8.00/day -> clean 

Two lines flagged, disputed total $142.65. In the live tool's own sample set the numbers
are Billed lines checked: 4 / Lines flagged: 2 / Disputed total: $224.65 — same shape,
different sample rows. When I ran the real page in a browser after shipping it, those were the
exact strings rendered, and a deliberately broken input produced the exact expected delta.
That is the only kind of verification I trust for a calculator: the number has to move when
the input moves.

Three things that broke, in order of embarrassment

Thousands separators. Splitting a CSV on , while the invoice contains $1,000.00 turns
one thousand dollars into one hundred cents. It parsed fine on every test row I wrote until I
fed it a real-shaped big number. Fix: a splitCsv() that respects quoted fields, and a test
whose only job is a quoted amount with a comma in it.

Lines starting with #. Treating a leading # as a comment is reasonable until the order
number is #1042 — then every order line in the export is silently skipped, and the tool
reports a suspiciously clean invoice. Fix: a leading # is a comment only when followed by
whitespace or end of line.

Silent defaults. The first version priced unmatched fees at zero delta instead of saying
"no basis found". Zero looks like a pass. A checker that quietly passes what it cannot read is
worse than no checker, because it produces confidence. Rule D exists to make that state visible.

Why it runs in the browser

Invoice CSVs are a company's shipping volumes, SKU mix, and per-unit cost structure — that is
close to the most sensitive document a small brand owns, and there is no good reason for it to
leave their laptop for a first pass. So there is no upload and no account for the free tool:
the parse, the match and the diff all happen client-side, and nothing is stored. If you want a
copy of the numbers, the tool writes a plain-text dispute pack you can paste into an email.

What I charge for, and what I don't

The checker is free and unlimited. The thing I do not automate is the judgement call: whether
Rule A's surcharge is actually excluded by clause 4.2 of your contract, and how to write the
dispute so the account manager forwards it instead of filing it. That part is a
$49 per-invoice review — a human reads your card, your invoice, and the flagged lines, and
sends back the dispute pack with the contract citations in it. If a line turns out to be
correctly billed, it comes off the list; the pack is only as aggressive as the card supports.

If you want to try the mechanics first: the checker is at
www.saasclerk.app/threepl-audit
— paste the sample data, change one rate, watch the total move. That "watch it move" step is
the whole point: an invoice you can re-run is an invoice you can argue with.