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
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
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
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
Material Items
View Calculation Details
// Waste factor
adjustedQuantity = quantity × (1 + wastePercent/100)
// Cost
baseCost = adjustedQuantity × unitCost
taxAmount = taxExempt ? 0 : baseCost × taxRate
totalCost = baseCost + taxAmount
Subcontractor Items
View Calculation Details
// Waste factor
adjustedQuantity = quantity × (1 + wastePercent/100)
// Base cost
subtotal = adjustedQuantity × ratePerUnit
// Buffer markup
totalCost = subtotal × (1 + buffer/100)
Miscellaneous Items
View Calculation Details
totalCost = quantity × ratePerUnit
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
Important (BM-32): Material items with sourceConcreteItemId are excluded to prevent double-counting concrete-generated materials.
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:
Percentage of hard cost
Percentage of hard cost
Percentage of (hard cost + WC + overhead)
Percentage of (hard cost + WC + overhead + profit)
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
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
concreteCost = $10,000
laborCost = $5,000
equipmentCost = $2,000
totalCost = $17,000 × 1.0 = $17,000
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
Concrete: $14,310.60
Labor: $7,155.30
Equipment: $2,862.12
Subtotal = $24,328.02
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
Manual recalculation is rarely needed - the system auto-calculates on all relevant changes.