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%.
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.
Related metrics
- Job cost variance: the budget side of the same EAC.
- WIP over/under billing: uses the same contract value and EAC.
- Backlog: contract value still to be earned.
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.