Skip to content

Anchor the computers and pharmaceutical exports on BEA's 2017 cells (#1270, #1194) - #1295

Merged
WesIngwersen merged 6 commits into
nab_scrap_correctionfrom
computers_exports_anchored
Nov 13, 2026
Merged

WesIngwersen merged 6 commits into
nab_scrap_correctionfrom
computers_exports_anchored

Conversation

@WesIngwersen

@WesIngwersen WesIngwersen commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Part of #1270 (to-do 4) and #1194 (medicinal ingredients). Stacked on #1292. It changes Step 5 inputs, so it reaches the tables only in the next rebuild.

What was wrong

The export column (F04000) is Census domestic exports, mapped to BEA commodities (#762), and nothing conditions it. Imports, by contrast, are tied to BEA's summary totals (#785), and the computers family's imports are anchored on BEA's 2017 cells (#763).

For computers (3341), Census domestic exports run well above BEA's. In 2017, $bn:

Commodity BEA published exports Census domestic exports Output
334111 computers 7.14 7.89 8.32
334112 storage devices 2.22 2.60 4.16
334118 terminals and peripherals 6.06 9.22 8.14
family 15.42 19.71 (1.28×)

Terminals export more than they produce: 1.01 times output in 2022 and 1.11 in 2024. A row that exports more than it produces leaves its imports nowhere to go. Total use excluding exports falls below imports, so q = L_dom y fails for terminals and the #1282 guard flags the row (2024: imports $0.76bn above use).

It isn't re-exports. Terminals are heavily re-exported, $16.4bn in 2017 against $19.9bn of Census imports, but #762 already leaves re-exports out. The excess is in the domestic exports themselves. On the import side, re-exports don't distort the family either. Its 2017 imports are BEA's published cells (#763). After that they move with Census import growth, which is almost the same with or without re-exports in 2024 (1.50 against 1.44 times 2017).

The change

bedrock/transform/iot/nowcast_export_anchoring.py, used in derive_initial_Y_pur: each 3341 commodity's export cell is its published 2017 F04000 cell moved by its own Census growth. That is the #763 construction for imports, applied to exports:

X_t = X_pub,2017 × (X_census,t / X_census,2017)

The same edge rules as for imports apply. A commodity with no published 2017 value keeps Census. A commodity with no Census 2017 value holds its published value. Every commodity outside ANCHORED_EXPORT_FAMILIES keeps Census.

Effect on the export column (before the balance)

Terminals' exports as a share of output:

2017 2018 2019 2020 2021 2022 2023 2024
Census 1.13 1.16 1.22 1.21 1.25 1.40 1.36 1.52
anchored 0.74 0.76 0.80 0.80 0.82 0.92 0.89 1.00

Computers and storage devices stay below output in every year (0.70-0.92 and 0.32-0.84).

⚠️ 2024 terminals is still on the edge, $6.85bn of exports against $6.86bn of output. Census exports grow 13% from 2017 while terminals' output falls from $8.1bn to $6.9bn. Whether that fall is real is a question for the output side; this PR doesn't touch it.

Pharmaceuticals: anchored on the family's growth

Mo found the same gap for pharmaceuticals on #1286. Census puts pharmaceutical preparations onto medicinal ingredients (325411) and diagnostics (325413). In 2017, $bn:

Commodity BEA published exports Census domestic exports
325411 medicinal ingredients 0.08 7.30
325412 preparations 30.13 16.69
325413 diagnostics 0.12 6.57
325414 biologics 22.57 19.72
family 52.90 50.28

Medicinal ingredients exporting $14.0bn in 2024, more than its $13.2bn of output, is what drove its Phi to 0.04 (#1194).

Here a row's own Census line is no mover, because the rows are misclassified. With own-row growth, biologics would export $52.7bn in 2024 against $48.0bn of output. So FAMILY_GROWTH_EXPORT_FAMILIES = {'3254'} holds BEA's 2017 split of the family and moves the whole family by its Census growth (1.81 times 2017 by 2024):

X_t = X_pub,2017 × (Σ_family X_census,t / Σ_family X_census,2017)

All four rows stay below output in every year 2017-2024. Biologics peaks at 0.87, medicinal ingredients and diagnostics are at 0.01, and preparations at 0.35. Computers keep own-row growth (ANCHORED_EXPORT_FAMILIES = {'3341'}), because their rows' own Census lines are sound. anchored_exports applies both modes, and every row outside the two families keeps Census.

Not added: automobiles (336111). Its export input is already right ($23.7bn against $23.5bn published), and the pair is already split by BEA's 2017 export mix. The doubling happens in the Step 5 balance (#1296).

Also in this PR

Verified on a CI build of 2022 and 2024

Run 43303427722 built Steps 5-7 from this stack. Nothing was uploaded. On the balanced tables, after redefinitions:

Terminals 334118 8365fa4 this stack
2022 exports / output 1.01 0.67
2024 exports / output 1.11 0.73
2024 imports above use +$0.76bn none

q = L_dom y passes in both years. (The same build also carried a car import split, which didn't work and isn't in this PR. See #1296.)

Checks

black, ruff, mypy, and the unit tests in test_nowcast_export_anchoring.py (6 passed): both modes, 2017 reproducing BEA, rows outside the families, and the edge rules. The effect on the balanced tables needs a rebuild.

🤖 Generated with Claude Code

#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>
@bl-young
bl-young added this pull request to stack #1291 November 12, 2026 21:40

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

The mechanism is right and mirrors #763. I checked the export ratios against the 8365fa4 trade FBS. 334118 exports/output goes 1.13 -> 0.74 (2017, equal to BEA), 1.40 -> 0.92 (2022), 1.36 -> 0.89 (2023) and 1.52 -> 1.00 (2024). 334111 and 334112 stay below 0.9. Every rebuild year job already builds Trade_Exports_2017 via build_trade({2017, year}), so no workflow change is needed.

Notes:

  1. 2024 terminals land at exactly 1.00, so the 334118 xfail and the #1282 warning likely survive the next rebuild. Worth an issue for the output side (terminals' output falling from $8.1bn to $6.9bn).

  2. Mo's other export findings on #1286 (325411 pharma, 336111 doubling) look like the same Census-vs-BEA gap. Add them to ANCHORED_EXPORT_FAMILIES here, or file them separately?

  3. rebuild_nowcast.yml uses the same setup-python on ubuntu-latest, so it will hit the Python 3.11.13 failure that's breaking CI here. Fix that before dispatching the rebuild that picks this up.

  4. Nit: the comment in scripts/rebuild_nowcast_year.py says "2017 is the import anchor"; it's the export anchor now too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WesIngwersen and others added 3 commits November 12, 2026 18:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rowth (#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>
@WesIngwersen WesIngwersen changed the title Anchor the computers family's exports on BEA's 2017 cells (#1270) Anchor the computers and pharmaceutical exports on BEA's 2017 cells (#1270, #1194) Nov 13, 2026
@WesIngwersen

Copy link
Copy Markdown
Member Author

Thanks @bl-young. Taking your notes in order, in 1331e58b:

  1. Terminals' 2024 output: no issue filed, because the fall is BEA's own. BEA's gross output for industry 334118 goes from $8.76bn in 2017 to $7.43bn in 2024 (−15%), and our commodity output follows it ($8.14bn to $6.86bn, −16%). The 2024 tie at 1.00 is Census exports growing 13% against that. Anchoring on the family's growth instead would make it worse: the family grows 1.42 times, so terminals would reach $8.6bn. So own-row growth stays for computers. If the 2024 row still warns after the rebuild, the balance has $0.01bn to move.
  2. Pharmaceuticals: added, with a family-growth mode. Own-row growth would have put biologics (325414) at $52.7bn of 2024 exports against $48.0bn of output, because the Census rows are misclassified. So 3254 holds BEA's 2017 split and moves by the family's growth (1.81 by 2024). All four rows stay below output in 2017-2024. Medicinal ingredients goes from $14.0bn of 2024 exports to $0.15bn, which is the Nowcast v1.0 margins: producer value collapses or turns negative while margins don't (Phi 0.011 for 336213, 0.143 for 325411) #1194 Phi case.
    Automobiles: not added. Its export input matches BEA ($23.7bn against $23.5bn), and the pair is already split by BEA's 2017 mix. The doubling is the Step 5 balance (Make cars (336111) and light trucks (336112) mirror each other until they are merged #1296), which the car import split I tried couldn't fix either.
  3. rebuild_nowcast.yml: pinned to ubuntu-24.04. ci: pin ubuntu-24.04 so setup-python can resolve 3.11.13 #1297 didn't cover it.
  4. The nit: the comment now says 2017 is the import and export anchor.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@WesIngwersen
WesIngwersen merged commit 83dfd6b into main Nov 13, 2026
5 checks passed
@WesIngwersen
WesIngwersen deleted the computers_exports_anchored branch November 13, 2026 01:43
WesIngwersen added a commit that referenced this pull request Nov 13, 2026
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>
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