Functional Weave
Code in Rust

construction.retention@1.0.0

impl/typescript.ts

1,650 bytes · the TypeScript 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 { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type Retention, type RetentionStage } from "./construction_retention_types.ts";

/**
 * Retention on a valuation, the way JCT-style contracts release it.
 *
 * Retention is always worked out afresh on the cumulative value, never
 * accumulated valuation by valuation, so a later valuation corrects an earlier
 * one. At practical completion half the percentage applies (not half of a
 * figure rounded earlier), and after the defects are made good none does.
 */
export function retention(cumulativeValue: Money, basisPoints: number, stage: RetentionStage): Retention {
  if (!Number.isInteger(basisPoints) || basisPoints < 0 || basisPoints > 10000) {
    throw new RangeError(`basisPoints must be a whole number from 0 to 10000, received ${basisPoints}`);
  }
  if (cumulativeValue.minor < 0) {
    throw new RangeError(`cumulativeValue must not be negative, received ${cumulativeValue.minor}`);
  }
  const product = cumulativeValue.minor * basisPoints;
  if (!Number.isSafeInteger(product)) {
    throw new RangeError("the retention calculation exceeds 2^53 - 1");
  }
  const full = roundDiv(product, 10000, "half-up");
  let held: number;
  if (stage === "interim") held = full;
  else if (stage === "practical-completion") held = roundDiv(product, 20000, "half-up");
  else if (stage === "final") held = 0;
  else throw new RangeError(`unknown retention stage "${stage}"`);
  const currency = cumulativeValue.currency;
  return { full: money(full, currency), held: money(held, currency), released: money(full - held, currency) };
}