docs/plans/2026-03-29-001-feat-gold-layer-commodity-variant-plan.md
Enhance WorldMonitor's commodity variant with learnings from the Yazan-Abuawwad/gold-monitor fork. The fork was a fully independent Angular+Spring Boot app (not based on our codebase), but its gold layer design surfaces four concrete gaps in our current commodity variant:
MINING_SITES geo datagold-silver panelImportantly, much of what the fork built already exists in our stack:
GC=F gold futures seeded every 5 min via seedCommodityQuotes() in ais-relay.cjsgold-silver news panel with Kitco, GoldSeek, SilverSeek, WGC, Google NewsMINING_SITES layer in commodity-geo.ts already has operator + annualOutput fields and gold mine records (Nevada Gold Mines, Lihir, Pueblo Viejo, Cortez, Peñasquito)COMMODITY_MARKET_SYMBOLS (NEM, GOLD, AEM, WPM, RGLD, FNV)This plan is additive enrichment, not new infrastructure.
The commodity variant (commodity.worldmonitor.app) serves users focused on gold, metals, and energy markets. Compared to the fork's gold layer design, three user-facing gaps exist:
['markets', 'economic', 'crypto', 'finance'] — all of which are finance-variant categories. Commodity users get a stock market brief, not a gold/commodities brief.GC=F). Professional users track XAU/EUR, XAU/CNY (yuan debasement hedge), XAU/TRY (emerging market inflation). No cross-currency view exists.Four phases, ordered by effort and risk:
commodity-geo.tsfeeds.ts gold-silver block + add XAUUSD=X spot to commodities.jsonCommoditiesPanel showing XAU in 10 currenciesAll changes are additive. No new Redis keys, no new seeder scripts, no new API routes, no new bootstrap registrations for Phases 1-3. Phase 4 requires FX symbols added to shared/commodities.json (auto-seeded by existing seedCommodityQuotes()).
Gold Standard compliance: The commodity quote seeder already runs on 5-min interval with 2h TTL. Any FX symbols added to shared/commodities.json are automatically picked up by loadSharedConfig('commodities.json') in both ais-relay.cjs and scripts/seed-commodity-quotes.mjs — no seeder code changes needed.
File: src/config/commodity-geo.ts
Add these 10 mines to MINING_SITES[], following the existing pattern. All are mineral: 'gold', status: 'producing' unless noted:
| Mine | Country | Operator | Lat/Lng | Annual Output |
|---|---|---|---|---|
| Muruntau | Uzbekistan | Navoi Mining & Metallurgy | 41.56, 64.58 | ~2.8 Moz/yr |
| Kibali | DRC | Barrick Gold / AngloGold JV | 3.07, 29.76 | ~800 Koz/yr |
| Sukhoi Log | Russia | Polyus (development) | 58.29, 115.22 | ~2.3 Moz/yr (projected) — status: 'development' |
| Ahafo | Ghana | Newmont | 7.06, -2.34 | ~800 Koz/yr |
| Loulo-Gounkoto | Mali | Barrick Gold | 14.85, -11.41 | ~700 Koz/yr |
| South Deep | South Africa | Gold Fields | -26.52, 27.54 | ~300 Koz/yr |
| Kumtor | Kyrgyzstan | Centerra Gold | 41.81, 78.19 | ~500 Koz/yr |
| Yanacocha | Peru | Newmont / Buenaventura | -6.94, -78.56 | ~400 Koz/yr |
| Cerro Negro | Argentina | Newmont | -46.75, -67.50 | ~300 Koz/yr |
| Tropicana | Australia | AngloGold Ashanti / Regis | -29.30, 124.80 | ~500 Koz/yr |
These mirror the exact mines in the fork's hardcoded GOLD_MINES[] array that were absent from our data.
No type changes needed: MineSite already has operator: string, annualOutput?: string, and mineral: MineralType includes 'gold'.
Acceptance criteria for Phase 1:
◆ markers on the commodity map mining layeroperator and annualOutputFile 2a: src/config/feeds.ts — gold-silver block (line ~1107)
Append two direct feeds that the fork had and we don't:
{ name: 'Gold Silver Worlds', url: rss('https://goldsilverworlds.com/feed/') },
{ name: 'FX Empire Gold', url: rss('https://www.fxempire.com/api/v1/en/markets/commodity/Gold/news/feed') },
Both are direct XML feeds (no Google News proxy), giving us higher-quality gold-specific content alongside the existing Kitco + GoldSeek feeds.
File 2b: shared/commodities.json
Add XAUUSD=X (London spot gold, Yahoo Finance):
{ "symbol": "XAUUSD=X", "name": "Gold Spot", "display": "XAU SPOT" }
This is auto-picked up by ais-relay.cjs COMMODITY_SYMBOLS + YAHOO_ONLY set check (it ends in =X, similar treatment to =F futures). Verify: confirm YAHOO_ONLY_SYMBOLS in server/worldmonitor/market/v1/_shared.ts includes XAUUSD=X or add it.
Value: frontend can now show futures vs spot basis spread in CommoditiesPanel (contango/backwardation signal for gold market sentiment).
Acceptance criteria for Phase 2:
gold-silver panel shows articles from Gold Silver Worlds + FX Empire GoldXAUUSD=X appears in commodity bootstrap dataXAUUSD=X in YAHOO_ONLY_SYMBOLS in both _shared.ts and ais-relay.cjsnpm run test:data passes (no new bootstrap key — XAUUSD=X goes through existing market:commodities-bootstrap:v1)File 3a: src/components/InsightsPanel.ts — line 553
Current:
${SITE_VARIANT === 'tech' ? '🚀 TECH BRIEF' : '🌍 WORLD BRIEF'}
Update:
${SITE_VARIANT === 'tech' ? '🚀 TECH BRIEF' : SITE_VARIANT === 'commodity' ? '⛏️ COMMODITY BRIEF' : '🌍 WORLD BRIEF'}
File 3b: src/services/daily-market-brief.ts — BRIEF_NEWS_CATEGORIES (line 97)
Make variant-aware:
const BRIEF_NEWS_CATEGORIES = SITE_VARIANT === 'commodity'
? ['commodity-news', 'gold-silver', 'mining-news', 'energy', 'critical-minerals']
: ['markets', 'economic', 'crypto', 'finance'];
This routes the headline pool for AI summarization to the commodity feed categories (which are already populated in newsByCategory context) instead of finance-variant categories.
File 3c: src/components/InsightsPanel.ts — geoContext block (~line 398-401)
Add commodity context injection alongside the existing full variant theater context:
let geoContext = SITE_VARIANT === 'full'
? (focalSummary.aiContext || signalSummary.aiContext) + theaterContext
: SITE_VARIANT === 'commodity'
? buildCommodityContext(options) // new helper
: '';
New helper buildCommodityContext(options) (in daily-market-brief.ts):
GC=F quote from commodity data → format as "Gold: $X,XXX (+1.2% today)"commodity-news categorygold-silver / mining-news feedThis gives the LLM commodity-specific framing ("you are analyzing gold and commodities markets") instead of generic geopolitical framing.
Acceptance criteria for Phase 3:
New file: src/components/GoldCurrencyWidget.ts
A sub-component (not a full Panel) embedded in CommoditiesPanel when SITE_VARIANT === 'commodity'.
Shows XAU (gold) priced in 10 currencies with live calculation:
| Currency | Symbol to add to commodities.json |
|---|---|
| USD | — (use GC=F directly) |
| EUR | EURUSD=X |
| GBP | GBPUSD=X |
| JPY | JPYUSD=X |
| CNY | CNYUSD=X |
| INR | INRUSD=X |
| AUD | AUDUSD=X |
| CHF | CHFUSD=X |
| CAD | CADUSD=X |
| TRY | TRYUSD=X |
Seeder impact: Adding 9 FX pairs to shared/commodities.json adds 9 Yahoo calls per 5-min cycle. Current load is ~23 symbols → ~27ms total with 150ms gaps. Adding 9 more brings total to 32 symbols, adding ~1.35s per cycle. Within acceptable bounds per yahooGate() rate limiter. All =X forex symbols are Yahoo-only — add to YAHOO_ONLY_SYMBOLS in _shared.ts and ais-relay.cjs.
Widget rendering:
gcFPrice / fxRateToUSD) | 24h % change | 10-char sparklinemarket:commodities-bootstrap:v1 which will now include both GC=F and the FX pairsxauInCurrency = gcF.regularMarketPrice / fxPair.regularMarketPriceCommoditiesPanel below the main metals gridIntegration point: src/components/CommoditiesPanel.ts — add if (SITE_VARIANT === 'commodity') this.renderGoldCurrencyWidget() in the panel's content update method.
Acceptance criteria for Phase 4:
market:commodities-bootstrap:v1YAHOO_ONLY_SYMBOLS (both files)cache-keys.ts / bootstrap.js / health.js / gateway.ts — no changes needed since these symbols route through the existing commodityQuotes bootstrap keynpm run test:data passesPromise.all — sequential with 150ms delays per gold standard.XAUUSD=X) go through the existing market:commodities-bootstrap:v1 key. No 4-file checklist changes required.SITE_VARIANT === 'commodity' guarded. The finance, tech, and full variants are unaffected.MINING_SITES entries increases DeckGLMap scatter plot point count slightly. No perf concern — ScatterplotLayer handles thousands of points.gold-silver panel includes Gold Silver Worlds + FX Empire Gold articlesXAUUSD=X spot price appears in commodity panels alongside GC=F futuresnpm run typecheck + npm run typecheck:api)npm run test:data passes (bootstrap key parity)npm run lint)=F or =X symbols added to YAHOO_ONLY_SYMBOLS in both server/worldmonitor/market/v1/_shared.ts and ais-relay.cjsseedCommodityQuotes())MARKET_SEED_TTL = 7200 (2h) — no change| Risk | Mitigation |
|---|---|
FX Yahoo symbols (EURUSD=X) behave differently than futures | Test each symbol with fetchYahooChartDirect manually before adding; =X symbols are confirmed YAHOO_ONLY in existing memory |
Sukhoi Log is status: 'development' — mine not yet in production | Correct in the data; tooltip should note "Under Development (est. 2.3 Moz/yr)" |
| Gold Silver Worlds / FX Empire RSS feeds may have CORS/SSL issues | rss() helper in feeds.ts wraps through Google News proxy by default; use direct URL only if it's known-stable XML |
| Phase 4 commodity brief may get stale cache from previous 'WORLD BRIEF' content | InsightsPanel.BRIEF_CACHE_KEY should be variant-specific, or clear on variant change. Check if it's already variant-scoped. |
gold-silver panel goes from 7 → 9 sourcessrc/config/variants/commodity.tssrc/config/commodity-geo.ts:27 (MineSite interface), line 75 (MINING_SITES)src/config/feeds.ts:1107 (gold-silver block)shared/commodities.jsonscripts/ais-relay.cjs seedCommodityQuotes() ~line 1413server/worldmonitor/market/v1/_shared.ts YAHOO_ONLY_SYMBOLSsrc/services/daily-market-brief.ts:97 (BRIEF_NEWS_CATEGORIES)src/components/InsightsPanel.ts:553src/config/map-layer-definitions.tsgithub.com/Yazan-Abuawwad/gold-monitor — fully independent Angular/Spring Boot app. Key learnings: 20 gold mine list (10 missing from ours), 7 gold RSS feeds (2 missing), XAU 10-currency widget pattern, gold AI brief prompt structure covering price sentiment + geopolitical supply risks + mining output.feedback_seeder_gold_standard.md — TTL ≥ 3x interval, upstashExpire on both failure paths, 20min retry, inFlight guardworldmonitor-bootstrap-registration.md