# logistics.freight-rate Prices a consignment on a carrier's tariff: find the zone's weight break that covers the chargeable weight on the ship date, price it, check whether a heavier break would be cheaper, and add the fuel surcharge in force that day. ## The tariff is an argument Freight rates are private contracts between a shipper and a carrier, so the tariff is passed in (`rates`), not shipped as registry data. Each row still carries `validFrom` and `validTo`, so one table can hold last year's rates and this year's and the ship date picks between them. Carriers publish their fuel surcharge as a percentage that changes weekly or monthly; that is the second table. The figures in the vectors are illustrative, not any carrier's. ## Pricing one break ``` ratedGrams = chargeableGrams rounded up to stepGrams freight = base + ratedGrams x perKgMinor / 1000 (rounded half up to the minor unit) freight = max(freight, minimum) ``` That one shape covers the common tariffs: a flat price per weight band (`perKgMinor` 0), a per-kilogram rate by weight break with a minimum charge (air freight's M / N / +45 / +100 ...), and a base plus a per-kilogram element. The break is chosen on the chargeable weight as given, before the break's own rounding; bands must not overlap. ## A heavier break can be cheaper Per-kilogram rates fall as the weight rises, so 40 kg at 4.50/kg (180.00) costs more than 45 kg at 3.80/kg (171.00). Carriers charge the lower figure by pricing the consignment at the first weight of the heavier break, and so does this: every heavier break of the zone in force on the date is tried at its `fromGrams`, and the cheapest wins (on a tie, the lighter rated weight). `ratedGrams` and `breakFromGrams` say what happened. Pricing only the break the weight falls in is the mistake this vector catches. ## Fuel surcharge `fuelSurcharge = freight x basisPoints / 10000`, rounded half up, applied to the freight charge only. Of the rows in force on the ship date, the one with the latest `validFrom` wins, so a new week's figure can be appended without closing the previous row. An empty table means no surcharge; a non-empty table with no row for the date is an error, because it almost always means the table has not been updated. ## Errors No break for the zone and date covering the weight, two breaks covering it, a weight below 1 g, a step below 1, a negative rate, and mixed currencies are all errors, each naming what it found. 1.0.1 fixes Python accepting non-ASCII digits in shipDate; adds tests.