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 Sequence
from .agri_gross_margin_types import GrossMargin, MarginBasis, MarginLine
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_add import subtract_money ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .money_sum import sum_money ← from money.sum ^1.0.0 · built alongside by fune
MAX_MINOR = 900000000000
def gross_margin(
outputs: Sequence[MarginLine],
variable_costs: Sequence[MarginLine],
per: MarginBasis,
units: float,
currency: str,
) -> GrossMargin:
"""Gross margin: enterprise output less its variable costs, per hectare or head.
Each per-unit figure is its own total divided once, half up. Taking output
per head less costs per head instead can be a penny out: 3 head with 10000
output and 5000 costs is 3333 - 1667 = 1666 that way, but 5000 / 3 = 1667.
"""
if per == "hectare":
if isinstance(units, bool) or not isinstance(units, (int, float)) or not math.isfinite(units) or units <= 0 or units > 10000000:
raise ValueError("units must be a finite number of hectares greater than 0 and at most 10000000, received %r" % (units,))
divisor = _round_half_away(units * 10000)
if divisor < 1:
raise ValueError("units must be at least 0.0001 hectares, received %r" % (units,))
scale = 10000
elif per == "head":
if isinstance(units, bool) or not isinstance(units, int) or units < 1 or units > 10000000:
raise ValueError("units must be a whole number of head from 1 to 10000000, received %r" % (units,))
divisor = units
scale = 1
else:
raise ValueError('unknown basis "%s": use hectare or head' % (per,))
output = sum_money([line.amount for line in outputs], currency)
costs = sum_money([line.amount for line in variable_costs], currency)
margin = subtract_money(output, costs)
for m in (output, costs, margin):
if abs(m.minor) > MAX_MINOR:
raise ValueError("totals must be within 900000000000 minor units either side of zero")
def per_unit(m: Money) -> Money:
return money(round_div(m.minor * scale, divisor, "half-up"), currency)
return GrossMargin(
output=output,
variable_costs=costs,
gross_margin=margin,
output_per_unit=per_unit(output),
variable_costs_per_unit=per_unit(costs),
gross_margin_per_unit=per_unit(margin),
margin_basis_points=None if output.minor == 0 else round_div(margin.minor * 10000, output.minor, "half-up"),
)
def _round_half_away(x: float) -> int:
# Math.round and f64::round agree with this for positive numbers.
return int(math.floor(x + 0.5))