Skip to content

Commit

Permalink
Add column mapping for proposed multifuel table (#3988)
Browse files Browse the repository at this point in the history
* Add column mapping for proposed multifuel table

* Add multifuel transform

* Clean more boolean columns

* Address most multifuel review feedback

* Update primary key and boolean field names

* Add release note

* Add report_date and drop pk dupes

* Update alemic

* Update src/pudl/metadata/resources/eia860.py

* Update field descriptions

* Update column names and alembic

* Quick update to limit column descriptions

* Update 860 docs

---------

Co-authored-by: Austen Sharpe <[email protected]>
Co-authored-by: Zane Selvans <[email protected]>
Co-authored-by: Austen Sharpe <[email protected]>
  • Loading branch information
4 people authored Jan 29, 2025
1 parent c0eb11d commit f904b0a
Show file tree
Hide file tree
Showing 18 changed files with 535 additions and 57 deletions.
4 changes: 4 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ v2024.XX.x (2024-MM-DD)
New Data Coverage
^^^^^^^^^^^^^^^^^

EIA 860
~~~~~~~
* Added EIA 860 Multifuel data. See :issue:`3438` and :pr:`3946`.

EIA 176
~~~~~~~
* Add a couple of semi-transformed interim EIA-176 (natural gas sources and
Expand Down
1 change: 0 additions & 1 deletion docs/templates/eia860_child.rst.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ Each quarter, PUDL also combines data from the most recent EIA-860M filing with
relevant EIA-860 tables. These records are marked with the ``monthly_update`` tag in the
``data_maturity`` field.

PUDL does not yet include the EIA-860 table for multi-fuel generators (Schedule 3.5).
{% endblock %}


Expand Down
152 changes: 152 additions & 0 deletions migrations/versions/3b65c445d4b4_add_multifuel_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Add multifuel table
Revision ID: 3b65c445d4b4
Revises: 450d100cd30b
Create Date: 2025-01-28 18:38:52.927885
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '3b65c445d4b4'
down_revision = '450d100cd30b'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('core_eia860__scd_generators_multifuel',
sa.Column('report_date', sa.Date(), nullable=False, comment='Date reported.'),
sa.Column('utility_id_eia', sa.Integer(), nullable=False, comment='The EIA Utility Identification number.'),
sa.Column('utility_name_eia', sa.Text(), nullable=True, comment='The name of the utility.'),
sa.Column('plant_id_eia', sa.Integer(), nullable=False, comment='The unique six-digit facility identification number, also called an ORISPL, assigned by the Energy Information Administration.'),
sa.Column('plant_name_eia', sa.Text(), nullable=True, comment='Plant name.'),
sa.Column('state', sa.Text(), nullable=True, comment='Two letter US state abbreviation.'),
sa.Column('county', sa.Text(), nullable=True, comment='County name.'),
sa.Column('generator_id', sa.Text(), nullable=False, comment='Generator ID is usually numeric, but sometimes includes letters. Make sure you treat it as a string!'),
sa.Column('operational_status_code', sa.Text(), nullable=True, comment='The operating status of the asset.'),
sa.Column('technology_description', sa.Text(), nullable=True, comment='High level description of the technology used by the generator to produce electricity.'),
sa.Column('prime_mover_code', sa.Text(), nullable=True, comment='Code for the type of prime mover (e.g. CT, CG)'),
sa.Column('sector_name_eia', sa.Text(), nullable=True, comment='EIA assigned sector name, corresponding to high level NAICS sector, designated by the primary purpose, regulatory status and plant-level combined heat and power status'),
sa.Column('sector_id_eia', sa.Integer(), nullable=True, comment='EIA assigned sector ID, corresponding to high level NAICS sector, designated by the primary purpose, regulatory status and plant-level combined heat and power status'),
sa.Column('capacity_mw', sa.Float(), nullable=True, comment='Total installed (nameplate) capacity, in megawatts.'),
sa.Column('summer_capacity_mw', sa.Float(), nullable=True, comment='The net summer capacity.'),
sa.Column('winter_capacity_mw', sa.Float(), nullable=True, comment='The net winter capacity.'),
sa.Column('current_planned_generator_operating_date', sa.Date(), nullable=True, comment='The most recently updated effective date on which the generator is scheduled to start operation'),
sa.Column('energy_source_code_1', sa.Text(), nullable=True, comment='The code representing the most predominant type of energy that fuels the generator.'),
sa.Column('energy_source_code_2', sa.Text(), nullable=True, comment='The code representing the second most predominant type of energy that fuels the generator'),
sa.Column('can_burn_multiple_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can burn multiple fuels.'),
sa.Column('can_cofire_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire fuels.'),
sa.Column('cofire_energy_source_1', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be co-fired.'),
sa.Column('cofire_energy_source_2', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be co-fired.'),
sa.Column('cofire_energy_source_3', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be co-fired.'),
sa.Column('cofire_energy_source_4', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be co-fired.'),
sa.Column('cofire_energy_source_5', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be co-fired.'),
sa.Column('cofire_energy_source_6', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be co-fired.'),
sa.Column('can_switch_oil_gas', sa.Boolean(), nullable=True, comment='Whether the generator can switch between oil and natural gas.'),
sa.Column('time_to_switch_oil_to_gas', sa.Text(), nullable=True, comment='The time required to switch the generator from running 100 percent oil to running 100 percent natural gas.'),
sa.Column('time_to_switch_gas_to_oil', sa.Text(), nullable=True, comment='The time required to switch the generator from running 100 percent natural gas to running 100 percent oil.'),
sa.Column('can_switch_when_operating', sa.Boolean(), nullable=True, comment='Whether the generator can switch fuel while operating.'),
sa.Column('net_summer_capacity_natural_gas_mw', sa.Float(), nullable=True, comment='The maximum net summer output achievable when running on natural gas.'),
sa.Column('net_summer_capacity_oil_mw', sa.Float(), nullable=True, comment='The maximum net summer output achievable when running on oil.'),
sa.Column('net_winter_capacity_natural_gas_mw', sa.Float(), nullable=True, comment='The maximum net winter output achievable when running on natural gas.'),
sa.Column('net_winter_capacity_oil_mw', sa.Float(), nullable=True, comment='The maximum net summer output achievable when running on oil.'),
sa.Column('has_factors_that_limit_switching', sa.Boolean(), nullable=True, comment="Whether there are factors that limit the generator's ability to switch between oil and natural gas."),
sa.Column('has_storage_limits', sa.Boolean(), nullable=True, comment="Whether limited on-site fuel storage is a factor that limits the generator's ability to switch between oil and natural gas."),
sa.Column('has_air_permit_limits', sa.Boolean(), nullable=True, comment='Whether air permit limits are a factor that limits the operation of the generator when running on 100 percent oil.'),
sa.Column('has_other_factors_that_limit_switching', sa.Boolean(), nullable=True, comment="Whether there are factors other than air permit limits and storage that limit the generator's ability to switch between oil and natural gas."),
sa.Column('can_cofire_oil_and_gas', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire oil and gas.'),
sa.Column('can_cofire_100_oil', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire 100 oil.'),
sa.Column('max_oil_heat_input', sa.Float(), nullable=True, comment='The maximum oil heat input (percent of MMBtus) expected for proposed unit when co-firing with natural gas'),
sa.Column('max_oil_output_mw', sa.Float(), nullable=True, comment='The maximum output (net MW) expected for proposed unit, when making the maximum use of oil and co-firing natural gas.'),
sa.Column('can_fuel_switch', sa.Boolean(), nullable=True, comment='Whether a unit is able to switch fuels.'),
sa.Column('has_regulatory_limits', sa.Boolean(), nullable=True, comment='Whether there are factors that limit the operation of the generator when running on 100 percent oil'),
sa.Column('fuel_switch_energy_source_1', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be able to be used as a sole source of fuel for this unit.'),
sa.Column('fuel_switch_energy_source_2', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be able to be used as a sole source of fuel for this unit.'),
sa.Column('fuel_switch_energy_source_3', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be able to be used as a sole source of fuel for this unit.'),
sa.Column('fuel_switch_energy_source_4', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be able to be used as a sole source of fuel for this unit.'),
sa.Column('fuel_switch_energy_source_5', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be able to be used as a sole source of fuel for this unit.'),
sa.Column('fuel_switch_energy_source_6', sa.Text(), nullable=True, comment='The codes representing the type of fuel that will be able to be used as a sole source of fuel for this unit.'),
sa.ForeignKeyConstraint(['energy_source_code_1'], ['core_eia__codes_energy_sources.code'], name=op.f('fk_core_eia860__scd_generators_multifuel_energy_source_code_1_core_eia__codes_energy_sources')),
sa.ForeignKeyConstraint(['energy_source_code_2'], ['core_eia__codes_energy_sources.code'], name=op.f('fk_core_eia860__scd_generators_multifuel_energy_source_code_2_core_eia__codes_energy_sources')),
sa.ForeignKeyConstraint(['operational_status_code'], ['core_eia__codes_operational_status.code'], name=op.f('fk_core_eia860__scd_generators_multifuel_operational_status_code_core_eia__codes_operational_status')),
sa.ForeignKeyConstraint(['plant_id_eia', 'generator_id', 'report_date'], ['core_eia860__scd_generators.plant_id_eia', 'core_eia860__scd_generators.generator_id', 'core_eia860__scd_generators.report_date'], name=op.f('fk_core_eia860__scd_generators_multifuel_plant_id_eia_core_eia860__scd_generators')),
sa.ForeignKeyConstraint(['prime_mover_code'], ['core_eia__codes_prime_movers.code'], name=op.f('fk_core_eia860__scd_generators_multifuel_prime_mover_code_core_eia__codes_prime_movers')),
sa.ForeignKeyConstraint(['sector_id_eia'], ['core_eia__codes_sector_consolidated.code'], name=op.f('fk_core_eia860__scd_generators_multifuel_sector_id_eia_core_eia__codes_sector_consolidated')),
sa.ForeignKeyConstraint(['utility_id_eia', 'report_date'], ['core_eia860__scd_utilities.utility_id_eia', 'core_eia860__scd_utilities.report_date'], name=op.f('fk_core_eia860__scd_generators_multifuel_utility_id_eia_core_eia860__scd_utilities')),
sa.PrimaryKeyConstraint('report_date', 'utility_id_eia', 'generator_id', 'plant_id_eia', name=op.f('pk_core_eia860__scd_generators_multifuel'))
)
with op.batch_alter_table('_out_eia__yearly_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('can_cofire_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire fuels.'))
batch_op.add_column(sa.Column('can_burn_multiple_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can burn multiple fuels.'))
batch_op.add_column(sa.Column('can_switch_oil_gas', sa.Boolean(), nullable=True, comment='Whether the generator can switch between oil and natural gas.'))
batch_op.drop_column('multiple_fuels')
batch_op.drop_column('cofire_fuels')
batch_op.drop_column('switch_oil_gas')

with op.batch_alter_table('core_eia860__scd_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('can_burn_multiple_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can burn multiple fuels.'))
batch_op.add_column(sa.Column('can_cofire_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire fuels.'))
batch_op.add_column(sa.Column('can_switch_oil_gas', sa.Boolean(), nullable=True, comment='Whether the generator can switch between oil and natural gas.'))
batch_op.drop_column('multiple_fuels')
batch_op.drop_column('cofire_fuels')
batch_op.drop_column('switch_oil_gas')

with op.batch_alter_table('out_eia__monthly_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('can_cofire_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire fuels.'))
batch_op.add_column(sa.Column('can_burn_multiple_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can burn multiple fuels.'))
batch_op.add_column(sa.Column('can_switch_oil_gas', sa.Boolean(), nullable=True, comment='Whether the generator can switch between oil and natural gas.'))
batch_op.drop_column('multiple_fuels')
batch_op.drop_column('cofire_fuels')
batch_op.drop_column('switch_oil_gas')

with op.batch_alter_table('out_eia__yearly_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('can_cofire_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can co-fire fuels.'))
batch_op.add_column(sa.Column('can_burn_multiple_fuels', sa.Boolean(), nullable=True, comment='Whether the generator can burn multiple fuels.'))
batch_op.add_column(sa.Column('can_switch_oil_gas', sa.Boolean(), nullable=True, comment='Whether the generator can switch between oil and natural gas.'))
batch_op.drop_column('multiple_fuels')
batch_op.drop_column('cofire_fuels')
batch_op.drop_column('switch_oil_gas')

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('out_eia__yearly_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('switch_oil_gas', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('cofire_fuels', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('multiple_fuels', sa.BOOLEAN(), nullable=True))
batch_op.drop_column('can_switch_oil_gas')
batch_op.drop_column('can_burn_multiple_fuels')
batch_op.drop_column('can_cofire_fuels')

with op.batch_alter_table('out_eia__monthly_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('switch_oil_gas', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('cofire_fuels', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('multiple_fuels', sa.BOOLEAN(), nullable=True))
batch_op.drop_column('can_switch_oil_gas')
batch_op.drop_column('can_burn_multiple_fuels')
batch_op.drop_column('can_cofire_fuels')

with op.batch_alter_table('core_eia860__scd_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('switch_oil_gas', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('cofire_fuels', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('multiple_fuels', sa.BOOLEAN(), nullable=True))
batch_op.drop_column('can_switch_oil_gas')
batch_op.drop_column('can_cofire_fuels')
batch_op.drop_column('can_burn_multiple_fuels')

with op.batch_alter_table('_out_eia__yearly_generators', schema=None) as batch_op:
batch_op.add_column(sa.Column('switch_oil_gas', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('cofire_fuels', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('multiple_fuels', sa.BOOLEAN(), nullable=True))
batch_op.drop_column('can_switch_oil_gas')
batch_op.drop_column('can_burn_multiple_fuels')
batch_op.drop_column('can_cofire_fuels')

op.drop_table('core_eia860__scd_generators_multifuel')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions src/pudl/extract/eia860.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def get_dtypes(page, **partition):
"raw_eia860__generator_wind_existing",
"raw_eia860__generator_wind_retired",
"raw_eia860__multifuel_existing",
"raw_eia860__multifuel_proposed",
"raw_eia860__multifuel_retired",
"raw_eia860__ownership",
"raw_eia860__plant",
Expand Down
Loading

0 comments on commit f904b0a

Please sign in to comment.