Functional Weave
Code in TypeScript

agri.stocking-rate@1.0.1

impl/typescript.ts

2,336 bytes · the TypeScript implementation · view raw

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

import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { roundFloat } from "./math_round_float.ts";  ← from math.round-float ^1.0.0 · built alongside by fune
import { LIVESTOCK_UNITS } from "./agri_stocking_rate_data.ts";  ← this capability’s own data, compiled from data/livestock-units.json into the same file by fune build
import { type LivestockCount, type LivestockUnitLine, type StockingRate } from "./agri_stocking_rate_types.ts";

const MAX_COUNT = 10000000;

/**
 * Livestock units and the stocking rate per hectare.
 *
 * Coefficients are held in thousandths of a livestock unit, so a herd's total
 * is an exact integer and only the rate per hectare is rounded. Adding float
 * coefficients drifts: 3 x 0.7 is 2.0999999999999996, which puts 3 yearlings
 * on 12 ha at 0.17 LU/ha instead of 0.18 (0.175 rounded half up).
 */
export function stockingRate(scheme: string, animals: readonly LivestockCount[], areaHectares: number): StockingRate {
  const table = LIVESTOCK_UNITS.filter((row) => row.scheme === scheme);
  if (table.length === 0) {
    throw new RangeError(`unknown scheme "${scheme}": use eurostat or uk-countryside-stewardship`);
  }
  if (typeof areaHectares !== "number" || !Number.isFinite(areaHectares) || areaHectares <= 0 || areaHectares > 1000000) {
    throw new RangeError(`areaHectares must be a finite number greater than 0 and at most 1000000, received ${areaHectares}`);
  }
  const area = Math.round(roundFloat(areaHectares, 4) * 10000);
  if (area < 1) throw new RangeError(`areaHectares must be at least 0.0001 hectares, received ${areaHectares}`);

  let total = 0;
  const lines: LivestockUnitLine[] = animals.map((animal, i) => {
    const row = table.find((r) => r.category === animal.category);
    if (!row) throw new RangeError(`unknown category "${animal.category}" in scheme ${scheme}`);
    if (typeof animal.count !== "number" || !Number.isInteger(animal.count) || animal.count < 0 || animal.count > MAX_COUNT) {
      throw new RangeError(`animal ${i + 1}: count must be a whole number from 0 to 10000000, received ${animal.count}`);
    }
    const thousandths = animal.count * row.thousandths;
    total += thousandths;
    return { category: animal.category, count: animal.count, coefficient: row.thousandths / 1000, livestockUnits: thousandths / 1000 };
  });
  // LU/ha in hundredths = (total / 1000) / (area / 10000) x 100.
  return { livestockUnits: total / 1000, unitsPerHectare: roundDiv(total * 1000, area, "half-up") / 100, lines };
}