Functional Weave
Code in Python

agri.milk-price

Milk price per litre and payment from a contract's base price, butterfat/protein adjustments and bonuses.

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

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

What it does

The price a dairy farmer is paid for one period's milk under a buyer's contract: a base price for milk at standard constituents, adjusted for the butterfat and protein actually delivered, plus a volume bonus and a seasonal adjustment, and the payment for the litres collected.

Contracts differ between buyers and change often, so every term is supplied by the caller in a `MilkContract`; nothing about any buyer is built in.

For example

  • milk_price(currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, …) → base price 38,000, butterfat adjustment 500, protein adjustment 400, volume bonus 200, seasonal adjustment 1,000, price per litre 40,100, total £300,750.00 September, good constituents, middle volume tier: 40.100 ppl on 750,000 litres is £300,750
  • milk_price(currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, …) → base price 38,000, butterfat adjustment -375, protein adjustment -320, volume bonus 400, seasonal adjustment -1,500, price per litre 36,205, total £434,460.00 May spring flush with low constituents on the top volume tier
  • milk_price(currency GBP, base price 30,000, standard butterfat basis points 4%, butterfat value 255, standard protein basis points 3.3%, protein value 0, volume bonuses , seasonality , litre…) → base price 30,000, butterfat adjustment 77, protein adjustment 0, volume bonus 0, seasonal adjustment 0, price per litre 30,077, total £300.77 a pro-rata butterfat adjustment of 76.5 thousandths rounds up to 77

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 milk_price(contract: MilkContract, supply: MilkSupply) -> MilkPrice
contractMilkContractthe buyer's terms; prices in thousandths of a minor unit per litre (38.25 ppl is 38250)
supplyMilkSupplythe month's collection and its average constituents
returnsMilkPrice

The types it declares, generated into your project

@dataclass(frozen=True)
class MilkContract:
    """A milk buyer's price schedule. Every price is thousandths of a minor unit per litre."""

    currency: str
    #: for milk at the standard constituents
    base_price: int
    #: 400 = 4.00% butterfat
    standard_butterfat_basis_points: int
    #: per 0.1 percentage point above or below standard
    butterfat_value: int
    #: 330 = 3.30% protein
    standard_protein_basis_points: int
    #: per 0.1 percentage point above or below standard
    protein_value: int
    #: ascending by minimumLitres; the highest reached applies
    volume_bonuses: List[VolumeBonus]
    #: 12 monthly adjustments, January first, or empty for none
    seasonality: List[int]

@dataclass(frozen=True)
class VolumeBonus:
    """A volume tier."""

    #: litres in the period at which this tier starts
    minimum_litres: int
    #: thousandths of a minor unit per litre
    bonus: int

@dataclass(frozen=True)
class MilkSupply:
    """One period's milk."""

    #: 0 or more
    litres: int
    #: average butterfat, 412 = 4.12%
    butterfat_basis_points: int
    #: average protein, 335 = 3.35%
    protein_basis_points: int
    #: 1 to 12, picks the seasonal adjustment
    month: int

@dataclass(frozen=True)
class MilkPrice:
    """Each component per litre, the price per litre, and the payment."""

    base_price: int
    #: pro rata, rounded half away from zero to a thousandth
    butterfat_adjustment: int
    #: pro rata, rounded half away from zero to a thousandth
    protein_adjustment: int
    volume_bonus: int
    seasonal_adjustment: int
    #: the sum of the five, thousandths of a minor unit per litre
    price_per_litre: int
    #: litres x pricePerLitre, rounded half up to the minor unit
    total: Money

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

from fune.agri.milk_price import milk_price  # agri.milk-price@^1
impl/python.py · 70 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.

from .agri_milk_price_types import MilkContract, MilkPrice, MilkSupply
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import money  ← from money.amount ^1.0.0 · built alongside by fune

MAX_PRICE = 1000000
MAX_LITRES = 1000000000


def _whole(value: int, name: str, low: int, high: int) -> int:
    if isinstance(value, bool) or not isinstance(value, int) or value < low or value > high:
        raise ValueError("%s must be a whole number from %d to %d, received %r" % (name, low, high, value))
    return value


def milk_price(contract: MilkContract, supply: MilkSupply) -> MilkPrice:
    """Milk price for one period under a buyer's contract.

    Prices are thousandths of a penny (or cent) per litre, because contracts
    quote to 0.01 or 0.001 ppl. Constituent adjustments are pro rata per 0.1
    percentage point and are each rounded to a thousandth, half away from
    zero; the payment is litres x price, rounded once to the penny.
    """
    money(0, contract.currency)
    base = _whole(contract.base_price, "basePrice", 0, MAX_PRICE)
    std_fat = _whole(contract.standard_butterfat_basis_points, "standardButterfatBasisPoints", 0, 10000)
    fat_value = _whole(contract.butterfat_value, "butterfatValue", -MAX_PRICE, MAX_PRICE)
    std_protein = _whole(contract.standard_protein_basis_points, "standardProteinBasisPoints", 0, 10000)
    protein_value = _whole(contract.protein_value, "proteinValue", -MAX_PRICE, MAX_PRICE)
    litres = _whole(supply.litres, "litres", 0, MAX_LITRES)
    fat = _whole(supply.butterfat_basis_points, "butterfatBasisPoints", 0, 10000)
    protein = _whole(supply.protein_basis_points, "proteinBasisPoints", 0, 10000)
    month = _whole(supply.month, "month", 1, 12)

    # 0.1 percentage point is 10 basis points.
    fat_adjustment = round_div((fat - std_fat) * fat_value, 10, "half-up")
    protein_adjustment = round_div((protein - std_protein) * protein_value, 10, "half-up")

    volume_bonus = 0
    previous = -1
    for i, tier in enumerate(contract.volume_bonuses):
        minimum = _whole(tier.minimum_litres, "volumeBonuses[%d].minimumLitres" % i, 0, MAX_LITRES)
        bonus = _whole(tier.bonus, "volumeBonuses[%d].bonus" % i, -MAX_PRICE, MAX_PRICE)
        if minimum <= previous:
            raise ValueError("volumeBonuses must be in strictly ascending order of minimumLitres")
        previous = minimum
        if litres >= minimum:
            volume_bonus = bonus

    seasonal = 0
    if len(contract.seasonality) == 12:
        for i, v in enumerate(contract.seasonality):
            _whole(v, "seasonality[%d]" % i, -MAX_PRICE, MAX_PRICE)
        seasonal = contract.seasonality[month - 1]
    elif len(contract.seasonality) != 0:
        raise ValueError("seasonality must have 12 monthly values or none, received %d" % len(contract.seasonality))

    price = base + fat_adjustment + protein_adjustment + volume_bonus + seasonal
    if price < 0:
        raise ValueError("price per litre must not be negative, received %d" % price)
    if price > MAX_PRICE:
        raise ValueError("price per litre must be at most %d, received %d" % (MAX_PRICE, price))
    return MilkPrice(
        base_price=base,
        butterfat_adjustment=fat_adjustment,
        protein_adjustment=protein_adjustment,
        volume_bonus=volume_bonus,
        seasonal_adjustment=seasonal,
        price_per_litre=price,
        total=money(round_div(price * litres, 1000, "half-up"), contract.currency),
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 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 agri.milk-price
Download for Python agri.milk-price-1.0.0-python.fune · 29,759 bytes sha256 b420065fcf70c2a8df77306d0b90b56f2c20f31de578d331bb43c3db74556ca5

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./agri.milk-price-1.0.0-python.fune, or fetch it from a terminal with fune pull agri.milk-price@1.0.0:python.

The whole function, every language, is one file too: agri.milk-price-1.0.0.fune, 39,277 bytes, sha256 c11c5977dbfc8fb8c2181f4bb679e4b08fd9394b0a95e65f64565c17ab8a23d2. 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 agri.milk-price

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

# fune: after agri.milk-price

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 agri.milk-price
# fune: replace money.amount in agri.milk-price

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 agri.milk-price --steps.

# fune: step agri.milk-price 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
September, good constituents, middle volume tier: 40.100 ppl on 750,000 litres is £300,750 currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment 500, protein adjustment 400, volume bonus 200, seasonal adjustment 1,000, price per litre 40,100, total £300,750.00
May spring flush with low constituents on the top volume tier currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment -375, protein adjustment -320, volume bonus 400, seasonal adjustment -1,500, price per litre 36,205, total £434,460.00
a pro-rata butterfat adjustment of 76.5 thousandths rounds up to 77 currency GBP, base price 30,000, standard butterfat basis points 4%, butterfat value 255, standard protein basis points 3.3%, protein value 0, volume bonuses , seasonality , litre… → base price 30,000, butterfat adjustment 77, protein adjustment 0, volume bonus 0, seasonal adjustment 0, price per litre 30,077, total £300.77
below standard it rounds away from zero, to -77 currency GBP, base price 30,000, standard butterfat basis points 4%, butterfat value 255, standard protein basis points 3.3%, protein value 0, volume bonuses , seasonality , litre… → base price 30,000, butterfat adjustment -77, protein adjustment 0, volume bonus 0, seasonal adjustment 0, price per litre 29,923, total £299.23
the payment is rounded once: 333 litres at 39.900 ppl is 13,286.7p currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment 500, protein adjustment 400, volume bonus 0, seasonal adjustment 1,000, price per litre 39,900, total £132.87
the payment rounds half up: 5 litres at 30.100 ppl is 150.5p, paid as 151p currency GBP, base price 30,100, standard butterfat basis points 4%, butterfat value 0, standard protein basis points 3.3%, protein value 0, volume bonuses , seasonality , litres … → base price 30,100, butterfat adjustment 0, protein adjustment 0, volume bonus 0, seasonal adjustment 0, price per litre 30,100, total £1.51
a volume tier starts at exactly its minimum currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment 0, protein adjustment 0, volume bonus 200, seasonal adjustment 0, price per litre 38,200, total £191,000.00
one litre short of a tier gets nothing currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment 0, protein adjustment 0, volume bonus 0, seasonal adjustment 0, price per litre 38,000, total £189,999.62
December uses the last seasonal value currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment 0, protein adjustment 0, volume bonus 0, seasonal adjustment 0, price per litre 38,000, total £38,000.00
no milk is no payment currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → base price 38,000, butterfat adjustment 0, protein adjustment 0, volume bonus 0, seasonal adjustment -1,000, price per litre 37,000, total £0.00
Show the other 8 tests
CaseArgumentsExpected
a euro contract in cents currency EUR, base price 45,000, standard butterfat basis points 4%, butterfat value 300, standard protein basis points 3.3%, protein value 500, volume bonuses , seasonality , lit… → base price 45,000, butterfat adjustment 1,500, protein adjustment 1,000, volume bonus 0, seasonal adjustment 0, price per litre 47,500, total €9,500.00
a seasonality list that is not 12 long is an error currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → error: seasonality must have 12 monthly values or none
month 13 is an error currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → error: month must be a whole number from 1 to 12
volume tiers out of order are an error currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → error: volumeBonuses must be in strictly ascending order of minimumLitres
negative litres are an error currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → error: litres must be a whole number from 0 to 1000000000
butterfat given as a percentage is an error currency GBP, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → error: butterfatBasisPoints must be a whole number
a price that goes negative is an error currency GBP, base price 1,000, standard butterfat basis points 4%, butterfat value 1,000, standard protein basis points 3.3%, protein value 0, volume bonuses , seasonality , litr… → error: price per litre must not be negative
a bad currency code is an error currency gbp, base price 38,000, standard butterfat basis points 4%, butterfat value 250, standard protein basis points 3.3%, protein value 400, volume bonuses ×2, seasonality 0, … → error: is not an uppercase ISO 4217 currency code

More from the author

## Units

Prices are integers in **thousandths of a minor unit per litre**: 38.25 pence per litre (ppl) is `38250`, 0.035 ppl is `35`. Buyers quote to 0.01 or 0.001 ppl, which a whole-penny price cannot hold and a float cannot hold exactly. Constituents are in basis points of the milk: 4.12% butterfat is `412`.

## How it is worked out

1. **Butterfat and protein**: (actual - standard) in 0.1 percentage points, times the contract's value per 0.1%, pro rata. 4.20% butterfat against a 4.00% standard at 0.25 ppl per 0.1% is +0.500 ppl. Each adjustment is rounded to a thousandth of a penny, half away from zero, so a deficit costs exactly what the same surplus earns. 2. **Volume bonus**: the tiers are in ascending order of `minimumLitres`; the highest tier the period's litres reach applies (not a sum of tiers). No tier reached, or no tiers, is 0. 3. **Seasonality**: twelve monthly adjustments, January first, and `month` picks one; an empty list means none. 4. The price per litre is the sum of the five components, and the payment is litres x price, rounded once, half up, to the minor unit.

A price that comes out negative is refused rather than paid.

## What it does not do

It does not apply hygiene or cell-count penalties, deductions (levies, haulage, membership), a pence-per-kg-of-solids (A+B-C) schedule, or retrospective price changes; model those as separate lines. Some contracts adjust per complete 0.1% step rather than pro rata; this one is pro rata.

## Limits

Prices and adjustments within ±1,000,000 (±10 major units per litre), the base and the final price from 0; constituents 0 to 10,000 basis points; litres 0 to 1,000,000,000; month 1 to 12.

Files

PathBytes
README.md2,148
impl/python.py3,418
impl/rust.rs5,747
impl/typescript.ts3,398
vectors.json17,215