Functional Weave
Code in Rust

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.0 (not the latest) · published 2026-10-03 by charlie · Anterra

Pinned by 24 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.

pub fn freight_rate(rates: &[FreightRate], fuel_surcharges: &[FuelSurcharge], zone: &str, chargeable_grams: i64, 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

/// One weight break of a tariff for one zone.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FreightRate {
    pub zone: String,
    /// lightest chargeable weight in the break, inclusive
    pub from_grams: i64,
    /// heaviest, inclusive; null for no upper limit
    pub to_grams: Option<i64>,
    /// the weight is rounded up to a multiple of this before pricing; 1 for none
    pub step_grams: i64,
    /// fixed charge for the break; zero for a pure per-kilogram rate
    pub base: Money,
    /// minor units per kilogram of rated weight; 0 for a flat-priced band
    pub per_kg_minor: i64,
    /// the least the break charges; null for none
    pub minimum: Option<Money>,
    pub valid_from: String,
    /// last day in force, inclusive; null while current
    pub valid_to: Option<String>,
}

/// A fuel surcharge percentage and the dates it applies.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FuelSurcharge {
    /// 1850 = 18.5% of the freight charge
    pub basis_points: i64,
    pub valid_from: String,
    /// last day in force, inclusive; null while current
    pub valid_to: Option<String>,
}

/// The freight charge, the surcharge on it, and what decided them.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FreightQuote {
    pub zone: String,
    /// the weight priced: rounded up to the step, or a heavier break's first weight when that was cheaper
    pub rated_grams: i64,
    /// fromGrams of the break that priced it
    pub break_from_grams: i64,
    pub freight: Money,
    pub fuel_surcharge_basis_points: i64,
    pub fuel_surcharge: Money,
    pub total: Money,
}

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

fune!(logistics.freight-rate@^1);  // then call freight_rate(…)
impl/rust.rs · 184 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_add::add_money;  ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate;  ← from money.apply-rate ^1.0.0 · built alongside by fune
use super::money_compare::compare_money;  ← from money.compare ^1.0.0 · built alongside by fune

fn is_iso_date(value: &str) -> bool {
    let bytes = value.as_bytes();
    bytes.len() == 10
        && bytes[4] == b'-'
        && bytes[7] == b'-'
        && bytes
            .iter()
            .enumerate()
            .all(|(i, b)| i == 4 || i == 7 || b.is_ascii_digit())
}

fn in_force(valid_from: &str, valid_to: &Option<String>, on_date: &str) -> bool {
    on_date >= valid_from
        && match valid_to {
            None => true,
            Some(to) => on_date <= to.as_str(),
        }
}

fn round_up_to(grams: i64, step: i64) -> i64 {
    round_div(grams, step, "up") * step
}

/// The break's charge at a weight: base plus the per-kilogram element, at least the minimum.
fn charge(rate: &FreightRate, rated_grams: i64) -> Money {
    let variable = round_div(rated_grams * rate.per_kg_minor, 1000, "half-up");
    let freight = money(rate.base.minor + variable, &rate.base.currency);
    if let Some(minimum) = &rate.minimum {
        if compare_money(&freight, minimum) < 0 {
            return minimum.clone();
        }
    }
    freight
}

/// 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.
///
/// # Panics
/// Panics on a weight below 1 g, a malformed date, a bad break, mixed
/// currencies, no break or two breaks covering the weight, or a surcharge
/// table with no row for the date.
pub fn freight_rate(
    rates: &[FreightRate],
    fuel_surcharges: &[FuelSurcharge],
    zone: &str,
    chargeable_grams: i64,
    ship_date: &str,
) -> FreightQuote {
    if chargeable_grams < 1 {
        panic!("chargeableGrams must be 1 or more, received {}", chargeable_grams);
    }
    if !is_iso_date(ship_date) {
        panic!("shipDate must be an ISO date (YYYY-MM-DD), received \"{}\"", ship_date);
    }

    let candidates: Vec<&FreightRate> = rates
        .iter()
        .filter(|r| r.zone == zone && in_force(&r.valid_from, &r.valid_to, ship_date))
        .collect();
    for rate in &candidates {
        if rate.step_grams < 1 {
            panic!("stepGrams must be 1 or more, received {}", rate.step_grams);
        }
        if rate.per_kg_minor < 0 {
            panic!("perKgMinor must not be negative, received {}", rate.per_kg_minor);
        }
        assert_same_currency(&candidates[0].base, &rate.base);
    }

    let covering: Vec<&&FreightRate> = candidates
        .iter()
        .filter(|r| chargeable_grams >= r.from_grams && r.to_grams.map_or(true, |t| chargeable_grams <= t))
        .collect();
    if covering.is_empty() {
        panic!("no freight rate for zone \"{}\" on {} covers {} g", zone, ship_date, chargeable_grams);
    }
    if covering.len() > 1 {
        panic!("two freight rates for zone \"{}\" on {} cover {} g", zone, ship_date, chargeable_grams);
    }

    let mut best: &FreightRate = covering[0];
    let mut best_grams = round_up_to(chargeable_grams, best.step_grams);
    let mut 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;
        }
        let grams = round_up_to(rate.from_grams, rate.step_grams);
        let freight = charge(rate, grams);
        let order = compare_money(&freight, &best_freight);
        if order < 0 || (order == 0 && grams < best_grams) {
            best = rate;
            best_grams = grams;
            best_freight = freight;
        }
    }

    let mut basis_points = 0;
    if !fuel_surcharges.is_empty() {
        let mut found: Option<&FuelSurcharge> = None;
        for row in fuel_surcharges {
            if !in_force(&row.valid_from, &row.valid_to, ship_date) {
                continue;
            }
            if found.map_or(true, |f| row.valid_from > f.valid_from) {
                found = Some(row);
            }
        }
        // A table that stops short of the date has usually not been updated.
        match found {
            None => panic!("no fuel surcharge in force on {}", ship_date),
            Some(row) => basis_points = row.basis_points,
        }
    }
    let fuel_surcharge = apply_rate(&best_freight, basis_points, "half-up");
    FreightQuote {
        zone: zone.to_string(),
        rated_grams: best_grams,
        break_from_grams: best.from_grams,
        total: add_money(&best_freight, &fuel_surcharge),
        freight: best_freight,
        fuel_surcharge_basis_points: basis_points,
        fuel_surcharge,
    }
}

fn opt_str(v: &Value) -> Option<String> {
    if v.is_null() {
        None
    } else {
        Some(v.as_str().to_string())
    }
}

pub fn freight_rate_from_value(v: &Value) -> FreightRate {
    FreightRate {
        zone: v.get("zone").as_str().to_string(),
        from_grams: v.get("fromGrams").as_i64(),
        to_grams: if v.get("toGrams").is_null() { None } else { Some(v.get("toGrams").as_i64()) },
        step_grams: v.get("stepGrams").as_i64(),
        base: money_from_value(v.get("base")),
        per_kg_minor: v.get("perKgMinor").as_i64(),
        minimum: if v.get("minimum").is_null() { None } else { Some(money_from_value(v.get("minimum"))) },
        valid_from: v.get("validFrom").as_str().to_string(),
        valid_to: opt_str(v.get("validTo")),
    }
}

pub fn fuel_surcharge_from_value(v: &Value) -> FuelSurcharge {
    FuelSurcharge {
        basis_points: v.get("basisPoints").as_i64(),
        valid_from: v.get("validFrom").as_str().to_string(),
        valid_to: opt_str(v.get("validTo")),
    }
}

pub fn freight_quote_to_value(q: &FreightQuote) -> Value {
    Value::obj(vec![
        ("zone", Value::str(&q.zone)),
        ("ratedGrams", Value::Int(q.rated_grams)),
        ("breakFromGrams", Value::Int(q.break_from_grams)),
        ("freight", money_to_value(&q.freight)),
        ("fuelSurchargeBasisPoints", Value::Int(q.fuel_surcharge_basis_points)),
        ("fuelSurcharge", money_to_value(&q.fuel_surcharge)),
        ("total", money_to_value(&q.total)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let rates: Vec<FreightRate> = args[0].as_arr().iter().map(freight_rate_from_value).collect();
    let fuel: Vec<FuelSurcharge> = args[1].as_arr().iter().map(fuel_surcharge_from_value).collect();
    freight_quote_to_value(&freight_rate(&rates, &fuel, args[2].as_str(), args[3].as_i64(), args[4].as_str()))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 5 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add logistics.freight-rate
Download for Rust logistics.freight-rate-1.0.0-rust.fune · 63,746 bytes sha256 ac7aef43d3149baa54a4d8eb3588e14109bc39c3d02a5e6bf2f5ede145c42c25

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

The whole function, every language, is one file too: logistics.freight-rate-1.0.0.fune, 72,801 bytes, sha256 8bea10f04f6f84cc8fdf6cedc848e6c79afef95634eae21234454081c74ab780. 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 14 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

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.

Files

PathBytes
README.md2,473
impl/python.py4,534
impl/rust.rs6,828
impl/typescript.ts4,212
vectors.json42,945