from .agri_milk_price_types import MilkContract, MilkPrice, MilkSupply from .math_round_div import round_div from .money_amount import money MAX_PRICE = 1000000 MAX_LITRES = 1000000000 def _whole(value: int, name: str, low: int, high: int) -> int: if isinstance(value, bool) or not isinstance(value, int) or value < low or value > high: raise ValueError("%s must be a whole number from %d to %d, received %r" % (name, low, high, value)) return value def milk_price(contract: MilkContract, supply: MilkSupply) -> MilkPrice: """Milk price for one period under a buyer's contract. Prices are thousandths of a penny (or cent) per litre, because contracts quote to 0.01 or 0.001 ppl. Constituent adjustments are pro rata per 0.1 percentage point and are each rounded to a thousandth, half away from zero; the payment is litres x price, rounded once to the penny. """ money(0, contract.currency) base = _whole(contract.base_price, "basePrice", 0, MAX_PRICE) std_fat = _whole(contract.standard_butterfat_basis_points, "standardButterfatBasisPoints", 0, 10000) fat_value = _whole(contract.butterfat_value, "butterfatValue", -MAX_PRICE, MAX_PRICE) std_protein = _whole(contract.standard_protein_basis_points, "standardProteinBasisPoints", 0, 10000) protein_value = _whole(contract.protein_value, "proteinValue", -MAX_PRICE, MAX_PRICE) litres = _whole(supply.litres, "litres", 0, MAX_LITRES) fat = _whole(supply.butterfat_basis_points, "butterfatBasisPoints", 0, 10000) protein = _whole(supply.protein_basis_points, "proteinBasisPoints", 0, 10000) month = _whole(supply.month, "month", 1, 12) # 0.1 percentage point is 10 basis points. fat_adjustment = round_div((fat - std_fat) * fat_value, 10, "half-up") protein_adjustment = round_div((protein - std_protein) * protein_value, 10, "half-up") volume_bonus = 0 previous = -1 for i, tier in enumerate(contract.volume_bonuses): minimum = _whole(tier.minimum_litres, "volumeBonuses[%d].minimumLitres" % i, 0, MAX_LITRES) bonus = _whole(tier.bonus, "volumeBonuses[%d].bonus" % i, -MAX_PRICE, MAX_PRICE) if minimum <= previous: raise ValueError("volumeBonuses must be in strictly ascending order of minimumLitres") previous = minimum if litres >= minimum: volume_bonus = bonus seasonal = 0 if len(contract.seasonality) == 12: for i, v in enumerate(contract.seasonality): _whole(v, "seasonality[%d]" % i, -MAX_PRICE, MAX_PRICE) seasonal = contract.seasonality[month - 1] elif len(contract.seasonality) != 0: raise ValueError("seasonality must have 12 monthly values or none, received %d" % len(contract.seasonality)) price = base + fat_adjustment + protein_adjustment + volume_bonus + seasonal if price < 0: raise ValueError("price per litre must not be negative, received %d" % price) if price > MAX_PRICE: raise ValueError("price per litre must be at most %d, received %d" % (MAX_PRICE, price)) return MilkPrice( base_price=base, butterfat_adjustment=fat_adjustment, protein_adjustment=protein_adjustment, volume_bonus=volume_bonus, seasonal_adjustment=seasonal, price_per_litre=price, total=money(round_div(price * litres, 1000, "half-up"), contract.currency), )