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
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
name | string (text) | ✅ | — | Human-readable name, e.g., “Suppliers — Steel Ltd (May–Aug 2026)” or “In-House Labour Rates — Q2 2026” |
price_book_type | enum | ✅ | — | Project-Specific · Internal · External. See §3 |
source_type | enum | ✅ | user | user (user-created) · adjudication (system-generated). See §4 |
status | enum | ✅ | Active | Active · Archived. See §5 |
scope_start_date | date | ✅ | — | Start of validity window (e.g., 01 May 2026). Every Price Book has a scope date range |
scope_end_date | date | ✅ | — | End of validity window (e.g., 31 Aug 2026). Must be ≥ scope_start_date |
scope_region | string (optional) | ❌ | null | Geographic scope if applicable (e.g., “Auckland”, “North Island”). Optional; Resources may carry finer regional tags via Categorization |
project_id | Project ref | ❌ | null | Required if price_book_type = Project-Specific. Links to a single Tender/Estimate for project-scoped rates |
supplier_id | Company ref | conditional | null | Required if price_book_type = External (must be Company with Supplier role). Null for Internal and Project-Specific types (BR-050/051/052) |
adjudication_id | Subcontract Package Adjudication ref | ❌ | null | For system-generated Price Books: pointer to the Adjudication that produced it (lineage; BR-066). Null for user-created |
description | long text | ❌ | null | Notes on scope, supplier terms, applicability (e.g., “Regular supplier terms; includes cartage to metro area only”) |
is_hidden_from_ui | boolean | ✅ | false | If true, excluded from default Price Book list (applies to system-generated; BR-054). Admin/Power User can show via toggle |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
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).
| Type | Purpose | Supplier required? | Project-link required? | Typical use case |
|---|---|---|---|---|
| External | Tied to a supplier; supplier-specific rates | ✅ | ❌ | “Concrete Co rates (May–Aug 2026)”, “Hire Co plant schedule (Q2)“ |
| Internal | In-house reference rates, no supplier | ❌ | ❌ | “In-House Labour Rates — May 2026”, “Our standard material costs (May)“ |
| Project-Specific | Scoped 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).
| Source | Origin | Visibility | Editability | Lineage |
|---|---|---|---|---|
| user (user-created) | Manually curated by Estimator / Admin / Lead Estimator via Price Book UI | Default view (shown in Price Book list) | Full: can add/edit/delete Resources | None (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-adjudication | Lineage 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
| State | Meaning | When used | Who can transition |
|---|---|---|---|
| Active | Current, in-use rates | New Price Books start here; used for Resource reference during estimate build-up | Estimator, Lead Estimator, Admin |
| Archived | Superseded 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_date | Lead 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)
| From | Cardinality | Notes |
|---|---|---|
| Resource | Resource M:1 Price Book | Every Resource lives in exactly one Price Book (BR-021) |
| Subcontract Package Adjudication | SPA M:0..1 Price Book | System-generated Price Books only (BR-063, BR-066); points back via adjudication_id |
| Price Book Adjudication | PBA M:1 Price Book (implicit scope) | Adjudication scopes over Resources from Estimates, which live in Price Books; indirect relationship |
Outbound (things Price Book references)
| To | Cardinality | Required | Notes |
|---|---|---|---|
| Company (Supplier) | Price Book M:0..1 Company | conditional (BR-050) | Required if External; null for Internal and Project-Specific |
| Tender / Estimate (Project) | Price Book M:0..1 Tender | conditional (BR-052) | Required if Project-Specific; null for others |
| Resource | Price Book 1:M Resource | ✅ | Every Price Book contains zero or more Resources (M:1 from Resource side) |
| Subcontract Package Adjudication | Price Book 1:0..1 SPA | ❌ | System-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:
-
Type consistency.
price_book_typemust be one of: Project-Specific, Internal, External (enum). Enforced at write time. -
Scope date range.
scope_start_date≤scope_end_date; both are required and non-null. Validated on create and update. -
External Price Books require supplier. If
price_book_type = External, thensupplier_idmust be non-null and point to a valid Company with Supplier role (BR-050). -
Internal Price Books forbid supplier. If
price_book_type = Internal, thensupplier_idmust be null. -
Project-Specific Price Books require Tender link. If
price_book_type = Project-Specific, thenproject_id(Tender ref) must be non-null and valid (BR-052). -
Project-Specific forbids supplier. If
price_book_type = Project-Specific, thensupplier_idmust be null. -
Source type enum.
source_typemust be one of:user,adjudication. Enforced at write time. -
System-generated lineage. If
source_type = adjudication, thenadjudication_idmust be non-null and point to a valid Subcontract Package Adjudication.source_type = userPrice Books must haveadjudication_id = null. -
Status enum.
statusmust be one of: Active, Archived. Enforced at write time. -
Auto-archive trigger. When
scope_end_date < today,statusautomatically transitions to Archived. Logic applied at query time. -
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.
-
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”).
-
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
| Attribute | Derivation | Notes |
|---|---|---|
resource_count | Count of Resources where price_book_id = this.id | Useful for UI summary; recomputed on Resource add/delete |
is_active | status = Active AND scope_end_date >= today | True if currently valid (within date range and not manually archived) |
is_in_scope | scope_start_date <= today <= scope_end_date | True if today falls within validity window (independent of status) |
supplier_name | supplier_id → company.name if supplier_id non-null, else null | Convenience for display; derived from supplier relationship |
supplier_display | Name 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)