borsa / nextjs-app /src /lib /analysis-history.ts
bot
diag: add /api/diag/telegram to inspect bot credentials + reachability
fb9081e
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;
}