EIA-923 Plant 99999: Generation That Belongs to No Plant

On this page

Problem statement

Sum EIA-923 generation by plant, join to EIA-860 for locations, and the total no longer matches EIA's published figure: 12.48 TWh has gone missing. It was never at a plant. EIA-923 carries 352 rows with plant ID 99999 and the name "State-Fuel Level Increment" β€” one per state, fuel and prime mover β€” which hold generation EIA includes in its totals but does not assign to any plant. They have no location, no capacity and no EIA-860 record. This page shows what they contain and how to keep totals and maps consistent.

Quick answer

Keep plant 99999 when you compute national or state totals, and leave it out when you work at plant level (maps, capacity factors, nearest-plant analysis). Say which you did: the difference is 0.28% nationally, more for some states and fuels.

g = read_923()
inc = g[g["Plant Id"] == 99999]
print(f"rows with plant ID 99999: {len(inc)}; plant name: {inc['Plant Name'].unique().tolist()}")
print(f"their net generation: {inc['Net Generation (Megawatthours)'].sum() / 1e6:.2f} TWh, "
      f"{inc['Net Generation (Megawatthours)'].sum() / g['Net Generation (Megawatthours)'].sum():.2%} of the US total")
rows with plant ID 99999: 352; plant name: ['State-Fuel Level Increment']
their net generation: 12.48 TWh, 0.28% of the US total

The 352 increment rows hold 12.48 TWh of net generation in 2025, 0.28% of the US total.

Horizontal bars of net generation in plant 99999 rows by fuel code, natural gas largest Horizontal bars of net generation in plant 99999 rows by fuel code, natural gas largest
The 2025 state-fuel increments: 6.55 TWh of gas, 2.58 of coal, 1.87 of solar.

Inside the increment rows

1. One row per state, fuel and prime mover

print(f"states: {inc['Plant State'].nunique()}; fuel codes: {inc['MER Fuel Type Code'].nunique()}; rows with non-zero generation: {(inc['Net Generation (Megawatthours)'] != 0).sum()}")
print("by fuel, TWh:", inc.groupby("MER Fuel Type Code")["Net Generation (Megawatthours)"].sum().div(1e6).sort_values(ascending=False).head(5).round(2).to_dict())
states: 45; fuel codes: 15; rows with non-zero generation: 219
by fuel, TWh: {'NG': 6.55, 'COL': 2.58, 'SUN': 1.87, 'WWW': 0.59, 'WND': 0.42}

They cover 45 states and 15 fuel codes, but only 219 rows are non-zero. Natural gas has the largest increment (6.55 TWh), then coal (2.58) and solar (1.87), with wood and wind smaller.

2. What they stand for

s = inc.groupby("Plant State")["Net Generation (Megawatthours)"].sum()
t = g.groupby("Plant State")["Net Generation (Megawatthours)"].sum()
share = (s / t).sort_values(ascending=False)
print("states where the increment is the largest share of generation:", share.head(5).round(3).to_dict())
states where the increment is the largest share of generation: {'TN': 0.014, 'CO': 0.014, 'LA': 0.011, 'ME': 0.01, 'AL': 0.009}

No state's increment is large: the highest are Tennessee and Colorado at 1.4% of their generation and Louisiana at 1.1%. They are adjustments that bring EIA's plant-level data into line with its state and fuel estimates β€” EIA's estimate of generation it does not attribute to a reporting plant.

3. A join to the plant file drops them

p = plants[["Plant Code", "Latitude", "Longitude"]].rename(columns={"Plant Code": "Plant Id"})
p["Plant Id"] = p["Plant Id"].astype(int)
j = g.merge(p, on="Plant Id", how="inner")
lost = g["Net Generation (Megawatthours)"].sum() - j["Net Generation (Megawatthours)"].sum()
print(f"generation lost in an inner join to plant locations: {lost / 1e6:.2f} TWh, of which plant 99999: {inc['Net Generation (Megawatthours)'].sum() / 1e6:.2f} TWh")
generation lost in an inner join to plant locations: 12.48 TWh, of which plant 99999: 12.48 TWh

An inner join of EIA-923 to EIA-860 plant locations loses exactly 12.48 TWh, all of it plant 99999. A map of generation by plant therefore shows slightly less than the national total, which is correct as long as the difference is stated.

Map of the lower 48 states shaded by the increment's share of each state's generation, highest in Tennessee and Colorado Map of the lower 48 states shaded by the increment's share of each state's generation, highest in Tennessee and Colorado
No state's increment exceeds 1.4% of its generation.

4. Report totals with them, maps without them

solar = g[g["MER Fuel Type Code"] == "SUN"]
print(f"US solar 2025: {solar['Net Generation (Megawatthours)'].sum() / 1e6:.1f} TWh in the file; "
      f"at mappable plants: {solar.loc[solar['Plant Id'] != 99999, 'Net Generation (Megawatthours)'].sum() / 1e6:.1f} TWh")
US solar 2025: 300.3 TWh in the file; at mappable plants: 298.4 TWh

US solar generation in the file is 300.3 TWh; at plants that can be mapped it is 298.4 TWh. Report the first as the national figure and the second as the mapped one.

Monthly increments and other unmatched IDs

The data used on this page

The US Energy Information Administration's EIA-923 2025 final generation file and the EIA-860 2025 plant file (saved copies).

import io
import zipfile
from pathlib import Path

import geopandas as gpd
import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
# EIA-860 (2025, released September 2026), EIA-923 (2025 final) and EIA-860M (August 2026), saved on 15 October 2026.
# Live: https://www.eia.gov/electricity/data/eia860/xls/eia8602025.zip, .../eia923/xls/f923_2025.zip and
# .../eia860m/xls/august_generator2026.xlsx; the annual files move to archive/xls/ when the next year's data appear.
FILES = {"eia860": "eia-860-2025-2026-10-15.zip", "eia923": "eia-923-2025-final-2026-10-15.zip",
         "eia860m": "eia-860m-august-2026-2026-10-15.xlsx"}
for name in FILES.values():
    if not Path(f"data/{name}").exists():
        Path(f"data/{name}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{name}", timeout=600).content)


def read_860(member, sheet=0):
    """One sheet of an EIA-860 workbook; the first row is a title, the last row of some sheets a note."""
    with zipfile.ZipFile(f"data/{FILES['eia860']}") as z:
        d = pd.read_excel(io.BytesIO(z.read(member)), sheet_name=sheet, skiprows=1)
    return d[pd.to_numeric(d["Plant Code"], errors="coerce").notna()]


def read_923(sheet="Page 1 Generation and Fuel Data"):
    with zipfile.ZipFile(f"data/{FILES['eia923']}") as z:
        d = pd.read_excel(io.BytesIO(z.read("EIA923_Schedules_2_3_4_5_M_12_2025_Final.xlsx")), sheet_name=sheet, skiprows=5)
    d.columns = [c.replace("\n", " ") for c in d.columns]
    return d


GROUP = {"Conventional Steam Coal": "coal", "Coal Integrated Gasification Combined Cycle": "coal",
         "Natural Gas Fired Combined Cycle": "gas", "Natural Gas Fired Combustion Turbine": "gas",
         "Natural Gas Steam Turbine": "gas", "Natural Gas Internal Combustion Engine": "gas",
         "Natural Gas with Compressed Air Storage": "gas", "Other Natural Gas": "gas", "Nuclear": "nuclear",
         "Onshore Wind Turbine": "wind", "Offshore Wind Turbine": "wind", "Solar Photovoltaic": "solar",
         "Solar Thermal with or without Energy Storage": "solar", "Conventional Hydroelectric": "hydro",
         "Batteries": "storage", "Hydroelectric Pumped Storage": "storage", "Flywheels": "storage",
         "Petroleum Liquids": "oil", "Petroleum Coke": "oil", "Wood/Wood Waste Biomass": "biomass",
         "Landfill Gas": "biomass", "Municipal Solid Waste": "biomass", "Other Waste Biomass": "biomass",
         "Geothermal": "geothermal"}
gen = read_860("3_1_Generator_Y2025.xlsx", "Operable")
gen["MW"] = pd.to_numeric(gen["Nameplate Capacity (MW)"], errors="coerce")
gen["group"] = gen.Technology.map(GROUP).fillna("other")
plants = read_860("2___Plant_Y2025.xlsx")
print(f"{len(plants):,} plants, {len(gen):,} operable generators, {gen.MW.sum() / 1000:,.1f} GW nameplate")
17,349 plants, 27,918 operable generators, 1,376.7 GW nameplate

Example 1: monthly values for the increment

months = [c for c in g.columns if c.startswith("Netgen ")]
im = inc[months].replace(".", np.nan).astype(float).sum() / 1e6
print("plant 99999, TWh by month:", im.rename(lambda c: c.split()[1][:3]).round(3).to_dict())
plant 99999, TWh by month: {'Jan': 0.383, 'Feb': 1.765, 'Mar': 0.741, 'Apr': 1.246, 'May': 1.167, 'Jun': 2.06, 'Jul': 0.413, 'Aug': 0.625, 'Sep': 1.079, 'Oct': 1.148, 'Nov': 0.947, 'Dec': 0.908}

The increment changes month by month β€” 0.383 TWh in January, 2.06 TWh in June β€” so it affects monthly series too, not only annual totals.

Bar chart of plant 99999 net generation by month in 2025, varying between about 0.4 and 2 TWh Bar chart of plant 99999 net generation by month in 2025, varying between about 0.4 and 2 TWh
The increment changes month by month: 0.383 TWh in January, 2.06 TWh in June.

Example 2: other placeholder IDs

odd = g[~g["Plant Id"].isin(plants["Plant Code"].astype(int))]
print(f"plant IDs in EIA-923 with no EIA-860 plant: {odd['Plant Id'].nunique()}; their generation {odd['Net Generation (Megawatthours)'].sum() / 1e6:.2f} TWh")
plant IDs in EIA-923 with no EIA-860 plant: 38; their generation 12.48 TWh

38 plant IDs in EIA-923 have no EIA-860 plant, and their generation is 12.48 TWh β€” the same as plant 99999 alone. The other 37 report no net generation in 2025, so 99999 is the only one that matters for generation totals.

Why EIA adds generation without a plant

EIA-923 is a census for large plants and a sample for many smaller ones: plants below a size threshold report annually or are estimated. EIA publishes state and national statistics that must account for all generation, so it adds the difference between its estimates and the plant-level data as a pseudo-plant per state, fuel and prime mover. The name says what it is: a state-fuel level increment.

For most work the increment is too small to change conclusions, but it breaks exact reconciliation. If your plant-level sum is compared with EIA's Electric Power Monthly or a state profile, the increment is the gap; if your map is compared with a national total, it is the gap the other way.

Handling it in practice

  • Name it in code. INCREMENT = 99999 and a comment are clearer than a filter on a magic number.
  • Groupby by plant puts all increments together as one huge "plant" with rows from 45 states; filter it before ranking plants.
  • Fuel consumption columns have increments too; the same rule applies to fuel and emissions estimates.
  • Puerto Rico is reported on a separate sheet with its own caveats.

FAQ

What is plant ID 99999 in EIA-923?

A "State-Fuel Level Increment": generation EIA includes in state and national totals but does not assign to a plant. In 2025 it was 12.48 TWh.

Should I drop plant 99999?

For plant-level work, yes; for state or national totals, keep it. State which you did.