Construction · Job profitability

Gross margin by job

How much of each job's contract value is left after cost. Tracked month by month, it shows which jobs are making money and which are slowly giving it back.

Plan your metrics

What it is

Gross margin by job is the contract value of a job minus its cost, shown in dollars and as a percent of contract value.

Contractors usually look at it two ways. Projected margin uses the estimate at completion, so it shows where the job is expected to finish. Earned margin to date uses earned revenue and cost to date, so it shows what has been recognized so far.

The drop in projected margin from bid to now is often called margin fade. A rise is called margin gain.

Why operators track it

  • Profit is made or lost job by job. A company total can hide one job that is losing money.
  • Early warning. Fade usually shows up month by month before a job closes at a loss.
  • Bidding. Final margins by job type feed back into future estimates.
  • Bonding and credit. Sureties and lenders look at projected margins on the WIP schedule.

How it is calculated

Projected gross margin % = (revised contract value − estimate at completion) ÷ revised contract value

  • Revised contract value = original contract + approved change orders (revenue side)
  • Estimate at completion (EAC) = cost to date + estimated cost to complete
  • Earned margin to date = earned revenue − cost to date
  • Fade = projected margin % at bid − projected margin % now

Worked example

Job 2417 was bid at $1,400,000 with a budget of $1,200,000, a 14.3% margin. Approved change orders added $100,000 to the contract. Cost to date is $910,000 and cost to complete is $420,000, so the EAC is $1,330,000. Projected margin is now $170,000, or 11.3%.

1. Job 2417: where the contract value goesAt bidOriginal contractCost $1,200,000Margin $200,00014.3% of $1,400,000NowRevised contractCost $1,330,000Margin $170,00011.3% of $1,500,0002. Projected margin month by month (fade)15%12.5%10%14.3%Bid14.3%Jul13.6%Aug12.4%Sep11.3%Oct3.0 pts of fade
Example data Job 2417. The contract grew by $100,000 in change orders, but projected cost grew by $130,000, so projected margin fell from 14.3% to 11.3%.

How to read it

  • Read the trend, not one month. Steady fade over several months is a stronger signal than one change.
  • Compare to the bid. Fade from the bid margin is often more useful than the margin level by itself.
  • Large gains deserve questions too. A sudden gain can mean cost was not posted or the forecast was not updated.
  • Change orders move both sides. Check that a change order added cost budget as well as revenue.
  • Margins vary by trade, region, and job type, so we do not publish a benchmark.

Definitional choices and common pitfalls

  • Projected or earned. Say which one a report shows. They answer different questions.
  • Pending change orders. Most reports use approved change orders only. If pending ones are included, label them.
  • What counts as cost. Decide whether overhead allocations and equipment charges are in job cost.
  • Stale forecasts. Projected margin is only as current as the cost-to-complete forecast.
  • Bid margin source. Decide whether "at bid" means the estimate or the first budget loaded into the system.

How different roles use it

Role How they use it
Owner / CEO Looks at projected margin and fade across all jobs, and asks about any job fading fast.
CFO / controller Includes projected margin on the WIP schedule and checks that fade is explained.
Project manager Updates cost to complete and explains changes in margin on their jobs.
Estimator Compares final margin to bid margin by job type to improve future bids.

Questions it answers

  • Which jobs are fading, and by how much?
  • What is projected margin across all open jobs?
  • Which PMs or divisions have the most fade?
  • How did finished jobs end up compared to the bid?

How to present it in a report

  • One row per open job: contract value, EAC, projected margin in dollars and percent, bid margin, and fade.
  • A small line per job, or a portfolio line, showing projected margin by month.
  • Sort by fade, largest first.
  • Label whether pending change orders are included.

Data you need

Fields

Field What it means Example value
Job The project, with its PM and division Job 2417, PM: R. Diaz
Original contract value Contract amount at award $1,400,000
Original budget Total budgeted cost at award $1,200,000
Approved change orders Revenue and cost budget added by approved changes $100,000 revenue
Cost to date All posted job cost $910,000
Estimated cost to complete The PM's forecast of remaining cost, by period $420,000

What the raw data looks like

Example data A few rows as they might come out of the source system.

Job Contract Approved CO revenue Original budget Cost to date Cost to complete Period
2417 $1,400,000 $100,000 $1,200,000 $910,000 $420,000 Oct
2417 $1,400,000 $100,000 $1,200,000 $780,000 $535,000 Sep
2420 $860,000 $0 $720,000 $240,000 $470,000 Oct

Where this data usually lives

  • Construction ERP and accounting holds budgets, job cost, change orders, billing, and retainage. For example: Sage 300 Construction and Real Estate, Viewpoint Vista (Trimble).
  • Project management often holds change orders and their status, and sometimes budgets and forecasts. For example: Procore.
  • Estimating produces the original budget by cost code before the job starts.
  • Payroll and time tracking feed labor cost and hours to job cost.
  • Spreadsheets often hold the PM's cost-to-complete forecast or the WIP schedule itself.

The same field can live in more than one system. Agree with the customer which one is the source of truth.

Common data gaps and traps

  • Forecast history not kept. If only the latest cost to complete is stored, you cannot show fade over time. Save a snapshot each month.
  • Change orders on one side only. Revenue added without cost budget makes margin look better than it is.
  • Bid margin not stored. The estimate may live in a separate estimating tool.
  • Unposted costs. Invoices not yet entered make margin look better for a month.

FAQ

What is margin fade?
The drop in projected margin from the bid to now. Margin gain is the opposite.
Should I use projected or earned margin?
Projected margin shows where the job should finish. Earned margin shows what has been recognized so far. Many reports show both. Say which one each column is.
Why does margin change when nothing happened on site?
Usually because the cost-to-complete forecast was updated, cost was posted late, or a change order was approved.
For your data teamFormula variants, tables, SQL sketch, and dashboard spec

Formula details

revised_contract_value = original_contract + approved_co_revenue
eac                    = cost_to_date + estimated_cost_to_complete
projected_margin       = revised_contract_value - eac
projected_margin_pct   = projected_margin / revised_contract_value
bid_margin_pct         = (original_contract - original_budget) / original_contract
fade_pts               = bid_margin_pct - projected_margin_pct
earned_revenue         = cost_to_date / eac * revised_contract_value   -- cost-to-cost
earned_margin_to_date  = earned_revenue - cost_to_date

Worked example detail

Job 2417
Original contract / budget $1,400,000 / $1,200,000
Bid margin 200,000 / 1,400,000 = 14.3%
Revised contract value $1,500,000
EAC 910,000 + 420,000 = $1,330,000
Projected margin 1,500,000 - 1,330,000 = $170,000 (11.3%)
Fade 14.3% - 11.3% = 3.0 points
Earned margin to date 1,026,316 - 910,000 = $116,316

Tables and fields

Table Fields Notes
jobs job_id, pm_id, division, original_contract, original_budget, status
change_orders job_id, revenue_amount, budget_amount, status, approved_at
job_costs job_id, amount, posted_at
cost_forecasts job_id, period, estimated_cost_to_complete Keep one row per period for fade

SQL sketch

WITH co AS (
  SELECT job_id, SUM(revenue_amount) AS co_rev
  FROM change_orders WHERE status = 'approved' AND approved_at <= :as_of GROUP BY 1
),
cost AS (SELECT job_id, SUM(amount) AS ctd FROM job_costs WHERE posted_at <= :as_of GROUP BY 1),
ctc AS (SELECT job_id, estimated_cost_to_complete AS ctc FROM cost_forecasts WHERE period = :period)
SELECT j.job_id, j.pm_id,
       j.original_contract + COALESCE(co.co_rev,0)                     AS revised_contract_value,
       c.ctd + COALESCE(t.ctc,0)                                       AS eac,
       (j.original_contract + COALESCE(co.co_rev,0)) - (c.ctd + COALESCE(t.ctc,0)) AS projected_margin,
       ((j.original_contract + COALESCE(co.co_rev,0)) - (c.ctd + COALESCE(t.ctc,0)))
         / NULLIF(j.original_contract + COALESCE(co.co_rev,0), 0)      AS projected_margin_pct,
       (j.original_contract - j.original_budget) / NULLIF(j.original_contract,0) AS bid_margin_pct
FROM jobs j
JOIN cost c ON c.job_id = j.job_id
LEFT JOIN co ON co.job_id = j.job_id
LEFT JOIN ctc t ON t.job_id = j.job_id
WHERE j.status = 'open';

As a dashboard card

  • Card type: table of open jobs with projected margin %, bid margin %, and fade, plus a portfolio line of projected margin by month.
  • Measures: projected_margin, projected_margin_pct, bid_margin_pct, fade_pts, earned_margin_to_date.
  • Dimensions: job, PM, division, period.
  • Default view: open jobs, current period, sorted by fade.

Common mistakes

  • Overwriting the forecast each month, so fade cannot be shown.
  • Adding change order revenue without the matching cost budget.
  • Mixing projected and earned margin in one column.

Version 1.0 · Last reviewed 2026-10-20 · Machine-readable definition