Functional Weave
Code in TypeScript

health.dose-weight-based@1.0.1

impl/python.py

3,480 bytes · the Python implementation · view raw

from typing import Optional

from .health_dose_weight_based_types import WeightBasedDose


def _check_range(name: str, value: int, low: int, high: int) -> None:
    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))


def _half_up(num: int, den: int) -> int:
    """num / den rounded half-up, for num >= 0 and den > 0."""
    return (2 * num + den) // (2 * den)


def _half_away_from_zero(num: int, den: int) -> int:
    magnitude = _half_up(abs(num), den)
    return -magnitude if num < 0 else magnitude


def weight_based_dose(
    weight_grams: int,
    dose_per_kg_micrograms: int,
    max_single_dose_micrograms: Optional[int],
    strength_micrograms: int,
    strength_volume_microlitres: int,
    measuring_increment_microlitres: int,
) -> WeightBasedDose:
    """A weight-based dose, capped at the maximum single dose and turned into
    a volume the measuring device can show.

    Every quantity is a whole number of micrograms, microlitres or grams and
    every step is an exact fraction, so a 0.1 mL step never drifts. The volume
    is the nearest whole number of increments, a tie going down, and never one
    that would deliver more than the maximum single dose.
    """
    _check_range("weightGrams", weight_grams, 200, 500000)
    _check_range("dosePerKgMicrograms", dose_per_kg_micrograms, 1, 1000000)
    if max_single_dose_micrograms is not None:
        _check_range("maxSingleDoseMicrograms", max_single_dose_micrograms, 1, 100000000)
    _check_range("strengthMicrograms", strength_micrograms, 1, 100000000)
    _check_range("strengthVolumeMicrolitres", strength_volume_microlitres, 1, 1000000)
    _check_range("measuringIncrementMicrolitres", measuring_increment_microlitres, 1, 100000)

    per_kg = weight_grams * dose_per_kg_micrograms  # micrograms x 1000
    max_dose = max_single_dose_micrograms
    capped = max_dose is not None and per_kg > max_dose * 1000
    # The target dose as the exact fraction target_num / target_den micrograms.
    target_num = max_dose if capped else per_kg
    target_den = 1 if capped else 1000
    assert target_num is not None

    strength = strength_micrograms
    volume = strength_volume_microlitres
    increment = measuring_increment_microlitres
    # Increments needed: target x volume / (strength x increment), nearest, a tie down.
    steps_num = target_num * volume
    steps_den = target_den * strength * increment
    steps, remainder = divmod(steps_num, steps_den)
    if 2 * remainder > steps_den:
        steps += 1
    # Rounding up to the nearest step must not take the dose over the maximum.
    if max_dose is not None and steps * increment * strength > max_dose * volume:
        steps -= 1
    if steps == 0:
        raise ValueError("dose is less than one measuring increment; use a more dilute product or a finer measure")

    delivered_num = steps * increment * strength  # micrograms x volume
    return WeightBasedDose(
        calculated_micrograms=_half_up(per_kg, 1000),
        capped_at_maximum=capped,
        target_micrograms=_half_up(target_num, target_den),
        volume_microlitres=steps * increment,
        delivered_micrograms=_half_up(delivered_num, volume),
        difference_basis_points=_half_away_from_zero(
            (delivered_num * target_den - target_num * volume) * 10000, target_num * volume
        ),
    )