← back to Wine Finder Next
app/api/best-deal/route.ts
137 lines
import { NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
const PRICE_HISTORY_FILE = '/root/Projects/wine-finder-next/data/price-history-5sec.jsonl'
interface WineEntry {
timestamp: string
name: string
price: number
rating: number | null
vintage: number
availability: string
url: string
image: string
source: string
searchQuery: string
winery: string
region: string
ratingsCount: number
}
interface PriceStats {
name: string
winery: string
vintage: number
image: string
url: string
source: string
region: string
currentPrice: number
lowestPrice: number
highestPrice: number
discount: number // percentage
savings: number // dollar amount
priceHistory: Array<{timestamp: string, price: number}>
trackingSince: string
lastUpdated: string
}
export async function GET() {
try {
// Read price history
if (!fs.existsSync(PRICE_HISTORY_FILE)) {
return NextResponse.json({ bestDeal: null, message: 'No price history available yet' })
}
const data = fs.readFileSync(PRICE_HISTORY_FILE, 'utf-8')
const lines = data.trim().split('\n').filter(l => l)
if (lines.length === 0) {
return NextResponse.json({ bestDeal: null, message: 'No price data available' })
}
// Parse all entries
const entries: WineEntry[] = lines.map(line => JSON.parse(line))
// Group by wine (name + vintage + winery)
const wineMap = new Map<string, WineEntry[]>()
entries.forEach(entry => {
const key = `${entry.name}|${entry.vintage}|${entry.winery}`
if (!wineMap.has(key)) {
wineMap.set(key, [])
}
wineMap.get(key)!.push(entry)
})
// Calculate stats for each wine
const stats: PriceStats[] = []
wineMap.forEach((entries, key) => {
const sorted = entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
const prices = entries.map(e => e.price)
const lowestPrice = Math.min(...prices)
const highestPrice = Math.max(...prices)
const currentPrice = sorted[sorted.length - 1].price
const latest = sorted[sorted.length - 1]
// Calculate discount from highest to current
const savings = highestPrice - currentPrice
const discount = highestPrice > 0 ? ((savings / highestPrice) * 100) : 0
stats.push({
name: latest.name,
winery: latest.winery,
vintage: latest.vintage,
image: latest.image,
url: latest.url,
source: latest.source,
region: latest.region,
currentPrice,
lowestPrice,
highestPrice,
discount: Math.round(discount * 10) / 10,
savings: Math.round(savings * 100) / 100,
priceHistory: sorted.map(e => ({ timestamp: e.timestamp, price: e.price })),
trackingSince: sorted[0].timestamp,
lastUpdated: sorted[sorted.length - 1].timestamp
})
})
// Find the best deal: SORT BY DOLLAR SAVINGS ONLY (not percentage)
// This ensures we show "$50 saved" instead of "50% off $2"
const bestDeal = stats
.filter(s => s.savings > 0)
.sort((a, b) => {
// ONLY sort by dollar savings - highest $ saved wins
return b.savings - a.savings
})[0]
// Also find: most expensive wine currently available (for display when no deals)
const mostExpensive = stats.sort((a, b) => b.currentPrice - a.currentPrice)[0]
// Find: biggest percentage discount (for comparison)
const biggestPercentageOff = stats
.filter(s => s.discount > 0)
.sort((a, b) => b.discount - a.discount)[0]
return NextResponse.json({
bestDeal: bestDeal || null, // Highest $ savings
mostExpensive: mostExpensive || null,
biggestPercentageOff: biggestPercentageOff || null, // Highest % off
totalWinesTracked: stats.length,
totalPricePoints: entries.length,
trackingStarted: entries[0]?.timestamp
})
} catch (error) {
console.error('Error calculating best deal:', error)
return NextResponse.json(
{ error: 'Failed to calculate best deal', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
)
}
}