/** * @fileoverview v0.17.0 Helper Functions - Stable Hashes & Validation * Clean helpers for validation, deduplication, and TA fingerprinting */ import { z } from 'zod'; import { Signal } from './ml.js'; // Stable JSON for consistent hashing export function stableJson(obj: any): string { return JSON.stringify(obj, Object.keys(obj).sort()); } // Stable params hash for TA deduplication export function taParamsHash(params: Record): string { const keys = Object.keys(params || {}).sort(); let raw = ""; for (const k of keys) raw += `${k}:${String((params as any)[k])}|`; let h = 2166136261 >>> 0; for (let i = 0; i < raw.length; i++) { h ^= raw.charCodeAt(i); h = Math.imul(h, 16777619); } return (h >>> 0).toString(16).padStart(8, "0"); } // Signal deduplication key (stable format) export function buildSignalDedupKey(i: { symbol: string; timeframe: string; bar_ts: string; model_id: string; }): string { return `${i.symbol.toUpperCase()}:${i.timeframe}:${i.bar_ts}:${i.model_id}`; } // TA fingerprint for signal reproducibility export function buildTAFingerprint(items: Array<{name: string; params_hash: string}>): string { const canon = items .map(i => `${i.name}:${i.params_hash}`) .sort() .join("|"); let h = 2166136261 >>> 0; for (let i = 0; i < canon.length; i++) { h ^= canon.charCodeAt(i); h = Math.imul(h, 16777619); } return (h >>> 0).toString(16).padStart(8, "0"); } // Consensus deduplication key export function buildConsensusDedupKey(params: { symbol: string; timeframe: string; bar_ts: string; }): string { const { symbol, timeframe, bar_ts } = params; const cleanBarTs = bar_ts.replace(/[:.]/g, '-'); return `${symbol}-${timeframe}-${cleanBarTs}-consensus`; } // Common validation patterns export function validateGitHubUsername(username: string): boolean { return /^[a-z0-9-]+$/i.test(username) && username.length >= 1 && username.length <= 39; } export function validateSymbol(symbol: string): boolean { return /^[A-Z0-9.-]+$/i.test(symbol) && symbol.length >= 1 && symbol.length <= 20; } export function validateTimeframe(timeframe: string): boolean { return ['1m', '5m', '15m', '1h', '4h', '1d'].includes(timeframe); } export function validateDecision(decision: string): boolean { return ['BUY', 'SELL', 'HOLD'].includes(decision.toUpperCase()); } // Signal creation helper with validation export function makeSignal(params: { owner: string; model_id: string; symbol: string; timeframe: string; bar_ts: string; decision: string; confidence: number; model_version?: string; features_used?: string[]; bar_id?: string; ta_fingerprint?: string; }): { ok: boolean; data?: any; error?: string } { try { // Validate required fields early if (!params.owner || !params.model_id || !params.symbol || !params.timeframe || !params.bar_ts || !params.decision) { return { ok: false, error: 'Missing required fields: owner, model_id, symbol, timeframe, bar_ts, decision' }; } const signal = { owner: params.owner, model_id: params.model_id, symbol: params.symbol.toUpperCase(), timeframe: params.timeframe, bar_ts: params.bar_ts, decision: params.decision.toUpperCase(), confidence: Math.max(0, Math.min(1, params.confidence)), dedup_key: buildSignalDedupKey(params), model_version: params.model_version, features_used: params.features_used, bar_id: params.bar_id, ta_fingerprint: params.ta_fingerprint, meta: {} }; const validation = Signal.safeParse(signal); if (!validation.success) { return { ok: false, error: `Signal validation failed: ${validation.error.message}` }; } return { ok: true, data: validation.data }; } catch (error) { return { ok: false, error: `Signal creation failed: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } // v0.18.0 Enhanced Helpers for Infrastructure Integration export function createIdempotencyHeaders(dedup_key: string): Record { return { 'Idempotency-Key': `idem_${dedup_key.substring(0, 32)}`, 'X-Dedup-Key': dedup_key }; } // Enhanced header generation for Infrastructure integration export function createContractHeaders(version: string = '0.18.4', requestId?: string): Record { return { 'Content-Type': 'application/json', 'X-Contracts-Version': version, 'X-Request-Id': requestId || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, }; } // Flexible timeframe normalization (supports both 'timeframe' and 'tf') export function normalizeTimeframe(params: { timeframe?: string; tf?: string }): string { const timeframe = params.timeframe || params.tf; if (!timeframe) { throw new Error('Either timeframe or tf parameter is required'); } return timeframe; } // Enhanced query parameter normalization export function normalizeQueryParams(params: { timeframe?: string; tf?: string; symbol?: string; limit?: number | string; offset?: number | string; }): { symbol?: string; timeframe: string; limit: number; offset: number; } { return { symbol: params.symbol?.toUpperCase(), timeframe: normalizeTimeframe(params), limit: typeof params.limit === 'string' ? parseInt(params.limit, 10) : (params.limit || 100), offset: typeof params.offset === 'string' ? parseInt(params.offset, 10) : (params.offset || 0) }; } // Job deduplication key builder export function buildJobDedupKey(params: { type: string; owner: string; metadata?: Record; }): string { const metaHash = params.metadata ? taParamsHash(params.metadata) : 'none'; return `${params.type}:${params.owner}:${metaHash}`; } // Type exports export type ValidationResult = { ok: boolean; data?: T; error?: string; };