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

AttributeTypeRequiredDefaultNotes
idUUIDgeneratedSystem-managed
descriptionstring (short text)What this Item represents. Shown to the client when Schedule Item
codestring (free text)nullThe “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
unitUnit refOne of the managed Unit library (e.g. , hr, LS). See Unit glossary
quantitynumberconditionalnullRequired for all Item Types except Rate-Only. Must be ≥ 0
item_typeenumNormalNormal · Schedule · Provisional Sum · Rate-Only · Excluded · Included Elsewhere · Risk. See §3
item_flagsmulti-select[]Zero or more of: Indirect Cost · Inactive. See §4
statusenumUnpricedUnpriced · Plugged · Priced · Reviewed · Locked. See §5. Mostly derived from Worksheet content + plug_rate presence
plug_ratenumbernullItem-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_idUser refnullAssigned Estimator (informational only; v1 runs a flat access model — any User with Estimator role can act on any Estimate)
parent_typeenumHeading or Item
parent_idUUIDPoints at Heading or Item depending on parent_type
display_orderintegerautoOrder within parent
categorization_optionsmany-to-many[]Multi-select Categorization Option references (Items can be tagged against any Categorization whose scope includes Item)
workcentre_option_idCode Option refsee §7nullThe required Workcentre Code Option (the Code applied to Items, per BR-125 and the Benchmark migration mapping)
noteslong textnullItem-level notes — free text
created_at / updated_attimestampsystemAudit
created_by / updated_byUser refsystemAudit

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).

TypePurposeNesting rulesIn submission?Notes
NormalInternal cost build-up lineCan nest under Schedule or Normal parent; sub-items allowedDefault Type. Supports Inactive flag. Contributes cost to parent Schedule Item’s rate
ScheduleClient-facing submission lineMust sit at highest level of its branch (BR-003/004). Cannot nest under another ScheduleHas a Submission Value. Can have sub-items (Normal).
Provisional SumAllowance with special commercial treatmentSchedule-only✅ (flagged)Proposed: toggleable on/off in totals (rule not yet catalogued). Anomaly Review flags as “provisional active”
Rate-OnlyUnit rate with no committed quantity (measure-and-value)Schedule-onlyquantity is null; rate quoted per Unit
ExcludedSchedule line marked out-of-scopeSchedule-only✅ (as “Excluded”)No cost contribution; client-facing signal
Included ElsewhereSchedule line whose cost is covered by another lineSchedule-only✅ (as “Included Elsewhere”)No cost contribution
RiskContingency / risk allowanceCan nest under any parentConfigurableAlways 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.

FlagApplies toPurposeBehaviour
Indirect CostAny Item TypeOverride the structural direct/indirect derivationWhen true, Item is treated as indirect for Commercials and reporting regardless of tree position (BR-016)
InactiveNormal Items onlySoft-disable without deletionContributes $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

StateMeaningSet by
UnpricedItem has no pricing information — empty Worksheet (no Resources, Recipes, or sub-Items contributing cost) AND no plug_rate set. Default on create.Derived
PluggedItem 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)
PricedItem’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)
ReviewedPriced + senior review complete. No outstanding Anomaly flags blocking.Manual — Lead Estimator / Admin
LockedParent 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:

FromToTrigger
UnpricedPluggedEstimator enters a plug_rate on the Item
UnpricedPricedEstimator adds first Worksheet Resource/Recipe/sub-Item that contributes cost
PluggedPricedEstimator adds first Worksheet Resource/Recipe (plug_rate is cleared automatically — see validation §8)
PluggedUnpricedEstimator clears the plug_rate
PricedPluggedEstimator removes all Worksheet build-up AND sets a plug_rate
PricedUnpricedEstimator removes all Worksheet build-up (no plug_rate entered)
PricedReviewedLead Estimator / Admin reviews the Item (manual)
ReviewedPricedLead Estimator / Admin re-opens the Item (manual)
ReviewedPricedRate change on any Worksheet Resource within the Item’s Worksheet (by per-instance override, bulk override, or fork)
AnyLockedParent 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)

FromCardinalityNotes
HeadingHeading 1:M ItemWhen Item is a root-level Item under a Heading
Item (parent)Item 1:M ItemWhen Item is a sub-item
Worksheet ResourceWR M:1 ItemVia parent Worksheet
Worksheet RecipeWRec M:1 ItemVia parent Worksheet
Program TaskPT M:M ItemVia link (BR-102/103); exposes duration as Variable on Item’s Worksheet
Subcontract PackageSP M:M ItemPackage membership
Submission ValueSV 1:1 ItemSchedule Items only

Outbound (things Item references)

ToCardinalityRequiredNotes
WorksheetItem 1:1 WorksheetEvery Item has exactly one (BR-005). Sparse if simple
UnitItem M:1 UnitRequired
Item (self, parent)Item M:0..1 Itemsee §7Self-referential; alternative to Heading parent
HeadingItem M:0..1 Headingsee §7Alternative to Item parent
User (Estimator)Item M:0..1 UserInformational
Categorization OptionItem M:M CatOptionMulti-select
Code Option (Workcentre)Item M:0..1 CodeOptiontiered (BR-127)Soft at Estimate (nullable); hard at Cost Mgmt export

7. Validation / Invariants

Rules that must hold at all times:

  1. Parent exactly one type. parent_type must be Heading or Item, never both, never null.
  2. Schedule Item placement. If item_type = Schedule, there must be no ancestor Item (anywhere above) with item_type = Schedule. Validated on create and on move.
  3. Provisional / Rate-Only / Excluded / Included Elsewhere → Schedule only. Enforced at write time.
  4. Inactive → Normal only. Enforced at write time.
  5. Risk → Indirect. If item_type = Risk, derived is_indirect must be true regardless of structural position.
  6. Quantity requirement. quantity must be non-null and ≥ 0 for all Item Types except Rate-Only. Rate-Only must have null quantity.
  7. Unit mandatory. unit cannot be null.
  8. Worksheet 1:1. Every Item has a Worksheet, created atomically when the Item is created. No orphaned Worksheets (cascade delete).
  9. Depth cap. Item’s depth through ancestor Items must be ≤ 5 (BR-002).
  10. Tree integrity. No circular parent chains.
  11. Workcentre Code requirement. Soft-enforced in Estimate working view; hard-enforced at Cost Management export (BR-127).
  12. 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).
  13. Plugged / Priced mutual exclusion. If Worksheet has any Resources/Recipes/sub-Items contributing cost → status is Priced (or further). If plug_rate is set AND Worksheet has no cost-contributing children → status is Plugged. Both cannot be true simultaneously — setting one clears the other.
  14. Plug rate requires empty Worksheet cost-contribution. plug_rate is 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

AttributeDerivationNotes
total_costSum of: Worksheet Resources (qty × snapshot_rate) + sum of sub-Items recursive + sum of Worksheet Recipes evaluatedRecomputed when any child changes
unit_costtotal_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 = RiskCross-references BR-015, BR-016, BR-019
depthCount of Item ancestors (not Heading ancestors)For depth cap validation
is_schedule_descendantExists a Schedule Item in ancestor chainUsed by BR-003 enforcement
has_anomaliesTrue if any Anomaly Review check fires on this ItemUI badge
is_submission_readystatus ∈ {Priced, Reviewed, Locked}Used to gate Estimate Submit action
has_plug_rate_resourcesTrue if any Worksheet Resource has the Plug Rate flagSeparate from Item status; flagged in Anomaly Review but doesn’t change Priced/Plugged classification
Derived statusUnpriced / Plugged / Priced derived from Worksheet content + plug_rate presence (see §5 transitions); Reviewed / Locked are manual/cascaded states built on topThe 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)