336 lines
10 KiB
TypeScript
336 lines
10 KiB
TypeScript
/**
|
|
* 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<string, any>;
|
|
timestamp: Date;
|
|
context?: Record<string, any>;
|
|
}
|
|
|
|
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<string, any>;
|
|
}
|
|
|
|
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<string, DetectedPattern> = 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<DetectedPattern | null> {
|
|
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<Interaction[]> {
|
|
// 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<void> {
|
|
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<DetectedPattern[]> {
|
|
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<void> {
|
|
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<PatternDetectorConfig>): 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;
|