| import { NextResponse } from 'next/server' |
| import { wallets } from '@/lib/mock-data' |
| import { calculateChurnScore, detectPreChurn } from '@/lib/agent-engine' |
| import { sendCustomEvent, isTorqueConfigured, sendTelegramAlert } from '@/lib/torque-mcp' |
| import { pushEvent } from '@/lib/event-store' |
| import { recordIntervention, recordRecovery } from '@/lib/attribution-store' |
|
|
| function walletToSignals(w: typeof wallets[0]) { |
| const daysMatch = w.lastActive.match(/(\d+)d/) |
| const daysInactive = daysMatch ? parseInt(daysMatch[1]) : 0 |
| return { |
| daysInactive, |
| volumeDropPct: w.streak === 0 ? 80 : Math.max(0, (10 - Math.min(w.streak, 10)) * 8), |
| uniqueProtocols: w.protocols.length, |
| currentStreak: w.streak, |
| hasLiquidation: false, |
| } |
| } |
|
|
| export async function POST() { |
| const configured = isTorqueConfigured() |
| const detections: Array<{ |
| wallet: string; risk: string; score: number; eventName: string; |
| eventSent: boolean; eventId?: string; error?: string; preChurn?: boolean; walletType?: string |
| }> = [] |
|
|
| for (const wallet of wallets) { |
| const signals = walletToSignals(wallet) |
| const { score, risk } = calculateChurnScore(signals) |
|
|
| |
| const { isPreChurn, walletType } = detectPreChurn( |
| signals.daysInactive, signals.currentStreak, wallet.protocols |
| ) |
|
|
| if (risk === 'critical' || risk === 'high' || risk === 'medium') { |
| const eventName = risk === 'critical' || risk === 'high' ? 'churn_risk_high' : 'churn_risk_medium' |
|
|
| const result = configured |
| ? await sendCustomEvent(wallet.address, eventName, { |
| risk, score, |
| daysInactive: signals.daysInactive, |
| protocols: wallet.protocols, |
| walletType, |
| detectedBy: 'flowstate-ai-agent', |
| }) |
| : { success: false, error: 'TORQUE_INGEST_KEY not configured' } |
|
|
| if (result.success && result.eventId) { |
| pushEvent({ |
| ingestionId: result.eventId, |
| wallet: wallet.address, |
| eventName, |
| risk, |
| score, |
| firedAt: new Date().toISOString(), |
| source: 'scan', |
| }) |
| recordIntervention(wallet.address, eventName, score) |
| } |
|
|
| detections.push({ |
| wallet: wallet.address, risk, score, eventName, |
| eventSent: result.success, |
| eventId: result.eventId, |
| error: result.error, |
| walletType, |
| }) |
| } else if (isPreChurn) { |
| |
| const result = configured |
| ? await sendCustomEvent(wallet.address, 'inactivity_detected', { |
| risk: 'pre_churn', |
| score, |
| daysInactive: signals.daysInactive, |
| walletType, |
| preChurnWarning: true, |
| detectedBy: 'flowstate-ai-agent', |
| }) |
| : { success: false, error: 'TORQUE_INGEST_KEY not configured' } |
|
|
| if (result.success && result.eventId) { |
| pushEvent({ |
| ingestionId: result.eventId, |
| wallet: wallet.address, |
| eventName: 'inactivity_detected', |
| risk: 'low', |
| score, |
| firedAt: new Date().toISOString(), |
| source: 'scan', |
| }) |
| } |
|
|
| detections.push({ |
| wallet: wallet.address, risk: 'pre_churn', score, eventName: 'inactivity_detected', |
| eventSent: result.success, eventId: result.eventId, error: result.error, |
| preChurn: true, walletType, |
| }) |
| } |
| } |
|
|
| |
| const criticalCount = detections.filter(d => d.risk === 'critical').length |
| if (criticalCount >= 5) { |
| const fired = detections.filter(d => d.eventSent).length |
| await sendTelegramAlert( |
| `🚨 ${criticalCount} critical wallets detected in scan\n` + |
| `📡 ${fired}/${detections.length} Torque events confirmed\n` + |
| `⏱ ${new Date().toLocaleTimeString('en-US', { hour12: false })} UTC` |
| ) |
| } |
|
|
| return NextResponse.json({ |
| detections, |
| count: detections.length, |
| preChurnCount: detections.filter(d => d.preChurn).length, |
| configured, |
| timestamp: new Date().toISOString(), |
| }) |
| } |
|
|
| export async function GET() { |
| return NextResponse.json({ |
| status: 'active', |
| configured: isTorqueConfigured(), |
| capabilities: [ |
| 'churn_detection', 'pre_churn_early_warning', 'wallet_type_classification', |
| 'auto_campaign_creation', 'comeback_detection', 'streak_tracking', |
| 'recovery_attribution', 'telegram_alerts', |
| ], |
| }) |
| } |
|
|