1. Purpose

A Price Book is a named collection of Resources, the container from which estimators source rates during estimate build-up. Every Price Book has a Price Book Type (Project-Specific, Internal, External), a scope (date range, region, project), and a source type (user-created or system-generated). Resources live inside Price Books and inherit their scope.

Price Books serve three distinct roles in oxFlow:

  • User-created Price Books — manually curated reference rate collections, supplier-scoped or company-internal
  • System-generated Price Books — automatically created by Subcontract Package Adjudications on award; hidden from default UI; read-only via Price Book UI
  • Archived Price Books — preserved historical rate windows from Benchmark migration; queryable by Anomaly Review and AI Review Assistant

Analogy (glossary cooking model): the Price Book is the grocery store catalogue — the collection of available ingredients with their rates.


2. Attributes

AttributeTypeRequiredDefaultNotes
idUUIDgeneratedSystem-managed
namestring (text)Human-readable name, e.g., “Suppliers — Steel Ltd (May–Aug 2026)” or “In-House Labour Rates — Q2 2026”
price_book_typeenumProject-Specific · Internal · External. See §3
source_typeenumuseruser (user-created) · adjudication (system-generated). See §4
statusenumActiveActive · Archived. See §5
scope_start_datedateStart of validity window (e.g., 01 May 2026). Every Price Book has a scope date range
scope_end_datedateEnd of validity window (e.g., 31 Aug 2026). Must be ≥ scope_start_date
scope_regionstring (optional)nullGeographic scope if applicable (e.g., “Auckland”, “North Island”). Optional; Resources may carry finer regional tags via Categorization
project_idProject refnullRequired if price_book_type = Project-Specific. Links to a single Tender/Estimate for project-scoped rates
supplier_idCompany refconditionalnullRequired if price_book_type = External (must be Company with Supplier role). Null for Internal and Project-Specific types (BR-050/051/052)
adjudication_idSubcontract Package Adjudication refnullFor system-generated Price Books: pointer to the Adjudication that produced it (lineage; BR-066). Null for user-created
descriptionlong textnullNotes on scope, supplier terms, applicability (e.g., “Regular supplier terms; includes cartage to metro area only”)
is_hidden_from_uibooleanfalseIf true, excluded from default Price Book list (applies to system-generated; BR-054). Admin/Power User can show via toggle
created_at / updated_attimestampsystemAudit
created_by / updated_byUser refsystemAudit

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


3. Price Book Types

Type changes supplier requirement and scope constraints, not core behaviour. One Type per Price Book (enum, not multi-select).

TypePurposeSupplier required?Project-link required?Typical use case
ExternalTied to a supplier; supplier-specific rates“Concrete Co rates (May–Aug 2026)”, “Hire Co plant schedule (Q2)“
InternalIn-house reference rates, no supplier“In-House Labour Rates — May 2026”, “Our standard material costs (May)“
Project-SpecificScoped to a Tender (label uses “Project” for industry convention); no supplier“Site-specific rates for Project Acme”, “Client’s preferred subcontractor rates for Acme”

Note: Multiple Price Books per Supplier for the same date range are allowed (e.g., different product categories; e.g., “Steel Ltd — Rebar” and “Steel Ltd — Structural Sections”); names must be unique workspace-wide.

BR-050 — External Price Books must have a Supplier. When price_book_type = External, supplier_id must be non-null and point to a Company with Supplier role. Enforced at write time.

BR-051 — Internal Price Books do not require a supplier. supplier_id must be null.

BR-052 — Project-Specific Price Books do not require a supplier but must be scoped to a Tender. supplier_id must be null; project_id (Tender ref) must be non-null and valid.


4. Source Type (User-Created vs System-Generated)

Source Type determines origin, editability, and visibility. One per Price Book (enum, not multi-select).

SourceOriginVisibilityEditabilityLineage
user (user-created)Manually curated by Estimator / Admin / Lead Estimator via Price Book UIDefault view (shown in Price Book list)Full: can add/edit/delete ResourcesNone (origin is manual entry)
adjudication (system-generated)Automatically created by Subcontract Package Adjudication on award (BR-063)Hidden from default list (togglable via “show system-generated”); navigable via Adjudication (BR-054)Read-only via Price Book UI; updated only by re-adjudicationLineage via Price Book’s adjudication_id (BR-066)

BR-054 — System-generated Price Books are hidden from the default Price Book list. The default Price Book dropdown in Estimate build-up excludes system-generated Price Books to reduce clutter. Admin or Power User can toggle “show system-generated” to browse and reference them. System-generated Price Books are always accessible via their owning Adjudication.

Adjudication lineage (BR-066): Every system-generated Price Book retains adjudication_id pointing to the Subcontract Package Adjudication that created it. Estimators can trace a Subcontract Resource’s cost back to the award; the Adjudication shows the winning supplier, round number, and decision date.


5. Lifecycle States

StateMeaningWhen usedWho can transition
ActiveCurrent, in-use ratesNew Price Books start here; used for Resource reference during estimate build-upEstimator, Lead Estimator, Admin
ArchivedSuperseded or historical; preserved for reference and audit; queryable by Anomaly Review (Release milestone)Rates are no longer current but should be retained (e.g., historical rate windows from Benchmark migration); price book has exceeded its scope_end_dateLead Estimator, Admin (manual archive); Auto-archive on scope_end_date expiry

State transitions:

Active ──manual archive or scope_end_date < today (auto)──> Archived
Archived ──unarchive──> Active (infrequent; reactivate if needed for historical reference)

Auto-archive behaviour (BR-053a): When a Price Book’s scope_end_date < today, the Price Book automatically transitions to Archived status (logical check at query time). Archived Price Books are read-only and not offered for new Worksheet Resource creation; existing Worksheet Resources continue to reference them with snapshot semantics. Estimators can still view and reference archived Price Books.


6. Relationships

Inbound (things referring to Price Book)

FromCardinalityNotes
ResourceResource M:1 Price BookEvery Resource lives in exactly one Price Book (BR-021)
Subcontract Package AdjudicationSPA M:0..1 Price BookSystem-generated Price Books only (BR-063, BR-066); points back via adjudication_id
Price Book AdjudicationPBA M:1 Price Book (implicit scope)Adjudication scopes over Resources from Estimates, which live in Price Books; indirect relationship

Outbound (things Price Book references)

ToCardinalityRequiredNotes
Company (Supplier)Price Book M:0..1 Companyconditional (BR-050)Required if External; null for Internal and Project-Specific
Tender / Estimate (Project)Price Book M:0..1 Tenderconditional (BR-052)Required if Project-Specific; null for others
ResourcePrice Book 1:M ResourceEvery Price Book contains zero or more Resources (M:1 from Resource side)
Subcontract Package AdjudicationPrice Book 1:0..1 SPASystem-generated only: each system-gen PB is produced by exactly one SPA, and each SPA produces at most one PB. Lineage pointer (BR-066)

7. Validation / Invariants

Rules that must hold at all times:

  1. Type consistency. price_book_type must be one of: Project-Specific, Internal, External (enum). Enforced at write time.

  2. Scope date range. scope_start_datescope_end_date; both are required and non-null. Validated on create and update.

  3. External Price Books require supplier. If price_book_type = External, then supplier_id must be non-null and point to a valid Company with Supplier role (BR-050).

  4. Internal Price Books forbid supplier. If price_book_type = Internal, then supplier_id must be null.

  5. Project-Specific Price Books require Tender link. If price_book_type = Project-Specific, then project_id (Tender ref) must be non-null and valid (BR-052).

  6. Project-Specific forbids supplier. If price_book_type = Project-Specific, then supplier_id must be null.

  7. Source type enum. source_type must be one of: user, adjudication. Enforced at write time.

  8. System-generated lineage. If source_type = adjudication, then adjudication_id must be non-null and point to a valid Subcontract Package Adjudication. source_type = user Price Books must have adjudication_id = null.

  9. Status enum. status must be one of: Active, Archived. Enforced at write time.

  10. Auto-archive trigger. When scope_end_date < today, status automatically transitions to Archived. Logic applied at query time.

  11. Resource ownership. No orphaned Resources: every Resource must belong to exactly one Price Book. Cascade delete: if a Price Book is deleted, all its Resources are deleted (with audit trail). Resources cannot carry scope attributes (date range, region, Tender link) that contradict their parent Price Book; Resources inherit Price Book scope.

  12. Name uniqueness. Price Book names must be unique workspace-wide. Allows same Supplier to have multiple Price Books for same date range if names differ (e.g., “Steel Ltd — Rebar” vs “Steel Ltd — Structural Sections”).

  13. Tuple uniqueness (relaxed). Tuples (name, supplier_id, scope_start_date, scope_end_date) need not be unique (allows multiple PBs per Supplier); only name must be globally unique.


8. Derived / Computed Attributes

AttributeDerivationNotes
resource_countCount of Resources where price_book_id = this.idUseful for UI summary; recomputed on Resource add/delete
is_activestatus = Active AND scope_end_date >= todayTrue if currently valid (within date range and not manually archived)
is_in_scopescope_start_date <= today <= scope_end_dateTrue if today falls within validity window (independent of status)
supplier_namesupplier_id → company.name if supplier_id non-null, else nullConvenience for display; derived from supplier relationship
supplier_displayName of supplier (if External) or text “Internal” / “Project-Specific” (otherwise)For UI table cells showing supplier context

9. Worked Examples

Example A — External Price Book for a Supplier (user-created, current window)

A supplier-specific rate collection for a live project phase:

Price Book:
  name: "Suppliers — Steel Ltd (May–Aug 2026)"
  price_book_type: External
  source_type: user
  status: Active
  scope_start_date: 2026-05-01
  scope_end_date: 2026-08-31
  scope_region: "Auckland metro"
  supplier_id: <Company "Steel Ltd" with Supplier role>
  adjudication_id: null
  description: "Regular supplier. Supply prices; cartage incl. metro area only. Min order $500. Updated 28 Apr."
  is_hidden_from_ui: false

Resources (3):
  1. "Steel reinforcement 500MPa coil" — rate $1.25/kg — Labour/Material
  2. "Structural hollow section 200×100×5.6" — rate $145.00/ea
  3. "Welding & inspection certification" — rate $50.00/hour (Labour)

Derived:
  resource_count = 3
  is_active = true (today = 15 Apr 2026, within May 1–Aug 31 window)
  is_in_scope = false (today is before scope_start_date)
  supplier_name = "Steel Ltd"

Example B — Internal Price Book (in-house reference rates)

A company-maintained rate catalogue for standard labour and common materials:

Price Book:
  name: "In-House Labour Rates — Q2 2026"
  price_book_type: Internal
  source_type: user
  status: Active
  scope_start_date: 2026-04-01
  scope_end_date: 2026-06-30
  scope_region: null (applies nationwide)
  supplier_id: null
  adjudication_id: null
  description: "Standard company labour rates (inc. small tools, benefits). Applies to all projects in-region. Material cost — standard suppliers."
  is_hidden_from_ui: false

Resources (5):
  1. "Carpenter — general (includes small tools)" — rate $185.50/day
  2. "Labourer — general" — rate $125.00/day
  3. "Site supervisor" — rate $210.00/day
  4. "Formwork — standard panel hire" — rate $15.00/panel/day
  5. "Concrete supply (standard 25MPa)" — rate $420.00/m³

Derived:
  resource_count = 5
  is_active = true
  is_in_scope = true (today 15 Apr falls within Apr 1–Jun 30)
  supplier_name = null (Internal)

Example C — Project-Specific Price Book

Rates negotiated specifically for a single project (Tender “Acme Office Tower”):

Price Book:
  name: "Acme Office Tower — Preferred Contractor Rates (contract phase)"
  price_book_type: Project-Specific
  source_type: user
  status: Active
  scope_start_date: 2026-04-15
  scope_end_date: 2027-03-31
  scope_region: "Central Auckland (Level 25–30, Acme Tower)"
  project_id: <Tender "Acme Office Tower">
  supplier_id: null
  adjudication_id: null
  description: "Client-approved rates for structural frame, façade, and MEP packages. Site-specific labour multipliers (access, safety, etc.). Valid from contract award date through practical completion."
  is_hidden_from_ui: false

Resources (4):
  1. "Structural steelwork assembly (site-specific crew)" — rate $95.00/hour (Labour)
  2. "Façade glazing installation" — rate $320.00/m² (Subcontract)
  3. "MEP rough-in (combined labour)" — rate $180.00/hour (Labour)
  4. "Lifting (tower crane, 24/7 on-site)" — rate $1,200.00/day (Plant)

Derived:
  resource_count = 4
  is_active = true
  is_in_scope = true
  supplier_name = null (Project-Specific)

Example D — System-Generated Price Book from Subcontract Package Adjudication

A system-created Price Book produced when a Subcontract Package adjudication is awarded:

Price Book:
  name: "[System-generated] Subcontract Package Adjudication #42 — Formwork Round 1"
  price_book_type: External (inferred from winning subcontractor)
  source_type: adjudication
  status: Active
  scope_start_date: 2026-04-23 (date of award)
  scope_end_date: 2026-06-30 (contract completion target)
  scope_region: null (site-specific)
  project_id: null (not project-scoped; inherited from adjudication context)
  supplier_id: <Company "XYZ Formwork Ltd" with Subcontractor role>
  adjudication_id: <Subcontract Package Adjudication #42>
  description: "Auto-generated from adjudication #42 award. XYZ Formwork selected as winning subcontractor. Includes site office, OH&S coordination, temporary works. Quote ref XYZ-2047."
  is_hidden_from_ui: true (system-generated, hidden from default list)

Resources (4 — one per Item in package):
  1. "Ground floor slab formwork" (Type: Subcontract) — rate $45.75/m² — refs Item #A "Formwork — ground slab, 36 m²"
  2. "Level 1 formwork" (Type: Subcontract) — rate $47.50/m² — refs Item #B
  3. "Level 2 formwork" (Type: Subcontract) — rate $48.00/m² — refs Item #C
  4. "Temporary props & bracing" (Type: Subcontract) — rate $1,850.00/LS — refs Item #D

Lineage:
  origin: Subcontract Package Adjudication #42 (Round 1, awarded 23 Apr 2026)
  winning_supplier: XYZ Formwork Ltd
  package_scope: 4 Items (formwork across 3 levels + temporary works)

Estimator can:
  - View this Price Book by clicking "show system-generated" toggle, or by navigating from Adjudication #42
  - See each Resource linked to its Item via Worksheet Resource
  - Trace the cost lineage back to the adjudication decision (e.g., "why is formwork $45.75/m²?" → "awarded to XYZ on 23 Apr, quote XYZ-2047")

Derived:
  resource_count = 4
  is_active = true
  is_in_scope = true
  supplier_name = "XYZ Formwork Ltd"

Example E — Archived Price Book (historical rates from Benchmark migration)

Preserved rate window for historical reference and cost comparison:

Price Book:
  name: "[Archived] In-House Labour Rates — Q1 2026 (historical)"
  price_book_type: Internal
  source_type: user
  status: Archived
  scope_start_date: 2026-01-01
  scope_end_date: 2026-03-31
  scope_region: null
  supplier_id: null
  adjudication_id: null
  description: "Historical rate window from Benchmark migration. Rates superseded; retained for audit and historical cost comparison (Anomaly Review, Release milestone)."
  is_hidden_from_ui: true (archived, not active)

Resources (5 — snapshots from Q1):
  1. "Carpenter — general (includes small tools)" — rate $180.00/day (was $185.50 in Q2; rate increase 2.4%)
  2. "Labourer — general" — rate $120.00/day (was $125.00 in Q2; rate increase 4%)
  3. "Site supervisor" — rate $205.00/day (was $210.00; rate increase)
  4. "Formwork — standard panel hire" — rate $14.50/panel/day (was $15.00; rate increase)
  5. "Concrete supply (standard 25MPa)" — rate $410.00/m³ (was $420.00; rate increase)

Usage by Anomaly Review:
  - When reviewing an Item with labour, Anomaly Review (Release) can compare current rate ($185.50/day) against historical rates to flag outliers
  - AI Review Assistant uses archived windows to establish historical trends ("carpenter rates have increased 3% YoY")
  - Queries: "show all resources with rates between $180–$190/day in Q1 2026" for trending analysis

Derived:
  resource_count = 5
  is_active = false (status = Archived AND scope_end_date < today)
  is_in_scope = false (scope expired)
  supplier_name = null (Internal)