mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
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.
This commit is contained in:
@@ -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" },
|
||||
|
||||
@@ -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<string, unknown> | null {
|
||||
if (!commodities || typeof commodities !== 'object') return null;
|
||||
const d = commodities as Record<string, unknown>;
|
||||
const quotes = Array.isArray(d.quotes) ? d.quotes : [];
|
||||
const q = quotes.find((q: unknown) => {
|
||||
const quote = q as Record<string, unknown>;
|
||||
return safeStr(quote.symbol) === symbol;
|
||||
});
|
||||
return q ? (q as Record<string, unknown>) : 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<string, unknown>;
|
||||
@@ -417,6 +440,8 @@ const SOURCE_LABELS: Array<[keyof Omit<AnalystContext, 'timestamp' | 'degraded'
|
||||
['forecasts', 'Forecasts'],
|
||||
['marketData', 'Markets'],
|
||||
['energyExposure', 'EnergyMix'],
|
||||
['coalSpotPrice', 'CoalSpot'],
|
||||
['gasSpotTtf', 'GasTTF'],
|
||||
['macroSignals', 'Macro'],
|
||||
['predictionMarkets', 'Prediction'],
|
||||
['countryBrief', 'Country'],
|
||||
@@ -452,6 +477,9 @@ export async function assembleAnalystContext(
|
||||
const ENERGY_EXPOSURE_DOMAINS = new Set(['geo', 'economic', 'all']);
|
||||
const needsEnergyExposure = ENERGY_EXPOSURE_DOMAINS.has(resolvedDomain);
|
||||
|
||||
const SPOT_ENERGY_DOMAINS = new Set(['economic', 'geo', 'all']);
|
||||
const needsSpotEnergy = SPOT_ENERGY_DOMAINS.has(resolvedDomain);
|
||||
|
||||
const [
|
||||
insightsResult,
|
||||
riskResult,
|
||||
@@ -503,6 +531,8 @@ export async function assembleAnalystContext(
|
||||
marketData: buildMarketData(get(stocksResult), get(commoditiesResult)),
|
||||
macroSignals: buildMacroSignals(get(macroResult)),
|
||||
energyExposure: buildEnergyExposure(get(energyExposureResult)),
|
||||
coalSpotPrice: needsSpotEnergy ? buildSpotCommodityLine(get(commoditiesResult), 'MTF=F', 'Newcastle coal', '$', '/t') : '',
|
||||
gasSpotTtf: needsSpotEnergy ? buildSpotCommodityLine(get(commoditiesResult), 'TTF=F', 'TTF gas', '€') : '',
|
||||
predictionMarkets: buildPredictionMarkets(get(predResult)),
|
||||
countryBrief: buildCountryBrief(get(countryResult)),
|
||||
liveHeadlines: getStr(headlinesResult),
|
||||
|
||||
@@ -10,9 +10,9 @@ const DOMAIN_EMPHASIS: Record<string, string> = {
|
||||
/** Context fields included per domain. 'all' includes everything. */
|
||||
const DOMAIN_SECTIONS: Record<string, Set<string>> = {
|
||||
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'))
|
||||
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user