Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate EIA 860 multifuel table #3988

Merged
merged 17 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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