← back to Handbag Authentication

scripts/deal-notifier.js

168 lines

require('dotenv').config();
const Database = require('../db/schema');
const SlackNotifier = require('../ai/slack-notifier');

class DealNotifier {
  constructor() {
    this.db = new Database(process.env.DATABASE_PATH);
    this.slack = new SlackNotifier();
    this.minDealPercentage = parseFloat(process.env.DEAL_THRESHOLD) || 30;
  }

  async checkAndNotify() {
    console.log('=== Checking for High-Value Deals ===\n');

    // Get new deals that haven't been notified
    const sql = `
      SELECT
        l.*,
        d.deal_percentage,
        d.avg_us_price,
        d.confidence_score,
        d.ai_notes,
        d.is_deal
      FROM listings l
      JOIN deal_analysis d ON l.id = d.listing_id
      WHERE d.is_deal = 1
        AND d.deal_percentage >= ?
        AND l.is_active = 1
        AND NOT EXISTS (
          SELECT 1 FROM notifications n
          WHERE n.listing_id = l.id
        )
      ORDER BY d.deal_percentage DESC
      LIMIT 20
    `;

    return new Promise((resolve, reject) => {
      this.db.db.all(sql, [this.minDealPercentage], async (err, deals) => {
        if (err) {
          reject(err);
          return;
        }

        console.log(`Found ${deals.length} new high-value deals`);

        for (const deal of deals) {
          await this.notifyDeal(deal);
        }

        resolve(deals.length);
      });
    });
  }

  async notifyDeal(listing) {
    const dealAnalysis = {
      deal_percentage: listing.deal_percentage,
      avg_us_price: listing.avg_us_price,
      confidence_score: listing.confidence_score,
      ai_notes: listing.ai_notes
    };

    try {
      await this.slack.notifyDeal(listing, dealAnalysis);

      // Mark as notified
      this.markNotified(listing.id);

      console.log(`✓ Notified: ${listing.title} (${listing.deal_percentage.toFixed(1)}% off)`);
    } catch (error) {
      console.error(`Error notifying deal ${listing.id}:`, error.message);
    }

    // Rate limit
    await new Promise(resolve => setTimeout(resolve, 1000));
  }

  markNotified(listingId) {
    // Create notifications table if it doesn't exist
    this.db.db.run(`
      CREATE TABLE IF NOT EXISTS notifications (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        listing_id INTEGER UNIQUE,
        notified_at TEXT DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY(listing_id) REFERENCES listings(id) ON DELETE CASCADE
      )
    `);

    // Insert notification record
    this.db.db.run(
      'INSERT OR IGNORE INTO notifications (listing_id) VALUES (?)',
      [listingId]
    );
  }

  async sendDailySummary() {
    console.log('=== Generating Daily Summary ===\n');

    const queries = [
      `SELECT COUNT(*) as total FROM listings WHERE is_active = 1 AND DATE(crawled_at) = DATE('now')`,
      `SELECT COUNT(*) as deals FROM deal_analysis WHERE is_deal = 1 AND DATE(analysis_date) = DATE('now')`,
      `SELECT AVG(deal_percentage) as avg FROM deal_analysis WHERE is_deal = 1 AND DATE(analysis_date) = DATE('now')`,
      `SELECT MAX(deal_percentage) as max FROM deal_analysis WHERE is_deal = 1 AND DATE(analysis_date) = DATE('now')`
    ];

    try {
      const results = await Promise.all(queries.map(query =>
        new Promise((resolve, reject) => {
          this.db.db.get(query, (err, row) => {
            if (err) reject(err);
            else resolve(row);
          });
        })
      ));

      const stats = {
        total_listings: results[0].total || 0,
        new_deals: results[1].deals || 0,
        avg_deal_percentage: Math.round((results[2].avg || 0) * 10) / 10,
        best_deal_percentage: Math.round((results[3].max || 0) * 10) / 10
      };

      if (stats.new_deals > 0) {
        await this.slack.notifyDailySummary(stats);
        console.log('✓ Daily summary sent to Slack');
      } else {
        console.log('No new deals today, skipping summary');
      }

    } catch (error) {
      console.error('Error sending daily summary:', error);
    }
  }

  close() {
    this.db.close();
  }
}

// Run if called directly
if (require.main === module) {
  const notifier = new DealNotifier();
  const command = process.argv[2] || 'check';

  (async () => {
    try {
      if (command === 'check') {
        await notifier.checkAndNotify();
      } else if (command === 'summary') {
        await notifier.sendDailySummary();
      } else {
        console.log('Usage:');
        console.log('  node deal-notifier.js check    - Check for new deals and notify');
        console.log('  node deal-notifier.js summary  - Send daily summary');
      }

      notifier.close();
      process.exit(0);
    } catch (error) {
      console.error('Notifier error:', error);
      notifier.close();
      process.exit(1);
    }
  })();
}

module.exports = DealNotifier;