Functional Weave
Code in Python

logistics.freight-rate

Freight charge from a carrier's dated weight-break tariff for a zone, with the fuel surcharge in force on the ship date.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 26 tests, run in TypeScript, Python and Rust.

What it does

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

For example

  • freight_rate(rates ×8, fuel surcharges ×3, DE, 20,000, 2026-05-10) → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 18.5%, fuel surcharge £16.65, total £106.65 per-kilogram break: 20 kg at 4.50/kg plus 18.5% fuel
  • freight_rate(rates ×8, fuel surcharges ×3, DE, 20,000, 2026-07-01) → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 21.25%, fuel surcharge £19.13, total £109.13 the fuel surcharge changes on 1 July and rounds 1912.5 up
  • freight_rate(rates ×8, fuel surcharges ×3, DE, 20,000, 2026-06-30) → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 18.5%, fuel surcharge £16.65, total £106.65 the last day of the old surcharge

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def freight_rate(rates: Sequence[FreightRate], fuel_surcharges: Sequence[FuelSurcharge], zone: str, chargeable_grams: int, ship_date: str) -> FreightQuote
ratesFreightRate[]the carrier's tariff, every zone and date; contracts are private, so the caller supplies it
fuel_surchargesFuelSurcharge[]the carrier's published fuel surcharge by date; empty for none
zonestring
chargeable_gramsint1 or more: the greater of actual and volumetric weight, e.g. from retail.volumetric-weight
ship_datedatethe collection date, which decides the tariff and surcharge in force
returnsFreightQuote

The types it declares, generated into your project

@dataclass(frozen=True)
class FreightRate:
    """One weight break of a tariff for one zone."""

    zone: str
    #: lightest chargeable weight in the break, inclusive
    from_grams: int
    #: heaviest, inclusive; null for no upper limit
    to_grams: Optional[int]
    #: the weight is rounded up to a multiple of this before pricing; 1 for none
    step_grams: int
    #: fixed charge for the break; zero for a pure per-kilogram rate
    base: Money
    #: minor units per kilogram of rated weight; 0 for a flat-priced band
    per_kg_minor: int
    #: the least the break charges; null for none
    minimum: Optional[Money]
    valid_from: str
    #: last day in force, inclusive; null while current
    valid_to: Optional[str]

@dataclass(frozen=True)
class FuelSurcharge:
    """A fuel surcharge percentage and the dates it applies."""

    #: 1850 = 18.5% of the freight charge
    basis_points: int
    valid_from: str
    #: last day in force, inclusive; null while current
    valid_to: Optional[str]

@dataclass(frozen=True)
class FreightQuote:
    """The freight charge, the surcharge on it, and what decided them."""

    zone: str
    #: the weight priced: rounded up to the step, or a heavier break's first weight when that was cheaper
    rated_grams: int
    #: fromGrams of the break that priced it
    break_from_grams: int
    freight: Money
    fuel_surcharge_basis_points: int
    fuel_surcharge: Money
    total: Money

Your code names it in one line, in the file that uses it

from fune.logistics.freight_rate import freight_rate  # logistics.freight-rate@^1
impl/python.py · 109 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import re
from typing import List, Optional, Sequence

from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_add import add_money  ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money, assert_same_currency, money  ← from money.amount ^1.0.0 · built alongside by fune
from .money_apply_rate import apply_rate  ← from money.apply-rate ^1.0.0 · built alongside by fune
from .money_compare import compare_money  ← from money.compare ^1.0.0 · built alongside by fune
from .logistics_freight_rate_types import FreightRate, FuelSurcharge, FreightQuote

_ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")


def _whole(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def _in_force(valid_from: str, valid_to: Optional[str], on_date: str) -> bool:
    return on_date >= valid_from and (valid_to is None or on_date <= valid_to)


def _round_up_to(grams: int, step: int) -> int:
    return round_div(grams, step, "up") * step


def _charge(rate: FreightRate, rated_grams: int) -> Money:
    """The break's charge at a weight: base plus the per-kilogram element, at least the minimum."""
    variable = round_div(rated_grams * rate.per_kg_minor, 1000, "half-up")
    freight = money(rate.base.minor + variable, rate.base.currency)
    if rate.minimum is not None and compare_money(freight, rate.minimum) < 0:
        return rate.minimum
    return freight


def freight_rate(
    rates: Sequence[FreightRate],
    fuel_surcharges: Sequence[FuelSurcharge],
    zone: str,
    chargeable_grams: int,
    ship_date: str,
) -> FreightQuote:
    """Freight for a consignment on a caller-supplied tariff: the zone's break
    covering the weight on the ship date, or a heavier break when that is
    cheaper, plus the fuel surcharge in force that day."""
    if not _whole(chargeable_grams) or chargeable_grams < 1:
        raise ValueError("chargeableGrams must be 1 or more, received %r" % (chargeable_grams,))
    if not isinstance(ship_date, str) or not _ISO_DATE.fullmatch(ship_date):
        raise ValueError('shipDate must be an ISO date (YYYY-MM-DD), received "%s"' % (ship_date,))

    candidates: List[FreightRate] = [
        r for r in rates if r.zone == zone and _in_force(r.valid_from, r.valid_to, ship_date)
    ]
    for rate in candidates:
        if not _whole(rate.step_grams) or rate.step_grams < 1:
            raise ValueError("stepGrams must be 1 or more, received %r" % (rate.step_grams,))
        if not _whole(rate.per_kg_minor) or rate.per_kg_minor < 0:
            raise ValueError("perKgMinor must not be negative, received %r" % (rate.per_kg_minor,))
        assert_same_currency(candidates[0].base, rate.base)

    covering = [
        r
        for r in candidates
        if chargeable_grams >= r.from_grams and (r.to_grams is None or chargeable_grams <= r.to_grams)
    ]
    if not covering:
        raise ValueError(
            'no freight rate for zone "%s" on %s covers %d g' % (zone, ship_date, chargeable_grams)
        )
    if len(covering) > 1:
        raise ValueError(
            'two freight rates for zone "%s" on %s cover %d g' % (zone, ship_date, chargeable_grams)
        )

    best = covering[0]
    best_grams = _round_up_to(chargeable_grams, best.step_grams)
    best_freight = _charge(best, best_grams)
    # Rates per kilogram fall as weight rises, so a heavier break charged at
    # its first weight can undercut the break the consignment falls in.
    for rate in candidates:
        if rate.from_grams <= chargeable_grams:
            continue
        grams = _round_up_to(rate.from_grams, rate.step_grams)
        freight = _charge(rate, grams)
        order = compare_money(freight, best_freight)
        if order < 0 or (order == 0 and grams < best_grams):
            best, best_grams, best_freight = rate, grams, freight

    basis_points = 0
    if len(fuel_surcharges) > 0:
        found: Optional[FuelSurcharge] = None
        for row in fuel_surcharges:
            if not _in_force(row.valid_from, row.valid_to, ship_date):
                continue
            if found is None or row.valid_from > found.valid_from:
                found = row
        # A table that stops short of the date has usually not been updated.
        if found is None:
            raise ValueError("no fuel surcharge in force on %s" % ship_date)
        basis_points = found.basis_points
    fuel_surcharge = apply_rate(best_freight, basis_points, "half-up")
    return FreightQuote(
        zone=zone,
        rated_grams=best_grams,
        break_from_grams=best.from_grams,
        freight=best_freight,
        fuel_surcharge_basis_points=basis_points,
        fuel_surcharge=fuel_surcharge,
        total=add_money(best_freight, fuel_surcharge),
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 5 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add logistics.freight-rate
Download for Python logistics.freight-rate-1.0.1-python.fune · 65,810 bytes sha256 869d904581607f9144d487fb9340b9d96412662dc746ce7e111390d0ea4e6399

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./logistics.freight-rate-1.0.1-python.fune, or fetch it from a terminal with fune pull logistics.freight-rate@1.0.1:python.

The whole function, every language, is one file too: logistics.freight-rate-1.0.1.fune, 77,267 bytes, sha256 23349243b0c363ec3d796e3aa555df9607f22fdd26c6b21ea95fb440639411d4. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before logistics.freight-rate

after — your function gets the result and the arguments, and returns the final result.

# fune: after logistics.freight-rate

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace math.round-div in logistics.freight-rate
# fune: replace money.add in logistics.freight-rate
# fune: replace money.amount in logistics.freight-rate
# fune: replace money.apply-rate in logistics.freight-rate
# fune: replace money.compare in logistics.freight-rate

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show logistics.freight-rate --steps.

# fune: step logistics.freight-rate after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
per-kilogram break: 20 kg at 4.50/kg plus 18.5% fuel rates ×8, fuel surcharges ×3, DE, 20,000, 2026-05-10 → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 18.5%, fuel surcharge £16.65, total £106.65
the fuel surcharge changes on 1 July and rounds 1912.5 up rates ×8, fuel surcharges ×3, DE, 20,000, 2026-07-01 → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 21.25%, fuel surcharge £19.13, total £109.13
the last day of the old surcharge rates ×8, fuel surcharges ×3, DE, 20,000, 2026-06-30 → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 18.5%, fuel surcharge £16.65, total £106.65
40 kg is cheaper charged as 45 kg at the next break (pricing only the 40 kg break gives 180.00) rates ×8, fuel surcharges ×3, DE, 40,000, 2026-05-10 → zone DE, rated grams 45,000, break from grams 45,000, freight £171.00, fuel surcharge basis points 18.5%, fuel surcharge £31.64, total £202.64
90 kg is cheaper charged as 100 kg rates ×8, fuel surcharges ×3, DE, 90,000, 2026-05-10 → zone DE, rated grams 100,000, break from grams 100,000, freight £320.00, fuel surcharge basis points 18.5%, fuel surcharge £59.20, total £379.20
a light consignment pays the break's minimum charge rates ×8, fuel surcharges ×3, DE, 5,000, 2026-05-10 → zone DE, rated grams 5,000, break from grams 1, freight £50.00, fuel surcharge basis points 18.5%, fuel surcharge £9.25, total £59.25
the weight is rounded up to the break's 500 g step rates ×8, fuel surcharges ×3, DE, 12,345, 2026-05-10 → zone DE, rated grams 12,500, break from grams 1, freight £56.25, fuel surcharge basis points 18.5%, fuel surcharge £10.41, total £66.66
exactly on a break boundary uses that break rates ×8, fuel surcharges ×3, DE, 45,000, 2026-05-10 → zone DE, rated grams 45,000, break from grams 45,000, freight £171.00, fuel surcharge basis points 18.5%, fuel surcharge £31.64, total £202.64
last year's tariff and surcharge for a 2025 shipment rates ×8, fuel surcharges ×3, DE, 20,000, 2025-11-01 → zone DE, rated grams 20,000, break from grams 1, freight £80.00, fuel surcharge basis points 17%, fuel surcharge £13.60, total £93.60
flat-priced band: a heavier band is dearer, so the band stands rates ×8, fuel surcharges ×3, FR, 1,900, 2026-05-10 → zone FR, rated grams 1,900, break from grams 1, freight £8.95, fuel surcharge basis points 18.5%, fuel surcharge £1.66, total £10.61
Show the other 16 tests
CaseArgumentsExpected
base plus per kilogram on a 1 kg step rates ×8, fuel surcharges ×3, FR, 15,200, 2026-05-10 → zone FR, rated grams 16,000, break from grams 10,001, freight £26.55, fuel surcharge basis points 18.5%, fuel surcharge £4.91, total £31.46
the per-kilogram charge 499.5 rounds half up rates ×8, fuel surcharges ×3, IT, 1,500, 2026-05-10 → zone IT, rated grams 1,500, break from grams 1, freight £5.00, fuel surcharge basis points 18.5%, fuel surcharge £0.93, total £5.93
an empty surcharge table means no surcharge rates ×8, , DE, 20,000, 2026-05-10 → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 0%, fuel surcharge £0.00, total £90.00
the latest-starting surcharge row in force wins rates ×8, fuel surcharges ×4, DE, 20,000, 2026-05-10 → zone DE, rated grams 20,000, break from grams 1, freight £90.00, fuel surcharge basis points 20%, fuel surcharge £18.00, total £108.00
no break for the zone rates ×8, fuel surcharges ×3, ES, 20,000, 2026-05-10 → error: no freight rate for zone "ES" on 2026-05-10 covers 20000 g
a weight above the zone's last break rates ×8, fuel surcharges ×3, FR, 30,001, 2026-05-10 → error: no freight rate for zone "FR" on 2026-05-10 covers 30001 g
no break in force before the tariff starts rates ×8, fuel surcharges ×3, DE, 20,000, 2024-12-31 → error: no freight rate for zone "DE" on 2024-12-31 covers 20000 g
a surcharge table that does not reach the date rates ×8, fuel surcharges ×3, IT, 1,500, 2024-06-01 → error: no fuel surcharge in force on 2024-06-01
two breaks covering the same weight rates ×2, , DE, 5,000, 2026-05-10 → error: two freight rates for zone "DE" on 2026-05-10 cover 5000 g
zero weight rates ×8, fuel surcharges ×3, DE, 0, 2026-05-10 → error: chargeableGrams must be 1 or more
a step below 1 g rates ×1, , DE, 5,000, 2026-05-10 → error: stepGrams must be 1 or more
a negative per-kilogram rate rates ×1, , DE, 5,000, 2026-05-10 → error: perKgMinor must not be negative
breaks in two currencies rates ×2, , DE, 5,000, 2026-05-10 → error: currency mismatch
a malformed ship date rates ×8, fuel surcharges ×3, DE, 20,000, 10/05/2026 → error: shipDate must be an ISO date
a ship date with a trailing newline rates ×8, fuel surcharges ×3, DE, 20,000, 2026-05-10 → error: shipDate must be an ISO date
a ship date in Arabic-Indic digits rates ×8, fuel surcharges ×3, DE, 20,000, ٢٠٢٦-05-10 → error: shipDate must be an ISO date

More from the author

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.

Files

PathBytes
README.md2,545
impl/python.py4,541
impl/rust.rs6,828
impl/typescript.ts4,212
vectors.json46,782