Functional Weave
Code in Rust

health.dose-weight-based Unreviewed

Weight-based dose (per kg) capped at a maximum single dose and rounded to a measurable volume, in exact micro-units.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 20 tests, run in TypeScript, Python and Rust.

Unreviewed. Not for clinical use; regulatory review pending. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified pharmacist has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

Not professional advice. This capability calculates health figures from published rules. It is a software component for developers, not medical advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a pharmacist review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

Not a medical device. It is not intended to diagnose, treat or support clinical decisions about any individual. Anyone building it into clinical software is responsible for that software’s regulatory status, and must validate it under their own clinical governance.

What it does

Status: needs review and sign-off by a qualified clinician before it is published. Not a medical device; for decision support only; always follow local clinical guidelines.

Works out a weight-based dose (so much per kilogram), caps it at a maximum single dose, and turns it into a volume the measuring device can actually show: a whole number of 0.1 mL, 0.01 mL or 1 mL steps. It holds no drug data. The prescriber's dose per kg, the maximum single dose and the product strength all come from the caller, from the prescription and the product in hand.

For example

  • weight_based_dose(12,400, 15,000, —, 120,000, 5,000, 100) → calculated micrograms 186,000, capped at maximum false, target micrograms 186,000, volume microlitres 7,700, delivered micrograms 184,800, difference basis points -0.65% 15 mg/kg for 12.4 kg is 186 mg; at 120 mg/5 mL that is 7.75 mL, an exact tie between 0.1 mL steps, so it goes down to 7.7 mL (184.8 mg, 0.65% under)
  • weight_based_dose(12,600, 15,000, 500,000, 120,000, 5,000, 100) → calculated micrograms 189,000, capped at maximum false, target micrograms 189,000, volume microlitres 7,900, delivered micrograms 189,600, difference basis points 0.32% 15 mg/kg for 12.6 kg is 189 mg = 7.875 mL, nearest 0.1 mL step is up: 7.9 mL (189.6 mg, 0.32% over)
  • weight_based_dose(60,000, 20,000, 1,000,000, 500,000, 5,000, 500) → calculated micrograms 1,200,000, capped at maximum true, target micrograms 1,000,000, volume microlitres 10,000, delivered micrograms 1,000,000, difference basis points 0% 20 mg/kg for 60 kg is 1200 mg, capped at the 1000 mg maximum: 500 mg/5 mL in 0.5 mL steps is exactly 10 mL

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

pub fn weight_based_dose(weight_grams: i64, dose_per_kg_micrograms: i64, max_single_dose_micrograms: Option<i64>, strength_micrograms: i64, strength_volume_microlitres: i64, measuring_increment_microlitres: i64) -> WeightBasedDose
weight_gramsintthe patient's weight in grams, 200 to 500000; 12.4 kg is 12400
dose_per_kg_microgramsintthe prescribed dose per kg in micrograms, 1 to 1000000; 15 mg/kg is 15000
max_single_dose_microgramsint?the maximum single dose in micrograms, 1 to 100000000; null for none
strength_microgramsintthe product strength: this many micrograms, 1 to 100000000 ...
strength_volume_microlitresint... in this many microlitres, 1 to 1000000; 120 mg/5 mL is 120000 in 5000
measuring_increment_microlitresintthe smallest step the measuring device shows, 1 to 100000; 0.1 mL is 100
returnsWeightBasedDose

The type it declares, generated into your project

/// The dose worked out, the volume to give, and how far the measurable volume is from the dose.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WeightBasedDose {
    /// weight × dose per kg, half-up to a whole microgram, before any cap
    pub calculated_micrograms: i64,
    /// true when the calculated dose was above the maximum single dose
    pub capped_at_maximum: bool,
    /// the dose aimed for: the maximum when capped, else the calculated dose; half-up
    pub target_micrograms: i64,
    /// the volume to give, a whole number of measuring increments
    pub volume_microlitres: i64,
    /// what that volume contains, half-up to a whole microgram
    pub delivered_micrograms: i64,
    /// delivered against target, signed; -65 = 0.65% under; half away from zero
    pub difference_basis_points: i64,
}

Your code names it in one line, in the file that uses it

fune!(health.dose-weight-based@^1);  // then call weight_based_dose(…)
impl/rust.rs · 129 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one

fn check_range(name: &str, value: i64, low: i64, high: i64) {
    if value < low || value > high {
        panic!("{} must be a whole number from {} to {}, received {}", name, low, high, value);
    }
}

/// num / den rounded half-up, for num >= 0 and den > 0.
fn half_up(num: i128, den: i128) -> i128 {
    (2 * num + den) / (2 * den)
}

fn half_away_from_zero(num: i128, den: i128) -> i128 {
    let magnitude = half_up(num.abs(), den);
    if num < 0 {
        -magnitude
    } else {
        magnitude
    }
}

/// 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 (i128),
/// 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.
///
/// # Panics
/// Panics on an argument out of range, or a dose smaller than one increment.
pub fn weight_based_dose(
    weight_grams: i64,
    dose_per_kg_micrograms: i64,
    max_single_dose_micrograms: Option<i64>,
    strength_micrograms: i64,
    strength_volume_microlitres: i64,
    measuring_increment_microlitres: i64,
) -> WeightBasedDose {
    check_range("weightGrams", weight_grams, 200, 500000);
    check_range("dosePerKgMicrograms", dose_per_kg_micrograms, 1, 1000000);
    if let Some(max) = max_single_dose_micrograms {
        check_range("maxSingleDoseMicrograms", max, 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);

    let per_kg = weight_grams as i128 * dose_per_kg_micrograms as i128; // micrograms x 1000
    let max = max_single_dose_micrograms.map(|m| m as i128);
    let capped = matches!(max, Some(m) if per_kg > m * 1000);
    // The target dose as the exact fraction target_num / target_den micrograms.
    let (target_num, target_den) = if capped { (max.unwrap(), 1) } else { (per_kg, 1000) };

    let strength = strength_micrograms as i128;
    let volume = strength_volume_microlitres as i128;
    let increment = measuring_increment_microlitres as i128;
    // Increments needed: target x volume / (strength x increment), nearest, a tie down.
    let steps_num = target_num * volume;
    let steps_den = target_den * strength * increment;
    let mut steps = steps_num / steps_den;
    if 2 * (steps_num % steps_den) > steps_den {
        steps += 1;
    }
    // Rounding up to the nearest step must not take the dose over the maximum.
    if let Some(m) = max {
        if steps * increment * strength > m * volume {
            steps -= 1;
        }
    }
    if steps == 0 {
        panic!("dose is less than one measuring increment; use a more dilute product or a finer measure");
    }

    let delivered_num = steps * increment * strength; // micrograms x volume
    WeightBasedDose {
        calculated_micrograms: half_up(per_kg, 1000) as i64,
        capped_at_maximum: capped,
        target_micrograms: half_up(target_num, target_den) as i64,
        volume_microlitres: (steps * increment) as i64,
        delivered_micrograms: half_up(delivered_num, volume) as i64,
        difference_basis_points: half_away_from_zero(
            (delivered_num * target_den - target_num * volume) * 10000,
            target_num * volume,
        ) as i64,
    }
}

pub fn weight_based_dose_to_value(result: &WeightBasedDose) -> Value {
    Value::obj(vec![
        ("calculatedMicrograms", Value::Int(result.calculated_micrograms)),
        ("cappedAtMaximum", Value::Bool(result.capped_at_maximum)),
        ("targetMicrograms", Value::Int(result.target_micrograms)),
        ("volumeMicrolitres", Value::Int(result.volume_microlitres)),
        ("deliveredMicrograms", Value::Int(result.delivered_micrograms)),
        ("differenceBasisPoints", Value::Int(result.difference_basis_points)),
    ])
}

const RANGES: [(&str, i64, i64); 6] = [
    ("weightGrams", 200, 500000),
    ("dosePerKgMicrograms", 1, 1000000),
    ("maxSingleDoseMicrograms", 1, 100000000),
    ("strengthMicrograms", 1, 100000000),
    ("strengthVolumeMicrolitres", 1, 1000000),
    ("measuringIncrementMicrolitres", 1, 100000),
];

/// Refuse what an i64 cannot hold (a fraction, a string) with the wording
/// TypeScript and Python use, rather than let the conversion change it.
fn int_arg(args: &[Value], index: usize) -> i64 {
    let (name, low, high) = RANGES[index];
    match &args[index] {
        Value::Int(v) => *v,
        other => panic!("{} must be a whole number from {} to {}, received {:?}", name, low, high, other),
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    let max = if args[2].is_null() { None } else { Some(int_arg(args, 2)) };
    weight_based_dose_to_value(&weight_based_dose(
        int_arg(args, 0),
        int_arg(args, 1),
        max,
        int_arg(args, 3),
        int_arg(args, 4),
        int_arg(args, 5),
    ))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add health.dose-weight-based
Download for Rust health.dose-weight-based-1.0.1-rust.fune · 19,275 bytes sha256 7bb3f0730e26c319322566e20e4d6bca6e2872c6b071fcb8102b879b5d401562

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./health.dose-weight-based-1.0.1-rust.fune, or fetch it from a terminal with fune pull health.dose-weight-based@1.0.1:rust.

The whole function, every language, is one file too: health.dose-weight-based-1.0.1.fune, 26,519 bytes, sha256 fb3a319fd96fe97fc55f9f76484cb17fd73dfdd268483893ef40cb6d90639fa5. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before health.dose-weight-based

after — your function gets the result and the arguments, and returns the final result.

// fune: after health.dose-weight-based

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show health.dose-weight-based --steps.

// fune: step health.dose-weight-based after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
15 mg/kg for 12.4 kg is 186 mg; at 120 mg/5 mL that is 7.75 mL, an exact tie between 0.1 mL steps, so it goes down to 7.7 mL (184.8 mg, 0.65% under) 12,400, 15,000, —, 120,000, 5,000, 100 → calculated micrograms 186,000, capped at maximum false, target micrograms 186,000, volume microlitres 7,700, delivered micrograms 184,800, difference basis points -0.65%
15 mg/kg for 12.6 kg is 189 mg = 7.875 mL, nearest 0.1 mL step is up: 7.9 mL (189.6 mg, 0.32% over) 12,600, 15,000, 500,000, 120,000, 5,000, 100 → calculated micrograms 189,000, capped at maximum false, target micrograms 189,000, volume microlitres 7,900, delivered micrograms 189,600, difference basis points 0.32%
20 mg/kg for 60 kg is 1200 mg, capped at the 1000 mg maximum: 500 mg/5 mL in 0.5 mL steps is exactly 10 mL 60,000, 20,000, 1,000,000, 500,000, 5,000, 500 → calculated micrograms 1,200,000, capped at maximum true, target micrograms 1,000,000, volume microlitres 10,000, delivered micrograms 1,000,000, difference basis points 0%
capped at 1000 mg, 120 mg/5 mL is 41.667 mL; the nearest 0.1 mL step (41.7 mL, 1000.8 mg) would exceed the maximum, so 41.6 mL 60,000, 20,000, 1,000,000, 120,000, 5,000, 100 → calculated micrograms 1,200,000, capped at maximum true, target micrograms 1,000,000, volume microlitres 41,600, delivered micrograms 998,400, difference basis points -0.16%
a dose exactly at the maximum is not capped 50,000, 20,000, 1,000,000, 500,000, 5,000, 500 → calculated micrograms 1,000,000, capped at maximum false, target micrograms 1,000,000, volume microlitres 10,000, delivered micrograms 1,000,000, difference basis points 0%
a neonate on an oral syringe marked in 0.01 mL: 3.45 kg x 15 mg/kg = 51.75 mg = 2.15625 mL, nearest 2.16 mL 3,450, 15,000, —, 120,000, 5,000, 10 → calculated micrograms 51,750, capped at maximum false, target micrograms 51,750, volume microlitres 2,160, delivered micrograms 51,840, difference basis points 0.17%
a 1 mL measure: 157.5 mg at 250 mg/5 mL is 3.15 mL, so 3 mL (150 mg, 4.76% under) 21,000, 7,500, —, 250,000, 5,000, 1,000 → calculated micrograms 157,500, capped at maximum false, target micrograms 157,500, volume microlitres 3,000, delivered micrograms 150,000, difference basis points -4.76%
half a microgram: the target is shown half-up (1235) but the difference is measured from the exact 1234.5, and the tie in steps goes down 12,345, 100, —, 1,000, 1,000, 1 → calculated micrograms 1,235, capped at maximum false, target micrograms 1,235, volume microlitres 1,234, delivered micrograms 1,234, difference basis points -0.04%
an awkward strength stays exact: 1 g in 3 mL, 10 mg/kg for 20 kg is 200 mg = 0.6 mL 20,000, 10,000, —, 1,000,000, 3,000, 100 → calculated micrograms 200,000, capped at maximum false, target micrograms 200,000, volume microlitres 600, delivered micrograms 200,000, difference basis points 0%
a dose smaller than one measuring increment is an error, not zero 1,000, 100, —, 120,000, 5,000, 100 → error: dose is less than one measuring increment
Show the other 10 tests
CaseArgumentsExpected
an exact half increment rounds down to nothing and is refused 1,000, 1,200, —, 120,000, 5,000, 100 → error: dose is less than one measuring increment
weight below 200 g is refused 199, 15,000, —, 120,000, 5,000, 100 → error: weightGrams must be a whole number from 200 to 500000
weight in kilograms by mistake (12.4) is refused, not rounded 12.4, 15,000, —, 120,000, 5,000, 100 → error: weightGrams must be a whole number from 200 to 500000
a fractional weight in grams is refused 12,400.5, 15,000, —, 120,000, 5,000, 100 → error: weightGrams must be a whole number from 200 to 500000
weight above 500 kg is refused 500,001, 15,000, —, 120,000, 5,000, 100 → error: weightGrams must be a whole number from 200 to 500000
a zero dose per kg is refused 12,400, 0, —, 120,000, 5,000, 100 → error: dosePerKgMicrograms must be a whole number from 1 to 1000000
a zero maximum is refused, not read as no maximum 12,400, 15,000, 0, 120,000, 5,000, 100 → error: maxSingleDoseMicrograms must be a whole number from 1 to 100000000
a zero strength is refused 12,400, 15,000, —, 0, 5,000, 100 → error: strengthMicrograms must be a whole number from 1 to 100000000
a zero strength volume is refused 12,400, 15,000, —, 120,000, 0, 100 → error: strengthVolumeMicrolitres must be a whole number from 1 to 1000000
a measuring increment over 100 mL is refused 12,400, 15,000, —, 120,000, 5,000, 100,001 → error: measuringIncrementMicrolitres must be a whole number from 1 to 100000

More from the author

## Units

Everything is a whole number in small units, so no step drifts. In floating point, three 0.1 mL steps are 0.30000000000000004 mL. Here they are 300 µL.

| you have | pass | |----------|---------------------------| | 1 kg | 1000 g | | 1 mg | 1000 µg | | 1 mL | 1000 µL | | 120 mg/5 mL | strength 120000 µg in 5000 µL | | 1 g in 3 mL | strength 1000000 µg in 3000 µL (no 333.33… mg/mL rounding) |

A weight of 12.4 is refused, not read as grams, because a slip between kilograms and grams is a thousand-fold error.

## Worked example

15 mg/kg for a 12.4 kg child is 186 mg. At 120 mg in 5 mL that is 7.75 mL. The syringe is marked in 0.1 mL, and 7.75 lies exactly between 7.7 and 7.8. The tie goes down, so the answer is 7.7 mL, which holds 184.8 mg. `differenceBasisPoints` is -65, meaning the given dose is 0.65% below the calculated dose.

## Rounding rules

- **Cap first.** When weight × dose per kg is more than the maximum single dose, the maximum becomes the target and `cappedAtMaximum` is true. A dose exactly at the maximum is not capped. - **Nearest measurable volume, a tie goes down.** When the dose is exactly halfway between two steps, the smaller one is chosen, because that is the more cautious choice. - **Never over the maximum.** If rounding to the nearest step would deliver more than the maximum single dose, the volume drops one step. - **Never zero.** If the dose rounds to no increments at all, the function raises an error. The fix is a more dilute product or a finer measure, not silently giving nothing.

Local policy may round differently. For example, some policies always round down, and some allow up to ±10%. `differenceBasisPoints` compares the delivered dose with the target (signed, 100 = 1%), so a caller can enforce its own tolerance. The integer outputs (`calculatedMicrograms`, `targetMicrograms`, `deliveredMicrograms`) are rounded half-up to a whole microgram for display. The comparisons and the difference use the exact fractions.

## Limits (refused, not clamped)

weightGrams 200 to 500000; dosePerKgMicrograms 1 to 1000000; maxSingleDoseMicrograms 1 to 100000000 or null for none; strengthMicrograms 1 to 100000000; strengthVolumeMicrolitres 1 to 1000000; measuringIncrementMicrolitres 1 to 100000.

It does not check that the dose per kg, the maximum or the product are right for the drug, the patient's age or renal function, or the route. It does not convert to dosing by body surface area, and it does not cap by a daily maximum.

## Before you rely on this

**Not professional advice.** This capability calculates health figures from published rules. It is a software component for developers, not medical advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed above, and have a pharmacist review how you use it, before anyone relies on the output. Provided "as is" under its licence, without warranty.

**Not a medical device.** It is not intended to diagnose, treat or support clinical decisions about any individual. Anyone building it into clinical software is responsible for that software's regulatory status, and must validate it under their own clinical governance.

**Not for clinical use; regulatory review pending.** Whether publishing this capability makes it a medical device is under regulatory review. Until that is settled it is a developer library only.

**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified pharmacist has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

1.0.1 marks it unreviewed (not for clinical use; regulatory review pending). The code and the tests are unchanged.

Files

PathBytes
README.md4,777
impl/python.py3,480
impl/rust.rs5,178
impl/typescript.ts3,533
vectors.json5,176