Skip to main content

Cost Rollup Data Flow

Overview

ForgeX uses a sophisticated three-level cost rollup system: Items → Scopes → Bids. Each level applies specific calculations and markups to produce accurate project estimates.

Flow Diagram

🔍 Click diagram to expand

Level 1: Item Calculations

Each module has its own calculation logic:

Concrete Items

View Calculation Details
// Cubic yards
sf = length × width
cubicYards = (length × width × depth) / 27

// Rebar
rebarPoundsPerSF = lookupRebarRate(slabRebarSize, slabRebarOC)
totalRebarPounds = sf × rebarPoundsPerSF
rebarPoundsWithWaste = totalRebarPounds × 1.10 // 10% waste

// Costs
concreteBaseCost = cubicYards × mixPricePerCY
concreteTaxAmount = taxExempt ? 0 : concreteBaseCost × concreteTaxRate
concreteCost = concreteBaseCost + concreteTaxAmount

rebarBaseCost = rebarPoundsWithWaste × rebarPricePerPound
rebarTaxAmount = taxExempt ? 0 : rebarBaseCost × rebarTaxRate
rebarCost = rebarBaseCost + rebarTaxAmount

totalCost = concreteCost + rebarCost

View Concrete API →

Labor Items

View Calculation Details
// Base cost
baseCost = quantity × hoursPerDay × days × ratePerHour

// Soft costs (payroll taxes)
ficaAmount = baseCost × 0.0765 // 7.65%
futaAmount = baseCost × 0.006 // 0.6%
sutaAmount = baseCost × 0.0175 // 1.75%

// Auto costs (vehicles)
autoDepreciation = baseCost × 0.03 // 3%
autoFuel = baseCost × 0.02 // 2%
autoInsurance = baseCost × 0.01 // 1%

totalCost = baseCost + fica + futa + suta + autoCosts

View Labor API →

Equipment Items

View Calculation Details
// Base rental
baseRentalCost = quantity × duration × ratePerUnit
rentalTaxAmount = taxExempt ? 0 : baseRentalCost × rentalTaxRate
rentalCost = baseRentalCost + rentalTaxAmount

// Additional charges
fuelCharge = quantity × days × fuelChargePerDay
deliveryFee = onPrevious ? 0 : deliveryFee
trucksCost = truckCount × truckHaulCost

totalCost = rentalCost + fuelCharge + deliveryFee + trucksCost

View Equipment API →

Material Items

View Calculation Details
// Waste factor
adjustedQuantity = quantity × (1 + wastePercent/100)

// Cost
baseCost = adjustedQuantity × unitCost
taxAmount = taxExempt ? 0 : baseCost × taxRate
totalCost = baseCost + taxAmount

View Material API →

Subcontractor Items

View Calculation Details
// Waste factor
adjustedQuantity = quantity × (1 + wastePercent/100)

// Base cost
subtotal = adjustedQuantity × ratePerUnit

// Buffer markup
totalCost = subtotal × (1 + buffer/100)

View Subcontractor API →

Miscellaneous Items

View Calculation Details
totalCost = quantity × ratePerUnit

View Misc API →

Level 2: Scope Rollup

Scopes aggregate all items by module and apply multiplier:

// Sum all items by module
scope.concreteCost = Σ ConcreteItem.totalCost
scope.laborCost = Σ LaborItem.totalCost
scope.equipmentCost = Σ EquipmentItem.totalCost

// Material cost EXCLUDES items linked to concrete (prevents double-counting)
// Items with sourceConcreteItemId are already counted in concreteCost
scope.materialCost = Σ MaterialItem.totalCost
WHERE sourceConcreteItemId IS NULL

scope.subcontractorCost = Σ SubcontractorItem.totalCost
scope.miscCost = Σ MiscItem.totalCost

// Apply scope multiplier
moduleTotals = concreteCost + laborCost + equipmentCost +
materialCost + subcontractorCost + miscCost
scope.totalCost = moduleTotals × multiplier
warning

Important (BM-32): Material items with sourceConcreteItemId are excluded to prevent double-counting concrete-generated materials.

View Scope API →

Level 3: Bid Rollup

Bids aggregate all scopes and apply module-level markups:

Step 1: Sum Scopes by Module

bid.concreteCost = Σ Scope.concreteCost
bid.laborCost = Σ Scope.laborCost
bid.equipmentCost = Σ Scope.equipmentCost
bid.materialCost = Σ Scope.materialCost
bid.subcontractorCost = Σ Scope.subcontractorCost
bid.miscCost = Σ Scope.miscCost

Step 2: Apply Module Markups

Each module applies 5 markup layers:

1
Workers Compensation (WC)

Percentage of hard cost

2
Overhead

Percentage of hard cost

3
Profit

Percentage of (hard cost + WC + overhead)

4
GL Pollution

Percentage of (hard cost + WC + overhead + profit)

5
Rental Insurance (Equipment Only)

Percentage of base rental cost

function calculateModuleTotal(moduleKey, hardCost, bid) {
// Get module-specific rates
const wcPercent = getModuleVariable(moduleKey, 'wc_percentage')
const overheadPercent = getModuleVariable(moduleKey, 'overhead_percentage')
const profitPercent = getModuleVariable(moduleKey, 'profit_percentage')
const glPollutionPercent = getModuleVariable(moduleKey, 'gl_pollution_percentage')

// Calculate markups
const wcAmount = hardCost × wcPercent
const overheadAmount = hardCost × overheadPercent
const profitBase = hardCost + wcAmount + overheadAmount
const profitAmount = profitBase × profitPercent
const glPollutionBase = profitBase + profitAmount
const glPollutionAmount = glPollutionBase × glPollutionPercent

// Equipment only: add rental insurance
let rentalInsuranceAmount = 0
if (moduleKey === 'equipment') {
rentalInsuranceAmount = baseRentalCost × rentalInsurancePercent
}

return hardCost + wcAmount + overheadAmount + profitAmount +
glPollutionAmount + rentalInsuranceAmount
}

Step 3: Calculate Subtotal

bid.subtotalCost = concreteWithMarkups + laborWithMarkups + 
equipmentWithMarkups + materialWithMarkups +
subcontractorWithMarkups + miscWithMarkups

Step 4: Apply Bid-Level Overhead and Profit

// Overhead
bid.overheadAmount = subtotalCost × bid.overheadPercentage

// Profit (applied to subtotal + overhead)
profitBase = subtotalCost + overheadAmount
bid.profitAmount = profitBase × bid.profitPercentage

// Total
bid.totalCost = subtotalCost + overheadAmount + profitAmount

View Bid API →

Example Calculation

Input

Items:

  • Concrete: $10,000
  • Labor: $5,000
  • Equipment: $2,000

Scope:

  • Multiplier: 1.0 (no scaling)

Module Markups:

  • WC: 12%
  • Overhead: 10%
  • Profit: 15%
  • GL Pollution: 2%

Bid:

  • Overhead: 10%
  • Profit: 15%

Calculation

1
Scope Totals
concreteCost = $10,000
laborCost = $5,000
equipmentCost = $2,000
totalCost = $17,000 × 1.0 = $17,000
2
Module Markups (Concrete Example)
WC = $10,000 × 0.12 = $1,200
Overhead = $10,000 × 0.10 = $1,000
Base = $10,000 + $1,200 + $1,000 = $12,200
Profit = $12,200 × 0.15 = $1,830
GL Base = $12,200 + $1,830 = $14,030
GL = $14,030 × 0.02 = $280.60
Total = $14,310.60
3
Subtotal
Concrete: $14,310.60
Labor: $7,155.30
Equipment: $2,862.12
Subtotal = $24,328.02
4
Bid Overhead & Profit
Overhead = $24,328.02 × 0.10 = $2,432.80
Profit Base = $24,328.02 + $2,432.80 = $26,760.82
Profit = $26,760.82 × 0.15 = $4,014.12
Total = $30,774.94

Automatic Recalculation

Cost rollup is automatically triggered when:

  • ✅ Item created/updated/deleted
  • ✅ Scope created/updated/deleted
  • ✅ Bid variables updated (overhead, profit)
  • ✅ Pricing items updated
  • ✅ Global variables updated

Performance Optimizations

Async Processing

Cost rollup runs asynchronously to avoid blocking API responses

📚

Batch Updates

Multiple item changes trigger single recalculation

🗄️

Database Caching

Scope and bid totals cached in database fields

🔄

Incremental Updates

Only affected scopes/bids recalculated

Manual Recalculation API

# Recalculate single scope
POST /api/costs/scope/:scopeId

# Recalculate scope and propagate to bid
POST /api/costs/scope/:scopeId/propagate

# Recalculate entire bid (all scopes)
POST /api/costs/bid/:bidId/full

# Recalculate bid totals only
POST /api/costs/bid/:bidId
info

Manual recalculation is rarely needed - the system auto-calculates on all relevant changes.