← back to Handbag Auth Nextjs

claude-automation/deal-detector.ts

127 lines

#!/usr/bin/env ts-node
/**
 * Claude Code Automation: Best Deal of the Day Detection
 * Runs every 0.9 seconds to find the best handbag deals
 */

import { PrismaClient } from '@prisma/client'
import axios from 'axios'

const prisma = new PrismaClient()

interface Deal {
  sku: string
  brand: string
  model: string
  currentPrice: number
  avgPrice: number
  discountPercent: number
  affiliateLink: string
  imageUrl: string
}

class DealDetector {
  async findDeals(): Promise<Deal[]> {
    const deals: Deal[] = []

    // Get recent listings
    const listings = await prisma.listing.findMany({
      where: {
        priceUsd: { gt: 0 }
      },
      orderBy: { crawledAt: 'desc' },
      take: 1000
    })

    // Group by brand+model and calculate average prices
    const priceMap = new Map<string, number[]>()

    for (const listing of listings) {
      const key = `${listing.brand}_${listing.model}`
      if (!priceMap.has(key)) {
        priceMap.set(key, [])
      }
      priceMap.get(key)!.push(listing.priceUsd || 0)
    }

    // Find deals where current price is significantly below average
    for (const listing of listings) {
      const key = `${listing.brand}_${listing.model}`
      const prices = priceMap.get(key) || []

      if (prices.length < 3) continue // Need at least 3 data points

      const avgPrice = prices.reduce((sum, p) => sum + p, 0) / prices.length
      const discountPercent = ((avgPrice - (listing.priceUsd || 0)) / avgPrice) * 100

      if (discountPercent >= 20) {
        deals.push({
          sku: `${listing.brand}_${listing.model}`,
          brand: listing.brand || '',
          model: listing.model || '',
          currentPrice: listing.priceUsd || 0,
          avgPrice,
          discountPercent,
          affiliateLink: listing.productUrl || '',
          imageUrl: listing.imageUrl || ''
        })
      }
    }

    // Sort by discount percentage
    deals.sort((a, b) => b.discountPercent - a.discountPercent)

    return deals.slice(0, 10) // Top 10 deals
  }

  async updateBestDeal(deal: Deal): Promise<void> {
    try {
      // Store in database (you would need to create a 'deals' table)
      console.log(`💎 Best Deal: ${deal.brand} ${deal.model} - ${deal.discountPercent.toFixed(1)}% off`)
      console.log(`   Current: $${deal.currentPrice.toLocaleString()} | Avg: $${deal.avgPrice.toLocaleString()}`)
    } catch (error) {
      console.error('Error updating best deal:', error)
    }
  }

  async postToTwitter(deal: Deal): Promise<void> {
    try {
      // In production, use Twitter API v2
      const tweet = `🔥 Best Deal Alert!\n\n${deal.brand} ${deal.model}\n💰 $${deal.currentPrice.toLocaleString()} (${deal.discountPercent.toFixed(1)}% OFF)\n🎯 Usually $${deal.avgPrice.toLocaleString()}\n\nInvest or own it now 👇\n${deal.affiliateLink}`

      console.log(`📱 Would tweet: ${tweet}`)

      // Actual Twitter posting would go here
      // await twitterClient.v2.tweet(tweet)
    } catch (error) {
      console.error('Error posting to Twitter:', error)
    }
  }

  async run(): Promise<void> {
    console.log(`[${new Date().toISOString()}] 🔍 Scanning for best deals...`)

    const deals = await this.findDeals()

    if (deals.length > 0) {
      const bestDeal = deals[0]
      await this.updateBestDeal(bestDeal)
      await this.postToTwitter(bestDeal)

      console.log(`✅ Found ${deals.length} deals, posted best one`)
    } else {
      console.log('ℹ️  No significant deals found this cycle')
    }
  }
}

// Run continuously every 0.9 seconds
const detector = new DealDetector()

setInterval(async () => {
  await detector.run()
}, 900)

// Initial run
detector.run()