Skip to content

feat(analysis): make the two explanations of the N change reproducible - #877

Open
WesIngwersen wants to merge 1 commit into
mainfrom
n-change-decomposition-script
Open

feat(analysis): make the two explanations of the N change reproducible#877
WesIngwersen wants to merge 1 commit into
mainfrom
n-change-decomposition-script

Conversation

@WesIngwersen

@WesIngwersen WesIngwersen commented Sep 8, 2026

Copy link
Copy Markdown
Member

Both of these tables had been produced by hand and quoted in write-ups. Nobody could re-run them after a rebuild, which is how an explanation goes stale without anyone noticing. They are now one script, and both come from a single derivation of B and A — deriving them takes minutes, and the two views have to describe the same build or they are not comparable.

Table 1 — which input moved N

Holds one of A / B at the baseline and moves the other. This is the only way to say whether a change in N came from the IO rebuild or from the GHG regeneration.

what moves median change in N previous run
both B and A −8.05% −10.64%
A only, B held at v0.3 −4.58% −8.30%
B only, A held at v0.3 −2.74% −1.73%
direct intensity D = 1'B −6.35% −1.59%

D is the control on the B row: if B moves D and N alike the emissions change is a level shift; if D moves much more, it sits in sectors nothing else depends on and never propagates. It now moves more than twice what B-only moves N, which is a real change in character from the previous run.

Table 2 — the mechanism, in units you can say out loud

N factors exactly, per sector:

N_j = sum_i d_i L_ij
    = (sum_i L_ij)      x  (sum_i d_i L_ij / sum_i L_ij)
    = output multiplier x  average intensity of what is pulled
quantity v0.3 baseline nowcast 2024 change
intermediate input per $ output (col sum of A) 0.5033 0.5030 −1.55%
output multiplier (col sum of L) 2.0064 1.9629 −2.69%
avg intensity of what a $ pulls (kg CO2e/$) 0.0930 0.0860 −4.94%
N (kg CO2e/$) 0.2024 0.1746 −8.05%

Validation that this reproduces the hand method rather than something adjacent: the baseline column lands on 0.5033 / 2.0064 / 0.0930, matching the figures quoted by hand last week to four decimals.

The emphasis has flipped since then. The multiplier effect halved (−4.0% → −2.69%) because the merged stack brought the median column sum of A back to 0.5030 against v0.3's 0.5033 — the export re-splits, purchaser-value margin anchor and EC conditioning restored intermediate input intensity the earlier build had lost. Meanwhile the intensity effect grew to −4.94%. The fall in N is now mostly a cleaner mix rather than a shorter supply chain. Table 1 agrees independently: A's contribution halved while B's grew.

⚠️ Neither table is an identity

  • N is not additive in A and B, so table 1's middle rows do not sum to its first; the remainder is their interaction.
  • Medians are taken per row, so table 2's components do not multiply to its N row.

Both report the size of an effect, not a decomposition that closes. The script prints that under each table so a reader cannot take the sum.

Testing

Analysis script, so no unit tests per repo convention. The reproduction check is the baseline column matching the hand figures, and it prints on every run alongside the MUT vintage and sector count, so a replay states what it ran on.

uv run python -m bedrock.analysis.nowcasting.results.n_change_decomposition --csv
uv run black --check . ; uv run ruff check .    # clean

🤖 Generated with Claude Code

Both tables had been produced by hand and quoted in write-ups, so nobody
could re-run them after a rebuild. They are now one script, and both come
from a single derivation of B and A because deriving them takes minutes
and the two views have to describe the same build.

Table 1 holds one of A / B at the baseline and moves the other, which is
the only way to say whether a change in N came from the IO rebuild or the
GHG regeneration. It reports D = 1'B alongside as the control on the B
row: if B moves D and N alike the change is a level shift, and if D moves
much more it sits in sectors nothing depends on.

Table 2 factors N per sector into the output multiplier and the average
intensity of what that multiplier pulls, which is the version that can be
said out loud - "a dollar drags less of the economy behind it" versus
"what it drags is cleaner" - with the size of each.

Validation that this reproduces the hand method rather than something
adjacent: the baseline column lands on 0.5033 col sum of A, 2.0064
multiplier and 0.0930 kg CO2e/$ average intensity, matching the figures
quoted last week to four decimals.

On the current build the emphasis has flipped. The multiplier effect
halved (-4.0% to -2.69%) because the merged stack brought the median col
sum of A back to 0.5030 against v0.3's 0.5033, while the intensity effect
grew to -4.94%. The fall in N is now mostly a cleaner mix rather than a
shorter supply chain.

⚠️ Neither table is an identity. N is not additive in A and B, so table
1's middle rows do not sum to its first; and medians are per row, so
table 2's components do not multiply to its N row. Both say the size of
an effect, not a decomposition that closes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

from bedrock.analysis.nowcasting.results._ef_smoke_lib import (
NOWCAST_COMPARE_YEAR,
V03_SNAPSHOT,

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 comes from _ef_smoke_lib which still pints the v0.3.1 model, which doesn't include the MECS update. We need to bump that to v0.3.2.

There may be other related updates as well needed alongside this

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.

2 participants