{"record":{"id":"e9cdec42007f4689","repo":"koala73/worldmonitor","slug":"indexkey","errorCode":null,"errorMessage":"${indexKey}","messagePattern":"\\$\\{indexKey\\}","errorType":"exception","errorClass":"SeedUnavailableError","httpStatus":503,"severity":"error","filePath":"server/worldmonitor/market/v1/premium-stock-store.ts","lineNumber":155,"sourceCode":"  const ids = await zrevrange(indexKey, ANALYSIS_HISTORY_LIMIT + 4);\n  await trimIndexTail(indexKey, ids, ANALYSIS_HISTORY_LIMIT);\n}\n\nexport async function getStoredStockAnalysisHistory(\n  symbols: string[],\n  includeNews: boolean,\n  limitPerSymbol = ANALYSIS_HISTORY_LIMIT,\n): Promise<AnalysisHistoryRecord> {\n  const normalized = normalizeSymbolList(symbols);\n  const clampedLimit = Math.max(1, Math.min(ANALYSIS_HISTORY_LIMIT, limitPerSymbol));\n  const out: AnalysisHistoryRecord = {};\n\n  await Promise.all(normalized.map(async (symbol) => {\n    const indexKey = analysisHistoryIndexKey(symbol, includeNews);\n    const index = await runRedisPipeline([['ZREVRANGE', indexKey, 0, clampedLimit - 1]]);\n    const ids = index[0]?.result;\n    if (index.length !== 1 || index[0]?.error || !Array.isArray(ids)\n      || !ids.every(id => typeof id === 'string')) throw new SeedUnavailableError(indexKey);\n    if (ids.length === 0) {\n      out[symbol] = [];\n      return;\n    }\n    const records = await runRedisPipeline(ids.map(id => ['GET', analysisItemKey(String(id))]));\n    if (records.length !== ids.length) throw new SeedUnavailableError(indexKey);\n    out[symbol] = records.flatMap(record => {\n      if (record.error) throw new SeedUnavailableError(indexKey);\n      if (record.result === null) return [];\n      if (typeof record.result !== 'string') throw new SeedUnavailableError(indexKey);\n      let snapshot: AnalyzeStockResponse;\n      try {\n        snapshot = JSON.parse(record.result);\n      } catch {\n        throw new SeedUnavailableError(indexKey);\n      }\n      if (!snapshot || typeof snapshot !== 'object') throw new SeedUnavailableError(indexKey);\n      return snapshot.available ? [snapshot] : [];","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/worldmonitor/market/v1/premium-stock-store.ts#L137-L173","documentation":"SeedUnavailableError is thrown when the Redis seed data backing the stock analysis history index is missing or structurally invalid. Here the ZREVRANGE read of the per-symbol analysis history index (analysisHistoryIndexKey) either errored, returned a malformed payload, or the pipeline produced an unexpected number of results. The store treats any seed corruption/absence as 'seed unavailable' so callers can distinguish missing seed data from an empty result.","triggerScenarios":"Calling getStoredStockAnalysisHistory (via the history RPC) for a symbol whose ZSET index key does not exist in Redis, or when the Redis pipeline returns an error entry or non-string member IDs.","commonSituations":"Redis was flushed or migrated without re-running the seeder; the seeder for this symbol never ran; Upstash connection errors surface as pipeline error entries; a key naming/version change made old indexes invisible.","solutions":["Run the premium stock analysis seeder to populate the ZREVRANGE index for the requested symbols.","Verify Redis/Upstash connectivity and credentials (loadEnvFile) — pipeline error entries usually mean connection or auth problems.","Check that the seeder and reader use the same key prefix/version (analysisHistoryIndexKey) and that includeNews matches what the seeder wrote.","ZSCAN the index key directly to confirm it exists and contains string member IDs."],"exampleFix":"// before: reader fails when the index was seeded with a different includeNews flag\nconst indexKey = analysisHistoryIndexKey(symbol, includeNews);\n// after: probe both variants or normalize the flag the seeder used\nconst indexKey = analysisHistoryIndexKey(symbol, includeNews);\nlet index = await runRedisPipeline([['ZREVRANGE', indexKey, 0, clampedLimit - 1]]);\nif (index[0]?.error || !Array.isArray(index[0]?.result)) {\n  index = await runRedisPipeline([['ZREVRANGE', analysisHistoryIndexKey(symbol, !includeNews), 0, clampedLimit - 1]]);\n}","handlingStrategy":"try-catch","validationCode":"// no client-side pre-check; verify server-side seed health before calling\nconst ok = await redis.exists(analysisHistoryIndexKey(symbol, includeNews));","typeGuard":null,"tryCatchPattern":"try {\n  const history = await getStoredStockAnalysisHistory(symbols, limit, includeNews);\n} catch (e) {\n  if (e instanceof SeedUnavailableError) return fallbackHistory(symbols);\n  throw e;\n}","preventionTips":["Seed before serving: gate the history endpoint on seed health checks.","Keep seeder and reader key-building functions in one shared module.","Monitor ZCARD on history indexes and alert when zero."],"tags":["redis","seed-data","cache-miss"],"backgroundTag":"empty-result-set","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}