1. Purpose
A Recipe is a reusable, named combination of Resources — with its own Worksheet, logic, at least one Input Parameter, and a declared Output Unit. Recipes live in a shared Recipe Library accessible to all Estimates in a workspace. They model reusable methodologies or “crew compositions” that can be dropped into Item Worksheets and instantiated with different input values.
Everything in a Recipe is structured the same way as an Item’s Worksheet:
- Variables and Calculation Blocks for internal logic
- Worksheet Resources (Resource references with snapshot rates)
- Nested Recipes (recursive composition)
- Content Blocks (Inclusions, Exclusions, Task Breakdown, Risks)
The Recipe’s Worksheet evaluates to a rate per Output Unit. When dropped into an Item’s Worksheet, the host Item supplies a quantity (in the Output Unit) and the contribution = Recipe rate × quantity.
Analogy (glossary cooking model): the Recipe is the recipe — the reusable instructions (combination of ingredients and methodology) that produce a standardized output. Compare to Item (the cake, the final product) and Resource (the ingredient).
2. Attributes
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
name | string (short text) | ✅ | — | Human-readable name. Unique workspace-wide (no namespacing by Categorization) |
description | string (long text) | ❌ | null | What this Recipe represents and when to use it |
output_unit | Unit ref | ✅ | — | The Unit the Recipe’s result is expressed in (e.g., day, m³, pump). Required per BR-030 |
categorization_options | many-to-many | ❌ | [] | Multi-select tags for library organisation (e.g., “Concrete Works”, “Labour”). Optional; never affects behaviour (BR-120) |
notes | long text | ❌ | null | Additional context; free form |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
Derived attributes (computed, not stored) — see §9.
3. Input Parameters
Definition: Named variables defined at the Recipe level (not at usage time). Every Recipe must declare ≥1 Input Parameter per BR-030.
Input Parameters are the inputs to the Recipe’s Worksheet — they drive the internal calculation. They are NOT the same as the Output Unit (which declares how the result is expressed). They are also NOT the same as Worksheet Variables (which are derived inside the Worksheet).
| Attribute | Type | Required | Notes |
|---|---|---|---|
name | string | ✅ | E.g., “Concrete Quantity”, “Number of Trips”, “Pile Depth” |
unit | Unit ref | ✅ | The unit this parameter is measured in |
default_value | number | ❌ | Optional sensible default; used if host Worksheet doesn’t override |
description | string | ❌ | Plain English explanation of what this parameter represents |
Semantics:
- Input Parameters are required per Recipe (at least one). Each one must have a name and unit.
- When a Worksheet Recipe (a usage of this Recipe in an Item Worksheet) is created, the host estimator provides values for each Input Parameter. These values can be constants or expressions referencing Worksheet Variables.
- Input Parameters are available as Variables inside the Recipe’s Worksheet — the Recipe’s Calculation Blocks and Worksheet Resources reference them by name.
4. Output Unit Semantics
The Output Unit (a required Unit) declares the unit of measurement for the Recipe’s computed result.
How it works:
- The Recipe’s Worksheet evaluates all its Worksheet Resources, Calculation Blocks, and nested Recipes, producing a total cost.
- That total cost is divided by the (implicit or explicit) quantity per Output Unit to produce a rate per Output Unit.
- When a Worksheet Recipe references this Recipe, the host Worksheet multiplies the Recipe’s rate by a quantity (in Output Units) to get the total contribution.
Example — “Concrete Pump” Recipe:
- Input Parameters:
Concrete Quantity (m³),Number of Trips - Worksheet Resources: Pump mobilisation ($1,500), daily running ($800), demobilisation ($1,500), driver labour ($150/day)
- Calculation: total cost based on inputs
- Output Unit:
day(the recipe is priced per day of pump hire) - Result: e.g., $1,200/day
- When used: host Item provides quantity=3 (days) → contribution = $1,200 × 3 = $3,600
The Output Unit is a fixed property of the Recipe definition, not something the host Worksheet negotiates. This ensures the Recipe’s internal logic remains consistent wherever it’s used.
5. Lifecycle States
Alpha (v1) Recipes have no formal state machine. All Recipes are treated as Published — they can be used in Estimates immediately upon creation. Versioning concerns (e.g., “Recipe v1.0 vs v2.0”) are deferred to Beta (see Open items §11).
Rationale: In v1, a Recipe either exists (usable) or doesn’t. Divergence between the Recipe definition and a Worksheet Recipe that references it is flagged by Anomaly Review (BR-041/042/043), not prevented by state rules.
Future (Beta): Recipes may gain versioning — allowing a Recipe to evolve while Worksheet Recipes retain “snapshots” of older versions. Recipe snapshot semantics are governed by BR-041 (snapshot at embed), BR-042 (no auto-sync), BR-043 (divergence flagged by Anomaly Review).
6. Relationships
Inbound (things referring to Recipe)
| From | Cardinality | Notes |
|---|---|---|
| Worksheet Recipe | WRec M:1 Recipe | A Recipe can be dropped into many Item Worksheets; each usage is a Worksheet Recipe |
| Recipe (self, nested) | Recipe 1:M Recipe | A Recipe can contain nested Recipes via Worksheet Recipe (composition) |
Outbound (things Recipe references)
| To | Cardinality | Required | Notes |
|---|---|---|---|
| Worksheet | Recipe 1:1 Worksheet | ✅ | Every Recipe has exactly one (BR-006). Contains the Recipe’s methodology |
| Unit (Output Unit) | Recipe M:1 Unit | ✅ | Required; declares how the Recipe’s result is expressed |
| Input Parameter | Recipe 1:M InputParameter | ✅ | Required; ≥1 per Recipe (BR-030) |
| Categorization Option | Recipe M:M CatOption | ❌ | Optional multi-select tags for library organisation |
| User | Recipe M:0..1 User | ❌ | Not currently used; reserved for future ownership/lock |
7. Validation / Invariants
Rules that must hold at all times:
- At least one Input Parameter. Every Recipe must have ≥1 Input Parameter defined. Enforced on create and on deletion (cannot delete the last one).
- Output Unit mandatory.
output_unitcannot be null. Must reference a valid Unit from the library. - Unique name within workspace. Recipe names must be unique across the entire Recipe Library. Open (§11): should uniqueness be per-Categorization instead (allowing “Concrete Pump” under both “Concrete Works” and “Formwork”)?
- Worksheet 1:1. Every Recipe has a Worksheet, created atomically when the Recipe is created. No orphaned Worksheets (cascade delete).
- Input Parameter unit validity. Each Input Parameter must reference a valid Unit from the library.
- Worksheet Resources in Recipe reference valid Resources. All Worksheet Resource references inside the Recipe’s Worksheet must point to Resources that exist in a Price Book (optionally across any Price Book in the workspace, or Estimate-scoped — open).
- No circular nesting. A Recipe cannot transitively include itself (via nested Recipes in the Worksheet). Validated on Worksheet Recipe creation.
- Categorization validity. All Categorization Options applied to a Recipe must belong to Categorizations whose scope includes Recipe (or “combination” scopes).
8. Derived / Computed Attributes
| Attribute | Derivation | Notes |
|---|---|---|
unit_cost | Computed by evaluating the Recipe’s Worksheet (sum of Worksheet Resources evaluated) | Not stored; computed on demand when Worksheet Recipe supplies Input Parameter values |
rate_per_output_unit | unit_cost / quantity_per_output_unit | Where quantity_per_output_unit is implicit (1) unless the Worksheet explicitly scales by an Input Parameter |
total_cost_when_instantiated | rate_per_output_unit × quantity_supplied_by_host | Only computed when Worksheet Recipe is evaluated in the context of a host Item’s Worksheet |
has_anomalies | True if any Anomaly Review check fires (recipe snapshot divergence, missing resources, etc.) | UI badge |
9. Worked Examples
Example A — “2-Person Concrete Crew” Recipe
A simple, reusable crew composition:
Recipe:
name: "2-Person Concrete Crew"
description: "Standard two-person team for concrete finishing. Includes labour + small tools markup."
output_unit: hr (hour)
input_parameters:
- name: "hours"
unit: hr
default_value: null
description: "Total labour hours for the crew"
Worksheet (1 Worksheet Resource):
WR1: Resource "Finisher (skilled)" @ $65/hr, qty = hours × 1
WR2: Resource "Finisher (semi-skilled)" @ $48/hr, qty = hours × 1
WR3: Resource "Small tools markup" @ $8/hr, qty = hours × 1
Calculation Block:
crew_cost_per_hour = 65 + 48 + 8 = $121/hr
Result: $121/hr
Example instantiation in an Item's Worksheet:
Worksheet Recipe: "2-Person Concrete Crew"
Input Parameter "hours" = 40 (hours)
→ Contribution = $121/hr × 40 hr = $4,840
Example B — “Concrete Pump” Recipe
A more complex recipe with multiple input parameters:
Recipe:
name: "Concrete Pump (Boom Pump)"
description: "Mobilisation, running, and demobilisation of a boom pump for concrete placement."
output_unit: day
input_parameters:
- name: "Concrete Quantity"
unit: m³
default_value: null
description: "Total cubic metres of concrete to be pumped"
- name: "Number of Trips"
unit: no (number)
default_value: null
description: "Number of separate pump-down operations (e.g., floor pours)"
Worksheet Variables:
mob_cost = 1500 (fixed mobilisation, $/pump)
demob_cost = 1500 (fixed demobilisation, $/pump)
daily_rate = 800 ($/day operating cost)
driver_rate = 150 ($/day driver labour)
pump_efficiency = Concrete Quantity / Number of Trips (m³ per trip — informational)
Worksheet Resources:
WR1: Resource "Pump Mobilisation (1 day)" @ $1,500/day, qty = 1
WR2: Resource "Pump Daily Rate" @ $800/day, qty = 1
WR3: Resource "Pump Driver" @ $150/day, qty = 1
Calculation Block:
total_cost = mob_cost + demob_cost + daily_rate + driver_rate = $3,950
Result: $3,950/day
Example instantiation:
Worksheet Recipe: "Concrete Pump"
Input Param "Concrete Quantity" = 200 (m³)
Input Param "Number of Trips" = 2 (trips)
→ Contribution = $3,950/day × 3 days = $11,850
Example C — “Pile Drive” Recipe (discrete output unit)
Demonstrates a Recipe with a count-based Output Unit:
Recipe:
name: "Pile Drive (All-in)"
description: "Complete pile driving service including mobilisation, setup, and demob."
output_unit: pile (count — each pile)
input_parameters:
- name: "Pile Depth"
unit: m (metres)
default_value: null
description: "Average depth of piles in the project"
- name: "Number of Piles"
unit: no
default_value: null
description: "Total number of piles to be driven"
- name: "Pile Diameter"
unit: mm
default_value: 400
description: "Diameter of pile shafts"
Worksheet Variables:
mob_cost_per_project = 8000
hourly_drive_rate = 50 (m/hour for a hammer rig)
total_drive_hours = (Pile Depth × Number of Piles) / hourly_drive_rate
drive_labour_cost_per_hour = 200
Worksheet Resources:
WR1: Resource "Pile Rig (12-tonne hammer)" @ $1,200/day, qty = (total_drive_hours / 8) [days]
WR2: Resource "Rig Driver" @ $200/day, qty = (total_drive_hours / 8) [days]
WR3: Resource "Pile Mobilisation (all-in)" @ $8,000/project, qty = 1
Calculation Block:
total_cost = (sum of WR costs) + pile-specific allowance per pile
Result: $X per pile
Example instantiation:
Worksheet Recipe: "Pile Drive"
Input Param "Pile Depth" = 18 (metres)
Input Param "Number of Piles" = 24 (piles)
Input Param "Pile Diameter" = 600 (mm)
→ Rate computed for all 24 piles
→ Contribution = rate_per_pile × 24
Example D — Recipe Promoted from an Item
Workflow showing promotion:
Initial state: Item "Concrete pier cap assembly" in Estimate A
Worksheet contains:
WR1: "Concrete 32MPa" @ $360/m³, qty = 1.5 m³
WR2: "Reinforcement" @ $2,400/tonne, qty = 0.09 tonne
WR3: "Formwork (hire)" @ $12/m², qty = 8 m²
WR4: "Placement crew (3 days)" @ $900/day, qty = 3
Estimator decides: "We'll use this crew composition across many projects."
Action: Estimator opens Item's Worksheet and selects "Promote to Recipe" from menu.
Wizard flow:
1. Name the Recipe: "Concrete Pier Cap Assembly (1.5m³, 6m² forming)"
2. Define Output Unit: select "pier cap" (or "LS" if count isn't preferred; could be m³)
3. Extract Input Parameters:
- "Pier Cap Quantity" (qty of 1.5 m³ per cap → becomes input parameter)
- "Forming Area per Cap" (m² per cap → becomes input parameter)
- "Labour Days" (fixed at 3 days? Or parameterised? Estimator decides)
4. Review Worksheet: auto-populated from Item's Worksheet
5. Save as Recipe
Result: New Recipe in Library, ready to be dropped into Estimates B, C, D, etc.
When used: different projects parameterise with their own Quantity and Forming Area