# Earned Value Management

Earned Value Management (EVM) is a project management technique that integrates scope, schedule, and cost to measure project performance objectively. By comparing planned work against actual work accomplished and actual cost incurred, EVM provides early warning of cost overruns and schedule delays.

## Key Metrics

Metric | Formula | Interpretation
--- | --- | ---
PV – Planned Value | BAC × Planned% | Budget authorized for scheduled work
EV – Earned Value | BAC × Actual% | Budget for work actually completed
AC – Actual Cost | Σ period costs | Total cost incurred to date
CV – Cost Variance | EV − AC | > 0: under budget; < 0: over budget
SV – Schedule Variance | EV − PV | > 0: ahead of schedule; < 0: behind schedule
CPI – Cost Performance Index | EV / AC | > 1: efficient; = 1: on target; < 1: inefficient
SPI – Schedule Performance Index | EV / PV | > 1: ahead; = 1: on target; < 1: behind
EAC – Estimate at Completion | See below | Forecast of total project cost
ETC – Estimate to Complete | EAC − AC | Remaining cost to finish the project
VAC – Variance at Completion | BAC − EAC | Expected budget surplus (positive) or overrun (negative)
TCPI – To-Complete Performance Index | (BAC−EV) / (BAC−AC) | Efficiency required on remaining work

## Example Setup

    library(PRA)

We will track a project with a total budget of $500,000 over a 5-period schedule.

    bac <- 500000
    schedule <- c(0.10, 0.25, 0.50, 0.75, 1.00)
    time_period <- 3

### Planned Value (PV)

PV is the authorized budget for work planned through the current period.

    pv_val <- pv(bac, schedule, time_period)
    cat("Planned Value (PV): $", format(pv_val, big.mark = ","), "\n")

Planned Value (PV): $ 250,000

The project was planned to be 50% complete by period 3, so PV = $250,000.

### Earned Value (EV)

EV reflects the budget value of work actually completed.

    actual_per_complete <- 0.40
    ev_val <- ev(bac, actual_per_complete)
    cat("Earned Value (EV): $", format(ev_val, big.mark = ","), "\n")

Earned Value (EV): $ 2e+05

Only 40% of work is done despite 50% being planned — we are behind schedule.

### Actual Cost (AC)

AC is the cumulative cost incurred. Use `cumulative = FALSE` when passing individual period costs.

    period_costs <- c(45000, 110000, 135000)
    ac_val <- ac(period_costs, time_period, cumulative = FALSE)
    cat("Actual Cost (AC): $", format(ac_val, big.mark = ","), "\n")

Actual Cost (AC): $ 290,000

### Performance Indicators

    sv_val <- sv(ev_val, pv_val)
    cv_val <- cv(ev_val, ac_val)
    spi_val <- spi(ev_val, pv_val)
    cpi_val <- cpi(ev_val, ac_val)

    cat("Schedule Variance (SV):          $", format(sv_val, big.mark = ","), "\n")

Schedule Variance (SV): $ -50,000

    cat("Cost Variance (CV):              $", format(cv_val, big.mark = ","), "\n")

Cost Variance (CV): $ -90,000

    cat("Schedule Performance Index (SPI):", round(spi_val, 3), "\n")

Schedule Performance Index (SPI): 0.8

    cat("Cost Performance Index (CPI):    ", round(cpi_val, 3), "\n")

Cost Performance Index (CPI): 0.69

Interpretation: SPI < 1 means we are behind schedule (only earning 80 cents of planned value per dollar of schedule). CPI < 1 means we are over budget (earning only 69 cents of value per dollar spent).

## Forecasting: Estimate at Completion (EAC)

EAC forecasts the total cost at project completion. Three methods are available, each making a different assumption about future performance:

Method | Formula | When to use
--- | --- | ---
Typical | BAC / CPI | Current cost inefficiency is expected to continue
Atypical | AC + (BAC − EV) | Cost overrun was a one-time event; future work will proceed at planned rate
Combined | AC + (BAC − EV) / (CPI × SPI) | Both cost and schedule performance will influence future costs

    eac_typical <- eac(bac, method = "typical", cpi = cpi_val)
    eac_atypical <- eac(bac, method = "atypical", ac = ac_val, ev = ev_val)
    eac_combined <- eac(bac,
      method = "combined", cpi = cpi_val, ac = ac_val,
      ev = ev_val, spi = spi_val
    )

    cat("EAC (typical):  $", format(round(eac_typical), big.mark = ","), "\n")

EAC (typical): $ 725,000

    cat("EAC (atypical): $", format(round(eac_atypical), big.mark = ","), "\n")

EAC (atypical): $ 590,000

    cat("EAC (combined): $", format(round(eac_combined), big.mark = ","), "\n")

EAC (combined): $ 833,750

The typical method gives the most conservative (highest cost) estimate because it assumes the current CPI persists. The atypical method is the most optimistic, assuming past overruns won’t recur.

### EAC Comparison Table

    eac_table <- data.frame(
      Method = c("Typical", "Atypical", "Combined"),
      EAC = c(round(eac_typical), round(eac_atypical), round(eac_combined)),
      Overrun = c(
        round(eac_typical - bac),
        round(eac_atypical - bac),
        round(eac_combined - bac)
      ),
      Assumption = c(
        "Current CPI continues",
        "Future work at planned rate",
        "CPI and SPI both factor in"
      )
    )
    knitr::kable(eac_table,
      format.args = list(big.mark = ","),
      caption = "EAC Comparison by Method"
    )

EAC Comparison by Method
Method | EAC | Overrun | Assumption
--- | --- | --- | ---
Typical | 725,000 | 225,000 | Current CPI continues
Atypical | 590,000 | 90,000 | Future work at planned rate
Combined | 833,750 | 333,750 | CPI and SPI both factor in

## Additional Metrics

    etc_val <- etc(bac, ev_val, cpi_val)
    vac_val <- vac(bac, eac_typical)

    # TCPI to meet original BAC
    tcpi_bac <- tcpi(bac, ev_val, ac_val, target = "bac")

    # TCPI to meet revised EAC (typical)
    tcpi_eac <- tcpi(bac, ev_val, ac_val, target = "eac", eac = eac_typical)

    cat("Estimate to Complete (ETC):         $", format(round(etc_val), big.mark = ","), "\n")

Estimate to Complete (ETC): $ 435,000

    cat("Variance at Completion (VAC):       $", format(round(vac_val), big.mark = ","), "\n")

Variance at Completion (VAC): $ -225,000

    cat("TCPI (to meet BAC):                ", round(tcpi_bac, 3), "\n")

TCPI (to meet BAC): 1.429

    cat("TCPI (to meet EAC):                ", round(tcpi_eac, 3), "\n")

TCPI (to meet EAC): 0.69

Interpretation: TCPI > 1 means the team must work more efficiently than they have been to meet the target. A TCPI of 1.43 to meet BAC means the remaining work must be done at 143% efficiency — significantly better than the current CPI of 0.69. Using the EAC target is more realistic and shows whether the revised budget is achievable.

## Performance Trend Chart

The chart below shows cumulative PV, AC, and EV over time, with horizontal reference lines for BAC and EAC.

    time_periods <- c(1, 2, 3)
    actual_pct <- c(0.08, 0.22, 0.40)
    p_costs <- c(45000, 110000, 135000)

    pv_vals <- sapply(time_periods, function(t) pv(bac, schedule, t))
    ac_vals <- cumsum(p_costs)
    ev_vals <- sapply(actual_pct, function(a) ev(bac, a))

    trend_data <- data.frame(
      Period = time_periods,
      PV     = pv_vals,
      AC     = ac_vals,
      EV     = ev_vals
    )

    p <- ggplot2::ggplot(trend_data, ggplot2::aes(x = Period)) +
      ggplot2::geom_line(ggplot2::aes(y = PV, color = "Planned Value (PV)"), linewidth = 1.2) +
      ggplot2::geom_line(ggplot2::aes(y = AC, color = "Actual Cost (AC)"), linewidth = 1.2) +
      ggplot2::geom_line(ggplot2::aes(y = EV, color = "Earned Value (EV)"), linewidth = 1.2) +
      ggplot2::geom_point(ggplot2::aes(y = PV, color = "Planned Value (PV)"), size = 3) +
      ggplot2::geom_point(ggplot2::aes(y = AC, color = "Actual Cost (AC)"), size = 3) +
      ggplot2::geom_point(ggplot2::aes(y = EV, color = "Earned Value (EV)"), size = 3) +
      ggplot2::geom_hline(yintercept = bac, linetype = "dashed", color = "black", linewidth = 0.8) +
      ggplot2::geom_hline(yintercept = eac_typical, linetype = "dotted", color = "darkred", linewidth = 0.8) +
      ggplot2::annotate("text", x = 2.8, y = bac + 12000, label = "BAC = $500K", size = 3.5) +
      ggplot2::annotate("text", x = 2.8, y = eac_typical + 12000, label = "EAC = $725K", size = 3.5) +
      ggplot2::scale_color_manual(values = c(
        "Planned Value (PV)" = "steelblue",
        "Actual Cost (AC)"   = "tomato",
        "Earned Value (EV)"  = "forestgreen"
      )) +
      ggplot2::scale_y_continuous(labels = scales::label_dollar(scale = 1e-3, suffix = "K")) +
      ggplot2::labs(
        title  = "Earned Value Management - Performance Trend",
        x      = "Time Period",
        y      = "Value",
        color  = NULL
      ) +
      ggplot2::theme_minimal() +
      ggplot2::theme(legend.position = "bottom")

    print(p)

Reading the chart: The gap between PV and EV (both measured on the same vertical axis) shows the schedule gap, EV is below PV, confirming we are behind. The gap between AC and EV shows the cost overrun, we have spent more than the value we have earned.