FERC EQR Filing Automation

A quarter closes, a settlement analyst exports three months of wholesale power transactions into a spreadsheet, and the manual reformatting begins — until a mislabeled Class Name, a missing contract identifier, or a price expressed in the wrong rate unit bounces the whole submission back from FERC’s validation engine days before the deadline. That failure mode is what a FERC Electric Quarterly Report (EQR) automation pipeline exists to eliminate. Within the Regulatory Filing Preparation framework, this component takes the same reconciled ledger that priced and settled each trade and re-projects it into the exact transaction and contract layout FERC’s Order No. 2001 data dictionary demands, validates every field against the enumerated code lists, and produces a submission package that clears FERC’s own checks on the first attempt. It draws its source figures directly from the Settlement Calculation & Validation Engines so that what you report to the regulator is byte-for-byte reconcilable with what you invoiced the counterparty.

The distinction that governs the whole design is that an EQR is not one dataset but two linked ones — an infrequently changing set of contracts and a high-volume set of transactions that reference them — and the automation must keep referential integrity between them while reshaping ledger rows that were never structured for FERC in the first place.

The diagram below traces the pipeline this component implements, from the settlement ledger through filable-transaction extraction, schema mapping, and validation into the packaged CSV datasets submitted to FERC’s EQR system.

FERC EQR generation pipeline The settlement ledger feeds a filter that selects FERC-jurisdictional transactions. Those rows are mapped to the EQR transaction and contract datasets, validated against the Order 2001 data dictionary and referential-integrity rules, and packaged into two CSV files submitted to FERC's EQR system. Records that fail validation route to an exception queue that re-enters the mapping stage. Settlementledger Filter filableFERC-jurisdictional Map to EQRtxn + contract Validatedictionary + refs Package CSVtwo datasets SubmitFERC EQR Exception queuefix + re-map fail re-map

Everything downstream of extraction is a deterministic transformation: given the same reconciled ledger snapshot and the same code revision, the pipeline emits identical EQR files, so a resubmission or an audit response reproduces the original filing exactly. The sections that follow fix the regulatory specification the files must satisfy, walk the production build in numbered steps, catalogue the failure modes that bounce a filing, and describe the reconciliation and alerting that keep the quarterly cycle boring.

Specification & Standards

The EQR was established by FERC Order No. 2001, which requires public utilities with market-based rate or cost-based authority to report their wholesale electric sales and transmission transactions every calendar quarter. The obligation now rides on FERC Form No. 920, and the field-level contract is the FERC EQR Data Dictionary — the authoritative enumeration of every column, its data type, its allowed values, and whether it is mandatory. An automation pipeline treats that dictionary as versioned input data, exactly the way the Settlement Calculation & Validation Engines treat a tariff revision as versioned input rather than hard-coded logic, so a dictionary revision is a data change rather than a code rewrite.

An EQR submission carries three logical parts: identification (the filer and any agent), contracts, and transactions. The identification block is nearly static across quarters. The interesting engineering is the relationship between the contract and transaction datasets, because every transaction row must reference a contract that is either present in the same filing or was established in a prior one. Getting that referential link wrong is the most common structural rejection.

Dataset Cardinality per quarter Changes Keyed by Purpose
Identification 1 filer block Rarely Company/CID Who is filing and on whose behalf
Contract Tens to hundreds On execution / amendment Contract Unique ID Terms of each service agreement
Transaction Thousands to millions Every reporting quarter Transaction Unique ID Each priced delivery under a contract

The transaction dataset is where a settlement ledger maps most directly. The dictionary fixes both the columns and their coded vocabularies; the table below lists the transaction fields that a ledger-to-EQR mapper must populate and the enumerations FERC validates them against.

EQR transaction field Meaning Type / allowed values
Transaction Unique ID Stable identifier for the transaction String, unique within filer
Transaction Begin/End Date Delivery window bounds Datetime, YYYYMMDDHHMMSS
Trade Date Date the deal was struck Date, on or before begin date
Time Zone Reference zone for the timestamps ED, CD, MD, PD, EP, CP, MP, PP, AD, AP
Point of Delivery Balancing Authority Delivery control area Registered BA/CA code
Class Name Firmness of the product F firm, NF non-firm, UP unit power
Term Name Duration bucket LT long-term, ST short-term
Increment Name Delivery granularity H, D, W, M, Y
Increment Peaking Name Peak coverage FP, P, OP
Product Name What was sold ENERGY, CAPACITY, BOOKED OUT, TRANS. …
Transaction Quantity Volume delivered Decimal, in the stated units
Price Unit price Decimal
Rate Units Denominator of the price $/MWH, $/MW-DAY, $/KW-MO, FLAT RATE
Standardized Quantity/Price Normalized MWh and $/MWh Decimal, FERC-normalized
Total Transmission Charge Transmission cost on the deal Decimal
Transaction Charge Total consideration Decimal

The reporting calendar is unforgiving and drives the automation’s scheduling. Each quarter’s report is due within 30 days of the quarter’s close, and a filing that fails FERC’s validation is not considered filed — so the pipeline must leave slack for a rejection-and-resubmit loop rather than aiming at the deadline itself.

Reporting quarter Delivery period Filing due (approx.)
Q1 Jan 1 – Mar 31 Apr 30
Q2 Apr 1 – Jun 30 Jul 31
Q3 Jul 1 – Sep 30 Oct 31
Q4 Oct 1 – Dec 31 Jan 31

The concrete mechanics of emitting the transaction CSV — exact column order, Decimal price and quantity handling, timezone-correct timestamp formatting, and an audit manifest — are worked end to end in Generating FERC EQR transaction reports in Python. This section stays at the level of what the datasets are and why; that guide is the reference implementation of how.

Step-by-Step Implementation

A production EQR pipeline is a short, auditable sequence of pure transformations over a reconciled ledger snapshot. Each step below is idempotent and keyed so a re-run replaces only the affected rows.

1. Pin the reporting window and the ledger snapshot. The pipeline resolves the quarter to an explicit UTC-anchored delivery window and selects the finalized ledger as of a fixed snapshot time, so a re-run for an amended filing reads the same source rows.

from datetime import datetime, timezone
from decimal import Decimal

def resolve_quarter(year: int, quarter: int) -> dict:
    """Map a reporting quarter to explicit delivery-window bounds."""
    starts = {1: (1, 1), 2: (4, 1), 3: (7, 1), 4: (10, 1)}
    ends = {1: (3, 31), 2: (6, 30), 3: (9, 30), 4: (12, 31)}
    m0, d0 = starts[quarter]
    m1, d1 = ends[quarter]
    return {
        "run_id": f"EQR-{year}Q{quarter}",
        "window_start": datetime(year, m0, d0, 0, 0, tzinfo=timezone.utc),
        "window_end": datetime(year, m1, d1, 23, 59, 59, tzinfo=timezone.utc),
    }

2. Filter to FERC-jurisdictional, filable transactions. Not every ledger row is reportable — retail deliveries, bookouts handled elsewhere, and non-jurisdictional sales are excluded per the filer’s reporting posture. The filter is explicit and logged so an auditor can see exactly what was and was not reported.

def select_filable(ledger_df, window: dict):
    """Keep only FERC-reportable wholesale rows inside the delivery window."""
    in_window = (
        (ledger_df["delivery_start"] >= window["window_start"])
        & (ledger_df["delivery_start"] <= window["window_end"])
    )
    reportable = ledger_df["ferc_reportable"] & (ledger_df["market_segment"] == "WHOLESALE")
    filable_df = ledger_df[in_window & reportable].copy()
    filable_df["run_id"] = window["run_id"]
    return filable_df

3. Resolve each transaction to its contract. Every filable row must carry a contract_unique_id that resolves to a contract in this filing or a prior one. A transaction whose contract cannot be resolved is an exception, never a silent blank.

def attach_contract_refs(filable_df, contract_master: dict):
    """Bind each transaction to its governing contract; flag orphans."""
    filable_df["contract_unique_id"] = filable_df["deal_id"].map(contract_master)
    orphans = filable_df[filable_df["contract_unique_id"].isna()]
    if not orphans.empty:
        # Route to the exception queue rather than emit an unlinked row.
        raise ValueError(f"{len(orphans)} transactions have no resolvable contract")
    return filable_df

4. Map ledger columns onto the transaction dataset. This is the projection into the dictionary’s coded vocabularies — firmness to Class Name, delivery cadence to Increment Name, ledger money into Decimal price and charge fields. The full column-accurate mapper is the subject of Generating FERC EQR transaction reports in Python.

5. Validate, package, and stage for submission. The two datasets are written as CSVs in the dictionary’s column order, a manifest hash is computed over each, and the package is staged for the FERC EQR system. The transaction-charge total is reconciled back against the ledger before anything is submitted.

Because contract data mutates only on execution or amendment, most quarters re-emit an unchanged contract dataset and a wholly new transaction dataset — a property the mapper exploits by caching the contract projection keyed on the contract master version.

Edge Cases & Failure Modes

The rejections that consume a filing team’s week are rarely arithmetic — they are structural and temporal. The table catalogues the recurring ones and the deterministic handling each requires.

Failure mode Symptom Handling
Orphan transaction Transaction references a contract absent from this and all prior filings Block the row to the exception queue; require a contract row or a corrected reference
Rate-unit mismatch Price expressed in $/MWH but quantity carried as MW-day Normalize units at map time; assert Rate Units matches the quantity basis
DST boundary in delivery window Spring-forward hour missing or fall-back hour duplicated in Transaction Begin/End Date Localize to the market clock, emit explicit YYYYMMDDHHMMSS per occurrence, never a naive index
Negative price on a real deal Congestion drove a settled price below zero Preserve the sign — a negative Price is valid and must not be clamped
Zero-volume interval Curtailed or booked-out delivery with Transaction Quantity of 0 Emit if the deal is reportable; suppress only per the filer’s documented bookout policy
Data-dictionary drift FERC revises an enumerated code list or column Re-load the versioned dictionary; fail closed on any unmapped code
Stale ledger snapshot Late resettlement changed a figure after extraction Re-pin the snapshot and re-run the quarter idempotently on run_id

Negative prices and DST boundaries are the two that most often survive a naive spreadsheet process and detonate at FERC’s validator. The sign-preservation discipline is the same one the settlement layer applies to negative congestion, and the timestamp discipline mirrors the timezone-aware handling used throughout Trade Ingestion & Matching Workflows. Treating the dictionary itself as versioned data — and failing closed when a code no longer maps — is what stops silent schema drift from producing a technically valid but substantively wrong filing.

Thresholds & Alerting

An EQR pipeline earns its keep by surfacing problems while there is still time to fix them, which means alerting keys off the calendar as much as off the data. Configurable parameters govern how early the pipeline runs a dry pass and how aggressively it escalates.

  • dry_run_offset_days — how many days before the deadline the pipeline builds and validates the package without submitting, so a rejection is discovered with slack to spare. A common posture is 10 days.
  • charge_reconciliation_tolerance — the maximum permitted absolute difference, in dollars, between the summed Transaction Charge and the ledger’s settled total for the quarter. A Decimal("0.00") target with a small non-zero band accommodates documented rounding.
  • orphan_alert_threshold — the count of unresolved-contract transactions that escalates from informational to blocking.

Alerts route by tier much as settlement exceptions do. Informational alerts (a handful of rows normalized) log and continue; review-required alerts (a rate-unit normalization touched many rows, or the reconciliation residual approached its band) notify the filing owner; blocking alerts (any orphan transaction, a dictionary code that failed to map, or a reconciliation breach) halt submission and page the on-call analyst. Routing severity by tier keeps a single malformed contract from silently degrading a filing while also refusing to drown the team in noise for benign normalizations — the same discipline described in Settlement Calculation & Validation Engines.

Testing & Reconciliation

The controlling correctness property is that the EQR is a faithful re-projection of the ledger, so the primary test is a shadow reconciliation: independently sum the settled charges for the quarter straight from the Settlement Calculation & Validation Engines output and assert it equals the summed Transaction Charge across the emitted transaction dataset, to the cent.

from decimal import Decimal

def reconcile_eqr_to_ledger(eqr_rows, ledger_total: Decimal,
                            tolerance: Decimal = Decimal("0.00")) -> None:
    """Assert the EQR transaction charges reconcile to the ledger's settled total."""
    eqr_total = sum((Decimal(str(r["transaction_charge"])) for r in eqr_rows),
                    start=Decimal("0.00"))
    residual = abs(eqr_total - ledger_total)
    if residual > tolerance:
        raise AssertionError(
            f"EQR charge total {eqr_total} off ledger total {ledger_total} "
            f"by {residual} (tolerance {tolerance})"
        )

Unit tests pin the coded-vocabulary mappings against fixtures: a firm hourly energy sale must map to Class Name F, Increment Name H, Product Name ENERGY; a negative-price row must survive the mapper with its sign; a delivery window crossing a DST boundary must emit the correct number of hourly occurrences. Contract referential integrity is tested by asserting that the set of contract_unique_id values in the transaction dataset is a subset of the union of this filing’s contracts and the prior-filing contract master. A golden-file test — compare the generated CSV byte-for-byte against a hand-verified fixture for a known quarter — catches column-order regressions that FERC’s validator would otherwise catch far later. The schema-enforcement approach mirrors Schema Validation Frameworks, applied to the EQR dictionary rather than an internal trade contract.

Frequently Asked Questions

What are the two datasets in a FERC EQR and how do they relate?

An EQR carries a contract dataset and a transaction dataset (plus a mostly static identification block). Contracts change only on execution or amendment and are keyed by a Contract Unique ID; transactions are the high-volume, per-delivery rows and each must reference a contract that appears in the same filing or was established in a prior one. Maintaining that referential link is the main structural constraint — an unresolved reference is the most common cause of rejection.

How do I map a settlement ledger to EQR transaction rows?

Filter the ledger to FERC-reportable wholesale rows inside the quarter’s delivery window, resolve each row to its governing contract, then project ledger columns onto the dictionary’s coded vocabularies — firmness to Class Name, cadence to Increment Name, money into Decimal price and charge fields, and delivery bounds into explicit YYYYMMDDHHMMSS timestamps in the correct market time zone. The column-exact mapper and CSV emitter are covered in Generating FERC EQR transaction reports in Python.

When is the EQR due and what happens if it fails validation?

Each quarter’s report is due within 30 days of the quarter’s close — roughly April 30, July 31, October 31, and January 31. A filing that fails FERC’s validation is not considered filed, so a pipeline should run a validating dry pass several days early to leave slack for a rejection-and-resubmit loop rather than aiming at the deadline itself.

Why treat the FERC EQR data dictionary as versioned input data?

FERC periodically revises column definitions and enumerated code lists. Loading the dictionary as versioned data — rather than hard-coding its codes — means a revision is a data change, and the pipeline can fail closed whenever a ledger value no longer maps to a valid code. That prevents silent schema drift from producing a filing that passes structural checks but reports substantively wrong values.

Explore this topic