| export interface AnalysisRecord { |
| id: string; |
| symbol: string; |
| prediction: 'BUY' | 'SELL' | 'HOLD' | string; |
| confidence: number; |
| created_at: string; |
| actual_outcome?: 'correct' | 'incorrect' | 'pending'; |
| } |
|
|
| const HISTORY_PREFIX = 'borsa.analysis_history.'; |
| const DEFAULT_MAX_RECORDS = 500; |
|
|
| let fallbackCounter = 0; |
|
|
| function generateId(): string { |
| if (typeof globalThis !== 'undefined') { |
| const cryptoObj = (globalThis as Record<string, unknown>).crypto as { randomUUID?: () => string } | undefined; |
| if (cryptoObj?.randomUUID) return cryptoObj.randomUUID(); |
| } |
| fallbackCounter += 1; |
| return `${Date.now().toString(36)}-${fallbackCounter.toString(36)}`; |
| } |
|
|
| function safeParseJson(value: string | null): unknown { |
| if (!value) return null; |
| try { |
| return JSON.parse(value); |
| } catch { |
| return null; |
| } |
| } |
|
|
| function normalizeRecord(input: unknown): AnalysisRecord | null { |
| if (!input || typeof input !== 'object') return null; |
| const rec = input as Record<string, unknown>; |
| if (typeof rec.symbol !== 'string') return null; |
| if (typeof rec.prediction !== 'string') return null; |
| const confidenceRaw = typeof rec.confidence === 'number' ? rec.confidence : Number(rec.confidence); |
| const confidence = Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(100, confidenceRaw)) : 0; |
| const createdAt = typeof rec.created_at === 'string' ? rec.created_at : new Date().toISOString(); |
|
|
| const actual = rec.actual_outcome; |
| const actual_outcome = actual === 'correct' || actual === 'incorrect' || actual === 'pending' ? actual : undefined; |
|
|
| return { |
| id: typeof rec.id === 'string' && rec.id ? rec.id : generateId(), |
| symbol: rec.symbol.toUpperCase(), |
| prediction: rec.prediction, |
| confidence, |
| created_at: createdAt, |
| actual_outcome, |
| }; |
| } |
|
|
| export function getAnalysisHistoryKey(userId?: string | null): string { |
| return `${HISTORY_PREFIX}${userId || 'anonymous'}`; |
| } |
|
|
| export function loadAnalysisHistory(userId?: string | null): AnalysisRecord[] { |
| if (typeof window === 'undefined') return []; |
| const key = getAnalysisHistoryKey(userId); |
| const parsed = safeParseJson(window.localStorage.getItem(key)); |
| if (!Array.isArray(parsed)) return []; |
| const normalized = parsed.map(normalizeRecord).filter(Boolean) as AnalysisRecord[]; |
| normalized.sort((a, b) => (a.created_at < b.created_at ? 1 : a.created_at > b.created_at ? -1 : 0)); |
| return normalized; |
| } |
|
|
| export function saveAnalysisHistory(records: AnalysisRecord[], userId?: string | null, maxRecords = DEFAULT_MAX_RECORDS): void { |
| if (typeof window === 'undefined') return; |
| const key = getAnalysisHistoryKey(userId); |
| const trimmed = records.slice(0, Math.max(1, maxRecords)); |
| window.localStorage.setItem(key, JSON.stringify(trimmed)); |
| } |
|
|
| export function appendAnalysisRecord(record: Omit<AnalysisRecord, 'id'> & { id?: string }, userId?: string | null): AnalysisRecord { |
| const normalized = normalizeRecord(record); |
| const finalized = normalized ?? { |
| id: generateId(), |
| symbol: String(record.symbol || '').toUpperCase(), |
| prediction: String(record.prediction || 'HOLD'), |
| confidence: 0, |
| created_at: new Date().toISOString(), |
| actual_outcome: (record as Partial<AnalysisRecord>).actual_outcome, |
| }; |
|
|
| const existing = loadAnalysisHistory(userId); |
| saveAnalysisHistory([finalized, ...existing], userId); |
| return finalized; |
| } |
|
|
| export function clearAnalysisHistory(userId?: string | null): void { |
| if (typeof window === 'undefined') return; |
| window.localStorage.removeItem(getAnalysisHistoryKey(userId)); |
| } |
|
|
| export function setAnalysisRecordOutcome( |
| recordId: string, |
| outcome: 'correct' | 'incorrect' | 'pending', |
| userId?: string | null |
| ): AnalysisRecord[] { |
| if (typeof window === 'undefined') return []; |
| const existing = loadAnalysisHistory(userId); |
| const next = existing.map((r) => (r.id === recordId ? { ...r, actual_outcome: outcome } : r)); |
| saveAnalysisHistory(next, userId); |
| return next; |
| } |
|
|