Functional Weave
Code in TypeScript

inventory.safety-stock@1.0.0

impl/python.py

2,751 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 Optional

from .inventory_safety_stock_data import Z_SCORES  ← this capability’s own data, compiled from data/z-scores.json into the same file by fune build
from .inventory_safety_stock_types import SafetyStockInput


def _amount(name: str, value: Optional[float], method: str) -> float:
    if value is None:
        raise ValueError("%s needs %s" % (method, name))
    if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0:
        raise ValueError("%s must be a finite number, not negative, received %r" % (name, value))
    return float(value)


def _whole_units_up(x: float) -> int:
    # Six decimal places, half away from zero, then up to whole units: float
    # noise such as 55.00000000000001 must not cost a whole extra unit.
    y = x * 1e6
    micro = math.floor(y)
    if y - micro >= 0.5:
        micro += 1
    return (micro + 999999) // 1000000


def safety_stock(input: SafetyStockInput) -> int:
    """Safety stock in whole units, by the service-level or max-minus-average method."""
    lead_time = _amount("leadTime", input.lead_time, input.method)
    if input.method == "service-level":
        level = input.service_level_basis_points
        if level is None:
            raise ValueError("service-level needs serviceLevelBasisPoints")
        row = next((r for r in Z_SCORES if r.service_level_basis_points == level), None)
        if row is None:
            raise ValueError(
                "no z-score for a service level of %s basis points: use one of %s"
                % (level, ", ".join(str(r.service_level_basis_points) for r in Z_SCORES))
            )
        sd = _amount("demandStdDev", input.demand_std_dev, input.method)
        variance = lead_time * sd * sd
        sl = _amount("leadTimeStdDev", 0.0 if input.lead_time_std_dev is None else input.lead_time_std_dev, input.method)
        if sl > 0:
            d = _amount("averageDemand", input.average_demand, "service-level with a varying lead time")
            variance = variance + d * d * sl * sl
        return _whole_units_up((row.z_ten_thousandths / 10000) * math.sqrt(variance))
    if input.method == "max-minus-average":
        max_demand = _amount("maxDemand", input.max_demand, input.method)
        max_lead_time = _amount("maxLeadTime", input.max_lead_time, input.method)
        average_demand = _amount("averageDemand", input.average_demand, input.method)
        worst = max_demand * max_lead_time
        usual = average_demand * lead_time
        if worst < usual:
            raise ValueError(
                "maxDemand x maxLeadTime (%s) must not be less than averageDemand x leadTime (%s)" % (worst, usual)
            )
        return _whole_units_up(worst - usual)
    raise ValueError('unknown safety stock method "%s"' % (input.method,))