Skip to content

Calculating

totals = cuf.totals()  # under the file's policy (the usage rules by default)
totals.estimate  # Costs(hours=…, labour=…, material=…, …)
totals.estimate.total  # direct costs, excluding markups and VAT
totals[bundle]  # a bundle's costs: the sum of its direct children
totals[line]  # a line's costs, before multipliers
totals.extended(line)  # its contribution to the estimate, multipliers applied
totals.resource(resource_line)  # a resource line's costs

Costs holds labour hours plus the five cost types of CUF-XML: labour (loon), material (materiaal), equipment (materieel), subcontracting (onderaanneming) and other (overig). total is the sum of the five amounts. All arithmetic is exact Decimal arithmetic at 60 significant digits in pycuf's own decimal context: your context's precision, rounding, exponent limits and traps change nothing, also when you read total, extended() or a table inside it. Real estimates need fewer than 30 digits; a result that would need more than 60 (only absurd inputs produce one) is rounded half-even to 60. Numbers in a file are limited to the range pycuf reads, so no calculation can overflow or silently become 0.

The formulas

Estimate line (BEGROTINGSREGEL), with Q = HOEVEELHEID × HOEVEELHEID_FACTOR:

Cost Formula
hours Q × UUR_NORM
labour Q × UUR_NORM × UUR_TARIEF
material, equipment, subcontracting, other Q × MATERIAALPRIJS, × MATERIEELPRIJS, × ONDERAANNEMINGSPRIJS, × OVERIGE_KOSTEN

AANTAL × INZET and PRODUCTIE are informative and never change a cost.

Resource line (MAMO_REGEL): labour (LOON) hours = HOEVEELHEID × UUR_NORM, labour = hours × UUR_TARIEF × PRIJS_FACTOR; other cost types HOEVEELHEID × PRIJS × PRIJS_FACTOR.

Bundle (BUNDELING): the sum of its direct children. A child bundle counts with its own DOORREKEN_HOEVEELHEID; the bundle's own multiplier is not included, because that is what its stated totals show. The estimate is the sum of its direct children in the same way.

Missing or empty numbers count as 0, missing or empty factors as 1.

Policies

The two specification texts (the 4.003 schema comments and the 2006 usage rules) disagree in places, and exporters and importers read them differently. A Policy makes every choice explicit:

Field Default Choices
zero_factor "one" what a factor written as 0 means: ignore it (usage rules) or literally 0
estimate_style "auto" whether DOORREKEN_HOEVEELHEID multiplies: "element", "traditional" (ignore) or "auto" (when present)
labour "spec" labour resource lines: the spec formula, or "erp" (HOEVEELHEID is hours, PRIJS the rate)
resources "fallback" price a line from its resource lines when it states no prices itself, "ignore" them, or "prefer" them
resource_basis "total" a resource line's quantity is for the whole line, or "per-unit" of the line
abs_tol, rel_tol 0.01, 0 tolerances for checking stated totals (math.isclose semantics)
rounding "ROUND_HALF_UP" rounding of a computed value to the decimals of a stated value before comparing

Comparing a stated with a computed value first rounds the computed value to the decimals the stated value is written with (1234.50: two), then applies the tolerances. A stated value written without decimals (100, 1E+2) is not rounded to: exporters drop trailing zeros, so 100 usually means 100.00, and rounding to whole units would hide differences of up to 0.50. It is compared as written, within abs_tol and rel_tol.

Presets

Name Constant Meaning
"usage-rules" pycuf.policy.USAGE_RULES the 2006 "Gebruikersregels CUF 4003"; the default
"schema" pycuf.policy.SCHEMA the literal schema: a factor of 0 is 0, resource lines are only informative
"erp" pycuf.policy.ERP labour resource lines as ERP importers read them
from decimal import Decimal
from pycuf.policy import ERP

cuf.totals(policy="schema")  # a preset by name
cuf.totals(policy=ERP.replace(abs_tol=Decimal("0.05")))  # a preset with one field changed
pycuf.read(path, policy="erp")  # the file's default from then on

Policies are immutable and hashable: replace() returns a validated copy, and cuf.totals() caches one result per policy. Every result records the policy it used (totals.policy) and the resolved estimate style (totals.estimate_style).

Element estimates

In an element estimate (elementenbegroting) bundles are building elements with a quantity, DOORREKEN_HOEVEELHEID, that multiplies all costs beneath them: a façade element of 100 m² whose lines are priced per m². With estimate_style="auto" pycuf applies the multipliers whenever a file contains them and reports it (CUF7005). totals.multiplier(node) gives the product of the multipliers applied to a node.

Resource lines

CUF-XML makes the estimate line leading and its resource (MAMO) lines informative. Some exporters only price the resource lines, so with resources="fallback" a line without prices of its own is priced from its resource lines (reported as CUF5006). When a line has both, pycuf checks that they agree (CUF5004).

Why these defaults?

The defaults follow the 2006 usage rules, which Ketenstandaard publishes as the rules for 4.003 and which AFAS also documents ("leeg of 0 … is dus 1"). Each alternative exists because a real exporter or importer behaves that way; the format guide has the details.