diff --git a/dbt/models/ferc1/core_ferc1__yearly_depreciation_factors_sched336/schema.yml b/dbt/models/ferc1/core_ferc1__yearly_depreciation_factors_sched336/schema.yml new file mode 100644 index 0000000000..8b71e5e478 --- /dev/null +++ b/dbt/models/ferc1/core_ferc1__yearly_depreciation_factors_sched336/schema.yml @@ -0,0 +1,18 @@ +version: 2 +sources: + - name: pudl + tables: + - name: core_ferc1__yearly_depreciation_factors_sched336 + columns: + - name: record_id + - name: report_year + - name: utility_id_ferc1 + - name: depreciation_factors + - name: depreciable_plant_base + - name: net_salvage_pct + - name: depreciation_rate + - name: mortality_curve_type + - name: order_num + - name: account_num + - name: service_life_avg + - name: remaining_life_avg diff --git a/migrations/versions/8c456b39d789_add_new_ferc1_depreciation_table.py b/migrations/versions/8c456b39d789_add_new_ferc1_depreciation_table.py new file mode 100644 index 0000000000..d07d6d35be --- /dev/null +++ b/migrations/versions/8c456b39d789_add_new_ferc1_depreciation_table.py @@ -0,0 +1,42 @@ +"""add new ferc1 depreciation table + +Revision ID: 8c456b39d789 +Revises: 18771235c92f +Create Date: 2026-04-02 12:42:30.413608 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8c456b39d789' +down_revision = '18771235c92f' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('core_ferc1__yearly_depreciation_factors_sched336', + sa.Column('record_id', sa.Text(), nullable=True, comment='Identifier indicating original FERC Form 1 source record. format: {table_name}_{report_year}_{report_prd}_{respondent_id}_{spplmnt_num}_{row_number}. Unique within FERC Form 1 DB tables which are not row-mapped.'), + sa.Column('report_year', sa.Integer(), nullable=True, comment='Four-digit year in which the data was reported.'), + sa.Column('utility_id_ferc1', sa.Integer(), nullable=True, comment='PUDL-assigned utility ID, identifying a FERC1 utility. This is an auto-incremented ID and is not expected to be stable from year to year.'), + sa.Column('depreciation_factors', sa.Text(), nullable=True, comment='Label of the factor of depreciation factors. This field contains is an unstructured, free-form strings. It often includes FERC account IDs, sometimes it includes plant names and sometimes includes headers. Indicating the beginning of a new section - such as a plant or asset type header which is followed by sub-components like FERC account IDs or plant names depending on the section.'), + sa.Column('depreciable_plant_base', sa.Float(), nullable=True, comment='Depreciable plant balance (depreciable base) to which rates are applied.'), + sa.Column('net_salvage_pct', sa.Float(), nullable=True, comment='Percentage representing the estimated value of utility plant at the end of its service life. Be aware that the formatting of this column is not expected to be standard - expect some values between 0-1 and some between 0-100.'), + sa.Column('depreciation_rate', sa.Text(), nullable=True, comment='Depreciation rate applied to utility plant balance.'), + sa.Column('mortality_curve_type', sa.Text(), nullable=True, comment='Description of the type of mortality curve selected in plant mortality studies prepared to assist in estimating average service lives.'), + sa.Column('order_num', sa.Float(), nullable=True, comment="This field is defined in FERC-XBRL documentation as a field that is used or sequence a table.FERC-XBRL documentation notes: 'This field is added to a table to control ordering of the items on the table.'FERC's documentation also notes that this field should always be an integer - although there are many instances of floating point values which seem to increment by decimal points. Nonetheless, this field can be used to help understand the original order of the table. This field did not exist prior to FERC publishing Form 1 as XBRL and thus is always null prior to 2021."), + sa.Column('account_num', sa.Text(), nullable=True, comment='Account number(s) in connection with factors used in estimating depreciation charges.'), + sa.Column('service_life_avg', sa.Text(), nullable=True, comment="Estimated average service life of utility plant. This field is typed as a string because it contains a mix of year-like numbers and strings `PnYnMnDTnHnMnS`.FERC1's Taxonomy documents this field as:\n\nEstimated average service life of utility plant, in 'PnYnMnDTnHnMnS' format, for example 'P4Y7M12D' represents a fact of four years, seven months, and 12 days."), + sa.Column('remaining_life_avg', sa.Text(), nullable=True, comment='Estimated weighted average of remaining life of utility plant assets.'), + sa.ForeignKeyConstraint(['utility_id_ferc1'], ['core_pudl__assn_ferc1_pudl_utilities.utility_id_ferc1'], name=op.f('fk_core_ferc1__yearly_depreciation_factors_sched336_utility_id_ferc1_core_pudl__assn_ferc1_pudl_utilities')) + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('core_ferc1__yearly_depreciation_factors_sched336') + # ### end Alembic commands ### diff --git a/src/pudl/extract/ferc1.py b/src/pudl/extract/ferc1.py index 8940c55547..a450ce7a0c 100644 --- a/src/pudl/extract/ferc1.py +++ b/src/pudl/extract/ferc1.py @@ -171,6 +171,10 @@ "dbf": "f1_dacs_epda", "xbrl": "summary_of_depreciation_and_amortization_charges_section_a_336", }, + "core_ferc1__yearly_depreciation_factors_sched336": { + "dbf": "f1_edcfu_epda", + "xbrl": "factors_used_in_estimating_depreciation_charges_section_c_336", + }, "core_ferc1__yearly_depreciation_changes_sched219": { "dbf": "f1_accumdepr_prvsn", "xbrl": "accumulated_provision_for_depreciation_of_electric_utility_plant_changes_section_a_219", diff --git a/src/pudl/metadata/fields.py b/src/pudl/metadata/fields.py index fe17eb7dee..c863a9d7be 100644 --- a/src/pudl/metadata/fields.py +++ b/src/pudl/metadata/fields.py @@ -10006,6 +10006,63 @@ "description": "The cost of materials and supplies used.", "unit": "USD", }, + "depreciable_plant_base": { + "type": "number", + "description": "Depreciable plant balance (depreciable base) to which rates are applied.", + }, + "net_salvage_pct": { + "type": "number", + "description": ( + "Percentage representing the estimated value of utility plant at the end of its service life. " + "Be aware that the formatting of this column is not expected to be standard - expect some " + "values between 0-1 and some between 0-100." + ), + }, + "depreciation_rate": { + "type": "string", + "description": "Depreciation rate applied to utility plant balance.", + }, + "mortality_curve_type": { + "type": "string", + "description": "Description of the type of mortality curve selected in plant mortality studies prepared to assist in estimating average service lives.", + }, + "depreciation_factors": { + "type": "string", + "description": ( + "Label of the factor of depreciation factors. This field contains is an unstructured, free-form strings. " + "It often includes FERC account IDs, sometimes it includes plant names and sometimes includes headers. " + "Indicating the beginning of a new section - such as a plant or asset type header which is followed " + "by sub-components like FERC account IDs or plant names depending on the section." + ), + }, + "order_num": { + "type": "number", + "description": ( + "This field is defined in FERC-XBRL documentation as a field that is used or sequence a table." + "FERC-XBRL documentation notes: 'This field is added to a table to control ordering of the items on the table.'" + "FERC's documentation also notes that this field should always be an integer - although " + "there are many instances of floating point values which seem to increment by decimal points. " + "Nonetheless, this field can be used to help understand the original order of the table. " + "This field did not exist prior to FERC publishing Form 1 as XBRL and thus is always null prior to 2021." + ), + }, + "account_num": { + "type": "string", + "description": "Account number(s) in connection with factors used in estimating depreciation charges.", + }, + "service_life_avg": { + "type": "string", + "description": ( + "Estimated average service life of utility plant. This field is typed as a string because it contains " + "a mix of year-like numbers and strings `PnYnMnDTnHnMnS`." + "FERC1's Taxonomy documents this field as:\n\n" + "Estimated average service life of utility plant, in 'PnYnMnDTnHnMnS' format, for example 'P4Y7M12D' represents a fact of four years, seven months, and 12 days." + ), + }, + "remaining_life_avg": { + "type": "string", + "description": "Estimated weighted average of remaining life of utility plant assets.", + }, "utility_plant_group": { "type": "string", "description": "High-level category of utility plant asset type.", diff --git a/src/pudl/metadata/resources/ferc1.py b/src/pudl/metadata/resources/ferc1.py index 436ad85d95..af3acfbc0a 100644 --- a/src/pudl/metadata/resources/ferc1.py +++ b/src/pudl/metadata/resources/ferc1.py @@ -54,6 +54,29 @@ "amortization changes." ), }, + "yearly_depreciation_factors_sched336": { + "additional_summary_text": "factors used in estimating depreciation charges.", + "additional_source_text": "(Schedule 336 - Section C)", + "usage_warnings": [ + "aggregation_hazard", + "free_text", + { + "type": "custom", + "description": "The rate and percentage (pct) columns are reported either as values between 0-1 or 0-100.", + }, + ], + "additional_primary_key_text": "This table is too unstructured to have a primary key.", + "additional_details_text": ( + "This table contains details at a variety of levels of granularity." + "There are many free-form text fields in this table which results in respondents " + "filling out this table very differently from each other or from year to year. " + "We recommend using this table carefully with one utility-year at a time - not " + "attempting to perform analysis across long time-series or across utilities without " + "much caution and cleaning. " + "This table only " + "contains information from Section C: Factors Used in Estimating Depreciation Charges." + ), + }, "yearly_energy_sources_sched401": { "additional_summary_text": "sources of electric energy generated or purchased, exchanged and wheeled.", "additional_source_text": "(Schedule 401a)", @@ -436,6 +459,28 @@ "etl_group": "ferc1", "field_namespace": "ferc1", }, + "core_ferc1__yearly_depreciation_factors_sched336": { + "description": TABLE_DESCRIPTIONS["yearly_depreciation_factors_sched336"], + "schema": { + "fields": [ + "record_id", + "report_year", + "utility_id_ferc1", + "depreciation_factors", + "depreciable_plant_base", + "net_salvage_pct", + "depreciation_rate", + "mortality_curve_type", + "order_num", + "account_num", + "service_life_avg", + "remaining_life_avg", + ], + }, + "sources": ["ferc1"], + "etl_group": "ferc1", + "field_namespace": "ferc1", + }, "core_ferc1__yearly_energy_sources_sched401": { "description": TABLE_DESCRIPTIONS["yearly_energy_sources_sched401"], "schema": { diff --git a/src/pudl/transform/ferc1.py b/src/pudl/transform/ferc1.py index 76cd3e0b03..4e0c2dc9f4 100644 --- a/src/pudl/transform/ferc1.py +++ b/src/pudl/transform/ferc1.py @@ -147,6 +147,7 @@ class TableIdFerc1(enum.Enum): OPERATING_EXPENSES = "core_ferc1__yearly_operating_expenses_sched320" BALANCE_SHEET_LIABILITIES = "core_ferc1__yearly_balance_sheet_liabilities_sched110" DEPRECIATION_SUMMARY = "core_ferc1__yearly_depreciation_summary_sched336" + DEPRECIATION_FACTORS = "core_ferc1__yearly_depreciation_factors_sched336" BALANCE_SHEET_ASSETS = "core_ferc1__yearly_balance_sheet_assets_sched110" RETAINED_EARNINGS = "core_ferc1__yearly_retained_earnings_sched118" INCOME_STATEMENTS = "core_ferc1__yearly_income_statements_sched114" @@ -5564,6 +5565,22 @@ def process_xbrl_metadata( return meta +class DepreciationFactorsTransformer(Ferc1AbstractTableTransformer): + """Transformer class for :ref:`core_ferc1__yearly_depreciation_factors_sched336` table.""" + + table_id: TableIdFerc1 = TableIdFerc1.DEPRECIATION_FACTORS + has_unique_record_ids: bool = False + + def transform_main(self, df): + """Convert $1000s to $s after standard transform_main.""" + df = ( + super() + .transform_main(df) + .assign(depreciable_plant_base=lambda x: x.depreciable_plant_base / 1000) + ) + return df + + class DepreciationChangesTableTransformer(Ferc1AbstractTableTransformer): """Transformer class for :ref:`core_ferc1__yearly_depreciation_changes_sched219` table.""" @@ -6105,6 +6122,7 @@ class OtherRegulatoryLiabilitiesTableTransformer(Ferc1AbstractTableTransformer): "core_ferc1__yearly_operating_expenses_sched320": OperatingExpensesTableTransformer, "core_ferc1__yearly_balance_sheet_liabilities_sched110": BalanceSheetLiabilitiesTableTransformer, "core_ferc1__yearly_depreciation_summary_sched336": DepreciationSummaryTableTransformer, + "core_ferc1__yearly_depreciation_factors_sched336": DepreciationFactorsTransformer, "core_ferc1__yearly_balance_sheet_assets_sched110": BalanceSheetAssetsTableTransformer, "core_ferc1__yearly_income_statements_sched114": IncomeStatementsTableTransformer, "core_ferc1__yearly_depreciation_changes_sched219": DepreciationChangesTableTransformer, diff --git a/src/pudl/transform/params/ferc1.py b/src/pudl/transform/params/ferc1.py index a23824b511..599c62ed02 100644 --- a/src/pudl/transform/params/ferc1.py +++ b/src/pudl/transform/params/ferc1.py @@ -1954,6 +1954,48 @@ }, }, }, + "core_ferc1__yearly_depreciation_factors_sched336": { + "rename_columns_ferc1": { + "dbf": { + "columns": { + "respondent_id": "utility_id_ferc1_dbf", + "report_year": "report_year", + "spplmnt_num": "spplmnt_num", + "row_number": "row_number", + "row_seq": "row_seq", + "row_prvlg": "row_prvlg", + "depr_plnt_base": "depreciable_plant_base", # $1000s + "est_avg_srvce_lf": "service_life_avg", # est + "net_salvage": "net_salvage_pct", + "apply_depr_rate": "depreciation_rate", + "mrtlty_crv_typ": "mortality_curve_type", + "avg_remaining_lf": "remaining_life_avg", + "acct_num": "account_num", + "report_prd": "report_prd", + } + }, + "xbrl": { + "columns": { + "entity_id": "utility_id_ferc1_xbrl", + "report_year": "report_year", + "estimated_depreciation_charges_factors_axis": "depreciation_factors", + "utility_plant_applied_depreciation_rate": "depreciation_rate", + "utility_plant_estimated_average_service_life": "service_life_avg", + "utility_plant_weighted_average_remaining_life": "remaining_life_avg", + "order_number": "order_num", + "mortality_curve_type": "mortality_curve_type", + "account_number_factors_used_in_estimating_depreciation_charges": "account_num", + "depreciable_plant_base": "depreciable_plant_base", + "utility_plant_net_salvage_value_percentage": "net_salvage_pct", + } + }, + }, + "normalize_strings": { + "account_num": FERC1_STRING_NORM, + "service_life_avg": FERC1_STRING_NORM, + "remaining_life_avg": FERC1_STRING_NORM, + }, + }, "core_ferc1__yearly_operating_revenues_sched300": { "rename_columns_ferc1": { "duration_xbrl": {