← back to Wine Finder Next

scripts/daily-price-tracker.js

202 lines

#!/usr/bin/env node
/**
 * Daily Wine Price Tracker
 *
 * This script runs daily to:
 * 1. Fetch current prices from all wine sources
 * 2. Log prices to Google Sheets with timestamp
 * 3. Build historical price database
 * 4. Detect price changes and send alerts
 *
 * Usage: node scripts/daily-price-tracker.js
 * Cron: 0 8 * * * (daily at 8 AM)
 */

require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });

const vivinoScraper = require('../lib/services/vivinoScraper');
const totalWineScraper = require('../lib/services/totalWineScraper');
const klWineScraper = require('../lib/services/klWineScraper');
const googleSheets = require('../lib/services/googleSheets');
const slackNotifier = require('../lib/services/slackNotifier');

// Wines to track daily (add your favorites here)
const TRACKED_WINES = [
  'Caymus Cabernet Sauvignon',
  'Silver Oak Cabernet',
  'Opus One',
  'Screaming Eagle',
  'Château Margaux',
  'Dom Pérignon',
  'Stag\'s Leap',
  'Jordan Cabernet',
  'Far Niente Chardonnay',
  'Shafer Hillside Select'
];

// Generic popular searches to track market trends
const MARKET_SEARCHES = [
  'cabernet sauvignon',
  'pinot noir',
  'chardonnay',
  'merlot',
  'sauvignon blanc',
  'zinfandel'
];

async function trackDailyPrices() {
  console.log('🍷 Starting Daily Wine Price Tracker');
  console.log(`📅 ${new Date().toISOString()}`);
  console.log('');

  const results = {
    tracked: 0,
    logged: 0,
    priceChanges: [],
    errors: []
  };

  // Track specific wines
  console.log('📊 Tracking Specific Wines...');
  for (const wineName of TRACKED_WINES) {
    try {
      console.log(`  Searching: ${wineName}`);

      // Search all sources in parallel
      const [vivinoResult, totalResult, klResult] = await Promise.all([
        vivinoScraper.search(wineName).catch(e => ({ wines: [] })),
        totalWineScraper.search(wineName).catch(e => ({ wines: [] })),
        klWineScraper.search(wineName).catch(e => ({ wines: [] }))
      ]);

      const allWines = [
        ...(vivinoResult.wines || []),
        ...(totalResult.wines || []),
        ...(klResult.wines || [])
      ];

      if (allWines.length > 0) {
        // Log to Google Sheets
        const logResult = await googleSheets.logMultipleWines(allWines, wineName);

        if (logResult.success) {
          results.tracked += allWines.length;
          results.logged += logResult.count || 0;
          console.log(`    ✅ Logged ${allWines.length} prices`);
        }

        // Check for price changes
        for (const wine of allWines) {
          const lastPrice = await googleSheets.getLastPrice(wine.name);
          if (lastPrice && wine.price) {
            const priceDiff = parseFloat(wine.price) - parseFloat(lastPrice.price);
            const percentChange = (priceDiff / parseFloat(lastPrice.price)) * 100;

            if (Math.abs(percentChange) >= 10) {
              results.priceChanges.push({
                wine: wine.name,
                source: wine.source,
                oldPrice: lastPrice.price,
                newPrice: wine.price,
                change: percentChange.toFixed(2),
                url: wine.url
              });
            }
          }
        }
      } else {
        console.log(`    ⚠️  No results found`);
      }

      // Rate limiting - wait 2s between searches
      await new Promise(resolve => setTimeout(resolve, 2000));

    } catch (error) {
      console.error(`    ❌ Error tracking ${wineName}:`, error.message);
      results.errors.push({ wine: wineName, error: error.message });
    }
  }

  // Track market trends
  console.log('');
  console.log('📈 Tracking Market Trends...');
  for (const searchTerm of MARKET_SEARCHES) {
    try {
      console.log(`  Searching: ${searchTerm}`);

      const vivinoResult = await vivinoScraper.search(searchTerm);

      if (vivinoResult.wines && vivinoResult.wines.length > 0) {
        // Take top 5 results for market tracking
        const topWines = vivinoResult.wines.slice(0, 5);

        const logResult = await googleSheets.logMultipleWines(topWines, searchTerm);

        if (logResult.success) {
          results.tracked += topWines.length;
          results.logged += logResult.count || 0;
          console.log(`    ✅ Logged ${topWines.length} market prices`);
        }
      }

      await new Promise(resolve => setTimeout(resolve, 2000));

    } catch (error) {
      console.error(`    ❌ Error tracking ${searchTerm}:`, error.message);
      results.errors.push({ search: searchTerm, error: error.message });
    }
  }

  // Send summary report
  console.log('');
  console.log('📧 Sending Summary Report...');

  const summary = {
    date: new Date().toLocaleDateString(),
    tracked: results.tracked,
    logged: results.logged,
    priceChanges: results.priceChanges.length,
    errors: results.errors.length
  };

  console.log(`  ✅ Tracked ${results.tracked} wines`);
  console.log(`  ✅ Logged ${results.logged} prices to Google Sheets`);
  console.log(`  📊 Detected ${results.priceChanges.length} price changes`);
  console.log(`  ⚠️  ${results.errors.length} errors`);

  // Send Slack notification if there are significant price changes
  if (results.priceChanges.length > 0) {
    const alerts = results.priceChanges.map(pc => ({
      wine: pc.wine,
      currentPrice: pc.newPrice,
      lastPrice: pc.oldPrice,
      percentageChange: pc.change,
      source: pc.source,
      url: pc.url
    }));

    await slackNotifier.sendAlerts(alerts);
    console.log(`  ✅ Sent Slack alerts for ${results.priceChanges.length} price changes`);
  }

  console.log('');
  console.log('🎉 Daily Price Tracking Complete!');

  return summary;
}

// Run the tracker
if (require.main === module) {
  trackDailyPrices()
    .then(summary => {
      console.log('Summary:', summary);
      process.exit(0);
    })
    .catch(error => {
      console.error('Fatal error:', error);
      process.exit(1);
    });
}

module.exports = { trackDailyPrices, TRACKED_WINES, MARKET_SEARCHES };