← back to Designer Wallcoverings

DW-Agents/dw-agents/daily-price-tracker.ts

289 lines

/**
 * Daily Price Tracker for Past Orders
 * Automatically scrapes prices every morning for items you've ordered before
 * Shows best current prices for easy reordering
 */

import fetch from 'node-fetch';
import * as cheerio from 'cheerio';
import * as fs from 'fs';

interface Order {
  id: string;
  item: string;
  vendor: string;
  price: number;
  status: string;
  timestamp: Date;
}

interface PriceTracking {
  item: string;
  originalPrice: number;
  currentPrice: number | null;
  priceChange: number;
  percentChange: number;
  url: string;
  lastChecked: Date;
  bestDeal: boolean;
}

interface PriceHistory {
  item: string;
  prices: Array<{
    date: Date;
    price: number;
    vendor: string;
  }>;
  lowestPrice: number;
  highestPrice: number;
  averagePrice: number;
}

/**
 * Scrape current Amazon price and product URL for an item
 */
async function scrapeCurrentPrice(itemName: string): Promise<{ price: number; url: string } | null> {
  try {
    const amazonUrl = `https://www.amazon.com/s?k=${encodeURIComponent(itemName)}`;
    const response = await fetch(amazonUrl, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'text/html,application/xhtml+xml',
        'Accept-Language': 'en-US,en;q=0.5'
      }
    });

    if (response.ok) {
      const html = await response.text();
      const $ = cheerio.load(html);

      // Find first product result
      const firstProduct = $('div[data-component-type="s-search-result"]').first();

      if (firstProduct.length > 0) {
        const priceWhole = firstProduct.find('.a-price-whole').first().text().replace(/[^0-9]/g, '');
        const priceFraction = firstProduct.find('.a-price-fraction').first().text().replace(/[^0-9]/g, '');
        const productLink = firstProduct.find('h2 a').attr('href');

        if (priceWhole && productLink) {
          const price = parseFloat(`${priceWhole}.${priceFraction || '00'}`);
          const fullUrl = `https://www.amazon.com${productLink}`;
          const urlWithTag = fullUrl + (fullUrl.includes('?') ? '&' : '?') + 'tag=designerwal0b-20';

          console.log(`  ✅ ${itemName.substring(0, 40)}: $${price}`);
          return { price, url: urlWithTag };
        } else {
          console.log(`  ⚠️ Could not find price/link for: ${itemName.substring(0, 40)}`);
        }
      } else {
        console.log(`  ⚠️ No product results found for: ${itemName.substring(0, 40)}`);
      }
    } else {
      console.log(`  ⚠️ HTTP ${response.status} for: ${itemName.substring(0, 40)}`);
    }
  } catch (e) {
    console.log(`  ⚠️ Failed to scrape: ${itemName.substring(0, 40)}`);
  }

  return null;
}

/**
 * Track prices for all past orders
 */
async function trackAllPrices(): Promise<PriceTracking[]> {
  console.log('🔍 Daily Price Tracker - Starting...\n');
  console.log(`📅 Date: ${new Date().toLocaleDateString()}`);
  console.log(`⏰ Time: ${new Date().toLocaleTimeString()}\n`);

  // Load past orders
  const ordersPath = '/root/Projects/Designer-Wallcoverings/DW-Agents/data/amazon-orders.json';
  if (!fs.existsSync(ordersPath)) {
    console.log('❌ No order history found');
    return [];
  }

  const orders: Order[] = JSON.parse(fs.readFileSync(ordersPath, 'utf-8'));
  console.log(`📦 Tracking ${orders.length} items from past orders\n`);

  const priceTracking: PriceTracking[] = [];

  // Get unique items (avoid duplicates)
  const uniqueItems = new Map<string, Order>();
  orders.forEach(order => {
    const key = order.item.toLowerCase();
    if (!uniqueItems.has(key) || new Date(order.timestamp) > new Date(uniqueItems.get(key)!.timestamp)) {
      uniqueItems.set(key, order);
    }
  });

  // Scrape current prices
  let count = 0;
  for (const [, order] of uniqueItems) {
    count++;
    console.log(`[${count}/${uniqueItems.size}] Checking: ${order.item.substring(0, 50)}...`);

    const result = await scrapeCurrentPrice(order.item);

    if (result) {
      const priceChange = result.price - order.price;
      const percentChange = ((priceChange / order.price) * 100);

      priceTracking.push({
        item: order.item,
        originalPrice: order.price,
        currentPrice: result.price,
        priceChange,
        percentChange,
        url: result.url,
        lastChecked: new Date(),
        bestDeal: priceChange < 0
      });
    }

    // Rate limiting - wait 2 seconds between requests
    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  return priceTracking;
}

/**
 * Save price tracking data
 */
function savePriceTracking(tracking: PriceTracking[]): void {
  const outputPath = '/root/Projects/Designer-Wallcoverings/DW-Agents/data/price-tracking.json';
  fs.writeFileSync(outputPath, JSON.stringify(tracking, null, 2));
  console.log(`\n💾 Saved price tracking to: ${outputPath}`);

  // Also update price history
  updatePriceHistory(tracking);
}

/**
 * Update price history over time
 */
function updatePriceHistory(tracking: PriceTracking[]): void {
  const historyPath = '/root/Projects/Designer-Wallcoverings/DW-Agents/data/price-history.json';

  let history: Record<string, PriceHistory> = {};
  if (fs.existsSync(historyPath)) {
    history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
  }

  tracking.forEach(track => {
    if (!track.currentPrice) return;

    if (!history[track.item]) {
      history[track.item] = {
        item: track.item,
        prices: [],
        lowestPrice: track.currentPrice,
        highestPrice: track.currentPrice,
        averagePrice: track.currentPrice
      };
    }

    const item = history[track.item];
    item.prices.push({
      date: new Date(),
      price: track.currentPrice,
      vendor: 'Amazon'
    });

    // Keep only last 90 days
    item.prices = item.prices.filter(p =>
      (new Date().getTime() - new Date(p.date).getTime()) < 90 * 24 * 60 * 60 * 1000
    );

    // Update stats
    const prices = item.prices.map(p => p.price);
    item.lowestPrice = Math.min(...prices);
    item.highestPrice = Math.max(...prices);
    item.averagePrice = prices.reduce((a, b) => a + b, 0) / prices.length;

    history[track.item] = item;
  });

  fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
  console.log(`📈 Updated price history`);
}

/**
 * Generate daily report
 */
async function generateDailyReport(): Promise<void> {
  const tracking = await trackAllPrices();

  if (tracking.length === 0) {
    console.log('\n❌ No prices tracked');
    return;
  }

  savePriceTracking(tracking);

  // Generate report
  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('📊 DAILY PRICE REPORT');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

  // Price drops
  const drops = tracking.filter(t => t.priceChange < 0).sort((a, b) => a.percentChange - b.percentChange);
  if (drops.length > 0) {
    console.log('🎉 PRICE DROPS - BUY NOW!\n');
    drops.forEach(item => {
      console.log(`✅ ${item.item.substring(0, 60)}`);
      console.log(`   Was: $${item.originalPrice.toFixed(2)} → Now: $${item.currentPrice?.toFixed(2)}`);
      console.log(`   💰 Save $${Math.abs(item.priceChange).toFixed(2)} (${Math.abs(item.percentChange).toFixed(1)}% off)`);
      console.log(`   🛒 ${item.url}\n`);
    });
  }

  // Price increases
  const increases = tracking.filter(t => t.priceChange > 0);
  if (increases.length > 0) {
    console.log(`\n⚠️  PRICE INCREASES (${increases.length} items)\n`);
    increases.slice(0, 3).forEach(item => {
      console.log(`❗ ${item.item.substring(0, 60)}`);
      console.log(`   Was: $${item.originalPrice.toFixed(2)} → Now: $${item.currentPrice?.toFixed(2)} (+${item.percentChange.toFixed(1)}%)\n`);
    });
  }

  // No change
  const noChange = tracking.filter(t => Math.abs(t.priceChange) < 0.50);
  console.log(`\n📊 Summary:`);
  console.log(`   Price Drops: ${drops.length} items`);
  console.log(`   Price Increases: ${increases.length} items`);
  console.log(`   Stable Prices: ${noChange.length} items`);
  console.log(`   Total Tracked: ${tracking.length} items\n`);

  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
}

/**
 * Get reorder recommendations based on price tracking
 */
export function getReorderRecommendations(): PriceTracking[] {
  const trackingPath = '/root/Projects/Designer-Wallcoverings/DW-Agents/data/price-tracking.json';

  if (!fs.existsSync(trackingPath)) {
    return [];
  }

  const tracking: PriceTracking[] = JSON.parse(fs.readFileSync(trackingPath, 'utf-8'));

  // Filter for best deals (price drops)
  return tracking
    .filter(t => t.priceChange < 0)
    .sort((a, b) => a.percentChange - b.percentChange)
    .slice(0, 10);
}

// Run if called directly
if (require.main === module) {
  generateDailyReport().catch(console.error);
}

export { trackAllPrices, savePriceTracking, generateDailyReport, PriceTracking };