Functional Weave
Code in TypeScript

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

  • milkPrice(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
  • milkPrice(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
  • milkPrice(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.

export function milkPrice(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

/** A milk buyer's price schedule. Every price is thousandths of a minor unit per litre. */
export interface MilkContract {
  readonly currency: string;
  /** for milk at the standard constituents */
  readonly basePrice: number;
  /** 400 = 4.00% butterfat */
  readonly standardButterfatBasisPoints: number;
  /** per 0.1 percentage point above or below standard */
  readonly butterfatValue: number;
  /** 330 = 3.30% protein */
  readonly standardProteinBasisPoints: number;
  /** per 0.1 percentage point above or below standard */
  readonly proteinValue: number;
  /** ascending by minimumLitres; the highest reached applies */
  readonly volumeBonuses: readonly VolumeBonus[];
  /** 12 monthly adjustments, January first, or empty for none */
  readonly seasonality: readonly number[];
}

/** A volume tier. */
export interface VolumeBonus {
  /** litres in the period at which this tier starts */
  readonly minimumLitres: number;
  /** thousandths of a minor unit per litre */
  readonly bonus: number;
}

/** One period's milk. */
export interface MilkSupply {
  /** 0 or more */
  readonly litres: number;
  /** average butterfat, 412 = 4.12% */
  readonly butterfatBasisPoints: number;
  /** average protein, 335 = 3.35% */
  readonly proteinBasisPoints: number;
  /** 1 to 12, picks the seasonal adjustment */
  readonly month: number;
}

/** Each component per litre, the price per litre, and the payment. */
export interface MilkPrice {
  readonly basePrice: number;
  /** pro rata, rounded half away from zero to a thousandth */
  readonly butterfatAdjustment: number;
  /** pro rata, rounded half away from zero to a thousandth */
  readonly proteinAdjustment: number;
  readonly volumeBonus: number;
  readonly seasonalAdjustment: number;
  /** the sum of the five, thousandths of a minor unit per litre */
  readonly pricePerLitre: number;
  /** litres x pricePerLitre, rounded half up to the minor unit */
  readonly total: Money;
}

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

import { milkPrice } from "#fune/agri.milk-price@^1";
impl/typescript.ts · 69 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 { money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type MilkContract, type MilkPrice, type MilkSupply } from "./agri_milk_price_types.ts";

const MAX_PRICE = 1000000;
const MAX_LITRES = 1000000000;

function whole(value: number, name: string, min: number, max: number): number {
  if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
    throw new RangeError(`${name} must be a whole number from ${min} to ${max}, received ${value}`);
  }
  return value;
}

/**
 * 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.
 */
export function milkPrice(contract: MilkContract, supply: MilkSupply): MilkPrice {
  money(0, contract.currency);
  const base = whole(contract.basePrice, "basePrice", 0, MAX_PRICE);
  const stdFat = whole(contract.standardButterfatBasisPoints, "standardButterfatBasisPoints", 0, 10000);
  const fatValue = whole(contract.butterfatValue, "butterfatValue", -MAX_PRICE, MAX_PRICE);
  const stdProtein = whole(contract.standardProteinBasisPoints, "standardProteinBasisPoints", 0, 10000);
  const proteinValue = whole(contract.proteinValue, "proteinValue", -MAX_PRICE, MAX_PRICE);
  const litres = whole(supply.litres, "litres", 0, MAX_LITRES);
  const fat = whole(supply.butterfatBasisPoints, "butterfatBasisPoints", 0, 10000);
  const protein = whole(supply.proteinBasisPoints, "proteinBasisPoints", 0, 10000);
  const month = whole(supply.month, "month", 1, 12);

  // 0.1 percentage point is 10 basis points.
  const fatAdjustment = roundDiv((fat - stdFat) * fatValue, 10, "half-up");
  const proteinAdjustment = roundDiv((protein - stdProtein) * proteinValue, 10, "half-up");

  let volumeBonus = 0;
  let previous = -1;
  contract.volumeBonuses.forEach((tier, i) => {
    const minimum = whole(tier.minimumLitres, `volumeBonuses[${i}].minimumLitres`, 0, MAX_LITRES);
    const bonus = whole(tier.bonus, `volumeBonuses[${i}].bonus`, -MAX_PRICE, MAX_PRICE);
    if (minimum <= previous) throw new RangeError("volumeBonuses must be in strictly ascending order of minimumLitres");
    previous = minimum;
    if (litres >= minimum) volumeBonus = bonus;
  });

  let seasonal = 0;
  if (contract.seasonality.length === 12) {
    contract.seasonality.forEach((v, i) => whole(v, `seasonality[${i}]`, -MAX_PRICE, MAX_PRICE));
    seasonal = contract.seasonality[month - 1];
  } else if (contract.seasonality.length !== 0) {
    throw new RangeError(`seasonality must have 12 monthly values or none, received ${contract.seasonality.length}`);
  }

  const price = base + fatAdjustment + proteinAdjustment + volumeBonus + seasonal;
  if (price < 0) throw new RangeError(`price per litre must not be negative, received ${price}`);
  if (price > MAX_PRICE) throw new RangeError(`price per litre must be at most ${MAX_PRICE}, received ${price}`);
  return {
    basePrice: base,
    butterfatAdjustment: fatAdjustment,
    proteinAdjustment,
    volumeBonus,
    seasonalAdjustment: seasonal,
    pricePerLitre: price,
    total: money(roundDiv(price * litres, 1000, "half-up"), contract.currency),
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the TypeScript 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 TypeScript agri.milk-price-1.0.0-typescript.fune · 29,734 bytes sha256 e812db248ccf46053e15bf2bd56663380a329dbcc8010f219ff69246fb1753ee

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

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