fix(gold-intelligence): read correct Redis key and data shape (#3013)

* fix(gold-intelligence): read correct Redis key and data shape

Handler read 'market:commodity-quotes:v1' (doesn't exist). Actual seeded
key is 'market:commodities-bootstrap:v1'. Also expected raw array but
seeder writes { quotes: [...] }. Both bugs caused permanent "Gold data
unavailable" in the panel.

* fix(market): return unavailable when GC=F quote is missing from commodity snapshot
This commit is contained in:
Elie Habib
2026-04-12 19:41:27 +04:00
committed by GitHub
parent 89fe297fa2
commit ca8f53959b
2 changed files with 28 additions and 4 deletions

View File

@@ -7,7 +7,7 @@ import type {
} from '../../../../src/generated/server/worldmonitor/market/v1/service_server';
import { getCachedJson } from '../../../_shared/redis';
const COMMODITY_KEY = 'market:commodity-quotes:v1';
const COMMODITY_KEY = 'market:commodities-bootstrap:v1';
const COT_KEY = 'market:cot:v1';
interface RawQuote {
@@ -44,18 +44,22 @@ export async function getGoldIntelligence(
_req: GetGoldIntelligenceRequest,
): Promise<GetGoldIntelligenceResponse> {
try {
const [rawQuotes, rawCot] = await Promise.all([
getCachedJson(COMMODITY_KEY, true) as Promise<RawQuote[] | null>,
const [rawPayload, rawCot] = await Promise.all([
getCachedJson(COMMODITY_KEY, true) as Promise<{ quotes?: RawQuote[] } | null>,
getCachedJson(COT_KEY, true) as Promise<{ instruments?: RawCotInstrument[]; reportDate?: string } | null>,
]);
if (!rawQuotes || !Array.isArray(rawQuotes)) {
const rawQuotes = rawPayload?.quotes;
if (!rawQuotes || !Array.isArray(rawQuotes) || rawQuotes.length === 0) {
return { goldPrice: 0, goldChangePct: 0, goldSparkline: [], silverPrice: 0, platinumPrice: 0, palladiumPrice: 0, crossCurrencyPrices: [], updatedAt: '', unavailable: true };
}
const quoteMap = new Map(rawQuotes.map(q => [q.symbol, q]));
const gold = quoteMap.get('GC=F');
if (!gold) {
return { goldPrice: 0, goldChangePct: 0, goldSparkline: [], silverPrice: 0, platinumPrice: 0, palladiumPrice: 0, crossCurrencyPrices: [], updatedAt: '', unavailable: true };
}
const silver = quoteMap.get('SI=F');
const platinum = quoteMap.get('PL=F');
const palladium = quoteMap.get('PA=F');

View File

@@ -97,6 +97,26 @@ describe('Gold Intelligence', () => {
assert.ok(Math.abs(premium - ((3200 - 950) / 950) * 100) < 0.01);
});
it('returns unavailable when GC=F is missing from commodity snapshot', () => {
const quotes = [
{ symbol: 'SI=F', price: 35 },
{ symbol: 'PL=F', price: 950 },
{ symbol: 'PA=F', price: 1020 },
{ symbol: 'EURUSD=X', price: 1.08 },
];
const quoteMap = new Map(quotes.map(q => [q.symbol, q]));
const gold = quoteMap.get('GC=F');
assert.strictEqual(gold, undefined);
const goldPrice = gold?.price ?? 0;
assert.strictEqual(goldPrice, 0);
const ratio = computeGoldSilverRatio(goldPrice, 35);
assert.strictEqual(ratio, null);
const cross = computeCrossCurrency(goldPrice, quotes);
assert.strictEqual(cross.length, 0);
});
it('partial availability: price works when cot is null, and vice versa', () => {
const goldPrice = 3200;
const silverPrice = 35;