Skip to main content

Labor Items

Overview

Labor items represent crew members with automatic calculation of soft costs (payroll taxes) and auto costs (vehicle depreciation, fuel, insurance). The system also auto-calculates dependent roles like Operations Coordinator and Labor Foreman.

Endpoints

List Labor Items

GET /api/labor/scope/:scopeId
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Create Labor Item

POST /api/labor
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"scopeId": "uuid",
"role": "Skilled Laborer",
"quantity": 4,
"hoursPerDay": 8,
"ratePerHour": 25.00,
"days": 5
}

Roles: ADMIN, ESTIMATOR

Required Fields:

  • scopeId (string, UUID) - Parent scope
  • role (string) - Job role (e.g., "Skilled Laborer", "Operator")
  • quantity (number) - Number of workers
  • hoursPerDay (number) - Hours worked per day
  • ratePerHour (number) - Hourly wage
  • days (number) - Duration in days

Update Labor Item

PUT /api/labor/:id
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"quantity": 6,
"days": 7
}

Roles: ADMIN, ESTIMATOR


Delete Labor Item

DELETE /api/labor/:id
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR

Automatic Calculations

Base Cost

dollarPerDay = quantity × hoursPerDay × ratePerHour
baseCost = dollarPerDay × days

Soft Costs (Payroll Taxes)

ficaRate = 0.0765      // 7.65% (Social Security + Medicare)
futaRate = 0.006 // 0.6% (Federal Unemployment)
sutaRate = 0.0175 // 1.75% (State Unemployment)

ficaAmount = baseCost × ficaRate
futaAmount = baseCost × futaRate
sutaAmount = baseCost × sutaRate

Auto Costs (Vehicle Expenses)

For roles that require company vehicles:

autoDepreciationRate = 0.03    // 3%
autoFuelRate = 0.02 // 2%
autoInsuranceRate = 0.01 // 1%

autoDepreciation = baseCost × autoDepreciationRate
autoFuel = baseCost × autoFuelRate
autoInsurance = baseCost × autoInsuranceRate

autoCosts = autoDepreciation + autoFuel + autoInsurance

Total Cost

totalCost = baseCost + fica + futa + suta + autoCosts

Dependent Roles (Read-Only)

Certain roles are automatically calculated based on skilled labor hours. These appear in the UI but cannot be directly edited.

Operations Coordinator

skilledLaborHours = Σ (quantity × hoursPerDay × days) for "Skilled Laborer" role
opsCoordinatorHours = Math.min(skilledLaborHours × 0.05, 40)
  • 5% of skilled labor hours
  • Capped at 40 hours

Labor Foreman

totalLaborers = Σ quantity for all labor items
laborForemanCount = Math.ceil(totalLaborers / 10)
  • 1 foreman per 10 laborers

Junior Foreman

totalLaborers = Σ quantity for all labor items
juniorForemanCount = Math.ceil(totalLaborers / 5)
  • 1 junior foreman per 5 laborers

Data Model

interface LaborItem {
id: string // UUID
scopeId: string // Parent scope UUID
role: string // Job role
quantity: number // Number of workers
hoursPerDay: number // Daily hours
ratePerHour: number // Hourly wage
days: number // Duration

// Calculated Values (read-only)
dollarPerDay: number
totalCost: number
autoCosts: {
depreciation: number
fuel: number
insurance: number
} | null
softCosts: {
fica: number
futa: number
suta: number
}

// Relations
scope: Scope
}

Common Labor Roles

  • Skilled Laborer - General construction worker
  • Operator - Heavy equipment operator
  • Carpenter - Framing/formwork specialist
  • Concrete Finisher - Concrete finishing specialist
  • Foreman - Crew supervisor
  • Junior Foreman - Assistant supervisor
  • Operations Coordinator - Project coordination (auto-calculated)

Example: 4-Person Crew for 5 Days

{
"role": "Skilled Laborer",
"quantity": 4,
"hoursPerDay": 8,
"ratePerHour": 25.00,
"days": 5
}

Calculation:

  • Base: 4 × 8 × $25 × 5 = $4,000
  • FICA (7.65%): $306
  • FUTA (0.6%): $24
  • SUTA (1.75%): $70
  • Auto Costs (6%): $240
  • Total: $4,640

Gantt Mode

When bid.laborBiddingMode = "GANTT", labor is tracked at bid level with timeline-based allocation. Labor types are placed on a day-based timeline and costs distribute automatically to overlapping scopes.

List Bid Labor

GET /api/bids/:bidId/bid-labor
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Create Gantt Labor

POST /api/bids/:bidId/bid-labor
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"laborType": "Skilled Labor",
"quantity": 4,
"hoursPerDay": 8,
"ratePerHour": 27.63,
"startDay": 3,
"durationDays": 10
}

Roles: ADMIN, ESTIMATOR

Required Fields:

  • laborType (string) - Labor role name
  • quantity (number) - Number of workers
  • hoursPerDay (number) - Hours per worker per day
  • ratePerHour (number) - Hourly wage
  • startDay (number) - Start day (1-based)
  • durationDays (number) - Duration in days

Update Gantt Labor

PATCH /api/bids/:bidId/bid-labor/:laborId
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"startDay": 5,
"durationDays": 8,
"quantity": 6
}

Roles: ADMIN, ESTIMATOR


Delete Gantt Labor

DELETE /api/bids/:bidId/bid-labor/:laborId
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR


Batch Update Labor

POST /api/bids/:bidId/bid-labor/batch
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"create": [
{
"laborType": "Operator",
"quantity": 2,
"hoursPerDay": 8,
"ratePerHour": 29.84,
"startDay": 1,
"durationDays": 5
}
],
"update": [
{
"id": "uuid",
"startDay": 3,
"durationDays": 8
}
],
"delete": ["uuid1", "uuid2"]
}

Roles: ADMIN, ESTIMATOR


Switch Labor Bidding Mode

PATCH /api/bids/:bidId/bid-labor/mode
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"laborBiddingMode": "GANTT"
}

Roles: ADMIN, ESTIMATOR

warning

Switching modes will clear all existing labor items for the selected mode. Use /mode/confirm to proceed.


Confirm Labor Mode Switch

POST /api/bids/:bidId/bid-labor/mode/confirm
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"laborBiddingMode": "GANTT"
}

Roles: ADMIN, ESTIMATOR


Get Labor Distributions

GET /api/bids/:bidId/bid-labor/distributions
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Get Scope Labor Allocations

GET /api/bids/:bidId/bid-labor/scope/:scopeId
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Get Labor Coverage Details

GET /api/bids/:bidId/bid-labor/:laborId/coverage
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Recalculate Labor Distributions

POST /api/bids/:bidId/bid-labor/recalculate
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR

Gantt Mode Cost Calculations

// Base cost
totalCost = quantity × hoursPerDay × ratePerHour × durationDays

// Distribute across scopes based on timeline overlap
for each scope {
overlapDays = calculateOverlap(labor.startDay, labor.durationDays, scope.timeline)
percentage = overlapDays / totalOverlapDays
allocatedCost = totalCost × percentage
}

Gantt Mode Data Model

interface BidLabor {
id: string
bidId: string
laborType: string
quantity: number
hoursPerDay: number
ratePerHour: number
startDay: number
durationDays: number
totalCost: number
distributions: LaborDistribution[]
}

interface LaborDistribution {
scopeId: string
scopeName: string
allocatedDays: number
percentage: number
allocatedCost: number
}