Functional Weave
Code in TypeScript

construction.valuation

Interim valuation: gross value to date, retention, previous payments, amount due, VAT or reverse charge, and CIS.

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

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

What it does

An interim valuation (application for payment, payment certificate) from the quantity surveyor's figures to the amount the payer actually transfers.

## The order of operations

For example

  • interimValuation(work done £100,000.00, materials on site £5,000.00, retention basis points 5%, stage interim, previously certified £70,000.00, cis status —, cis materials £0.00, vat category STAN…) → gross valuation £105,000.00, retention …, net valuation £99,750.00, previously certified £70,000.00, amount due £29,750.00, vat £5,950.00, vat basis points 20%, reverse charge vat… 100,000 of work and 5,000 of materials on site, 5% retention, 70,000 paid before, standard-rated VAT, no CIS
  • interimValuation(work done £100,000.00, materials on site £5,000.00, retention basis points 5%, stage interim, previously certified £70,000.00, cis status standard, cis materials £9,750.00, vat ca…) → gross valuation £105,000.00, retention …, net valuation £99,750.00, previously certified £70,000.00, amount due £29,750.00, vat £0.00, vat basis points 20%, reverse charge vat £5,… the same valuation under the reverse charge and CIS: no VAT charged, 20% withheld from the labour only
  • interimValuation(work done £10,000.00, materials on site £0.00, retention basis points 3%, stage interim, previously certified £5,000.00, cis status higher, cis materials £0.00, vat category —, re…) → gross valuation £10,000.00, retention …, net valuation £9,700.00, previously certified £5,000.00, amount due £4,700.00, vat £0.00, vat basis points 0%, reverse charge vat £0.00, c… a subcontractor not registered for VAT or CIS: 30% of the whole amount due

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.

export function interimValuation(input: ValuationInput): Valuation
inputValuationInput
returnsValuation

The types it declares, generated into your project

/** One valuation, cumulative to date, as a quantity surveyor prepares it. */
export interface ValuationInput {
  /** value of work properly executed to date, variations included */
  readonly workDone: Money;
  /** value of unfixed materials included in the valuation */
  readonly materialsOnSite: Money;
  /** the retention percentage, 500 = 5% */
  readonly retentionBasisPoints: number;
  /** interim, practical-completion or final */
  readonly stage: RetentionStage;
  /** total of the amounts due on earlier valuations */
  readonly previouslyCertified: Money;
  /** null when the payment is not made under CIS */
  readonly cisStatus: CisStatus | null;
  /** materials, plant hire and the like within this amount due, excluded from the CIS deduction */
  readonly cisMaterials: Money;
  /** GB VAT category of the supply (STANDARD, REDUCED, ZERO); null when the payee is not VAT registered */
  readonly vatCategory: string | null;
  /** true when the domestic reverse charge applies (see construction.vat-reverse-charge) */
  readonly reverseCharge: boolean;
  /** the tax point and payment date that decide the VAT and CIS rates */
  readonly valuationDate: string;
}

/** The payment certificate, line by line. */
export interface Valuation {
  /** work done plus materials on site */
  readonly grossValuation: Money;
  readonly retention: Retention;
  /** gross less retention held */
  readonly netValuation: Money;
  readonly previouslyCertified: Money;
  /** net valuation less previously certified, before VAT and CIS */
  readonly amountDue: Money;
  /** VAT charged on the invoice; zero under the reverse charge */
  readonly vat: Money;
  readonly vatBasisPoints: number;
  /** VAT the customer accounts for under the reverse charge; zero otherwise */
  readonly reverseChargeVat: Money;
  /** the CIS deduction, null when not under CIS */
  readonly cis: CisDeduction | null;
  /** amount due plus VAT charged less the CIS deduction */
  readonly amountPayable: Money;
}

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

import { interimValuation } from "#fune/construction.valuation@^1";
impl/typescript.ts · 61 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.

import { type CisDeduction, cisDeduction } from "./construction_cis_deduction.ts";  ← from construction.cis-deduction ^1.0.0 · built alongside by fune
import { retention } from "./construction_retention.ts";  ← from construction.retention ^1.0.0 · built alongside by fune
import { addVat } from "./finance_tax_add_vat.ts";  ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
import { addMoney, subtractMoney } from "./money_add.ts";  ← from money.add ^1.0.0 · built alongside by fune
import { money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type Valuation, type ValuationInput } from "./construction_valuation_types.ts";

/**
 * An interim valuation, from the cumulative value of the work to what is paid.
 *
 * The order is the whole point: retention is taken on the cumulative gross,
 * previous certificates come off the net, VAT goes on the amount due, and the
 * CIS deduction comes off the labour in the amount due, never off the VAT.
 * Each step is its own capability; this one only fixes the order.
 */
export function interimValuation(input: ValuationInput): Valuation {
  const grossValuation = addMoney(input.workDone, input.materialsOnSite);
  const held = retention(grossValuation, input.retentionBasisPoints, input.stage);
  const netValuation = subtractMoney(grossValuation, held.held);
  const amountDue = subtractMoney(netValuation, input.previouslyCertified);
  const currency = amountDue.currency;

  let vat = money(0, currency);
  let reverseChargeVat = money(0, currency);
  let vatBasisPoints = 0;
  if (input.vatCategory === null) {
    if (input.reverseCharge) {
      throw new RangeError("the reverse charge needs a VAT category: the supplier must be VAT registered");
    }
  } else {
    if (input.reverseCharge && input.vatCategory !== "STANDARD" && input.vatCategory !== "REDUCED") {
      throw new RangeError(`the reverse charge applies only to STANDARD or REDUCED supplies, received ${input.vatCategory}`);
    }
    const breakdown = addVat(amountDue, "GB", input.vatCategory, input.valuationDate);
    vatBasisPoints = breakdown.basisPoints;
    if (input.reverseCharge) reverseChargeVat = breakdown.tax;
    else vat = breakdown.tax;
  }

  let cis: CisDeduction | null = null;
  if (input.cisStatus !== null) {
    if (amountDue.minor < 0) {
      throw new RangeError(`the amount due is negative (${amountDue.minor}), so there is no payment to make a CIS deduction from`);
    }
    cis = cisDeduction(amountDue, input.cisMaterials, input.cisStatus, input.valuationDate, "down");
  }

  const payable = addMoney(amountDue, vat);
  return {
    grossValuation,
    retention: held,
    netValuation,
    previouslyCertified: input.previouslyCertified,
    amountDue,
    vat,
    vatBasisPoints,
    reverseChargeVat,
    cis,
    amountPayable: cis === null ? payable : subtractMoney(payable, cis.deduction),
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 5 dependencies, pins them in fune.lock, downloads only the TypeScript 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. Or pin a range in fune.project and build in one step:

fune add construction.valuation
Download for TypeScript construction.valuation-1.0.0-typescript.fune · 35,812 bytes sha256 04400efcfd6ab416628da70dd98a5c4b4432a0a0d4fab86ac02112df9b024caa

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./construction.valuation-1.0.0-typescript.fune, or fetch it from a terminal with fune pull construction.valuation@1.0.0:typescript.

The whole function, every language, is one file too: construction.valuation-1.0.0.fune, 44,079 bytes, sha256 1974a3927d6147e478ea118428c408484aed5f276db955298896d5162f0078a8. 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 construction.valuation

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

// fune: after construction.valuation

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 construction.cis-deduction in construction.valuation
// fune: replace construction.retention in construction.valuation
// fune: replace finance.tax.add-vat in construction.valuation
// fune: replace money.add in construction.valuation
// fune: replace money.amount in construction.valuation

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 construction.valuation --steps.

// fune: step construction.valuation 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,000 of work and 5,000 of materials on site, 5% retention, 70,000 paid before, standard-rated VAT, no CIS work done £100,000.00, materials on site £5,000.00, retention basis points 5%, stage interim, previously certified £70,000.00, cis status —, cis materials £0.00, vat category STAN… → gross valuation £105,000.00, retention …, net valuation £99,750.00, previously certified £70,000.00, amount due £29,750.00, vat £5,950.00, vat basis points 20%, reverse charge vat…
the same valuation under the reverse charge and CIS: no VAT charged, 20% withheld from the labour only work done £100,000.00, materials on site £5,000.00, retention basis points 5%, stage interim, previously certified £70,000.00, cis status standard, cis materials £9,750.00, vat ca… → gross valuation £105,000.00, retention …, net valuation £99,750.00, previously certified £70,000.00, amount due £29,750.00, vat £0.00, vat basis points 20%, reverse charge vat £5,…
a subcontractor not registered for VAT or CIS: 30% of the whole amount due work done £10,000.00, materials on site £0.00, retention basis points 3%, stage interim, previously certified £5,000.00, cis status higher, cis materials £0.00, vat category —, re… → gross valuation £10,000.00, retention …, net valuation £9,700.00, previously certified £5,000.00, amount due £4,700.00, vat £0.00, vat basis points 0%, reverse charge vat £0.00, c…
practical completion releases half the retention: the amount due is the released half work done £100,000.00, materials on site £0.00, retention basis points 5%, stage practical-completion, previously certified £95,000.00, cis status —, cis materials £0.00, vat cate… → gross valuation £100,000.00, retention …, net valuation £97,500.00, previously certified £95,000.00, amount due £2,500.00, vat £500.00, vat basis points 20%, reverse charge vat £0…
the final valuation releases the rest; gross payment status withholds nothing work done £100,000.00, materials on site £0.00, retention basis points 5%, stage final, previously certified £97,500.00, cis status gross, cis materials £0.00, vat category STANDA… → gross valuation £100,000.00, retention …, net valuation £100,000.00, previously certified £97,500.00, amount due £2,500.00, vat £500.00, vat basis points 20%, reverse charge vat £…
over-certified before: a negative amount due, with negative VAT, when there is no CIS work done £10,000.00, materials on site £0.00, retention basis points 5%, stage interim, previously certified £10,000.00, cis status —, cis materials £0.00, vat category STANDARD,… → gross valuation £10,000.00, retention …, net valuation £9,500.00, previously certified £10,000.00, amount due -£500.00, vat -£100.00, vat basis points 20%, reverse charge vat …
reduced-rated work: 5% of 11,975.30 is 598.765, VAT rounds half up to 598.77; CIS on the 10,000.00 labour work done £12,345.67, materials on site £0.00, retention basis points 3%, stage interim, previously certified £0.00, cis status standard, cis materials £1,975.30, vat category RED… → gross valuation £12,345.67, retention …, net valuation £11,975.30, previously certified £0.00, amount due £11,975.30, vat £598.77, vat basis points 5%, reverse charge vat £0.00, c…
the CIS deduction rounds down: 20% of 123.48 is 24.696, withheld 24.69 work done £123.48, materials on site £0.00, retention basis points 0%, stage interim, previously certified £0.00, cis status standard, cis materials £0.00, vat category —, reverse… → gross valuation £123.48, retention …, net valuation £123.48, previously certified £0.00, amount due £123.48, vat £0.00, vat basis points 0%, reverse charge vat £0.00, cis …, amoun…
zero-rated work: VAT at 0% work done £5,000.00, materials on site £0.00, retention basis points 5%, stage interim, previously certified £0.00, cis status —, cis materials £0.00, vat category ZERO, reverse c… → gross valuation £5,000.00, retention …, net valuation £4,750.00, previously certified £0.00, amount due £4,750.00, vat £0.00, vat basis points 0%, reverse charge vat £0.00, cis —,…
the rate on the valuation date: 17.5% in 2010 work done £1,000.00, materials on site £0.00, retention basis points 0%, stage interim, previously certified £0.00, cis status —, cis materials £0.00, vat category STANDARD, rever… → gross valuation £1,000.00, retention …, net valuation £1,000.00, previously certified £0.00, amount due £1,000.00, vat £175.00, vat basis points 17.5%, reverse charge vat £0.00, c…
Show the other 6 tests
CaseArgumentsExpected
the reverse charge without a VAT category work done £1,000.00, materials on site £0.00, retention basis points 0%, stage interim, previously certified £0.00, cis status —, cis materials £0.00, vat category —, reverse char… → error: the reverse charge needs a VAT category
the reverse charge on zero-rated work work done £1,000.00, materials on site £0.00, retention basis points 0%, stage interim, previously certified £0.00, cis status —, cis materials £0.00, vat category ZERO, reverse c… → error: the reverse charge applies only to STANDARD or REDUCED supplies
a negative amount due under CIS work done £10,000.00, materials on site £0.00, retention basis points 5%, stage interim, previously certified £10,000.00, cis status standard, cis materials £0.00, vat category —,… → error: the amount due is negative (-50000)
CIS materials above the amount due work done £1,000.00, materials on site £0.00, retention basis points 0%, stage interim, previously certified £0.00, cis status standard, cis materials £1,000.01, vat category —, r… → error: cannot exceed the payment
materials on site in another currency work done £1,000.00, materials on site €1.00, retention basis points 0%, stage interim, previously certified £0.00, cis status —, cis materials £0.00, vat category —, reverse char… → error: currency mismatch
an unknown retention stage work done £1,000.00, materials on site £0.00, retention basis points 5%, stage handover, previously certified £0.00, cis status —, cis materials £0.00, vat category —, reverse cha… → error: unknown retention stage "handover"

More from the author

1. **Gross valuation** = work done to date + materials on site. Both are cumulative, as valuations always are. 2. **Retention** on the cumulative gross, with `construction.retention`: the full percentage before practical completion, half after it, none after the defects are made good. 3. **Net valuation** = gross - retention held. 4. **Amount due** = net - previously certified. Because everything above is cumulative, retention released at practical completion simply shows up here as money due. 5. **VAT** on the amount due, with `finance.tax.add-vat` (GB, the category and the valuation date). Under the domestic reverse charge nothing is charged: `vat` is zero and `reverseChargeVat` shows what the customer accounts for to HMRC. Whether the reverse charge applies is a separate question for `construction.vat-reverse-charge`; pass its answer as `reverseCharge`. 6. **CIS** with `construction.cis-deduction`: the deduction is taken from the amount due less `cisMaterials`, never from the VAT, at the payee's status and the rate on the valuation date. The deduction rounds **down** to the penny (HMRC publishes no rounding rule; down never over-deducts). Call `construction.cis-deduction` yourself if you need another mode. 7. **Amount payable** = amount due + VAT charged - CIS deduction.

## Edges

- A valuation below what was previously certified gives a negative amount due, with negative VAT: the payee owes the difference. Under CIS that is an error, because there is no payment to deduct from; correct it in the next valuation. - `vatCategory` null means the payee is not VAT registered; the reverse charge then cannot apply and is refused, as is a reverse charge on zero-rated work. - `cisMaterials` is ignored when `cisStatus` is null. It must not exceed the amount due. - All amounts must share one currency.

Payment timing (due date, payment and pay-less notices) is `construction.payment-notice-dates`.

Files

PathBytes
README.md2,175
impl/python.py2,781
impl/rust.rs5,191
impl/typescript.ts2,616
vectors.json22,929