What it is
Cost to complete (also called estimate to complete) is a forecast, not an accounting number. The PM builds it from remaining quantities, open commitments, and labor still needed.
This page covers the number itself and two ways to check it: forecast age (days since last update) and forecast change (how much it moved since last month).
Why operators track it
- Everything downstream depends on it. Under cost-to-cost, percent complete is cost to date divided by cost to date plus cost to complete.
- Understated forecasts get caught. Understating remaining cost raises percent complete and earned revenue until the cost catches up, and sureties read the trend for this (Surety Bond USA).
- Stale forecasts hide problems. A forecast not updated for months makes a troubled job look stable.
- It is where PM judgment enters the numbers. Tracking it by PM shows whose forecasts move most.
How it is calculated
Cost to complete = estimate at completion − cost to date
- A bottom-up forecast by cost code is stronger than budget minus cost to date.
- Forecast age = as-of date − date the forecast was last saved.
- Forecast change = this month's estimate at completion − last month's.
Worked example
Job 2417 has $910,000 of cost to date and a forecast of $420,000 to complete, updated three days ago. Job 2431's forecast of $310,000 was last updated 63 days ago and equals its remaining budget.
How to read it
- Check age before reading the number. An old forecast is a question, not an answer.
- Budget minus cost is not a forecast. If cost to complete always equals remaining budget, no one is forecasting.
- Large swings late in the job suggest earlier forecasts were not realistic.
- Compare to committed cost. Remaining cost should at least cover open commitments.
- We do not publish a benchmark for forecast accuracy.
Definitional choices and common pitfalls
- Bottom-up or default. Decide whether jobs with no PM update default to remaining budget, and flag them when they do.
- Level. By cost code or job total. Cost code is better for review; job total is enough for WIP.
- Update cadence. Monthly at close is common. Define what counts as stale.
- Negative remaining budget. If cost to date exceeds budget on a code, the forecast must still be at least zero.
How different roles use it
| Role | How they use it |
|---|---|
| Project manager | Updates cost to complete by cost code every month. |
| CFO / controller | Checks forecast age and large changes before closing the WIP. |
| Owner / CEO | Asks about jobs whose forecast jumped. |
| Operations / superintendent | Supplies remaining quantities and crew needs from the field. |
Questions it answers
- Which forecasts are out of date?
- Which jobs' forecasts moved the most this month?
- Are any forecasts just budget minus cost?
- Does remaining cost cover open commitments?
How to present it in a report
- One row per job: cost to date, cost to complete, estimate at completion, change vs last month, and last updated date.
- Flag stale forecasts and forecasts equal to remaining budget.
- Drill down to cost code.
| Job | PM | Cost to date | Cost to complete | EAC | Updated | Flag |
|---|---|---|---|---|---|---|
| 2431 | A. Diaz | $1,620,000 | $310,000 | $1,930,000 | Aug 29 | Stale, equals remaining budget |
| 2417 | J. Moore | $910,000 | $420,000 | $1,330,000 | Oct 28 | |
| 2420 | J. Moore | $240,000 | $470,000 | $710,000 | Oct 30 |
Data you need
Fields
| Field | What it means | Example value |
|---|---|---|
| Job and cost code | Where the forecast applies | Job 2431, 03-300 concrete |
| Cost to date | Posted cost | $1,620,000 |
| Cost to complete | PM's remaining cost forecast | $310,000 |
| Forecast saved date | When the PM last saved it | Aug 29 |
| Forecast by | Who saved it | A. Diaz |
| Remaining budget | Revised budget minus cost to date | $310,000 |
What the raw data looks like
Example data A few rows as they might come out of the source system.
| Job | Cost to date | Cost to complete | Remaining budget | Saved | By |
|---|---|---|---|---|---|
| 2417 | $910,000 | $420,000 | $370,000 | Oct 28 | J. Moore |
| 2420 | $240,000 | $470,000 | $478,000 | Oct 30 | J. Moore |
| 2431 | $1,620,000 | $310,000 | $310,000 | Aug 29 | A. Diaz |
Where this data usually lives
- Construction ERP and accounting holds job cost, budgets, billing, AP, and AR. For example: Sage 300 Construction and Real Estate, Viewpoint Vista (Trimble), and Foundation. Some smaller contractors use general accounting software such as QuickBooks with jobs set up as projects.
- Project management often holds change orders, commitments, and project documents. For example: Procore.
- Spreadsheets often hold forecasts, logs, or the report 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
- No saved date. If the forecast has no timestamp, age cannot be measured. Audit logs may help.
- Forecasts in spreadsheets. The real forecast may never reach the system.
- Overwritten history. Without monthly snapshots, forecast change cannot be calculated.
- Default to remaining budget. Some systems fill it in automatically, which looks like a forecast but is not.
Related metrics
- Job cost variance: budget vs estimate at completion.
- WIP over/under billing: percent complete depends on it.
- Committed cost: the floor under the forecast.
FAQ
- Is cost to complete the same as remaining budget?
- No. Remaining budget is budget minus cost. Cost to complete is the PM's forecast of what is actually left.
- How often should it be updated?
- Commonly every month before close. Define your own staleness rule.
- Why does it change earned revenue?
- Under cost-to-cost, percent complete uses estimated total cost, so a change in the forecast changes earned revenue.
For your data teamFormula variants, tables, SQL sketch, and dashboard spec
Formula details
cost_to_complete = pm_forecast (bottom-up, by cost code)
eac = cost_to_date + cost_to_complete
forecast_age = :as_of - forecast_saved_at
forecast_change = eac[now] - eac[prev]
defaulted = cost_to_complete = revised_budget - cost_to_date
Worked example detail
| Job | EAC | Age (days) | Defaulted? |
|---|---|---|---|
| 2417 | $1,330,000 | 3 | No |
| 2420 | $710,000 | 1 | No |
| 2431 | $1,930,000 | 63 | Yes |
Tables and fields
| Table | Fields | Notes |
|---|---|---|
cost_forecasts |
job_id, cost_code, period, cost_to_complete, saved_at, saved_by | Keep every period |
job_costs |
job_id, cost_code, amount, posted_at | |
budgets |
job_id, cost_code, revised_budget |
SQL sketch
SELECT f.job_id, SUM(f.cost_to_complete) AS ctc, :as_of - MAX(f.saved_at) AS forecast_age,
BOOL_AND(f.cost_to_complete = b.revised_budget - c.ctd) AS defaulted
FROM cost_forecasts f JOIN budgets b USING (job_id, cost_code)
JOIN (SELECT job_id, cost_code, SUM(amount) ctd FROM job_costs GROUP BY 1,2) c USING (job_id, cost_code)
WHERE f.period = :period GROUP BY f.job_id;
Build spec
Copy this into a ticket. It lists the grain, inputs, logic, and edge cases.
metric: construction.cost_to_complete
grain: one row per job per cost code per period
inputs:
- cost_to_date
- cost_to_complete
- saved_at
- revised_budget
logic:
- eac = cost_to_date + cost_to_complete
- age = as_of - saved_at
- flag stale if age > cadence
- flag defaulted if ctc = remaining budget
edge_cases:
- ctc < 0: set to 0 and flag
- no forecast saved: show as missing, do not default silently
- closed job: ctc = 0
As a dashboard card
- Table of jobs with cost to complete, age, change, and flags; KPI for count of stale forecasts.
Common mistakes
- Treating remaining budget as a forecast.
- Not timestamping forecasts.
- Letting negative cost to complete through.