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
interim_valuation(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 CISinterim_valuation(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 onlyinterim_valuation(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.
def interim_valuation(input: ValuationInput) -> Valuation
| input | ValuationInput | |
| returns | Valuation |
The types it declares, generated into your project
@dataclass(frozen=True)
class ValuationInput:
"""One valuation, cumulative to date, as a quantity surveyor prepares it."""
#: value of work properly executed to date, variations included
work_done: Money
#: value of unfixed materials included in the valuation
materials_on_site: Money
#: the retention percentage, 500 = 5%
retention_basis_points: int
#: interim, practical-completion or final
stage: RetentionStage
#: total of the amounts due on earlier valuations
previously_certified: Money
#: null when the payment is not made under CIS
cis_status: Optional[CisStatus]
#: materials, plant hire and the like within this amount due, excluded from the CIS deduction
cis_materials: Money
#: GB VAT category of the supply (STANDARD, REDUCED, ZERO); null when the payee is not VAT registered
vat_category: Optional[str]
#: true when the domestic reverse charge applies (see construction.vat-reverse-charge)
reverse_charge: bool
#: the tax point and payment date that decide the VAT and CIS rates
valuation_date: str
@dataclass(frozen=True)
class Valuation:
"""The payment certificate, line by line."""
#: work done plus materials on site
gross_valuation: Money
retention: Retention
#: gross less retention held
net_valuation: Money
previously_certified: Money
#: net valuation less previously certified, before VAT and CIS
amount_due: Money
#: VAT charged on the invoice; zero under the reverse charge
vat: Money
vat_basis_points: int
#: VAT the customer accounts for under the reverse charge; zero otherwise
reverse_charge_vat: Money
#: the CIS deduction, null when not under CIS
cis: Optional[CisDeduction]
#: amount due plus VAT charged less the CIS deduction
amount_payable: Money
Your code names it in one line, in the file that uses it
from fune.construction.valuation import interim_valuation # construction.valuation@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import Optional
from .construction_cis_deduction import CisDeduction, cis_deduction ← from construction.cis-deduction ^1.0.0 · built alongside by fune
from .construction_retention import retention ← from construction.retention ^1.0.0 · built alongside by fune
from .construction_valuation_types import Valuation, ValuationInput
from .finance_tax_add_vat import add_vat ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
from .money_add import add_money, subtract_money ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import money ← from money.amount ^1.0.0 · built alongside by fune
def interim_valuation(input: ValuationInput) -> Valuation:
"""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.
"""
gross_valuation = add_money(input.work_done, input.materials_on_site)
held = retention(gross_valuation, input.retention_basis_points, input.stage)
net_valuation = subtract_money(gross_valuation, held.held)
amount_due = subtract_money(net_valuation, input.previously_certified)
currency = amount_due.currency
vat = money(0, currency)
reverse_charge_vat = money(0, currency)
vat_basis_points = 0
if input.vat_category is None:
if input.reverse_charge:
raise ValueError("the reverse charge needs a VAT category: the supplier must be VAT registered")
else:
if input.reverse_charge and input.vat_category not in ("STANDARD", "REDUCED"):
raise ValueError(
"the reverse charge applies only to STANDARD or REDUCED supplies, received %s" % (input.vat_category,)
)
breakdown = add_vat(amount_due, "GB", input.vat_category, input.valuation_date)
vat_basis_points = breakdown.basis_points
if input.reverse_charge:
reverse_charge_vat = breakdown.tax
else:
vat = breakdown.tax
cis: Optional[CisDeduction] = None
if input.cis_status is not None:
if amount_due.minor < 0:
raise ValueError(
"the amount due is negative (%d), so there is no payment to make a CIS deduction from" % (amount_due.minor,)
)
cis = cis_deduction(amount_due, input.cis_materials, input.cis_status, input.valuation_date, "down")
payable = add_money(amount_due, vat)
return Valuation(
gross_valuation=gross_valuation,
retention=held,
net_valuation=net_valuation,
previously_certified=input.previously_certified,
amount_due=amount_due,
vat=vat,
vat_basis_points=vat_basis_points,
reverse_charge_vat=reverse_charge_vat,
cis=cis,
amount_payable=payable if cis is None else subtract_money(payable, cis.deduction),
)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 5 dependencies, pins them in fune.lock, downloads only the Python 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
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./construction.valuation-1.0.0-python.fune, or fetch it from a terminal with fune pull construction.valuation@1.0.0:python.
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| README.md | 2,175 |
| impl/python.py | 2,781 |
| impl/rust.rs | 5,191 |
| impl/typescript.ts | 2,616 |
| vectors.json | 22,929 |