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,
)