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 .agri_spray_mix_types import DoseUnit, SprayMix, TankLoad
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
def _scaled(name: str, value: float, decimals: int, low: int, high: int) -> int:
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < low or value > high:
raise ValueError("%s must be a finite number from %d to %d, received %r" % (name, low, high, value))
return int(round(round_float(float(value), decimals) * 10**decimals))
def spray_mix(
area_hectares: float,
water_volume_litres_per_hectare: float,
product_dose_per_hectare: float,
dose_unit: DoseUnit,
tank_litres: float,
) -> SprayMix:
"""Tank mix for a sprayer.
Area (0.0001 ha), water volume (0.1 L/ha), dose (0.001/ha) and tank
(0.1 L) are held as integers, so the job's spray volume is exact and the
count of full tanks cannot be thrown by a float: 129.2 ha at 250 L/ha is
exactly 19 tanks of 1700 L, where 129.2 x 250 in floating point is
32299.999999999996 and makes it 18 and a sliver.
"""
area = _scaled("areaHectares", area_hectares, 4, 0, 100000)
rate = _scaled("waterVolumeLitresPerHectare", water_volume_litres_per_hectare, 1, 0, 10000)
dose = _scaled("productDosePerHectare", product_dose_per_hectare, 3, 0, 100)
tank = _scaled("tankLitres", tank_litres, 1, 0, 100000)
if area < 1:
raise ValueError("areaHectares must be at least 0.0001 hectares")
if rate < 1:
raise ValueError("waterVolumeLitresPerHectare must be at least 0.1 litres")
if tank < 1:
raise ValueError("tankLitres must be at least 0.1 litres")
if dose_unit not in ("litres", "kilograms"):
raise ValueError('unknown doseUnit "%s": use litres or kilograms' % (dose_unit,))
# A litre of product per litre of spray would leave no room for water.
if dose_unit == "litres" and dose >= rate * 100:
raise ValueError("productDosePerHectare must be less than waterVolumeLitresPerHectare for a liquid product")
spray = area * rate # litres x 10^5
tank_spray = tank * 10000 # litres x 10^5
full_tanks = spray // tank_spray
remainder = spray - full_tanks * tank_spray
# A load from its spray volume in litres x 10^5: area = spray / rate,
# product = spray x dose / (rate x 10^4) in thousandths.
def load(s: int) -> TankLoad:
spray_ml = round_div(s, 100, "half-up")
product = round_div(s * dose, rate * 10000, "half-up")
return TankLoad(
area_hectares=round_div(s, rate, "half-up") / 10000,
spray_litres=spray_ml / 1000,
product=product / 1000,
water_litres=(spray_ml - product if dose_unit == "litres" else spray_ml) / 1000,
)
return SprayMix(
tank_fills=full_tanks + (1 if remainder > 0 else 0),
full_tanks=full_tanks,
full_tank=load(tank_spray),
partial_tank=load(remainder) if remainder > 0 else None,
total_spray_litres=round_div(spray, 100, "half-up") / 1000,
total_product=round_div(area * dose, 10000, "half-up") / 1000,
)