From 4053a5119a0f969c5e785fbe215f3c5fc7157da5 Mon Sep 17 00:00:00 2001 From: Elie Habib Date: Sun, 5 Apr 2026 13:29:14 +0400 Subject: [PATCH] feat(energy): surface coal and TTF gas spot prices in analyst context (#2715) * feat(intelligence): surface coal and TTF gas spot prices in analyst context - Add TTF=F (TTF Natural Gas) to scripts/shared/commodities.json after NG=F - Add coalSpotPrice and gasSpotTtf fields to AnalystContext interface - Add extractCommodityQuote and buildSpotCommodityLine helpers in chat-analyst-context.ts - Derive coal (MTF=F) and TTF (TTF=F) from already-fetched commodities bootstrap, no extra Redis calls - Gate on SPOT_ENERGY_DOMAINS (economic, geo, all) - Register both fields in SOURCE_LABELS with CoalSpot/GasTTF labels - Add coalSpotPrice and gasSpotTtf to DOMAIN_SECTIONS for geo and economic in chat-analyst-prompt.ts - Push Coal Spot Price and TTF Gas Price sections into context when present * fix(energy): use correct unit for Newcastle coal (/t not /MWh) buildSpotCommodityLine hardcoded /MWh for all commodities. Coal is priced per metric ton. Added denominator parameter with /MWh default, pass /t for Newcastle coal. --- scripts/shared/commodities.json | 1 + .../intelligence/v1/chat-analyst-context.ts | 30 +++++++++++++++++++ .../intelligence/v1/chat-analyst-prompt.ts | 8 +++-- shared/commodities.json | 1 + 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/scripts/shared/commodities.json b/scripts/shared/commodities.json index ce681bac6..58cb9b436 100644 --- a/scripts/shared/commodities.json +++ b/scripts/shared/commodities.json @@ -10,6 +10,7 @@ { "symbol": "CL=F", "name": "Crude Oil WTI", "display": "OIL" }, { "symbol": "BZ=F", "name": "Brent Crude", "display": "BRENT" }, { "symbol": "NG=F", "name": "Natural Gas", "display": "NATGAS" }, + { "symbol": "TTF=F", "name": "TTF Natural Gas", "display": "TTF GAS" }, { "symbol": "RB=F", "name": "Gasoline RBOB", "display": "GASOLINE" }, { "symbol": "HO=F", "name": "Heating Oil", "display": "HEATING OIL" }, { "symbol": "URA", "name": "Uranium (Global X)", "display": "URANIUM" }, diff --git a/server/worldmonitor/intelligence/v1/chat-analyst-context.ts b/server/worldmonitor/intelligence/v1/chat-analyst-context.ts index 7fc7c6598..16fe1fed7 100644 --- a/server/worldmonitor/intelligence/v1/chat-analyst-context.ts +++ b/server/worldmonitor/intelligence/v1/chat-analyst-context.ts @@ -28,6 +28,8 @@ export interface AnalystContext { liveHeadlines: string; relevantArticles: string; energyExposure: string; + coalSpotPrice: string; + gasSpotTtf: string; activeSources: string[]; degraded: boolean; } @@ -230,6 +232,27 @@ function buildEnergyExposure(data: unknown): string { return lines.join('\n'); } +function extractCommodityQuote(commodities: unknown, symbol: string): Record | null { + if (!commodities || typeof commodities !== 'object') return null; + const d = commodities as Record; + const quotes = Array.isArray(d.quotes) ? d.quotes : []; + const q = quotes.find((q: unknown) => { + const quote = q as Record; + return safeStr(quote.symbol) === symbol; + }); + return q ? (q as Record) : null; +} + +function buildSpotCommodityLine(commodities: unknown, symbol: string, label: string, unit: string, denominator = '/MWh'): string { + const q = extractCommodityQuote(commodities, symbol); + if (!q) return ''; + const price = safeNum(q.price); + const change = safeNum(q.change ?? q.changePercent); + if (!price) return ''; + const sign = change >= 0 ? '+' : ''; + return `${label}: ${unit}${price.toFixed(2)}${denominator} (${sign}${change.toFixed(2)}% today)`; +} + function buildCountryBrief(data: unknown): string { if (!data || typeof data !== 'object') return ''; const d = data as Record; @@ -417,6 +440,8 @@ const SOURCE_LABELS: Array<[keyof Omit = { /** Context fields included per domain. 'all' includes everything. */ const DOMAIN_SECTIONS: Record> = { market: new Set(['relevantArticles', 'marketData', 'macroSignals', 'marketImplications', 'predictionMarkets', 'forecasts', 'liveHeadlines']), - geo: new Set(['relevantArticles', 'worldBrief', 'riskScores', 'forecasts', 'predictionMarkets', 'countryBrief', 'energyExposure', 'liveHeadlines']), + geo: new Set(['relevantArticles', 'worldBrief', 'riskScores', 'forecasts', 'predictionMarkets', 'countryBrief', 'energyExposure', 'coalSpotPrice', 'gasSpotTtf', 'liveHeadlines']), military: new Set(['relevantArticles', 'worldBrief', 'riskScores', 'forecasts', 'countryBrief', 'liveHeadlines']), - economic: new Set(['relevantArticles', 'marketData', 'macroSignals', 'marketImplications', 'riskScores', 'energyExposure', 'liveHeadlines']), + economic: new Set(['relevantArticles', 'marketData', 'macroSignals', 'marketImplications', 'riskScores', 'energyExposure', 'coalSpotPrice', 'gasSpotTtf', 'liveHeadlines']), }; export function buildAnalystSystemPrompt(ctx: AnalystContext, domainFocus?: string): string { @@ -44,6 +44,10 @@ export function buildAnalystSystemPrompt(ctx: AnalystContext, domainFocus?: stri contextSections.push(`## ${ctx.macroSignals}`); if (ctx.energyExposure && include('energyExposure')) contextSections.push(`## Energy Exposure\n${ctx.energyExposure}`); + if (ctx.coalSpotPrice && include('coalSpotPrice')) + contextSections.push(`## Coal Spot Price\n${ctx.coalSpotPrice}`); + if (ctx.gasSpotTtf && include('gasSpotTtf')) + contextSections.push(`## TTF Gas Price\n${ctx.gasSpotTtf}`); if (ctx.predictionMarkets && include('predictionMarkets')) contextSections.push(`## ${ctx.predictionMarkets}`); if (ctx.countryBrief && include('countryBrief')) diff --git a/shared/commodities.json b/shared/commodities.json index ce681bac6..58cb9b436 100644 --- a/shared/commodities.json +++ b/shared/commodities.json @@ -10,6 +10,7 @@ { "symbol": "CL=F", "name": "Crude Oil WTI", "display": "OIL" }, { "symbol": "BZ=F", "name": "Brent Crude", "display": "BRENT" }, { "symbol": "NG=F", "name": "Natural Gas", "display": "NATGAS" }, + { "symbol": "TTF=F", "name": "TTF Natural Gas", "display": "TTF GAS" }, { "symbol": "RB=F", "name": "Gasoline RBOB", "display": "GASOLINE" }, { "symbol": "HO=F", "name": "Heating Oil", "display": "HEATING OIL" }, { "symbol": "URA", "name": "Uranium (Global X)", "display": "URANIUM" },