Back to ER Diagram
EVM & Cost Control

EVM & Cost Control Logic

Earned Value Management — PV, EV, AC, CPI, SPI tracking for project cost and schedule control

PostgreSQL
7 Tables
Schema: evms
Performance Indices

Overview

InfraTraq implements a full Earned Value Management System (EVMS) with Performance Measurement Baseline (PMB), Control Accounts, Work Packages, S-curves, and comprehensive forecasting (EAC/ETC/IEAC). The system provides objective, quantitative project performance measurement by integrating scope, schedule, and cost into a single framework for proactive decision-making.


Set PMB

Assign WP
Methods

Collect
Period Data

Calculate
Indices

Variance
Analysis

Forecast
EAC/ETC

S-Curve &
Alerts

Mgmt
Reports

EVM Formula Summary

  • BCWS (PV) = Planned Value — budgeted cost of work scheduled
  • BCWP (EV) = Earned Value — budgeted cost of work performed
  • ACWP (AC) = Actual Cost — real spend to date
  • SPI = EV / PV    CPI = EV / AC    TCPI = (BAC − EV) / (BAC − AC)
7
EVMS Tables
7
Key Metrics
5
EVM Methods
Integrated
Project + Finance + Estimation

EVM Metrics & Thresholds

Core EVM metrics with traffic-light threshold indicators for at-a-glance project health assessment.

SPI
1.03
Schedule Performance Index
On Track
CPI
0.97
Cost Performance Index
On Track
SV
+₹2.4L
Schedule Variance
Ahead
CV
-₹1.8L
Cost Variance
Watch
EAC
₹52.3Cr
Estimate at Completion
VAC
-₹2.3Cr
Variance at Completion
Over Budget
TCPI
1.04
To-Complete Perf. Index

Threshold Indicators

  • Green — SPI/CPI > 0.95 — On track, no action required
  • Yellow — SPI/CPI 0.85 – 0.95 — Watch zone, PM review required
  • Red — SPI/CPI < 0.85 — Critical, management escalation required

Status States

StatusDescriptionAllowed ActionsNext States
DraftBaseline or control account created but not approvedEdit, Submit for Approval, DeletePending Approval
Pending ApprovalAwaiting PMB approval by project sponsorApprove, Reject, ReturnApproved, Rejected
ApprovedBaseline is current; control accounts activeCollect Data, Amend BaselineActive, Re-baseline
ActiveEVM data collection and analysis in progressCollect Period Data, Forecast, ReportComplete, Re-baseline
WatchSPI/CPI between warning and critical thresholdsReview, Create Action PlanActive, Escalated
EscalatedSPI/CPI below critical threshold; mgmt interventionCorrective Action, Re-baseline, AuditActive, Re-baseline
Re-baselineFormal baseline change in progressReview Change, Approve New PMBApproved
CompleteProject EVM tracking closed; final metrics archivedView, Audit, Lessons Learned

Database Schema

evms.pm_baseline

  • baseline_id — PK, Performance Measurement Baseline header
  • project_id — FK → project.project
  • total_bac — Budget at Completion for the project
  • time_phased_budget — JSON: period-by-period planned value distribution
  • is_current — Boolean, only one active baseline per project

evms.control_account

  • ca_id — PK, control account linking WBS to EVM metrics
  • project_id — FK → project.project
  • wbs_id — FK → project.wbs_element
  • bac — Budget at Completion for this control account
  • eac, etc — Estimate at/to Completion
  • acwp, bcwp, bcws — Actual Cost, Earned Value, Planned Value
  • spi, cpi — Schedule and Cost Performance Indices

evms.work_package

  • wp_id — PK, work package within a control account
  • ca_id — FK → evms.control_account
  • activity_id — FK → project.activity
  • budget, actual_cost, earned_value — WP-level tracking
  • evm_method — 0/100, 50/50, % Complete, Milestones, or LOE

evms.earned_value

  • id — PK, period-level EVM data record
  • project_id — FK → project.project
  • ca_id — FK → evms.control_account
  • period_date — Reporting period date
  • bcws, bcwp, acwp — Three data streams
  • sv, cv — Schedule and Cost Variance
  • spi, cpi — Performance indices
  • eac — Estimate at Completion for the period

evms.evm_forecast

  • id — PK, project-level EAC/ETC forecast record
  • project_id — FK → project.project
  • forecast_type — CPI_BASED, SPI_CPI, BOTTOM_UP
  • eac, etc — Estimated amounts
  • estimated_completion_date — Projected finish

evms.s_curve_data

  • id — PK, cumulative S-curve data point
  • project_id — FK → project.project
  • data_date — Period date for the data point
  • planned_cumulative — Cumulative BCWS (PV)
  • actual_cumulative — Cumulative ACWP (AC)
  • earned_cumulative — Cumulative BCWP (EV)

evms.evm_threshold

  • id — PK, configurable threshold record
  • project_id — FK → project.project
  • metric — spi, cpi, sv, cv, etc.
  • warning_threshold — Yellow alert trigger value
  • critical_threshold — Red escalation trigger value

Process Flow


SET PMB
BAC by period, time-phased budget loaded

MONTHLY DATA COLLECTION
BCWS ← baseline, BCWP ← % done, ACWP ← actuals

CALCULATE INDICES
SPI = BCWP/BCWS, CPI = BCWP/ACWP

VARIANCES
SV = BCWP − BCWS, CV = BCWP − ACWP

FORECAST
EAC = BAC/CPI, ETC = EAC − ACWP, VAC = BAC − EAC

S-CURVE & ALERTS
Generate S-Curve, threshold alerts, mgmt reports

Step-by-Step Logic

1

Baseline Establishment

The Performance Measurement Baseline (PMB) is established by loading the approved Budget at Completion (BAC) into evms.pm_baseline with time-phased distribution across project periods. The baseline represents the cumulative planned value (BCWS) curve against which performance is measured. Only one baseline can be marked is_current = true per project.

2

Work Package Setup with EVM Methods

Each work package in evms.work_package is assigned an earned value measurement method: 0/100 (credit at completion), 50/50 (50% at start, 50% at finish), % Complete (weighted by physical progress), Milestones (weighted milestone values), or Level of Effort (time-apportioned). Method selection depends on work package duration, measurability, and type.

3

Periodic Data Collection

Each reporting period (typically monthly), three data streams are collected: BCWS from the time-phased baseline, BCWP calculated from physical % complete applied to each work package budget using the assigned EVM method, and ACWP from the finance module's actual cost postings. Data is stored in evms.earned_value.

4

Variance Analysis

Schedule Variance (SV = BCWP − BCWS) and Cost Variance (CV = BCWP − ACWP) are computed at control account and project levels. Positive values indicate favourable performance. The SPI and CPI indices are checked against evms.evm_threshold to trigger traffic-light status and alerts.

5

Forecasting (EAC Methods)

Multiple Estimate at Completion (EAC) methods are calculated and stored in evms.evm_forecast: EAC = BAC / CPI (assumes current cost efficiency continues), EAC = AC + ETC (manager's bottom-up re-estimate), and EAC = AC + (BAC − EV) / CPI (remaining work at current efficiency). The ETC (Estimate to Complete) = EAC − ACWP. VAC = BAC − EAC shows projected overrun/underrun.

6

S-Curve Generation

Cumulative planned, earned, and actual values are plotted as S-curves in evms.s_curve_data. Each period close appends a data point with planned_cumulative, earned_cumulative, and actual_cumulative values. The three curves visually show schedule and cost deviations — earned below planned indicates schedule delay; actual above earned indicates cost overrun.

7

Management Reporting

EVM reports are generated at control account, WBS, and project levels, including variance analysis summaries, trend charts, TCPI (To-Complete Performance Index), and corrective action recommendations. Reports feed into the analytics dashboards and risk assessment modules for holistic project oversight.

Code Implementation

class EVMService {

  /** Establish the Performance Measurement Baseline for a project */
  async establishBaseline(projectId) {
    const project = await Project.findById(projectId);
    const wbsItems = await WBS.findAll({ project_id: projectId });
    const totalBAC = wbsItems.reduce((sum, w) => sum + w.budget, 0);

    // Deactivate any existing current baseline
    await PMBaseline.update({ is_current: false }, {
      where: { project_id: projectId, is_current: true }
    });

    const baseline = await PMBaseline.create({
      project_id: projectId,
      total_bac: totalBAC,
      time_phased_budget: this.distributeByPeriod(wbsItems, project),
      is_current: true
    });

    // Create control accounts for each WBS element
    for (const wbs of wbsItems) {
      await ControlAccount.create({
        project_id: projectId, wbs_id: wbs.id,
        bac: wbs.budget, eac: wbs.budget, etc: wbs.budget,
        acwp: 0, bcwp: 0, bcws: 0, spi: 1.0, cpi: 1.0
      });
    }
    return baseline;
  }

  /** Collect period data — BCWS, BCWP, ACWP for all control accounts */
  async collectPeriodData(projectId, periodDate) {
    const baseline = await PMBaseline.findOne({
      project_id: projectId, is_current: true
    });
    const accounts = await ControlAccount.findAll({ project_id: projectId });

    for (const ca of accounts) {
      const bcws = this.getBCWSFromBaseline(baseline, ca.wbs_id, periodDate);
      const bcwp = await this.calculateBCWP(ca.ca_id, periodDate);
      const acwp = await FinanceService.getActualCost(ca.wbs_id, periodDate);

      await this.calculateEVM(ca.ca_id, periodDate, bcws, bcwp, acwp);
    }
    await this.generateSCurve(projectId, periodDate);
    await this.checkThresholds(projectId);
  }

  /** Calculate all EVM metrics for a control account */
  async calculateEVM(caId, periodDate, bcws, bcwp, acwp) {
    const ca = await ControlAccount.findById(caId);
    const sv = bcwp - bcws;                      // Schedule Variance
    const cv = bcwp - acwp;                      // Cost Variance
    const spi = bcws > 0 ? bcwp / bcws : 1.0;   // Schedule Perf Index
    const cpi = acwp > 0 ? bcwp / acwp : 1.0;   // Cost Perf Index
    const eac = cpi > 0 ? ca.bac / cpi : ca.bac; // Estimate at Completion
    const etc = eac - acwp;                      // Estimate to Complete
    const vac = ca.bac - eac;                    // Variance at Completion
    const tcpi = (ca.bac - bcwp) > 0             // To-Complete Perf Index
      ? (ca.bac - bcwp) / (ca.bac - acwp) : 1.0;

    // Persist period EVM data
    await EarnedValue.create({
      project_id: ca.project_id, ca_id: caId, period_date: periodDate,
      bcws, bcwp, acwp, sv, cv, spi, cpi, eac
    });

    // Update control account running totals
    await ControlAccount.update(caId, {
      bcws, bcwp, acwp, spi, cpi, eac, etc
    });

    return { sv, cv, spi, cpi, eac, etc, vac, tcpi };
  }

  /** Generate S-curve data point for the period */
  async generateSCurve(projectId, periodDate) {
    const accounts = await ControlAccount.findAll({ project_id: projectId });
    const planned = accounts.reduce((s, ca) => s + ca.bcws, 0);
    const earned  = accounts.reduce((s, ca) => s + ca.bcwp, 0);
    const actual  = accounts.reduce((s, ca) => s + ca.acwp, 0);

    await SCurveData.create({
      project_id: projectId, data_date: periodDate,
      planned_cumulative: planned,
      earned_cumulative: earned,
      actual_cumulative: actual
    });
  }

  /** Check EVM thresholds and raise alerts */
  async checkThresholds(projectId) {
    const thresholds = await EVMThreshold.findAll({ project_id: projectId });
    const latest = await EarnedValue.findLatest(projectId);

    for (const t of thresholds) {
      const value = latest[t.metric]; // e.g., 'spi', 'cpi'
      if (value < t.critical_threshold) {
        await AlertService.escalate(projectId, t.metric, value, 'critical');
      } else if (value < t.warning_threshold) {
        await AlertService.notify(projectId, t.metric, value, 'warning');
      }
    }
  }

  /** Forecast completion using multiple EAC methods */
  async forecastCompletion(projectId) {
    const ev = await EarnedValue.findLatest(projectId);
    const bac = await PMBaseline.getBAC(projectId);

    const forecasts = [
      { type: 'CPI_BASED',    eac: bac / ev.cpi,
        etc: (bac / ev.cpi) - ev.acwp },
      { type: 'SPI_CPI',      eac: ev.acwp + (bac - ev.bcwp) / (ev.cpi * ev.spi),
        etc: (bac - ev.bcwp) / (ev.cpi * ev.spi) },
      { type: 'BOTTOM_UP',    eac: ev.acwp + await this.getBottomUpETC(projectId),
        etc: await this.getBottomUpETC(projectId) },
    ];

    for (const f of forecasts) {
      await EVMForecast.upsert({
        project_id: projectId, forecast_type: f.type,
        eac: f.eac, etc: f.etc,
        estimated_completion_date: this.estimateDate(f.eac, ev)
      });
    }
    return forecasts;
  }
}

Validation Rules

RuleConditionAction
BAC Integrity CheckBAC ≠ sum of control account budgetsBlock baseline approval, show discrepancy
EVM Method RequiredWork package has no evm_method assignedBlock period data collection, flag WP for setup
ACWP ReconciliationACWP does not match finance actuals for periodHalt EVM calculation, notify finance team
BCWP Ceiling CheckCumulative BCWP exceeds BAC for a control accountCap BCWP at BAC, generate warning
S-Curve MonotonicityCumulative S-curve data point less than previousReject data point, flag data quality issue
Division by Zero GuardBCWS = 0 or ACWP = 0 when computing SPI/CPIDefault index to 1.0, flag as insufficient data

Automated Actions & Triggers

EventSource TableAuto Action
Baseline Approvedevms.pm_baselineSnapshot time-phased budget, create control accounts, populate S-curve plan line
Period Data Collectedevms.earned_valueAuto-calculate all EVM metrics (SPI, CPI, SV, CV, EAC, ETC, VAC, TCPI)
SPI or CPI < Warning Thresholdevms.evm_thresholdAlert PM with variance analysis summary
SPI or CPI < Critical Thresholdevms.evm_thresholdEscalate to senior management, require corrective action plan
EAC > BAC + Contingencyevms.evm_forecastEscalation to project sponsor, trigger budget revision workflow
Monthly Period Closefinance.fiscal_periodGenerate S-curve data point, update all forecasts, produce EVM report
Baseline Change Approvedevms.pm_baselineRe-snapshot PMB, recalculate cumulative BCWS, log baseline revision

Integration Points

Upstream (Data Sources)

  • Project WBS & Activities — WBS structure defines control accounts; activities define work packages
  • Finance (ACWP) — Actual costs from commitment accounting, AP invoices, and payment vouchers
  • Estimation (BAC) — Budget at Completion derived from BOQ and approved cost estimates
  • Progress Tracking — Physical % complete feeds BCWP calculation for each work package

Downstream (Consumers)

  • Analytics Dashboards — Real-time EVM metrics, S-curves, and trend visualisations
  • Management Reports — Monthly EVM summary, variance reports, corrective action tracking
  • Cash Flow Forecasting — EAC/ETC feeds future cash requirement projections
  • Risk Assessment — SPI/CPI trends feed risk scoring and early warning systems

Best Practices

EVM Method Selection Guidance

  • 0/100 — Short-duration tasks (< 2 periods); simple but delays earned value recognition
  • 50/50 — Tasks spanning 2-3 periods; balances simplicity with timeliness
  • % Complete — Longer tasks with measurable physical progress (concrete poured, km laid)
  • Milestones — Tasks with clear deliverable checkpoints; weighted milestone values must sum to 100%
  • Level of Effort (LOE) — Support activities (site supervision, QA) apportioned by time; does not indicate real schedule performance

Implementation Guidelines

  • Baseline changes must follow formal change management — document justification and re-approve PMB
  • Use consistent % complete measurement — physical progress preferred over cost-based for BCWP
  • CPI-based EAC is most reliable after 20% project completion — use bottom-up ETC for early stages
  • LOE work packages should not exceed 10-15% of total BAC to avoid masking true schedule performance
  • Index evms.earned_value on (project_id, period_date) and (ca_id, period_date) for fast queries
  • Maintain at least monthly data collection cadence; weekly for critical or fast-track projects

Common Pitfalls

  • Using cost-based % complete for BCWP — inflates earned value when costs overrun without proportional progress
  • Ignoring TCPI — failing to assess the required future efficiency to meet budget target
  • Excessive LOE work packages — masks true schedule and cost performance
  • Informal baseline changes — "rubber baseline" syndrome destroys EVM credibility
  • Not selecting appropriate EAC method — BAC/CPI is unreliable in early project phases or when scope changes