(() => { const MODEL_CONFIG = window.AIFORECAST_MODEL_CONFIG || null; const ANALYSIS_CACHE_VERSION = window.AIFORECAST_ANALYSIS_CACHE_VERSION || 'chronos2-v1'; window.AIFORECAST_ANALYSIS_CACHE_VERSION = ANALYSIS_CACHE_VERSION; const AI_CONTEXT_POLICY = window.AIFORECAST_FORECAST_CONTEXT_CONFIG || window.AI_CONTEXT_POLICY || Object.freeze({ default: 384, allowed: [128, 256, 384, 512], scope: "workspace", }); function normalizeModels(selection = null) { if (MODEL_CONFIG && typeof MODEL_CONFIG.normalizeSelection === 'function') { return MODEL_CONFIG.normalizeSelection(selection); } return { kronos: Boolean(selection?.kronos), timesfm: Boolean(selection?.timesfm), chronos: Boolean(selection?.chronos), }; } function getModelSignature(selection = null) { if (MODEL_CONFIG && typeof MODEL_CONFIG.signature === 'function') { return MODEL_CONFIG.signature(selection); } const normalized = normalizeModels(selection); return Object.entries(normalized) .filter(([, enabled]) => enabled) .map(([modelKey]) => modelKey) .join('+') || 'none'; } function normalizeContextLength(contextLength, fallback = AI_CONTEXT_POLICY.default || 384) { const numericValue = Number(contextLength); if (Number.isFinite(numericValue) && AI_CONTEXT_POLICY.allowed.includes(numericValue)) { return numericValue; } return fallback; } function normalizeHorizon(value) { const numericValue = Number(value); return Number.isFinite(numericValue) && numericValue > 0 ? numericValue : null; } function buildAnalysisRequestContext(requestMeta = {}, pane = null, payload = null) { const workspaceContextLength = typeof window.getWorkspaceForecastContextLength === 'function' ? window.getWorkspaceForecastContextLength() : (window.Workspace?.forecastSettings?.contextLength ?? AI_CONTEXT_POLICY.default); const models = normalizeModels( requestMeta.models || payload?.model_selection?.requested || pane?.aiModels || null, ); return { symbol: requestMeta.symbol ?? payload?.symbol ?? pane?.symbol ?? null, interval: requestMeta.interval ?? payload?.interval ?? pane?.interval ?? null, horizon: normalizeHorizon(requestMeta.horizon ?? payload?.horizon ?? pane?.horizon), models, modelSignature: requestMeta.modelSignature ?? getModelSignature(models), cacheVersion: ANALYSIS_CACHE_VERSION, contextLength: normalizeContextLength( requestMeta.contextLength ?? payload?.model_selection?.shared_context_length ?? workspaceContextLength, AI_CONTEXT_POLICY.default || 384, ), }; } function contextsMatch(left, right, options = {}) { if (!left || !right) { return false; } const requireHorizon = options.requireHorizon !== false; return ( left.symbol === right.symbol && left.interval === right.interval && left.modelSignature === right.modelSignature && left.cacheVersion === right.cacheVersion && left.contextLength === right.contextLength && ( !requireHorizon || left.horizon === right.horizon ) ); } function buildPaneAnalysisCacheKey(requestMeta = {}, pane = null, payload = null) { const context = buildAnalysisRequestContext(requestMeta, pane, payload); return [ context.symbol || 'unknown', context.interval || 'unknown', `h=${context.horizon ?? 'na'}`, `m=${context.modelSignature}`, `ctx=${context.contextLength}`, `rv=${ANALYSIS_CACHE_VERSION}`, ].join('|'); } function createPaneAnalysisCacheEntry(payload, requestMeta = {}, pane = null) { return { payload, ...buildAnalysisRequestContext(requestMeta, pane, payload), }; } function isPayloadCommitReady(payload, pane = null) { if (!payload?.analysis) { return false; } if (pane?.lastAnalysis?.payload === payload) { if (pane.lastAnalysis.commitReady === true) { return true; } if (pane.lastAnalysis.complete === true) { return true; } } const aggregation = payload?.analysis?.presentation?.aggregation || {}; const aiModels = payload?.analysis?.ai_models || {}; if (aggregation.commit_ready === true || aiModels.commit_ready === true) { return true; } if (aggregation.complete === true || aiModels.complete === true) { return true; } return false; } function matchesPanePayloadCurrentContext( pane, payload, options = {}, ) { if (!pane || !payload?.analysis) { return false; } const currentContext = buildAnalysisRequestContext({}, pane); const requireComplete = options.requireComplete === true; const allowTechnicalFallback = options.allowTechnicalFallback === true; if (pane.lastAnalysis?.payload === payload) { const storedContext = buildAnalysisRequestContext(pane.lastAnalysis, pane, payload); return ( contextsMatch(storedContext, currentContext) && (!requireComplete || isPayloadCommitReady(payload, pane)) ); } if (pane.lastTechnicalAnalysis?.payload === payload) { return ( allowTechnicalFallback && pane.lastTechnicalAnalysis.symbol === currentContext.symbol && pane.lastTechnicalAnalysis.interval === currentContext.interval ); } const payloadContext = buildAnalysisRequestContext({}, pane, payload); return ( contextsMatch(payloadContext, currentContext) && (!requireComplete || isPayloadCommitReady(payload, pane)) ); } function resolvePaneDisplayPayload(pane, preferredPayload = null) { const currentContext = buildAnalysisRequestContext({}, pane); const displaySnapshotPayload = pane?.aiSession?.displaySnapshot?.payload || null; if (matchesPanePayloadCurrentContext(pane, displaySnapshotPayload, { requireComplete: true })) { return displaySnapshotPayload; } if (matchesPanePayloadCurrentContext(pane, preferredPayload)) { return preferredPayload; } if (matchesPanePayloadCurrentContext(pane, pane?.lastAnalysis?.payload)) { return pane.lastAnalysis.payload; } const cachedPayload = getCachedPaneAnalysisPayload(pane, currentContext); if (matchesPanePayloadCurrentContext(pane, cachedPayload, { requireComplete: true })) { return cachedPayload; } if (matchesPanePayloadCurrentContext(pane, pane?.lastTechnicalAnalysis?.payload, { allowTechnicalFallback: true })) { return pane.lastTechnicalAnalysis.payload; } return null; } function getCachedPaneAnalysisPayload(pane, requestMeta = {}) { const cacheKey = buildPaneAnalysisCacheKey(requestMeta, pane); const cacheEntry = pane?.analysisPayloadCache?.[cacheKey]; if (!cacheEntry) { return null; } const entry = cacheEntry?.payload ? cacheEntry : createPaneAnalysisCacheEntry(cacheEntry, requestMeta, pane); const requestedContext = buildAnalysisRequestContext(requestMeta, pane); const cachedContext = buildAnalysisRequestContext(entry, pane, entry.payload); return contextsMatch(cachedContext, requestedContext) ? entry.payload : null; } window.AIForecastAnalysisRuntime = Object.freeze({ ANALYSIS_CACHE_VERSION, normalizeModels, getModelSignature, normalizeContextLength, buildAnalysisRequestContext, buildPaneAnalysisCacheKey, createPaneAnalysisCacheEntry, getCachedPaneAnalysisPayload, matchesPanePayloadCurrentContext, resolvePaneDisplayPayload, }); })();