Skip to main content

Cost Calculations

Overview

ForgeX uses a sophisticated cost rollup system that automatically calculates costs from individual items → scopes → bids with module-specific markups applied at each level.

See the Cost Rollup Diagram for a visual representation.

Calculation Flow

1
Item-Level Calculations

Each module calculates its own item costs with module-specific logic (cubic yards, soft costs, waste factors, etc.)

2
Scope Rollup

Sum all item costs by module within a scope, then apply scope multiplier

3
Bid Rollup

Sum all scope costs by module, apply module markups (WC, overhead, profit, GL pollution)

4
Bid Totals

Calculate subtotal, apply bid-level overhead and profit

Item-Level Calculations

Concrete Items

// 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 Endpoints →

Labor Items

// 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 Endpoints →

Equipment Items

// 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 Endpoints →

Material Items

// Waste factor
adjustedQuantity = quantity * (1 + wastePercent/100)

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

View Material Endpoints →

Subcontractor Items

// Waste factor
adjustedQuantity = quantity * (1 + wastePercent/100)

// Base cost
subtotal = adjustedQuantity * ratePerUnit

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

View Subcontractor Endpoints →

Miscellaneous Items

totalCost = quantity * ratePerUnit

View Misc Endpoints →

Scope-Level Rollup

// 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
info

BM-32: Material items with sourceConcreteItemId are excluded from cost rollup to prevent double-counting concrete-generated materials.

Bid-Level Rollup

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:

  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 from global variables
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') {
const rentalInsurancePercent = getVariable('rental_insurance')
rentalInsuranceAmount = baseRentalCost * rentalInsurancePercent
}

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

// Apply to each module
const concreteWithMarkups = calculateModuleTotal('concrete', bid.concreteCost, bid)
const laborWithMarkups = calculateModuleTotal('labor', bid.laborCost, bid)
const equipmentWithMarkups = calculateModuleTotal('equipment', bid.equipmentCost, bid)
const materialWithMarkups = calculateModuleTotal('material', bid.materialCost, bid)
const subcontractorWithMarkups = calculateModuleTotal('subcontractor', bid.subcontractorCost, bid)
const miscWithMarkups = calculateModuleTotal('misc', bid.miscCost, bid)

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

Bid with Single Scope

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-Level:

  • Overhead: 10%
  • Profit: 15%

Step-by-Step

// 1. Scope totals (no multiplier)
scope.concreteCost = $10,000
scope.laborCost = $5,000
scope.equipmentCost = $2,000
scope.totalCost = $17,000

// 2. Bid module totals (sum of scopes)
bid.concreteCost = $10,000
bid.laborCost = $5,000
bid.equipmentCost = $2,000

// 3. Module markups (example for concrete)
concreteWC = $10,000 * 0.12 = $1,200
concreteOverhead = $10,000 * 0.10 = $1,000
concreteBase = $10,000 + $1,200 + $1,000 = $12,200
concreteProfit = $12,200 * 0.15 = $1,830
concreteGLBase = $12,200 + $1,830 = $14,030
concreteGL = $14,030 * 0.02 = $280.60
concreteTotal = $14,310.60

// (Repeat for labor and equipment)

// 4. Subtotal
bid.subtotalCost = $14,310.60 + $7,155.30 + $2,862.12 = $24,328.02

// 5. Bid overhead
bid.overheadAmount = $24,328.02 * 0.10 = $2,432.80

// 6. Bid profit
profitBase = $24,328.02 + $2,432.80 = $26,760.82
bid.profitAmount = $26,760.82 * 0.15 = $4,014.12

// 7. Final total
bid.totalCost = $24,328.02 + $2,432.80 + $4,014.12 = $30,774.94

Automatic Recalculation

Cost rollup is automatically triggered when:

  • ✅ Item created/updated/deleted
  • ✅ Scope created/updated/deleted (affects multiplier)
  • ✅ Bid variables updated (overhead, profit)
  • ✅ Pricing items updated (affects item costs)
  • ✅ Global variables updated (affects markups)

API Endpoints

Trigger Manual Recalculation

# 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 (no scope changes)
POST /api/costs/bid/:bidId
info

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

Performance

  • Async Processing: Cost rollup runs asynchronously to avoid blocking API responses
  • Batch Updates: Multiple item changes trigger single recalculation
  • Caching: Scope and bid totals cached in database fields
  • Incremental Updates: Only affected scopes/bids recalculated