1. Purpose

A Unit is a measurement label — the dimension in which an Item’s quantity, a Resource’s rate, a Recipe’s Output Unit, a Reference Rate, or a Worksheet Variable is expressed. Units are drawn from a managed library that admins can extend; every Item, Resource, Recipe Output, Reference Rate, and many Variables must reference a Unit.

oxFlow does not auto-convert between Units in v1 (BR-111). When a Resource’s Unit differs from the host Item’s Unit, the estimator performs the conversion explicitly inside the Worksheet (as a Variable or Calculation Block). Unit mismatches are flagged by Anomaly Review (BR-112) for review before Commercials.

Analogy (glossary cooking model): the Unit is the measure — cup, gram, pinch — what you’re counting in. Recipes don’t auto-convert from cups to grams; the cook does it.


2. Attributes

AttributeTypeRequiredDefaultNotes
idUUIDgeneratedSystem-managed
symbolstring (short text)Short display symbol — what appears in tables and rates (e.g., m, , , hr, day, LS)
display_namestring (text)Full human-readable name (e.g., “metre”, “square metre”, “cubic metre”, “hour”, “day”, “lump sum”)
is_built_inbooleanfalseTrue for the seeded standard set; false for admin-added custom units. Built-in units cannot be deleted
categoryenumnullOptional grouping for the Unit picker UI (e.g., Length / Area / Volume / Time / Mass / Count / Currency / Other)
descriptionlong textnullAdmin notes — when to use this Unit, conversion guidance, regional convention
created_at / updated_attimestampsystemAudit
created_by / updated_byUser refsystemAudit. created_by = system for built-ins; admin User for custom additions

Derived attributes (computed, not stored) — see §8.

Note on conversions: Units do not carry conversion factors in v1. Auto-conversion is intentionally deferred (see §10 Open items). The category attribute is for UI grouping only — it does not enable cross-Unit math.


3. Built-in Unit set

The seeded standard set covers common construction-estimating Units. Admin can extend (BR-113) but cannot delete built-ins.

SymbolDisplay nameCategoryNotes
mmetreLength
square metreArea
cubic metreVolume
lmlinear metreLengthOften used for piles, beams
mmmillimetreLengthSpec-detail; rarely used as Item Unit
kgkilogramMass
ttonneMass
hrhourTime
daydayTime
wkweekTime
mthmonthTime
eaeachCountGeneric count unit
nonumberCountSynonym for ea; both kept for estimator preference
LSlump sumCurrency-equivalentUsed for any non-quantity-driven Item or Resource (BR-110)
kmkilometreLengthCartage / travel modifiers

Notes:

  • LS is a normal Unit — nothing special structurally. It just means “no unit-rate math; the rate IS the total” (BR-110).
  • ea vs no — both are kept in the built-in library because Oxcon estimators historically use them interchangeably; merging would force migration of legacy data. See migration-benchmark.md §3.10 for mapping.

4. Lifecycle States

Units have no lifecycle state machine. Once created they persist; built-in Units cannot be deleted. Custom Units can be soft-deleted (archived) via admin — archived Units remain readable on historical entities that used them but are not selectable for new entries (mirrors Categorization soft-delete pattern, BR-120a).


5. Relationships

Inbound (things referring to Unit)

FromCardinalityNotes
ItemItem M:1 UnitRequired (BR-010)
ResourceResource M:1 UnitRequired (BR-020)
RecipeRecipe M:1 Unit (as output_unit)Required (BR-030)
Input ParameterInput Parameter M:1 UnitRequired (every Input Parameter has a Unit)
VariableVariable M:0..1 UnitOptional; estimator may declare a Unit on a Variable for clarity
Reference RateReference Rate M:1 UnitRequired (implicit in the Reference Rate attribute list; BR-144 defines Reference Rates as admin-defined benchmarks, non-versioned)
Program TaskProgram Task — implicit Unit (day for duration)Not stored as FK; duration Unit is conventionally day

Outbound (things Unit references)

Units have no outbound references — they are pure leaf entities in the reference-data layer.


6. Validation / Invariants

Rules that must hold at all times:

  1. Symbol unique. No two Units may share the same symbol (case-sensitive). Enforced at write time.
  2. Display name required. display_name cannot be null or empty.
  3. Symbol required. symbol cannot be null or empty.
  4. Built-in protection. Units with is_built_in = true cannot be deleted, archived, or have their symbol/display_name changed via the admin UI. Description edits allowed.
  5. Soft-delete only. Custom Units use archive (soft-delete); existing entities referencing them retain the reference. Hard delete is not supported.
  6. Cannot delete with references. Even custom Units cannot be hard-deleted while referenced by any Item, Resource, Recipe, or Reference Rate (archive is the only path).

7. Derived / Computed Attributes

AttributeDerivationNotes
usage_countCount of Items + Resources + Recipes (output) + Reference Rates referencing this UnitInformational; helps admins decide whether a custom Unit is in use
is_archivableis_built_in = false AND usage_count = 0Only archivable if custom and unused

8. Worked Examples

Example A — Built-in Unit (m³)

A standard volume Unit used across the estimating model:

Unit:
  symbol: "m³"
  display_name: "cubic metre"
  is_built_in: true
  category: Volume
  description: "Standard SI volume unit. Used for concrete, earthworks, bulk materials."

Usage:
  - Item "Concrete pour — pile caps" (quantity: 18 m³)
  - Resource "Concrete supply 32MPa" (rate: $230/m³)
  - Recipe "Concrete Pump" output_unit: m³ (alternative — could be `day`)
  - Reference Rate "Reinforced concrete works" (range: $450–550/m³)

Derived:
  usage_count = (varies by workspace; typically high for built-in volume Units)
  is_archivable = false (built-in)

Example B — Admin-added custom Unit

Admin adds a project-specific Unit for an unusual measurement:

Unit (admin-created):
  symbol: "truckload"
  display_name: "truckload (8m³ tipper)"
  is_built_in: false
  category: Volume
  description: "Project-specific. Used for spoil-removal estimates on Bridge Project. Each truckload = ~8 m³ loose."
  created_by: admin@oxcon.local

Usage:
  - Item "Spoil cartage to off-site disposal" (quantity: 250 truckload)
  - Resource "Tipper truck — 8m³" (rate: $180/truckload)

Anomaly Review:
  No flag — both Item and Resource use the same Unit. Production rate logic in
  Worksheet handles the conversion to volume if needed (manual per BR-111).

Derived:
  usage_count = 2
  is_archivable = false (in use)

Example C — Unit mismatch flagged by Anomaly Review (BR-112)

Item uses but its Worksheet contains a Resource priced in hr with no time-to-area bridging:

Item "External cladding — install":
  unit: m²
  quantity: 320

Worksheet:
  Worksheet Resource: "Cladder day rate" (Resource Unit: hr, snapshot_rate: $85/hr)
    quantity: ?  ← estimator entered 220 (hours)

  No Variable bridging hours → m² (no Production Rate, no Calculation Block
  converting time-to-area).

Anomaly Review (BR-112 heuristic):
  ⚠️ "Unit mismatch: Item priced in m² but Worksheet contains only hr-based
      Resources with no time-to-area conversion. Confirm conversion logic."

Estimator action:
  Add Variable: install_rate = 1.45 m²/hr (manual Production Rate)
  Add Calculation: implied_area = qty × install_rate = 220 × 1.45 = 319 m²
  Cross-check vs Item quantity (320 m²) — within 0.3%, accepted.