1. Purpose
The Commercials layer turns cost into revenue. After Items are costed, a sequenced set of Rules applies commercial adjustments (markup, margin, contingency, risk allocation) to derive the Submission Values shown to the client. Submission Values can then be manually overridden before Publisher Output is generated — the final submittable artifact (PDF, Excel) sent to the client.
Commercials are the bridge between internal build-up and client-facing pricing.
2. The three Commercials entities
2.1 Rule
A single commercial adjustment rule. Rules apply in sequence; later rules stack on earlier ones. Each Rule has a type (Percentage / Lump Sum), a scope (which Items/Costs it targets), and a value.
2.2 Submission Value
The final rate or amount shown to the client for a Schedule Item. Computed by Commercials, manually overridable, one-per-Schedule-Item.
2.3 Publisher Output
The client-submittable artifact generated at publish time (PDF, Excel, etc.). Captures the final Schedule, branding, cover letter, conditions, inclusions, exclusions. Preview-vs-publish distinction: preview is a read-only render of the submission artifact accessible to estimators; publish commits it and transitions the parent Estimate to Submitted (subject to BR-019c).
3. Rule — Attributes & Behavior
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
name | string | ✅ | — | E.g., “Contingency 5%”, “Margin 8%”, “Risk allowance $20K” |
rule_type | enum | ✅ | — | Percentage / Lump Sum. See §3.3 |
value | number | ✅ | — | For Percentage: 0–100 (e.g., 5 for 5%). For Lump Sum: absolute amount (e.g., 20000 for $20K) |
sequence_order | integer | ✅ | auto | Applied in ascending order (BR-070). Unique within an Estimate’s Rules |
scope_target | polymorphic | ✅ | — | Specifies which Items/costs this Rule targets (see §3.4). Many-to-many join |
scope_target_details | JSON | conditional | null | Additional filters (e.g., if target is “Specific Item”, which Item ID; if “Categorization Option”, which option) |
notes | long text | ❌ | null | Rule rationale / assumptions |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
Derived attributes (computed, not stored) — see §9.
3.3 Rule Type
Enum: Percentage or Lump Sum. No Rate Adjustment type in v1 (BR-071; rate adjustments are handled via Submission Value overrides, BR-072).
| Type | Behavior | Example |
|---|---|---|
| Percentage | Applied to running total as a percentage. Stacks multiplicatively. | ”Margin 8%”: adds 8% of the subtotal (after prior rules) |
| Lump Sum | Fixed dollar amount, distributed proportionally across in-scope Items by cost contribution | ”Risk allowance $20K”: split across Items as item_share = (item_cost / total_in_scope_cost) × $20K (BR-074a) |
3.4 Rule Scope Targets (BR-073)
A Rule targets Items/costs by scope. Multiple scope targets can apply; a Rule can target multiple Items through different scope definitions (polymorphic join).
Confirmed v1 scope targets:
- All — apply to all direct costs in the Estimate
- Direct-only — Items structurally derived as direct (Schedule Items + their descendants, unless Indirect Cost flag overrides)
- Indirect-only — Items structurally derived as indirect, OR flagged with Indirect Cost flag
- Specific Heading (subtree) — everything nested under a chosen Heading
- Item Type — e.g., “all Schedule Items”, “all Provisional Items”
- Resource Type — e.g., “all Labour Resources” (applies to the cost of those Resources across the Estimate)
- Categorization Option — e.g., “all items tagged ‘Mechanical’”
- Code value — e.g., “all items with Workcentre = ‘Civil’”
- Specific Item — one-off surgical adjustment to a single Item (see A7.2, Margin/risk allocation per item)
Relationships:
- Rule
M:MScope Target — a Rule can reference multiple scope targets (e.g., both “Direct-only” and “a specific Categorization Option” simultaneously — both must match for the Rule to apply to that Item)
3.5 Rule Application (BR-070, BR-074)
Rules apply in sequence order (ascending sequence_order). Each Rule:
- Evaluates its scope targets to determine which Items it applies to
- Computes its adjustment (Percentage of current running scope total, or fixed Lump Sum)
- Adds the adjustment to the running total for those Items
Rule application is sequential and compounding. Each rule applies to the running state of its scope after all prior rules in the sequence have been applied. Changing rule order changes the final totals—this is intentional and a key reason rule sequencing exists (BR-070).
No cross-rule dependencies. Each Rule takes the current running total (after all preceding Rules) and applies its adjustment independently. No Rule references another Rule; no conflict resolution.
Spread mechanics (BR-074a): When a Rule targets multiple Items (e.g., “Margin 8% on Direct costs” or Lump Sum $20K across 50 Items), the rule amount is distributed proportionally across in-scope Items based on their cost contribution to the total in-scope cost. For Percentage rules, each Item receives its proportional share of the percentage-calculated adjustment. For Lump Sum rules, each Item receives a proportional share: item_share = (item_cost / total_in_scope_cost) × lump_sum_amount. The underlying Item costs don’t change — the adjustment is applied on top, distributed proportionally by cost weight.
4. Submission Value — Attributes & Behavior
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
item_id | Item ref | ✅ | — | Foreign key. One-per-Schedule-Item (1:1 relationship). Schedule Items only |
computed_value | number | ✅ | — | Result of running all Commercials Rules on this Item (see §4.2). Stored for audit |
override_value | number | ❌ | null | Estimator override (BR-072). When set, this value replaces computed_value in final submission. Audit trail preserved |
final_value | number | ✅ | computed | Either override_value (if set) or computed_value |
audit_notes | long text | ❌ | null | Estimator-entered justification for override (e.g., “market check $875/m³”) |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
4.2 Submission Value Derivation
The computed_value is derived by:
- Starting with the Item’s total cost (from its Worksheet — see Item spec §9)
- Running all Rules in
sequence_orderthat apply to this Item (per Rule scope targets) - Accumulating adjustments (Percentage Rules compound; Lump Sum Rules stack additively)
- Storing the final computed total in
computed_value
Before publish, the estimator can review and override any Submission Value (BR-072). The override is optional but auditable — if set, final_value = override_value; otherwise final_value = computed_value. Estimator provides optional notes justifying the override.
5. Publisher Output — Attributes & Behavior
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
estimate_id | Estimate ref | ✅ | — | Foreign key. M:1 relationship. Only latest version kept per Estimate (BR-075) |
file_type | enum | ✅ | PDF / Excel (extensible) | |
generated_date | timestamp | ✅ | system | When output was generated |
version | string | ❌ | “v1” | Admin-controlled version label (e.g., “v1”, “Final”, “Client Review”) |
cover_letter | long text | ❌ | null | Submission cover letter text (estimator-entered, carries forward across republishes) |
conditions | long text | ❌ | null | General conditions / terms (estimator-entered) |
inclusions | long text | ❌ | null | What is included (Content Block instance or free text) |
exclusions | long text | ❌ | null | What is excluded (Content Block instance or free text) |
branding_config | JSON | ❌ | {} | Branding (logo, header, footer, colours, fonts). References branding template from admin config |
schedule_snapshot | JSON | ✅ | — | Frozen copy of the Schedule Item structure + Submission Values at time of publish (for stable output) |
notes | long text | ❌ | null | Internal notes (e.g., “sent to client 15 Apr”) |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
Relationship: Publisher Output M:1 Estimate. Only the latest version is retained per Estimate (BR-075); older versions are not archived.
Submit gate (BR-019c): the Publisher Output preview is always accessible to estimators — useful for seeing the current submission state and what’s still outstanding. The final Submit action, however, is blocked while any Item in the parent Estimate has status Unpriced or Plugged. When blocked, the Submit action surfaces an anomaly list highlighting the Items that still need pricing.
6. Relationships
Inbound (things referring to Commercials entities)
| From | Cardinality | Notes |
|---|---|---|
| Estimate | Estimate 1:M Rule | Rules belong to an Estimate’s Commercials layer |
| Item (Schedule) | Item 1:1 Submission Value | Schedule Items only |
| Estimate | Estimate 1:0..1 Publisher Output | Latest version only (BR-075); absent until first publish |
Outbound (things Commercials entities reference)
| To | Cardinality | Required | Notes |
|---|---|---|---|
| Estimate (Rule) | Rule M:1 Estimate | ✅ | |
| Estimate (Publisher Output) | Publisher Output M:1 Estimate | ✅ | |
| Item (Submission Value) | Submission Value 1:1 Item | ✅ | Schedule Items only |
| Scope Target (polymorphic) (Rule) | Rule M:M Scope Target | ✅ | Can target: Heading, Item Type, Resource Type, Categorization Option, Code, specific Item, structural (All/Direct/Indirect) |
7. Validation / Invariants
- Unique sequence within Estimate. Every Rule’s
sequence_orderwithin an Estimate must be unique and start from 0 or 1. - Rule value non-negative.
valuemust be ≥ 0. - Percentage bounds. If
rule_type = Percentage,valueshould typically be 0–100 (but not hard-capped; allow for >100% for exceptional cases). - Submission Value 1:1 Schedule Item. Every Schedule Item gets exactly one Submission Value, created atomically with the Item. Non-Schedule Items don’t have Submission Values.
- Final Value derivation.
final_value = override_value if override_value is not null, else computed_value. Enforced at write/read time. - Publisher Output latest only. Only the latest Publisher Output per Estimate is retained. Publishing a new version replaces the prior one.
- Audit trail on override. When
override_valueis set,updated_byandupdated_atare recorded, and optionallyaudit_notes.
8. Derived / Computed Attributes
| Attribute | On entity | Derivation | Notes |
|---|---|---|---|
computed_value | Submission Value | Estimate’s total_cost (from Item/Worksheet roll-up) with all applicable Rules applied in sequence (BR-070) | Recomputed when Item costs change or Rules change |
final_value | Submission Value | override_value if set, else computed_value | Read-only; derived at query time |
effective_margin_percent | Rule (Percentage only) | (rule_value / running_total_before_rule) × 100 | Informational; shows actual %; rule value is the %-adjustment |
9. Worked Examples
Example 1 — Sequence of three Rules stacking
Estimate total cost (from Item build-up): $100,000 (Direct)
Rules (in sequence order):
-
Rule 1 — “Contingency 5%” (Percentage, scope: Direct-only)
- Applies to: Direct costs ($100K)
- Adjustment: +$5,000
- Running total: $105,000
-
Rule 2 — “Risk allowance $20K” (Lump Sum, scope: All)
- Applies to: all Items
- Adjustment: +$20,000
- Running total: $125,000
-
Rule 3 — “Margin 8%” (Percentage, scope: Direct-only)
- Applies to: Direct costs (now $105K after Rule 1’s compounding)
- Adjustment: +$8,400 (8% × $105K, not $100K)
- Final total: $133,400
Submission Value computed: $133,400 per Schedule Item (or distributed proportionally if multiple Schedule Items were priced in parallel)
Note: Rules apply in order; each rule stacks on the running total after prior rules (BR-070, BR-074). Margin (Rule 3) applies to the compounded direct-cost state ($105K), not the original ($100K). The sequencing ensures Contingency is applied first, then Risk, then Margin on the updated base.
Example 2 — Submission Value with estimator override
Item: “Supply/install 150 m³ @ estimated $850/m³ = $127,500”
Commercials Rules applied: Margin 8% → Computed Submission Value = $137,700
Estimator’s market check: competitor quote shows $875/m³ for same work → $131,250 for 150 m³
Override action:
- Estimator sets
override_value = 131250 - Estimator notes: “Market check Q2 2026; competitor quote dated 10 Apr”
final_valuenow reads131250(override takes precedence)- Audit trail preserved:
updated_by,updated_at,audit_notes
Client sees: $131,250 on the schedule (not $137,700)
Example 3 — Publisher Output with branding & conditions
Estimate: Base offer for office fit-out, 5 Schedule Items
Publisher Output generation:
- Captures all Schedule Item Submission Values (final) as of publish time
- Cover letter: “Thank you for the opportunity. Below is our pricing for office fit-out works…”
- Conditions: “This estimate is valid for 30 days. Prices exclude GST. All work is subcontracted to [Contractor names]…”
- Inclusions: “Supply and install all finishes per specification. Site attendance during works. 24-hour rectification warranty.”
- Exclusions: “Preliminary works. Sitewide safety/OHS. Power/water coordination with existing facilities.”
- Branding: company logo (top), green accent colour, footer with contact details
- Snapshot: JSON record of final Schedule structure (in case future Estimates edit Items)
Output: PDF or Excel file generated, ready to send to client. Only this version is retained (BR-075).