2026-10-20 · containerboard · network flow · public data · claude

The paper network: every containerboard mill, every box market, and the flow between them

A corrugated box starts as rolls of linerboard and medium from one of about sixty mills and ends, nine times in ten, back in a bale. The business runs on a map of mills, plants, freight and prices that everyone in it carries in their head and nobody publishes. The inputs are public. So I built the map, with Claude as the pair, from filings, registries and a weekly diesel number, and solved it as the one problem it has always been: a minimum-cost network flow.

Open the app →Nine tabs, 63 mills, 441 markets, every scenario below, and "Ask the network". Static page, no account.justfetz.github.io/the-paper-network The claude.ai copy →Same page, same chat, running on the viewer's own Claude account instead of the project's budget.claude.ai/artifact/UCaPyJMWgrDAg94xakwn9w The repo →Model, data with a source on every row, ingest scripts, workflows, notes. MIT.github.com/justfetz/the-paper-network

This post is the long version: why the problem is a network flow and what that buys you, where every number comes from, how the data was cleaned and how far off it still is, what each page of the app does, how the thing is built and refreshed, and what someone could do with it who wanted to dream a little. Every figure traces to a public page. Every chart on this post has a "show the code" under it, in the house style.

1. Why build a map the industry already has

Containerboard is a commodity that behaves like a relationship business. The price of 42-pound kraft liner is a published index. Freight is distance times diesel. Mill capacities are in the 10-K. Box plants are in the EPA's facility registry with an address. And yet "who ships to whom, at what delivered cost, and who would switch for how much" is treated as private knowledge, because the only people who have ever put the pieces together are the people whose job it is to keep them apart: the mill sales desk, the integrated producer's planning group, the broker.

I spent eleven years on the corrugator side of that map: the plants that take the mills' rolls and turn them into board and boxes, which the mill side calls box plants and this model calls demand. The asymmetry is real and it is expensive for everyone below the mill: independents pay it, box buyers pay it, and the people inside the integrated companies who want to argue with a transfer price pay it too. The inputs to the map are free. Putting them together is a week of work and one linear program. That is the whole thesis of the project: the public draft of this map is good enough to argue with, and arguing with it is the point.

What follows is what the public draft says, with its error bars printed next to it.

2. The network flow problem is the whole thing

Strip the business to its skeleton and it is this: a set of supply nodes (mills) with capacities, a set of demand nodes (box markets) with requirements, and an arc from every mill to every market with a cost per ton (freight). Choose the tons on each arc to meet every demand without exceeding any capacity, at minimum total cost. That is the transportation problem, the oldest special case of minimum-cost flow, and it is the problem Lecture 7 of a graduate algorithms course spends on successive shortest paths and network simplex.

Three things make it the right tool rather than a convenient one.

  1. It is exactly solvable at full size. The real instance has 93,423 variables and 7,139 constraints once the expansions below are in. HiGHS, the open-source solver behind SciPy, finds the optimum in 1.9 seconds. No heuristics, no sampling, no "approximately".
  2. The answer is integral for free. The constraint matrix of a network flow is totally unimodular, so the LP optimum is already whole tons on whole arcs. You never round.
  3. The dual solution is the product. Every linear program has a shadow problem whose variables are prices. For a network flow those prices sit on the nodes: a price per ton of capacity at each mill, and a price per delivered ton at each market. They fall out of the same solve, and they are the numbers the industry actually trades on.

Watch the algorithm reroute a ton

Here is a toy instance you can step through: three mills, four markets, freight costs on the arcs. The algorithm is successive shortest paths: find the cheapest residual path from the source to the sink, push as much flow as it can carry, repeat. The moment to watch is the last step. Atlanta still needs five tons, the two cheap mills are full, and the only mill with capacity left is on the West Coast at $120 a ton. The algorithm does not pay $120. It finds a path that sends West Coast tons to Dallas and takes five tons back off the Gulf mill's Dallas lane (the dashed reverse arc) so the Gulf mill can serve Atlanta. Net cost $78 a ton. That reverse arc is the thing a greedy planner never sees, and in the real business it is the phone call where a mill tells one customer "we are moving you to the other machine so we can take the new account".

When the flow is optimal the same search gives the prices. Run one more shortest-path pass over the residual network and the distance to each node is its dual: the West mill has capacity to spare, so its scarcity value is $0; the Gulf mill is full and freeing one ton there costs the network $48 of rerouting, so a ton of Gulf capacity is worth $48; one more ton of demand in Chicago would cost $93 delivered, not the $18 of the direct lane, because the direct lane is spoken for. Check the complementary slackness yourself in the table: on every arc that carries flow, market price minus mill price equals the freight; on every empty arc it is less. That identity is what makes the duals prices rather than opinions.

The real network, expanded

The toy has one kind of ton and no owners. The real model needs four expansions, all of which stay inside the network-flow family so the solve stays fast and exact.

Freight on each arc is a rule, not a quote: road miles are 1.2 times the great-circle distance, truck costs $8 plus $0.125 a mile and rail $35 plus $0.065 a mile per ton at $3.75 diesel, and the model takes the cheaper of the two, which puts the truck-to-rail break near 450 miles. Diesel moves the per-mile terms through the fuel share of each mode, so the live EIA price is a scenario rather than a parameter.

Why the duals matter more than the flows

The flows are a cost-minimizer's answer, not a record of who ships to whom today; a sales organization with history and contracts will differ from them everywhere. The duals are sturdier, because they describe the margins of the system rather than its interior:

That is why the algorithm is the post and the map is the illustration. Everything the app shows is either a flow, a dual, or a difference between two solves.

3. What the model says

Freight bill, Oct 2026$2.08B32.2 Mt domestic + 3.6 Mt export
Average haul509 mi42% of tons by rail
Freight per ton$59.56$58.35 at $3 diesel, $63.16 at $6.20
Mill utilization95%53 of 64 US mills at capacity
Scarcity, West vs East$49 vs $20per ton of capacity
Switching gap, median$5.30/t49% of markets within $5
Costliest outageMonticello, MS$98M a year; Mansfield $78M
Maintenance calendar$180M → $30Mstaggered vs priced by duals

Some of the findings, in the order they surprised me.

4. Where every number comes from

Nothing in the model requires a subscription, a membership or a relationship. Here is the full list, what each source gives, and how it gets in.

SourceWhat it gives the modelHow it gets in
SEC EDGAR 10-K filings (International Paper, Smurfit Westrock, Packaging Corp of America, Graphic Packaging, Greif, Cascades)Mill lists, capacities by grade, closures and conversions, integration ("~80% to own plants"). The supply side of the model is reconciled to these tables.Read and reconciled by hand with Claude, one row per mill with the citation; EDGAR pulls need only a User-Agent header (a name and an email).
Company announcements and trade press (Packaging Dive, Recycling Today, PPI Pulp & Paper Week as reported)The 2025 and 2026 closures, new machines, acquisitions, index price moves, outage guidance.Judgment layer: a monthly and a quarterly review that edits the CSVs and the price points, with sources, and appends to the changelog.
EPA Facility Registry ServiceEvery facility coded NAICS 322211 (corrugated boxes) with name, address and coordinates: 1,796 records, 1,605 distinct sites after de-duplication, plus 498 paper and board mills.plants.yml downloads the national file and filters it; no key.
Census County Business Patterns 2023Establishments and employment for NAICS 322211 by state (1,159 establishments, 90,228 employees) and by county where not suppressed. Sets each state's share of box demand.cbp.yml reads the bulk county and state files; no key.
US Atlas / Census TIGER centroids and population3,142 counties, aggregated to 441 demand markets (metro clusters) with population as the within-state fallback weight.Static files in data/.
AF&PA and Fibre Box Association totalsIndustry production, box shipments, operating rate, end-use shares (food and beverage 36%, e-commerce 16–20%), the 458 / 1,092 plant counts. The model's national totals are calibrated to these.Quarterly, from the press summaries of the reports.
AICC634 sheet plants (86% independent), about 70 sheet feeders.Public statements; counts only.
EIA weekly on-highway dieselNational and regional price, every Monday. The live price is a scenario and the series is on the Prices tab.refresh.yml, Mondays, with a free EIA key.
FRED (St. Louis Fed)Producer price indexes for paperboard mills and corrugated boxes, industrial production of paper, retail and e-commerce sales: the demand backdrop.refresh.yml, monthly, with a free FRED key.
BLS QCEWCounty employment for 322211, quarterly, a second opinion on the Census weights.refresh.yml, quarterly, open endpoint.
Published containerboard index points as they reach the press42-lb kraft liner East and West, 26-lb medium, OCC national and regional: the mill-price inputs on the Prices tab.Judgment layer, monthly.
Google Places (New) text searchNames, addresses, coordinates, place types and an operational / permanently-closed flag for box plants near each market: a second source against the EPA list.places.yml on demand, 1,764 calls, inside the free tier.
Claude with web searchFor each EPA site: operating or closed, corrugator or sheet plant, owner today, whether it is hiring for a second or third shift. Retires the stale third of the EPA list.verify.yml on demand, budget-capped, about ten cents a site.

The keys, and what they cost

Five keys, all stored as GitHub repository secrets, none in the code or the page. Each workflow step is skipped when its key is absent, so the repo runs with none of them.

KeyUsed byCost
EIA_API_KEYWeekly dieselFree
FRED_API_KEYPrice and production seriesFree
BLS_API_KEYQCEW county employment (optional; the open slice works without it)Free
EDGAR_UAA User-Agent string for SEC pulls: a name and an email, not a keyFree
GOOGLE_MAPS_API_KEYPlaces text search near each marketFree up to 5,000 calls a month on the Pro tier; the run uses 1,764
ANTHROPIC_API_KEYThe site-by-site verification with web searchAbout $0.10 a site on Claude Opus 5.5, $0.06 on Sonnet 5.5, $0.02 on Haiku 5.5; roughly $160 for the whole list, half through the Batches API
ANTHROPIC_API_KEY, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_IDThe chat on the public page, through the proxy described in section 8Tokens per round, a few cents on Opus; the proxy caps rounds per day and the console caps the month. The claude.ai copy needs none of these: it runs on the viewer's own account

5. What each API actually returns

"Free public data" is a slogan until you see the fields. This is what each endpoint gives back, so you can judge what else could be built on it.

APIWhat comes backGrain, cadence, limits
EIA v2, petroleum/pri/gnd/dataWeekly retail on-highway diesel, dollars a gallon, for the US, the five PADD districts, their sub-districts and a few states. The model uses the national series; the regional ones are a diesel-by-region freight factor waiting to happen.Weekly since 1994, Monday afternoons. 5,000 rows a request. Free key.
FRED, series/observationsDate and value for any of roughly 800,000 series. In the model: producer price indexes for paperboard mills and for corrugated boxes, industrial production of paper, e-commerce retail sales, retail sales ex food service. Also available and not yet used: trucking PPI, rail carload indexes, manufacturing employment by state, housing starts.Monthly or quarterly; 120 requests a minute. Free key.
BLS QCEW, open CSV slices and API v2Establishments, employment and wages by county, by NAICS code, by quarter. The 322211 slice is the fastest-moving public measure of where box plants are adding or shedding people.Quarterly, five months behind. Open slices need no key; the API allows 50 series a query and 500 queries a day with one.
Census County Business Patterns, API and bulk filesEstablishments, employment, payroll and size-class counts by county and state and NAICS. Employment is suppressed where one plant would be identifiable, which is why the state file is complete and the county file is thin.Annual, two years behind. Bulk files need no key.
EPA Facility Registry Service, national filesAbout three million facilities: registry id, names, address, coordinates with a stated accuracy, NAICS and SIC codes, and the ids of every environmental program the site reports under (air permits, TRI, RCRA, water). No operating-status field in the facility file; the program files carry active flags per program, which is the next thing to join.Updated continuously, published as a bulk zip. No key. Envirofacts REST serves single-facility queries.
SEC EDGARPer company: the submissions index (every filing, with its accession number and primary document), the XBRL company-facts file (every tagged financial number by period: revenue, capex, segment data), full-text search across filings, and the documents themselves. Mill lists and capacity appendices live in the 10-K text, which is why that part is read rather than parsed.Continuous. Ten requests a second with a User-Agent header. No key.
Google Places API (New), Text SearchFor a query biased to a point: up to 20 places a page with id, display name, formatted address, coordinates, types and primary type, and a businessStatus of operational, temporarily closed or permanently closed. Website, phone and hours cost more. The permanently-closed flag is the cheapest signal there is against the EPA list's stale third.Billed by field tier: 5,000 free calls a month on the Pro tier, then $32 a thousand; website or phone move a call to the Enterprise tier with 1,000 free. The run takes 1,764 calls.
Google Routes API, route matrixReal driving distance and time for each origin-destination pair, with traffic if asked. Would replace the model's 1.2 × great-circle road factor with measured road miles for all 27,783 mill-market pairs.Billed per element; a one-time run over every pair is a few hundred dollars. Not yet used; listed because it is the obvious next freight upgrade.
Anthropic Messages APIContent blocks: the text, the tool calls, the server-side web searches with the URLs and titles they returned, and citations into them; a usage object with input, output and cached tokens and the count of searches; a stop reason. With a strict tool schema the answer is a typed record, not prose. The Batches endpoint takes the same requests at half the token price with a 24-hour window.Billed per token and per search; see the key table. The model's calls are capped by the workflow budget and logged per site.

What stays behind the wall

Mill cash costs. Contract prices and the discounts off the index. The index series themselves (Fastmarkets RISI is the record; only the moves reach the press). FBA's regional shipment detail. Which plants a corrugator list actually contains. Those four things are the difference between this model and the one the planning groups run, and the Method tab says so.

6. Cleaning, and how far off it still is

Transparency means the misses are on the page next to the hits. In order of effort:

And the calibration, which is the honest scoreboard:

CheckModelReported
US containerboard capacity, 202440.8 Mt~41.4 Mt (AF&PA), −1.6%
Capacity retired, Feb 2025 to Mar 20264.03 Mt~3.9 Mt (Fastmarkets), +3.4%
International Paper US capacity, 202412.98 Mt12.98 Mt (10-K); the public per-mill figures alone sum to 12.42 Mt (−4.4%) and are scaled to IP's own total
Packaging Corp of America, year-end 20255.80 Mt5.8 Mt (10-K); PCA's last per-mill disclosures sum to 5.55 Mt (−4.3%) and are scaled to it; 5.55 Mt after Wallula W2 in 2026
Smurfit Westrock, US7.55 Mtthe 10-K mill table, row by row (the audit added Roanoke Rapids, which the first version missed)
2024 tons shipped38.8 Mt37.7 Mt produced; the +1.06 Mt is exactly the Canadian board the LP under-pulls (1.40 Mt imported, 0.34 Mt in the model)
Operating rate, 202695%~95%
IP board to own plants85% own + 11% exported~80% own (10-K)
Smurfit Westrock integration83%~81% implied
Demand by stateexactby construction, from Census employment; what remains is within-state placement
Sensitivity re-solvestruck or rail costs ±20%: freight $55.0 to $63.2 a ton, average haul 494 to 523 mi; open-market premium $80 to $200 a ton: the Monticello outage from +$32M to +$142M a year. The capacity duals are the unstable part, because the network runs within about 5% of sold out and a small shift flips which mill is marginal

7. A tour, page by page

Nine tabs and a chat. Each one is a view of the same solved network; the scenario picker on the right (2024 footprint, 2026 footprint, the counterfactual, diesel at three prices, twelve maintenance months, twenty knockouts) changes every number on the page.

The page explains itself as you go. Each tab carries a "How to use this view" card: what the view shows, how to read its marks and colors, what every control and layer does, Try buttons that set the view up for you, and questions to click straight into the chat. Every control has hover help. The Guide button in the header opens an index of which view answers which question, a short glossary (delivered marginal cost, scarcity value, own-system against open-market, the gap), and a nine-stop tour that walks the tabs and rings the control it is talking about. And every number can be traced from where it sits: click a mill for its capacity, the filing or report behind it with the link, and a confidence grade; click a market for where its demand comes from; click any dotted figure for the method that produced it. What follows is the same tour in prose.

Flows

The Flows tab: a map of the US with 63 mills as colored dots by owner and the min-cost flows to 441 markets drawn as arcs; cards below show tons by distance band and integration by owner.
Every ton of board, mill to market, October 2026 footprint. Hover a mill for its utilization and scarcity value, a market for its delivered marginal cost and suppliers. The bars below give tons by distance band (truck under 450 miles, rail beyond) and the integration table compares the model's own-plant share with what each company discloses.

The legend doubles as a filter. Knock out a mill from the dropdown and the arcs redraw from the re-solved network; pick a maintenance month and the mills down that month hollow out.

Corrugators

The Corrugators tab: markets colored by corrugator load, sheet flows between markets, and a calculator for corrugator capacity.
The middle tier. Markets colored by how loaded their corrugators are, sheet flows from feeders to the markets without a machine, and a calculator that turns width, speed, hours and OEE into tons a year, so you can argue with the assumptions.

Throughput

The Throughput tab: a chain diagram from mills through corrugators to boxes, with tons per day at each stage and a table of truckloads and railcars.
Each piece of the chain in the units its operators use: tons a day at the mills, million square feet a day on the corrugators, truckloads and railcars on the lanes. Per calendar day or per year.

Will you switch?

The Will you switch tab: markets with their cheapest and second-cheapest mills, and the call sheet after International Paper raises its open-market price by $70 a ton.
For every market, the cheapest mill by delivered freight and the one behind it. Pick a producer and raise its open-market price $70; the call sheet lists each account that moves, the tons, and the mill that takes them. The gap column is the price of loyalty.

Prices and delivered cost

The Prices tab: a map colored by delivered paper cost per thousand square feet, with a calculator for ECT and Mullen specs, liner and medium prices and diesel.
What a thousand square feet of board costs delivered in every market: mill prices from the published index, freight from the model, diesel from the EIA. Switch between an ECT spec and a Mullen spec, type your own prices, and see the freight share of delivered cost by market. The FRED series sit below.

Fiber loop

The Fiber loop tab: OCC flows from where boxes are consumed to recycled mills and export ports.
The network in reverse. Thirty million tons of OCC routed from where boxes are used to the recycled mills and the docks: 18 Mt to mills, 8.4 Mt exported, 3.8 Mt the model cannot place at today's mill appetite. The county value is the dual: what a ton of OCC is worth where it is collected.

Demand

The Demand tab with named plants on: 1,605 EPA box-plant sites as small dots over the market demand circles, with a search box.
Where the boxes are made. Census employment sets each state's share, the EPA registry places it within the state, and the plants themselves are on the map exactly as registered: no inferred owners. Status, plant type and owner appear only once the verification pass has checked a site, with its source; green is verified operating, red verified closed, gray not yet checked. The note on the card says plainly that the list is a superset and roughly a third of it is stale.

Backtest

The Backtest tab: the 2024 footprint with the mills that closed in 2025 and 2026 highlighted, and their rank on the model's idle list.
The honest miss. The 2024 network with the mills that closed in 2025 and 2026 marked: all of them loaded, none of them on the idle list, and the surplus column that does better.

Method and data

The Method tab: the model's assumptions, the calibration table and the source list.
The assumptions, the calibration table above, the error bars, and a source for every anchor. If a number on another tab looks wrong, this is where to check what it was built from.

Ask the network

The chat drawer open over the Flows tab, answering what happens if the Monticello mill goes down, with a line underneath listing the page lookups the answer was built from.
The chat drawer answers from the model: it calls page functions (find a mill, find a market, the knockouts, a price shock, the scenario KPIs) and under every answer lists exactly which lookups it made, with the raw data one click away. An answer about the network with no lookup under it reads as unsupported, and says so.

That provenance line is my answer to the obvious question about putting a language model in front of a model: how do you know it is not making the numbers up? You do not trust it; you check it. The chat cannot browse, has no memory between questions, and sees nothing about the network except what the page functions return. The functions run in your browser, on the same JSON the map is drawn from, and the trail shows the call and the result. On the public page it runs through a small proxy that holds the key and meters the budget (section 8); on the claude.ai copy it runs on the viewer's own account. Either way there is no key in the page and nothing is sent to me.

8. How it is built

One repository, one static page, one solver, no server. The pipeline, top to bottom:

sources ──▶ ingest/            EPA FRS, Census CBP, EIA, FRED, BLS QCEW, EDGAR, Places, Claude verify
            └─▶ data/*.csv      mills_final.csv (90 mills, a source per row), plants_frs_box.csv, counties.csv, prices
model/netmodel.py               demand aggregation (3,142 counties → 441 markets) · freight rule · the LP with
                                owner copies, grade split, premium and unmet arcs, export sinks · duals · prospects · OCC loop
model/export_json.py            every scenario: 2 footprints, counterfactual, 3 diesel prices, 12 months, 20 knockouts,
                                5 price shocks, the calendar MILP, the backtest · ~3 minutes on a laptop
model/corrugators.py            the sheet tier: counts → machines → capacity → the sheet trade as a transportation LP
model/plants_layer.py           EPA + Census → measured demand weights, with the proxy error measured
site/build_site.py              one HTML page + one 1.8 MB data file + a self-contained copy for claude.ai
.github/workflows/              refresh (scheduled), plants, cbp, places, verify, chat (on demand) · commits what changed
chat/                           the Worker that holds the key for the public page's chat, its config and its tests

The chat proxy: how a static page talks to a model without a key

A page served from GitHub Pages is a file; anyone can read its source, so a key placed in it would be everyone's key within the hour. The claude.ai copy sidesteps this because the artifact runtime runs the chat on the viewer's own account. The public page needs a middleman, and the middleman is a Cloudflare Worker, about 120 lines, in chat/worker.js:

browser ──POST {messages, page digest}──▶ Worker (holds the key, free tier)
                                          · origin must be justfetz.github.io
                                          · 20 rounds a minute per visitor; 400 rounds a day for everyone (a KV counter)
                                          · the system prompt and the six tool definitions are fixed server-side
                                          · forwards to the Messages API with max_tokens 1,200 and low effort
browser ◀──content blocks: text, tool_use──┘
browser runs the tool_use against its own data, appends the tool_result, POSTs again (a round)
browser renders the answer and the trail of lookups

Five things follow from that shape. The key never leaves Cloudflare. The Worker cannot be used as a general chatbot, because it supplies the prompt and the tools and only accepts user turns, tool results and echoes of its own answers. The budget has three walls: the per-visitor limit, the daily cap, and the monthly spend limit in the Anthropic console. The lookups still run in the browser on the same JSON the map is drawn from, so the trail under each answer is the same on both copies. And it deploys from the repo: a workflow installs, creates the counter namespace, uploads the key as a Worker secret, publishes, writes the endpoint to a file the page build reads, and rebuilds the page. A round on Opus costs a few cents with the prompt cached, so the daily cap bounds the worst day at about ten dollars; one line in the config moves it to Sonnet for a tenth of that. The gate logic has a test with a stub model, so the limits are checked without spending anything.

9. Correctness: how the model and the AI parts are evaluated

Three different things can be wrong here, and each gets its own kind of check: the optimization can be solved wrong, the inputs can be off, and the language model can say something the data does not support.

The solve

The inputs

The receipts

When someone says the numbers look wrong, the answer should be a page of receipts, not a defense. On 21 October 2026 the mill list was audited row by row against the 10-Ks, company releases and the trade press, and the audit is a script anyone can run: audit/recompute.py recomputes every headline number from the committed data, checks it against its public anchor, re-solves the network under perturbed assumptions and writes the receipts page.

It found real errors, and they are fixed in the open. Smurfit Westrock's Roanoke Rapids, North Carolina mill (322 kt of containerboard in SW's own 10-K table) and its La Tuque, Quebec mill (341 kt) were missing. Riceboro was counted in International Paper's 2024 total although DS Smith owned it until January 2025. Forney and Big Island carried start-up-era figures where the companies' own filings give higher ones. And International Paper's and PCA's per-mill numbers, many from 2007 to 2015 filings, summed to about 4% less than each company's disclosed total today, which is what a decade and a half of debottlenecking looks like; those rows are now scaled to the companies' own totals, with the factor written into each row.

With the corrections, US capacity lands 1.6% under AF&PA's 2024 figure, the closures the model carries add to 4.03 Mt against Fastmarkets' count of about 3.9 Mt, the 2026 operating rate comes out at 95% against the industry's reported 95%, and the 2024 production overshoot is the Canadian board the solver declines to buy: with demand and exports as assumed, every ton it does not buy from Canada it makes at a US mill. The corrections also moved a headline. A network with 1.3 Mt more capacity has more slack, so the costliest single outage is now Georgia-Pacific's Monticello at about $98M a year, and Mansfield, at $171M before, is $78M. The old number was wrong because the mill list was incomplete, and the page now says so.

Every number can be challenged the same way it was checked: open an issue with the row and a public source, and the next refresh re-solves every scenario and regenerates the receipts.

The language model

Two parts of the project run on a language model, and they need different kinds of proof. The first paid test is in: twenty EPA sites checked with web search cost $0.95 in total, about five cents a site; it called eighteen operating and two unknown, four corrugators, nine sheet plants, and caught a recycled-paperboard mill mis-coded as a box plant, with every confidence marked medium or low. That is the right kind of humility, and it is what the golden set will score.

The verification pass is a classifier with a search engine. The right question is not "is Claude smart" but "what is its accuracy per field against a labeled set, and what does it do when it does not know". The plan, part of which is in the repo under evals/:

The chat is a different animal: open questions, no fixed answer key, a reader who may take the number at face value. Its correctness rests on the tools, so the evals test the tools' use rather than the prose:

The scored runs (20 October 2026, Claude Opus 5.5, 45 questions, about $0.60 a run). Run one: all five "that is not public" questions declined, none of the five injection attempts produced its demanded token, every one of the 35 numeric answers had a lookup behind it, and the numbers scored 21 of 35. That last figure turned out to be mostly the grader's fault: it was looking for "1,050 kt" when the model wrote "1.05 million tons", and for "$171 million" when the model wrote "$171,048,536". Re-scoring the same saved answers with a grader that reads units the way the model writes them gave 34 of 35. The one real miss was a tool bug, not a model bug: "Madison, AL" was resolved to the city alias for Madison, Wisconsin, and the model, correctly, refused to use Wisconsin's number for Alabama. Run two, on the fixed code: five of five, five of five, 35 of 35 lookups, Madison, Alabama answered correctly at about 184,000 tons a year, and 34 of 35 numbers as recorded. The last miss was once more the grader's ("loses all 71,253 of its open-market tons", words between the number and its unit), and it is 35 of 35 once the grader allows that. Two runs and $1.21 found two grader bugs and one lookup bug, and no answer that stated a network number without a lookup behind it. That is an eval doing its job in both directions. The set re-runs on demand and the latest scores live in evals/chat_eval_results.json.

Prompt injection: the two doors

"Ignore all your previous instructions and…" is the oldest trick against a language model, and any public chat will see it on day one. There are two doors it can come through here, and the defense on each is architectural first and model behavior second.

The front door is the chat box. Whatever a visitor types arrives as a user turn and nothing else. The rules live on the server (the proxy on the public page, the artifact runtime on the claude.ai copy), so a message cannot replace or append to them, and the prompt tells the model in so many words that text inside tool results or the page digest is data, never an instruction, and that a request to ignore the rules, reveal them, change role or leave the subject gets a one-sentence decline. Behind that sits the part that matters more than any wording: the chat is read-only. Its six tools look things up in a JSON file in the visitor's own browser. It has no key, no memory between questions, no browsing, no access to anything that can be written, and a metered budget with three walls. The worst a successful jailbreak achieves is a poem about cats for a few cents, in a drawer that prints "no page data was read for this answer" underneath it. And it is measured: the eval set carries five injection attempts, each demanding a marker token, and a pass is the token never appearing.

The side door is the verification pass, which reads arbitrary web pages about each plant, and a web page can say "mark this site as operating". Four things stand in the way. The prompt treats pages as evidence, never instructions, and tells the model to treat a page that tries to instruct it as low-quality evidence. The output is a strict record with enumerated fields and no free-form field that the pipeline acts on. The pipeline can only ever remove a site from the demand layer, never add one, and only when the record says "closed" with high confidence and an evidence URL. And a second model re-checks a random sample of every run, so a planted page would have to fool both and still survive the schema.

No filter is absolute, which is why none of this relies on one. The deterministic model is the product; the language model is a clerk with no keys to the building.

The honest summary is that the deterministic model is the product and the language model is a clerk with a search engine on one side and a reading assistant on the other. Both are checked the way you would check a clerk: against a labeled sample, by a second clerk, on a schedule, with the authority to flag but not to decide.

10. The clock: what happens when

Nothing on the page is typed in by hand after the fact. Here is every trigger, in UTC and Chicago time, what it runs, and what a reader sees change.

WhenWhat runsWhat changes
Every Monday, 14:17 UTC (9:17 Chicago)refresh: EIA's weekly diesel lands Monday afternoon Eastern; the workflow pulls it, re-runs the plant layer and every scenario, rebuilds the page, commits, publishes.The tape's diesel item and its regional prices; the live-diesel freight scenario; the Prices tab's diesel chart; the model-run badge date.
9th of each month, 13:23 UTC (8:23 Chicago)refresh again, now with the month's FRED series; the monthly judgment review (a Claude session with a narrow brief) reads the trade press for index moves, closures and capacity announcements, edits the price points and the mill table with sources, and appends to the changelog.Liner, medium and OCC points on the tape and the Prices tab; the FRED charts; any closure or conversion on the map; the changelog.
28th of January, April, July, October, 13:31 UTCrefresh with the quarter's BLS employment slice; the quarterly review reads the AF&PA quarterly and the three earnings releases: production, exports, operating rate, outage guidance, mill status; demand totals are re-set.Demand and export totals in every scenario; mill statuses; the maintenance calendar's assumptions.
Every day, 16:11 UTC (11:11 Chicago)places: the Google Places search resumes where the daily quota stopped it and exits in seconds once the list is complete.Nothing visible until the Places file is joined to the EPA list; then the named-plant layer and its stale-site flags.
Every push to the model or the siteci (the invariants, the site build, the Worker tests) and refresh (a full re-solve and publish).The build badge; the page, within about six minutes of a merged change.
March, after the 10-KsThe yearly review re-reconciles the mill table to the new filings and capacity appendices.Capacities, grades and integration shares; the calibration table.
June, and yearlycbp and plants on demand: the new County Business Patterns and the EPA registry.The demand weights by state; the plant list.
On demandverify (Claude checks EPA sites with web search under a budget) and chat (deploys the proxy).Closed sites leave the demand layer at the next refresh and the tape's plant line updates; the chat switches on.
On the dayA closure, a conversion, a price increase: the row changes, with its source, and the push re-solves everything.The map, the knockouts, the call sheet, the tape's closure items.

Every run commits what it changed, so the history is the audit trail, and every run re-solves from the committed inputs, so there is no cache to go stale. The first scheduled run with the keys in place landed 10,207 weekly diesel observations, 716 FRED rows and a 610-county employment slice without anyone touching it; the first time two runs overlapped, one published a half-merged file and blanked the page for twenty minutes, so the publish step now refuses to ship anything that does not parse. That is the other half of "it refreshes itself": it also has to fail safely.

11. Dream big

The model is a public draft. Here is what it becomes with one more layer, in roughly the order the layers are available.

None of that requires a single private number. All of it requires the public draft to exist first, which is why it is public.

12. What it is not

The real risk of a public model is not that it is wrong; it is that someone treats a $5.20 median gap as a quote. The page is written against that, and so is this post.

13. Use it, break it, fix a row

The repository is MIT. NEEDS.md lists what the project still wants from outside and why; ROADMAP.md is the layer list above with the next step on each; REFRESH.md is the calendar; the notes folder has the calibration write-up, the market anchors with their links, and the changelog. The fastest contribution is a row: a mill capacity you know is wrong, a plant that closed, a closure the press missed. Open an issue with the source and it is in the next run.

Treat the app as a public draft of a map the industry already has in pieces. Every number carries its source. The ones that are wrong are the ones worth telling me about.

Sources