Functional Weave
Code in TypeScript

agri.stocking-rate@1.0.0

impl/python.py

2,603 bytes · the Python 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 math
from typing import List, Sequence

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

MAX_COUNT = 10000000


def stocking_rate(scheme: str, animals: Sequence[LivestockCount], area_hectares: float) -> StockingRate:
    """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).
    """
    table = [row for row in LIVESTOCK_UNITS if row.scheme == scheme]
    if not table:
        raise ValueError('unknown scheme "%s": use eurostat or uk-countryside-stewardship' % (scheme,))
    if (
        isinstance(area_hectares, bool)
        or not isinstance(area_hectares, (int, float))
        or not math.isfinite(area_hectares)
        or area_hectares <= 0
        or area_hectares > 1000000
    ):
        raise ValueError("areaHectares must be a finite number greater than 0 and at most 1000000, received %r" % (area_hectares,))
    area = int(round(round_float(float(area_hectares), 4) * 10000))
    if area < 1:
        raise ValueError("areaHectares must be at least 0.0001 hectares, received %r" % (area_hectares,))

    total = 0
    lines: List[LivestockUnitLine] = []
    for i, animal in enumerate(animals):
        row = next((r for r in table if r.category == animal.category), None)
        if row is None:
            raise ValueError('unknown category "%s" in scheme %s' % (animal.category, scheme))
        count = animal.count
        if isinstance(count, bool) or not isinstance(count, int) or count < 0 or count > MAX_COUNT:
            raise ValueError("animal %d: count must be a whole number from 0 to 10000000, received %r" % (i + 1, count))
        thousandths = count * row.thousandths
        total += thousandths
        lines.append(
            LivestockUnitLine(
                category=animal.category,
                count=count,
                coefficient=row.thousandths / 1000,
                livestock_units=thousandths / 1000,
            )
        )
    # LU/ha in hundredths = (total / 1000) / (area / 10000) x 100.
    return StockingRate(
        livestock_units=total / 1000,
        units_per_hectare=round_div(total * 1000, area, "half-up") / 100,
        lines=lines,
    )