1. Purpose
An Item is the unit of priceable work — what the client buys. It’s the bridge between scope (what’s being done) and cost (what we spend). Items live inside the Estimate tree, nest hierarchically, and own a Worksheet that derives their rate.
Everything in oxFlow’s cost model ultimately connects to Items:
- Schedule Items → the lines the client sees priced
- Normal Items → internal build-up that rolls up into Schedule Items
- Risk Items → contingency lines tracked discretely
- Etc.
The Item is structurally simple (description, unit, quantity, rate) but behaviourally rich: its Item Type changes what it’s allowed to do, its Item Flags modify treatment, its Worksheet carries its methodology, and its position in the tree (Heading or parent Item) determines its direct-vs-indirect classification.
Analogy (glossary cooking model): the Item is the cake — the final produced thing the client pays for.
2. Attributes
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
description | string (short text) | ✅ | — | What this Item represents. Shown to the client when Schedule Item |
code | string (free text) | ❌ | null | The “schedule code” — free-form identifier, client-facing, not the same as Code (structured classifier). For Schedule Items this is typically the client’s BOQ line number |
unit | Unit ref | ✅ | — | One of the managed Unit library (e.g. m³, hr, LS). See Unit glossary |
quantity | number | conditional | null | Required for all Item Types except Rate-Only. Must be ≥ 0 |
item_type | enum | ✅ | Normal | Normal · Schedule · Provisional Sum · Rate-Only · Excluded · Included Elsewhere · Risk. See §3 |
item_flags | multi-select | ❌ | [] | Zero or more of: Indirect Cost · Inactive. See §4 |
status | enum | ✅ | Unpriced | Unpriced · Plugged · Priced · Reviewed · Locked. See §5. Mostly derived from Worksheet content + plug_rate presence |
plug_rate | number | ❌ | null | Item-level direct rate (bypasses Worksheet build-up). When set and no Worksheet Resources/Recipes contribute cost → Item is Plugged. Cleared automatically when Worksheet gets Resources/Recipes |
estimator_id | User ref | ❌ | null | Assigned Estimator (informational only; v1 runs a flat access model — any User with Estimator role can act on any Estimate) |
parent_type | enum | ✅ | — | Heading or Item |
parent_id | UUID | ✅ | — | Points at Heading or Item depending on parent_type |
display_order | integer | ✅ | auto | Order within parent |
categorization_options | many-to-many | ❌ | [] | Multi-select Categorization Option references (Items can be tagged against any Categorization whose scope includes Item) |
workcentre_option_id | Code Option ref | see §7 | null | The required Workcentre Code Option (the Code applied to Items, per BR-125 and the Benchmark migration mapping) |
notes | long text | ❌ | null | Item-level notes — free text |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
Derived attributes (computed, not stored) — see §9.
3. Item Types
Item Type changes behaviour, not just appearance. One Type per Item (enum, not multi-select).
| Type | Purpose | Nesting rules | In submission? | Notes |
|---|---|---|---|---|
| Normal | Internal cost build-up line | Can nest under Schedule or Normal parent; sub-items allowed | ❌ | Default Type. Supports Inactive flag. Contributes cost to parent Schedule Item’s rate |
| Schedule | Client-facing submission line | Must sit at highest level of its branch (BR-003/004). Cannot nest under another Schedule | ✅ | Has a Submission Value. Can have sub-items (Normal). |
| Provisional Sum | Allowance with special commercial treatment | Schedule-only | ✅ (flagged) | Proposed: toggleable on/off in totals (rule not yet catalogued). Anomaly Review flags as “provisional active” |
| Rate-Only | Unit rate with no committed quantity (measure-and-value) | Schedule-only | ✅ | quantity is null; rate quoted per Unit |
| Excluded | Schedule line marked out-of-scope | Schedule-only | ✅ (as “Excluded”) | No cost contribution; client-facing signal |
| Included Elsewhere | Schedule line whose cost is covered by another line | Schedule-only | ✅ (as “Included Elsewhere”) | No cost contribution |
| Risk | Contingency / risk allowance | Can nest under any parent | Configurable | Always treated as Indirect Cost by default. Surfaced separately in Anomaly Review and portfolio reporting (BR-019) |
Open: Provisional and Risk interaction — can a Risk Item also be Provisional? Current stance: no (enum), enforce mutual exclusion.
4. Item Flags
Flags are multi-select modifiers that don’t redefine Type but add behaviour.
| Flag | Applies to | Purpose | Behaviour |
|---|---|---|---|
| Indirect Cost | Any Item Type | Override the structural direct/indirect derivation | When true, Item is treated as indirect for Commercials and reporting regardless of tree position (BR-016) |
| Inactive | Normal Items only | Soft-disable without deletion | Contributes $0 to roll-up; UI renders greyed / struck-through; preserves record and history (BR-018) |
Open: future flag candidates (not in v1): Escalation-applicable, Currency-sensitive, Audit-flagged.
5. Lifecycle States
| State | Meaning | Set by |
|---|---|---|
| Unpriced | Item has no pricing information — empty Worksheet (no Resources, Recipes, or sub-Items contributing cost) AND no plug_rate set. Default on create. | Derived |
| Plugged | Item has a direct plug_rate set, but the Worksheet has no Resources/Recipes. The rate comes from estimator judgement, not a build-up. | Derived (from plug_rate + empty Worksheet) |
| Priced | Item’s rate is derived from a real Worksheet build-up — one or more Worksheet Resources, Recipes, or sub-Items contributing cost. | Derived (from Worksheet content) |
| Reviewed | Priced + senior review complete. No outstanding Anomaly flags blocking. | Manual — Lead Estimator / Admin |
| Locked | Parent Estimate has been Submitted. No edits allowed (BR-094). | Automatic cascade from Estimate state |
Key distinction — Plugged vs. Plug Rate:
- Plugged is an Item-level status meaning “the rate on this Item was entered directly, not built up from Resources.”
- Plug Rate is a separate Resource-level flag meaning “this Resource is a placeholder / rough number.” A Priced Item (with a full Worksheet build-up) can perfectly well contain Resources that happen to be flagged as Plug Rate — those get flagged in Anomaly Review, but the Item remains Priced.
In other words: the presence of Plug Rate Resources in a Worksheet doesn’t change the Item’s status. What determines Plugged vs Priced is whether the Item’s rate comes from a build-up or a direct entry.
State transitions:
| From | To | Trigger |
|---|---|---|
| Unpriced | Plugged | Estimator enters a plug_rate on the Item |
| Unpriced | Priced | Estimator adds first Worksheet Resource/Recipe/sub-Item that contributes cost |
| Plugged | Priced | Estimator adds first Worksheet Resource/Recipe (plug_rate is cleared automatically — see validation §8) |
| Plugged | Unpriced | Estimator clears the plug_rate |
| Priced | Plugged | Estimator removes all Worksheet build-up AND sets a plug_rate |
| Priced | Unpriced | Estimator removes all Worksheet build-up (no plug_rate entered) |
| Priced | Reviewed | Lead Estimator / Admin reviews the Item (manual) |
| Reviewed | Priced | Lead Estimator / Admin re-opens the Item (manual) |
| Reviewed | Priced | Rate change on any Worksheet Resource within the Item’s Worksheet (by per-instance override, bulk override, or fork) |
| Any | Locked | Parent Estimate transitions to Submitted (automatic cascade) |
Note: re-open from Reviewed always goes back to Priced — not further back. Estimator can then manually remove Worksheet content to drop to Plugged or Unpriced if needed, but that’s a separate action.
Automatic Reviewed → Priced cascade on rate change (BR-049a): when any Worksheet Resource’s effective rate changes via one of the three rate-edit mechanisms (BR-047 per-instance, BR-048 bulk Apply-to-Estimate, BR-049 fork to new Resource), any affected Item currently in Reviewed status is automatically dropped back to Priced. The senior review is considered no longer current. Locked items are unaffected (they’re protected by parent Estimate lock, BR-094).
Submission-block behaviour:
An Estimate cannot be Submitted while any of its Items are in Unpriced or Plugged state. Estimators can still navigate to the Publisher / submission page to preview the output — the Submit action itself is blocked with a clear anomaly list showing which Items still need proper pricing. This preview-but-block pattern lets estimators see their WIP state without accidentally submitting rough numbers.
States that allow Estimate submission: Priced, Reviewed, Locked.
Open: review transition events — triggered purely by manual flip, or gated by “no Layer 1 anomalies on this Item”? Confirm with Oxcon.
6. Relationships
Inbound (things referring to Item)
| From | Cardinality | Notes |
|---|---|---|
| Heading | Heading 1:M Item | When Item is a root-level Item under a Heading |
| Item (parent) | Item 1:M Item | When Item is a sub-item |
| Worksheet Resource | WR M:1 Item | Via parent Worksheet |
| Worksheet Recipe | WRec M:1 Item | Via parent Worksheet |
| Program Task | PT M:M Item | Via link (BR-102/103); exposes duration as Variable on Item’s Worksheet |
| Subcontract Package | SP M:M Item | Package membership |
| Submission Value | SV 1:1 Item | Schedule Items only |
Outbound (things Item references)
| To | Cardinality | Required | Notes |
|---|---|---|---|
| Worksheet | Item 1:1 Worksheet | ✅ | Every Item has exactly one (BR-005). Sparse if simple |
| Unit | Item M:1 Unit | ✅ | Required |
| Item (self, parent) | Item M:0..1 Item | see §7 | Self-referential; alternative to Heading parent |
| Heading | Item M:0..1 Heading | see §7 | Alternative to Item parent |
| User (Estimator) | Item M:0..1 User | ❌ | Informational |
| Categorization Option | Item M:M CatOption | ❌ | Multi-select |
| Code Option (Workcentre) | Item M:0..1 CodeOption | tiered (BR-127) | Soft at Estimate (nullable); hard at Cost Mgmt export |
7. Validation / Invariants
Rules that must hold at all times:
- Parent exactly one type.
parent_typemust beHeadingorItem, never both, never null. - Schedule Item placement. If
item_type = Schedule, there must be no ancestor Item (anywhere above) withitem_type = Schedule. Validated on create and on move. - Provisional / Rate-Only / Excluded / Included Elsewhere → Schedule only. Enforced at write time.
- Inactive → Normal only. Enforced at write time.
- Risk → Indirect. If
item_type = Risk, derivedis_indirectmust be true regardless of structural position. - Quantity requirement.
quantitymust be non-null and ≥ 0 for all Item Types except Rate-Only. Rate-Only must have null quantity. - Unit mandatory.
unitcannot be null. - Worksheet 1:1. Every Item has a Worksheet, created atomically when the Item is created. No orphaned Worksheets (cascade delete).
- Depth cap. Item’s depth through ancestor Items must be ≤ 5 (BR-002).
- Tree integrity. No circular parent chains.
- Workcentre Code requirement. Soft-enforced in Estimate working view; hard-enforced at Cost Management export (BR-127).
- Submission readiness. An Estimate can only transition to Submitted when every Item’s status ∈ {Priced, Reviewed, Locked}. Items in Unpriced or Plugged block submission (but not navigation to the Publisher preview).
- Plugged / Priced mutual exclusion. If Worksheet has any Resources/Recipes/sub-Items contributing cost → status is
Priced(or further). Ifplug_rateis set AND Worksheet has no cost-contributing children → status isPlugged. Both cannot be true simultaneously — setting one clears the other. - Plug rate requires empty Worksheet cost-contribution.
plug_rateis null when the Worksheet has any cost-contributing children. Validation enforces this (either by rejecting the plug_rate entry or by auto-clearing on Resource add).
8. Derived / Computed Attributes
| Attribute | Derivation | Notes |
|---|---|---|
total_cost | Sum of: Worksheet Resources (qty × snapshot_rate) + sum of sub-Items recursive + sum of Worksheet Recipes evaluated | Recomputed when any child changes |
unit_cost | total_cost / quantity when quantity > 0; null for Rate-Only | |
is_indirect | (item_type != Schedule AND no Schedule ancestor) OR Indirect Cost flag set OR item_type = Risk | Cross-references BR-015, BR-016, BR-019 |
depth | Count of Item ancestors (not Heading ancestors) | For depth cap validation |
is_schedule_descendant | Exists a Schedule Item in ancestor chain | Used by BR-003 enforcement |
has_anomalies | True if any Anomaly Review check fires on this Item | UI badge |
is_submission_ready | status ∈ {Priced, Reviewed, Locked} | Used to gate Estimate Submit action |
has_plug_rate_resources | True if any Worksheet Resource has the Plug Rate flag | Separate from Item status; flagged in Anomaly Review but doesn’t change Priced/Plugged classification |
| Derived status | Unpriced / Plugged / Priced derived from Worksheet content + plug_rate presence (see §5 transitions); Reviewed / Locked are manual/cascaded states built on top | The status attribute stores the committed state (to preserve Reviewed / Locked); transitions between Unpriced/Plugged/Priced update it automatically when the Worksheet or plug_rate changes |
9. Worked Examples
Example A — Simple Schedule Item (sparse)
An Item imported from a client’s BOQ with a subcontract rate plugged in:
Item:
description: "Supply and place 32MPa concrete to bridge pier caps"
code: "03.12.01" (client's BOQ line)
unit: m³
quantity: 25
item_type: Schedule
item_flags: []
status: Priced
parent_type: Heading
parent_id: <heading "03. Concrete Works">
workcentre_option_id: <"Concrete" workcentre>
Worksheet (sparse):
1 Worksheet Resource:
resource: "Subcontract — Brown's Concrete" (Resource Type: Subcontract)
snapshot_rate: $460/m³
quantity: 25
notes: "Price from Brown's 14 Apr quote"
Derived:
total_cost = 25 × $460 = $11,500
unit_cost = $460/m³
is_indirect = false
depth = 0
Example B — Schedule Item with internal build-up (sub-items)
Item A (Schedule): "Concrete pile caps"
unit: no (number of caps)
quantity: 12
status: Reviewed
Sub-Item A.1 (Normal): "Concrete supply" → 1.1 m³ per cap × 12
Sub-Item A.2 (Normal): "Concrete place crew" → 0.5 hr per cap × 12
Sub-Item A.3 (Normal): "Reinforcement" → 90 kg per cap × 12
Derived:
A.total_cost = A.1.total + A.2.total + A.3.total = (say) $18,720
A.unit_cost = $1,560 per cap
A is_indirect = false
A.1/2/3 is_indirect = false (structurally direct via Schedule ancestor)
Example C — Risk Item
Item: "Weather contingency — earthworks phase"
unit: LS
quantity: 1
item_type: Risk
item_flags: [] (Indirect Cost derived = true because type=Risk)
status: Priced
parent_type: Heading
parent_id: <heading "Risks & Contingencies">
workcentre_option_id: <"Earthworks" workcentre>
Worksheet (sparse):
1 Worksheet Resource:
resource: "Risk allowance (manual)"
snapshot_rate: $12,000
quantity: 1
Derived:
total_cost = $12,000
is_indirect = true (by BR-019)
flagged in Anomaly Review as a Risk Item (traceability)
Example D — Plugged Item (direct rate, no build-up)
Estimator has a rough number in mind but hasn’t built up the detail yet:
Item:
description: "Temporary works — site hoardings"
unit: LS
quantity: 1
item_type: Schedule
item_flags: []
plug_rate: 18000 ← direct entry at Item level
status: Plugged ← derived from plug_rate set + empty Worksheet
parent_type: Heading
parent_id: <heading "Preliminaries">
workcentre_option_id: <"General Preliminaries" workcentre>
Worksheet: empty (no Resources, no Recipes, no sub-Items)
Derived:
total_cost = 18,000 (uses plug_rate since Worksheet is empty)
unit_cost = $18,000/LS
is_indirect = false (Schedule Item)
is_submission_ready = false (Plugged blocks submission)
flagged in Anomaly Review: "Item is Plugged — no build-up"
Estimator next steps:
- Option A: leave Plugged, submit Estimate after replacing with real numbers
- Option B: add Worksheet Resources (e.g., fence panels, labour, install)
→ plug_rate auto-clears, status auto-flips to Priced
Example E — Priced Item with Plug Rate Resources (distinct from Plugged)
An Item with a full Worksheet build-up, but one of the Resources inside is flagged as a Plug Rate placeholder. Item is Priced, not Plugged:
Item:
description: "Structural concrete columns"
unit: m³
quantity: 40
item_type: Schedule
status: Priced ← because the Worksheet has cost-contributing Resources
plug_rate: null
Worksheet:
3 Worksheet Resources:
1. "Concrete supply 32MPa" qty: 40 m³ snapshot_rate: $230/m³
2. "Concretor crew" qty: 8 hrs snapshot_rate: $420/hr
3. "Formwork" qty: 60 m² snapshot_rate: $95/m²
← This Resource is flagged in the Price Book as Plug Rate (estimator doesn't have the final supplier quote yet)
Derived:
total_cost = $9,200 + $3,360 + $5,700 = $18,260
is_submission_ready = true (Priced status is enough)
has_plug_rate_resources = true
flagged in Anomaly Review: "Worksheet contains a Plug Rate Resource" (but Item is not Plugged)
This example highlights why Plugged (Item status) and Plug Rate (Resource flag) are not the same — the Item here is submission-ready from a status perspective, but the anomaly review still surfaces the Plug Rate inside it for the estimator to resolve.
Example F — Inactive Item
Item: "Contingency — corrosion protection"
unit: m²
quantity: 430
item_type: Normal
item_flags: [Inactive] // decided not to include; retained for history
status: Priced
parent_type: Item
parent_id: <Item "External structural steel">
Derived:
total_cost = $0 (Inactive excludes from roll-up)
unit_cost = null (quantity > 0 but cost 0; displayed greyed)