import math from typing import List, Sequence from .agri_stocking_rate_data import LIVESTOCK_UNITS from .agri_stocking_rate_types import LivestockCount, LivestockUnitLine, StockingRate from .math_round_div import round_div from .math_round_float import round_float 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, )