Functional Weave
Code in Python

logistics.delivery-date@1.0.0

impl/python.py

2,798 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 re
from typing import List, Optional, Sequence

from .dates_add_business_days import add_business_days  ← from dates.add-business-days ^1.0.0 · built alongside by fune
from .dates_bank_holidays import UkRegion, bank_holidays  ← from dates.bank-holidays ^1.0.0 · built alongside by fune
from .dates_day_of_week import day_of_week  ← from dates.day-of-week ^1.0.0 · built alongside by fune
from .logistics_delivery_date_types import DeliveryEstimate

_TIME = re.compile(r"([01][0-9]|2[0-3]):[0-5][0-9]")


def _estimate(
    order_date: str, order_time: str, cut_off: str, service_days: int, holidays: Sequence[str]
) -> DeliveryEstimate:
    working = day_of_week(order_date) <= 5 and order_date not in holidays
    # HH:MM compares correctly as text. Strictly before: 15:00 misses a 15:00 cut-off.
    same_day = working and order_time < cut_off
    dispatch = order_date if same_day else add_business_days(order_date, 1, holidays)
    return DeliveryEstimate(
        dispatch_date=dispatch,
        delivery_date=add_business_days(dispatch, service_days, holidays),
        same_day_dispatch=same_day,
    )


def delivery_date(
    order_date: str,
    order_time: str,
    cut_off: str,
    service_days: int,
    region: Optional[UkRegion],
    closures: Sequence[str],
) -> DeliveryEstimate:
    """When an order leaves the warehouse and when it should arrive: same
    working day if placed before the cut-off, then service_days working days in
    transit, skipping weekends, the region's bank holidays and the caller's
    closures."""
    if not isinstance(order_time, str) or not _TIME.fullmatch(order_time):
        raise ValueError('orderTime must be a 24-hour time HH:MM, received "%s"' % (order_time,))
    if not isinstance(cut_off, str) or not _TIME.fullmatch(cut_off):
        raise ValueError('cutOff must be a 24-hour time HH:MM, received "%s"' % (cut_off,))
    if isinstance(service_days, bool) or not isinstance(service_days, int):
        raise TypeError("serviceDays must be an integer, received %r" % (service_days,))
    if service_days < 0:
        raise ValueError("serviceDays must not be negative, received %d" % service_days)
    day_of_week(order_date)  # validates the date before its year is used

    holidays: List[str] = list(closures)
    last_year = int(order_date[0:4])
    if region is not None:
        holidays.extend(h.date for h in bank_holidays(region, last_year))
    # Load each later year the estimate reaches: an order on 31 December must
    # skip next year's New Year's Day. More holidays only move the answer later,
    # so this settles.
    while True:
        result = _estimate(order_date, order_time, cut_off, service_days, holidays)
        reached = int(result.delivery_date[0:4])
        if region is None or reached <= last_year:
            return result
        for year in range(last_year + 1, reached + 1):
            holidays.extend(h.date for h in bank_holidays(region, year))
        last_year = reached