Published September 17, 2026 | Version v2026.9.0

Public Utility Data Liberation Project (PUDL) Data Release

Description

v2026.9.0 (2026-09-11)

This is a regular monthly PUDL data release, primarily motivated by updating the EIA-860M monthly data through August 2026. The biggest change this month is that we've started publishing a DuckDB version of the PUDL database! We will be deprecating the old pudl.sqlite database in January, 2027.

We also closed a gap where Pandera's Polars backend was silently skipping some of the schema validation checks declared in our metadata (thankfully they were being enforced independently elsewhere!). We've added several new tables (EIA-176 company characteristics, EIA-923 fuel stocks and energy storage, PHMSA distribution mains by install decade), and extended the new CEMS-derived operational characteristics analysis back to 2000. See below for all the details, and the linked PRs and issues for the full story.

Output Formats & Distribution

  • Added a fully processed ``pudl.duckdb`` database. After the ETL completes we now assemble all of the non-hourly PUDL tables into a single DuckDB database named pudl.duckdb alongside pudl.sqlite, both built directly from the Parquet outputs. It preserves the full set of column checks and primary key constraints. Foreign key constraints are currently omitted due to a handful of column type conflicts outlined in issue #5552 being addressed in PR #5554. Both databases are published to S3, GCS, and Zenodo along with our other outputs. See Data Access. See PR #5538.

  • The fully processed ``pudl.sqlite`` database is deprecated. PUDL's ETL no longer writes SQLite directly; pudl.sqlite is now built from the Parquet outputs after the ETL purely for backwards compatibility. We will stop producing SQLite versions of the fully processed PUDL data in 2027. Please migrate to the Parquet outputs or the new pudl.duckdb database. This deprecation does not affect the minimally processed raw FERC data, which will continue to be distributed as SQLite for the time being, but will likely transition at some point in the near future as well. See PR #5538.

New Data

EIA-176

  • Added core_eia176__yearly_company_characteristics with company operation type, ownership type, and company characteristic fields from EIA Form 176 Part 3 (Lines A-D). Includes alternative_fleet_size, customer_choice_residential_eligible, customer_choice_residential_participating, has_sales_or_acquisitions, and natural_gas_pump_price_dollars_per_mcf (2014-2016 only). National-level adjustment records (operating_state FX, MX, BL, OO) are excluded. The raw is_other_ownership and is_other_ownership_2 fields (which never co-occur) are merged into a single is_other_ownership boolean. See #4697 and #5412. Thank's to @MeadBarrel for all the work on getting natural gas data integrated into PUDL, and the ongoing support from @switchbox-data.

EIA-923

EPA CEMS

  • Added a new analysis output, out_epacems__yearly_operational_characteristics, which estimates generator operational characteristics such as minimum stable operating level, minimum up/down times, ramp rates, and heat rates at maximum and minimum load, inferred from hourly EPA CEMS gross load and fuel heat content data over a rolling three-year window. This dagsterizes and vectorizes an analysis originally developed by Sylvan Energy, making it available for all reporting states rather than just California. The output is experimental and marked accordingly, since we are soliciting feedback from the community on the underlying methodology. See issue #5106 and PR #5190.

  • out_epacems__yearly_operational_characteristics now reports these estimates for every calendar year with a full three-year trailing window of usable EPA CEMS data, rather than only the most recent year, going back to 2000 (EPA CEMS's first three reporting years, 1995-1997, are excluded due to known poor unit coverage). Also recalibrated the associated dbt data validations against physically grounded bounds (e.g. the 3.412 MMBtu/MWh thermodynamic floor on heat rates, and the exact trailing window length as an upper bound on minimum up/down times) rather than thresholds fit to a single year of data. See PR #5474.

PHMSA

  • Added the core_phmsagas__yearly_distribution_by_install_decade table, which reports PHMSA gas distribution mains miles and services by installation decade. See issue #5266 and PR #5443.

Expanded Data Coverage

EIA-191

  • Updated EIA-191 data to include additional records through June 2026. See PR #5572.

EIA-860M

EIA-861

EIA-930

  • Updated EIA-930 data to include data published up through the beginning of September 2026. See PR #5572.

EIA API Bulk Electricity Data

  • Updated the EIA Bulk Electricity data to include data published up through the beginning of June 2026. See PR #5572.

EPA CEMS

  • Updated the EPA CEMS data with additional records through June 2026. See PR #5572.

EPA MATS

  • Updated the raw EPA MATS archive with additional records through 2026 Q2 (June 2026). This data is not yet deeply integrated into PUDL. See PR #5572.

NREL ATB

  • Updated the NREL ATB extractor and transformer to accommodate changes to the 2024 data and format. See issue #5467 and PR #5513.

  • Added NREL ATB 2025 data. See PR #5569.

PHMSA

  • Added 2025 distribution and transmission data for PHMSA. See issue #5504 and #5548.

FERC EQR

FERC Form 2

  • Updated the raw FERC Form 2 archive to include additional 2025 data. This data is converted to SQLite, but not deeply integrated into PUDL. See PR #5572.

Vibrant Pattern Futures Resource Adequacy Renewable Energy (RARE) Power Dataset

New Data Tests & Validations

  • The subcomponents_sum_to_total dbt test can now identify subcomponents and totals by a combination of categorical columns (e.g. (cost_group, cost_type) tuples) rather than values from a single column, enabling validation of calculations that cross more than one categorical column. Used this to complete the totals validations for core_rus12__yearly_plant_costs and out_rus12__yearly_plant_costs, adding maintenance, operations & maintenance, fixed cost, and total power cost checks, and fixing several existing checks that referenced non-existent cost categories. The test also gained an opt-in minimum_total_coverage argument that catches totals which silently match no records at all (e.g. a misspelled total_label or a category renamed in a future data update), enabled for all of the plant costs checks. See issues #5378, #5154 and PR #5510.

  • Calibrated per-year error thresholds for the heat-rate outlier checks on out_epacems__yearly_operational_characteristics, since the multi-year extension in PR #5474 introduced report years whose data quality, and thus expected outlier rate, varies considerably over the 2000-2025 span. See PR #5571.

Bug Fixes & Data Cleaning

  • Fixed a bug in out_eia__yearly_generators_by_ownership where every ownership record for a jointly owned generator reported the plant operator's utility_id_pudl and utility_name_eia instead of the owner's. When ownership slices are generated, the owner's PUDL utility ID and EIA utility name are now swapped in alongside the owner's utility_id_eia. See issue #5430 and PR #5506.

  • Fixed set_gcs_temporary_hold only protecting the top level of a versioned release path from deletion. It shelled out to gcloud storage objects update gs://bucket/prefix/*, and that glob only matches one path segment, so anything nested in a subdirectory was silently left unheld. Updated the function to use the google-cloud-storage API to recursively hold every object under the prefix and verify after the fact that none were missed. Manually re-applied the hold to 4,000+ previously published versioned release objects that had been missed by the original bug. See PR #5477.

  • Fixed EIA-176 extraction bug where raw_eia176__operation_types_and_sector_items was always empty due to a mismatched page key. See #4697 and #5412.

  • Recovered dbt data validation tests that were being silently dropped from several tables as their source schema.human.yml files still used the deprecated tests: key instead of data_tests:, meaning dbt_helper silently discarded the tests they contained instead of merging them into the generated schema.yml. See PR #5458.

  • Fixed several sources of non-deterministic row counts, where identical code and data produced different results on different machines (e.g. local macOS vs. nightly Linux builds) because several functions resolved ties among candidate values using incidental pandas/numpy sort or dedup behavior instead of an explicit, deterministic rule. Affected tables now use the standard, exact check_row_counts_per_partition dbt test in place of the looser row-count range checks previously used to work around the instability. See #4574, #4254, and #5503.

  • Fixed add_null_overrides() in the FERC1-EIA record linkage nulling out the condensed report_date, report_year, plant_id_pudl, and utility_id_pudl columns for every known-unmatched FERC1 record, instead of only the EIA match columns. With the fix, 788 records in out_pudl__yearly_assn_eia_ferc1_plant_parts which used to have a NULL report_date now appear with correct date information. See issue #4130 and PR #5503

  • Improved the robustness of FERC EQR raw data extraction. Filings that are missing one or more of the expected CSV files, or whose company identity information is missing or unparsable, now have as much of their data extracted as possible instead of being dropped entirely. Parquet outputs for FERC EQR's constrained categorical columns also now preserve their full set of allowed values, matching the behavior of other PUDL categorical columns. See PR #5442.

  • Disabled primary key uniqueness enforcement on core_ferceqr__quarterly_index_pub, which was found to contain duplicate primary key values in FERC EQR filings from 2023 onward. See PR #5442.

  • Fixed a race condition that intermittently failed the docs build due to the HTML and Markdown builds attempting to clean up the same dynamically generated output files at the end of their build. Fixed by setting llms_txt_build_parallel = False. See issue #5502 and PR #5516.

  • Changed subplant_id in core_epa__assn_eia_epacamd_subplant_ids to be 1-indexed instead of 0-indexed within each plant_id_eia, so the first subplant at a plant is now 1 rather than 0. See issue #5499 and PR #5541.

  • Retired the interim output _core_phmsagas__yearly_distribution_by_install_decade, which was replaced by the cleaned and validated core_phmsagas__yearly_distribution_by_install_decade. See #5504 and #5548.

  • Fixed the DuckDB examples in Data Access and the per-table access snippets in the data dictionary. Because our S3 bucket name contains dots, DuckDB's default virtual-host addressing hit a TLS certificate mismatch; the examples now create an anonymous path-style S3 secret (CREATE SECRET (TYPE s3, PROVIDER config, REGION 'us-west-2', URL_STYLE 'path')) before querying. See PR #5538.

  • Added a data validation test that checks pudl.sqlite, pudl.duckdb, and the Parquet outputs are mutually consistent: every table defined in PUDL_PACKAGE is present in both databases, neither database has extra tables, and every table has the same columns and the same row count in SQLite, DuckDB, and its source Parquet file. See PR #5538.

  • Closed a long-standing gap in which Pandera's Polars backend only checked column presence and dtype for pl.LazyFrame assets, silently skipping every range, enum, nullability, regex, and uniqueness check declared in our metadata for the vast majority of PUDL tables. Content validation is now explicitly enabled for these assets, checked one column at a time to keep memory bounded even on PUDL's largest tables. Thankfully these schema checks were also being enforced through independent mechanisms, so no data quality issues resulted from this gap. See PR #5432.

  • Corrected the last_annual_meeting_date field in core_rus7__yearly_meeting_and_board and core_rus12__yearly_meeting_and_board from a datetime to a date type. See PR #5518.

Performance Improvements

  • Fixed a performance regression resulting from the update to Numpy 2.5, which ended up using complex arithmetic in calculating eigenvalues due to floating point noise in the imaginary components of the matrix math we were doing in our timeseries imputations. See PR #5503.

Developer Experience

  • Added ferceqr_pipeline_diagnostics, a metadata-only Dagster asset that compiles the extraction stats and schema-check results already recorded for every FERC EQR quarter into one wide table, so anomalies like missing tables, rejected records, or primary-key violations are visible across the whole backfill without opening each quarter's materialization individually. See PR #5457.

  • Reworked how the Google Batch jobs launched by the build-pudl, deploy-pudl, and build-deploy-ferceqr GitHub workflows are configured. The Batch config generator moved from devtools/ into the package as the batch_config script (pudl.scripts.batch_config); the workflows now select an explicit VM machine type and boot disk rather than passing raw resource counts, and every VM and its logs are tagged with the pipeline that launched them so a shared Cloud Monitoring dashboard can filter resource-usage metrics by pipeline. VM sizes and the ETL's process and thread parallelism were tuned to match measured resource usage and stop oversubscribing the CPUs. See #5545.

  • Branch builds (build-pudl runs triggered via workflow_dispatch) now skip the S3 deployment by default and only deploy to GCS. S3 egress fees cost more than a full ETL run, and the nightly build already exercises the real S3 deployment every night. The build-pudl and deploy-pudl workflow-dispatch forms expose deploy_to_gcs / deploy_to_s3 checkboxes to override this per run, and when neither target is enabled build-pudl skips triggering deploy-pudl altogether (e.g. a build run only to regenerate row counts). Nightly and stable deployments are unchanged and still deploy to both. See issue #5557 and PR #5558.

  • Fixed several issues with how dbt_helper update-tables renders schema.yml (pudl.dbt_schema): long description: fields are now wrapped into readable paragraph blocks and strings that need quoting prefer double quotes. This now matches Prettier's YAML conventions, minimizing the need for reformatting after generation. Standardized multi-line description: fields across all schema.human.yml inputs. dbt_helper update-tables --schema --clobber all is now idempotent across all tables. See PR #5458.

  • Pydantic models representing dbt structures defined in pudl.dbt_schema now reject any unrecognized keys (like stray tests: instead of data_tests:) at parse time instead of silently discarding them. Whitespace in description fields is also normalized at parse-time to avoid spurious diffs. See PR #5458.

  • Reorganized tests/ into four tiers by run-time: fast unit tests run as a pre-commit hook, slower integration tests run on every push, and slower ETL-dependent pipeline and data validation tests run in the merge queue. Previously tests/integration mixed ETL and integration tests together, so many lightweight tests weren't getting run until the merge queue, leading to unexpected late-stage failures. A pytest collection hook enforces the ETL/no-ETL split. Also fixed a live Zulip notification firing from the test suite and tightened the dbt schema.yml round-trip test. See issue #5508 and PR #5507.

  • Validate foreign key constraints with dbt instead of SQLite. Update our dbt_helper script to autogenerate FK constraint tests based on the PUDL metadata. Remove the SQLite based FK checking infrastructure. Also add sensible defaults for our row-count expectation checking test so we can remove boilerplate test specs. See issues #4564, #5208 and PR #5519.

  • Automated updating the Zenodo deposition metadata (creators, keywords, version, description, and structured resource links) for monthly PUDL data releases, which previously had to be hand-edited in the Zenodo web UI every month. Creators and keywords are now read from .zenodo.json, and the description is assembled from the built release notes for that version plus a footer of release-specific resource links (versioned docs, data dictionary, S3/GCS paths, the GitHub release, and the corresponding GitHub-repo Zenodo software archive), which are also populated as structured related_identifiers for better DataCite/OpenAIRE indexing. See issue #3326 and PR #5484.

  • Removed Alembic and the PUDL SQLite schema migrations. With PUDL's own tables no longer written to SQLite during the ETL, there is no schema for Alembic to manage, so alembic.ini, the migrations/ directory, and the alembic dependency have been removed. See PR #5538.

  • Replaced the pudl_engine pytest fixture (a SQLAlchemy engine) with pudl_sqlite_connection alongside a pudl_duckdb_connection. Both of which are DuckDB connections. One dedicated to reading pudl.sqlite via DuckDB's sqlite extension, so tests query both build outputs through one API as PUDL moves toward DuckDB. See PR #5538.

Other PUDL v2026.9.0 Resources

Contact Us

If you're using PUDL, we would love to hear from you! Even if it's just a note to let us know that you exist, and how you're using the software or data. Here's a bunch of different ways to get in touch:

Files

censusdp1tract.sqlite.zip

Files (29.8 GB)

Name Size
md5:4e64b8560450cf9610077c1a2cedadf5
506.7 MB Preview Download
md5:5239df64ba57d4bbd8f879548a0583e5
327.2 kB Preview Download
md5:e1e6f1031059f22d85d9fac238f85b52
275.0 MB Preview Download
md5:b82a9c89408a87f2fadee1e2ac7f2c62
202.9 MB Preview Download
md5:7e864459106af44bdc1bedcb98504361
274.6 kB Preview Download
md5:702faefc2138ebdc2aec18c43d7cae5e
1.2 GB Download
md5:d79872a8b9f30825ed38f9c8d17fc7e8
239.9 MB Preview Download
md5:d5e585c3dbcf21d5914f26d13c89ee05
202.7 MB Preview Download
md5:ac791f866f0cb67bb1b1f04bd0f48192
2.3 MB Preview Download
md5:ee5c7f18d569c58f3f008b96a16bedff
8.5 MB Preview Download
md5:4eb401259482f13f273c5482e547b970
74.7 MB Preview Download
md5:329b1fad43620746a516f719a4305e01
48.3 MB Preview Download
md5:e3e61cc19e85ede64ab84de4099bf259
221.2 kB Preview Download
md5:76c5747a4244f162463cfca11ca5e66c
148.9 MB Download
md5:5c6ef340d648ef904dbb87158de56698
36.0 MB Preview Download
md5:fdf4322578e90a62f7e98614a013a59c
23.3 MB Preview Download
md5:cb515032c8d9c1e2ab3cd826b2526a9b
2.3 MB Preview Download
md5:bd09d11e1a79a1623b3d9789bf336c9e
7.2 MB Preview Download
md5:a5bf71167aa426be3a030d1f7e6f06cd
2.9 MB Preview Download
md5:b14d153e4b3c5e203b4bf0c3e5c71ea3
2.8 MB Preview Download
md5:90a7386f6ee4339e182d22585f3ef79d
72.2 kB Preview Download
md5:94f2f336da56ce35c3022d6a15bed985
50.9 MB Download
md5:b02d8cac4cefda28f5db95849afc0da9
5.4 MB Preview Download
md5:0e2e88ae13513c90aafbe4509e3d70ee
6.6 MB Preview Download
md5:dd3d996292a7d8abda1786f324d4d611
978.1 kB Preview Download
md5:ba679325b7ae96c87b6fa5584cfae491
2.0 MB Preview Download
md5:dcb61c62bb28d318ff5e646483476893
44.0 MB Preview Download
md5:93ca5baf9e1d1587768a524aabd86269
27.2 MB Preview Download
md5:ca7d402847ca8025450e1c0372f7fb2a
127.9 kB Preview Download
md5:3756bc76d3a179f40dbb1df1b6cb1bec
71.6 MB Download
md5:c6d0545c9fb00ff227abbc9f8fa1d0e6
29.0 MB Preview Download
md5:9596df5033694640cfda1297f6facadb
15.1 MB Preview Download
md5:1ee15665cb980eda6dd95d3199c8a255
1.3 MB Preview Download
md5:240ab648e9b6fb13f2540b217638676e
3.0 MB Preview Download
md5:10aef9cb5046ef110b7448ac90ebd4d8
83.9 MB Download
md5:d9b4204ded3d5948f9a73e573523d39a
243.1 MB Preview Download
md5:144ce3037e1cf556092707e6f8374d4e
43.4 MB Preview Download
md5:25e18b72f3b5effdf053d7721e52270d
65.7 kB Preview Download
md5:83bcf37029395440c6a0efb7ce49ee8e
192.9 kB Preview Download
md5:4b01272f7761dd2cb4366178a83f1a0d
38 Bytes Download
md5:bec6189f321f0d5740f701a45b8de79f
9.5 GB Download
md5:c3a5c872afc8c79c2ba16fbcf4213486
3.5 GB Preview Download
md5:ee54a52952326ea54360f71c25037144
13.2 GB Preview Download
md5:04467fe97cb409aebd4dab8094f37ae7
4.5 MB Preview Download

Additional details