The six payloads, and what breaks without them
The seam between the two halves. Each is written by exactly one builder, read by one or two modules, and committed to git because Vercel cannot regenerate it. Sizes below are bytes on disk, read on the server; nothing heavy is fetched into your browser until you press Inspect.
Past roughly 50 MB these should move to Vercel Blob and the fetches should be swapped for signed URLs. Nothing else in the product would tell you this is approaching — the figure is live here for that reason. It counts every file in public/evidence/ (40 of them, 19.12 MB), not the single representative one shown in the table below; that directory is the largest thing here and also the cheapest, because it is only ever fetched one account at a time.
Builder, payload, reader
This page is wrong — what do I rebuild, and in what order?
The dotted edge is the ordering constraint: prospect.index reads dashboard-data.json, so opportunity.build must have run first.
Payloads
public/dashboard-data.json
Everything under Sell. Accounts, demand rows, plays, coverage verdicts and the HS6 search index all come from here, and the four /api routes read it server-side.
Built by python3 -m scripts.opportunity.build · read by lib/contract.ts, lib/server-data.ts
public/evidence/arkema.json
Incumbent tables and the declaration-level evidence on account and opportunity pages. Fetched lazily per account, so a missing file degrades one account rather than the app.
Built by python3 -m scripts.opportunity.build · read by lib/contract.ts — loadEvidence()
public/analytics-cube.json
The home page entirely — every chart and the cross-filter. Small enough that a full filter pass is sub-millisecond, which is what makes exact cross-filtering possible with no server.
Built by python3 -m scripts.analytics.cube · read by lib/cube.ts
public/entity-cube.json
Company-level relationships on /product/[hs6] — who supplies whom, the sankey and the whitespace matrix. Capped at 60 pairs per code; the cap's cost is reported in its own meta.
Built by python3 -m scripts.analytics.entities · read by lib/entities.ts
public/prospect-index.json
/prospect, /competition and every /supplier/[key] page. Must be rebuilt after the opportunity contract, because it reads that file to decide which companies count as accounts.
Built by python3 -m scripts.prospect.index · read by lib/prospect.ts
public/lanxess-catalog.json
All four /lanxess routes and the product strip on /product/[hs6]. Built from a customer spreadsheet, not the customs pipeline — the only payload that does not derive from trade_clean_all.csv.
Built by python3 -m scripts.lanxess.catalog · read by lib/lanxess.ts, lib/use-lanxess.ts
API routes
Four JSON endpoints with no rendered UI. They do not read public/ — they import the contract server-side through lib/server-data.ts, so they answer from the bundle rather than over the network. No document mentioned them until this one.
/api/accounts | Coverage rows plus metric definitions, straight from the contract. |
/api/demand | One account's demand, bucketed against a portfolio passed in the query string. |
/api/search | BM25 over the shipped HS6 search index. |
/api/pull | Deep-extract requests. No provider is wired; it says so rather than spinning. |