Equities, infrastructure, and power data for the AI power economy.
GridTilt is a research dashboard tracking the financial and physical buildout of AI infrastructure: compute, data centers, generation, transmission, and the public companies positioned around them. It combines live equity quotes, live GPU rental prices, hand-curated infrastructure datasets, and grid-relevant news.
Live: gridtilt.com
Nine tools in the top navigation. Retired routes (/supply-chain, /power-deals, /queue, /gpu-economics, /trade, /portfolio, /brief) redirect into tabs of these.
| Tool | Route | What's inside |
|---|---|---|
| Tilt Overview | /overview |
Measured headline gauges (tracked AI data center power in GW, fleet-average GPU rental cost, tightest reserve margin among AI-load RTOs), tracked buildout over time, top movers, sector pulse, next catalysts, and a US electricity demand chart (static annual series from EIA Electric Power Monthly with labeled projections). |
| The Stack | /stack |
100 tickers across 13 supply-chain layers with live quotes and sparklines. Card, table, heatmap, and flow views; the flow view is a d3 force graph of 24 supply-chain nodes and 52 real supply relationships. Also carries the uranium correlation card (see below). |
| Power | /power-map |
US data center facility map (Leaflet), plus tabs for AI power deals and the grid interconnection queue. |
| Compute Frontier | /compute-frontier |
235 named AI superclusters across 77 operators: GPUs, chip type, rated and planned power, grid region, energy source. Each cluster carries its own source links, and every estimated value sits in a per-cluster estimated[] list. GPU counts appear only where an operator disclosed one. |
| GPU Prices | /neocloud-intel |
GPU rental price index built from live marketplace observations, with per-model price history. The economics tab models the cost of AI compute. |
| Analyze | /analyze |
Portfolio tab scores any portfolio 0-100 on AI-power exposure; scenario tab models demand growth, nuclear capacity, and grid stress. |
| Catalyst Tracker | /catalysts |
Earnings dates (live from Yahoo, seeded fallback) plus curated thesis catalysts on a monthly calendar. |
| Analysis | /blog |
Research articles plus the daily Buildout Brief. |
State pages (/state/:slug) are the indexable, maintained reference for a state: its grid facts, dated public decisions with their documents, next public dates, related project records and the limits of what a price can say about a bill. Only Maryland is published (/state/maryland, from server/data/state-pages.json); any other slug is a 404.
A news ticker runs across every dashboard page: a 7-day rolling feed from 8 industry RSS sources (Utility Dive, DCD, World Nuclear News, Power Engineering, POWER Magazine, Latitude Media, DOE, EIA), with NewsData.io as an optional additional tier.
The rule across the app: serve real observations, flag estimates, and degrade with an explicit error instead of fabricating a number.
- GPU prices were recorded daily from RunPod (secure and community on-demand pricing) and Vast.ai (median of the cheapest verified on-demand offers). That sweep is paused until both providers give written permission; it runs only when
GPU_LIVE_SWEEP_ENABLED=true. A model with no live source that day records nothing; curated anchor prices are flaggedest.and charts interpolate across gaps instead of inventing points. - Uranium correlation is computed from 52 weeks of real weekly closes: SRUUF (Sprott Physical Uranium Trust, the closest freely quotable spot proxy) against CCJ and CEG. On fetch failure the card shows an unavailable state, not invented dots.
- Equity quotes fall back per ticker to labeled static data when Yahoo throttles; the change column renders "--" rather than a stale number pretending to be live.
- Keyed sources respond
{configured: false}with setup instructions when the key is absent; upstream failures return 502. Nothing is faked on failure.
| Data | Source | Behavior |
|---|---|---|
| Equity quotes, sparklines, fundamentals (~100 tickers) | Yahoo Finance via yahoo-finance2 (unofficial, indicative) |
Live; per-ticker labeled static fallback |
| GPU rental prices | Curated, source-verified estimates (est.); RunPod + Vast.ai live sweep paused pending written permission |
Recorded days stay in history; no new live points while paused |
| Uranium correlation | SRUUF vs CCJ and CEG weekly closes via Yahoo | Live; null (unavailable) on failure |
| US monthly electric output | FRED IPG2211A2N at /api/physical/electricity-output |
Live, daily cache; 502 on failure |
| US48 hourly demand | EIA v2 at /api/physical/load-hourly (needs EIA_API_KEY) |
Live, 30-min cache; {configured: false} without a key |
| Industry news | 8 RSS feeds; NewsData.io with an optional key | Live, refreshed hourly |
| Facilities, superclusters, deals, interconnection queue, catalysts | Curated JSON in server/data/ with per-item sources; some files are machine-written by recorders and a reviewed RSS ingester |
Curated, refreshed as announcements land |
No proprietary data feeds and no scraped paywalled sources. Projections are labeled as projections.
The former headline gauges (AI Demand, Grid Stress, NPI) were sentiment formulas over equity moves. They no longer appear on the dashboard; the overview shows measured numbers instead. /api/kpis and /api/index-history still serve and record them so the published series stays continuous. Methodology and backtest: docs/INDEX_VALIDATION.md, npm run backtest:indices.
- Client: React 18, TypeScript, Vite, wouter, TanStack Query v5, Tailwind CSS, shadcn/ui (house-modified fork in
client/src/components/ui; do not regenerate from upstream), Recharts, visx, d3, react-leaflet. - Server: Express 5, single process. Vite middleware in dev, static files from
dist/publicin prod. Port 5000. - Persistence: JSON files in
server/data/andcontent/blog/, and Postgres for subscribers, consent and the newsletter delivery ledger whenDATABASE_URLis set (seedocs/runbooks/subscriber-storage.md). - Tests: Node's built-in test runner, across the server and client lib suites (
npm test).
npm install
cp .env.example .env # set UNSUB_TOKEN_SECRET and ADMIN_API_KEY; the rest is optional
npm run dev # Express + Vite middleware on port 5000npm run check # typecheck
npm test # node:test suites
npm run build # vite client + esbuild server -> dist/
npm start # serve the built bundle| Var | Required | Notes |
|---|---|---|
UNSUB_TOKEN_SECRET |
yes | HMAC key for unsubscribe tokens. openssl rand -hex 32. |
ADMIN_API_KEY |
yes | Guards /api/admin/*, newsletter send, and daily export. openssl rand -hex 32. |
DATABASE_URL |
production | Postgres for subscribers (a GridTilt-only Neon database). Without it, production answers signups "unavailable"; development uses an ignored local file. See docs/runbooks/subscriber-storage.md. |
RESEND_API_KEY |
to send | Newsletter sending through Resend (one email per call, idempotency keys). Without it, signups are still stored and sending answers with the missing configuration. |
NEWSLETTER_FROM, NEWSLETTER_POSTAL_ADDRESS, NEWSLETTER_PRIVACY_URL |
to send | Sender on a Resend-verified domain, the approved mailing address, and the published privacy notice. Sending is blocked until all are set. NEWSLETTER_REPLY_TO and NEWSLETTER_CONTACT_EMAIL are optional. See docs/runbooks/newsletter-sending.md. |
RESEND_WEBHOOK_SECRET |
to send | Verifies Resend's delivery, bounce and complaint webhooks at /api/webhooks/resend. |
EIA_API_KEY |
no | Free key from eia.gov/opendata. Enables live US48 hourly demand and the residential electricity prices on the state pages; without it they say prices are unavailable and link EIA's own table. |
NEWSDATA_API_KEY |
no | Optional newsdata.io key. The 8 RSS feeds work without it. |
CARTO_API |
no | Free Carto basemap key (5M tiles/month). Without it the maps still render, but every tile is stamped "API KEY REQUIRED". Public by nature - restrict it by domain. |
X_API_* |
no | Four X credentials for the weekday auto-poster; it dry-runs and logs locally without them. |
X_POSTING_ENABLED |
no | Kill switch for the auto-poster; defaults off (dry-run). Each weekday post states one dated basis or skips with its reason in the social log. |
DISABLE_DATACENTER_INGESTER |
no | Set to 1 to disable the 6-hour RSS datacenter ingester. |
client/src/
pages/ route-level components, one file per tool
components/ shared UI; components/ui is the modified shadcn fork
data/ static client config (supply chain graph, catalysts, RTOs)
lib/ query client, design tokens, pure helpers (tested)
server/
routes.ts all HTTP endpoints
*.ts pure modules the routes call (indices, clusters, deals,
gpu-index, gpu-live, gpu-economics, brief, physical, ...)
data/ JSON persistence: hand-curated and machine-written files
__tests__/ node:test suites
content/blog/ articles.json
script/build.ts production build (vite client + esbuild server)
scripts/ backtest and maintenance scripts
There is no shared/ directory, no ORM, and no external datastore. API payload shapes are declared where they are consumed.
Replit autoscale. A push to GitHub does not deploy, and autoscale does not auto-pull: merge, then redeploy manually in Replit (Deployments, then Redeploy). After a redeploy, sanity-check /api/stack, /api/kpis, and /api/clusters/metrics.
Machine-written JSON (subscribers, price history, ingested facilities) lives on the instance disk, which is ephemeral across redeploys.
MIT, see LICENSE.
Built by Jack Schwartz.
