     1→const express = require('express');
     2→const router = express.Router();
     3→const { supabase } = require('../config/database');
     4→
     5→// ============================================
     6→// Achievement Definitions
     7→// ============================================
     8→
     9→const ACHIEVEMENTS = {
    10→  // Getting Started (10-25 XP)
    11→  first_blood: {
    12→    id: 'first_blood',
    13→    name: 'First Blood',
    14→    description: 'Make your first call',
    15→    emoji: '🩸',
    16→    xp: 10,
    17→    category: 'getting_started',
    18→    check: async (userId, data) => data.totalCalls >= 1
    19→  },
    20→  first_contact: {
    21→    id: 'first_contact',
    22→    name: 'First Contact',
    23→    description: 'Have your first conversation',
    24→    emoji: '👋',
    25→    xp: 15,
    26→    category: 'getting_started',
    27→    check: async (userId, data) => data.conversations >= 1
    28→  },
    29→  closer: {
    30→    id: 'closer',
    31→    name: 'Closer',
    32→    description: 'Get your first conversion',
    33→    emoji: '🎯',
    34→    xp: 50,
    35→    category: 'getting_started',
    36→    check: async (userId, data) => data.conversions >= 1
    37→  },
    38→
    39→  // Call Volume (25-100 XP)
    40→  dialer: {
    41→    id: 'dialer',
    42→    name: 'Dialer',
    43→    description: 'Make 10 calls',
    44→    emoji: '📞',
    45→    xp: 25,
    46→    category: 'volume',
    47→    check: async (userId, data) => data.totalCalls >= 10
    48→  },
    49→  call_machine: {
    50→    id: 'call_machine',
    51→    name: 'Call Machine',
    52→    description: 'Make 50 calls',
    53→    emoji: '🤖',
    54→    xp: 50,
    55→    category: 'volume',
    56→    check: async (userId, data) => data.totalCalls >= 50
    57→  },
    58→  phone_warrior: {
    59→    id: 'phone_warrior',
    60→    name: 'Phone Warrior',
    61→    description: 'Make 100 calls',
    62→    emoji: '⚔',
    63→    xp: 100,
    64→    category: 'volume',
    65→    check: async (userId, data) => data.totalCalls >= 100
    66→  },
    67→  call_legend: {
    68→    id: 'call_legend',
    69→    name: 'Call Legend',
    70→    description: 'Make 500 calls',
    71→    emoji: '👑',
    72→    xp: 250,
    73→    category: 'volume',
    74→    check: async (userId, data) => data.totalCalls >= 500
    75→  },
    76→
    77→  // Streaks (25-200 XP)
    78→  on_fire: {
    79→    id: 'on_fire',
    80→    name: 'On Fire',
    81→    description: '3-day streak',
    82→    emoji: '🔥',
    83→    xp: 25,
    84→    category: 'streak',
    85→    check: async (userId, data) => data.currentStreak >= 3
    86→  },
    87→  unstoppable: {
    88→    id: 'unstoppable',
    89→    name: 'Unstoppable',
    90→    description: '7-day streak',
    91→    emoji: '💪',
    92→    xp: 75,
    93→    category: 'streak',
    94→    check: async (userId, data) => data.currentStreak >= 7
    95→  },
    96→  consistency_king: {
    97→    id: 'consistency_king',
    98→    name: 'Consistency King',
    99→    description: '30-day streak',
   100→    emoji: '🏆',
   101→    xp: 200,
   102→    category: 'streak',
   103→    check: async (userId, data) => data.currentStreak >= 30
   104→  },
   105→
   106→  // Conversions (50-500 XP)
   107→  deal_maker: {
   108→    id: 'deal_maker',
   109→    name: 'Deal Maker',
   110→    description: 'Convert 5 leads',
   111→    emoji: '🤝',
   112→    xp: 75,
   113→    category: 'conversions',
   114→    check: async (userId, data) => data.conversions >= 5
   115→  },
   116→  sales_star: {
   117→    id: 'sales_star',
   118→    name: 'Sales Star',
   119→    description: 'Convert 25 leads',
   120→    emoji: '⭐',
   121→    xp: 200,
   122→    category: 'conversions',
   123→    check: async (userId, data) => data.conversions >= 25
   124→  },
   125→  rainmaker: {
   126→    id: 'rainmaker',
   127→    name: 'Rainmaker',
   128→    description: 'Convert 100 leads',
   129→    emoji: '🌧',
   130→    xp: 500,
   131→    category: 'conversions',
   132→    check: async (userId, data) => data.conversions >= 100
   133→  },
   134→
   135→  // Special Achievements (50-150 XP)
   136→  hot_streak: {
   137→    id: 'hot_streak',
   138→    name: 'Hot Streak',
   139→    description: '3 conversions in one day',
   140→    emoji: '🌟',
   141→    xp: 100,
   142→    category: 'special',
   143→    check: async (userId, data) => data.conversionsToday >= 3
   144→  },
   145→  early_bird: {
   146→    id: 'early_bird',
   147→    name: 'Early Bird',
   148→    description: 'Make a call before 8am',
   149→    emoji: '🐦',
   150→    xp: 25,
   151→    category: 'special',
   152→    check: async (userId, data) => data.earlyCall
   153→  },
   154→  night_owl: {
   155→    id: 'night_owl',
   156→    name: 'Night Owl',
   157→    description: 'Make a call after 8pm',
   158→    emoji: '🦉',
   159→    xp: 25,
   160→    category: 'special',
   161→    check: async (userId, data) => data.lateCall
   162→  },
   163→  ai_master: {
   164→    id: 'ai_master',
   165→    name: 'AI Master',
   166→    description: 'Use AI Copilot for 10 calls',
   167→    emoji: '🤖',
   168→    xp: 50,
   169→    category: 'special',
   170→    check: async (userId, data) => data.copilotCalls >= 10
   171→  },
   172→  script_follower: {
   173→    id: 'script_follower',
   174→    name: 'Script Follower',
   175→    description: 'Complete a full script session',
   176→    emoji: '📜',
   177→    xp: 30,
   178→    category: 'special',
   179→    check: async (userId, data) => data.completedScripts >= 1
   180→  },
   181→
   182→  // Performance (100-300 XP)
   183→  conversion_king: {
   184→    id: 'conversion_king',
   185→    name: 'Conversion King',
   186→    description: '20%+ conversion rate (min 10 calls)',
   187→    emoji: '👑',
   188→    xp: 150,
   189→    category: 'performance',
   190→    check: async (userId, data) => data.totalCalls >= 10 && data.conversionRate >= 20
   191→  },
   192→  perfect_week: {
   193→    id: 'perfect_week',
   194→    name: 'Perfect Week',
   195→    description: 'Hit 100% of weekly targets',
   196→    emoji: '💯',
   197→    xp: 200,
   198→    category: 'performance',
   199→    check: async (userId, data) => data.weeklyTargetsMet
   200→  },
   201→};
   202→
   203→// ============================================
   204→// Get all achievement definitions
   205→// ============================================
   206→
   207→router.get('/', async (req, res) => {
   208→  try {
   209→    const achievements = Object.values(ACHIEVEMENTS).map(a => ({
   210→      id: a.id,
   211→      name: a.name,
   212→      description: a.description,
   213→      emoji: a.emoji,
   214→      xp: a.xp,
   215→      category: a.category,
   216→    }));
   217→
   218→    // Group by category
   219→    const categories = {
   220→      getting_started: achievements.filter(a => a.category === 'getting_started'),
   221→      volume: achievements.filter(a => a.category === 'volume'),
   222→      streak: achievements.filter(a => a.category === 'streak'),
   223→      conversions: achievements.filter(a => a.category === 'conversions'),
   224→      special: achievements.filter(a => a.category === 'special'),
   225→      performance: achievements.filter(a => a.category === 'performance'),
   226→    };
   227→
   228→    res.json({
   229→      success: true,
   230→      achievements,
   231→      categories,
   232→      total: achievements.length,
   233→    });
   234→  } catch (error) {
   235→    console.error('Error fetching achievements:', error);
   236→    res.status(500).json({ success: false, error: error.message });
   237→  }
   238→});
   239→
   240→// ============================================
   241→// Get user's unlocked achievements
   242→// ============================================
   243→
   244→router.get('/user', async (req, res) => {
   245→  try {
   246→    // For now, use a default user ID or get from session
   247→    const userId = req.user?.id || 'default-user';
   248→
   249→    const { data: unlocked, error } = await supabase
   250→      .from('user_achievements')
   251→      .select('*')
   252→      .eq('user_id', userId)
   253→      .order('unlocked_at', { ascending: false });
   254→
   255→    if (error) throw error;
   256→
   257→    // Get full achievement details for unlocked ones
   258→    const unlockedWithDetails = (unlocked || []).map(ua => {
   259→      const def = ACHIEVEMENTS[ua.achievement_id];
   260→      return {
   261→        ...ua,
   262→        name: def?.name || ua.achievement_id,
   263→        description: def?.description || '',
   264→        emoji: def?.emoji || '🏅',
   265→        xp: def?.xp || 0,
   266→        category: def?.category || 'special',
   267→      };
   268→    });
   269→
   270→    // Calculate progress for locked achievements
   271→    const allIds = Object.keys(ACHIEVEMENTS);
   272→    const unlockedIds = new Set((unlocked || []).map(u => u.achievement_id));
   273→    const lockedAchievements = allIds
   274→      .filter(id => !unlockedIds.has(id))
   275→      .map(id => ({
   276→        id,
   277→        ...ACHIEVEMENTS[id],
   278→        locked: true,
   279→      }));
   280→
   281→    res.json({
   282→      success: true,
   283→      unlocked: unlockedWithDetails,
   284→      locked: lockedAchievements,
   285→      totalUnlocked: unlockedWithDetails.length,
   286→      totalAchievements: allIds.length,
   287→      percentComplete: Math.round((unlockedWithDetails.length / allIds.length) * 100),
   288→    });
   289→  } catch (error) {
   290→    console.error('Error fetching user achievements:', error);
   291→    res.status(500).json({ success: false, error: error.message });
   292→  }
   293→});
   294→
   295→// ============================================
   296→// Check and award achievements
   297→// ============================================
   298→
   299→router.post('/check', async (req, res) => {
   300→  try {
   301→    const userId = req.user?.id || 'default-user';
   302→
   303→    // Gather user stats for achievement checking
   304→    const stats = await gatherUserStats(userId);
   305→
   306→    // Get already unlocked achievements
   307→    const { data: existing } = await supabase
   308→      .from('user_achievements')
   309→      .select('achievement_id')
   310→      .eq('user_id', userId);
   311→
   312→    const existingIds = new Set((existing || []).map(e => e.achievement_id));
   313→
   314→    // Check each achievement
   315→    const newlyUnlocked = [];
   316→
   317→    for (const [id, achievement] of Object.entries(ACHIEVEMENTS)) {
   318→      if (existingIds.has(id)) continue;
   319→
   320→      try {
   321→        const earned = await achievement.check(userId, stats);
   322→        if (earned) {
   323→          // Award the achievement
   324→          const { error: insertError } = await supabase
   325→            .from('user_achievements')
   326→            .insert({
   327→              user_id: userId,
   328→              achievement_id: id,
   329→              unlocked_at: new Date().toISOString(),
   330→              metadata: { stats_at_unlock: stats },
   331→            });
   332→
   333→          if (!insertError) {
   334→            newlyUnlocked.push({
   335→              id,
   336→              name: achievement.name,
   337→              description: achievement.description,
   338→              emoji: achievement.emoji,
   339→              xp: achievement.xp,
   340→            });
   341→
   342→            // Award XP
   343→            await awardXP(userId, achievement.xp, `achievement_${id}`);
   344→          }
   345→        }
   346→      } catch (err) {
   347→        console.error(`Error checking achievement ${id}:`, err);
   348→      }
   349→    }
   350→
   351→    res.json({
   352→      success: true,
   353→      newlyUnlocked,
   354→      totalNewXP: newlyUnlocked.reduce((sum, a) => sum + a.xp, 0),
   355→    });
   356→  } catch (error) {
   357→    console.error('Error checking achievements:', error);
   358→    res.status(500).json({ success: false, error: error.message });
   359→  }
   360→});
   361→
   362→// ============================================
   363→// Helper: Gather user stats
   364→// ============================================
   365→
   366→async function gatherUserStats(userId) {
   367→  const today = new Date().toISOString().split('T')[0];
   368→  const currentHour = new Date().getHours();
   369→
   370→  // Get call counts
   371→  const { data: calls } = await supabase
   372→    .from('call_log')
   373→    .select('id, created_at, outcome')
   374→    .eq('user_id', userId);
   375→
   376→  const totalCalls = calls?.length || 0;
   377→  const conversions = calls?.filter(c => c.outcome === 'converted' || c.outcome === 'won')?.length || 0;
   378→
   379→  // Get today's conversions
   380→  const { data: todayCalls } = await supabase
   381→    .from('call_log')
   382→    .select('id, outcome')
   383→    .eq('user_id', userId)
   384→    .gte('created_at', today);
   385→
   386→  const conversionsToday = todayCalls?.filter(c => c.outcome === 'converted' || c.outcome === 'won')?.length || 0;
   387→
   388→  // Get conversations count
   389→  const { data: conversations } = await supabase
   390→    .from('sms_messages')
   391→    .select('lead_id')
   392→    .eq('direction', 'inbound');
   393→
   394→  const uniqueConversations = new Set(conversations?.map(c => c.lead_id)).size;
   395→
   396→  // Get streak info
   397→  const { data: xpData } = await supabase
   398→    .from('user_xp')
   399→    .select('current_streak, longest_streak')
   400→    .eq('user_id', userId)
   401→    .single();
   402→
   403→  // Get copilot sessions
   404→  const { data: copilotSessions } = await supabase
   405→    .from('copilot_sessions')
   406→    .select('id')
   407→    .eq('user_id', userId);
   408→
   409→  // Get completed script sessions
   410→  const { data: scriptSessions } = await supabase
   411→    .from('script_sessions')
   412→    .select('id')
   413→    .eq('user_id', userId)
   414→    .eq('status', 'completed');
   415→
   416→  return {
   417→    totalCalls,
   418→    conversions,
   419→    conversionsToday,
   420→    conversations: uniqueConversations,
   421→    currentStreak: xpData?.current_streak || 0,
   422→    longestStreak: xpData?.longest_streak || 0,
   423→    conversionRate: totalCalls > 0 ? (conversions / totalCalls) * 100 : 0,
   424→    copilotCalls: copilotSessions?.length || 0,
   425→    completedScripts: scriptSessions?.length || 0,
   426→    earlyCall: currentHour < 8,
   427→    lateCall: currentHour >= 20,
   428→    weeklyTargetsMet: false, // Would need more complex logic
   429→  };
   430→}
   431→
   432→// ============================================
   433→// Helper: Award XP
   434→// ============================================
   435→
   436→async function awardXP(userId, amount, reason, relatedId = null) {
   437→  try {
   438→    // Insert XP history
   439→    await supabase
   440→      .from('xp_history')
   441→      .insert({
   442→        user_id: userId,
   443→        amount,
   444→        reason,
   445→        related_id: relatedId,
   446→      });
   447→
   448→    // Update or create user XP record
   449→    const { data: existing } = await supabase
   450→      .from('user_xp')
   451→      .select('*')
   452→      .eq('user_id', userId)
   453→      .single();
   454→
   455→    if (existing) {
   456→      await supabase
   457→        .from('user_xp')
   458→        .update({
   459→          total_xp: existing.total_xp + amount,
   460→          last_activity_date: new Date().toISOString().split('T')[0],
   461→        })
   462→        .eq('user_id', userId);
   463→    } else {
   464→      await supabase
   465→        .from('user_xp')
   466→        .insert({
   467→          user_id: userId,
   468→          total_xp: amount,
   469→          level: 1,
   470→          current_streak: 1,
   471→          last_activity_date: new Date().toISOString().split('T')[0],
   472→        });
   473→    }
   474→
   475→    return true;
   476→  } catch (error) {
   477→    console.error('Error awarding XP:', error);
   478→    return false;
   479→  }
   480→}
   481→
   482→module.exports = router;
   483→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Battleground API Routes
     3→ * Part of Gamification Battleground (Priority #7)
     4→ */
     5→const express = require('express');
     6→const router = express.Router();
     7→const leaderboardService = require('../services/leaderboardService');
     8→const challengesService = require('../services/challengesService');
     9→const badgesService = require('../services/badgesService');
    10→
    11→// ============================================
    12→// LEADERBOARD
    13→// ============================================
    14→
    15→// GET /api/battleground/leaderboard?period=today|week|month|all
    16→router.get('/leaderboard', async (req, res) => {
    17→  try {
    18→    const { period = 'today' } = req.query;
    19→    const result = await leaderboardService.getLeaderboard(period, 10);
    20→    res.json({ success: true, ...result });
    21→  } catch (error) {
    22→    console.error('Leaderboard error:', error);
    23→    res.status(500).json({ success: false, error: error.message });
    24→  }
    25→});
    26→
    27→// GET /api/battleground/leaderboard/:userId - Get specific user stats
    28→router.get('/leaderboard/:userId', async (req, res) => {
    29→  try {
    30→    const { userId } = req.params;
    31→    const { supabase } = require('../config/database');
    32→
    33→    const { data: user, error: userError } = await supabase
    34→      .from('user_xp')
    35→      .select('*')
    36→      .eq('user_id', userId)
    37→      .single();
    38→
    39→    if (userError) {
    40→      return res.status(404).json({ success: false, error: 'User not found' });
    41→    }
    42→
    43→    const { data: calls } = await supabase
    44→      .from('call_log')
    45→      .select('id, outcome')
    46→      .eq('user_id', userId);
    47→
    48→    const conversions = calls?.filter(c => c.outcome === 'converted' || c.outcome === 'won')?.length || 0;
    49→    const badges = await badgesService.getUserBadges(userId);
    50→
    51→    res.json({
    52→      success: true,
    53→      user: {
    54→        ...user,
    55→        totalCalls: calls?.length || 0,
    56→        totalConversions: conversions,
    57→        badges,
    58→      },
    59→    });
    60→  } catch (error) {
    61→    console.error('User stats error:', error);
    62→    res.status(500).json({ success: false, error: error.message });
    63→  }
    64→});
    65→
    66→// POST /api/battleground/leaderboard/snapshot - Save daily snapshot
    67→router.post('/leaderboard/snapshot', async (req, res) => {
    68→  try {
    69→    const periods = ['today', 'week', 'month', 'all'];
    70→    const results = {};
    71→
    72→    for (const period of periods) {
    73→      results[period] = await leaderboardService.saveLeaderboardSnapshot(period);
    74→    }
    75→
    76→    res.json({ success: true, results });
    77→  } catch (error) {
    78→    console.error('Snapshot error:', error);
    79→    res.status(500).json({ success: false, error: error.message });
    80→  }
    81→});
    82→
    83→// ============================================
    84→// HALL OF FAME
    85→// ============================================
    86→
    87→// GET /api/battleground/hall-of-fame
    88→router.get('/hall-of-fame', async (req, res) => {
    89→  try {
    90→    const records = await leaderboardService.getHallOfFame();
    91→    res.json({ success: true, records });
    92→  } catch (error) {
    93→    console.error('Hall of Fame error:', error);
    94→    res.status(500).json({ success: false, error: error.message });
    95→  }
    96→});
    97→
    98→// POST /api/battleground/hall-of-fame/check - Check and potentially set a record
    99→router.post('/hall-of-fame/check', async (req, res) => {
   100→  try {
   101→    const { recordType, userId, userName, value, details } = req.body;
   102→
   103→    if (!recordType || !userId || !userName || value === undefined) {
   104→      return res.status(400).json({ success: false, error: 'Missing required fields' });
   105→    }
   106→
   107→    const result = await leaderboardService.checkRecord(recordType, userId, userName, value, details);
   108→    res.json({ success: true, ...result });
   109→  } catch (error) {
   110→    console.error('Check record error:', error);
   111→    res.status(500).json({ success: false, error: error.message });
   112→  }
   113→});
   114→
   115→// ============================================
   116→// CHALLENGES
   117→// ============================================
   118→
   119→// GET /api/battleground/challenges
   120→router.get('/challenges', async (req, res) => {
   121→  try {
   122→    const userId = req.user?.id || 'default-user';
   123→    const challenges = await challengesService.getTodayChallenges(userId);
   124→    res.json({ success: true, ...challenges });
   125→  } catch (error) {
   126→    console.error('Challenges error:', error);
   127→    res.status(500).json({ success: false, error: error.message });
   128→  }
   129→});
   130→
   131→// POST /api/battleground/challenges/progress
   132→router.post('/challenges/progress', async (req, res) => {
   133→  try {
   134→    const userId = req.user?.id || 'default-user';
   135→    const { actionType, count = 1 } = req.body;
   136→
   137→    if (!actionType) {
   138→      return res.status(400).json({ success: false, error: 'actionType is required' });
   139→    }
   140→
   141→    // Update individual challenges
   142→    const individualResult = await challengesService.updateChallengeProgress(userId, actionType, count);
   143→
   144→    // Update team challenges
   145→    const teamResult = await challengesService.updateTeamChallengeProgress(actionType, count);
   146→
   147→    res.json({
   148→      success: true,
   149→      individual: individualResult,
   150→      team: teamResult,
   151→    });
   152→  } catch (error) {
   153→    console.error('Challenge progress error:', error);
   154→    res.status(500).json({ success: false, error: error.message });
   155→  }
   156→});
   157→
   158→// POST /api/battleground/challenges/generate - Force generate challenges (admin)
   159→router.post('/challenges/generate', async (req, res) => {
   160→  try {
   161→    const userId = req.user?.id || 'default-user';
   162→
   163→    const individual = await challengesService.generateDailyChallenges(userId);
   164→    const team = await challengesService.generateTeamChallenge();
   165→
   166→    res.json({ success: true, individual, team });
   167→  } catch (error) {
   168→    console.error('Challenge generation error:', error);
   169→    res.status(500).json({ success: false, error: error.message });
   170→  }
   171→});
   172→
   173→// ============================================
   174→// TEAM PULSE
   175→// ============================================
   176→
   177→// GET /api/battleground/pulse
   178→router.get('/pulse', async (req, res) => {
   179→  try {
   180→    const { limit = 20 } = req.query;
   181→    const events = await leaderboardService.getTeamPulse(parseInt(limit));
   182→    res.json({ success: true, events });
   183→  } catch (error) {
   184→    console.error('Pulse error:', error);
   185→    res.status(500).json({ success: false, error: error.message });
   186→  }
   187→});
   188→
   189→// POST /api/battleground/pulse - Create a pulse event manually
   190→router.post('/pulse', async (req, res) => {
   191→  try {
   192→    const { event_type, user_id, user_name, title, subtitle, emoji, link_type, link_id } = req.body;
   193→
   194→    if (!event_type || !user_id || !user_name || !title) {
   195→      return res.status(400).json({ success: false, error: 'Missing required fields' });
   196→    }
   197→
   198→    await leaderboardService.createPulseEvent({
   199→      event_type,
   200→      user_id,
   201→      user_name,
   202→      title,
   203→      subtitle,
   204→      emoji,
   205→      link_type,
   206→      link_id,
   207→    });
   208→
   209→    res.json({ success: true });
   210→  } catch (error) {
   211→    console.error('Create pulse error:', error);
   212→    res.status(500).json({ success: false, error: error.message });
   213→  }
   214→});
   215→
   216→// ============================================
   217→// BADGES
   218→// ============================================
   219→
   220→// GET /api/battleground/badges/:userId
   221→router.get('/badges/:userId', async (req, res) => {
   222→  try {
   223→    const badges = await badgesService.getUserBadges(req.params.userId);
   224→    res.json({ success: true, badges });
   225→  } catch (error) {
   226→    console.error('Badges error:', error);
   227→    res.status(500).json({ success: false, error: error.message });
   228→  }
   229→});
   230→
   231→// GET /api/battleground/badges - Get current week's badges
   232→router.get('/badges', async (req, res) => {
   233→  try {
   234→    const badges = await badgesService.getCurrentWeekBadges();
   235→    res.json({ success: true, badges });
   236→  } catch (error) {
   237→    console.error('Current badges error:', error);
   238→    res.status(500).json({ success: false, error: error.message });
   239→  }
   240→});
   241→
   242→// POST /api/battleground/badges/award-weekly (called by cron)
   243→router.post('/badges/award-weekly', async (req, res) => {
   244→  try {
   245→    const result = await badgesService.awardWeeklyBadges();
   246→    res.json({ success: true, ...result });
   247→  } catch (error) {
   248→    console.error('Award badges error:', error);
   249→    res.status(500).json({ success: false, error: error.message });
   250→  }
   251→});
   252→
   253→// ============================================
   254→// STREAKS
   255→// ============================================
   256→
   257→// POST /api/battleground/streak/update - Update user's streak tier
   258→router.post('/streak/update', async (req, res) => {
   259→  try {
   260→    const { userId, streakDays } = req.body;
   261→
   262→    if (!userId || streakDays === undefined) {
   263→      return res.status(400).json({ success: false, error: 'Missing userId or streakDays' });
   264→    }
   265→
   266→    const tier = await leaderboardService.updateStreakTier(userId, streakDays);
   267→    res.json({ success: true, tier });
   268→  } catch (error) {
   269→    console.error('Streak update error:', error);
   270→    res.status(500).json({ success: false, error: error.message });
   271→  }
   272→});
   273→
   274→module.exports = router;
   275→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Bug Report API route
     3→ * Saves to dev_queue table AND sends to Slack
     4→ */
     5→
     6→const express = require('express');
     7→const router = express.Router();
     8→const slackService = require('../services/slack');
     9→const { supabase } = require('../config/database');
    10→
    11→/**
    12→ * POST /api/bug-report
    13→ * Submit a bug report - saves to DB and sends to Slack
    14→ */
    15→router.post('/', async (req, res) => {
    16→  try {
    17→    const { description, pageUrl, userEmail, deviceInfo, fullDeviceInfo, screenshot } = req.body;
    18→
    19→    if (!description || !description.trim()) {
    20→      return res.status(400).json({ error: 'Description is required' });
    21→    }
    22→
    23→    // Save to dev_queue table
    24→    let bugId = null;
    25→    if (supabase) {
    26→      const { data: bug, error: dbError } = await supabase
    27→        .from('dev_queue')
    28→        .insert({
    29→          type: 'bug',
    30→          title: description.trim().substring(0, 100), // First 100 chars as title
    31→          description: description.trim(),
    32→          page_url: pageUrl || null,
    33→          reported_by: userEmail || 'Unknown',
    34→          device_info: deviceInfo || null,
    35→          console_errors: fullDeviceInfo ? JSON.stringify(fullDeviceInfo) : null,
    36→          priority: 3 // Default priority
    37→        })
    38→        .select('id')
    39→        .single();
    40→
    41→      if (dbError) {
    42→        console.error('[BugReport] DB insert failed:', dbError);
    43→      } else {
    44→        bugId = bug.id;
    45→        console.log(`[BugReport] Saved to DB as #${bugId}`);
    46→      }
    47→    }
    48→
    49→    // Send to Slack with enhanced format
    50→    if (bugId) {
    51→      // Use new dev queue format with CC protocol
    52→      await slackService.sendDevQueueItem({
    53→        id: bugId,
    54→        title: description.trim().substring(0, 100),
    55→        description: description.trim(),
    56→        page_url: pageUrl,
    57→        reported_by: userEmail || 'Unknown',
    58→        device_info: deviceInfo
    59→      }, 'bug');
    60→    } else {
    61→      // Fallback to old format if DB failed
    62→      await slackService.sendBugReport({
    63→        description: description.trim(),
    64→        pageUrl: pageUrl || 'Unknown',
    65→        userEmail: userEmail || 'Unknown',
    66→        deviceInfo: deviceInfo || 'Unknown',
    67→        timestamp: new Date().toISOString(),
    68→        screenshot: screenshot || null,
    69→      });
    70→    }
    71→
    72→    console.log(`[BugReport] Received from ${userEmail} on ${pageUrl}${bugId ? ` (Bug #${bugId})` : ''}`);
    73→
    74→    res.json({
    75→      success: true,
    76→      message: bugId ? `Bug #${bugId} reported` : 'Bug report submitted',
    77→      id: bugId
    78→    });
    79→  } catch (error) {
    80→    console.error('[BugReport] Error:', error);
    81→    res.status(500).json({ error: 'Failed to submit bug report' });
    82→  }
    83→});
    84→
    85→module.exports = router;
    86→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Now let me read the next batch of files.


     1→/**
     2→ * Dev Queue API
     3→ * Bugs and Feature Requests - stored in DB, mirrored to Slack
     4→ */
     5→
     6→const express = require('express');
     7→const router = express.Router();
     8→const { supabase } = require('../config/database');
     9→const slack = require('../services/slack');
    10→
    11→/**
    12→ * POST /api/dev/bug
    13→ * Report a bug - saves to DB and sends to Slack
    14→ */
    15→router.post('/bug', async (req, res) => {
    16→  try {
    17→    const {
    18→      title,
    19→      description,
    20→      pageUrl,
    21→      deviceInfo,
    22→      consoleErrors,
    23→      parentFeatureId, // For TEST-BUG linking
    24→      reportedBy
    25→    } = req.body;
    26→
    27→    if (!title) {
    28→      return res.status(400).json({ error: 'Title is required' });
    29→    }
    30→
    31→    // Insert into DB
    32→    let bugId = null;
    33→    const { data: bug, error } = await supabase
    34→      .from('dev_queue')
    35→      .insert({
    36→        type: 'bug',
    37→        title,
    38→        description,
    39→        page_url: pageUrl,
    40→        device_info: deviceInfo,
    41→        console_errors: consoleErrors,
    42→        parent_feature_id: parentFeatureId || null,
    43→        reported_by: reportedBy || req.user?.email || 'Unknown',
    44→        priority: parentFeatureId ? 2 : 3 // Test bugs get higher priority
    45→      })
    46→      .select()
    47→      .single();
    48→
    49→    const isTestBug = !!parentFeatureId;
    50→
    51→    if (error) {
    52→      console.error('[DevQueue] Failed to insert bug:', error);
    53→      // Still send to Slack as fallback
    54→      await slack.sendDevQueueItem({
    55→        id: 'PENDING',
    56→        title,
    57→        description,
    58→        page_url: pageUrl,
    59→        device_info: deviceInfo,
    60→        reported_by: reportedBy || req.user?.email || 'Unknown'
    61→      }, isTestBug ? 'test_bug' : 'bug');
    62→      // Return success since Slack worked
    63→      return res.json({
    64→        success: true,
    65→        message: 'Bug report sent to Slack (DB unavailable)',
    66→        id: null
    67→      });
    68→    }
    69→
    70→    bugId = bug.id;
    71→
    72→    // Send to Slack
    73→    await slack.sendDevQueueItem(bug, isTestBug ? 'test_bug' : 'bug');
    74→
    75→    res.json({
    76→      success: true,
    77→      id: bugId,
    78→      message: `Bug #${bugId} reported`
    79→    });
    80→
    81→  } catch (error) {
    82→    console.error('[DevQueue] Bug report error:', error);
    83→    res.status(500).json({ error: 'Failed to report bug' });
    84→  }
    85→});
    86→
    87→/**
    88→ * POST /api/dev/feature
    89→ * Request a feature - saves to DB and sends to Slack
    90→ */
    91→router.post('/feature', async (req, res) => {
    92→  try {
    93→    const {
    94→      title,
    95→      description,
    96→      reportedBy
    97→    } = req.body;
    98→
    99→    if (!title) {
   100→      return res.status(400).json({ error: 'Title is required' });
   101→    }
   102→
   103→    // Insert into DB
   104→    let featureId = null;
   105→    const { data: feature, error } = await supabase
   106→      .from('dev_queue')
   107→      .insert({
   108→        type: 'feature',
   109→        title,
   110→        description,
   111→        reported_by: reportedBy || req.user?.email || 'Unknown',
   112→        priority: 5 // Features default to lower priority than bugs
   113→      })
   114→      .select()
   115→      .single();
   116→
   117→    if (error) {
   118→      console.error('[DevQueue] Failed to insert feature:', error);
   119→      // Still send to Slack as fallback
   120→      await slack.sendDevQueueItem({
   121→        id: 'PENDING',
   122→        title,
   123→        description,
   124→        reported_by: reportedBy || req.user?.email || 'Unknown'
   125→      }, 'feature');
   126→      // Return success since Slack worked
   127→      return res.json({
   128→        success: true,
   129→        message: 'Feature request sent to Slack (DB unavailable)',
   130→        id: null
   131→      });
   132→    }
   133→
   134→    featureId = feature.id;
   135→
   136→    // Send to Slack
   137→    await slack.sendDevQueueItem(feature, 'feature');
   138→
   139→    res.json({
   140→      success: true,
   141→      id: featureId,
   142→      message: `Feature #${featureId} requested`
   143→    });
   144→
   145→  } catch (error) {
   146→    console.error('[DevQueue] Feature request error:', error);
   147→    res.status(500).json({ error: 'Failed to request feature' });
   148→  }
   149→});
   150→
   151→/**
   152→ * GET /api/dev/queue
   153→ * Get all open items (for CC to read)
   154→ */
   155→router.get('/queue', async (req, res) => {
   156→  try {
   157→    const { type, status = 'open,in_progress,ready_to_test,testing' } = req.query;
   158→
   159→    let query = supabase
   160→      .from('dev_queue')
   161→      .select('*')
   162→      .in('status', status.split(','))
   163→      .order('priority', { ascending: true })
   164→      .order('reported_at', { ascending: true });
   165→
   166→    if (type) {
   167→      query = query.eq('type', type);
   168→    }
   169→
   170→    const { data: items, error } = await query;
   171→
   172→    if (error) {
   173→      console.error('[DevQueue] Failed to fetch queue:', error);
   174→      return res.status(500).json({ error: 'Failed to fetch queue' });
   175→    }
   176→
   177→    // Group by type
   178→    const bugs = items.filter(i => i.type === 'bug');
   179→    const features = items.filter(i => i.type === 'feature');
   180→
   181→    res.json({
   182→      bugs,
   183→      features,
   184→      total: items.length,
   185→      summary: {
   186→        bugs: bugs.length,
   187→        features: features.length,
   188→        highPriority: items.filter(i => i.priority <= 2).length
   189→      }
   190→    });
   191→
   192→  } catch (error) {
   193→    console.error('[DevQueue] Queue fetch error:', error);
   194→    res.status(500).json({ error: 'Failed to fetch queue' });
   195→  }
   196→});
   197→
   198→/**
   199→ * PATCH /api/dev/queue/:id/status
   200→ * Update item status (for CC to mark progress)
   201→ */
   202→router.patch('/queue/:id/status', async (req, res) => {
   203→  try {
   204→    const { id } = req.params;
   205→    const { status, notes } = req.body;
   206→
   207→    const validStatuses = ['open', 'in_progress', 'ready_to_test', 'testing', 'closed'];
   208→    if (!validStatuses.includes(status)) {
   209→      return res.status(400).json({ error: 'Invalid status' });
   210→    }
   211→
   212→    const updates = { status };
   213→
   214→    // Track timestamps
   215→    if (status === 'in_progress') {
   216→      updates.started_at = new Date().toISOString();
   217→    } else if (status === 'closed') {
   218→      updates.completed_at = new Date().toISOString();
   219→    }
   220→
   221→    if (notes) {
   222→      updates.cc_notes = notes;
   223→    }
   224→
   225→    const { data: item, error } = await supabase
   226→      .from('dev_queue')
   227→      .update(updates)
   228→      .eq('id', id)
   229→      .select()
   230→      .single();
   231→
   232→    if (error) {
   233→      console.error('[DevQueue] Failed to update status:', error);
   234→      return res.status(500).json({ error: 'Failed to update status' });
   235→    }
   236→
   237→    // Send Slack notification for ready_to_test
   238→    if (status === 'ready_to_test') {
   239→      await slack.sendDevQueueReady(item);
   240→    }
   241→
   242→    res.json({ success: true, item });
   243→
   244→  } catch (error) {
   245→    console.error('[DevQueue] Status update error:', error);
   246→    res.status(500).json({ error: 'Failed to update status' });
   247→  }
   248→});
   249→
   250→/**
   251→ * GET /api/dev/queue/:id
   252→ * Get single item details
   253→ */
   254→router.get('/queue/:id', async (req, res) => {
   255→  try {
   256→    const { id } = req.params;
   257→
   258→    const { data: item, error } = await supabase
   259→      .from('dev_queue')
   260→      .select('*')
   261→      .eq('id', id)
   262→      .single();
   263→
   264→    if (error || !item) {
   265→      return res.status(404).json({ error: 'Item not found' });
   266→    }
   267→
   268→    // If it's a feature, get any linked test bugs
   269→    if (item.type === 'feature') {
   270→      const { data: testBugs } = await supabase
   271→        .from('dev_queue')
   272→        .select('*')
   273→        .eq('parent_feature_id', id)
   274→        .order('reported_at', { ascending: false });
   275→
   276→      item.testBugs = testBugs || [];
   277→    }
   278→
   279→    res.json(item);
   280→
   281→  } catch (error) {
   282→    console.error('[DevQueue] Item fetch error:', error);
   283→    res.status(500).json({ error: 'Failed to fetch item' });
   284→  }
   285→});
   286→
   287→module.exports = router;
   288→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Coaching API - Call scoring, feedback, and insights
     3→ */
     4→
     5→const express = require('express');
     6→const router = express.Router();
     7→const { supabase } = require('../config/database');
     8→
     9→// Score weights for different call aspects
    10→const SCORE_WEIGHTS = {
    11→  opener: 0.15,
    12→  discovery: 0.20,
    13→  value_prop: 0.20,
    14→  objection_handling: 0.20,
    15→  close: 0.15,
    16→  rapport: 0.10,
    17→};
    18→
    19→// Coaching tips by category
    20→const COACHING_TIPS = {
    21→  opener: [
    22→    { tip: 'Start with a pattern interrupt - avoid generic "How are you?"', threshold: 50 },
    23→    { tip: 'Reference something specific about them (company, role, recent activity)', threshold: 60 },
    24→    { tip: 'State your value prop within the first 30 seconds', threshold: 70 },
    25→  ],
    26→  discovery: [
    27→    { tip: 'Ask more open-ended questions to understand their pain points', threshold: 50 },
    28→    { tip: 'Use the "tell me more about..." technique to dig deeper', threshold: 60 },
    29→    { tip: 'Summarize their situation back to show understanding', threshold: 70 },
    30→  ],
    31→  value_prop: [
    32→    { tip: 'Connect your solution directly to their stated problems', threshold: 50 },
    33→    { tip: 'Use specific numbers and results from similar customers', threshold: 60 },
    34→    { tip: 'Paint a vivid "after" picture of their life with your solution', threshold: 70 },
    35→  ],
    36→  objection_handling: [
    37→    { tip: 'Acknowledge the objection before responding', threshold: 50 },
    38→    { tip: 'Ask clarifying questions to understand the real concern', threshold: 60 },
    39→    { tip: 'Use the Feel-Felt-Found technique for common objections', threshold: 70 },
    40→  ],
    41→  close: [
    42→    { tip: 'Always ask for the next step - never end without an action item', threshold: 50 },
    43→    { tip: 'Use assumptive closes: "Shall I send the contract?"', threshold: 60 },
    44→    { tip: 'Create urgency with limited-time value or specific timelines', threshold: 70 },
    45→  ],
    46→  rapport: [
    47→    { tip: 'Mirror their tone and pace of speech', threshold: 50 },
    48→    { tip: 'Find common ground early in the conversation', threshold: 60 },
    49→    { tip: 'Use their name naturally throughout the call', threshold: 70 },
    50→  ],
    51→};
    52→
    53→// ============================================
    54→// Get user's call scores history
    55→// ============================================
    56→
    57→router.get('/scores', async (req, res) => {
    58→  try {
    59→    const userId = req.user?.id || 'default-user';
    60→    const { limit = 20, offset = 0 } = req.query;
    61→
    62→    const { data, error } = await supabase
    63→      .from('call_scores')
    64→      .select(`
    65→        *,
    66→        leads (
    67→          first_name,
    68→          last_name,
    69→          company,
    70→          type
    71→        )
    72→      `)
    73→      .eq('user_id', userId)
    74→      .order('created_at', { ascending: false })
    75→      .range(offset, offset + parseInt(limit) - 1);
    76→
    77→    if (error) throw error;
    78→
    79→    // Calculate averages
    80→    const scores = data || [];
    81→    const averages = {};
    82→    const counts = {};
    83→
    84→    scores.forEach(score => {
    85→      Object.entries(score.scores || {}).forEach(([key, value]) => {
    86→        if (typeof value === 'number') {
    87→          averages[key] = (averages[key] || 0) + value;
    88→          counts[key] = (counts[key] || 0) + 1;
    89→        }
    90→      });
    91→    });
    92→
    93→    Object.keys(averages).forEach(key => {
    94→      averages[key] = Math.round(averages[key] / (counts[key] || 1));
    95→    });
    96→
    97→    res.json({
    98→      success: true,
    99→      scores,
   100→      averages,
   101→      totalScored: scores.length,
   102→    });
   103→  } catch (error) {
   104→    console.error('Error fetching call scores:', error);
   105→    res.status(500).json({ success: false, error: error.message });
   106→  }
   107→});
   108→
   109→// ============================================
   110→// Score a call (from AI analysis)
   111→// ============================================
   112→
   113→router.post('/score', async (req, res) => {
   114→  try {
   115→    const userId = req.user?.id || 'default-user';
   116→    const { callId, leadId, scores, transcript, outcome } = req.body;
   117→
   118→    if (!callId || !scores) {
   119→      return res.status(400).json({
   120→        success: false,
   121→        error: 'callId and scores are required',
   122→      });
   123→    }
   124→
   125→    // Calculate overall score
   126→    let overallScore = 0;
   127→    let totalWeight = 0;
   128→
   129→    Object.entries(scores).forEach(([key, value]) => {
   130→      const weight = SCORE_WEIGHTS[key] || 0.1;
   131→      overallScore += value * weight;
   132→      totalWeight += weight;
   133→    });
   134→
   135→    overallScore = Math.round(overallScore / (totalWeight || 1));
   136→
   137→    // Generate AI feedback based on scores
   138→    const weakAreas = Object.entries(scores)
   139→      .filter(([_, value]) => value < 60)
   140→      .map(([key]) => key);
   141→
   142→    const strongAreas = Object.entries(scores)
   143→      .filter(([_, value]) => value >= 80)
   144→      .map(([key]) => key);
   145→
   146→    let aiFeedback = '';
   147→    if (strongAreas.length > 0) {
   148→      aiFeedback = `Great job on ${strongAreas.join(' and ')}! `;
   149→    }
   150→    if (weakAreas.length > 0) {
   151→      aiFeedback += `Focus on improving ${weakAreas.join(' and ')} in your next calls.`;
   152→    }
   153→    if (!aiFeedback) {
   154→      aiFeedback = 'Solid call overall. Keep up the consistency!';
   155→    }
   156→
   157→    // Save the score
   158→    const { data, error } = await supabase
   159→      .from('call_scores')
   160→      .insert({
   161→        call_id: callId,
   162→        lead_id: leadId,
   163→        user_id: userId,
   164→        scores,
   165→        overall_score: overallScore,
   166→        ai_feedback: aiFeedback,
   167→        transcript_summary: transcript ? transcript.substring(0, 500) : null,
   168→      })
   169→      .select()
   170→      .single();
   171→
   172→    if (error) throw error;
   173→
   174→    // Generate coaching tips based on weak areas
   175→    const tips = [];
   176→    weakAreas.forEach(area => {
   177→      const areaTips = COACHING_TIPS[area] || [];
   178→      const relevantTip = areaTips.find(t => scores[area] < t.threshold);
   179→      if (relevantTip) {
   180→        tips.push({ area, tip: relevantTip.tip });
   181→      }
   182→    });
   183→
   184→    res.json({
   185→      success: true,
   186→      score: data,
   187→      overallScore,
   188→      aiFeedback,
   189→      strongAreas,
   190→      weakAreas,
   191→      tips,
   192→    });
   193→  } catch (error) {
   194→    console.error('Error scoring call:', error);
   195→    res.status(500).json({ success: false, error: error.message });
   196→  }
   197→});
   198→
   199→// ============================================
   200→// Get personalized coaching tips
   201→// ============================================
   202→
   203→router.get('/tips', async (req, res) => {
   204→  try {
   205→    const userId = req.user?.id || 'default-user';
   206→
   207→    // Get user's recent scores to find weak areas
   208→    const { data: recentScores } = await supabase
   209→      .from('call_scores')
   210→      .select('scores, overall_score')
   211→      .eq('user_id', userId)
   212→      .order('created_at', { ascending: false })
   213→      .limit(10);
   214→
   215→    // Calculate averages per category
   216→    const averages = {};
   217→    const counts = {};
   218→
   219→    (recentScores || []).forEach(score => {
   220→      Object.entries(score.scores || {}).forEach(([key, value]) => {
   221→        if (typeof value === 'number') {
   222→          averages[key] = (averages[key] || 0) + value;
   223→          counts[key] = (counts[key] || 0) + 1;
   224→        }
   225→      });
   226→    });
   227→
   228→    Object.keys(averages).forEach(key => {
   229→      averages[key] = Math.round(averages[key] / (counts[key] || 1));
   230→    });
   231→
   232→    // Generate personalized tips based on weak areas
   233→    const tips = [];
   234→    Object.entries(averages)
   235→      .sort((a, b) => a[1] - b[1]) // Sort by weakest first
   236→      .slice(0, 3) // Top 3 weakest areas
   237→      .forEach(([area, score]) => {
   238→        const areaTips = COACHING_TIPS[area] || [];
   239→        const relevantTip = areaTips.find(t => score < t.threshold);
   240→        if (relevantTip) {
   241→          tips.push({
   242→            area,
   243→            score,
   244→            tip: relevantTip.tip,
   245→            priority: score < 50 ? 'high' : score < 70 ? 'medium' : 'low',
   246→          });
   247→        }
   248→      });
   249→
   250→    // Add a general tip if no specific ones
   251→    if (tips.length === 0 && recentScores && recentScores.length > 0) {
   252→      tips.push({
   253→        area: 'general',
   254→        tip: 'You\'re doing great! Try focusing on one area to master next.',
   255→        priority: 'low',
   256→      });
   257→    }
   258→
   259→    res.json({
   260→      success: true,
   261→      tips,
   262→      averages,
   263→      callsAnalyzed: recentScores?.length || 0,
   264→    });
   265→  } catch (error) {
   266→    console.error('Error getting coaching tips:', error);
   267→    res.status(500).json({ success: false, error: error.message });
   268→  }
   269→});
   270→
   271→// ============================================
   272→// Get weekly performance summary
   273→// ============================================
   274→
   275→router.get('/weekly', async (req, res) => {
   276→  try {
   277→    const userId = req.user?.id || 'default-user';
   278→
   279→    // Get start of current week (Sunday)
   280→    const now = new Date();
   281→    const weekStart = new Date(now);
   282→    weekStart.setDate(now.getDate() - now.getDay());
   283→    weekStart.setHours(0, 0, 0, 0);
   284→
   285→    // Check if we have a cached summary
   286→    const { data: existingSummary } = await supabase
   287→      .from('weekly_summaries')
   288→      .select('*')
   289→      .eq('user_id', userId)
   290→      .eq('week_start', weekStart.toISOString().split('T')[0])
   291→      .single();
   292→
   293→    if (existingSummary) {
   294→      return res.json({
   295→        success: true,
   296→        summary: existingSummary,
   297→        cached: true,
   298→      });
   299→    }
   300→
   301→    // Generate new summary from this week's data
   302→    const weekEndStr = new Date(weekStart.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString();
   303→
   304→    // Get call scores this week
   305→    const { data: callScores } = await supabase
   306→      .from('call_scores')
   307→      .select('scores, overall_score, created_at')
   308→      .eq('user_id', userId)
   309→      .gte('created_at', weekStart.toISOString())
   310→      .lt('created_at', weekEndStr);
   311→
   312→    // Get XP earned this week
   313→    const { data: xpHistory } = await supabase
   314→      .from('xp_history')
   315→      .select('amount, reason, created_at')
   316→      .eq('user_id', userId)
   317→      .gte('created_at', weekStart.toISOString())
   318→      .lt('created_at', weekEndStr);
   319→
   320→    // Get communications (calls/SMS) this week
   321→    const { data: comms } = await supabase
   322→      .from('communications')
   323→      .select('type, metadata')
   324→      .gte('created_at', weekStart.toISOString())
   325→      .lt('created_at', weekEndStr);
   326→
   327→    // Calculate stats
   328→    const stats = {
   329→      callsScored: callScores?.length || 0,
   330→      avgCallScore: 0,
   331→      xpEarned: 0,
   332→      totalCalls: 0,
   333→      totalSMS: 0,
   334→      conversions: 0,
   335→    };
   336→
   337→    if (callScores && callScores.length > 0) {
   338→      stats.avgCallScore = Math.round(
   339→        callScores.reduce((sum, s) => sum + (s.overall_score || 0), 0) / callScores.length
   340→      );
   341→    }
   342→
   343→    if (xpHistory) {
   344→      stats.xpEarned = xpHistory.reduce((sum, x) => sum + (x.amount || 0), 0);
   345→    }
   346→
   347→    if (comms) {
   348→      stats.totalCalls = comms.filter(c => c.type?.includes('call')).length;
   349→      stats.totalSMS = comms.filter(c => c.type?.includes('sms')).length;
   350→      stats.conversions = comms.filter(c => c.metadata?.outcome === 'converted').length;
   351→    }
   352→
   353→    // Generate insights
   354→    const insights = [];
   355→    if (stats.avgCallScore >= 80) {
   356→      insights.push('Your call quality is excellent this week! Keep it up.');
   357→    } else if (stats.avgCallScore >= 60) {
   358→      insights.push('Good progress on call quality. Focus on your weak areas to break 80.');
   359→    } else if (stats.avgCallScore > 0) {
   360→      insights.push('Room for improvement on calls. Review the coaching tips.');
   361→    }
   362→
   363→    if (stats.xpEarned >= 500) {
   364→      insights.push(`Massive XP week! You earned ${stats.xpEarned} XP.`);
   365→    } else if (stats.xpEarned >= 200) {
   366→      insights.push(`Solid activity! ${stats.xpEarned} XP earned.`);
   367→    }
   368→
   369→    if (stats.conversions > 0) {
   370→      insights.push(`${stats.conversions} conversion${stats.conversions > 1 ? 's' : ''} this week. Great work!`);
   371→    }
   372→
   373→    // Generate recommendations
   374→    const recommendations = [];
   375→    if (stats.totalCalls < 20) {
   376→      recommendations.push('Aim for at least 20 calls per week to hit your targets.');
   377→    }
   378→    if (stats.avgCallScore < 70 && stats.callsScored > 0) {
   379→      recommendations.push('Review your lowest-scored calls to identify patterns.');
   380→    }
   381→    if (stats.totalSMS > stats.totalCalls * 3) {
   382→      recommendations.push('Balance your SMS with more calls for better conversions.');
   383→    }
   384→
   385→    const summary = {
   386→      stats,
   387→      insights,
   388→      recommendations,
   389→      weekStart: weekStart.toISOString().split('T')[0],
   390→    };
   391→
   392→    // Cache the summary if we have data
   393→    if (stats.callsScored > 0 || stats.xpEarned > 0 || stats.totalCalls > 0) {
   394→      await supabase.from('weekly_summaries').upsert({
   395→        user_id: userId,
   396→        week_start: weekStart.toISOString().split('T')[0],
   397→        summary: stats,
   398→        insights,
   399→        recommendations,
   400→      });
   401→    }
   402→
   403→    res.json({
   404→      success: true,
   405→      summary: {
   406→        ...summary,
   407→        summary: stats,
   408→      },
   409→      cached: false,
   410→    });
   411→  } catch (error) {
   412→    console.error('Error getting weekly summary:', error);
   413→    res.status(500).json({ success: false, error: error.message });
   414→  }
   415→});
   416→
   417→// ============================================
   418→// Get winning patterns (from analytics)
   419→// ============================================
   420→
   421→router.get('/patterns', async (req, res) => {
   422→  try {
   423→    const { limit = 5 } = req.query;
   424→
   425→    const { data, error } = await supabase
   426→      .from('winning_patterns')
   427→      .select('*')
   428→      .order('confidence', { ascending: false })
   429→      .limit(parseInt(limit));
   430→
   431→    if (error) throw error;
   432→
   433→    res.json({
   434→      success: true,
   435→      patterns: data || [],
   436→    });
   437→  } catch (error) {
   438→    console.error('Error fetching winning patterns:', error);
   439→    res.status(500).json({ success: false, error: error.message });
   440→  }
   441→});
   442→
   443→module.exports = router;
   444→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * AI Call Scoring API Routes
     3→ * GPT-powered call scoring endpoints
     4→ */
     5→
     6→const express = require('express');
     7→const router = express.Router();
     8→const aiCallScoring = require('../services/aiCallScoring');
     9→
    10→/**
    11→ * POST /api/call-scoring/score
    12→ * Score a call using AI
    13→ */
    14→router.post('/score', async (req, res) => {
    15→  try {
    16→    const {
    17→      communicationId,
    18→      leadId,
    19→      transcript,
    20→      leadName,
    21→      trade,
    22→      durationSeconds,
    23→      outcome
    24→    } = req.body;
    25→
    26→    if (!communicationId) {
    27→      return res.status(400).json({ error: 'communicationId is required' });
    28→    }
    29→
    30→    if (!transcript) {
    31→      return res.status(400).json({ error: 'transcript is required' });
    32→    }
    33→
    34→    const result = await aiCallScoring.scoreCall({
    35→      communicationId,
    36→      leadId,
    37→      userId: req.user?.id,
    38→      transcript,
    39→      leadName,
    40→      trade,
    41→      durationSeconds,
    42→      outcome
    43→    });
    44→
    45→    if (!result.success) {
    46→      return res.status(500).json({ error: result.error });
    47→    }
    48→
    49→    res.json(result.score);
    50→  } catch (error) {
    51→    console.error('Error in POST /score:', error);
    52→    res.status(500).json({ error: 'Failed to score call' });
    53→  }
    54→});
    55→
    56→/**
    57→ * GET /api/call-scoring/:communicationId
    58→ * Get score for a specific call
    59→ */
    60→router.get('/:communicationId', async (req, res) => {
    61→  try {
    62→    const { communicationId } = req.params;
    63→    const score = await aiCallScoring.getCallScore(communicationId);
    64→
    65→    if (!score) {
    66→      return res.status(404).json({ error: 'Score not found' });
    67→    }
    68→
    69→    res.json(score);
    70→  } catch (error) {
    71→    console.error('Error in GET /:communicationId:', error);
    72→    res.status(500).json({ error: 'Failed to get call score' });
    73→  }
    74→});
    75→
    76→/**
    77→ * GET /api/call-scoring/user/:userId
    78→ * Get all scores for a user
    79→ */
    80→router.get('/user/:userId', async (req, res) => {
    81→  try {
    82→    const { userId } = req.params;
    83→    const { limit, startDate, endDate } = req.query;
    84→
    85→    const scores = await aiCallScoring.getUserScores(userId, {
    86→      limit: parseInt(limit) || 20,
    87→      startDate,
    88→      endDate
    89→    });
    90→
    91→    res.json(scores);
    92→  } catch (error) {
    93→    console.error('Error in GET /user/:userId:', error);
    94→    res.status(500).json({ error: 'Failed to get user scores' });
    95→  }
    96→});
    97→
    98→/**
    99→ * GET /api/call-scoring/user/:userId/stats
   100→ * Get score statistics for a user
   101→ */
   102→router.get('/user/:userId/stats', async (req, res) => {
   103→  try {
   104→    const { userId } = req.params;
   105→    const stats = await aiCallScoring.getUserScoreStats(userId);
   106→
   107→    if (!stats) {
   108→      return res.json({
   109→        total_calls: 0,
   110→        avg_score: null,
   111→        avg_rapport: null,
   112→        avg_discovery: null,
   113→        avg_value_prop: null,
   114→        avg_objection: null,
   115→        avg_close: null,
   116→        avg_professionalism: null,
   117→        excellent_calls: 0,
   118→        poor_calls: 0
   119→      });
   120→    }
   121→
   122→    res.json(stats);
   123→  } catch (error) {
   124→    console.error('Error in GET /user/:userId/stats:', error);
   125→    res.status(500).json({ error: 'Failed to get user stats' });
   126→  }
   127→});
   128→
   129→/**
   130→ * GET /api/call-scoring/best
   131→ * Get best calls for learning
   132→ */
   133→router.get('/best', async (req, res) => {
   134→  try {
   135→    const { limit, days } = req.query;
   136→    const calls = await aiCallScoring.getBestCalls({
   137→      limit: parseInt(limit) || 10,
   138→      days: parseInt(days) || 30
   139→    });
   140→
   141→    res.json(calls);
   142→  } catch (error) {
   143→    console.error('Error in GET /best:', error);
   144→    res.status(500).json({ error: 'Failed to get best calls' });
   145→  }
   146→});
   147→
   148→/**
   149→ * GET /api/call-scoring/needs-coaching
   150→ * Get calls needing coaching review
   151→ */
   152→router.get('/needs-coaching', async (req, res) => {
   153→  try {
   154→    const { limit, threshold } = req.query;
   155→    const calls = await aiCallScoring.getCallsNeedingCoaching({
   156→      limit: parseInt(limit) || 10,
   157→      threshold: parseFloat(threshold) || 5
   158→    });
   159→
   160→    res.json(calls);
   161→  } catch (error) {
   162→    console.error('Error in GET /needs-coaching:', error);
   163→    res.status(500).json({ error: 'Failed to get calls needing coaching' });
   164→  }
   165→});
   166→
   167→/**
   168→ * GET /api/call-scoring/leaderboard
   169→ * Get team score leaderboard
   170→ */
   171→router.get('/leaderboard', async (req, res) => {
   172→  try {
   173→    const days = parseInt(req.query.days) || 7;
   174→    const leaderboard = await aiCallScoring.getScoreLeaderboard(days);
   175→
   176→    res.json(leaderboard);
   177→  } catch (error) {
   178→    console.error('Error in GET /leaderboard:', error);
   179→    res.status(500).json({ error: 'Failed to get leaderboard' });
   180→  }
   181→});
   182→
   183→module.exports = router;
   184→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Analytics API - Performance trends, winning patterns, and insights
     3→ */
     4→
     5→const express = require('express');
     6→const router = express.Router();
     7→const { supabase } = require('../config/database');
     8→
     9→// ============================================
    10→// Get performance trends over time
    11→// ============================================
    12→
    13→router.get('/trends', async (req, res) => {
    14→  try {
    15→    const userId = req.user?.id || 'default-user';
    16→    const { days = 30 } = req.query;
    17→
    18→    const startDate = new Date();
    19→    startDate.setDate(startDate.getDate() - parseInt(days));
    20→
    21→    // Get daily communication counts
    22→    const { data: comms, error: commsError } = await supabase
    23→      .from('communications')
    24→      .select('type, created_at, metadata')
    25→      .gte('created_at', startDate.toISOString())
    26→      .order('created_at', { ascending: true });
    27→
    28→    if (commsError) throw commsError;
    29→
    30→    // Aggregate by day
    31→    const dailyData = {};
    32→    const today = new Date();
    33→
    34→    // Initialize all days
    35→    for (let i = 0; i < parseInt(days); i++) {
    36→      const date = new Date(today);
    37→      date.setDate(date.getDate() - i);
    38→      const dateStr = date.toISOString().split('T')[0];
    39→      dailyData[dateStr] = {
    40→        date: dateStr,
    41→        calls: 0,
    42→        sms: 0,
    43→        conversions: 0,
    44→        connected: 0,
    45→      };
    46→    }
    47→
    48→    // Populate with actual data
    49→    (comms || []).forEach(comm => {
    50→      const dateStr = comm.created_at.split('T')[0];
    51→      if (dailyData[dateStr]) {
    52→        if (comm.type?.includes('call')) {
    53→          dailyData[dateStr].calls++;
    54→          const outcome = comm.outcome || comm.metadata?.outcome;
    55→          if (outcome === 'connected' ||
    56→              outcome === 'interested' ||
    57→              outcome === 'converted') {
    58→            dailyData[dateStr].connected++;
    59→          }
    60→          if (outcome === 'converted') {
    61→            dailyData[dateStr].conversions++;
    62→          }
    63→        }
    64→        if (comm.type?.includes('sms')) {
    65→          dailyData[dateStr].sms++;
    66→        }
    67→      }
    68→    });
    69→
    70→    // Convert to array sorted by date
    71→    const trends = Object.values(dailyData)
    72→      .sort((a, b) => a.date.localeCompare(b.date));
    73→
    74→    // Calculate summary stats
    75→    const totalCalls = trends.reduce((sum, d) => sum + d.calls, 0);
    76→    const totalSMS = trends.reduce((sum, d) => sum + d.sms, 0);
    77→    const totalConversions = trends.reduce((sum, d) => sum + d.conversions, 0);
    78→    const totalConnected = trends.reduce((sum, d) => sum + d.connected, 0);
    79→
    80→    const connectRate = totalCalls > 0 ? Math.round((totalConnected / totalCalls) * 100) : 0;
    81→    const conversionRate = totalConnected > 0 ? Math.round((totalConversions / totalConnected) * 100) : 0;
    82→
    83→    // Calculate week-over-week change
    84→    const midPoint = Math.floor(trends.length / 2);
    85→    const firstHalf = trends.slice(0, midPoint);
    86→    const secondHalf = trends.slice(midPoint);
    87→
    88→    const firstHalfCalls = firstHalf.reduce((sum, d) => sum + d.calls, 0);
    89→    const secondHalfCalls = secondHalf.reduce((sum, d) => sum + d.calls, 0);
    90→    const callsTrend = firstHalfCalls > 0
    91→      ? Math.round(((secondHalfCalls - firstHalfCalls) / firstHalfCalls) * 100)
    92→      : 0;
    93→
    94→    res.json({
    95→      success: true,
    96→      trends,
    97→      summary: {
    98→        totalCalls,
    99→        totalSMS,
   100→        totalConversions,
   101→        totalConnected,
   102→        connectRate,
   103→        conversionRate,
   104→        callsTrend,
   105→        avgCallsPerDay: Math.round(totalCalls / parseInt(days)),
   106→      },
   107→    });
   108→  } catch (error) {
   109→    console.error('Error fetching trends:', error);
   110→    res.status(500).json({ success: false, error: error.message });
   111→  }
   112→});
   113→
   114→// ============================================
   115→// Get winning patterns
   116→// ============================================
   117→
   118→router.get('/patterns', async (req, res) => {
   119→  try {
   120→    const { limit = 10 } = req.query;
   121→
   122→    // Get stored patterns
   123→    const { data: storedPatterns } = await supabase
   124→      .from('winning_patterns')
   125→      .select('*')
   126→      .order('confidence', { ascending: false })
   127→      .limit(parseInt(limit));
   128→
   129→    // Also generate some real-time patterns from data
   130→    const realTimePatterns = await generateRealTimePatterns();
   131→
   132→    // Merge and dedupe
   133→    const allPatterns = [...(storedPatterns || []), ...realTimePatterns]
   134→      .sort((a, b) => (b.confidence || 0) - (a.confidence || 0))
   135→      .slice(0, parseInt(limit));
   136→
   137→    res.json({
   138→      success: true,
   139→      patterns: allPatterns,
   140→    });
   141→  } catch (error) {
   142→    console.error('Error fetching patterns:', error);
   143→    res.status(500).json({ success: false, error: error.message });
   144→  }
   145→});
   146→
   147→// ============================================
   148→// Get conversion funnel stats
   149→// ============================================
   150→
   151→router.get('/funnel', async (req, res) => {
   152→  try {
   153→    const { days = 30 } = req.query;
   154→
   155→    const startDate = new Date();
   156→    startDate.setDate(startDate.getDate() - parseInt(days));
   157→
   158→    // Get leads by status
   159→    const { data: leads, error } = await supabase
   160→      .from('leads')
   161→      .select('status, created_at, last_contact_at')
   162→      .gte('created_at', startDate.toISOString());
   163→
   164→    if (error) throw error;
   165→
   166→    // Count by status
   167→    const statusCounts = {};
   168→    const statuses = ['new', 'contacted', 'engaged', 'qualified', 'converted', 'closed_lost'];
   169→
   170→    statuses.forEach(s => statusCounts[s] = 0);
   171→
   172→    (leads || []).forEach(lead => {
   173→      if (statusCounts.hasOwnProperty(lead.status)) {
   174→        statusCounts[lead.status]++;
   175→      } else {
   176→        statusCounts['new']++;
   177→      }
   178→    });
   179→
   180→    const total = leads?.length || 0;
   181→
   182→    const funnel = statuses.map((status, index) => {
   183→      const count = statusCounts[status];
   184→      const prevCount = index === 0 ? total : statusCounts[statuses[index - 1]];
   185→      const dropoff = prevCount > 0 ? Math.round(((prevCount - count) / prevCount) * 100) : 0;
   186→
   187→      return {
   188→        stage: status,
   189→        count,
   190→        percentage: total > 0 ? Math.round((count / total) * 100) : 0,
   191→        dropoff,
   192→      };
   193→    });
   194→
   195→    // Find biggest drop-off
   196→    const biggestDropoff = funnel
   197→      .filter(f => f.stage !== 'new' && f.stage !== 'closed_lost')
   198→      .sort((a, b) => b.dropoff - a.dropoff)[0];
   199→
   200→    res.json({
   201→      success: true,
   202→      funnel,
   203→      total,
   204→      biggestDropoff,
   205→    });
   206→  } catch (error) {
   207→    console.error('Error fetching funnel:', error);
   208→    res.status(500).json({ success: false, error: error.message });
   209→  }
   210→});
   211→
   212→// ============================================
   213→// Get best performing times
   214→// ============================================
   215→
   216→router.get('/best-times', async (req, res) => {
   217→  try {
   218→    const { days = 30 } = req.query;
   219→
   220→    const startDate = new Date();
   221→    startDate.setDate(startDate.getDate() - parseInt(days));
   222→
   223→    // Get calls with outcomes
   224→    const { data: calls, error } = await supabase
   225→      .from('communications')
   226→      .select('created_at, metadata')
   227→      .eq('type', 'call_outbound')
   228→      .gte('created_at', startDate.toISOString());
   229→
   230→    if (error) throw error;
   231→
   232→    // Analyze by hour and day of week
   233→    const hourStats = {};
   234→    const dayStats = {};
   235→    const days_names = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
   236→
   237→    // Initialize
   238→    for (let h = 8; h <= 18; h++) {
   239→      hourStats[h] = { total: 0, connected: 0, converted: 0 };
   240→    }
   241→    days_names.forEach(d => {
   242→      dayStats[d] = { total: 0, connected: 0, converted: 0 };
   243→    });
   244→
   245→    // Aggregate
   246→    (calls || []).forEach(call => {
   247→      const date = new Date(call.created_at);
   248→      const hour = date.getHours();
   249→      const day = days_names[date.getDay()];
   250→      const outcome = call.outcome || call.metadata?.outcome;
   251→
   252→      if (hourStats[hour]) {
   253→        hourStats[hour].total++;
   254→        if (['connected', 'interested', 'converted', 'callback_scheduled'].includes(outcome)) {
   255→          hourStats[hour].connected++;
   256→        }
   257→        if (outcome === 'converted') {
   258→          hourStats[hour].converted++;
   259→        }
   260→      }
   261→
   262→      if (dayStats[day]) {
   263→        dayStats[day].total++;
   264→        if (['connected', 'interested', 'converted', 'callback_scheduled'].includes(outcome)) {
   265→          dayStats[day].connected++;
   266→        }
   267→        if (outcome === 'converted') {
   268→          dayStats[day].converted++;
   269→        }
   270→      }
   271→    });
   272→
   273→    // Calculate rates
   274→    const hourRates = Object.entries(hourStats)
   275→      .map(([hour, stats]) => ({
   276→        hour: parseInt(hour),
   277→        total: stats.total,
   278→        connectRate: stats.total > 0 ? Math.round((stats.connected / stats.total) * 100) : 0,
   279→      }))
   280→      .filter(h => h.total >= 3) // Only show hours with enough data
   281→      .sort((a, b) => b.connectRate - a.connectRate);
   282→
   283→    const dayRates = Object.entries(dayStats)
   284→      .map(([day, stats]) => ({
   285→        day,
   286→        total: stats.total,
   287→        connectRate: stats.total > 0 ? Math.round((stats.connected / stats.total) * 100) : 0,
   288→      }))
   289→      .filter(d => d.total >= 3)
   290→      .sort((a, b) => b.connectRate - a.connectRate);
   291→
   292→    // Best times
   293→    const bestHour = hourRates[0];
   294→    const bestDay = dayRates[0];
   295→
   296→    res.json({
   297→      success: true,
   298→      hourStats: hourRates,
   299→      dayStats: dayRates,
   300→      bestTime: bestHour ? `${bestHour.hour}:00` : null,
   301→      bestDay: bestDay?.day || null,
   302→      insight: bestHour && bestDay
   303→        ? `Your best connect rate is ${bestDay.day}s around ${bestHour.hour}:00 (${bestHour.connectRate}% connect rate)`
   304→        : 'Make more calls to discover your best times',
   305→    });
   306→  } catch (error) {
   307→    console.error('Error fetching best times:', error);
   308→    res.status(500).json({ success: false, error: error.message });
   309→  }
   310→});
   311→
   312→// ============================================
   313→// Get lead source performance
   314→// ============================================
   315→
   316→router.get('/sources', async (req, res) => {
   317→  try {
   318→    const { days = 30 } = req.query;
   319→
   320→    const startDate = new Date();
   321→    startDate.setDate(startDate.getDate() - parseInt(days));
   322→
   323→    const { data: leads, error } = await supabase
   324→      .from('leads')
   325→      .select('source, status, score')
   326→      .gte('created_at', startDate.toISOString());
   327→
   328→    if (error) throw error;
   329→
   330→    // Aggregate by source
   331→    const sourceStats = {};
   332→
   333→    (leads || []).forEach(lead => {
   334→      const source = lead.source || 'unknown';
   335→      if (!sourceStats[source]) {
   336→        sourceStats[source] = { total: 0, converted: 0, avgScore: 0, scores: [] };
   337→      }
   338→      sourceStats[source].total++;
   339→      if (lead.status === 'converted') {
   340→        sourceStats[source].converted++;
   341→      }
   342→      if (lead.score) {
   343→        sourceStats[source].scores.push(lead.score);
   344→      }
   345→    });
   346→
   347→    // Calculate averages and rates
   348→    const sources = Object.entries(sourceStats)
   349→      .map(([source, stats]) => ({
   350→        source,
   351→        total: stats.total,
   352→        converted: stats.converted,
   353→        conversionRate: stats.total > 0 ? Math.round((stats.converted / stats.total) * 100) : 0,
   354→        avgScore: stats.scores.length > 0
   355→          ? Math.round(stats.scores.reduce((a, b) => a + b, 0) / stats.scores.length)
   356→          : 0,
   357→      }))
   358→      .sort((a, b) => b.conversionRate - a.conversionRate);
   359→
   360→    const bestSource = sources[0];
   361→
   362→    res.json({
   363→      success: true,
   364→      sources,
   365→      bestSource,
   366→      insight: bestSource && bestSource.conversionRate > 0
   367→        ? `${bestSource.source} has your highest conversion rate at ${bestSource.conversionRate}%`
   368→        : 'Track lead sources to identify your best channels',
   369→    });
   370→  } catch (error) {
   371→    console.error('Error fetching sources:', error);
   372→    res.status(500).json({ success: false, error: error.message });
   373→  }
   374→});
   375→
   376→// ============================================
   377→// Save a winning pattern
   378→// ============================================
   379→
   380→router.post('/patterns', async (req, res) => {
   381→  try {
   382→    const { patternType, description, data, confidence } = req.body;
   383→
   384→    if (!patternType || !description) {
   385→      return res.status(400).json({
   386→        success: false,
   387→        error: 'patternType and description are required',
   388→      });
   389→    }
   390→
   391→    const { data: pattern, error } = await supabase
   392→      .from('winning_patterns')
   393→      .insert({
   394→        pattern_type: patternType,
   395→        description,
   396→        data: data || {},
   397→        confidence: confidence || 0.5,
   398→        sample_count: 1,
   399→      })
   400→      .select()
   401→      .single();
   402→
   403→    if (error) throw error;
   404→
   405→    res.json({
   406→      success: true,
   407→      pattern,
   408→    });
   409→  } catch (error) {
   410→    console.error('Error saving pattern:', error);
   411→    res.status(500).json({ success: false, error: error.message });
   412→  }
   413→});
   414→
   415→// Helper function to generate real-time patterns from data
   416→async function generateRealTimePatterns() {
   417→  const patterns = [];
   418→
   419→  try {
   420→    // Get recent conversions
   421→    const { data: conversions } = await supabase
   422→      .from('leads')
   423→      .select(`
   424→        id,
   425→        type,
   426→        source,
   427→        trade,
   428→        score,
   429→        created_at,
   430→        last_contact_at,
   431→        communications (
   432→          type,
   433→          created_at,
   434→          metadata
   435→        )
   436→      `)
   437→      .eq('status', 'converted')
   438→      .order('last_contact_at', { ascending: false })
   439→      .limit(50);
   440→
   441→    if (!conversions || conversions.length < 5) {
   442→      return patterns;
   443→    }
   444→
   445→    // Analyze common traits
   446→    const trades = {};
   447→    const sources = {};
   448→    const types = {};
   449→    let avgTouchpoints = 0;
   450→
   451→    conversions.forEach(lead => {
   452→      if (lead.trade) {
   453→        trades[lead.trade] = (trades[lead.trade] || 0) + 1;
   454→      }
   455→      if (lead.source) {
   456→        sources[lead.source] = (sources[lead.source] || 0) + 1;
   457→      }
   458→      if (lead.type) {
   459→        types[lead.type] = (types[lead.type] || 0) + 1;
   460→      }
   461→      avgTouchpoints += lead.communications?.length || 0;
   462→    });
   463→
   464→    avgTouchpoints = Math.round(avgTouchpoints / conversions.length);
   465→
   466→    // Find most common trade
   467→    const topTrade = Object.entries(trades).sort((a, b) => b[1] - a[1])[0];
   468→    if (topTrade && topTrade[1] >= 3) {
   469→      patterns.push({
   470→        id: 'trade-pattern',
   471→        pattern_type: 'trade',
   472→        description: `${topTrade[0]} leads convert at a higher rate`,
   473→        confidence: Math.min(topTrade[1] / conversions.length, 0.9),
   474→        sample_count: topTrade[1],
   475→        data: { trade: topTrade[0], count: topTrade[1] },
   476→      });
   477→    }
   478→
   479→    // Find best source
   480→    const topSource = Object.entries(sources).sort((a, b) => b[1] - a[1])[0];
   481→    if (topSource && topSource[1] >= 3) {
   482→      patterns.push({
   483→        id: 'source-pattern',
   484→        pattern_type: 'source',
   485→        description: `${topSource[0]} is your best lead source`,
   486→        confidence: Math.min(topSource[1] / conversions.length, 0.9),
   487→        sample_count: topSource[1],
   488→        data: { source: topSource[0], count: topSource[1] },
   489→      });
   490→    }
   491→
   492→    // Touchpoint pattern
   493→    if (avgTouchpoints > 0) {
   494→      patterns.push({
   495→        id: 'touchpoint-pattern',
   496→        pattern_type: 'touchpoints',
   497→        description: `Conversions average ${avgTouchpoints} touchpoints before closing`,
   498→        confidence: 0.7,
   499→        sample_count: conversions.length,
   500→        data: { avgTouchpoints },
   501→      });
   502→    }
   503→
   504→  } catch (error) {
   505→    console.error('Error generating patterns:', error);
   506→  }
   507→
   508→  return patterns;
   509→}
   510→
   511→// ============================================
   512→// Web Vitals Collection
   513→// ============================================
   514→
   515→router.post('/vitals', async (req, res) => {
   516→  try {
   517→    const { metric_name, metric_value, rating, page_path, delta, id, navigationType } = req.body;
   518→
   519→    if (!metric_name || metric_value === undefined) {
   520→      return res.status(400).json({
   521→        success: false,
   522→        error: 'metric_name and metric_value are required'
   523→      });
   524→    }
   525→
   526→    const { error } = await supabase.from('web_vitals').insert({
   527→      metric_name,
   528→      metric_value,
   529→      rating: rating || null,
   530→      page_path: page_path || null,
   531→      delta: delta || null,
   532→      metric_id: id || null,
   533→      navigation_type: navigationType || null,
   534→      recorded_at: new Date().toISOString()
   535→    });
   536→
   537→    if (error && error.code === '42P01') {
   538→      // Table doesn't exist yet
   539→      return res.json({ success: true, note: 'Table not created yet' });
   540→    }
   541→
   542→    if (error) throw error;
   543→
   544→    res.json({ success: true });
   545→  } catch (error) {
   546→    console.error('Error storing web vital:', error);
   547→    res.status(500).json({ success: false, error: error.message });
   548→  }
   549→});
   550→
   551→// ============================================
   552→// Frontend Error Collection
   553→// ============================================
   554→
   555→router.post('/error', async (req, res) => {
   556→  try {
   557→    const { message, stack, page_path, url, user_agent, type, component_stack, filename, lineno, colno } = req.body;
   558→
   559→    if (!message) {
   560→      return res.status(400).json({
   561→        success: false,
   562→        error: 'message is required'
   563→      });
   564→    }
   565→
   566→    const { error } = await supabase.from('frontend_errors').insert({
   567→      message,
   568→      stack: stack || null,
   569→      page_path: page_path || null,
   570→      url: url || null,
   571→      user_agent: user_agent || null,
   572→      error_type: type || 'unknown',
   573→      component_stack: component_stack || null,
   574→      filename: filename || null,
   575→      lineno: lineno || null,
   576→      colno: colno || null,
   577→      created_at: new Date().toISOString()
   578→    });
   579→
   580→    if (error && error.code === '42P01') {
   581→      // Table doesn't exist yet
   582→      return res.json({ success: true, note: 'Table not created yet' });
   583→    }
   584→
   585→    if (error) throw error;
   586→
   587→    res.json({ success: true });
   588→  } catch (error) {
   589→    console.error('Error storing frontend error:', error);
   590→    res.status(500).json({ success: false, error: error.message });
   591→  }
   592→});
   593→
   594→// ============================================
   595→// Get Web Vitals Summary
   596→// ============================================
   597→
   598→router.get('/vitals/summary', async (req, res) => {
   599→  try {
   600→    const { hours = 24 } = req.query;
   601→    const since = new Date(Date.now() - parseInt(hours) * 60 * 60 * 1000).toISOString();
   602→
   603→    const { data, error } = await supabase
   604→      .from('web_vitals')
   605→      .select('metric_name, metric_value, rating')
   606→      .gte('recorded_at', since);
   607→
   608→    if (error && error.code !== '42P01') throw error;
   609→
   610→    // Aggregate by metric
   611→    const metrics = {};
   612→    const metricNames = ['LCP', 'INP', 'CLS', 'FCP', 'TTFB'];
   613→
   614→    metricNames.forEach(name => {
   615→      const values = (data || []).filter(d => d.metric_name === name).map(d => d.metric_value);
   616→      if (values.length > 0) {
   617→        const avg = values.reduce((sum, v) => sum + v, 0) / values.length;
   618→        const p75 = values.sort((a, b) => a - b)[Math.floor(values.length * 0.75)];
   619→        const ratings = (data || []).filter(d => d.metric_name === name);
   620→        const goodCount = ratings.filter(r => r.rating === 'good').length;
   621→
   622→        metrics[name] = {
   623→          avg: Math.round(avg * 100) / 100,
   624→          p75: Math.round(p75 * 100) / 100,
   625→          count: values.length,
   626→          goodRate: Math.round((goodCount / values.length) * 100)
   627→        };
   628→      }
   629→    });
   630→
   631→    res.json({
   632→      success: true,
   633→      metrics,
   634→      period: `${hours}h`
   635→    });
   636→  } catch (error) {
   637→    console.error('Error fetching vitals summary:', error);
   638→    res.status(500).json({ success: false, error: error.message });
   639→  }
   640→});
   641→
   642→module.exports = router;
   643→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Call Analytics API Routes
     3→ * Quality metrics, coaching alerts, leaderboard
     4→ */
     5→
     6→const express = require('express');
     7→const router = express.Router();
     8→const callAnalytics = require('../services/callAnalytics');
     9→
    10→/**
    11→ * POST /api/call-analytics/analyze
    12→ * Trigger analysis for a call
    13→ */
    14→router.post('/analyze', async (req, res) => {
    15→  try {
    16→    const { communicationId, leadId, transcript, transcriptWords, durationSeconds } = req.body;
    17→
    18→    if (!communicationId) {
    19→      return res.status(400).json({ error: 'communicationId is required' });
    20→    }
    21→
    22→    const result = await callAnalytics.analyzeCall({
    23→      communicationId,
    24→      leadId,
    25→      userId: req.user?.id,
    26→      transcript,
    27→      transcriptWords,
    28→      durationSeconds
    29→    });
    30→
    31→    if (!result.success) {
    32→      return res.status(500).json({ error: result.error });
    33→    }
    34→
    35→    res.json(result.analytics);
    36→  } catch (error) {
    37→    console.error('Error in POST /analyze:', error);
    38→    res.status(500).json({ error: 'Failed to analyze call' });
    39→  }
    40→});
    41→
    42→/**
    43→ * GET /api/call-analytics/:communicationId
    44→ * Get analytics for a specific call
    45→ */
    46→router.get('/:communicationId', async (req, res) => {
    47→  try {
    48→    const { communicationId } = req.params;
    49→    const analytics = await callAnalytics.getCallAnalytics(communicationId);
    50→
    51→    if (!analytics) {
    52→      return res.status(404).json({ error: 'Analytics not found' });
    53→    }
    54→
    55→    res.json(analytics);
    56→  } catch (error) {
    57→    console.error('Error in GET /:communicationId:', error);
    58→    res.status(500).json({ error: 'Failed to get call analytics' });
    59→  }
    60→});
    61→
    62→/**
    63→ * GET /api/call-analytics/lead/:leadId
    64→ * Get all call analytics for a lead
    65→ */
    66→router.get('/lead/:leadId', async (req, res) => {
    67→  try {
    68→    const { leadId } = req.params;
    69→    const analytics = await callAnalytics.getLeadCallAnalytics(leadId);
    70→
    71→    res.json(analytics);
    72→  } catch (error) {
    73→    console.error('Error in GET /lead/:leadId:', error);
    74→    res.status(500).json({ error: 'Failed to get lead call analytics' });
    75→  }
    76→});
    77→
    78→/**
    79→ * GET /api/call-analytics/trends
    80→ * Get quality trends over time
    81→ */
    82→router.get('/trends', async (req, res) => {
    83→  try {
    84→    const { userId, startDate, endDate } = req.query;
    85→    const result = await callAnalytics.getQualityTrends({
    86→      userId,
    87→      startDate,
    88→      endDate
    89→    });
    90→
    91→    if (!result.success) {
    92→      return res.status(500).json({ error: result.error });
    93→    }
    94→
    95→    res.json(result.trends);
    96→  } catch (error) {
    97→    console.error('Error in GET /trends:', error);
    98→    res.status(500).json({ error: 'Failed to get quality trends' });
    99→  }
   100→});
   101→
   102→/**
   103→ * GET /api/call-analytics/leaderboard
   104→ * Get quality score leaderboard
   105→ */
   106→router.get('/leaderboard', async (req, res) => {
   107→  try {
   108→    const days = parseInt(req.query.days) || 7;
   109→    const leaderboard = await callAnalytics.getQualityLeaderboard(days);
   110→
   111→    res.json(leaderboard);
   112→  } catch (error) {
   113→    console.error('Error in GET /leaderboard:', error);
   114→    res.status(500).json({ error: 'Failed to get leaderboard' });
   115→  }
   116→});
   117→
   118→/**
   119→ * GET /api/call-analytics/coaching/alerts
   120→ * Get coaching alerts (calls needing review)
   121→ */
   122→router.get('/coaching/alerts', async (req, res) => {
   123→  try {
   124→    const { userId, limit, includeReviewed } = req.query;
   125→    const alerts = await callAnalytics.getCoachingAlerts({
   126→      userId,
   127→      limit: parseInt(limit) || 20,
   128→      includeReviewed: includeReviewed === 'true'
   129→    });
   130→
   131→    res.json(alerts);
   132→  } catch (error) {
   133→    console.error('Error in GET /coaching/alerts:', error);
   134→    res.status(500).json({ error: 'Failed to get coaching alerts' });
   135→  }
   136→});
   137→
   138→/**
   139→ * POST /api/call-analytics/coaching/alerts/:id/review
   140→ * Mark a coaching alert as reviewed
   141→ */
   142→router.post('/coaching/alerts/:id/review', async (req, res) => {
   143→  try {
   144→    const { id } = req.params;
   145→    const { notes } = req.body;
   146→
   147→    const result = await callAnalytics.reviewCoachingAlert(
   148→      id,
   149→      req.user?.id,
   150→      notes
   151→    );
   152→
   153→    if (!result.success) {
   154→      return res.status(500).json({ error: result.error });
   155→    }
   156→
   157→    res.json({ success: true });
   158→  } catch (error) {
   159→    console.error('Error in POST /coaching/alerts/:id/review:', error);
   160→    res.status(500).json({ error: 'Failed to review alert' });
   161→  }
   162→});
   163→
   164→module.exports = router;
   165→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>