     1→/**
     2→ * Job Scheduler
     3→ * Manages cron jobs for the Growth Engine
     4→ */
     5→
     6→const { runPlatformSync } = require('./platformSync');
     7→const { runDailySummary } = require('./dailySummary');
     8→const { runWeeklyReport } = require('./weeklyReport');
     9→const { runActivationMetrics } = require('./activationMetrics');
    10→const { processScheduledMessages } = require('./scheduledSms');
    11→const { processSequences } = require('./sequenceProcessor');
    12→const { recalculateAllScores } = require('../services/scoring');
    13→const { awardWeeklyBadges } = require('../services/badgesService');
    14→const { saveLeaderboardSnapshot } = require('../services/leaderboardService');
    15→const { runDailyQualityAudit } = require('./dailyQualityAudit');
    16→const { runWeeklyExcellenceReport } = require('./weeklyExcellenceReport');
    17→const { runCriticalAudit, runHourlyAudit } = require('./frequentAudit');
    18→const { runWeeklyRepReport } = require('./weeklyRepReport');
    19→const { runDailyMetricsAggregation } = require('./dailyMetricsAggregation');
    20→const { runFrictionDetection } = require('./frictionDetection');
    21→const { runMonthlyDeepDive } = require('./monthlyDeepDive');
    22→const { runPromptEvolution } = require('./promptEvolution');
    23→const { runImpactMeasurement } = require('./impactMeasurement');
    24→const { runBackfillHandledBy } = require('./backfillHandledBy');
    25→const { runReleaseStaleAssignments } = require('./releaseStaleAssignments');
    26→const { run: runRefreshCallListScores } = require('./refreshCallListScores');
    27→const { runJobWithLock } = require('../services/jobLocking');
    28→// const { startCallbackReminder } = require('./callbackReminder'); // Temporarily disabled
    29→
    30→// Simple interval-based scheduler
    31→// In production, consider using node-cron or a proper job queue
    32→
    33→let intervals = [];
    34→let weeklyTimeout = null;
    35→let badgesTimeout = null;
    36→let qualityAuditTimeout = null;
    37→let excellenceReportTimeout = null;
    38→let metricsAggregationTimeout = null;
    39→let monthlyDeepDiveTimeout = null;
    40→let promptEvolutionTimeout = null;
    41→
    42→/**
    43→ * Calculate milliseconds until next Monday 7am AEST
    44→ */
    45→function msUntilMondayAEST() {
    46→  const now = new Date();
    47→  // AEST is UTC+10 (simplified - ignores daylight saving)
    48→  const aestOffset = 10 * 60 * 60 * 1000;
    49→  const nowAEST = new Date(now.getTime() + aestOffset);
    50→
    51→  // Find next Monday
    52→  const daysUntilMonday = (8 - nowAEST.getDay()) % 7 || 7;
    53→  const nextMonday = new Date(nowAEST);
    54→  nextMonday.setDate(nowAEST.getDate() + daysUntilMonday);
    55→  nextMonday.setHours(7, 0, 0, 0);
    56→
    57→  // Convert back to local time
    58→  const nextMondayUTC = new Date(nextMonday.getTime() - aestOffset);
    59→  return Math.max(0, nextMondayUTC.getTime() - now.getTime());
    60→}
    61→
    62→/**
    63→ * Schedule weekly report for Monday 7am AEST
    64→ */
    65→function scheduleWeeklyReport() {
    66→  const ms = msUntilMondayAEST();
    67→  const hours = Math.round(ms / (60 * 60 * 1000));
    68→  console.log(`[Jobs] Weekly report scheduled in ${hours} hours`);
    69→
    70→  weeklyTimeout = setTimeout(async () => {
    71→    console.log('[Jobs] Running weekly report...');
    72→    await runWeeklyReport().catch(console.error);
    73→    // Reschedule for next week
    74→    scheduleWeeklyReport();
    75→  }, ms);
    76→}
    77→
    78→/**
    79→ * Calculate milliseconds until next Friday 3pm AEST
    80→ */
    81→function msUntilFridayAEST() {
    82→  const now = new Date();
    83→  // AEST is UTC+10 (simplified - ignores daylight saving)
    84→  const aestOffset = 10 * 60 * 60 * 1000;
    85→  const nowAEST = new Date(now.getTime() + aestOffset);
    86→
    87→  // Find next Friday (day 5)
    88→  const dayOfWeek = nowAEST.getDay();
    89→  let daysUntilFriday = (5 - dayOfWeek + 7) % 7;
    90→  if (daysUntilFriday === 0 && nowAEST.getHours() >= 15) {
    91→    daysUntilFriday = 7; // Already past 3pm Friday, schedule for next week
    92→  }
    93→
    94→  const nextFriday = new Date(nowAEST);
    95→  nextFriday.setDate(nowAEST.getDate() + daysUntilFriday);
    96→  nextFriday.setHours(15, 0, 0, 0); // 3pm AEST
    97→
    98→  // Convert back to local time
    99→  const nextFridayUTC = new Date(nextFriday.getTime() - aestOffset);
   100→  return Math.max(0, nextFridayUTC.getTime() - now.getTime());
   101→}
   102→
   103→/**
   104→ * Schedule weekly badges for Friday 3pm AEST
   105→ */
   106→function scheduleWeeklyBadges() {
   107→  const ms = msUntilFridayAEST();
   108→  const hours = Math.round(ms / (60 * 60 * 1000));
   109→  console.log(`[Jobs] Weekly badges scheduled in ${hours} hours`);
   110→
   111→  badgesTimeout = setTimeout(async () => {
   112→    console.log('[Jobs] Awarding weekly badges...');
   113→    await awardWeeklyBadges().catch(console.error);
   114→    // Reschedule for next week
   115→    scheduleWeeklyBadges();
   116→  }, ms);
   117→}
   118→
   119→/**
   120→ * Calculate milliseconds until next 6am AEST
   121→ */
   122→function msUntil6amAEST() {
   123→  const now = new Date();
   124→  // AEST is UTC+10 (simplified - ignores daylight saving)
   125→  const aestOffset = 10 * 60 * 60 * 1000;
   126→  const nowAEST = new Date(now.getTime() + aestOffset);
   127→
   128→  // Set target to 6am today
   129→  const target = new Date(nowAEST);
   130→  target.setHours(6, 0, 0, 0);
   131→
   132→  // If we've already passed 6am today, schedule for tomorrow
   133→  if (nowAEST >= target) {
   134→    target.setDate(target.getDate() + 1);
   135→  }
   136→
   137→  // Convert back to local time
   138→  const targetUTC = new Date(target.getTime() - aestOffset);
   139→  return Math.max(0, targetUTC.getTime() - now.getTime());
   140→}
   141→
   142→/**
   143→ * Schedule daily quality audit for 6am AEST
   144→ */
   145→function scheduleDailyQualityAudit() {
   146→  const ms = msUntil6amAEST();
   147→  const hours = Math.round(ms / (60 * 60 * 1000));
   148→  console.log(`[Jobs] Daily quality audit scheduled in ${hours} hours`);
   149→
   150→  qualityAuditTimeout = setTimeout(async () => {
   151→    console.log('[Jobs] Running daily quality audit...');
   152→    await runDailyQualityAudit().catch(console.error);
   153→    // Reschedule for tomorrow
   154→    scheduleDailyQualityAudit();
   155→  }, ms);
   156→}
   157→
   158→/**
   159→ * Calculate milliseconds until next Monday 7:15am AEST
   160→ */
   161→function msUntilMonday715AEST() {
   162→  const now = new Date();
   163→  const aestOffset = 10 * 60 * 60 * 1000;
   164→  const nowAEST = new Date(now.getTime() + aestOffset);
   165→
   166→  // Find next Monday
   167→  const daysUntilMonday = (8 - nowAEST.getDay()) % 7 || 7;
   168→  const nextMonday = new Date(nowAEST);
   169→  nextMonday.setDate(nowAEST.getDate() + daysUntilMonday);
   170→  nextMonday.setHours(7, 15, 0, 0); // 7:15am AEST
   171→
   172→  // Convert back to local time
   173→  const nextMondayUTC = new Date(nextMonday.getTime() - aestOffset);
   174→  return Math.max(0, nextMondayUTC.getTime() - now.getTime());
   175→}
   176→
   177→/**
   178→ * Schedule weekly excellence report for Monday 7:15am AEST
   179→ */
   180→function scheduleWeeklyExcellenceReport() {
   181→  const ms = msUntilMonday715AEST();
   182→  const hours = Math.round(ms / (60 * 60 * 1000));
   183→  console.log(`[Jobs] Weekly excellence report scheduled in ${hours} hours`);
   184→
   185→  excellenceReportTimeout = setTimeout(async () => {
   186→    console.log('[Jobs] Running weekly excellence report...');
   187→    await runWeeklyExcellenceReport().catch(console.error);
   188→    // Reschedule for next week
   189→    scheduleWeeklyExcellenceReport();
   190→  }, ms);
   191→}
   192→
   193→/**
   194→ * Calculate milliseconds until next 1am AEST
   195→ */
   196→function msUntil1amAEST() {
   197→  const now = new Date();
   198→  // AEST is UTC+10 (simplified - ignores daylight saving)
   199→  const aestOffset = 10 * 60 * 60 * 1000;
   200→  const nowAEST = new Date(now.getTime() + aestOffset);
   201→
   202→  // Set target to 1am today
   203→  const target = new Date(nowAEST);
   204→  target.setHours(1, 0, 0, 0);
   205→
   206→  // If we've already passed 1am today, schedule for tomorrow
   207→  if (nowAEST >= target) {
   208→    target.setDate(target.getDate() + 1);
   209→  }
   210→
   211→  // Convert back to local time
   212→  const targetUTC = new Date(target.getTime() - aestOffset);
   213→  return Math.max(0, targetUTC.getTime() - now.getTime());
   214→}
   215→
   216→/**
   217→ * Schedule daily metrics aggregation for 1am AEST
   218→ */
   219→function scheduleDailyMetricsAggregation() {
   220→  const ms = msUntil1amAEST();
   221→  const hours = Math.round(ms / (60 * 60 * 1000));
   222→  console.log(`[Jobs] Daily metrics aggregation scheduled in ${hours} hours`);
   223→
   224→  metricsAggregationTimeout = setTimeout(async () => {
   225→    console.log('[Jobs] Running daily metrics aggregation...');
   226→    await runDailyMetricsAggregation().catch(console.error);
   227→    // Reschedule for tomorrow
   228→    scheduleDailyMetricsAggregation();
   229→  }, ms);
   230→}
   231→
   232→/**
   233→ * Calculate milliseconds until 1st of next month 8am AEST
   234→ */
   235→function msUntilFirstOfMonth8amAEST() {
   236→  const now = new Date();
   237→  const aestOffset = 10 * 60 * 60 * 1000;
   238→  const nowAEST = new Date(now.getTime() + aestOffset);
   239→
   240→  // Target: 1st of next month at 8am
   241→  let targetMonth = nowAEST.getMonth() + 1;
   242→  let targetYear = nowAEST.getFullYear();
   243→  if (targetMonth > 11) {
   244→    targetMonth = 0;
   245→    targetYear++;
   246→  }
   247→
   248→  const target = new Date(targetYear, targetMonth, 1, 8, 0, 0, 0);
   249→
   250→  // Convert back to UTC
   251→  const targetUTC = new Date(target.getTime() - aestOffset);
   252→  return Math.max(0, targetUTC.getTime() - now.getTime());
   253→}
   254→
   255→/**
   256→ * Schedule monthly deep dive for 1st of month 8am AEST
   257→ */
   258→function scheduleMonthlyDeepDive() {
   259→  const ms = msUntilFirstOfMonth8amAEST();
   260→  const days = Math.round(ms / (24 * 60 * 60 * 1000));
   261→  console.log(`[Jobs] Monthly deep dive scheduled in ${days} days`);
   262→
   263→  monthlyDeepDiveTimeout = setTimeout(async () => {
   264→    console.log('[Jobs] Running monthly deep dive...');
   265→    await runMonthlyDeepDive().catch(console.error);
   266→    // Reschedule for next month
   267→    scheduleMonthlyDeepDive();
   268→  }, ms);
   269→}
   270→
   271→/**
   272→ * Calculate milliseconds until 15th of next month 9am AEST
   273→ * Runs mid-month after monthly deep dive has collected data
   274→ */
   275→function msUntil15thOfMonth9amAEST() {
   276→  const now = new Date();
   277→  const aestOffset = 10 * 60 * 60 * 1000;
   278→  const nowAEST = new Date(now.getTime() + aestOffset);
   279→
   280→  // Target: 15th of current or next month at 9am
   281→  let targetMonth = nowAEST.getMonth();
   282→  let targetYear = nowAEST.getFullYear();
   283→
   284→  // If past 15th, schedule for next month
   285→  if (nowAEST.getDate() > 15 || (nowAEST.getDate() === 15 && nowAEST.getHours() >= 9)) {
   286→    targetMonth++;
   287→    if (targetMonth > 11) {
   288→      targetMonth = 0;
   289→      targetYear++;
   290→    }
   291→  }
   292→
   293→  const target = new Date(targetYear, targetMonth, 15, 9, 0, 0, 0);
   294→
   295→  // Convert back to UTC
   296→  const targetUTC = new Date(target.getTime() - aestOffset);
   297→  return Math.max(0, targetUTC.getTime() - now.getTime());
   298→}
   299→
   300→/**
   301→ * Schedule prompt evolution for 15th of month 9am AEST
   302→ * DISABLED: Was causing Slack spam - needs investigation
   303→ */
   304→function schedulePromptEvolution() {
   305→  // TEMPORARILY DISABLED - causing infinite loop
   306→  console.log('[Jobs] Prompt evolution DISABLED - was causing spam');
   307→  return;
   308→
   309→  const ms = msUntil15thOfMonth9amAEST();
   310→  const days = Math.round(ms / (24 * 60 * 60 * 1000));
   311→  console.log(`[Jobs] Prompt evolution scheduled in ${days} days`);
   312→
   313→  promptEvolutionTimeout = setTimeout(async () => {
   314→    console.log('[Jobs] Running prompt evolution...');
   315→    await runPromptEvolution().catch(console.error);
   316→    // Reschedule for next month
   317→    schedulePromptEvolution();
   318→  }, ms);
   319→}
   320→
   321→/**
   322→ * Start all scheduled jobs
   323→ */
   324→function startJobs() {
   325→  console.log('Starting scheduled jobs...');
   326→
   327→  // Platform sync every 15 minutes
   328→  intervals.push(
   329→    setInterval(() => {
   330→      runPlatformSync().catch(console.error);
   331→    }, 15 * 60 * 1000)
   332→  );
   333→
   334→  // Critical audit every 15 minutes (checks DB, API health)
   335→  intervals.push(
   336→    setInterval(() => {
   337→      runCriticalAudit().catch(console.error);
   338→    }, 15 * 60 * 1000)
   339→  );
   340→
   341→  // Hourly audit (checks external services: Twilio, OpenAI, Deepgram)
   342→  intervals.push(
   343→    setInterval(() => {
   344→      runHourlyAudit().catch(console.error);
   345→    }, 60 * 60 * 1000)
   346→  );
   347→
   348→  // Score recalculation every hour
   349→  intervals.push(
   350→    setInterval(() => {
   351→      recalculateAllScores().catch(console.error);
   352→    }, 60 * 60 * 1000)
   353→  );
   354→
   355→  // Activation metrics every 6 hours
   356→  intervals.push(
   357→    setInterval(() => {
   358→      runActivationMetrics().catch(console.error);
   359→    }, 6 * 60 * 60 * 1000)
   360→  );
   361→
   362→  // Impact measurement every 24 hours (checks for suggestions needing measurement)
   363→  intervals.push(
   364→    setInterval(() => {
   365→      runImpactMeasurement().catch(console.error);
   366→    }, 24 * 60 * 60 * 1000)
   367→  );
   368→
   369→  // FIXED: Only run sequence processor - scheduledSms.js is redundant
   370→  // Both jobs were sending SMS from sequences, causing potential duplicates
   371→  // Sequence processor handles both scheduled messages AND sequence steps
   372→  intervals.push(
   373→    setInterval(() => {
   374→      runJobWithLock('sequence_processor', processSequences, 4 * 60 * 1000)
   375→        .then(result => {
   376→          if (result.skipped) {
   377→            console.log('[Jobs] Sequence processor skipped - already running');
   378→          }
   379→        })
   380→        .catch(console.error);
   381→    }, 60 * 1000)
   382→  );
   383→
   384→  // DISABLED: scheduledSms.js - redundant with sequenceProcessor.js
   385→  // intervals.push(
   386→  //   setInterval(() => {
   387→  //     processScheduledMessages().catch(console.error);
   388→  //   }, 60 * 1000)
   389→  // );
   390→
   391→  // Release stale lead assignments every hour
   392→  intervals.push(
   393→    setInterval(() => {
   394→      runReleaseStaleAssignments().catch(console.error);
   395→    }, 60 * 60 * 1000)
   396→  );
   397→
   398→  // Refresh call list scores every 4 hours (fixes stale "Replied 0 mins ago" bug)
   399→  intervals.push(
   400→    setInterval(() => {
   401→      runRefreshCallListScores().catch(console.error);
   402→    }, 4 * 60 * 60 * 1000)
   403→  );
   404→
   405→  // Run activation metrics once at startup
   406→  runActivationMetrics().catch(console.error);
   407→
   408→  // Process any pending sequence steps at startup (includes scheduled messages)
   409→  processSequences().catch(console.error);
   410→
   411→  // DISABLED: scheduledSms.js - redundant with sequenceProcessor.js
   412→  // processScheduledMessages().catch(console.error);
   413→
   414→  // Daily summary at startup (for testing)
   415→  // In production, use node-cron for 7am AEST
   416→  if (process.env.RUN_DAILY_SUMMARY_ON_START === 'true') {
   417→    runDailySummary().catch(console.error);
   418→  }
   419→
   420→  // DISABLED - Old Slack reports replaced by COO brief system on VPS
   421→  // scheduleWeeklyReport();        // Weekly report - Monday 7am AEST
   422→  // scheduleDailyQualityAudit();   // Daily quality audit - 6am AEST
   423→  // scheduleWeeklyExcellenceReport(); // Weekly excellence report - Monday 7:15am AEST
   424→  // scheduleMonthlyDeepDive();     // Monthly deep dive - 1st of month 8am AEST
   425→  // schedulePromptEvolution();     // Prompt evolution - 15th of month 9am AEST
   426→
   427→  // Weekly badges - Friday 3pm AEST (keep - gamification)
   428→  scheduleWeeklyBadges();
   429→
   430→  // Daily metrics aggregation - 1am AEST (keep - internal data, no Slack)
   431→  scheduleDailyMetricsAggregation();
   432→
   433→  // Run quality audit at startup if testing or RUN_QUALITY_AUDIT_NOW
   434→  if (process.env.RUN_QUALITY_AUDIT_ON_START === 'true') {
   435→    runDailyQualityAudit().catch(console.error);
   436→  }
   437→
   438→  // Run excellence report at startup if testing
   439→  if (process.env.RUN_EXCELLENCE_REPORT_ON_START === 'true') {
   440→    runWeeklyExcellenceReport().catch(console.error);
   441→  }
   442→
   443→  // Run metrics aggregation at startup if testing
   444→  if (process.env.RUN_METRICS_AGGREGATION_ON_START === 'true') {
   445→    runDailyMetricsAggregation().catch(console.error);
   446→  }
   447→
   448→  // Run monthly deep dive at startup if testing
   449→  if (process.env.RUN_MONTHLY_DEEP_DIVE_ON_START === 'true') {
   450→    runMonthlyDeepDive().catch(console.error);
   451→  }
   452→
   453→  // Run prompt evolution at startup if testing
   454→  if (process.env.RUN_PROMPT_EVOLUTION_ON_START === 'true') {
   455→    runPromptEvolution().catch(console.error);
   456→  }
   457→
   458→  // Leaderboard snapshot every hour (for position tracking)
   459→  intervals.push(
   460→    setInterval(() => {
   461→      saveLeaderboardSnapshot('today').catch(console.error);
   462→    }, 60 * 60 * 1000)
   463→  );
   464→
   465→  // Run weekly report at startup if testing
   466→  if (process.env.RUN_WEEKLY_REPORT_ON_START === 'true') {
   467→    runWeeklyReport().catch(console.error);
   468→  }
   469→
   470→  // Run weekly badges at startup if testing
   471→  if (process.env.RUN_WEEKLY_BADGES_ON_START === 'true') {
   472→    awardWeeklyBadges().catch(console.error);
   473→  }
   474→
   475→  // Start callback reminder job (every 5 min)
   476→  // TEMPORARILY DISABLED for debugging deploy issue
   477→  // startCallbackReminder();
   478→
   479→  console.log('Scheduled jobs started');
   480→}
   481→
   482→/**
   483→ * Stop all scheduled jobs
   484→ */
   485→function stopJobs() {
   486→  intervals.forEach(clearInterval);
   487→  intervals = [];
   488→  if (weeklyTimeout) {
   489→    clearTimeout(weeklyTimeout);
   490→    weeklyTimeout = null;
   491→  }
   492→  if (badgesTimeout) {
   493→    clearTimeout(badgesTimeout);
   494→    badgesTimeout = null;
   495→  }
   496→  if (qualityAuditTimeout) {
   497→    clearTimeout(qualityAuditTimeout);
   498→    qualityAuditTimeout = null;
   499→  }
   500→  if (excellenceReportTimeout) {
   501→    clearTimeout(excellenceReportTimeout);
   502→    excellenceReportTimeout = null;
   503→  }
   504→  if (metricsAggregationTimeout) {
   505→    clearTimeout(metricsAggregationTimeout);
   506→    metricsAggregationTimeout = null;
   507→  }
   508→  if (monthlyDeepDiveTimeout) {
   509→    clearTimeout(monthlyDeepDiveTimeout);
   510→    monthlyDeepDiveTimeout = null;
   511→  }
   512→  if (promptEvolutionTimeout) {
   513→    clearTimeout(promptEvolutionTimeout);
   514→    promptEvolutionTimeout = null;
   515→  }
   516→  console.log('Scheduled jobs stopped');
   517→}
   518→
   519→/**
   520→ * Run a specific job manually
   521→ * @param {string} jobName - Job to run
   522→ */
   523→async function runJob(jobName) {
   524→  switch (jobName) {
   525→    case 'platformSync':
   526→      return runPlatformSync();
   527→    case 'dailySummary':
   528→      return runDailySummary();
   529→    case 'weeklyReport':
   530→      return runWeeklyReport();
   531→    case 'weeklyRepReport':
   532→      return runWeeklyRepReport();
   533→    case 'activationMetrics':
   534→      return runActivationMetrics();
   535→    case 'scheduledSms':
   536→      // DISABLED: Use 'sequences' endpoint instead - scheduledSms.js is redundant
   537→      return { success: false, error: 'scheduledSms job disabled - use sequences endpoint instead' };
   538→    case 'sequences':
   539→      return processSequences();
   540→    case 'recalculateScores':
   541→      return recalculateAllScores();
   542→    case 'weeklyBadges':
   543→      return awardWeeklyBadges();
   544→    case 'leaderboardSnapshot':
   545→      return saveLeaderboardSnapshot('today');
   546→    case 'qualityAudit':
   547→      return runDailyQualityAudit();
   548→    case 'criticalAudit':
   549→      return runCriticalAudit();
   550→    case 'hourlyAudit':
   551→      return runHourlyAudit();
   552→    case 'excellenceReport':
   553→      return runWeeklyExcellenceReport();
   554→    case 'metricsAggregation':
   555→      return runDailyMetricsAggregation();
   556→    case 'frictionDetection':
   557→      return runFrictionDetection();
   558→    case 'monthlyDeepDive':
   559→      return runMonthlyDeepDive();
   560→    case 'promptEvolution':
   561→      // DISABLED - was causing infinite loop spam
   562→      return { success: false, error: 'Prompt evolution temporarily disabled - was causing spam' };
   563→    case 'impactMeasurement':
   564→      return runImpactMeasurement();
   565→    case 'backfillHandledBy':
   566→      return runBackfillHandledBy();
   567→    case 'releaseStaleAssignments':
   568→      return runReleaseStaleAssignments();
   569→    case 'refreshCallListScores':
   570→      return runRefreshCallListScores();
   571→    default:
   572→      throw new Error(`Unknown job: ${jobName}`);
   573→  }
   574→}
   575→
   576→module.exports = {
   577→  startJobs,
   578→  stopJobs,
   579→  runJob
   580→};
   581→

<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→ * Backfill handled_by column from metadata
     3→ * One-time migration job
     4→ */
     5→
     6→const { supabase, supabaseAdmin } = require('../config/database');
     7→
     8→/**
     9→ * Backfill handled_by column from metadata for existing communications
    10→ */
    11→async function runBackfillHandledBy() {
    12→  console.log('[BackfillHandledBy] Starting backfill...');
    13→
    14→  const db = supabaseAdmin || supabase;
    15→  if (!db) {
    16→    return { success: false, error: 'Database not configured' };
    17→  }
    18→
    19→  // First, add the column if it doesn't exist using raw SQL
    20→  console.log('[BackfillHandledBy] Ensuring handled_by column exists...');
    21→  const { error: alterError } = await db.rpc('exec_sql', {
    22→    sql: 'ALTER TABLE communications ADD COLUMN IF NOT EXISTS handled_by UUID'
    23→  }).maybeSingle();
    24→
    25→  // If rpc doesn't exist, try direct approach
    26→  if (alterError && alterError.message?.includes('function')) {
    27→    console.log('[BackfillHandledBy] Trying direct column add via supabase...');
    28→    // Column might already exist, try to proceed
    29→  }
    30→
    31→  // Get count before
    32→  let beforeCount = 0;
    33→  try {
    34→    const { count } = await db
    35→      .from('communications')
    36→      .select('*', { count: 'exact', head: true })
    37→      .not('handled_by', 'is', null);
    38→    beforeCount = count || 0;
    39→  } catch (e) {
    40→    console.log('[BackfillHandledBy] Column check failed, might not exist yet:', e.message);
    41→  }
    42→
    43→  console.log(`[BackfillHandledBy] Before: ${beforeCount} communications with handled_by set`);
    44→
    45→  // Get communications with metadata.handled_by but no top-level handled_by
    46→  const { data: commsToUpdate, error: fetchError } = await db
    47→    .from('communications')
    48→    .select('id, metadata')
    49→    .is('handled_by', null)
    50→    .not('metadata', 'is', null)
    51→    .limit(1000); // Process in batches
    52→
    53→  if (fetchError) {
    54→    console.error('[BackfillHandledBy] Fetch error:', fetchError.message);
    55→    return { success: false, error: fetchError.message };
    56→  }
    57→
    58→  console.log(`[BackfillHandledBy] Found ${commsToUpdate?.length || 0} communications to check`);
    59→
    60→  let updated = 0;
    61→  let skipped = 0;
    62→
    63→  // UUID regex pattern
    64→  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    65→
    66→  for (const comm of (commsToUpdate || [])) {
    67→    // Try to find a valid UUID in metadata
    68→    const handledBy = comm.metadata?.handled_by || comm.metadata?.user_id;
    69→
    70→    if (!handledBy || !uuidRegex.test(handledBy)) {
    71→      skipped++;
    72→      continue;
    73→    }
    74→
    75→    // Update the communication
    76→    const { error: updateError } = await db
    77→      .from('communications')
    78→      .update({ handled_by: handledBy })
    79→      .eq('id', comm.id);
    80→
    81→    if (updateError) {
    82→      console.error(`[BackfillHandledBy] Update error for ${comm.id}:`, updateError.message);
    83→      skipped++;
    84→    } else {
    85→      updated++;
    86→    }
    87→  }
    88→
    89→  // Get count after
    90→  const { count: afterCount } = await db
    91→    .from('communications')
    92→    .select('*', { count: 'exact', head: true })
    93→    .not('handled_by', 'is', null);
    94→
    95→  console.log(`[BackfillHandledBy] After: ${afterCount} communications with handled_by set`);
    96→  console.log(`[BackfillHandledBy] Updated: ${updated}, Skipped: ${skipped}`);
    97→
    98→  return {
    99→    success: true,
   100→    before: beforeCount,
   101→    after: afterCount,
   102→    updated,
   103→    skipped,
   104→  };
   105→}
   106→
   107→module.exports = {
   108→  runBackfillHandledBy,
   109→};
   110→

<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→ * Callback Reminder Job
     3→ * Runs every 5 minutes, sends push notifications for upcoming callbacks
     4→ */
     5→
     6→const { supabase, supabaseAdmin } = require('../config/database');
     7→const { notifyCallbackDue } = require('../services/pushNotifications');
     8→
     9→const db = supabaseAdmin || supabase;
    10→
    11→// Track if job is already running to prevent overlap
    12→let isRunning = false;
    13→
    14→/**
    15→ * Check for upcoming callbacks and send notifications
    16→ */
    17→async function checkUpcomingCallbacks() {
    18→  if (isRunning) {
    19→    console.log('[CallbackReminder] Already running, skipping');
    20→    return;
    21→  }
    22→
    23→  isRunning = true;
    24→  console.log('[CallbackReminder] Checking for upcoming callbacks...');
    25→
    26→  try {
    27→    // Get callbacks due in next 15 minutes that haven't been notified
    28→    const fifteenMinutesFromNow = new Date(Date.now() + 15 * 60 * 1000).toISOString();
    29→    const now = new Date().toISOString();
    30→
    31→    const { data: callbacks, error } = await db
    32→      .from('callbacks')
    33→      .select(`
    34→        id,
    35→        lead_id,
    36→        scheduled_at,
    37→        reason,
    38→        user_id,
    39→        leads (
    40→          id,
    41→          first_name,
    42→          last_name,
    43→          phone,
    44→          company
    45→        )
    46→      `)
    47→      .eq('status', 'pending')
    48→      .gte('scheduled_at', now)
    49→      .lte('scheduled_at', fifteenMinutesFromNow)
    50→      .is('notified_at', null)
    51→      .limit(50);
    52→
    53→    if (error) {
    54→      console.error('[CallbackReminder] Error fetching callbacks:', error);
    55→      return;
    56→    }
    57→
    58→    console.log(`[CallbackReminder] Found ${callbacks?.length || 0} callbacks due soon`);
    59→
    60→    let notified = 0;
    61→    for (const callback of (callbacks || [])) {
    62→      try {
    63→        const scheduledTime = new Date(callback.scheduled_at);
    64→        const minutesUntil = Math.max(1, Math.round((scheduledTime - Date.now()) / (1000 * 60)));
    65→
    66→        // Send push notification
    67→        const result = await notifyCallbackDue(callback, minutesUntil);
    68→
    69→        if (result.sent > 0) {
    70→          notified++;
    71→        }
    72→
    73→        // Mark as notified regardless of push success
    74→        // (prevents repeated attempts if user has no subscriptions)
    75→        await db
    76→          .from('callbacks')
    77→          .update({ notified_at: new Date().toISOString() })
    78→          .eq('id', callback.id);
    79→
    80→      } catch (callbackError) {
    81→        console.error(`[CallbackReminder] Error processing callback ${callback.id}:`, callbackError);
    82→      }
    83→    }
    84→
    85→    if (notified > 0) {
    86→      console.log(`[CallbackReminder] Sent ${notified} callback reminders`);
    87→    }
    88→
    89→    // Log overdue callbacks (for monitoring)
    90→    const { data: overdue, error: overdueError } = await db
    91→      .from('callbacks')
    92→      .select('id')
    93→      .eq('status', 'pending')
    94→      .lt('scheduled_at', now)
    95→      .limit(100);
    96→
    97→    if (!overdueError && overdue?.length > 0) {
    98→      console.log(`[CallbackReminder] Warning: ${overdue.length} overdue callbacks`);
    99→    }
   100→
   101→  } catch (error) {
   102→    console.error('[CallbackReminder] Job error:', error);
   103→  } finally {
   104→    isRunning = false;
   105→  }
   106→}
   107→
   108→/**
   109→ * Start the callback reminder job
   110→ * Runs every 5 minutes
   111→ */
   112→function startCallbackReminder() {
   113→  console.log('[CallbackReminder] Starting callback reminder job (every 5 min)');
   114→
   115→  // Run immediately on startup (with small delay to let other services initialize)
   116→  setTimeout(checkUpcomingCallbacks, 10000);
   117→
   118→  // Then every 5 minutes
   119→  setInterval(checkUpcomingCallbacks, 5 * 60 * 1000);
   120→}
   121→
   122→module.exports = {
   123→  startCallbackReminder,
   124→  checkUpcomingCallbacks
   125→};
   126→

<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→ * Daily Metrics Aggregation Job
     3→ * Runs daily at 1am AEST to aggregate usage_events into intelligence_daily_metrics
     4→ *
     5→ * Aggregates:
     6→ * - Page abandonment rates
     7→ * - Click patterns by page
     8→ * - Error rates
     9→ * - Flow completion rates
    10→ * - Average session durations
    11→ */
    12→
    13→const { supabase, supabaseAdmin } = require('../config/database');
    14→
    15→const db = supabaseAdmin || supabase;
    16→
    17→/**
    18→ * Calculate metrics for a specific date
    19→ */
    20→async function aggregateMetricsForDate(date) {
    21→  const startOfDay = new Date(date);
    22→  startOfDay.setHours(0, 0, 0, 0);
    23→
    24→  const endOfDay = new Date(date);
    25→  endOfDay.setHours(23, 59, 59, 999);
    26→
    27→  const dateStr = startOfDay.toISOString().split('T')[0];
    28→  console.log(`[Metrics] Aggregating metrics for ${dateStr}...`);
    29→
    30→  const metrics = [];
    31→
    32→  // 1. Page view counts
    33→  const { data: pageViews, error: pvError } = await db
    34→    .from('usage_events')
    35→    .select('page')
    36→    .eq('event_type', 'page_view')
    37→    .gte('created_at', startOfDay.toISOString())
    38→    .lte('created_at', endOfDay.toISOString());
    39→
    40→  if (!pvError && pageViews) {
    41→    const pageCounts = {};
    42→    pageViews.forEach(e => {
    43→      if (e.page) {
    44→        pageCounts[e.page] = (pageCounts[e.page] || 0) + 1;
    45→      }
    46→    });
    47→
    48→    const totalViews = Object.values(pageCounts).reduce((a, b) => a + b, 0);
    49→    metrics.push({
    50→      date: dateStr,
    51→      metric_name: 'total_page_views',
    52→      metric_value: totalViews,
    53→      metric_context: { by_page: pageCounts },
    54→      sample_size: pageViews.length
    55→    });
    56→  }
    57→
    58→  // 2. Flow completion rates by page
    59→  const { data: flowEvents, error: feError } = await db
    60→    .from('usage_events')
    61→    .select('page, event_type')
    62→    .in('event_type', ['flow_complete', 'flow_abandon'])
    63→    .gte('created_at', startOfDay.toISOString())
    64→    .lte('created_at', endOfDay.toISOString());
    65→
    66→  if (!feError && flowEvents) {
    67→    const flowStats = {};
    68→    flowEvents.forEach(e => {
    69→      if (e.page) {
    70→        if (!flowStats[e.page]) {
    71→          flowStats[e.page] = { complete: 0, abandon: 0 };
    72→        }
    73→        if (e.event_type === 'flow_complete') {
    74→          flowStats[e.page].complete++;
    75→        } else {
    76→          flowStats[e.page].abandon++;
    77→        }
    78→      }
    79→    });
    80→
    81→    // Calculate overall completion rate
    82→    let totalComplete = 0;
    83→    let totalAbandon = 0;
    84→    const pageRates = {};
    85→
    86→    Object.entries(flowStats).forEach(([page, stats]) => {
    87→      totalComplete += stats.complete;
    88→      totalAbandon += stats.abandon;
    89→      const total = stats.complete + stats.abandon;
    90→      pageRates[page] = total > 0 ? Math.round((stats.complete / total) * 100) : 0;
    91→    });
    92→
    93→    const overallRate = (totalComplete + totalAbandon) > 0
    94→      ? Math.round((totalComplete / (totalComplete + totalAbandon)) * 100)
    95→      : 0;
    96→
    97→    metrics.push({
    98→      date: dateStr,
    99→      metric_name: 'flow_completion_rate',
   100→      metric_value: overallRate,
   101→      metric_context: { by_page: pageRates, raw: flowStats },
   102→      sample_size: totalComplete + totalAbandon
   103→    });
   104→  }
   105→
   106→  // 3. Error rates
   107→  const { data: errorEvents, error: errError } = await db
   108→    .from('usage_events')
   109→    .select('page, event_name')
   110→    .eq('event_type', 'error')
   111→    .gte('created_at', startOfDay.toISOString())
   112→    .lte('created_at', endOfDay.toISOString());
   113→
   114→  if (!errError && errorEvents) {
   115→    const errorsByPage = {};
   116→    const errorsByType = {};
   117→
   118→    errorEvents.forEach(e => {
   119→      if (e.page) {
   120→        errorsByPage[e.page] = (errorsByPage[e.page] || 0) + 1;
   121→      }
   122→      if (e.event_name) {
   123→        errorsByType[e.event_name] = (errorsByType[e.event_name] || 0) + 1;
   124→      }
   125→    });
   126→
   127→    metrics.push({
   128→      date: dateStr,
   129→      metric_name: 'total_errors',
   130→      metric_value: errorEvents.length,
   131→      metric_context: { by_page: errorsByPage, by_type: errorsByType },
   132→      sample_size: errorEvents.length
   133→    });
   134→  }
   135→
   136→  // 4. Rage clicks
   137→  const { data: rageClicks, error: rcError } = await db
   138→    .from('usage_events')
   139→    .select('page')
   140→    .eq('event_type', 'rage_click')
   141→    .gte('created_at', startOfDay.toISOString())
   142→    .lte('created_at', endOfDay.toISOString());
   143→
   144→  if (!rcError && rageClicks) {
   145→    const rageByPage = {};
   146→    rageClicks.forEach(e => {
   147→      if (e.page) {
   148→        rageByPage[e.page] = (rageByPage[e.page] || 0) + 1;
   149→      }
   150→    });
   151→
   152→    metrics.push({
   153→      date: dateStr,
   154→      metric_name: 'rage_clicks',
   155→      metric_value: rageClicks.length,
   156→      metric_context: { by_page: rageByPage },
   157→      sample_size: rageClicks.length
   158→    });
   159→  }
   160→
   161→  // 5. Click engagement (button clicks)
   162→  const { data: clicks, error: clickError } = await db
   163→    .from('usage_events')
   164→    .select('page, event_name')
   165→    .eq('event_type', 'button_click')
   166→    .gte('created_at', startOfDay.toISOString())
   167→    .lte('created_at', endOfDay.toISOString());
   168→
   169→  if (!clickError && clicks) {
   170→    const clicksByPage = {};
   171→    const clicksByAction = {};
   172→
   173→    clicks.forEach(e => {
   174→      if (e.page) {
   175→        clicksByPage[e.page] = (clicksByPage[e.page] || 0) + 1;
   176→      }
   177→      if (e.event_name) {
   178→        clicksByAction[e.event_name] = (clicksByAction[e.event_name] || 0) + 1;
   179→      }
   180→    });
   181→
   182→    metrics.push({
   183→      date: dateStr,
   184→      metric_name: 'total_clicks',
   185→      metric_value: clicks.length,
   186→      metric_context: { by_page: clicksByPage, by_action: clicksByAction },
   187→      sample_size: clicks.length
   188→    });
   189→  }
   190→
   191→  // 6. Unique users
   192→  const { data: users, error: userError } = await db
   193→    .from('usage_events')
   194→    .select('user_id')
   195→    .not('user_id', 'is', null)
   196→    .gte('created_at', startOfDay.toISOString())
   197→    .lte('created_at', endOfDay.toISOString());
   198→
   199→  if (!userError && users) {
   200→    const uniqueUsers = new Set(users.map(u => u.user_id)).size;
   201→    metrics.push({
   202→      date: dateStr,
   203→      metric_name: 'unique_active_users',
   204→      metric_value: uniqueUsers,
   205→      metric_context: {},
   206→      sample_size: users.length
   207→    });
   208→  }
   209→
   210→  // 7. Device breakdown
   211→  const { data: devices, error: devError } = await db
   212→    .from('usage_events')
   213→    .select('device_type')
   214→    .gte('created_at', startOfDay.toISOString())
   215→    .lte('created_at', endOfDay.toISOString());
   216→
   217→  if (!devError && devices) {
   218→    const deviceCounts = {};
   219→    devices.forEach(e => {
   220→      const type = e.device_type || 'unknown';
   221→      deviceCounts[type] = (deviceCounts[type] || 0) + 1;
   222→    });
   223→
   224→    const mobileCount = deviceCounts['mobile'] || 0;
   225→    const total = devices.length || 1;
   226→
   227→    metrics.push({
   228→      date: dateStr,
   229→      metric_name: 'mobile_usage_percent',
   230→      metric_value: Math.round((mobileCount / total) * 100),
   231→      metric_context: { by_device: deviceCounts },
   232→      sample_size: devices.length
   233→    });
   234→  }
   235→
   236→  // Insert all metrics
   237→  if (metrics.length > 0) {
   238→    const { error: insertError } = await db
   239→      .from('intelligence_daily_metrics')
   240→      .upsert(metrics, { onConflict: 'date,metric_name' });
   241→
   242→    if (insertError) {
   243→      console.error('[Metrics] Insert error:', insertError);
   244→      throw insertError;
   245→    }
   246→
   247→    console.log(`[Metrics] Inserted ${metrics.length} metrics for ${dateStr}`);
   248→  }
   249→
   250→  return metrics;
   251→}
   252→
   253→/**
   254→ * Run the daily aggregation job
   255→ * Aggregates yesterday's metrics
   256→ */
   257→async function runDailyMetricsAggregation() {
   258→  console.log('\n📊 Starting Daily Metrics Aggregation...\n');
   259→
   260→  try {
   261→    // Aggregate yesterday's data
   262→    const yesterday = new Date();
   263→    yesterday.setDate(yesterday.getDate() - 1);
   264→
   265→    const metrics = await aggregateMetricsForDate(yesterday);
   266→
   267→    // Also clean up old raw events (older than 90 days)
   268→    const { data: cleanedCount } = await db.rpc('cleanup_old_usage_events');
   269→    if (cleanedCount) {
   270→      console.log(`[Metrics] Cleaned up ${cleanedCount} old usage events`);
   271→    }
   272→
   273→    console.log('✅ Daily metrics aggregation complete');
   274→    return { success: true, metricsCount: metrics.length };
   275→  } catch (error) {
   276→    console.error('❌ Daily metrics aggregation failed:', error);
   277→    throw error;
   278→  }
   279→}
   280→
   281→/**
   282→ * Backfill metrics for a date range (for initial setup)
   283→ */
   284→async function backfillMetrics(startDate, endDate) {
   285→  console.log(`\n📊 Backfilling metrics from ${startDate} to ${endDate}...\n`);
   286→
   287→  const current = new Date(startDate);
   288→  const end = new Date(endDate);
   289→
   290→  while (current <= end) {
   291→    try {
   292→      await aggregateMetricsForDate(current);
   293→    } catch (error) {
   294→      console.error(`Failed to aggregate for ${current.toISOString().split('T')[0]}:`, error.message);
   295→    }
   296→    current.setDate(current.getDate() + 1);
   297→  }
   298→
   299→  console.log('✅ Backfill complete');
   300→}
   301→
   302→module.exports = {
   303→  runDailyMetricsAggregation,
   304→  aggregateMetricsForDate,
   305→  backfillMetrics
   306→};
   307→

<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→ * Prompt Evolution Job
     3→ * Runs monthly to analyze prompt performance and evolve underperforming prompts
     4→ *
     5→ * Part of the 0.1% Self-Improvement Loop
     6→ */
     7→
     8→const { supabase, supabaseAdmin } = require('../config/database');
     9→const { sendSlackMessage } = require('../services/slack');
    10→const { generateAIAnalysis } = require('../services/ai');
    11→const {
    12→  DEFAULT_PROMPTS,
    13→  getPromptPerformance,
    14→  createPromptVersion,
    15→  activatePromptVersion,
    16→  getAllPromptStatus
    17→} = require('../services/promptManagement');
    18→
    19→const db = supabaseAdmin || supabase;
    20→
    21→// Minimum sample size before we consider evolving
    22→const MIN_OUTCOMES_FOR_EVOLUTION = 10;
    23→
    24→// Success rate threshold - below this, prompt needs improvement
    25→const SUCCESS_THRESHOLD = 60;
    26→
    27→/**
    28→ * Analyze why a prompt is underperforming
    29→ */
    30→async function analyzePromptFailures(promptType, version) {
    31→  // Get recent failures for this prompt
    32→  const { data: failures, error } = await db
    33→    .from('prompt_outcomes')
    34→    .select(`
    35→      *,
    36→      intelligence_suggestions(
    37→        suggestion_text,
    38→        status,
    39→        success_verdict,
    40→        rejection_reason
    41→      )
    42→    `)
    43→    .eq('prompt_version_id', version.id)
    44→    .eq('success', false)
    45→    .order('created_at', { ascending: false })
    46→    .limit(20);
    47→
    48→  if (error || !failures || failures.length === 0) {
    49→    return null;
    50→  }
    51→
    52→  // Extract patterns from failures
    53→  const rejectionReasons = failures
    54→    .filter(f => f.intelligence_suggestions?.rejection_reason)
    55→    .map(f => f.intelligence_suggestions.rejection_reason);
    56→
    57→  const failedSuggestions = failures
    58→    .filter(f => f.intelligence_suggestions?.suggestion_text)
    59→    .map(f => f.intelligence_suggestions.suggestion_text)
    60→    .slice(0, 5);
    61→
    62→  return {
    63→    totalFailures: failures.length,
    64→    rejectionReasons: [...new Set(rejectionReasons)],
    65→    sampleFailedSuggestions: failedSuggestions
    66→  };
    67→}
    68→
    69→/**
    70→ * Generate an improved prompt using AI
    71→ */
    72→async function generateImprovedPrompt(promptType, currentTemplate, failureAnalysis, performance) {
    73→  const prompt = `You are improving an AI prompt that is underperforming.
    74→
    75→CURRENT PROMPT:
    76→${currentTemplate}
    77→
    78→PERFORMANCE STATS:
    79→- Success rate: ${performance.successRate}% (target: ${SUCCESS_THRESHOLD}%+)
    80→- Total uses: ${performance.usage_count}
    81→- Failures: ${performance.failure_count}
    82→
    83→FAILURE ANALYSIS:
    84→${failureAnalysis ? `
    85→- Common rejection reasons: ${failureAnalysis.rejectionReasons.join(', ') || 'None recorded'}
    86→- Sample failed outputs:
    87→${failureAnalysis.sampleFailedSuggestions.map((s, i) => `  ${i + 1}. ${s.substring(0, 200)}...`).join('\n')}
    88→` : 'No detailed failure data available'}
    89→
    90→TASK:
    91→Improve this prompt to increase its success rate. Consider:
    92→1. Making instructions more specific
    93→2. Adding constraints to prevent common failure modes
    94→3. Including examples of good vs bad outputs
    95→4. Adjusting tone or format requirements
    96→
    97→Return ONLY the improved prompt template. Use the same {placeholder} format for variables.
    98→Do not include explanations - just the new prompt text.`;
    99→
   100→  try {
   101→    const improved = await generateAIAnalysis(prompt, 'prompt_evolution');
   102→    return improved;
   103→  } catch (error) {
   104→    console.error('[PromptEvolution] AI generation failed:', error);
   105→    return null;
   106→  }
   107→}
   108→
   109→/**
   110→ * Evaluate if a new prompt version should be activated
   111→ * Uses A/B test results to decide
   112→ */
   113→async function evaluatePromptPromotion(promptType) {
   114→  const { data: versions, error } = await db
   115→    .from('prompt_versions')
   116→    .select('*')
   117→    .eq('prompt_type', promptType)
   118→    .order('created_at', { ascending: false })
   119→    .limit(2);
   120→
   121→  if (error || !versions || versions.length < 2) {
   122→    return null;
   123→  }
   124→
   125→  const [newer, older] = versions;
   126→
   127→  // Need minimum sample size for comparison
   128→  if (newer.usage_count < MIN_OUTCOMES_FOR_EVOLUTION) {
   129→    return { action: 'wait', reason: `New version needs more data (${newer.usage_count}/${MIN_OUTCOMES_FOR_EVOLUTION})` };
   130→  }
   131→
   132→  const newerRate = newer.usage_count > 0
   133→    ? (newer.success_count / newer.usage_count) * 100
   134→    : 0;
   135→  const olderRate = older.usage_count > 0
   136→    ? (older.success_count / older.usage_count) * 100
   137→    : 0;
   138→
   139→  // If newer is significantly better (5%+ improvement), promote it
   140→  if (newerRate > olderRate + 5) {
   141→    return {
   142→      action: 'promote',
   143→      reason: `New version performs ${Math.round(newerRate - olderRate)}% better`,
   144→      newVersion: newer,
   145→      oldVersion: older
   146→    };
   147→  }
   148→
   149→  // If newer is significantly worse, rollback
   150→  if (newerRate < olderRate - 10) {
   151→    return {
   152→      action: 'rollback',
   153→      reason: `New version underperforms by ${Math.round(olderRate - newerRate)}%`,
   154→      newVersion: newer,
   155→      oldVersion: older
   156→    };
   157→  }
   158→
   159→  return { action: 'continue', reason: 'Performance difference not significant yet' };
   160→}
   161→
   162→/**
   163→ * Run A/B test allocation for a prompt
   164→ * Returns which version to use based on traffic split
   165→ */
   166→async function getAllocatedPromptVersion(promptType) {
   167→  const { data: versions, error } = await db
   168→    .from('prompt_versions')
   169→    .select('*')
   170→    .eq('prompt_type', promptType)
   171→    .in('status', ['active', 'testing'])
   172→    .order('created_at', { ascending: false })
   173→    .limit(2);
   174→
   175→  if (error || !versions || versions.length === 0) {
   176→    return null;
   177→  }
   178→
   179→  // If only one version, use it
   180→  if (versions.length === 1) {
   181→    return versions[0];
   182→  }
   183→
   184→  // A/B split: 80% to active, 20% to testing
   185→  const [testing, active] = versions.sort((a, b) =>
   186→    a.is_active === b.is_active ? 0 : a.is_active ? 1 : -1
   187→  );
   188→
   189→  return Math.random() < 0.8 ? active : testing;
   190→}
   191→
   192→/**
   193→ * Main prompt evolution job
   194→ */
   195→async function runPromptEvolution() {
   196→  console.log('\n🧬 Starting Prompt Evolution Job...\n');
   197→
   198→  const results = {
   199→    analyzed: 0,
   200→    evolved: 0,
   201→    promoted: 0,
   202→    rolledBack: 0,
   203→    skipped: 0
   204→  };
   205→
   206→  const evolutionSummary = [];
   207→
   208→  try {
   209→    // Analyze each prompt type
   210→    for (const promptType of Object.keys(DEFAULT_PROMPTS)) {
   211→      console.log(`[PromptEvolution] Analyzing: ${promptType}`);
   212→
   213→      const performance = await getPromptPerformance(promptType);
   214→      results.analyzed++;
   215→
   216→      // Get active version or default
   217→      const activeVersion = performance.versions.find(v => v.is_active);
   218→
   219→      // Skip if not enough data
   220→      const totalOutcomes = activeVersion
   221→        ? activeVersion.success_count + activeVersion.failure_count
   222→        : 0;
   223→
   224→      if (totalOutcomes < MIN_OUTCOMES_FOR_EVOLUTION) {
   225→        console.log(`  ⏭ Skipping - insufficient data (${totalOutcomes}/${MIN_OUTCOMES_FOR_EVOLUTION})`);
   226→        results.skipped++;
   227→        evolutionSummary.push({
   228→          prompt: promptType,
   229→          action: 'skipped',
   230→          reason: `Insufficient data (${totalOutcomes} outcomes)`
   231→        });
   232→        continue;
   233→      }
   234→
   235→      // Calculate success rate
   236→      const successRate = activeVersion
   237→        ? Math.round((activeVersion.success_count / Math.max(1, activeVersion.usage_count)) * 100)
   238→        : 100; // Default prompts assumed 100%
   239→
   240→      // Check if there's a version being tested
   241→      const evaluation = await evaluatePromptPromotion(promptType);
   242→
   243→      if (evaluation?.action === 'promote') {
   244→        // Promote the better-performing version
   245→        await activatePromptVersion(evaluation.newVersion.id, promptType);
   246→        results.promoted++;
   247→        console.log(`  ✅ Promoted v${evaluation.newVersion.version} - ${evaluation.reason}`);
   248→        evolutionSummary.push({
   249→          prompt: promptType,
   250→          action: 'promoted',
   251→          fromVersion: evaluation.oldVersion.version,
   252→          toVersion: evaluation.newVersion.version,
   253→          reason: evaluation.reason
   254→        });
   255→        continue;
   256→      }
   257→
   258→      if (evaluation?.action === 'rollback') {
   259→        // Rollback to the older, better version
   260→        await activatePromptVersion(evaluation.oldVersion.id, promptType);
   261→        results.rolledBack++;
   262→        console.log(`  ⚠ Rolled back to v${evaluation.oldVersion.version} - ${evaluation.reason}`);
   263→        evolutionSummary.push({
   264→          prompt: promptType,
   265→          action: 'rolledBack',
   266→          fromVersion: evaluation.newVersion.version,
   267→          toVersion: evaluation.oldVersion.version,
   268→          reason: evaluation.reason
   269→        });
   270→        continue;
   271→      }
   272→
   273→      // Check if prompt needs evolution
   274→      if (successRate < SUCCESS_THRESHOLD) {
   275→        console.log(`  📉 Success rate ${successRate}% below threshold - evolving...`);
   276→
   277→        // Analyze failures
   278→        const failureAnalysis = activeVersion
   279→          ? await analyzePromptFailures(promptType, activeVersion)
   280→          : null;
   281→
   282→        // Get current template
   283→        const currentTemplate = activeVersion?.template || DEFAULT_PROMPTS[promptType].template;
   284→
   285→        // Generate improved version
   286→        const improvedTemplate = await generateImprovedPrompt(
   287→          promptType,
   288→          currentTemplate,
   289→          failureAnalysis,
   290→          activeVersion || { successRate: 100, usage_count: 0, failure_count: 0 }
   291→        );
   292→
   293→        if (improvedTemplate && improvedTemplate !== currentTemplate) {
   294→          // Create new version for testing
   295→          const newVersion = await createPromptVersion(promptType, improvedTemplate, {
   296→            evolution_reason: `Auto-evolved due to ${successRate}% success rate (target: ${SUCCESS_THRESHOLD}%)`,
   297→            parent_version_id: activeVersion?.id
   298→          });
   299→
   300→          results.evolved++;
   301→          console.log(`  🧬 Created evolved version v${newVersion.version} for A/B testing`);
   302→          evolutionSummary.push({
   303→            prompt: promptType,
   304→            action: 'evolved',
   305→            currentVersion: activeVersion?.version || '1.0.0',
   306→            newVersion: newVersion.version,
   307→            successRate,
   308→            reason: `Below ${SUCCESS_THRESHOLD}% threshold`
   309→          });
   310→        } else {
   311→          console.log(`  ⏭ AI couldn't improve prompt or no changes needed`);
   312→          evolutionSummary.push({
   313→            prompt: promptType,
   314→            action: 'unchanged',
   315→            reason: 'No improvement generated'
   316→          });
   317→        }
   318→      } else {
   319→        console.log(`  ✅ Performance acceptable (${successRate}%)`);
   320→        evolutionSummary.push({
   321→          prompt: promptType,
   322→          action: 'healthy',
   323→          successRate
   324→        });
   325→      }
   326→    }
   327→
   328→    // Send summary to Slack
   329→    await sendEvolutionReport(results, evolutionSummary);
   330→
   331→    console.log('\n✅ Prompt Evolution Complete');
   332→    console.log(`   Analyzed: ${results.analyzed}`);
   333→    console.log(`   Evolved: ${results.evolved}`);
   334→    console.log(`   Promoted: ${results.promoted}`);
   335→    console.log(`   Rolled Back: ${results.rolledBack}`);
   336→    console.log(`   Skipped: ${results.skipped}`);
   337→
   338→    return { success: true, results, summary: evolutionSummary };
   339→  } catch (error) {
   340→    console.error('❌ Prompt Evolution failed:', error);
   341→
   342→    await sendSlackMessage({
   343→      text: `:x: Prompt Evolution failed: ${error.message}`
   344→    });
   345→
   346→    throw error;
   347→  }
   348→}
   349→
   350→/**
   351→ * Send evolution report to Slack
   352→ */
   353→async function sendEvolutionReport(results, summary) {
   354→  const evolved = summary.filter(s => s.action === 'evolved');
   355→  const promoted = summary.filter(s => s.action === 'promoted');
   356→  const rolledBack = summary.filter(s => s.action === 'rolledBack');
   357→  const healthy = summary.filter(s => s.action === 'healthy');
   358→
   359→  let message = `*:dna: PROMPT EVOLUTION REPORT*\n`;
   360→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
   361→
   362→  // Health overview
   363→  message += `*System Health:* ${healthy.length}/${results.analyzed} prompts performing well\n\n`;
   364→
   365→  // Evolutions
   366→  if (evolved.length > 0) {
   367→    message += `*:test_tube: New Versions Created (A/B Testing):*\n`;
   368→    evolved.forEach(e => {
   369→      message += `• ${e.prompt}: v${e.currentVersion} → v${e.newVersion} (was ${e.successRate}%)\n`;
   370→    });
   371→    message += `\n`;
   372→  }
   373→
   374→  // Promotions
   375→  if (promoted.length > 0) {
   376→    message += `*:trophy: Promoted (Better Performance):*\n`;
   377→    promoted.forEach(p => {
   378→      message += `• ${p.prompt}: v${p.fromVersion} → v${p.toVersion} - ${p.reason}\n`;
   379→    });
   380→    message += `\n`;
   381→  }
   382→
   383→  // Rollbacks
   384→  if (rolledBack.length > 0) {
   385→    message += `*:warning: Rolled Back (Underperforming):*\n`;
   386→    rolledBack.forEach(r => {
   387→      message += `• ${r.prompt}: v${r.fromVersion} → v${r.toVersion} - ${r.reason}\n`;
   388→    });
   389→    message += `\n`;
   390→  }
   391→
   392→  // Footer
   393→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
   394→  message += `_0.1% Self-Improvement Loop - Prompt Evolution_`;
   395→
   396→  await sendSlackMessage({
   397→    text: message,
   398→    unfurl_links: false,
   399→    unfurl_media: false
   400→  });
   401→}
   402→
   403→module.exports = {
   404→  runPromptEvolution,
   405→  evaluatePromptPromotion,
   406→  getAllocatedPromptVersion
   407→};
   408→

<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→ * Monthly Deep Dive Analysis Job
     3→ * Runs on the 1st of each month at 8am AEST
     4→ * Comprehensive analysis of system health, trends, and AI effectiveness
     5→ */
     6→
     7→const { supabase, supabaseAdmin } = require('../config/database');
     8→const { sendSlackMessage } = require('../services/slack');
     9→const { generateAIAnalysis } = require('../services/ai');
    10→const { generateSelfImprovementReport } = require('../services/selfImprovement');
    11→
    12→const db = supabaseAdmin || supabase;
    13→
    14→/**
    15→ * Get metrics for a specific month
    16→ */
    17→async function getMonthMetrics(year, month) {
    18→  const startDate = new Date(year, month - 1, 1);
    19→  const endDate = new Date(year, month, 0); // Last day of month
    20→
    21→  const { data, error } = await db
    22→    .from('intelligence_daily_metrics')
    23→    .select('*')
    24→    .gte('date', startDate.toISOString().split('T')[0])
    25→    .lte('date', endDate.toISOString().split('T')[0]);
    26→
    27→  if (error) {
    28→    console.error('[MonthlyDeepDive] Error fetching metrics:', error);
    29→    return [];
    30→  }
    31→
    32→  return data || [];
    33→}
    34→
    35→/**
    36→ * Get suggestion outcomes for a month
    37→ */
    38→async function getSuggestionOutcomes(year, month) {
    39→  const startDate = new Date(year, month - 1, 1);
    40→  const endDate = new Date(year, month, 0);
    41→
    42→  const { data, error } = await db
    43→    .from('intelligence_suggestions')
    44→    .select('*')
    45→    .or(`implemented_at.gte.${startDate.toISOString()},created_at.gte.${startDate.toISOString()}`)
    46→    .lte('created_at', endDate.toISOString());
    47→
    48→  if (error) {
    49→    console.error('[MonthlyDeepDive] Error fetching suggestions:', error);
    50→    return [];
    51→  }
    52→
    53→  return data || [];
    54→}
    55→
    56→/**
    57→ * Get friction points resolved/detected in a month
    58→ */
    59→async function getFrictionActivity(year, month) {
    60→  const startDate = new Date(year, month - 1, 1);
    61→  const endDate = new Date(year, month, 0);
    62→
    63→  const { data: detected, error: dError } = await db
    64→    .from('detected_friction')
    65→    .select('*')
    66→    .gte('created_at', startDate.toISOString())
    67→    .lte('created_at', endDate.toISOString());
    68→
    69→  const { data: resolved, error: rError } = await db
    70→    .from('detected_friction')
    71→    .select('*')
    72→    .gte('resolved_at', startDate.toISOString())
    73→    .lte('resolved_at', endDate.toISOString());
    74→
    75→  return {
    76→    detected: detected || [],
    77→    resolved: resolved || []
    78→  };
    79→}
    80→
    81→/**
    82→ * Calculate month-over-month trends
    83→ */
    84→async function calculateTrends(currentMonth, previousMonth) {
    85→  const trends = {};
    86→
    87→  // Aggregate metrics by name
    88→  const aggregateByName = (metrics) => {
    89→    const result = {};
    90→    metrics.forEach(m => {
    91→      if (!result[m.metric_name]) {
    92→        result[m.metric_name] = { total: 0, count: 0 };
    93→      }
    94→      result[m.metric_name].total += parseFloat(m.metric_value) || 0;
    95→      result[m.metric_name].count++;
    96→    });
    97→    // Convert to averages
    98→    Object.keys(result).forEach(name => {
    99→      result[name] = result[name].count > 0
   100→        ? result[name].total / result[name].count
   101→        : 0;
   102→    });
   103→    return result;
   104→  };
   105→
   106→  const currentAgg = aggregateByName(currentMonth);
   107→  const previousAgg = aggregateByName(previousMonth);
   108→
   109→  // Calculate changes
   110→  Object.keys(currentAgg).forEach(metric => {
   111→    const current = currentAgg[metric];
   112→    const previous = previousAgg[metric] || 0;
   113→
   114→    if (previous > 0) {
   115→      const change = ((current - previous) / previous) * 100;
   116→      trends[metric] = {
   117→        current: Math.round(current * 100) / 100,
   118→        previous: Math.round(previous * 100) / 100,
   119→        change: Math.round(change),
   120→        direction: change > 0 ? 'up' : change < 0 ? 'down' : 'flat'
   121→      };
   122→    } else {
   123→      trends[metric] = {
   124→        current: Math.round(current * 100) / 100,
   125→        previous: 0,
   126→        change: current > 0 ? 100 : 0,
   127→        direction: 'new'
   128→      };
   129→    }
   130→  });
   131→
   132→  return trends;
   133→}
   134→
   135→/**
   136→ * Analyze AI suggestion effectiveness
   137→ */
   138→function analyzeAIEffectiveness(suggestions) {
   139→  const stats = {
   140→    total: suggestions.length,
   141→    implemented: 0,
   142→    successful: 0,
   143→    failed: 0,
   144→    pending: 0,
   145→    rejected: 0,
   146→    averagePriorityScore: 0
   147→  };
   148→
   149→  let prioritySum = 0;
   150→  let priorityCount = 0;
   151→
   152→  suggestions.forEach(s => {
   153→    if (s.status === 'implemented') {
   154→      stats.implemented++;
   155→      if (s.success_verdict === 'success') stats.successful++;
   156→      else if (s.success_verdict === 'failed') stats.failed++;
   157→    } else if (s.status === 'rejected') {
   158→      stats.rejected++;
   159→    } else if (['suggested', 'planned', 'in_progress'].includes(s.status)) {
   160→      stats.pending++;
   161→    }
   162→
   163→    if (s.priority_score) {
   164→      prioritySum += parseFloat(s.priority_score);
   165→      priorityCount++;
   166→    }
   167→  });
   168→
   169→  stats.averagePriorityScore = priorityCount > 0
   170→    ? Math.round((prioritySum / priorityCount) * 10) / 10
   171→    : 0;
   172→
   173→  stats.successRate = stats.implemented > 0
   174→    ? Math.round((stats.successful / stats.implemented) * 100)
   175→    : 0;
   176→
   177→  stats.implementationRate = stats.total > 0
   178→    ? Math.round((stats.implemented / stats.total) * 100)
   179→    : 0;
   180→
   181→  return stats;
   182→}
   183→
   184→/**
   185→ * Generate AI insights for the monthly report
   186→ */
   187→async function generateMonthlyInsights(data) {
   188→  const prompt = `Analyze this monthly system performance data and provide strategic insights:
   189→
   190→MONTH-OVER-MONTH TRENDS:
   191→${JSON.stringify(data.trends, null, 2)}
   192→
   193→FRICTION ACTIVITY:
   194→- New friction points detected: ${data.friction.detected.length}
   195→- Friction points resolved: ${data.friction.resolved.length}
   196→- Top friction locations: ${data.friction.detected.slice(0, 5).map(f => f.location).join(', ')}
   197→
   198→AI SUGGESTION EFFECTIVENESS:
   199→- Total suggestions: ${data.aiStats.total}
   200→- Implementation rate: ${data.aiStats.implementationRate}%
   201→- Success rate of implemented: ${data.aiStats.successRate}%
   202→- Pending suggestions: ${data.aiStats.pending}
   203→
   204→Provide:
   205→1. TOP 3 WINS this month (what improved)
   206→2. TOP 3 CONCERNS (what needs attention)
   207→3. STRATEGIC RECOMMENDATION for next month
   208→4. AI SYSTEM HEALTH assessment (is the system improving itself?)
   209→
   210→Keep each section to 2-3 bullet points. Be specific and actionable.`;
   211→
   212→  try {
   213→    const analysis = await generateAIAnalysis(prompt, 'monthly_deep_dive');
   214→    return analysis;
   215→  } catch (error) {
   216→    console.error('[MonthlyDeepDive] AI analysis failed:', error);
   217→    return 'AI analysis unavailable';
   218→  }
   219→}
   220→
   221→/**
   222→ * Format the monthly report for Slack
   223→ */
   224→function formatSlackReport(data) {
   225→  const { monthName, year, trends, friction, aiStats, insights, weeklyReports, selfImprovement } = data;
   226→
   227→  // Overall health emoji
   228→  const healthEmoji = aiStats.successRate >= 70 ? ':white_check_mark:' :
   229→                      aiStats.successRate >= 50 ? ':warning:' : ':x:';
   230→
   231→  let message = `*:calendar: MONTHLY DEEP DIVE - ${monthName} ${year}*\n`;
   232→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
   233→
   234→  // Key Metrics Trends
   235→  message += `*:chart_with_upwards_trend: KEY METRIC TRENDS*\n`;
   236→  const keyMetrics = ['total_page_views', 'flow_completion_rate', 'total_errors', 'rage_clicks'];
   237→  keyMetrics.forEach(metric => {
   238→    if (trends[metric]) {
   239→      const t = trends[metric];
   240→      const arrow = t.direction === 'up' ? '↑' : t.direction === 'down' ? '↓' : '→';
   241→      const emoji = metric.includes('error') || metric.includes('rage')
   242→        ? (t.direction === 'down' ? ':white_check_mark:' : ':warning:')
   243→        : (t.direction === 'up' ? ':white_check_mark:' : ':warning:');
   244→      message += `${emoji} ${metric.replace(/_/g, ' ')}: ${t.current} (${arrow} ${Math.abs(t.change)}%)\n`;
   245→    }
   246→  });
   247→  message += `\n`;
   248→
   249→  // Friction Summary
   250→  message += `*:wrench: FRICTION SUMMARY*\n`;
   251→  message += `:new: Detected: ${friction.detected.length} new friction points\n`;
   252→  message += `:white_check_mark: Resolved: ${friction.resolved.length} friction points fixed\n`;
   253→  const netFriction = friction.detected.length - friction.resolved.length;
   254→  message += `${netFriction > 0 ? ':warning:' : ':tada:'} Net change: ${netFriction > 0 ? '+' : ''}${netFriction}\n\n`;
   255→
   256→  // AI System Health
   257→  message += `*:robot_face: AI SYSTEM HEALTH*\n`;
   258→  message += `${healthEmoji} Overall: ${aiStats.successRate}% success rate\n`;
   259→  message += `• Suggestions generated: ${aiStats.total}\n`;
   260→  message += `• Implemented: ${aiStats.implemented} (${aiStats.implementationRate}%)\n`;
   261→  message += `• Successful: ${aiStats.successful} | Failed: ${aiStats.failed}\n`;
   262→  message += `• Pending review: ${aiStats.pending}\n\n`;
   263→
   264→  // Weekly Reports Summary
   265→  if (weeklyReports && weeklyReports.length > 0) {
   266→    const avgHealth = weeklyReports.reduce((sum, r) => sum + (r.overall_health_score || 0), 0) / weeklyReports.length;
   267→    message += `*:clipboard: WEEKLY REPORTS SUMMARY*\n`;
   268→    message += `• ${weeklyReports.length} weekly reports generated\n`;
   269→    message += `• Average health score: ${Math.round(avgHealth)}/100\n\n`;
   270→  }
   271→
   272→  // AI Insights
   273→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
   274→  message += `*:brain: AI STRATEGIC INSIGHTS*\n\n`;
   275→  message += insights || 'No AI insights available';
   276→  message += `\n\n`;
   277→
   278→  // Self-Improvement Section
   279→  if (selfImprovement) {
   280→    message += `*:gear: SELF-IMPROVEMENT ACTIONS*\n`;
   281→    if (selfImprovement.summary && selfImprovement.summary.length > 0) {
   282→      selfImprovement.summary.forEach(item => {
   283→        message += `• ${item}\n`;
   284→      });
   285→    }
   286→    if (selfImprovement.thresholdAdjustments?.adjustmentsMade > 0) {
   287→      message += `\n:wrench: *Threshold Adjustments Made:*\n`;
   288→      selfImprovement.thresholdAdjustments.adjustments.forEach(adj => {
   289→        message += `• ${adj.threshold}: ${adj.old} → ${adj.new}\n`;
   290→      });
   291→    }
   292→    if (selfImprovement.patternLearning?.patternsFound > 0) {
   293→      message += `\n:bulb: *Patterns Learned:*\n`;
   294→      message += `• Analyzed ${selfImprovement.patternLearning.suggestionCount} successful suggestions\n`;
   295→      if (selfImprovement.patternLearning.topCategories?.length > 0) {
   296→        message += `• Top categories: ${selfImprovement.patternLearning.topCategories.map(c => c[0]).join(', ')}\n`;
   297→      }
   298→    }
   299→    message += `\n`;
   300→  }
   301→
   302→  // Footer
   303→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
   304→  message += `_0.1% Intelligence System - Monthly Deep Dive_`;
   305→
   306→  return {
   307→    text: message,
   308→    unfurl_links: false,
   309→    unfurl_media: false
   310→  };
   311→}
   312→
   313→/**
   314→ * Save monthly report to database
   315→ */
   316→async function saveMonthlyReport(data) {
   317→  const { year, month, trends, friction, aiStats, insights, weeklyReports } = data;
   318→
   319→  // Create a monthly_deep_dive_reports table entry if it doesn't exist
   320→  // For now, we'll store in weekly_excellence_reports with a special marker
   321→  const reportDate = new Date(year, month - 1, 1);
   322→
   323→  const { error } = await db
   324→    .from('weekly_excellence_reports')
   325→    .upsert({
   326→      week_start: reportDate.toISOString().split('T')[0],
   327→      week_end: new Date(year, month, 0).toISOString().split('T')[0],
   328→      friction_points: friction.detected,
   329→      quick_wins: [],
   330→      patterns_detected: [],
   331→      unused_features: [],
   332→      ai_quality: {
   333→        ...aiStats,
   334→        report_type: 'monthly_deep_dive'
   335→      },
   336→      overall_health_score: aiStats.successRate,
   337→      friction_score: 100 - (friction.detected.length * 5),
   338→      improvement_opportunities: friction.detected.length,
   339→      full_report_json: {
   340→        type: 'monthly_deep_dive',
   341→        year,
   342→        month,
   343→        trends,
   344→        friction,
   345→        aiStats,
   346→        insights,
   347→        weeklyReportsCount: weeklyReports?.length || 0
   348→      }
   349→    }, { onConflict: 'week_start' });
   350→
   351→  if (error) {
   352→    console.error('[MonthlyDeepDive] Failed to save report:', error);
   353→  }
   354→}
   355→
   356→/**
   357→ * Main monthly deep dive job
   358→ */
   359→async function runMonthlyDeepDive() {
   360→  console.log('\n📊 Starting Monthly Deep Dive Analysis...\n');
   361→
   362→  try {
   363→    // Determine which month to analyze (previous month)
   364→    const now = new Date();
   365→    const targetMonth = now.getMonth(); // 0-indexed, so this is previous month
   366→    const targetYear = targetMonth === 0 ? now.getFullYear() - 1 : now.getFullYear();
   367→    const adjustedMonth = targetMonth === 0 ? 12 : targetMonth;
   368→
   369→    const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
   370→                       'July', 'August', 'September', 'October', 'November', 'December'];
   371→    const monthName = monthNames[adjustedMonth - 1];
   372→
   373→    console.log(`[MonthlyDeepDive] Analyzing ${monthName} ${targetYear}`);
   374→
   375→    // Get data for current and previous month
   376→    const currentMonthMetrics = await getMonthMetrics(targetYear, adjustedMonth);
   377→    const prevMonth = adjustedMonth === 1 ? 12 : adjustedMonth - 1;
   378→    const prevYear = adjustedMonth === 1 ? targetYear - 1 : targetYear;
   379→    const previousMonthMetrics = await getMonthMetrics(prevYear, prevMonth);
   380→
   381→    // Calculate trends
   382→    const trends = await calculateTrends(currentMonthMetrics, previousMonthMetrics);
   383→
   384→    // Get suggestion data
   385→    const suggestions = await getSuggestionOutcomes(targetYear, adjustedMonth);
   386→    const aiStats = analyzeAIEffectiveness(suggestions);
   387→
   388→    // Get friction data
   389→    const friction = await getFrictionActivity(targetYear, adjustedMonth);
   390→
   391→    // Get weekly reports for the month
   392→    const startDate = new Date(targetYear, adjustedMonth - 1, 1);
   393→    const endDate = new Date(targetYear, adjustedMonth, 0);
   394→    const { data: weeklyReports } = await db
   395→      .from('weekly_excellence_reports')
   396→      .select('*')
   397→      .gte('week_start', startDate.toISOString().split('T')[0])
   398→      .lte('week_start', endDate.toISOString().split('T')[0]);
   399→
   400→    // Generate AI insights
   401→    const insights = await generateMonthlyInsights({
   402→      trends,
   403→      friction,
   404→      aiStats,
   405→      weeklyReports
   406→    });
   407→
   408→    // Run self-improvement analysis and auto-adjustments
   409→    console.log('[MonthlyDeepDive] Running self-improvement analysis...');
   410→    let selfImprovementReport = null;
   411→    try {
   412→      selfImprovementReport = await generateSelfImprovementReport();
   413→      console.log('[MonthlyDeepDive] Self-improvement complete:', selfImprovementReport.summary);
   414→    } catch (siError) {
   415→      console.error('[MonthlyDeepDive] Self-improvement failed:', siError);
   416→    }
   417→
   418→    // Compile report data
   419→    const reportData = {
   420→      year: targetYear,
   421→      month: adjustedMonth,
   422→      monthName,
   423→      trends,
   424→      friction,
   425→      aiStats,
   426→      insights,
   427→      weeklyReports,
   428→      selfImprovement: selfImprovementReport
   429→    };
   430→
   431→    // Save report
   432→    await saveMonthlyReport(reportData);
   433→
   434→    // Format and send to Slack
   435→    const slackPayload = formatSlackReport(reportData);
   436→    await sendSlackMessage(slackPayload);
   437→
   438→    console.log('✅ Monthly Deep Dive complete and sent to Slack');
   439→    return reportData;
   440→  } catch (error) {
   441→    console.error('❌ Monthly Deep Dive failed:', error);
   442→
   443→    // Send error notification
   444→    await sendSlackMessage({
   445→      text: `:x: Monthly Deep Dive failed: ${error.message}`
   446→    });
   447→
   448→    throw error;
   449→  }
   450→}
   451→
   452→module.exports = {
   453→  runMonthlyDeepDive,
   454→  calculateTrends,
   455→  analyzeAIEffectiveness,
   456→  generateMonthlyInsights
   457→};
   458→

<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→ * Impact Measurement Automation Job
     3→ * Automatically measures the impact of implemented suggestions
     4→ *
     5→ * Process:
     6→ * 1. Find suggestions that need impact measurement (implemented 7+ days ago)
     7→ * 2. Capture post-implementation metrics
     8→ * 3. Compare with pre-implementation baseline
     9→ * 4. Determine success verdict
    10→ * 5. Update prompt outcomes for self-improvement loop
    11→ */
    12→
    13→const { supabase, supabaseAdmin } = require('../config/database');
    14→const { sendSlackMessage } = require('../services/slack');
    15→const { recordPromptOutcome } = require('../services/promptManagement');
    16→
    17→const db = supabaseAdmin || supabase;
    18→
    19→// Days to wait before measuring impact
    20→const MEASUREMENT_DELAY_DAYS = 7;
    21→
    22→// Minimum improvement percentage to count as success
    23→const SUCCESS_THRESHOLD_PERCENT = 5;
    24→
    25→/**
    26→ * Get suggestions ready for impact measurement
    27→ */
    28→async function getSuggestionsToMeasure() {
    29→  const cutoffDate = new Date(Date.now() - MEASUREMENT_DELAY_DAYS * 24 * 60 * 60 * 1000);
    30→
    31→  const { data, error } = await db
    32→    .from('intelligence_suggestions')
    33→    .select('*')
    34→    .eq('status', 'implemented')
    35→    .is('success_verdict', null)
    36→    .lt('implemented_at', cutoffDate.toISOString())
    37→    .order('implemented_at', { ascending: true })
    38→    .limit(20);
    39→
    40→  if (error) {
    41→    console.error('[ImpactMeasurement] Error fetching suggestions:', error);
    42→    return [];
    43→  }
    44→
    45→  return data || [];
    46→}
    47→
    48→/**
    49→ * Get metrics for a specific date range
    50→ */
    51→async function getMetricsForPeriod(startDate, endDate, metricNames = null) {
    52→  let query = db
    53→    .from('intelligence_daily_metrics')
    54→    .select('*')
    55→    .gte('date', startDate.toISOString().split('T')[0])
    56→    .lte('date', endDate.toISOString().split('T')[0]);
    57→
    58→  if (metricNames && metricNames.length > 0) {
    59→    query = query.in('metric_name', metricNames);
    60→  }
    61→
    62→  const { data, error } = await query;
    63→
    64→  if (error) {
    65→    console.error('[ImpactMeasurement] Error fetching metrics:', error);
    66→    return [];
    67→  }
    68→
    69→  return data || [];
    70→}
    71→
    72→/**
    73→ * Calculate average metric value from a set of metrics
    74→ */
    75→function calculateAverage(metrics, metricName) {
    76→  const values = metrics
    77→    .filter(m => m.metric_name === metricName)
    78→    .map(m => parseFloat(m.metric_value) || 0);
    79→
    80→  if (values.length === 0) return null;
    81→  return values.reduce((sum, val) => sum + val, 0) / values.length;
    82→}
    83→
    84→/**
    85→ * Determine relevant metrics based on suggestion type
    86→ */
    87→function getRelevantMetrics(suggestion) {
    88→  const type = suggestion.suggestion_type || '';
    89→  const location = suggestion.target_location || '';
    90→
    91→  // Default metrics to track
    92→  const metrics = ['flow_completion_rate', 'total_errors', 'rage_clicks'];
    93→
    94→  // Add location-specific metrics if applicable
    95→  if (location) {
    96→    metrics.push(`page_views_${location}`);
    97→    metrics.push(`abandonment_rate_${location}`);
    98→  }
    99→
   100→  // Add type-specific metrics
   101→  switch (type) {
   102→    case 'ux_improvement':
   103→      metrics.push('avg_time_on_page', 'bounce_rate');
   104→      break;
   105→    case 'error_fix':
   106→      metrics.push('error_rate');
   107→      break;
   108→    case 'performance':
   109→      metrics.push('avg_page_load_time');
   110→      break;
   111→    case 'feature_request':
   112→      metrics.push('feature_usage_rate');
   113→      break;
   114→  }
   115→
   116→  return metrics;
   117→}
   118→
   119→/**
   120→ * Measure impact for a single suggestion
   121→ */
   122→async function measureImpact(suggestion) {
   123→  const implementedAt = new Date(suggestion.implemented_at);
   124→
   125→  // Pre-implementation period: 7 days before implementation
   126→  const preStart = new Date(implementedAt);
   127→  preStart.setDate(preStart.getDate() - 7);
   128→  const preEnd = new Date(implementedAt);
   129→  preEnd.setDate(preEnd.getDate() - 1);
   130→
   131→  // Post-implementation period: 7 days after implementation
   132→  const postStart = new Date(implementedAt);
   133→  postStart.setDate(postStart.getDate() + 1);
   134→  const postEnd = new Date();
   135→
   136→  const relevantMetrics = getRelevantMetrics(suggestion);
   137→
   138→  // Fetch metrics for both periods
   139→  const preMetrics = await getMetricsForPeriod(preStart, preEnd, relevantMetrics);
   140→  const postMetrics = await getMetricsForPeriod(postStart, postEnd, relevantMetrics);
   141→
   142→  // Calculate changes
   143→  const changes = {};
   144→  let improvementCount = 0;
   145→  let regressionCount = 0;
   146→  let totalMetricsCompared = 0;
   147→
   148→  for (const metricName of relevantMetrics) {
   149→    const prAvg = calculateAverage(preMetrics, metricName);
   150→    const postAvg = calculateAverage(postMetrics, metricName);
   151→
   152→    if (prAvg !== null && postAvg !== null) {
   153→      const changePercent = prAvg !== 0
   154→        ? ((postAvg - prAvg) / Math.abs(prAvg)) * 100
   155→        : (postAvg > 0 ? 100 : 0);
   156→
   157→      // Determine if change is good or bad based on metric type
   158→      const isNegativeMetric = metricName.includes('error') ||
   159→        metricName.includes('rage') ||
   160→        metricName.includes('abandon') ||
   161→        metricName.includes('bounce');
   162→
   163→      const isImprovement = isNegativeMetric
   164→        ? changePercent < -SUCCESS_THRESHOLD_PERCENT
   165→        : changePercent > SUCCESS_THRESHOLD_PERCENT;
   166→
   167→      const isRegression = isNegativeMetric
   168→        ? changePercent > SUCCESS_THRESHOLD_PERCENT
   169→        : changePercent < -SUCCESS_THRESHOLD_PERCENT;
   170→
   171→      changes[metricName] = {
   172→        before: Math.round(prAvg * 100) / 100,
   173→        after: Math.round(postAvg * 100) / 100,
   174→        changePercent: Math.round(changePercent),
   175→        isImprovement,
   176→        isRegression
   177→      };
   178→
   179→      totalMetricsCompared++;
   180→      if (isImprovement) improvementCount++;
   181→      if (isRegression) regressionCount++;
   182→    }
   183→  }
   184→
   185→  // Determine overall verdict
   186→  let verdict;
   187→  if (totalMetricsCompared === 0) {
   188→    verdict = 'inconclusive';
   189→  } else if (regressionCount > improvementCount) {
   190→    verdict = 'failed';
   191→  } else if (improvementCount >= Math.ceil(totalMetricsCompared / 2)) {
   192→    verdict = 'success';
   193→  } else {
   194→    verdict = 'neutral';
   195→  }
   196→
   197→  return {
   198→    verdict,
   199→    metrics: changes,
   200→    summary: {
   201→      totalMetricsCompared,
   202→      improvementCount,
   203→      regressionCount,
   204→      measurementPeriod: {
   205→        pre: { start: preStart.toISOString().split('T')[0], end: preEnd.toISOString().split('T')[0] },
   206→        post: { start: postStart.toISOString().split('T')[0], end: postEnd.toISOString().split('T')[0] }
   207→      }
   208→    }
   209→  };
   210→}
   211→
   212→/**
   213→ * Update suggestion with impact measurement results
   214→ */
   215→async function updateSuggestionWithImpact(suggestionId, impactData) {
   216→  const { error } = await db
   217→    .from('intelligence_suggestions')
   218→    .update({
   219→      success_verdict: impactData.verdict,
   220→      post_implementation_metrics: impactData.metrics,
   221→      actual_impact: impactData.summary,
   222→      impact_measured_at: new Date().toISOString(),
   223→      updated_at: new Date().toISOString()
   224→    })
   225→    .eq('id', suggestionId);
   226→
   227→  if (error) {
   228→    console.error('[ImpactMeasurement] Error updating suggestion:', error);
   229→    throw error;
   230→  }
   231→}
   232→
   233→/**
   234→ * Main impact measurement job
   235→ */
   236→async function runImpactMeasurement() {
   237→  console.log('\n📏 Starting Impact Measurement Job...\n');
   238→
   239→  const results = {
   240→    measured: 0,
   241→    successful: 0,
   242→    failed: 0,
   243→    neutral: 0,
   244→    inconclusive: 0,
   245→    errors: 0
   246→  };
   247→
   248→  try {
   249→    const suggestions = await getSuggestionsToMeasure();
   250→
   251→    if (suggestions.length === 0) {
   252→      console.log('[ImpactMeasurement] No suggestions ready for measurement');
   253→      return { success: true, results };
   254→    }
   255→
   256→    console.log(`[ImpactMeasurement] Found ${suggestions.length} suggestions to measure`);
   257→
   258→    for (const suggestion of suggestions) {
   259→      try {
   260→        console.log(`[ImpactMeasurement] Measuring: ${suggestion.suggestion_text?.substring(0, 50)}...`);
   261→
   262→        const impact = await measureImpact(suggestion);
   263→        await updateSuggestionWithImpact(suggestion.id, impact);
   264→
   265→        // Record outcome for prompt evolution
   266→        if (suggestion.prompt_version_id) {
   267→          await recordPromptOutcome(suggestion.prompt_version_id, {
   268→            success: impact.verdict === 'success',
   269→            suggestion_id: suggestion.id,
   270→            feedback: impact
   271→          });
   272→        }
   273→
   274→        results.measured++;
   275→        results[impact.verdict]++;
   276→
   277→        console.log(`  → Verdict: ${impact.verdict}`);
   278→      } catch (error) {
   279→        console.error(`  → Error measuring suggestion ${suggestion.id}:`, error.message);
   280→        results.errors++;
   281→      }
   282→    }
   283→
   284→    // Send summary to Slack if any were measured
   285→    if (results.measured > 0) {
   286→      await sendImpactSummary(results);
   287→    }
   288→
   289→    console.log('\n✅ Impact Measurement Complete');
   290→    console.log(`   Measured: ${results.measured}`);
   291→    console.log(`   Successful: ${results.successful}`);
   292→    console.log(`   Failed: ${results.failed}`);
   293→    console.log(`   Neutral: ${results.neutral}`);
   294→    console.log(`   Inconclusive: ${results.inconclusive}`);
   295→
   296→    return { success: true, results };
   297→  } catch (error) {
   298→    console.error('❌ Impact Measurement failed:', error);
   299→
   300→    await sendSlackMessage({
   301→      text: `:x: Impact Measurement failed: ${error.message}`
   302→    });
   303→
   304→    throw error;
   305→  }
   306→}
   307→
   308→/**
   309→ * Send impact measurement summary to Slack
   310→ */
   311→async function sendImpactSummary(results) {
   312→  const successRate = results.measured > 0
   313→    ? Math.round((results.successful / results.measured) * 100)
   314→    : 0;
   315→
   316→  let message = `*:bar_chart: IMPACT MEASUREMENT REPORT*\n`;
   317→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
   318→
   319→  message += `*Suggestions Measured:* ${results.measured}\n\n`;
   320→
   321→  message += `*Results:*\n`;
   322→  message += `✅ Successful: ${results.successful}\n`;
   323→  message += `❌ Failed: ${results.failed}\n`;
   324→  message += `➖ Neutral: ${results.neutral}\n`;
   325→  message += `❓ Inconclusive: ${results.inconclusive}\n\n`;
   326→
   327→  message += `*Success Rate:* ${successRate}%\n\n`;
   328→
   329→  if (results.errors > 0) {
   330→    message += `⚠ Errors during measurement: ${results.errors}\n\n`;
   331→  }
   332→
   333→  message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
   334→  message += `_0.1% Intelligence System - Impact Measurement_`;
   335→
   336→  await sendSlackMessage({
   337→    text: message,
   338→    unfurl_links: false,
   339→    unfurl_media: false
   340→  });
   341→}
   342→
   343→/**
   344→ * Capture baseline metrics when a suggestion is implemented
   345→ * Call this when marking a suggestion as implemented
   346→ */
   347→async function captureBaseline(suggestionId) {
   348→  const endDate = new Date();
   349→  const startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
   350→
   351→  // Get the suggestion to determine relevant metrics
   352→  const { data: suggestion, error: fetchError } = await db
   353→    .from('intelligence_suggestions')
   354→    .select('*')
   355→    .eq('id', suggestionId)
   356→    .single();
   357→
   358→  if (fetchError || !suggestion) {
   359→    console.error('[ImpactMeasurement] Could not find suggestion:', suggestionId);
   360→    return;
   361→  }
   362→
   363→  const relevantMetrics = getRelevantMetrics(suggestion);
   364→  const metrics = await getMetricsForPeriod(startDate, endDate, relevantMetrics);
   365→
   366→  // Calculate averages for baseline
   367→  const baseline = {};
   368→  for (const metricName of relevantMetrics) {
   369→    const avg = calculateAverage(metrics, metricName);
   370→    if (avg !== null) {
   371→      baseline[metricName] = Math.round(avg * 100) / 100;
   372→    }
   373→  }
   374→
   375→  // Update suggestion with baseline
   376→  await db
   377→    .from('intelligence_suggestions')
   378→    .update({
   379→      pre_implementation_metrics: baseline,
   380→      updated_at: new Date().toISOString()
   381→    })
   382→    .eq('id', suggestionId);
   383→
   384→  console.log(`[ImpactMeasurement] Baseline captured for suggestion ${suggestionId}`);
   385→}
   386→
   387→module.exports = {
   388→  runImpactMeasurement,
   389→  captureBaseline,
   390→  measureImpact
   391→};
   392→

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