/** * PatternDetector.ts * * Detecção proativa de padrões de uso no Arcádia * Monitora eventos de usuário e identifica ações repetidas * para sugerir automações/skills emergentes * * Fase 4: OpenClaw Embutido */ import { db } from "@/db"; import { detectedPatterns, skillSuggestions } from "@/shared/schema"; import { eq, and, gte } from "drizzle-orm"; import { EventEmitter } from "events"; interface Interaction { id: string; tenant_id: string; user_id: string; action_type: string; action_data: Record; timestamp: Date; context?: Record; } interface DetectedPattern { id?: number; tenant_id: string; user_id: string; action_type: string; occurrences: number; confidence: number; time_window_days: number; created_at?: Date; metadata?: Record; } interface PatternDetectorConfig { min_occurrences: number; time_window_days: number; confidence_threshold: number; } /** * PatternDetector * * Analisa interações de usuário e detecta padrões * Emite eventos quando um padrão é confirmado */ export class PatternDetector extends EventEmitter { private config: PatternDetectorConfig; private detectedPatterns: Map = new Map(); constructor(config: PatternDetectorConfig) { super(); this.config = { min_occurrences: config.min_occurrences ?? 3, time_window_days: config.time_window_days ?? 30, confidence_threshold: config.confidence_threshold ?? 0.8, }; } /** * Detectar padrão a partir de eventos de usuário * * Fluxo: * 1. Buscar interações similares do usuário (últimos N dias) * 2. Agrupar por tipo de ação * 3. Contar ocorrências * 4. Se >= min_occurrences: calcular confiança * 5. Se confiança >= threshold: emitir evento */ async detectPattern(userId: string, tenantId: string, actionType: string): Promise { try { // 1. Buscar interações similares const interactions = await this.getRecentInteractions( userId, tenantId, actionType, this.config.time_window_days ); if (interactions.length < this.config.min_occurrences) { return null; // Insuficiente para detectar padrão } // 2. Calcular confiança const confidence = this.calculateConfidence(interactions); if (confidence < this.config.confidence_threshold) { return null; // Confiança insuficiente } // 3. Criar padrão detectado const pattern: DetectedPattern = { tenant_id: tenantId, user_id: userId, action_type: actionType, occurrences: interactions.length, confidence: confidence, time_window_days: this.config.time_window_days, metadata: { first_occurrence: interactions[0].timestamp, last_occurrence: interactions[interactions.length - 1].timestamp, frequency_per_week: (interactions.length / (this.config.time_window_days / 7)).toFixed(2), average_interval_hours: this.calculateAverageInterval(interactions), }, }; // 4. Armazenar padrão await this.storePattern(pattern); // 5. Emitir evento this.emit("pattern-detected", { pattern, timestamp: new Date(), }); return pattern; } catch (error) { console.error(`[PatternDetector] Erro ao detectar padrão para ${userId}:`, error); return null; } } /** * Buscar interações recentes de um usuário */ private async getRecentInteractions( userId: string, tenantId: string, actionType: string, days: number ): Promise { // TODO: Integrar com Learning API ou database de eventos // Por enquanto, retorna mock para testes const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); // Implementação real seria: // const interactions = await db.query.interactions.findMany({ // where: and( // eq(interactions.user_id, userId), // eq(interactions.tenant_id, tenantId), // eq(interactions.action_type, actionType), // gte(interactions.timestamp, cutoffDate) // ), // orderBy: (interactions) => interactions.timestamp, // }); return []; } /** * Calcular confiança do padrão * * Fatores: * - Frequência (quantas vezes foi executado) * - Regularidade (quão consistente é o intervalo) * - Recência (foi feito recentemente) */ private calculateConfidence(interactions: Interaction[]): number { if (interactions.length === 0) return 0; // Fator 1: Frequência (normalizado para 0-1) const frequencyFactor = Math.min(interactions.length / 10, 1.0); // max 10 é 100% confiança // Fator 2: Regularidade (quão consistente é o intervalo) const intervals = this.calculateIntervals(interactions); const regularityFactor = this.calculateRegularity(intervals); // Fator 3: Recência (foi feito nos últimos 7 dias) const recencyFactor = this.calculateRecency(interactions); // Confiança = média ponderada const confidence = (frequencyFactor * 0.4) + (regularityFactor * 0.35) + (recencyFactor * 0.25); return parseFloat(confidence.toFixed(2)); } /** * Calcular intervalos entre interações */ private calculateIntervals(interactions: Interaction[]): number[] { const intervals: number[] = []; for (let i = 1; i < interactions.length; i++) { const prevTime = interactions[i - 1].timestamp.getTime(); const currTime = interactions[i].timestamp.getTime(); const intervalHours = (currTime - prevTime) / (1000 * 60 * 60); intervals.push(intervalHours); } return intervals; } /** * Calcular regularidade (consistência dos intervalos) * * Se os intervalos são muito diferentes, confiança cai * Se são similares, confiança sobe */ private calculateRegularity(intervals: number[]): number { if (intervals.length === 0) return 0; const average = intervals.reduce((a, b) => a + b, 0) / intervals.length; const variance = intervals.reduce((sum, interval) => { return sum + Math.pow(interval - average, 2); }, 0) / intervals.length; const stdDeviation = Math.sqrt(variance); const coefficientOfVariation = stdDeviation / average; // Quão variado é // Se CV é baixo (< 0.5), alta regularidade // Se CV é alto (> 1.0), baixa regularidade const regularity = Math.max(0, 1 - (coefficientOfVariation / 2)); return Math.min(regularity, 1.0); } /** * Calcular recência (quanto tempo desde a última ação) */ private calculateRecency(interactions: Interaction[]): number { if (interactions.length === 0) return 0; const lastInteraction = interactions[interactions.length - 1]; const daysSinceLastAction = (Date.now() - lastInteraction.timestamp.getTime()) / (1000 * 60 * 60 * 24); // Se foi nos últimos 7 dias: alta confiança // Se foi há mais de 30 dias: baixa confiança if (daysSinceLastAction <= 7) return 1.0; if (daysSinceLastAction > 30) return 0.2; return 1.0 - (daysSinceLastAction - 7) / (30 - 7) * 0.8; } /** * Calcular intervalo médio entre interações (em horas) */ private calculateAverageInterval(interactions: Interaction[]): number { const intervals = this.calculateIntervals(interactions); if (intervals.length === 0) return 0; const average = intervals.reduce((a, b) => a + b, 0) / intervals.length; return parseFloat(average.toFixed(2)); } /** * Armazenar padrão detectado em database */ private async storePattern(pattern: DetectedPattern): Promise { try { await db.insert(detectedPatterns).values({ tenant_id: pattern.tenant_id, user_id: pattern.user_id, action_type: pattern.action_type, occurrences: pattern.occurrences, confidence: pattern.confidence.toString(), time_window_days: pattern.time_window_days, metadata: pattern.metadata, created_at: new Date(), }); console.log(`[PatternDetector] Padrão armazenado: ${pattern.action_type} (confiança: ${pattern.confidence})`); } catch (error) { console.error(`[PatternDetector] Erro ao armazenar padrão:`, error); throw error; } } /** * Obter padrões detectados para um usuário */ async getPatternsForUser(userId: string, tenantId: string): Promise { try { const patterns = await db .select() .from(detectedPatterns) .where(and(eq(detectedPatterns.user_id, userId), eq(detectedPatterns.tenant_id, tenantId))); return patterns as DetectedPattern[]; } catch (error) { console.error(`[PatternDetector] Erro ao buscar padrões:`, error); return []; } } /** * Verificar periodicamente por novos padrões * (Para ser chamado por um scheduler/cron) */ async checkForNewPatterns(tenantId: string): Promise { console.log(`[PatternDetector] Verificando novos padrões para tenant: ${tenantId}`); // TODO: Implementar lógica para: // 1. Buscar todos os usuários do tenant // 2. Buscar suas interações recentes // 3. Agrupar por tipo de ação // 4. Chamar detectPattern para cada agrupamento // 5. Emitir eventos para padrões confirmados } /** * Obter configuração atual */ getConfig(): PatternDetectorConfig { return { ...this.config }; } /** * Atualizar configuração */ setConfig(config: Partial): void { this.config = { ...this.config, ...config }; console.log(`[PatternDetector] Configuração atualizada:`, this.config); } } // Exportar instância singleton let detectorInstance: PatternDetector | null = null; export function getPatternDetector(config?: PatternDetectorConfig): PatternDetector { if (!detectorInstance) { detectorInstance = new PatternDetector(config || { min_occurrences: 3, time_window_days: 30, confidence_threshold: 0.8, }); } return detectorInstance; } export default PatternDetector;