A rate library does not have to be a spreadsheet of numbers somebody typed once and dated. It can be authored as text — parametric, formula-driven, reviewable — and imported whole as a package the engine re-evaluates on arrival. Almost nobody talks about this. Here is how it actually works.
What most organisations call a rate library is a spreadsheet of unit rates with a date in the filename. It answers exactly one question — what did this cost — and only under the conditions that happened to be true when somebody wrote it down. Change the shift length, the access constraint, the crew size or the haul distance, and the spreadsheet has nothing to say. You either accept the rate and hope, or you rebuild the item from scratch, at which point the library has contributed a starting number and nothing else.
A parametric library answers a different question: what does this cost given these conditions. Every assumption that drives the rate survives in the library as a named variable rather than dissolving into a constant, and every quantity downstream is expressed as an arithmetic relationship to those variables. Hand that library to someone else and they do not receive a number. They receive a model that produces a number, and a set of controls for changing what they need to change.
TX1:Trinity imports a whole priced library as a .tx1 package — compressed, checksum-validated, with every authored formula re-evaluated by the engine on arrival.
The second thing nobody says out loud is that you do not have to build it inside the estimating application. A library can be authored entirely outside — in a workbook, in a database, generated by script from a single source of truth — provided it is written in the form the engine expects, and then imported as a package. That changes what is possible at the top end. Work authored outside can be version controlled, reviewed in bulk, built by several people at once behind a validation gate, and regenerated end to end when a rate moves. An 1,852-item library is not something anyone types into a dialog box one item at a time; it is something that gets generated, checked and published.
A library has three layers and the bottom one is a naming convention. Atomic resources carry a prefix for their cost type and then a structured tail, shaped TYPE.FAMILY.SPEC[.MOD].PER.UNIT — so a twenty-tonne wet-hire excavator is P.WET.EXC.20T and optical ground wire supplied by the kilometre is M.OPGW.SUPPLY.PER.KM. The convention looks like housekeeping. It is actually the thing that makes the library queryable, mergeable and reviewable, because a code that encodes its own meaning can be sorted, grouped and diffed without a lookup table.
| Prefix | Carries | Typical unit |
|---|---|---|
L. | Labour, all-in: award or enterprise agreement base plus on-costs | hr |
P. | Plant. A .WET code embeds its operator; a .DRY code does not | hr, day, wk |
M. | Material supply, carrying its own wastage allowance | m3, t, m, ea, km |
S. | Subcontract and specialist testing | ea, set, lot, test |
O. | Other: disposal, levies, accommodation, fees | t, wk, LS |
G. | Group composites: crews, wet plant, discipline spreads | hr, day |
One cost element, one rate, one basis. They are copied exactly into everything above them, so a resource rate is changed in one place and inherited everywhere.
Reusable bundles — a crew charged as a single productive hour, a wet plant item, a discipline spread. Composition is stable; productivity is not, and stays out.
The payment item itself: inputs, derived quantities, productivity, priced lines, summary, benchmarks. This is where the job-specific reasoning lives.
The wet-versus-dry distinction is worth dwelling on, because it is the most common double-count in civil estimating. A wet plant rate already contains its operator. Add a separate operator line underneath it and the item carries that person twice, in a way that is completely invisible in the total and obvious the moment someone reads the lines. Encoding the answer in the code itself — .WET means the operator is in there — turns a judgement call into something a validation rule can check across an entire library in one pass.
Group composites introduce the rule that most reliably breaks an import. A group publishes a rate, and the engine recomputes that rate as the sum of its contributing component rows. Every row in a group block is therefore flagged as contributing or not: the ownership, operating, on-cost, allowance and operator rows are money and contribute; the input definitions, the benchmark comparator and the display subtotal are scaffolding and do not. A wet excavator group is the clean worked example — dry ownership and operating cost, plus consumables, plus the operator drawn from a labour crew tier, plus allowances — and its published rate must equal the sum of exactly those rows. Get it wrong and nothing errors; the engine simply recomputes a different number from the one you authored, and your library has quietly changed on the way in.
Here is the part that surprises people. The formula column in an authored build-up is not a live spreadsheet formula. It is documentation text — a small pseudo-formula language — sitting beside quantity, rate and total columns that hold static numbers computed at authoring time. The workbook shows the answer; the formula text shows the reasoning; and on import the engine parses that text and re-evaluates it, which is precisely why the model stays alive on the other side.
| Construct | Meaning | Example |
|---|---|---|
set(#VAR, expr) | Declare a named variable, exactly once | set(#PAY_QTY, 180) |
#VAR | Reference a variable set earlier | #CONC_VOL * #FORM_RATIO |
IF(cond, a, b) | Conditional; nest for selectors and validity gates | IF(#NIGHT==1, #SHIFTS*4, 0) |
MAX(a, b) | A floor — typically a daily minimum hire | MAX(#EFF_HRS, #MIN_DAYS*#SHIFT_HRS) |
OR(…) | Combine conditions inside a gate | IF(OR(#PAY_QTY<=0, #TR_D>#MAX_D), 0, 1) |
ROUNDUP(x, 0) | Round up — shifts are whole things | ROUNDUP(#EFF_HRS/#SHIFT_HRS, 0) |
#lq<line> | The quantity of another line, by line number | (#lq44 + #lq45) / #PAY_QTY |
#dc_total | The item's direct-cost roll-up | #dc_total / #PAY_QTY |
SUM(L61:L62) | Range sum over total rows, for benchmark breakouts | SUM(L61:L62) / #PAY_QTY |
Three grammar rules do most of the work of keeping a library importable. A variable is set once and only once, because re-assignment has no defined meaning in a dependency graph. Absolute spreadsheet cell references never appear in formula text — the only permitted line reference is #lq against a line number, because line numbers travel with the export and cell addresses do not. And every number that a human might want to change lives in a set() row rather than being written inline, which is the subject of the next section and the single most important thing on this page.
The one sentence: authored formula text is what makes an imported library a model rather than a snapshot — the numbers tell the recipient what you concluded, and the formulas let them ask what happens if the conditions change.
A library whose levers are baked into constants cannot be tuned by the person who receives it. That is the whole argument, and everything else on this page is machinery in service of it.
Consider two versions of the same labour line. In the first, the quantity is written as 240 — a number that is entirely correct, arrived at by careful thought about a crew of four working three ten-hour shifts under staged traffic. In the second, the quantity is #CREW_SIZE * #SHIFT_HRS * #SHIFTS, and each of those three variables was declared in an A-section set() row further up the item. Both produce 240. Only one of them can be changed by somebody who was not in the room.
The reason this matters mechanically is that the A-section inputs are the parametric model. On import, each set() row becomes an item-scoped definition that surfaces in the parametrics panel — a labelled control with a value, sitting where an estimator will find it. Anything written straight into a downstream quantity is not there. It is not hidden exactly, but it can only be reached by opening the build-up and editing the arithmetic, which is a thing nobody does to a library they did not write. Above the item sit the project-wide levers — LOCATION_FACTOR, RISK_MULTIPLIER, ESCALATION_FACTOR, PRODUCTIVITY_FACTOR, ACCESS_FACTOR, CONTINGENCY_PCT — declared at global scope and available to every item's formulas, so an entire library can be moved to a different region or a different market without touching one item.
There is a clean test for whether you have shipped a model or a picture of one. Hand the library to an estimator in another state, on a job with different access conditions, different shift lengths and a different escalation base. Can they get to a defensible rate by changing values in a panel, without opening a single build-up? If yes, you shipped a model. If no, you shipped a very detailed spreadsheet, and the detail will make them trust it more than they should.
Every build-up in a serious library follows the same section order, every time. The consistency is not aesthetic. A reviewer reading their four-hundredth item should be able to find the productivity assumption without hunting, and an automated check should be able to assert that the priced lines sum to the declared unit rate without guessing where the priced lines start.
| Section | What it holds |
|---|---|
| ITEM_HEADER | Item number, governing specification reference, verbatim description, unit, final unit rate |
| A INPUTS | Every tunable as a set() row: payment quantity, geometry ratios, selectors, crew sizes, shift length, access and night flags, benchmark band |
| B CALCULATIONS | A validity gate, then every derived quantity that feeds a priced line, a test count or a shift count |
| ENVELOPE | Night, access and possession derates combined into one productivity factor |
| C PRODUCTIVITY | Effective crew hours after the factor is applied — once — and whole shifts by round-up |
| D1–D5 | Priced lines in fixed order: plant, materials, subcontract, testing and quality, labour |
| N ADDERS | Condition-only additions, gated live rather than deleted, so the item re-prices when the selector flips |
| F SUMMARY | Direct-cost total and the direct unit rate that divides it by the payment quantity |
| G BENCHMARKS | Per-category cost-per-unit breakouts and the item's position against its band |
| M MAN-HOURS | Direct labour hours per paid unit, and paid output per shift |
| H NOTES | Specification mapping, testing basis, hold and witness points, exclusions, open parameter gaps, revision and check date |
Read downwards, the order is an argument. Here is what I assumed; here is what follows arithmetically from it; here is the derate the conditions impose; here is the productivity that results; here is what I bought; here is the total; here is how it compares; here is what I excluded and why. A reviewer can attack any link without re-deriving the rest, and that is what auditable means in practice.
| Line | Authored formula text | What it does |
|---|---|---|
| A 16 | set(#PROD_ACCESS, 0.80) | Declares the access derate as a control, not a constant |
| A 17 | set(#NIGHT, 1) | Night selector; a 0/1 unit types it as a Boolean on export |
| B 23 | set(#TRENCH_VOL, #PAY_QTY*#TR_W*#TR_D) | Bedding and backfill come from geometry, not a flat allowance |
| B 25 | IF(OR(#PAY_QTY<=0, #TR_D>#MAX_D), 0, 1) | Validity gate — the item refuses to price outside its envelope |
| ENV 30 | set(#PROD_FACTOR, #PROD_ACCESS*IF(#NIGHT==1, #PROD_NIGHT, 1)) | Every condition collapses into one factor |
| C 35 | set(#SHIFTS, ROUNDUP(#EFF_HRS/#SHIFT_HRS, 0)) | Whole shifts; exported so traffic and mobilisation items can consume it |
| D1 41 | MAX(#EFF_HRS, #MIN_HIRE_D*#SHIFT_HRS) | Plant hours floored at the daily minimum hire |
| F 60 | #dc_total / #PAY_QTY | The direct unit rate the item publishes |
The failures worth naming are all small and all silent. A constant written downstream that should have been an A-input, so the lever never appears. The productivity factor applied twice — once in the envelope and again in the priced line — which is a quiet twenty per cent that nobody finds. A wet plant code with an operator line under it. A variable set twice in the same item. An input declared and never referenced, which is usually the fossil of an assumption that was removed from the maths but not from the documentation. And the group whose published rate no longer equals the sum of its contributing rows, which survives every visual review and fails on the first import.
One more, less obvious: deleting the branches of a selector you did not choose. If an item offers three methods and you delete two, the model can no longer re-price when somebody flips the selector, and you have traded the parametric behaviour for a tidier sheet. Price all three branches and gate the unselected ones to zero. A library that is consistently coded this way is also the raw material for a real benchmark, which is the argument on optimism bias and the outside view.
A .tx1 package is the portable form of a project or a library — compressed, checksum-validated, optionally AES-256 encrypted — carrying resources, group composites, every build-up line, the authored formula text and the parametric definitions together. On import, TX1:Trinity does not simply load your totals. It rebuilds the model.
The A-section set() rows become item-scoped definitions and appear in the parametrics panel; the six envelope levers land at global scope where every item's formulas can reach them. Every line exports, including the definitions, calculations and summaries that carry no money, because downstream formulas need them to resolve. Group resources arrive as built-up resource breakdowns whose base rate the engine recomputes from the contributing rows, with the non-contributing rows carried through at zero so the reasoning survives without polluting the arithmetic.
Underneath is a shunting-yard parser, an abstract syntax tree and a dependency graph with cycle detection, running more than seventy functions, project-wide and scoped parametrics, line references and direct-cost roll-ups. Change one component of a built-up resource and the package rate recomputes, every item using it recomputes, and all allocations across the code sets follow. Rates move through the pipeline as Direct Costs plus Overheads to Total Cost, then markup by resource type, then risk, corporate and profit margins to a sell price — with EffectiveBaseRate = BaseRate × AppliedFactor doing the factor work along the way.
TMR_MASTER.tx1 packageYes. A library can be authored entirely as structured text and numbers in a workbook or database, then imported as a .tx1 package that the formula engine re-evaluates on arrival. The requirement is that it be written in the form the engine expects: resource codes it recognises, formula text it can parse, and quantity, rate and total columns holding static pre-computed values rather than live spreadsheet formulas.
A .tx1 is the portable project and library package used by TX1:Trinity. It is compressed, checksum-validated and optionally AES-256 encrypted, and it carries resources, group composites, build-up lines, formula text and the parametric definitions together. Because the formula text travels with the numbers, the receiving copy can re-evaluate the model rather than merely displaying the sender's totals.
A group or composite resource publishes a rate that the engine recomputes as the sum of its contributing component rows. The contributes flag marks which rows are money and which are scaffolding: input definitions, memos, benchmark comparators and display subtotals do not contribute and export with a total of zero. If the published rate does not equal the sum of contributing rows, the recomputed rate will differ from the authored one on import.
Because the A-section inputs are the parametric model. On import each one becomes an item-scoped definition that appears in the parametrics panel, so it is a control the receiving estimator can change. A number written straight into a downstream quantity is invisible there, which means the assumption it represents can only be changed by opening and editing the build-up itself.
Large enough that authoring by hand inside an application stops being realistic, which is the practical argument for authoring outside it. The in-house TMR library carries 1,852 fully built-up payment items with their resources, group composites and formula text, and packages to a single TMR_MASTER.tx1 file of about 19.2 MB.
No. The formula grammar is a small pseudo-formula language written as plain text in a column, closer to spreadsheet notation than to programming, and a library can be authored entirely in a workbook. Scripting helps at scale, mainly for regenerating the workbook and package from a single source of truth, but the authoring itself is estimating work rather than software work.
Author it outside, package it once, and let the engine re-evaluate every formula on arrival — parametrics intact, group rates recomputed, allocations following through.