Functional Weave
Code in Rust

manufacturing.yield-scrap

Whole units to start for a required good output at a process yield, and the scrap that allows for.

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

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

What it does

How many units to start so that, at a process yield, enough good units come out:

input = ceil(requiredOutput / yield) scrap = input - requiredOutput

For example

  • input_for_yield(100, 95%) → input 106, scrap 6 100 good units at 95% need 105.26, so 106 starts and 6 allowed as scrap
  • input_for_yield(95, 95%) → input 100, scrap 5 95 good units at 95% need exactly 100 starts
  • input_for_yield(563, 56.3%) → input 1,000, scrap 437 563 good units at 56.3% need exactly 1000 starts; 563 / 0.563 in floating point is 1000.0000000000001, whose ceiling is 1001

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 input_for_yield(required_output: i64, yield_basis_points: i64) -> YieldPlan
required_outputintgood units needed, not negative
yield_basis_pointsintgood output as a share of input: 9500 = 95%, 1 to 10000
returnsYieldPlan

The type it declares, generated into your project

/// What to start and what the process is expected to lose.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct YieldPlan {
    /// units to start: the smallest whole number whose yield covers requiredOutput
    pub input: i64,
    /// input minus requiredOutput: the units the yield allows to be lost
    pub scrap: i64,
}

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

fune!(manufacturing.yield-scrap@^1);  // then call input_for_yield(…)
impl/rust.rs · 40 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
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune

const MAX_OUTPUT: i64 = ((1i64 << 53) - 1) / 10000;

/// Starts needed for a good output at a yield, rounded up in integers so an
/// exact answer (95 at 95% is 100) never becomes 101 through a float.
///
/// # Panics
/// Panics on a negative or oversized output, or a yield outside 1-10000.
pub fn input_for_yield(required_output: i64, yield_basis_points: i64) -> YieldPlan {
    if !(0..=MAX_OUTPUT).contains(&required_output) {
        panic!(
            "requiredOutput must be a whole number of units from 0 to {}, received {}",
            MAX_OUTPUT, required_output
        );
    }
    if !(1..=10000).contains(&yield_basis_points) {
        panic!("yieldBasisPoints must be a whole number from 1 to 10000, received {}", yield_basis_points);
    }
    let input = round_div(required_output * 10000, yield_basis_points, "up");
    YieldPlan {
        input,
        scrap: input - required_output,
    }
}

pub fn yield_plan_to_value(plan: &YieldPlan) -> Value {
    Value::obj(vec![("input", Value::Int(plan.input)), ("scrap", Value::Int(plan.scrap))])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = &args[0] {
        panic!("requiredOutput must be a whole number of units from 0 to {}, received {}", MAX_OUTPUT, f);
    }
    if let Value::Float(f) = &args[1] {
        panic!("yieldBasisPoints must be a whole number from 1 to 10000, received {}", f);
    }
    yield_plan_to_value(&input_for_yield(args[0].as_i64(), args[1].as_i64()))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, 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 manufacturing.yield-scrap
Download for Rust manufacturing.yield-scrap-1.0.0-rust.fune · 7,208 bytes sha256 aba752f956be1bbaa166f2fe858a9f7c5d091d2409277bfb915109d86b5bed86

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./manufacturing.yield-scrap-1.0.0-rust.fune, or fetch it from a terminal with fune pull manufacturing.yield-scrap@1.0.0:rust.

The whole function, every language, is one file too: manufacturing.yield-scrap-1.0.0.fune, 9,473 bytes, sha256 ab5b9cd98e14ed85a740cf8e559162cb6e2553b2573427c955d5584c4f4351aa. 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 manufacturing.yield-scrap

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

// fune: after manufacturing.yield-scrap

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace math.round-div in manufacturing.yield-scrap

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 manufacturing.yield-scrap --steps.

// fune: step manufacturing.yield-scrap 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
100 good units at 95% need 105.26, so 106 starts and 6 allowed as scrap 100, 95% → input 106, scrap 6
95 good units at 95% need exactly 100 starts 95, 95% → input 100, scrap 5
563 good units at 56.3% need exactly 1000 starts; 563 / 0.563 in floating point is 1000.0000000000001, whose ceiling is 1001 563, 56.3% → input 1,000, scrap 437
57 at 90% need 63.33, so 64 57, 90% → input 64, scrap 7
a 100% yield needs no allowance 250, 100% → input 250, scrap 0
a tiny shortfall still costs a whole start: 1000 at 99.99% need 1000.1, so 1001 1,000, 99.99% → input 1,001, scrap 1
one good unit at 30% needs 3.33, so 4 starts 1, 30% → input 4, scrap 3
the lowest yield, 0.01%: one good unit needs 10000 starts 1, 0.01% → input 10,000, scrap 9,999
zero output needs no input 0, 80% → input 0, scrap 0
a 50% yield doubles the input exactly 37, 50% → input 74, scrap 37
Show the other 5 tests
CaseArgumentsExpected
a zero yield is an error: nothing good ever comes out 100, 0% → error: yieldBasisPoints must be a whole number from 1 to 10000
a yield over 100% is an error 100, 100.01% → error: yieldBasisPoints must be a whole number from 1 to 10000
a negative output is an error -5, 95% → error: requiredOutput must be a whole number of units
a fractional output is an error 10.5, 95% → error: requiredOutput must be a whole number of units
a fractional yield is an error 100, 95.005% → error: yieldBasisPoints must be a whole number from 1 to 10000

More from the author

With a 95% yield, 100 good units need 106 starts (105.26 rounded up), and the plan allows 6 to be scrapped.

**Rounded up, exactly.** You cannot start part of a unit, and rounding down would leave you short, so the input is always rounded up. The division is done in integers (`requiredOutput * 10000 / yieldBasisPoints`, rounded up by `math.round-div`), never as a float: 563 good units at a 56.3% yield need exactly 1000 starts, but `563 / 0.563` in floating point is 1000.0000000000001, and its ceiling is 1001.

**Scrap is the allowance.** At the stated yield, `input` starts are expected to give at least `requiredOutput` good units and fewer than `requiredOutput + 1` (the rounding never buys a whole extra unit), so `scrap` is the whole number of units the plan expects to lose. The actual scrap is whatever the process does on the day.

**Yield is in basis points**, 1 to 10000 (0.01% to 100%). A yield of 0 is an error: no number of starts gives any good output. A required output of 0 needs no input.

For a routing with several steps, apply this from the last step backwards: the input of one step is the required output of the one before, rounded up at each step, because each step has to start whole units.

Files

PathBytes
README.md1,408
impl/python.py1,163
impl/rust.rs1,549
impl/typescript.ts1,003
vectors.json2,046