Skip to content

Carry BEA's negative-purchase sales in final demand and a published F09000 column (#1270) - #1290

Merged
WesIngwersen merged 5 commits into
fix_handle_negatives_copyfrom
nab_bea_negative_entries_to_final_demand
Nov 13, 2026
Merged

WesIngwersen merged 5 commits into
fix_handle_negatives_copyfrom
nab_bea_negative_entries_to_final_demand

Conversation

@WesIngwersen

@WesIngwersen WesIngwersen commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Part of #1270 (to-dos 1 and 2). Stacked on #1289, which this needs: without it, derive_U_with_negatives returns the clipped table once derive_U_set has run, and the new column comes out empty.

What was wrong

BEA records some sales as negative intermediate purchases. The model sets negative intermediate cells to zero (derive_U_set), so these sales dropped out of the Use rows, and the rows no longer added up to commodity output. That was most of the remaining q = L_dom y failure in #1270:

  • Used and secondhand goods (S00402): industries selling used goods book negative purchases of them: air transport (−$0.70bn in 2024), performing arts (−$0.58bn), water transport (−$0.51bn) and general government (S00500). It failed every year, by $0.3-2.0bn.
  • Oilseed farming (1111A0): federal nondefense general government (S00600) books negative purchases, from selling farm commodities out of government stocks: −$0.23bn in 2017, −$1.27bn in 2022, −$0.51bn in 2024. It failed in six of eight years, by 1-3%.

The same cells are in BEA's published 2017 Use table after redefinitions, the basis our tables are on. For used goods, the air transport cell comes from BEA's redefinitions: it is +$0.01bn before them and −$0.30bn after. Our 2017 used-goods row runs about 1.31 times BEA's after-redefinitions row on every cell (−$0.93bn against −$0.71bn), which points to our 2017 rebuild scaling the whole row. The Import cells are zero (BEA has $9M on the 1111A0 × S00600 cell). 1111A0 imports overall are right: $997M against $963M published in 2017.

The change

Effect

The model doesn't change. derive_U_set, A, y_nab and B are untouched, and ytot doesn't include the new column.

q = L_dom y, on v1.0.0_8365fa4, every year:

Year Before: failing sectors, gap After
2017 S00402, $0.93bn S00402, $0.00bn (q = 0)
2018 S00402, 1111A0, $1.29bn S00402, $0.00bn
2019 S00402, 1111A0, $1.37bn S00402, $0.01bn
2020 S00402, $0.93bn S00402, $0.00bn
2021 1111A0, S00402, $1.64bn S00402, $0.00bn
2022 1111A0, S00402, 562HAZ, $2.71bn 562HAZ $0.21bn (1.0%), S00402
2023 S00402, 1111A0, 562HAZ, $2.92bn 562HAZ $0.35bn (1.6%), S00402
2024 S00402, 1111A0, 334118, $2.93bn 334118 $0.30bn (4.4%), S00402

The test stays xfail, and its reason quotes the new 2024 numbers.

Checks

black, ruff, mypy, the publish tests and the national balance tests (9 passed), locally.

🤖 Generated with Claude Code

WesIngwersen and others added 3 commits November 12, 2026 13:43
…1270)

BEA records some sales as negative intermediate purchases: industries
selling used goods (S00402) and federal nondefense government selling farm
commodities from its stocks (1111A0 bought by S00600). derive_U_set sets
them to zero, so the Use row no longer added up to q. The Use-side final
demand now carries them. Only cells whose Use value is negative move; a
cell negative only because imports exceed use still shows as an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ses (#1270)

derive_negative_purchase_sales returns, by commodity, the domestic Use cells
that are negative in the Use table itself (used-goods sales, government sales
from stocks). The Use-side final demand and a new published U column,
F09000 "Sales recorded as negative intermediate purchases", carry them, so
the NIPA-sourced final-demand columns are unchanged and each published U row
still sums to output. F09000 is kept out of FINAL_DEMANDS.

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

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

Copy link
Copy Markdown
Member Author

FYI @jvendries @MoLi7 This created a new final demand column with a specific residual which eliminates the non-sense of negatives in the intermediate transactions for used goods and but is needed in y to achieve the national accounting balance.

@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.

Reproduced locally on v1.0.0_8365fa4 for 2017, 2022, 2023 and 2024. With F09000 in y, 1111A0 passes every year, and the remaining failures are exactly the ones the description lists (562HAZ 2022-23, 334118 2024, S00402 ~$0). F09000 totals -$1.19bn (2017) to -$2.59bn (2024), and none of it sits on 2211* rows.

A few things, none blocking:

  1. 2017 S00402 vs BEA. The description says these cells are in BEA's published 2017 Use table. That holds for 1111A0 (-$0.225bn ours vs -$0.240bn BEA), but our S00402 row is -$0.93bn against -$0.38bn in BEA's Use_SUT (before redefinitions). The largest cell, air transport 481000 at -$0.39bn, isn't in BEA's table at all, and water and performing arts are 1.3-1.5x BEA. None of it is imports. Do you know whether that comes from the redefinitions or the 2017 rebuild?

  2. Sign. F09000 holds negative values under the name "Sales recorded as negative intermediate purchases". Could the metadata name or description say the values are negative, so users summing U's final demand don't flip it?

  3. Reaggregation branch of get_U. The sales are on the 407 index and are reindexed onto 405, so any sale on a G/T/D child row would trip the guard. That's fine today. Summing children into 221100, as derive_disagg_Ytot_reaggregated does, would be sturdier.

  4. test_get_U_commodity_axis_is_parent_not_children mocks every get_U input except derive_negative_purchase_sales(), which now runs on real data inside that unit test. Worth monkeypatching too.

WesIngwersen and others added 2 commits November 12, 2026 17:37
…l_demand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a test mock (#1270)

Review on #1290 (Ben):
- get_U's reaggregation branch sums the negative-purchase sales on the
  electricity children into 221100, as the final-demand block does, instead
  of reindexing them onto the 405 axis.
- The F09000 description says its values are negative.
- test_get_U_commodity_axis_is_parent_not_children mocks
  derive_negative_purchase_sales (on the 407 axis), so it no longer runs on
  real data.

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

Copy link
Copy Markdown
Member Author

Thanks @bl-young. All four, in 146bdb06 (merged up into #1292 and #1295):

  1. 2017 S00402 against BEA. Our tables are after redefinitions, so BEA's after-redefinitions Use table is the right comparison, and there the air transport cell is in BEA's own table:

    S00402 × buyer, 2017, $bn BEA, before redef. BEA, after redef. ours ours / BEA after
    481000 air transport +0.012 −0.303 −0.394 1.30
    483000 water transport −0.183 −0.183 −0.241 1.32
    711100 performing arts −0.155 −0.155 −0.205 1.32
    S00500 general government −0.049 −0.049 −0.067 1.37
    negative cells, total −0.405 −0.708 −0.927 1.31

    So the air transport cell comes from BEA's redefinitions. The rest of the gap is a uniform factor of about 1.31 on the whole row. That looks like our 2017 rebuild scaling the used-goods row, not anything cell by cell, and it isn't imports. S00402 is an accounting sector the output check now skips (Stop counting scrap twice in the emission factors (#1293) #1292), so I've noted it in the description rather than chasing it here.

  2. Sign: the F09000 description is now "Sales recorded as negative intermediate purchases (values are negative)", and the constant's comment says so.

  3. Reaggregation: get_U now sums the sales on the electricity children into 221100 with reaggregate_electricity_children_y, as the final-demand block does, instead of reindexing them onto the 405 axis.

  4. Test: test_get_U_commodity_axis_is_parent_not_children now mocks derive_negative_purchase_sales, on the 407 axis that derive_U_with_negatives uses when disaggregation is on. The reaggregation and publish tests pass (11).

🤖 Generated with Claude Code

@WesIngwersen
WesIngwersen merged commit 615c900 into main Nov 13, 2026
5 checks passed
@WesIngwersen
WesIngwersen deleted the nab_bea_negative_entries_to_final_demand branch November 13, 2026 01:43
WesIngwersen added a commit that referenced this pull request Nov 13, 2026
…ild v1.0.0_fd271dc (#1194, #1270) (#1302)

* fix: clip negatives on a copy, not the cached input (#1270)

handle_negative_matrix_values and handle_negative_vector_values set
negatives to zero in place. derive_U_set passes them the cached output of
derive_U_with_negatives, so once derive_U_set ran, every later reader of
derive_U_with_negatives saw the clipped frame. Both now return a copy.

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

* fix(nab): move BEA's negative intermediate entries to final demand (#1270)

BEA records some sales as negative intermediate purchases: industries
selling used goods (S00402) and federal nondefense government selling farm
commodities from its stocks (1111A0 bought by S00600). derive_U_set sets
them to zero, so the Use row no longer added up to q. The Use-side final
demand now carries them. Only cells whose Use value is negative move; a
cell negative only because imports exceed use still shows as an error.

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

* feat(publish): F09000 column for sales BEA records as negative purchases (#1270)

derive_negative_purchase_sales returns, by commodity, the domestic Use cells
that are negative in the Use table itself (used-goods sales, government sales
from stocks). The Use-side final demand and a new published U column,
F09000 "Sales recorded as negative intermediate purchases", carry them, so
the NIPA-sourced final-demand columns are unchanged and each published U row
still sums to output. F09000 is kept out of FINAL_DEMANDS.

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

* test: quote the balance with the F09000 sales in the xfail reason (#1270)

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

* feat(nab): scrap correction for the national balance (#1270)

derive_Vnorm_scrap_corrected divides each industry's market shares by
1 - s, so A q counts its purchases at U / (1 - s) and the Use row no longer
adds up to q. derive_scrap_correction returns the difference by commodity,
-sum_j U[i, j] s_j / (1 - s_j), and derive_y_dom_from_use_final_demand
includes it. The scrap share moves to _scrap_share, shared with
derive_Vnorm_scrap_corrected (no change to A). On v1.0.0_8365fa4 it clears
562HAZ (1.0% and 1.6% in 2022-23) and the iron and steel gap.

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

* test(nab): leave BEA's accounting sectors out of q = L y, as USEEIO 2 does (#1270)

ACCOUNTING_SECTORS (scrap, used goods, noncomparable imports, the
rest-of-world adjustment) have no output of their own, so the check can't
hold for them. compare_output_vs_leontief_x_demand still computes L @ y on
every sector and compares the rest. The xfail reason now quotes the one 2024
failure, 334118; 2017-2023 pass.

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

* Revert the national-balance scrap correction (#1293)

It offset a double count in A and B instead of fixing it. #1293 fixes the
scrap correction itself.

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

* fix(eeio): don't divide market shares by 1 - s; x already excludes scrap (#1293)

USEEIO divides market shares by 1 - s, with s scrap's share of output
including scrap. The Cornerstone Make table has no scrap (S00401 maps with
weight 0), so x is already net of it, and U / x and E / x already land on
the other products. Dividing again counted scrap twice in A and B.
derive_Vnorm_scrap_corrected now returns the plain market shares, which
gives exactly USEEIO's A and B.

B @ q now equals E (4,808.491 in 2024; it was 4,812.5). N falls for
scrap-making industries: remediation -11.1%, metal stampings -5.9%,
hazardous waste -2.4%, landfills -1.5%; median -0.17%. New integration
test: B @ q conserves E.

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

* feat(nowcast): anchor the computers family's exports on published 2017 (#1270)

Census domestic exports put the computers family (3341) at 1.28 times BEA's
2017 exports, and terminals (334118) export more than they produce, so its
imports exceed its use. Each 3341 commodity's export cell is now its
published 2017 F04000 cell moved by its own Census growth, the #763
construction for imports. Terminals' exports fall to 0.74-0.92 of output in
2017-2023 (were 1.01-1.13 in 2022-2024); 2024 sits at 1.00 because output
falls while Census exports grow. Needs a rebuild to reach the tables.

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

* fix(eeio): keep the scrap correction where x is BEA gross output (#1293)

Review on #1292 (Ben): when use_ghg_year_x_in_B or the deflation branch puts
BEA gross output in B's x, that output includes scrap, so the 1 / (1 - s)
raise was doing real work there. derive_B_via_vnorm now divides the market
shares by 1 - s on that path only, with s = scrap / output including scrap
(scrap_share_of_output; electricity children share a parent's scrap by
output). Both paths are now USEEIO-exact: v0.3 B matches
E / (x_GO (1 - s)) V_n to 9e-16, and v1.0 B q = E (4,808.491). Also fixes the
stale Vnorm column-sum comment in inflation_helpers_usa.

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

* fix(eeio): share a split industry's scrap by output; mock it in the B steps test (#1293)

Review on #1292 (Ben): industry_corresp gave each of 562000's seven waste
children 1/7 of its scrap, so remediation's s was 7.28% against the parent's
0.72%, and v0.3 B ran 7.0% high on 562213. scrap_share_of_output now shares
a split BEA industry's scrap across its Cornerstone children by output, so
every child gets the parent's share (0.722% for all seven; nothing outside
562* moves). test_useeio_b_adjust_divide_transform_steps now mocks
scrap_share_of_output (I1 0.1, I2 0) and expects 0.08 / 0.9, which pins the
gross-output branch.

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

* feat(nowcast): anchor the pharmaceutical family's exports on family growth (#1194, #1270)

Review on #1295 (Ben): Mo's 325411 finding is the same Census-vs-BEA gap.
Census puts preparations on 325411 ($7.3bn of 2017 exports against $0.08bn
published) and 325413, so a row's own Census line is no mover: own-growth
anchoring would put biologics (325414) at $52.7bn of 2024 exports against
$48.0bn of output. FAMILY_GROWTH_EXPORT_FAMILIES = {'3254'} holds BEA's 2017
split of the family and moves it by the family's Census growth (1.81 by
2024). All four rows stay below output in 2017-2024 (325414 peaks at 0.87).
anchored_exports applies both modes.

Also from the review: rebuild_nowcast.yml pinned to ubuntu-24.04, as #1297
did for CI, and the rebuild script's anchor comment covers exports.

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

* fix(publish): F09000 on the reaggregated U, its sign in the name, and a test mock (#1270)

Review on #1290 (Ben):
- get_U's reaggregation branch sums the negative-purchase sales on the
  electricity children into 221100, as the final-demand block does, instead
  of reindexing them onto the 405 axis.
- The F09000 description says its values are negative.
- test_get_U_commodity_axis_is_parent_not_children mocks
  derive_negative_purchase_sales (on the 407 axis), so it no longer runs on
  real data.

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

* feat(nowcast): turn on trade_margin_receivers_follow_supply in the v1.0 configs (#1194)

Step 4 spread each year's trade margin over the receiving goods by frozen
2017 shares, so goods whose supply grew slower than total trade got margins
above their supply (motor homes 336213 at 1.17 of supply in 2024; Phi 0.19).
#1260 added the flag; this turns it on in the nine v1.0 configs and adds it
to the rebuild's flag check. Needs a rebuild.

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

* Pin v1.0 to the CI rebuild v1.0.0_fd271dc (#1270, #1194)

Run 43349311057 rebuilt 2017-2024 on main 83dfd6b (the #1289-#1295 stack)
plus trade_margin_receivers_follow_supply. Uploaded to GCS, nothing
overwritten. The nine v1.0 configs, the GHG FBS upstream pin, both 2024 pins
(new sha256), the B time-series assessment and the config tests move from
8365fa4 to fd271dc.

q = L_dom y passes in every year 2017-2024, so
test_domestic_L_y_from_use_final_demand_equals_q drops its xfail.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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