Skip to content

Implement changes from resolutions from discussion #85#490

Open
jvendries wants to merge 3 commits into
jv_PR3_decisions_analysisfrom
jv_PR3_1_electricity_disagg
Open

Implement changes from resolutions from discussion #85#490
jvendries wants to merge 3 commits into
jv_PR3_decisions_analysisfrom
jv_PR3_1_electricity_disagg

Conversation

@jvendries

@jvendries jvendries commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

cc:
Closes: cornerstone-data/methods#85

What changed? Why?

Introduces a compensated row-weight (w_row) mechanism for the electricity disaggregation pipeline (PR 3.1), replacing the previous approach of using the raw UGO305 gross-output weights (w_go) uniformly for both the use-intersection step and the final commodity-row split in Y.

Key changes:

  • build_electricity_disagg_use_intersection_weights() is now a cached function that derives step-2 intersection weights from EIA Table 8.3 Purchased Power + T/D expenses, replacing the prior use of ugo305_go_weights() for that step.
  • _compute_w_row() derives compensating uniform row weights that preserve the UGO305 total allocation across G/T/D children after the intersection has already been split with Table 8.3 weights. This closes the market-clearing gap introduced when w_int ≠ w_go.
  • get_electricity_commodity_row_weights() is a new cached function that assembles the full post-reallocation checkpoint, computes T (intersection total) and P (non-intersection purchases), and returns the compensating w_row. This is now used in both the production pipeline (derive_disagg_Ytot_with_trade, disaggregate_electricity_make_use_va) and the analysis baseline scenario.
  • A new scenario t8.3_purchased_power_diag_compensated is added to disagg_scenarios.py, wiring together w_go (make/column steps), Table 8.3 w_int (intersection), and the compensating w_row (commodity row split).
  • Decision 7 (D7) pure UGO305 per-child scaling is introduced via build_electricity_ugo305_scaling_ratios, apply_electricity_d7_scaling_correction_to_A, and apply_electricity_d7_scaling_correction_to_q. These are applied inside scale_cornerstone_A and scale_cornerstone_q when electricity disaggregation is enabled, giving each G/T/D child its own UGO305-derived scaling ratio rather than the uniform Utilities sector-22 summary ratio.
  • split_electricity_e_for_disaggregated_b is removed from the B-matrix and E-matrix derivation paths (derive_cornerstone_B_via_vnorm, derive_B_from_scenario); the eGRID FBS fallback path in load_E_from_flowsa is also removed.
  • Cache entries for the new functions are registered in cache_reset.py.
  • The old test_baseline_matches_production test is replaced by test_production_matches_compensated_scenario, which verifies parity between the production path and the new compensated scenario using mocked weights. A new integration test test_compensated_scenario_market_clearing_gap_under_1m acts as a merge gate, asserting all market-clearing gaps remain below $6M.

Testing

  • Unit test test_production_matches_compensated_scenario mocks all IO-bundle dependencies and asserts V, Udom, Uimp, VA, Y, q, and x match between the production path and the compensated scenario to rtol=1e-9.
  • TestTable83UseIntersectionWeights verifies Table 8.3 expense weights sum to 1 and produce the correct Purchased Power share.
  • TestCompensatingRowWeights verifies _compute_w_row closes the market-clearing identity for each child sector and that get_electricity_commodity_row_weights does not trigger IO bundle loading.
  • TestD7PureScaling (integration) confirms differentiated per-child q scaling produces three distinct values across G/T/D sectors.
  • Integration test test_compensated_scenario_market_clearing_gap_under_1m runs the full compensated scenario end-to-end and asserts all market-clearing gaps are below the $6M merge-gate threshold.

jvendries commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jvendries jvendries requested a review from bl-young June 25, 2026 20:29
@jvendries jvendries marked this pull request as ready for review June 25, 2026 20:31
@jvendries

jvendries commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

This PR is the latest in the electricity disaggregation stack (#462, #463, #481, #490). For reviewing, I ask to leave any comments to pertaining to any of those 4 PRs on this PR if possible, so that I may address them all in a consolidated location.

I added a state of disaggregation report here to aid in reviewing this PR.

@bl-young bl-young left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused functions

Functions added in the stack diff vs main with no references anywhere in the repo:

  1. split_electricity_e_for_disaggregated_bbedrock/transform/eeio/electricity_disaggregation.py:961 (PR #490)
  2. electricity_va_breakdownbedrock/analysis/electricity/d_85/balance_metrics.py:101 (PR #481)
  3. write_default_overrides_csvbedrock/analysis/electricity/d_85/end_use_mapping.py:117 (PR #481)

Tests

Stack adds ~1,570 lines across 18 test files. Most d_85/__tests__/ tests (~900 lines) are not needed — analysis/report scaffolding, not publish-path contracts.

Keep: test_production_matches_compensated_scenario.py (production↔analysis parity), test_disaggregation_matrices.py (export/config contract), transform + utils tests on the publish path.

Delete most of d_85/__tests__/ — optional slow gate: test_compensated_scenario.py (@pytest.mark.eeio_integration).

Keep (high signal)
File Why
d_85/__tests__/test_production_matches_compensated_scenario.py Cross-layer parity: production disagg path matches compensated analysis scenario
electricity/__tests__/test_disaggregation_matrices.py Export config guard, Excel/CSV artifact contract
transform/eeio/__tests__/test_electricity_disaggregation.py GO weights, VA balancing, schema invariants, w_row, D7 scaling
transform/eeio/__tests__/test_electricity_reallocation.py Conservation / reallocation invariants (trim 3 smokes)
utils/config/__tests__/test_usa_config.py model_validate dependency guards only
utils/validation/__tests__/test_diagnostics_sector_alignment.py 3 alignment math tests (drop sector-desc tautology)
Delete candidates (~500+ lines in d_85)
  • test_figures.py — PNG smoke, restates figure_data
  • test_balance_metrics.py — hand-built perfect data
  • test_diagnostics_comparison.py — static dict keys
  • test_scaling_scenarios.py, test_scenario_ef_pipeline.py — shape/smoke checks
  • test_end_use_mapping.py — registry completeness / NAICS pin
  • test_disagg_weights.py — duplicates inline asserts in disagg_weights.py

Trim or delete: test_disagg_scenarios.py (keep at most 2 small invariants), test_eia_inputs.py (at most missing_flowname_raises), shrink conftest.py to what parity test needs.

Transform trim candidates
  • test_electricity_disaggregation.py — drop bare pull_efs_for_diagnostics() call with no assertion
  • test_electricity_reallocation.py — delete test_row_sum_s_zero_raises, test_pipeline_aq_dimensions, test_diagnostics_helpers_run
  • test_usa_config.py — drop 2 YAML parsing tautologies; keep dependency guards
  • test_diagnostics_sector_alignment.py — drop test_get_aligned_sector_desc_electricity
Net shape

Lean stack: ~600–700 lines of tests instead of ~1,570 (~60% cut), almost entirely from d_85/__tests__/.

Model configs

Recommend more comments / clarification on new model YAML configs so flag names, dependencies, and intended use align with the USAConfig schema (especially prerequisite flags and what each config is meant to exercise).

Configs touched in stack
  • bedrock/utils/config/configs/2025_usa_cornerstone_full_model_electricity_disaggregation.yaml
  • bedrock/utils/config/configs/test_usa_config_waste_disagg_electricity_disaggregation.yaml
  • bedrock/utils/config/configs/test_usa_config_waste_disagg_electricity.yaml (reallocation-only variant)

Schema guards live in bedrock/utils/config/usa_config.py (implement_electricity_disaggregation requires reallocation + waste disagg).

d_85 analysis runs

Could not run run_resolution_analysis end-to-end on test_usa_config_waste_disagg_electricity_disaggregation.yaml — all decision reports fail when building EF diffs. monetary_disagg.build_assets runs successfully on the same config.

Failure detail
ValueError: matrices are not aligned
  scenario_ef_pipeline.py:203  return (E.div(x, axis=1)) @ Vnorm

Triggered from decision3_table83_analysis.build_reportdiff_ef_vectorscompute_ef_vectorsderive_B_from_scenario. Affects --decision 3, 5, 7, and all; no Excel reports written.

Underlying scenario/balance math runs (e.g. run_decision3_scenarios, compute_balance_metrics) complete without hitting this path.

Analysis READMEs

Recommend READMEs under bedrock/analysis/electricity/ subfolders — purpose, how to run entry points, inputs/outputs, and how each folder relates to the production transform path.

Subfolders that would benefit
  • bedrock/analysis/electricity/ — export scripts (write_221100_*, disaggregation_matrices.py) vs one-off verification
  • bedrock/analysis/electricity/d_85/ — discussion #85 resolution analysis; run_resolution_analysis.py and report outputs
  • bedrock/analysis/electricity/monetary_disagg/ — monetary disagg report assets; build_assets.py and generated figures

(generation), `221121` (transmission), and `221122` (distribution) — using normalized
weight shares. The production implementation follows [Methods Discussion #85](https://github.com/cornerstone-data/methods/discussions/85).

| Step | Matrix | What it does |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tracks with my understanding of the current implementation


---

## B. Methods #85 decisions — summary

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto- this tracks with my understanding

UGO305-A 2017 gross-output shares split the Make diagonal while preserving aggregate
Make row and column totals (`$455.8B` each).

![Decision 2 — Make diagonal after step 1](output/figure_d2_make_intersection.png)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the images are not pushed


![Decision 5 — Commodity row / Y split and compensating weights](output/figure_d5_row_y.png)

| Sector | w_go | w_int (step 2) | w_row (step 4 & Y) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the use table intersection, G & T get a higher than proportional share of the intersection, so it tracks that the residual here w_row would be higher than w_go for D and lower for G and T.

**Numerator:** eGRID-based national GHG inventory is the sole path. Plant combustion maps
to `221110`; SF6 maps to `221121`; `221122` receives negligible direct emissions. The
previous gas-type fallback (`split_electricity_e_for_disaggregated_b`) was removed — missing
eGRID FBS data raises an error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

fba = getFlowByActivity('EIA_ElectricPowerAnnual', year)
mask = (
(fba['Year'] == year)
& (fba['Description'].str.startswith(TABLE_8_3_DESCRIPTION, na=False))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm not sure that this chunk of module level variables, e.g., TABLE_8_3_TRANSMISSION_FLOW is necessary since they are only used here. Setting them directly I think its clearer IMO as they shouldn't change regularly if at all.

also for the description could consider perhaps just looking for the fields that start with "Table 8.3" rather than the whole string

)

expenses = _table83_purchased_power_expenses(
2017, fba=mock_fba_table83_table24()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per my other comment, this must be why you pass fba as a parameter. But I'm not sure this test is really necessary as really all its doing is confirming that we can calculate a simple percentage.

Comment on lines +351 to +354
keys = list(expenses.keys())
if len(keys) != 3:
raise ValueError(f'expected 3 expense buckets, got {keys!r}')
gen_key, trans_key, dist_key = keys

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels brittle to me to use a list and assign them by order.


@functools.cache
def build_electricity_disagg_use_intersection_weights() -> pd.Series[float]:
"""Return Table 8.3 Purchased Power + T/D shares for step 2 intersection."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our current implementation assumes a diagonal use table disaggregation, but I might expect that a function like this would actually return a matrix. Im not suggesting you change the implementation but that might be worth an additional note in the doc string

@@ -0,0 +1,117 @@
"""Production path parity vs compensated analysis scenario (PR 3.1)."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how should i interpret the need for test files in the analysis folder? To me the production of the intended outputs is proof enough that the code works - assuming these tests don't touch core pipeline code

@WesIngwersen WesIngwersen added this to the v0.4 milestone Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants