     1→/**
     2→ * Stage Diagnosis Service
     3→ * Detects patterns, generates scripts, and recommends actions for stuck leads
     4→ */
     5→
     6→const { generateChatCompletion } = require('./openai');
     7→const logger = require('../utils/logger');
     8→
     9→// Stage definitions
    10→// Workers need Stripe to receive payments
    11→const WORKER_STAGES = ['signed_up', 'profile_complete', 'stripe_ready', 'job_ready', 'applied', 'working', 'proven'];
    12→// Contractors pay with credit/debit card (no Stripe onboarding)
    13→const CONTRACTOR_STAGES = ['signed_up', 'profile_complete', 'job_posted', 'hired', 'active'];
    14→
    15→// Stage labels
    16→const STAGE_LABELS = {
    17→  signed_up: 'Signed Up',
    18→  profile_complete: 'Profile Complete',
    19→  job_ready: 'Job Ready',
    20→  stripe_ready: 'Stripe Ready',
    21→  job_posted: 'Job Posted',
    22→  applied: 'Applied',
    23→  working: 'Working',
    24→  hired: 'Hired',
    25→  proven: 'Proven',
    26→  active: 'Active',
    27→};
    28→
    29→// Common blockers by stage
    30→const STAGE_BLOCKERS = {
    31→  signed_up: {
    32→    worker: ['Unsure how the platform works', 'Waiting to see job listings first', 'Profile feels like too much effort'],
    33→    contractor: ['Unsure how the platform works', 'Just browsing options', 'Comparing with agencies'],
    34→  },
    35→  profile_complete: {
    36→    worker: ['Hesitant about Stripe setup', 'Concerns about giving bank details', 'Waiting to see if worth it'],
    37→    contractor: ['Not ready to post a job yet', 'Still evaluating platform', 'Waiting for a project to start'],
    38→  },
    39→  stripe_ready: {
    40→    worker: ['Missing required documents (insurance, white card)', 'Unsure what documents needed', 'Procrastinating on upload'],
    41→  },
    42→  job_ready: {
    43→    worker: ['Unsure how to search/apply', 'Waiting for "perfect" job', 'App navigation confusion', 'Location/travel concerns'],
    44→  },
    45→  job_posted: {
    46→    contractor: ['Not getting applicants', 'Job posting needs improvement', 'Unsure how to evaluate workers', 'Price expectations mismatch'],
    47→  },
    48→  applied: {
    49→    worker: ['Waiting to hear back', 'Unsure about follow-up etiquette', 'Applied to wrong jobs', 'Interview anxiety'],
    50→  },
    51→  hired: {
    52→    contractor: ['Onboarding the worker', 'Payment process questions', 'Setting expectations', 'First job jitters'],
    53→  },
    54→  working: {
    55→    worker: ['Job issues/disputes', 'Payment concerns', 'Looking for next job', 'Quality concerns'],
    56→  },
    57→};
    58→
    59→// Stage-specific actions
    60→const STAGE_ACTIONS = {
    61→  signed_up: {
    62→    worker: { action: 'Help Complete Profile', sms: 'walk through profile setup' },
    63→    contractor: { action: 'Help Complete Profile', sms: 'walk through profile setup' },
    64→  },
    65→  profile_complete: {
    66→    worker: { action: 'Walk Through Stripe Setup', sms: 'guide through payment setup to get paid' },
    67→    contractor: { action: 'Help Post First Job', sms: 'help post your first job' },
    68→  },
    69→  stripe_ready: {
    70→    worker: { action: 'Explain Document Requirements', sms: 'explain what documents are needed' },
    71→  },
    72→  job_ready: {
    73→    worker: { action: 'Help Find First Job', sms: 'show how to find and apply to jobs' },
    74→  },
    75→  job_posted: {
    76→    contractor: { action: 'Check In On Applicants', sms: 'help review applicants' },
    77→  },
    78→  applied: {
    79→    worker: { action: 'Follow Up On Applications', sms: 'check on application status' },
    80→  },
    81→  hired: {
    82→    contractor: { action: 'Ensure First Job Success', sms: 'help with worker onboarding' },
    83→  },
    84→  working: {
    85→    worker: { action: 'Check In On Job', sms: 'see how the job is going' },
    86→  },
    87→};
    88→
    89→/**
    90→ * Get stages array for lead type
    91→ */
    92→function getStagesForType(leadType) {
    93→  return leadType === 'worker' ? WORKER_STAGES : CONTRACTOR_STAGES;
    94→}
    95→
    96→/**
    97→ * Get stage index and progress percentage
    98→ */
    99→function getStageProgress(stage, leadType) {
   100→  const stages = getStagesForType(leadType);
   101→  const index = stages.indexOf(stage);
   102→  if (index === -1) return { index: 0, percentage: 0, total: stages.length };
   103→
   104→  return {
   105→    index,
   106→    percentage: Math.round(((index + 1) / stages.length) * 100),
   107→    total: stages.length,
   108→    isComplete: index === stages.length - 1,
   109→  };
   110→}
   111→
   112→/**
   113→ * Detect behavior pattern based on login data and stage tenure
   114→ */
   115→function detectPattern(platformData, daysInStage) {
   116→  const daysSinceLogin = platformData?.last_login
   117→    ? Math.floor((Date.now() - new Date(platformData.last_login).getTime()) / (1000 * 60 * 60 * 24))
   118→    : null;
   119→
   120→  const loginCount30d = platformData?.login_count_30d || 0;
   121→  const isStuck = daysInStage >= 7;
   122→
   123→  // Pattern: FORGOT - No recent login, low activity
   124→  if (daysSinceLogin !== null && daysSinceLogin > 7 && loginCount30d < 3) {
   125→    return {
   126→      pattern: 'forgot',
   127→      confidence: Math.min(0.95, 0.7 + (daysSinceLogin / 30) * 0.25),
   128→      evidence: `No login for ${daysSinceLogin} days, only ${loginCount30d} logins in 30 days`,
   129→      emoji: '😴',
   130→      label: 'Likely Forgot',
   131→      recommendation: 'Send friendly reminder SMS, then call if no response',
   132→    };
   133→  }
   134→
   135→  // Pattern: AVOIDING - Recent logins but no progress
   136→  if (daysSinceLogin !== null && daysSinceLogin < 3 && isStuck) {
   137→    return {
   138→      pattern: 'avoiding',
   139→      confidence: Math.min(0.9, 0.6 + (daysInStage / 20) * 0.3),
   140→      evidence: `Logged in ${daysSinceLogin} days ago but stuck for ${daysInStage} days`,
   141→      emoji: '🙈',
   142→      label: 'Possibly Avoiding',
   143→      recommendation: 'Call to understand blockers - there\'s a specific concern',
   144→    };
   145→  }
   146→
   147→  // Pattern: CONFUSED - High activity but no progress
   148→  if (loginCount30d > 5 && isStuck) {
   149→    return {
   150→      pattern: 'confused',
   151→      confidence: 0.8,
   152→      evidence: `${loginCount30d} logins in 30 days but stuck for ${daysInStage} days`,
   153→      emoji: '🤔',
   154→      label: 'Seems Confused',
   155→      recommendation: 'Call to walk through the next step - they want to progress',
   156→    };
   157→  }
   158→
   159→  // Pattern: BLOCKED - Default/external blocker
   160→  if (isStuck) {
   161→    return {
   162→      pattern: 'blocked',
   163→      confidence: 0.7,
   164→      evidence: `Stuck for ${daysInStage} days, unclear pattern`,
   165→      emoji: '🚧',
   166→      label: 'May Be Blocked',
   167→      recommendation: 'Call to discover the specific blocker',
   168→    };
   169→  }
   170→
   171→  // Not stuck
   172→  return {
   173→    pattern: 'progressing',
   174→    confidence: 0.9,
   175→    evidence: `${daysInStage} days in stage, normal progression`,
   176→    emoji: '✅',
   177→    label: 'Progressing',
   178→    recommendation: 'No action needed - check back if they stall',
   179→  };
   180→}
   181→
   182→/**
   183→ * Get likely blocker for stage and lead type
   184→ */
   185→function getLikelyBlocker(stage, leadType, pattern) {
   186→  const blockers = STAGE_BLOCKERS[stage]?.[leadType] || STAGE_BLOCKERS[stage]?.contractor || [];
   187→
   188→  // Weight blockers based on pattern
   189→  if (pattern === 'forgot') {
   190→    return blockers[0] || 'Lost track or got busy';
   191→  }
   192→  if (pattern === 'avoiding') {
   193→    return blockers[1] || blockers[0] || 'Specific concern they haven\'t voiced';
   194→  }
   195→  if (pattern === 'confused') {
   196→    return blockers[2] || blockers[0] || 'Unclear on next steps';
   197→  }
   198→
   199→  return blockers[0] || 'Unknown blocker';
   200→}
   201→
   202→/**
   203→ * Get recommended action for stage
   204→ */
   205→function getRecommendedAction(stage, leadType) {
   206→  const actions = STAGE_ACTIONS[stage];
   207→  if (!actions) {
   208→    return { action: 'Check In', sms: 'see how things are going' };
   209→  }
   210→  return actions[leadType] || actions.contractor || actions.worker;
   211→}
   212→
   213→/**
   214→ * Generate personalized script using AI
   215→ */
   216→async function generateScript(lead, stage, pattern, blocker, leadType) {
   217→  const stageLabel = STAGE_LABELS[stage] || stage;
   218→  const action = getRecommendedAction(stage, leadType);
   219→
   220→  const prompt = `You are a sales coach for RateRight, Australia's worker marketplace.
   221→
   222→Generate a natural, conversational phone script for this situation:
   223→
   224→LEAD: ${lead.first_name} ${lead.last_name || ''}
   225→COMPANY: ${lead.company || 'Unknown'}
   226→TYPE: ${leadType} (${leadType === 'worker' ? 'looking for work' : 'looking to hire workers'})
   227→CURRENT STAGE: ${stageLabel}
   228→DAYS STUCK: ${lead.daysInStage || 0}
   229→PATTERN: ${pattern.label} - ${pattern.evidence}
   230→LIKELY BLOCKER: ${blocker}
   231→
   232→RATERIGHT VALUE PROPS:
   233→- Direct connection (no agency middleman)
   234→- 9.9% fee vs 30-40% agency margins
   235→- Same-day matching for urgent jobs
   236→- Rating system for reliability
   237→- No lock-in contracts
   238→
   239→Generate a brief, natural opening line and 2-3 key talking points.
   240→The goal is to ${action.action.toLowerCase()}.
   241→
   242→Respond in JSON:
   243→{
   244→  "opening": "Natural opening line (15-25 words, sounds human not scripted)",
   245→  "talkingPoints": ["Point 1", "Point 2", "Point 3"],
   246→  "objectionHandler": "If they push back, say this...",
   247→  "closeAction": "Specific ask to end the call"
   248→}`;
   249→
   250→  try {
   251→    const result = await generateChatCompletion([{ role: 'user', content: prompt }], {
   252→      model: 'gpt-4o-mini',
   253→      temperature: 0.7,
   254→      response_format: { type: 'json_object' },
   255→    });
   256→
   257→    return JSON.parse(result);
   258→  } catch (error) {
   259→    logger.error('Failed to generate script', { error: error.message });
   260→
   261→    // Fallback script
   262→    return {
   263→      opening: `Hey ${lead.first_name}, it's [Name] from RateRight. Got a quick minute?`,
   264→      talkingPoints: [
   265→        `I noticed you signed up but haven't ${action.sms} yet`,
   266→        'Is there anything holding you back? Happy to help',
   267→        'Most people find it takes just a few minutes once they start',
   268→      ],
   269→      objectionHandler: 'Totally understand. Would it help if I walked you through it right now?',
   270→      closeAction: `Let's ${action.sms} together - I'll stay on the line`,
   271→    };
   272→  }
   273→}
   274→
   275→/**
   276→ * Generate SMS template for the stage
   277→ */
   278→function generateSMSTemplate(lead, stage, pattern, leadType) {
   279→  const firstName = lead.first_name || 'there';
   280→  const action = getRecommendedAction(stage, leadType);
   281→
   282→  const templates = {
   283→    forgot: `Hi ${firstName}! Just checking in from RateRight. Need any help getting set up? Happy to ${action.sms} if useful!`,
   284→    avoiding: `Hey ${firstName}, noticed you've been checking out RateRight. Any questions I can answer? Sometimes a quick chat helps.`,
   285→    confused: `Hi ${firstName}! Saw you've been active on RateRight. Want me to ${action.sms}? Takes 2 mins on a call.`,
   286→    blocked: `Hey ${firstName}, following up from RateRight. Is there anything blocking you from moving forward? Here to help!`,
   287→    progressing: `Hi ${firstName}! Just a quick check-in. How's everything going with RateRight?`,
   288→  };
   289→
   290→  return templates[pattern.pattern] || templates.progressing;
   291→}
   292→
   293→/**
   294→ * Main diagnosis function
   295→ */
   296→async function diagnose(lead, platformData, communications = []) {
   297→  const leadType = lead.metadata?.lead_type || 'contractor';
   298→  const stage = lead.platform_stage || 'signed_up';
   299→  const daysInStage = lead.daysInStage || 0;
   300→
   301→  // Detect pattern
   302→  const pattern = detectPattern(platformData, daysInStage);
   303→
   304→  // Get likely blocker
   305→  const blocker = getLikelyBlocker(stage, leadType, pattern.pattern);
   306→
   307→  // Get recommended action
   308→  const action = getRecommendedAction(stage, leadType);
   309→
   310→  // Generate personalized script
   311→  const script = await generateScript(
   312→    { ...lead, daysInStage },
   313→    stage,
   314→    pattern,
   315→    blocker,
   316→    leadType
   317→  );
   318→
   319→  // Generate SMS template
   320→  const smsTemplate = generateSMSTemplate(lead, stage, pattern, leadType);
   321→
   322→  // Get stage progress
   323→  const progress = getStageProgress(stage, leadType);
   324→
   325→  return {
   326→    // Pattern info
   327→    pattern: pattern.pattern,
   328→    patternLabel: pattern.label,
   329→    patternEmoji: pattern.emoji,
   330→    patternConfidence: pattern.confidence,
   331→    patternEvidence: pattern.evidence,
   332→    patternRecommendation: pattern.recommendation,
   333→
   334→    // Blocker info
   335→    likelyBlocker: blocker,
   336→
   337→    // Script
   338→    script: script.opening,
   339→    talkingPoints: script.talkingPoints,
   340→    objectionHandler: script.objectionHandler,
   341→    closeAction: script.closeAction,
   342→
   343→    // SMS
   344→    smsTemplate,
   345→
   346→    // Recommended action
   347→    recommendedAction: pattern.pattern === 'forgot' ? 'sms' : 'call',
   348→    actionLabel: action.action,
   349→    actionDescription: action.sms,
   350→
   351→    // Stage progress
   352→    stageProgress: progress,
   353→    stages: getStagesForType(leadType).map((s, i) => ({
   354→      stage: s,
   355→      label: STAGE_LABELS[s],
   356→      status: i < progress.index ? 'completed' : i === progress.index ? 'current' : 'pending',
   357→    })),
   358→  };
   359→}
   360→
   361→/**
   362→ * Get stage context for Intel Brief
   363→ */
   364→function getStageContext(stage, leadType) {
   365→  const stageLabel = STAGE_LABELS[stage] || stage;
   366→  const blockers = STAGE_BLOCKERS[stage]?.[leadType] || STAGE_BLOCKERS[stage]?.contractor || [];
   367→  const action = getRecommendedAction(stage, leadType);
   368→
   369→  return {
   370→    stageMeaning: getStageDescription(stage, leadType),
   371→    commonBlockers: blockers,
   372→    nextAction: action.action,
   373→    nextActionDetail: action.sms,
   374→  };
   375→}
   376→
   377→/**
   378→ * Get description of what stage means
   379→ */
   380→function getStageDescription(stage, leadType) {
   381→  const descriptions = {
   382→    signed_up: {
   383→      worker: 'They\'ve created an account but haven\'t completed their profile yet.',
   384→      contractor: 'They\'ve created an account but haven\'t completed their profile yet.',
   385→    },
   386→    profile_complete: {
   387→      worker: 'Profile is done but they\'re missing required documents (insurance/white card).',
   388→      contractor: 'Profile is done but they haven\'t connected Stripe for payments.',
   389→    },
   390→    stripe_ready: {
   391→      contractor: 'Stripe is set up but they haven\'t posted their first job yet.',
   392→    },
   393→    job_ready: {
   394→      worker: 'They have all documents and are ready to work, but haven\'t applied to any jobs.',
   395→    },
   396→    job_posted: {
   397→      contractor: 'They\'ve posted a job and are waiting for applicants or reviewing candidates.',
   398→    },
   399→    applied: {
   400→      worker: 'They\'ve applied to jobs and are waiting to hear back.',
   401→    },
   402→    hired: {
   403→      contractor: 'They\'ve hired a worker and are starting their first job.',
   404→    },
   405→    working: {
   406→      worker: 'They\'re currently working a job.',
   407→    },
   408→    proven: {
   409→      worker: 'They\'ve completed jobs and have reviews - a proven reliable worker.',
   410→    },
   411→    active: {
   412→      contractor: 'They\'ve completed jobs successfully - an active platform user.',
   413→    },
   414→  };
   415→
   416→  return descriptions[stage]?.[leadType] || descriptions[stage]?.contractor || 'Unknown stage';
   417→}
   418→
   419→module.exports = {
   420→  diagnose,
   421→  detectPattern,
   422→  getStageProgress,
   423→  getStageContext,
   424→  getStagesForType,
   425→  getLikelyBlocker,
   426→  getRecommendedAction,
   427→  generateSMSTemplate,
   428→  WORKER_STAGES,
   429→  CONTRACTOR_STAGES,
   430→  STAGE_LABELS,
   431→  STAGE_BLOCKERS,
   432→  STAGE_ACTIONS,
   433→};
   434→

<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→ * Stage Calculator Service
     3→ * Calculates lifecycle stages for workers and contractors based on platform data
     4→ */
     5→
     6→const { supabase } = require('../config/database');
     7→const logger = require('../utils/logger');
     8→
     9→// Worker stages in order of progression
    10→// Workers need Stripe to receive payments
    11→const WORKER_STAGES = [
    12→  'not_signed_up',
    13→  'signed_up',
    14→  'profile_complete',
    15→  'stripe_ready',    // Workers need Stripe to get paid
    16→  'job_ready',
    17→  'applied',
    18→  'working',
    19→  'proven'
    20→];
    21→
    22→// Contractor stages in order of progression
    23→// Contractors just pay with credit/debit card (no Stripe onboarding)
    24→const CONTRACTOR_STAGES = [
    25→  'not_signed_up',
    26→  'signed_up',
    27→  'profile_complete',
    28→  'job_posted',
    29→  'hired',
    30→  'active'
    31→];
    32→
    33→// Stage display names
    34→const STAGE_LABELS = {
    35→  not_signed_up: 'Not Signed Up',
    36→  signed_up: 'Signed Up',
    37→  profile_complete: 'Profile Complete',
    38→  job_ready: 'Job Ready',
    39→  applied: 'Applied',
    40→  working: 'Working',
    41→  proven: 'Proven',
    42→  stripe_ready: 'Stripe Ready',
    43→  job_posted: 'Job Posted',
    44→  hired: 'Hired',
    45→  active: 'Active'
    46→};
    47→
    48→/**
    49→ * Calculate worker lifecycle stage
    50→ * Workers need Stripe to receive payments for their work
    51→ * @param {object} platformData - All 35 fields from platform API
    52→ * @param {array} contracts - User's contracts from platform API
    53→ * @returns {string} Stage name
    54→ */
    55→function calculateWorkerStage(platformData, contracts = []) {
    56→  if (!platformData) {
    57→    return 'not_signed_up';
    58→  }
    59→
    60→  // Check for proven (highest stage)
    61→  if (platformData.jobs_completed > 0 && platformData.total_reviews > 0) {
    62→    return 'proven';
    63→  }
    64→
    65→  // Check for working (has active contract)
    66→  const activeContracts = contracts.filter(c =>
    67→    c.status === 'active' || c.status === 'in_progress'
    68→  );
    69→  if (activeContracts.length > 0) {
    70→    return 'working';
    71→  }
    72→
    73→  // Check for applied (has any contracts, even completed)
    74→  if (contracts.length > 0 || platformData.jobs_completed > 0) {
    75→    return 'applied';
    76→  }
    77→
    78→  // Check for job_ready (has insurance or white card AND Stripe ready)
    79→  const hasInsurance = platformData.public_liability_insurance ||
    80→                       platformData.workers_comp_insurance;
    81→  const hasWhiteCard = platformData.white_card_number &&
    82→                       platformData.white_card_expiry;
    83→  if ((hasInsurance || hasWhiteCard) && platformData.stripe_onboarding_complete) {
    84→    return 'job_ready';
    85→  }
    86→
    87→  // Check for stripe_ready (Stripe set up but no docs yet)
    88→  if (platformData.stripe_onboarding_complete) {
    89→    return 'stripe_ready';
    90→  }
    91→
    92→  // Check for profile_complete (has primary trade)
    93→  if (platformData.primary_trade) {
    94→    return 'profile_complete';
    95→  }
    96→
    97→  // Has account but not complete
    98→  return 'signed_up';
    99→}
   100→
   101→/**
   102→ * Calculate contractor lifecycle stage
   103→ * Contractors pay with credit/debit card - no Stripe onboarding needed
   104→ * @param {object} platformData - All 35 fields from platform API
   105→ * @param {array} jobs - User's jobs from platform API
   106→ * @param {array} contracts - User's contracts from platform API
   107→ * @returns {string} Stage name
   108→ */
   109→function calculateContractorStage(platformData, jobs = [], contracts = []) {
   110→  if (!platformData) {
   111→    return 'not_signed_up';
   112→  }
   113→
   114→  // Check for active (has completed jobs)
   115→  if (platformData.jobs_completed > 0) {
   116→    return 'active';
   117→  }
   118→
   119→  // Check for hired (has ANY contracts - means they've hired someone)
   120→  // Be lenient with status - any contract means they've hired
   121→  if (contracts.length > 0) {
   122→    return 'hired';
   123→  }
   124→
   125→  // Check for job_posted (has ANY jobs)
   126→  // Be lenient - any job means they've posted
   127→  if (jobs.length > 0) {
   128→    return 'job_posted';
   129→  }
   130→
   131→  // Check for profile_complete (has company or ABN)
   132→  // No Stripe needed - contractors pay with card
   133→  if (platformData.business_name || platformData.abn_number || platformData.company) {
   134→    return 'profile_complete';
   135→  }
   136→
   137→  // Has account but not complete
   138→  return 'signed_up';
   139→}
   140→
   141→/**
   142→ * Calculate health score (0-100)
   143→ * @param {object} platformData - Platform API data
   144→ * @returns {number} Health score
   145→ */
   146→function calculateHealthScore(platformData) {
   147→  if (!platformData) return 0;
   148→
   149→  let score = 0;
   150→
   151→  // Recency (40 points max)
   152→  if (platformData.last_login) {
   153→    const lastLogin = new Date(platformData.last_login);
   154→    const daysSince = Math.floor((Date.now() - lastLogin.getTime()) / (1000 * 60 * 60 * 24));
   155→
   156→    if (daysSince <= 1) score += 40;
   157→    else if (daysSince <= 7) score += 30;
   158→    else if (daysSince <= 30) score += 15;
   159→    else if (daysSince <= 90) score += 5;
   160→  }
   161→
   162→  // Activity (30 points max)
   163→  const jobsCompleted = platformData.jobs_completed || 0;
   164→  score += Math.min(jobsCompleted * 10, 30);
   165→
   166→  // Payment readiness (20 points max)
   167→  if (platformData.stripe_onboarding_complete) score += 10;
   168→  if (platformData.stripe_payouts_enabled) score += 10;
   169→
   170→  // Engagement (10 points max)
   171→  const responseRate = platformData.response_rate || 0;
   172→  score += Math.min(Math.floor(responseRate / 10), 10);
   173→
   174→  return Math.min(score, 100);
   175→}
   176→
   177→/**
   178→ * Get stage index for comparison
   179→ */
   180→function getStageIndex(stage, isWorker) {
   181→  const stages = isWorker ? WORKER_STAGES : CONTRACTOR_STAGES;
   182→  return stages.indexOf(stage);
   183→}
   184→
   185→/**
   186→ * Check if stage has progressed
   187→ */
   188→function hasStageProgressed(oldStage, newStage, isWorker) {
   189→  const oldIndex = getStageIndex(oldStage, isWorker);
   190→  const newIndex = getStageIndex(newStage, isWorker);
   191→  return newIndex > oldIndex;
   192→}
   193→
   194→/**
   195→ * Log stage transition to database and trigger auto-enrollment
   196→ */
   197→async function logStageTransition(leadId, fromStage, toStage, triggeredBy = 'platform_sync', metadata = {}) {
   198→  if (!supabase) return;
   199→
   200→  try {
   201→    await supabase
   202→      .from('stage_transitions')
   203→      .insert({
   204→        lead_id: leadId,
   205→        from_stage: fromStage,
   206→        to_stage: toStage,
   207→        triggered_by: triggeredBy,
   208→        metadata
   209→      });
   210→
   211→    logger.info('Stage transition logged', { leadId, fromStage, toStage });
   212→
   213→    // Trigger auto-enrollment in stage-appropriate sequence
   214→    await handleSequenceAutoEnrollment(leadId, fromStage, toStage, metadata.lead_type);
   215→
   216→  } catch (error) {
   217→    logger.error('Failed to log stage transition', { error: error.message, leadId });
   218→  }
   219→}
   220→
   221→/**
   222→ * Handle auto-enrollment when stage changes
   223→ * - Pauses any active enrollments for old stage
   224→ * - Enrolls in sequence for new stage (if exists)
   225→ * - Respects lead's sequence_opted_out flag
   226→ */
   227→async function handleSequenceAutoEnrollment(leadId, fromStage, toStage, leadType) {
   228→  if (!supabase) return;
   229→
   230→  try {
   231→    // 1. Check if lead has opted out of sequences
   232→    const { data: lead, error: leadError } = await supabase
   233→      .from('leads')
   234→      .select('id, type, sequence_opted_out')
   235→      .eq('id', leadId)
   236→      .single();
   237→
   238→    if (leadError || !lead) {
   239→      logger.warn('Lead not found for sequence enrollment', { leadId });
   240→      return;
   241→    }
   242→
   243→    if (lead.sequence_opted_out) {
   244→      logger.info('Lead opted out of sequences', { leadId });
   245→      return;
   246→    }
   247→
   248→    // Determine lead type from lead data if not provided
   249→    const actualLeadType = leadType || lead.type || 'worker';
   250→
   251→    // 2. Pause any active enrollments (stage changed means old sequence is no longer relevant)
   252→    const { data: activeEnrollments, error: enrollError } = await supabase
   253→      .from('sequence_enrollments')
   254→      .select('id, sequence:sequences(stage)')
   255→      .eq('lead_id', leadId)
   256→      .eq('status', 'active');
   257→
   258→    if (!enrollError && activeEnrollments?.length > 0) {
   259→      await supabase
   260→        .from('sequence_enrollments')
   261→        .update({
   262→          status: 'paused',
   263→          pause_reason: 'stage_changed',
   264→          paused_at: new Date().toISOString()
   265→        })
   266→        .eq('lead_id', leadId)
   267→        .eq('status', 'active');
   268→
   269→      logger.info('Paused active enrollments due to stage change', {
   270→        leadId,
   271→        count: activeEnrollments.length
   272→      });
   273→    }
   274→
   275→    // 3. Find sequence for new stage + lead type
   276→    const { data: sequence, error: seqError } = await supabase
   277→      .from('sequences')
   278→      .select('id, name')
   279→      .eq('stage', toStage)
   280→      .eq('lead_type', actualLeadType)
   281→      .eq('is_active', true)
   282→      .single();
   283→
   284→    if (seqError || !sequence) {
   285→      // No sequence for this stage - that's fine, not all stages have sequences
   286→      logger.debug('No sequence found for stage', { toStage, leadType: actualLeadType });
   287→      return;
   288→    }
   289→
   290→    // 4. Check if already enrolled in this sequence recently (within 30 days)
   291→    const thirtyDaysAgo = new Date();
   292→    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
   293→
   294→    const { data: existingEnrollment } = await supabase
   295→      .from('sequence_enrollments')
   296→      .select('id')
   297→      .eq('sequence_id', sequence.id)
   298→      .eq('lead_id', leadId)
   299→      .or(`status.eq.active,enrolled_at.gte.${thirtyDaysAgo.toISOString()}`)
   300→      .limit(1);
   301→
   302→    if (existingEnrollment?.length > 0) {
   303→      logger.debug('Lead already enrolled in sequence recently', { leadId, sequenceId: sequence.id });
   304→      return;
   305→    }
   306→
   307→    // 5. Get first step to calculate initial next_step_at
   308→    const { data: firstStep } = await supabase
   309→      .from('sequence_steps')
   310→      .select('delay_hours, delay_days_min, delay_days_max, send_window')
   311→      .eq('sequence_id', sequence.id)
   312→      .order('step_order', { ascending: true })
   313→      .limit(1)
   314→      .single();
   315→
   316→    // Calculate initial delay (for first step, usually 0 or small delay)
   317→    let nextStepAt = new Date();
   318→    if (firstStep) {
   319→      // Use v2 timing if available, else v1
   320→      const delayDays = (firstStep.delay_days_min || firstStep.delay_days_max)
   321→        ? Math.floor((firstStep.delay_days_min + firstStep.delay_days_max) / 2)
   322→        : Math.floor((firstStep.delay_hours || 0) / 24);
   323→      nextStepAt.setDate(nextStepAt.getDate() + delayDays);
   324→    }
   325→
   326→    // 6. Create enrollment
   327→    const { error: insertError } = await supabase
   328→      .from('sequence_enrollments')
   329→      .insert({
   330→        sequence_id: sequence.id,
   331→        lead_id: leadId,
   332→        current_step: 0,
   333→        status: 'active',
   334→        enrollment_source: 'stage_change',
   335→        enrolled_at: new Date().toISOString(),
   336→        next_step_at: nextStepAt.toISOString()
   337→      });
   338→
   339→    if (insertError) {
   340→      logger.error('Failed to auto-enroll in sequence', { error: insertError.message, leadId });
   341→    } else {
   342→      logger.info('Auto-enrolled lead in sequence', {
   343→        leadId,
   344→        sequenceId: sequence.id,
   345→        sequenceName: sequence.name,
   346→        stage: toStage
   347→      });
   348→    }
   349→
   350→  } catch (error) {
   351→    logger.error('Error in sequence auto-enrollment', { error: error.message, leadId });
   352→  }
   353→}
   354→
   355→/**
   356→ * Calculate days in current stage
   357→ */
   358→function calculateDaysInStage(stageChangedAt, createdAt) {
   359→  const referenceDate = stageChangedAt ? new Date(stageChangedAt) : new Date(createdAt);
   360→  const now = new Date();
   361→  return Math.floor((now - referenceDate) / (1000 * 60 * 60 * 24));
   362→}
   363→
   364→/**
   365→ * Determine if user is "stuck" (7+ days in stage with no progress)
   366→ */
   367→function isStuck(daysInStage, thresholdDays = 7) {
   368→  return daysInStage >= thresholdDays;
   369→}
   370→
   371→/**
   372→ * Diagnose why user might be stuck
   373→ * @returns 'forgot' | 'avoiding' | 'unknown'
   374→ */
   375→function diagnoseStuckReason(platformData, daysInStage) {
   376→  if (!platformData || !platformData.last_login) {
   377→    return 'unknown';
   378→  }
   379→
   380→  const lastLogin = new Date(platformData.last_login);
   381→  const daysSinceLogin = Math.floor((Date.now() - lastLogin.getTime()) / (1000 * 60 * 60 * 24));
   382→
   383→  // If they haven't logged in recently, they probably forgot
   384→  if (daysSinceLogin >= 7) {
   385→    return 'forgot';
   386→  }
   387→
   388→  // If they're logging in but not progressing, they might be avoiding
   389→  if (daysSinceLogin < 7 && daysInStage >= 7) {
   390→    return 'avoiding';
   391→  }
   392→
   393→  return 'unknown';
   394→}
   395→
   396→/**
   397→ * Get stages list for a lead type
   398→ */
   399→function getStagesForType(leadType) {
   400→  return leadType === 'worker' ? WORKER_STAGES : CONTRACTOR_STAGES;
   401→}
   402→
   403→/**
   404→ * Get stage label for display
   405→ */
   406→function getStageLabel(stage) {
   407→  return STAGE_LABELS[stage] || stage;
   408→}
   409→
   410→module.exports = {
   411→  // Stage calculation
   412→  calculateWorkerStage,
   413→  calculateContractorStage,
   414→  calculateHealthScore,
   415→
   416→  // Stage comparison
   417→  getStageIndex,
   418→  hasStageProgressed,
   419→  getStagesForType,
   420→  getStageLabel,
   421→
   422→  // Tracking
   423→  logStageTransition,
   424→  handleSequenceAutoEnrollment,
   425→  calculateDaysInStage,
   426→  isStuck,
   427→  diagnoseStuckReason,
   428→
   429→  // Constants
   430→  WORKER_STAGES,
   431→  CONTRACTOR_STAGES,
   432→  STAGE_LABELS
   433→};
   434→

<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→ * TwiML Route Update - Add Media Stream to outbound calls
     3→ *
     4→ * Add this to your existing /twiml route to enable real-time transcription
     5→ * of both sides of the call via Twilio Media Streams
     6→ */
     7→
     8→const VoiceResponse = require('twilio').twiml.VoiceResponse;
     9→
    10→/**
    11→ * Updated TwiML handler that starts a Media Stream for transcription
    12→ */
    13→function generateTwiMLWithStream(req, res) {
    14→  const twiml = new VoiceResponse();
    15→
    16→  // Get parameters from request
    17→  const to = req.body.To || req.query.To;
    18→  const leadId = req.body.leadId || req.query.leadId;
    19→  const callerId = req.body.callerId || process.env.TWILIO_PHONE_NUMBER;
    20→
    21→  console.log('[Voice TwiML] Generating TwiML with Media Stream:', { to, leadId });
    22→
    23→  // Start a unidirectional media stream to capture both tracks
    24→  // This runs in parallel with the call - doesn't block
    25→  const start = twiml.start();
    26→  start.stream({
    27→    url: `wss://${req.headers.host}/api/twilio-media`,  // Your WebSocket endpoint
    28→    track: 'both_tracks',  // Capture both inbound and outbound audio
    29→    name: `transcription-${leadId}`,
    30→  }).parameter({
    31→    name: 'leadId',
    32→    value: leadId || 'unknown',
    33→  }).parameter({
    34→    name: 'callSid',
    35→    value: req.body.CallSid || 'pending',
    36→  });
    37→
    38→  // Dial the number
    39→  const dial = twiml.dial({
    40→    callerId: callerId,
    41→    record: 'record-from-answer-dual',  // Keep your existing recording
    42→    recordingStatusCallback: `https://${req.headers.host}/api/recording-status`,
    43→    recordingStatusCallbackMethod: 'POST',
    44→  });
    45→  dial.number(to);
    46→
    47→  console.log('[Voice TwiML] Generated:', twiml.toString());
    48→
    49→  res.type('text/xml');
    50→  res.send(twiml.toString());
    51→}
    52→
    53→/**
    54→ * Example output TwiML:
    55→ *
    56→ * <?xml version="1.0" encoding="UTF-8"?>
    57→ * <Response>
    58→ *   <Start>
    59→ *     <Stream url="wss://app.rateright.com.au/api/twilio-media" track="both_tracks" name="transcription-abc123">
    60→ *       <Parameter name="leadId" value="abc123"/>
    61→ *       <Parameter name="callSid" value="CA..."/>
    62→ *     </Stream>
    63→ *   </Start>
    64→ *   <Dial callerId="+61..." record="record-from-answer-dual" recordingStatusCallback="...">
    65→ *     <Number>+61426246472</Number>
    66→ *   </Dial>
    67→ * </Response>
    68→ */
    69→
    70→module.exports = { generateTwiMLWithStream };
    71→

<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→ * 0.1% Quality Audit System
     3→ * Daily comprehensive testing + quality scoring
     4→ * Sends report to Slack
     5→ */
     6→
     7→const { fetchWithTimeout, isTimeoutError } = require('../utils/fetchWithTimeout');
     8→
     9→// Get supabase lazily to ensure env vars are loaded
    10→// Use supabaseAdmin to bypass RLS - audit jobs run without user context
    11→function getSupabase() {
    12→  const { supabaseAdmin, supabase } = require('../config/database');
    13→  const client = supabaseAdmin || supabase;
    14→  if (!client) {
    15→    throw new Error('Supabase not configured - check env vars');
    16→  }
    17→  return client;
    18→}
    19→
    20→// Test categories with weights
    21→const AUDIT_CATEGORIES = {
    22→  critical: { weight: 3, label: '🚨 Critical Systems' },
    23→  api: { weight: 2, label: '🔌 API Endpoints' },
    24→  external: { weight: 3, label: '🌐 External Services' },
    25→  core: { weight: 2, label: '⚡ Core Features' },
    26→  ai: { weight: 2, label: '🤖 AI Systems' },
    27→  business: { weight: 2, label: '📊 Business Metrics' },
    28→  journeys: { weight: 2, label: '🧪 Synthetic Journeys' },
    29→  quality: { weight: 1, label: '✨ 0.1% Quality' },
    30→  performance: { weight: 1, label: '🚀 Performance' },
    31→  frontend: { weight: 1, label: '📱 Frontend Quality' }
    32→};
    33→
    34→// Alert severity mapping for intelligence_alerts
    35→const SEVERITY_MAP = {
    36→  critical: 'critical',
    37→  api: 'high',
    38→  external: 'high',
    39→  core: 'medium',
    40→  ai: 'medium',
    41→  business: 'medium',
    42→  journeys: 'medium',
    43→  quality: 'low',
    44→  performance: 'low',
    45→  frontend: 'low'
    46→};
    47→
    48→/**
    49→ * Run all audit tests
    50→ */
    51→async function runFullAudit() {
    52→  const startTime = Date.now();
    53→  const results = {
    54→    timestamp: new Date().toISOString(),
    55→    categories: {},
    56→    summary: { passed: 0, failed: 0, warnings: 0 },
    57→    score: 0,
    58→    duration: 0
    59→  };
    60→
    61→  // Run all test categories
    62→  results.categories.critical = await runCriticalTests();
    63→  results.categories.api = await runAPITests();
    64→  results.categories.external = await runExternalServiceTests();
    65→  results.categories.core = await runCoreTests();
    66→  results.categories.ai = await runAITests();
    67→  results.categories.business = await runBusinessMetricTests();
    68→  results.categories.journeys = await runSyntheticJourneyTests();
    69→  results.categories.quality = await runQualityTests();
    70→  results.categories.performance = await runPerformanceTests();
    71→  results.categories.frontend = await runFrontendTests();
    72→
    73→  // Calculate totals
    74→  let totalWeight = 0;
    75→  let weightedScore = 0;
    76→
    77→  for (const [key, category] of Object.entries(results.categories)) {
    78→    const weight = AUDIT_CATEGORIES[key].weight;
    79→    totalWeight += weight;
    80→
    81→    const categoryScore = category.passed / (category.passed + category.failed) || 0;
    82→    weightedScore += categoryScore * weight;
    83→
    84→    results.summary.passed += category.passed;
    85→    results.summary.failed += category.failed;
    86→    results.summary.warnings += category.warnings || 0;
    87→  }
    88→
    89→  results.score = Math.round((weightedScore / totalWeight) * 100);
    90→  results.duration = Date.now() - startTime;
    91→
    92→  return results;
    93→}
    94→
    95→/**
    96→ * Critical Systems - Must pass for app to function
    97→ */
    98→async function runCriticalTests() {
    99→  const tests = [];
   100→  const supabase = getSupabase();
   101→
   102→  // 1. Database connection
   103→  tests.push(await runTest('Database Connection', async () => {
   104→    const { data, error } = await supabase.from('leads').select('id').limit(1);
   105→    if (error) throw error;
   106→    return { detail: 'Supabase connected' };
   107→  }));
   108→
   109→  // 2. Leads table accessible
   110→  tests.push(await runTest('Leads Table', async () => {
   111→    const { count, error } = await supabase.from('leads').select('*', { count: 'exact', head: true });
   112→    if (error) throw error;
   113→    return { detail: `${count} leads in database` };
   114→  }));
   115→
   116→  // 3. Communications table
   117→  tests.push(await runTest('Communications Table', async () => {
   118→    const { count, error } = await supabase.from('communications').select('*', { count: 'exact', head: true });
   119→    if (error) throw error;
   120→    return { detail: `${count} communications logged` };
   121→  }));
   122→
   123→  // 4. User XP table (gamification)
   124→  tests.push(await runTest('User XP Table', async () => {
   125→    const { count, error } = await supabase.from('user_xp').select('*', { count: 'exact', head: true });
   126→    if (error) throw error;
   127→    return { detail: `${count} users with XP` };
   128→  }));
   129→
   130→  // 5. Auth working (use user_xp table which always exists)
   131→  tests.push(await runTest('Auth System', async () => {
   132→    const { data, error } = await supabase.from('user_xp').select('id').limit(1);
   133→    if (error) throw error;
   134→    return { detail: 'User tables accessible' };
   135→  }));
   136→
   137→  return summarizeTests(tests);
   138→}
   139→
   140→/**
   141→ * API Endpoints - Test actual HTTP endpoints respond
   142→ */
   143→async function runAPITests() {
   144→  const tests = [];
   145→  const baseUrl = process.env.API_URL || process.env.RAILWAY_PUBLIC_DOMAIN
   146→    ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
   147→    : 'http://localhost:3000';
   148→
   149→  // 1. Health endpoint
   150→  tests.push(await runTest('Health Endpoint', async () => {
   151→    try {
   152→      const response = await fetchWithTimeout(`${baseUrl}/health`, {}, 5000);
   153→      if (!response.ok) throw new Error(`HTTP ${response.status}`);
   154→      const data = await response.json();
   155→      if (data.status !== 'healthy') throw new Error(`Status: ${data.status}`);
   156→      return { detail: `Uptime: ${Math.round(data.uptime)}s` };
   157→    } catch (error) {
   158→      if (isTimeoutError(error)) throw new Error('Health check timed out');
   159→      throw error;
   160→    }
   161→  }));
   162→
   163→  // 2. Dashboard API (no auth needed for health check)
   164→  tests.push(await runTest('Dashboard Stats', async () => {
   165→    const supabase = getSupabase();
   166→    // Test the query pattern directly since we can't easily auth
   167→    const { count, error } = await supabase
   168→      .from('leads')
   169→      .select('*', { count: 'exact', head: true })
   170→      .is('deleted_at', null);
   171→    if (error) throw error;
   172→    return { detail: `${count} leads accessible` };
   173→  }));
   174→
   175→  // 3. Call List query pattern
   176→  tests.push(await runTest('Call List Query', async () => {
   177→    const supabase = getSupabase();
   178→    const start = Date.now();
   179→    const { data, error } = await supabase
   180→      .from('leads')
   181→      .select('id, first_name, last_name, call_list_score, phone')
   182→      .not('status', 'in', '("converted","opted_out","archived")')
   183→      .is('deleted_at', null)
   184→      .order('call_list_score', { ascending: false, nullsFirst: false })
   185→      .limit(20);
   186→    if (error) throw error;
   187→    const duration = Date.now() - start;
   188→    return { detail: `${data.length} leads in ${duration}ms` };
   189→  }));
   190→
   191→  // 4. SMS conversations query pattern
   192→  tests.push(await runTest('SMS Inbox Query', async () => {
   193→    const supabase = getSupabase();
   194→    const start = Date.now();
   195→    const { data, error } = await supabase
   196→      .from('communications')
   197→      .select('id, lead_id, content, created_at, type')
   198→      .in('type', ['sms_inbound', 'sms_outbound'])
   199→      .order('created_at', { ascending: false })
   200→      .limit(50);
   201→    if (error) throw error;
   202→    const duration = Date.now() - start;
   203→    return { detail: `${data.length} messages in ${duration}ms` };
   204→  }));
   205→
   206→  // 5. Sequences accessible
   207→  tests.push(await runTest('Sequences API', async () => {
   208→    const supabase = getSupabase();
   209→    const { data, error } = await supabase
   210→      .from('sequences')
   211→      .select('id, name, is_active')
   212→      .limit(10);
   213→    if (error) throw error;
   214→    return { detail: `${data.length} sequences found` };
   215→  }));
   216→
   217→  return summarizeTests(tests);
   218→}
   219→
   220→/**
   221→ * External Services - Test third-party integrations are reachable
   222→ */
   223→async function runExternalServiceTests() {
   224→  const tests = [];
   225→
   226→  // 1. Twilio Configuration Check
   227→  tests.push(await runTest('Twilio Configured', async () => {
   228→    const accountSid = process.env.TWILIO_ACCOUNT_SID;
   229→    const authToken = process.env.TWILIO_AUTH_TOKEN;
   230→    const phoneNumber = process.env.TWILIO_PHONE_NUMBER;
   231→
   232→    if (!accountSid || !authToken) {
   233→      throw new Error('Missing TWILIO_ACCOUNT_SID or AUTH_TOKEN');
   234→    }
   235→    if (!phoneNumber) {
   236→      throw new Error('Missing TWILIO_PHONE_NUMBER');
   237→    }
   238→    return { detail: `Phone: ${phoneNumber.slice(-4)}` };
   239→  }));
   240→
   241→  // 2. Twilio API Reachable (lightweight call)
   242→  tests.push(await runTest('Twilio API Reachable', async () => {
   243→    const accountSid = process.env.TWILIO_ACCOUNT_SID;
   244→    const authToken = process.env.TWILIO_AUTH_TOKEN;
   245→
   246→    if (!accountSid || !authToken) {
   247→      return { detail: 'Skipped - not configured', warning: true };
   248→    }
   249→
   250→    try {
   251→      // Use fetch to check account status (lightweight)
   252→      const response = await fetchWithTimeout(
   253→        `https://api.twilio.com/2010-04-01/Accounts/${accountSid}.json`,
   254→        {
   255→          headers: {
   256→            'Authorization': 'Basic ' + Buffer.from(`${accountSid}:${authToken}`).toString('base64')
   257→          }
   258→        },
   259→        5000 // 5 second timeout for health checks
   260→      );
   261→
   262→      if (!response.ok) throw new Error(`HTTP ${response.status}`);
   263→      const data = await response.json();
   264→      return { detail: `Account: ${data.status}` };
   265→    } catch (error) {
   266→      if (isTimeoutError(error)) throw new Error('Twilio API timed out');
   267→      throw error;
   268→    }
   269→  }));
   270→
   271→  // 3. OpenAI Configuration
   272→  tests.push(await runTest('OpenAI Configured', async () => {
   273→    const apiKey = process.env.OPENAI_API_KEY;
   274→    if (!apiKey) {
   275→      throw new Error('Missing OPENAI_API_KEY');
   276→    }
   277→    return { detail: 'API key present' };
   278→  }));
   279→
   280→  // 4. OpenAI API Reachable (models list - cheap call)
   281→  tests.push(await runTest('OpenAI API Reachable', async () => {
   282→    const apiKey = process.env.OPENAI_API_KEY;
   283→    if (!apiKey) {
   284→      return { detail: 'Skipped - not configured', warning: true };
   285→    }
   286→
   287→    try {
   288→      const response = await fetchWithTimeout('https://api.openai.com/v1/models', {
   289→        headers: {
   290→          'Authorization': `Bearer ${apiKey}`
   291→        }
   292→      }, 5000); // 5 second timeout for health checks
   293→
   294→      if (!response.ok) throw new Error(`HTTP ${response.status}`);
   295→      return { detail: 'API responding' };
   296→    } catch (error) {
   297→      if (isTimeoutError(error)) throw new Error('OpenAI API timed out');
   298→      throw error;
   299→    }
   300→  }));
   301→
   302→  // 5. Deepgram Configuration
   303→  tests.push(await runTest('Deepgram Configured', async () => {
   304→    const deepgramService = require('./deepgram');
   305→    const config = deepgramService.getClientConfig();
   306→    if (!config.configured) {
   307→      throw new Error(config.error || 'Not configured');
   308→    }
   309→    return { detail: 'API key present' };
   310→  }));
   311→
   312→  // 6. Supabase Realtime Check
   313→  tests.push(await runTest('Supabase Realtime', async () => {
   314→    const supabase = getSupabase();
   315→
   316→    // Quick subscription test with timeout
   317→    const testChannel = supabase.channel('audit-test-' + Date.now());
   318→
   319→    const subscribed = await new Promise((resolve) => {
   320→      const timeout = setTimeout(() => {
   321→        testChannel.unsubscribe();
   322→        resolve(false);
   323→      }, 5000);
   324→
   325→      testChannel.subscribe((status) => {
   326→        if (status === 'SUBSCRIBED') {
   327→          clearTimeout(timeout);
   328→          testChannel.unsubscribe();
   329→          resolve(true);
   330→        }
   331→      });
   332→    });
   333→
   334→    if (!subscribed) throw new Error('Failed to subscribe within 5s');
   335→    return { detail: 'Realtime connected' };
   336→  }));
   337→
   338→  // 7. Slack Webhook Configured
   339→  tests.push(await runTest('Slack Configured', async () => {
   340→    const webhookUrl = process.env.SLACK_WEBHOOK_URL;
   341→    if (!webhookUrl) {
   342→      return { detail: 'No webhook configured', warning: true };
   343→    }
   344→    return { detail: 'Webhook URL present' };
   345→  }));
   346→
   347→  return summarizeTests(tests);
   348→}
   349→
   350→/**
   351→ * Core Features - Main app functionality
   352→ */
   353→async function runCoreTests() {
   354→  const tests = [];
   355→  const supabase = getSupabase();
   356→
   357→  // 1. Call list query works
   358→  tests.push(await runTest('Call List Query', async () => {
   359→    const { data, error } = await supabase
   360→      .from('leads')
   361→      .select('id, first_name, last_name, call_list_score')
   362→      .not('status', 'in', '("converted","opted_out","archived")')
   363→      .order('call_list_score', { ascending: false })
   364→      .limit(10);
   365→    if (error) throw error;
   366→    return { detail: `Top lead score: ${data[0]?.call_list_score || 0}` };
   367→  }));
   368→
   369→  // 2. Lead search works
   370→  tests.push(await runTest('Lead Search', async () => {
   371→    const { data, error } = await supabase
   372→      .from('leads')
   373→      .select('id, first_name, last_name')
   374→      .or('first_name.ilike.%test%,last_name.ilike.%test%')
   375→      .limit(5);
   376→    if (error) throw error;
   377→    return { detail: `Search returned ${data.length} results` };
   378→  }));
   379→
   380→  // 3. SMS history query
   381→  tests.push(await runTest('SMS History Query', async () => {
   382→    const { data, error } = await supabase
   383→      .from('communications')
   384→      .select('id, type, created_at')
   385→      .in('type', ['sms_inbound', 'sms_outbound'])
   386→      .order('created_at', { ascending: false })
   387→      .limit(1);
   388→    if (error) throw error;
   389→    const lastSms = data[0]?.created_at ? new Date(data[0].created_at) : null;
   390→    const hoursAgo = lastSms ? Math.round((Date.now() - lastSms) / (1000 * 60 * 60)) : 'N/A';
   391→    return { detail: `Last SMS: ${hoursAgo}h ago` };
   392→  }));
   393→
   394→  // 4. Call history query
   395→  tests.push(await runTest('Call History Query', async () => {
   396→    const { data, error } = await supabase
   397→      .from('communications')
   398→      .select('id, type, created_at')
   399→      .in('type', ['call_outbound', 'call_inbound'])
   400→      .order('created_at', { ascending: false })
   401→      .limit(1);
   402→    if (error) throw error;
   403→    return { detail: `Last call logged: ${data[0]?.created_at || 'Never'}` };
   404→  }));
   405→
   406→  // 5. Lead status distribution
   407→  tests.push(await runTest('Lead Status Health', async () => {
   408→    const { data, error } = await supabase
   409→      .from('leads')
   410→      .select('status')
   411→      .is('deleted_at', null);
   412→    if (error) throw error;
   413→
   414→    const statuses = {};
   415→    data.forEach(l => { statuses[l.status] = (statuses[l.status] || 0) + 1; });
   416→    const activeCount = data.filter(l => !['converted', 'opted_out', 'archived'].includes(l.status)).length;
   417→    return { detail: `${activeCount} active leads` };
   418→  }));
   419→
   420→  // 6. Sequences table
   421→  tests.push(await runTest('SMS Sequences', async () => {
   422→    const { count, error } = await supabase
   423→      .from('sequences')
   424→      .select('*', { count: 'exact', head: true })
   425→      .eq('is_active', true);
   426→    if (error) throw error;
   427→    return { detail: `${count} active sequences` };
   428→  }));
   429→
   430→  // 7. Callbacks pending
   431→  tests.push(await runTest('Callbacks Queue', async () => {
   432→    const { count, error } = await supabase
   433→      .from('callbacks')
   434→      .select('*', { count: 'exact', head: true })
   435→      .eq('status', 'pending')
   436→      .gte('scheduled_at', new Date().toISOString());
   437→    if (error) throw error;
   438→    return { detail: `${count} pending callbacks` };
   439→  }));
   440→
   441→  return summarizeTests(tests);
   442→}
   443→
   444→/**
   445→ * AI Systems - Intelligence features
   446→ */
   447→async function runAITests() {
   448→  const tests = [];
   449→  const supabase = getSupabase();
   450→
   451→  // 1. Copilot sessions exist
   452→  tests.push(await runTest('Copilot Sessions', async () => {
   453→    const { count, error } = await supabase
   454→      .from('copilot_sessions')
   455→      .select('*', { count: 'exact', head: true });
   456→    if (error) throw error;
   457→    return { detail: `${count} sessions recorded` };
   458→  }));
   459→
   460→  // 2. AI insights table
   461→  tests.push(await runTest('AI Insights', async () => {
   462→    const { count, error } = await supabase
   463→      .from('ai_insights')
   464→      .select('*', { count: 'exact', head: true });
   465→    if (error) throw error;
   466→    return { detail: `${count} insights generated` };
   467→  }));
   468→
   469→  // 3. Learning system - conversion patterns
   470→  tests.push(await runTest('Conversion Patterns', async () => {
   471→    const { count, error } = await supabase
   472→      .from('conversion_patterns')
   473→      .select('*', { count: 'exact', head: true });
   474→    if (error) throw error;
   475→    return { detail: `${count} patterns learned` };
   476→  }));
   477→
   478→  // 4. Objection responses
   479→  tests.push(await runTest('Objection Library', async () => {
   480→    const { count, error } = await supabase
   481→      .from('objection_responses')
   482→      .select('*', { count: 'exact', head: true });
   483→    if (error) throw error;
   484→    return { detail: `${count} objection responses` };
   485→  }));
   486→
   487→  // 5. Lead intel coverage
   488→  tests.push(await runTest('Lead Intel Coverage', async () => {
   489→    const { count: totalLeads } = await supabase
   490→      .from('leads')
   491→      .select('*', { count: 'exact', head: true })
   492→      .is('deleted_at', null);
   493→
   494→    const { count: withIntel } = await supabase
   495→      .from('lead_intel')
   496→      .select('*', { count: 'exact', head: true });
   497→
   498→    const coverage = totalLeads > 0 ? Math.round((withIntel / totalLeads) * 100) : 0;
   499→    return { detail: `${coverage}% leads have intel` };
   500→  }));
   501→
   502→  // 6. Winning patterns
   503→  tests.push(await runTest('Winning Patterns', async () => {
   504→    const { count, error } = await supabase
   505→      .from('winning_patterns')
   506→      .select('*', { count: 'exact', head: true })
   507→      .eq('is_active', true);
   508→    if (error) throw error;
   509→    return { detail: `${count} active patterns` };
   510→  }));
   511→
   512→  return summarizeTests(tests);
   513→}
   514→
   515→/**
   516→ * 0.1% Quality Metrics - The excellence standard
   517→ */
   518→async function runQualityTests() {
   519→  const tests = [];
   520→  const supabase = getSupabase();
   521→
   522→  // 1. Call outcomes logged (not abandoned)
   523→  tests.push(await runTest('Call Outcome Logging', async () => {
   524→    const { data: recentCalls } = await supabase
   525→      .from('communications')
   526→      .select('id, metadata')
   527→      .eq('type', 'call_outbound')
   528→      .order('created_at', { ascending: false })
   529→      .limit(20);
   530→
   531→    const withOutcome = recentCalls?.filter(c => c.metadata?.outcome).length || 0;
   532→    const rate = recentCalls?.length > 0 ? Math.round((withOutcome / recentCalls.length) * 100) : 0;
   533→
   534→    if (rate < 80) throw new Error(`Only ${rate}% calls have outcomes`);
   535→    return { detail: `${rate}% calls have outcomes logged` };
   536→  }));
   537→
   538→  // 2. Transcription rate
   539→  tests.push(await runTest('Transcription Rate', async () => {
   540→    const { data: recentCalls } = await supabase
   541→      .from('communications')
   542→      .select('id, metadata')
   543→      .eq('type', 'call_outbound')
   544→      .not('metadata->duration', 'is', null)
   545→      .order('created_at', { ascending: false })
   546→      .limit(20);
   547→
   548→    const withTranscript = recentCalls?.filter(c => c.metadata?.transcript).length || 0;
   549→    const rate = recentCalls?.length > 0 ? Math.round((withTranscript / recentCalls.length) * 100) : 0;
   550→    return { detail: `${rate}% calls transcribed`, warning: rate < 50 };
   551→  }));
   552→
   553→  // 3. Response time tracking
   554→  tests.push(await runTest('Speed-to-Lead Tracking', async () => {
   555→    const { count, error } = await supabase
   556→      .from('speed_to_lead')
   557→      .select('*', { count: 'exact', head: true })
   558→      .not('minutes_to_contact', 'is', null);
   559→    if (error) throw error;
   560→    return { detail: `${count} leads tracked` };
   561→  }));
   562→
   563→  // 4. Dossier coverage
   564→  tests.push(await runTest('Lead Dossier Coverage', async () => {
   565→    const { count: totalLeads } = await supabase
   566→      .from('leads')
   567→      .select('*', { count: 'exact', head: true })
   568→      .is('deleted_at', null);
   569→
   570→    const { count: withDossier } = await supabase
   571→      .from('lead_dossier')
   572→      .select('*', { count: 'exact', head: true });
   573→
   574→    const coverage = totalLeads > 0 ? Math.round((withDossier / totalLeads) * 100) : 0;
   575→    return { detail: `${coverage}% leads have dossiers` };
   576→  }));
   577→
   578→  // 5. Playbook messages generated
   579→  tests.push(await runTest('Playbook Messages', async () => {
   580→    const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
   581→    const { count, error } = await supabase
   582→      .from('playbook_messages')
   583→      .select('*', { count: 'exact', head: true })
   584→      .gte('created_at', yesterday);
   585→    if (error) throw error;
   586→    return { detail: `${count} messages generated today` };
   587→  }));
   588→
   589→  return summarizeTests(tests);
   590→}
   591→
   592→/**
   593→ * Performance Tests
   594→ */
   595→async function runPerformanceTests() {
   596→  const tests = [];
   597→  const supabase = getSupabase();
   598→
   599→  // 1. Database query speed
   600→  tests.push(await runTest('DB Query Speed', async () => {
   601→    const start = Date.now();
   602→    await supabase.from('leads').select('id').limit(100);
   603→    const duration = Date.now() - start;
   604→    if (duration > 2000) throw new Error(`Slow: ${duration}ms`);
   605→    return { detail: `${duration}ms for 100 leads` };
   606→  }));
   607→
   608→  // 2. Complex join query
   609→  tests.push(await runTest('Complex Query Speed', async () => {
   610→    const start = Date.now();
   611→    await supabase
   612→      .from('leads')
   613→      .select(`
   614→        id, first_name, last_name, status,
   615→        communications(id, type, created_at)
   616→      `)
   617→      .limit(10);
   618→    const duration = Date.now() - start;
   619→    if (duration > 3000) throw new Error(`Slow: ${duration}ms`);
   620→    return { detail: `${duration}ms for join query` };
   621→  }));
   622→
   623→  // 3. Battleground queries
   624→  tests.push(await runTest('Battleground Query', async () => {
   625→    const start = Date.now();
   626→    await supabase
   627→      .from('user_xp')
   628→      .select('*')
   629→      .order('total_xp', { ascending: false })
   630→      .limit(20);
   631→    const duration = Date.now() - start;
   632→    return { detail: `${duration}ms for leaderboard` };
   633→  }));
   634→
   635→  // 4. Duels table
   636→  tests.push(await runTest('Duels System', async () => {
   637→    const { count, error } = await supabase
   638→      .from('duels')
   639→      .select('*', { count: 'exact', head: true });
   640→    if (error) throw error;
   641→    return { detail: `${count} duels created` };
   642→  }));
   643→
   644→  // 5. Challenges table
   645→  tests.push(await runTest('Challenges System', async () => {
   646→    const { count, error } = await supabase
   647→      .from('manager_challenges')
   648→      .select('*', { count: 'exact', head: true });
   649→    if (error) throw error;
   650→    return { detail: `${count} challenges created` };
   651→  }));
   652→
   653→  return summarizeTests(tests);
   654→}
   655→
   656→/**
   657→ * Business Metrics - Key business health indicators
   658→ */
   659→async function runBusinessMetricTests() {
   660→  const tests = [];
   661→  const supabase = getSupabase();
   662→  const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
   663→
   664→  // 1. SMS delivery rate (last 24h)
   665→  tests.push(await runTest('SMS Delivery Rate', async () => {
   666→    const { data: outbound } = await supabase
   667→      .from('communications')
   668→      .select('id, metadata')
   669→      .eq('type', 'sms_outbound')
   670→      .gte('created_at', yesterday);
   671→
   672→    const total = outbound?.length || 0;
   673→    if (total === 0) {
   674→      return { detail: 'No SMS sent in 24h', warning: true };
   675→    }
   676→
   677→    const delivered = outbound.filter(sms =>
   678→      sms.metadata?.status === 'delivered' ||
   679→      sms.metadata?.status === 'sent' ||
   680→      !sms.metadata?.status // Assume delivered if no error
   681→    ).length;
   682→
   683→    const rate = Math.round((delivered / total) * 100);
   684→    if (rate < 90) throw new Error(`Only ${rate}% delivered (target: 90%)`);
   685→    return { detail: `${rate}% delivery rate (${delivered}/${total})` };
   686→  }));
   687→
   688→  // 2. Call outcome logging rate
   689→  tests.push(await runTest('Call Outcome Rate', async () => {
   690→    const { data: recentCalls } = await supabase
   691→      .from('communications')
   692→      .select('id, metadata')
   693→      .eq('type', 'call_outbound')
   694→      .gte('created_at', yesterday);
   695→
   696→    const total = recentCalls?.length || 0;
   697→    if (total === 0) {
   698→      return { detail: 'No calls in 24h', warning: true };
   699→    }
   700→
   701→    const withOutcome = recentCalls.filter(c => c.metadata?.outcome).length;
   702→    const rate = Math.round((withOutcome / total) * 100);
   703→
   704→    if (rate < 80) throw new Error(`Only ${rate}% logged outcomes (target: 80%)`);
   705→    return { detail: `${rate}% calls logged outcomes` };
   706→  }));
   707→
   708→  // 3. Speed-to-lead average
   709→  tests.push(await runTest('Speed-to-Lead', async () => {
   710→    const { data: speedData } = await supabase
   711→      .from('speed_to_lead')
   712→      .select('minutes_to_contact')
   713→      .not('minutes_to_contact', 'is', null)
   714→      .gte('created_at', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString())
   715→      .limit(100);
   716→
   717→    if (!speedData?.length) {
   718→      return { detail: 'No speed data available', warning: true };
   719→    }
   720→
   721→    const avg = Math.round(speedData.reduce((sum, d) => sum + d.minutes_to_contact, 0) / speedData.length);
   722→    if (avg > 30) throw new Error(`${avg} min avg (target: <30 min)`);
   723→    return { detail: `${avg} min average response time` };
   724→  }));
   725→
   726→  // 4. Conversion tracking health
   727→  tests.push(await runTest('Conversion Tracking', async () => {
   728→    const { data: conversions } = await supabase
   729→      .from('leads')
   730→      .select('id, last_contact_at')
   731→      .eq('status', 'converted')
   732→      .gte('updated_at', new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString());
   733→
   734→    const count = conversions?.length || 0;
   735→    return { detail: `${count} conversions in 30d` };
   736→  }));
   737→
   738→  // 5. Sequence processing health
   739→  tests.push(await runTest('Sequence Health', async () => {
   740→    const { data: stuck } = await supabase
   741→      .from('sequence_enrollments')
   742→      .select('id')
   743→      .eq('status', 'active')
   744→      .lt('next_step_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
   745→
   746→    const stuckCount = stuck?.length || 0;
   747→    if (stuckCount > 5) throw new Error(`${stuckCount} stuck enrollments`);
   748→    return { detail: `${stuckCount} overdue enrollments` };
   749→  }));
   750→
   751→  // 6. Lead contact rate
   752→  tests.push(await runTest('Lead Contact Rate', async () => {
   753→    const { count: totalLeads } = await supabase
   754→      .from('leads')
   755→      .select('*', { count: 'exact', head: true })
   756→      .is('deleted_at', null)
   757→      .not('status', 'in', '("converted","opted_out","archived")');
   758→
   759→    const { count: contactedLeads } = await supabase
   760→      .from('leads')
   761→      .select('*', { count: 'exact', head: true })
   762→      .is('deleted_at', null)
   763→      .not('status', 'in', '("converted","opted_out","archived")')
   764→      .not('last_contact_at', 'is', null);
   765→
   766→    const rate = totalLeads > 0 ? Math.round((contactedLeads / totalLeads) * 100) : 0;
   767→    return { detail: `${rate}% leads contacted (${contactedLeads}/${totalLeads})` };
   768→  }));
   769→
   770→  return summarizeTests(tests);
   771→}
   772→
   773→/**
   774→ * Synthetic Journey Tests - End-to-end flow validation
   775→ */
   776→async function runSyntheticJourneyTests() {
   777→  const tests = [];
   778→  const supabase = getSupabase();
   779→
   780→  // 1. Lead CRUD journey (read-only simulation)
   781→  tests.push(await runTest('Lead Query Flow', async () => {
   782→    const start = Date.now();
   783→
   784→    // Simulate lead list query
   785→    const { data: leads, error: listError } = await supabase
   786→      .from('leads')
   787→      .select('id, first_name, last_name, phone, status, score')
   788→      .is('deleted_at', null)
   789→      .order('score', { ascending: false })
   790→      .limit(10);
   791→
   792→    if (listError) throw listError;
   793→    if (!leads?.length) throw new Error('No leads found');
   794→
   795→    // Simulate lead detail query
   796→    const { data: detail, error: detailError } = await supabase
   797→      .from('leads')
   798→      .select(`
   799→        *,
   800→        communications(id, type, created_at)
   801→      `)
   802→      .eq('id', leads[0].id)
   803→      .single();
   804→
   805→    if (detailError) throw detailError;
   806→
   807→    const duration = Date.now() - start;
   808→    if (duration > 3000) throw new Error(`Slow: ${duration}ms`);
   809→    return { detail: `Lead flow completed in ${duration}ms` };
   810→  }));
   811→
   812→  // 2. SMS template rendering flow
   813→  tests.push(await runTest('SMS Template Flow', async () => {
   814→    const { data: templates } = await supabase
   815→      .from('sms_templates')
   816→      .select('id, name, content')
   817→      .limit(5);
   818→
   819→    if (!templates?.length) {
   820→      return { detail: 'No templates configured', warning: true };
   821→    }
   822→
   823→    // Check templates have valid placeholders
   824→    const validTemplates = templates.filter(t =>
   825→      t.content && t.content.length > 0
   826→    );
   827→
   828→    return { detail: `${validTemplates.length}/${templates.length} templates valid` };
   829→  }));
   830→
   831→  // 3. Call list generation performance
   832→  tests.push(await runTest('Call List Generation', async () => {
   833→    const start = Date.now();
   834→
   835→    const { data, error } = await supabase
   836→      .from('leads')
   837→      .select('id, first_name, last_name, phone, call_list_score, status, score')
   838→      .not('status', 'in', '("converted","opted_out","archived")')
   839→      .is('deleted_at', null)
   840→      .order('call_list_score', { ascending: false, nullsFirst: false })
   841→      .limit(50);
   842→
   843→    if (error) throw error;
   844→
   845→    const duration = Date.now() - start;
   846→    if (duration > 2000) throw new Error(`Slow: ${duration}ms (target: <2s)`);
   847→    return { detail: `${data.length} leads in ${duration}ms` };
   848→  }));
   849→
   850→  // 4. AI service readiness
   851→  tests.push(await runTest('AI Service Ready', async () => {
   852→    // Check learning service has data
   853→    const { count: patterns } = await supabase
   854→      .from('conversion_patterns')
   855→      .select('*', { count: 'exact', head: true });
   856→
   857→    const { count: objections } = await supabase
   858→      .from('objection_responses')
   859→      .select('*', { count: 'exact', head: true });
   860→
   861→    const hasData = patterns > 0 || objections > 0;
   862→    return {
   863→      detail: `${patterns} patterns, ${objections} objections`,
   864→      warning: !hasData
   865→    };
   866→  }));
   867→
   868→  // 5. Dashboard stats query
   869→  tests.push(await runTest('Dashboard Query', async () => {
   870→    const start = Date.now();
   871→
   872→    // Simulate dashboard stats query
   873→    const { count: totalLeads } = await supabase
   874→      .from('leads')
   875→      .select('*', { count: 'exact', head: true })
   876→      .is('deleted_at', null);
   877→
   878→    const { count: todayCalls } = await supabase
   879→      .from('communications')
   880→      .select('*', { count: 'exact', head: true })
   881→      .eq('type', 'call_outbound')
   882→      .gte('created_at', new Date().toISOString().split('T')[0]);
   883→
   884→    const duration = Date.now() - start;
   885→    return { detail: `${totalLeads} leads, ${todayCalls} calls today (${duration}ms)` };
   886→  }));
   887→
   888→  return summarizeTests(tests);
   889→}
   890→
   891→/**
   892→ * Frontend Quality Tests - Web vitals and error rates
   893→ */
   894→async function runFrontendTests() {
   895→  const tests = [];
   896→  const supabase = getSupabase();
   897→  const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
   898→
   899→  // 1. LCP Performance (Largest Contentful Paint)
   900→  tests.push(await runTest('LCP Performance', async () => {
   901→    const { data: lcpData } = await supabase
   902→      .from('web_vitals')
   903→      .select('metric_value')
   904→      .eq('metric_name', 'LCP')
   905→      .gte('recorded_at', yesterday)
   906→      .limit(100);
   907→
   908→    if (!lcpData?.length) {
   909→      return { detail: 'No LCP data yet', warning: true };
   910→    }
   911→
   912→    const avg = Math.round(lcpData.reduce((sum, d) => sum + d.metric_value, 0) / lcpData.length);
   913→    if (avg > 2500) throw new Error(`${avg}ms avg (target: <2500ms)`);
   914→    return { detail: `${avg}ms avg LCP (${lcpData.length} samples)` };
   915→  }));
   916→
   917→  // 2. INP Performance (Interaction to Next Paint)
   918→  tests.push(await runTest('INP Performance', async () => {
   919→    const { data: inpData } = await supabase
   920→      .from('web_vitals')
   921→      .select('metric_value')
   922→      .eq('metric_name', 'INP')
   923→      .gte('recorded_at', yesterday)
   924→      .limit(100);
   925→
   926→    if (!inpData?.length) {
   927→      return { detail: 'No INP data yet', warning: true };
   928→    }
   929→
   930→    const avg = Math.round(inpData.reduce((sum, d) => sum + d.metric_value, 0) / inpData.length);
   931→    if (avg > 200) throw new Error(`${avg}ms avg (target: <200ms)`);
   932→    return { detail: `${avg}ms avg INP (${inpData.length} samples)` };
   933→  }));
   934→
   935→  // 3. CLS Performance (Cumulative Layout Shift)
   936→  tests.push(await runTest('CLS Performance', async () => {
   937→    const { data: clsData } = await supabase
   938→      .from('web_vitals')
   939→      .select('metric_value')
   940→      .eq('metric_name', 'CLS')
   941→      .gte('recorded_at', yesterday)
   942→      .limit(100);
   943→
   944→    if (!clsData?.length) {
   945→      return { detail: 'No CLS data yet', warning: true };
   946→    }
   947→
   948→    const avg = clsData.reduce((sum, d) => sum + d.metric_value, 0) / clsData.length;
   949→    if (avg > 0.1) throw new Error(`${avg.toFixed(3)} avg (target: <0.1)`);
   950→    return { detail: `${avg.toFixed(3)} avg CLS (${clsData.length} samples)` };
   951→  }));
   952→
   953→  // 4. Frontend error rate
   954→  tests.push(await runTest('Frontend Error Rate', async () => {
   955→    const { count: errorCount } = await supabase
   956→      .from('frontend_errors')
   957→      .select('*', { count: 'exact', head: true })
   958→      .gte('created_at', yesterday);
   959→
   960→    if (errorCount > 50) throw new Error(`${errorCount} errors in 24h (high)`);
   961→    if (errorCount > 20) {
   962→      return { detail: `${errorCount} errors in 24h`, warning: true };
   963→    }
   964→    return { detail: `${errorCount} errors in 24h` };
   965→  }));
   966→
   967→  // 5. TTFB Performance
   968→  tests.push(await runTest('TTFB Performance', async () => {
   969→    const { data: ttfbData } = await supabase
   970→      .from('web_vitals')
   971→      .select('metric_value')
   972→      .eq('metric_name', 'TTFB')
   973→      .gte('recorded_at', yesterday)
   974→      .limit(100);
   975→
   976→    if (!ttfbData?.length) {
   977→      return { detail: 'No TTFB data yet', warning: true };
   978→    }
   979→
   980→    const avg = Math.round(ttfbData.reduce((sum, d) => sum + d.metric_value, 0) / ttfbData.length);
   981→    if (avg > 800) throw new Error(`${avg}ms avg (target: <800ms)`);
   982→    return { detail: `${avg}ms avg TTFB` };
   983→  }));
   984→
   985→  return summarizeTests(tests);
   986→}
   987→
   988→/**
   989→ * Run a single test with error handling
   990→ */
   991→async function runTest(name, testFn) {
   992→  try {
   993→    const result = await testFn();
   994→    return {
   995→      name,
   996→      status: result.warning ? 'warning' : 'passed',
   997→      detail: result.detail
   998→    };
   999→  } catch (error) {
  1000→    return {
  1001→      name,
  1002→      status: 'failed',
  1003→      detail: error.message
  1004→    };
  1005→  }
  1006→}
  1007→
  1008→/**
  1009→ * Summarize test results
  1010→ */
  1011→function summarizeTests(tests) {
  1012→  return {
  1013→    tests,
  1014→    passed: tests.filter(t => t.status === 'passed').length,
  1015→    failed: tests.filter(t => t.status === 'failed').length,
  1016→    warnings: tests.filter(t => t.status === 'warning').length
  1017→  };
  1018→}
  1019→
  1020→/**
  1021→ * Format audit results for Slack
  1022→ */
  1023→function formatSlackReport(results) {
  1024→  const scoreEmoji = results.score >= 90 ? '🏆' : results.score >= 70 ? '✅' : results.score >= 50 ? '⚠' : '🚨';
  1025→
  1026→  let blocks = [
  1027→    {
  1028→      type: 'header',
  1029→      text: {
  1030→        type: 'plain_text',
  1031→        text: `${scoreEmoji} Daily 0.1% Quality Audit`,
  1032→        emoji: true
  1033→      }
  1034→    },
  1035→    {
  1036→      type: 'section',
  1037→      fields: [
  1038→        {
  1039→          type: 'mrkdwn',
  1040→          text: `*Overall Score*\n${results.score}/100`
  1041→        },
  1042→        {
  1043→          type: 'mrkdwn',
  1044→          text: `*Tests Run*\n${results.summary.passed + results.summary.failed}`
  1045→        },
  1046→        {
  1047→          type: 'mrkdwn',
  1048→          text: `*Passed*\n✅ ${results.summary.passed}`
  1049→        },
  1050→        {
  1051→          type: 'mrkdwn',
  1052→          text: `*Failed*\n❌ ${results.summary.failed}`
  1053→        }
  1054→      ]
  1055→    },
  1056→    {
  1057→      type: 'divider'
  1058→    }
  1059→  ];
  1060→
  1061→  // Add category breakdowns
  1062→  for (const [key, category] of Object.entries(results.categories)) {
  1063→    const label = AUDIT_CATEGORIES[key].label;
  1064→    const statusIcon = category.failed === 0 ? '✅' : '❌';
  1065→
  1066→    let testDetails = category.tests
  1067→      .map(t => {
  1068→        const icon = t.status === 'passed' ? '✓' : t.status === 'warning' ? '⚠' : '✗';
  1069→        return `${icon} ${t.name}: ${t.detail}`;
  1070→      })
  1071→      .join('\n');
  1072→
  1073→    blocks.push({
  1074→      type: 'section',
  1075→      text: {
  1076→        type: 'mrkdwn',
  1077→        text: `*${statusIcon} ${label}* (${category.passed}/${category.passed + category.failed})\n\`\`\`${testDetails}\`\`\``
  1078→      }
  1079→    });
  1080→  }
  1081→
  1082→  // Add timestamp
  1083→  blocks.push({
  1084→    type: 'context',
  1085→    elements: [
  1086→      {
  1087→        type: 'mrkdwn',
  1088→        text: `Audit completed in ${results.duration}ms | ${new Date(results.timestamp).toLocaleString('en-AU', { timeZone: 'Australia/Sydney' })}`
  1089→      }
  1090→    ]
  1091→  });
  1092→
  1093→  return { blocks };
  1094→}
  1095→
  1096→/**
  1097→ * Generate text summary for logging
  1098→ */
  1099→function formatTextReport(results) {
  1100→  let report = `\n${'='.repeat(50)}\n`;
  1101→  report += `0.1% QUALITY AUDIT REPORT\n`;
  1102→  report += `${'='.repeat(50)}\n\n`;
  1103→  report += `Score: ${results.score}/100\n`;
  1104→  report += `Passed: ${results.summary.passed} | Failed: ${results.summary.failed}\n`;
  1105→  report += `Duration: ${results.duration}ms\n\n`;
  1106→
  1107→  for (const [key, category] of Object.entries(results.categories)) {
  1108→    const label = AUDIT_CATEGORIES[key].label;
  1109→    report += `${label}\n`;
  1110→    report += `-`.repeat(30) + '\n';
  1111→
  1112→    for (const test of category.tests) {
  1113→      const icon = test.status === 'passed' ? '✅' : test.status === 'warning' ? '⚠' : '❌';
  1114→      report += `  ${icon} ${test.name}: ${test.detail}\n`;
  1115→    }
  1116→    report += '\n';
  1117→  }
  1118→
  1119→  return report;
  1120→}
  1121→
  1122→module.exports = {
  1123→  runFullAudit,
  1124→  runAPITests,
  1125→  runExternalServiceTests,
  1126→  runCriticalTests,
  1127→  runBusinessMetricTests,
  1128→  runSyntheticJourneyTests,
  1129→  runFrontendTests,
  1130→  formatSlackReport,
  1131→  formatTextReport,
  1132→  AUDIT_CATEGORIES,
  1133→  SEVERITY_MAP
  1134→};
  1135→

<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>