Functional Weave
Code in Rust

agri.feed-ration@1.0.0

impl/python.py

3,538 bytes · the Python implementation · view raw

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, Optional, Sequence

from .agri_feed_ration_types import RationCost, RationFeed, RationLine
from .math_round_float import round_float  ← from math.round-float ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune

MAX_SAFE = 9007199254740991


def _half_up(numerator: int, denominator: int) -> int:
    # Half up for a non-negative numerator and a positive denominator.
    q = (2 * numerator + denominator) // (2 * denominator)
    if q > MAX_SAFE:
        raise ValueError("total cost is too large")
    return q


def _whole(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def feed_ration(feeds: Sequence[RationFeed], head: int, days: int, currency: str) -> RationCost:
    """Ration cost and dry matter intake.

    Weights are grams and prices minor units per tonne, so a feed's daily cost
    is exactly grams x price / 1,000,000 and every total is rounded once from
    the exact sum. Costing the group from a rounded daily cost multiplies the
    rounding: 467.5p a head a day is 1,402,500p for 100 cows over 30 days, not
    468 x 3000 = 1,404,000p.
    """
    if not _whole(head) or head < 1 or head > 1000000:
        raise ValueError("head must be a whole number from 1 to 1000000, received %r" % (head,))
    if not _whole(days) or days < 1 or days > 3660:
        raise ValueError("days must be a whole number from 1 to 3660, received %r" % (days,))
    money(0, currency)

    grams = 0
    dry_matter = 0  # grams x basis points
    cost = 0  # grams x minor units per tonne
    lines: List[RationLine] = []
    for i, feed in enumerate(feeds):
        n = i + 1
        kg = feed.fresh_kg_per_head
        if isinstance(kg, bool) or not isinstance(kg, (int, float)) or not math.isfinite(kg) or kg < 0 or kg > 1000:
            raise ValueError("feed %d: freshKgPerHead must be a finite number from 0 to 1000, received %r" % (n, kg))
        g = int(round(round_float(float(kg), 3) * 1000))
        bp = feed.dry_matter_basis_points
        if not _whole(bp) or bp < 0 or bp > 10000:
            raise ValueError("feed %d: dryMatterBasisPoints must be a whole number from 0 to 10000, received %r" % (n, bp))
        price = feed.price_per_tonne
        if price.currency != currency:
            raise ValueError("feed %d: currency mismatch: %s and %s" % (n, price.currency, currency))
        if not _whole(price.minor) or price.minor < 0 or price.minor > 100000000:
            raise ValueError("feed %d: pricePerTonne must be from 0 to 100000000 minor units, received %r" % (n, price.minor))
        line_dm = g * bp
        line_cost = g * price.minor
        grams += g
        dry_matter += line_dm
        cost += line_cost
        lines.append(
            RationLine(
                name=feed.name,
                fresh_kg_per_head=g / 1000,
                dry_matter_kg_per_head=_half_up(line_dm, 10000) / 1000,
                cost_per_head=money(_half_up(line_cost, 1000000), currency),
            )
        )

    per_tonne_dm: Optional[Money] = None if dry_matter == 0 else money(_half_up(cost * 10000, dry_matter), currency)
    return RationCost(
        lines=lines,
        fresh_kg_per_head=grams / 1000,
        dry_matter_kg_per_head=_half_up(dry_matter, 10000) / 1000,
        dry_matter_basis_points=None if grams == 0 else _half_up(dry_matter, grams),
        cost_per_head_per_day=money(_half_up(cost, 1000000), currency),
        total_cost=money(_half_up(cost * head * days, 1000000), currency),
        cost_per_tonne_dry_matter=per_tonne_dm,
    )