← back to Handbag Auth Nextjs
scripts/generate-historical-prices.ts
251 lines
#!/usr/bin/env ts-node
/**
* Historical Price Generator for Luxury Handbags
*
* Based on real market research:
* - Hermès Birkin: 500% increase over 35 years (5% CAGR)
* - Chanel Classic Flap: 100% increase over 5 years (15% CAGR)
* - Louis Vuitton: 8-12% annual appreciation
* - Luxury resale market: 10% CAGR overall
*
* Sources: Baghunter study, The RealReal annual reports
*/
import Database from 'better-sqlite3'
import path from 'path'
const DB_PATH = path.join(process.cwd(), 'data', 'handbags.db')
// Real market appreciation rates by brand (Annual CAGR)
const BRAND_APPRECIATION: Record<string, { min: number; max: number; avg: number }> = {
'Hermes': { min: 3, max: 7, avg: 5 }, // 5% CAGR (Baghunter)
'Hermès': { min: 3, max: 7, avg: 5 }, // Alternative spelling
'Chanel': { min: 10, max: 20, avg: 15 }, // 15% CAGR (doubled in 5y)
'Louis Vuitton': { min: 8, max: 12, avg: 10 }, // 8-12% average
'Dior': { min: 6, max: 10, avg: 8 },
'Bottega Veneta': { min: 5, max: 9, avg: 7 },
'Celine': { min: 4, max: 8, avg: 6 },
'Gucci': { min: 3, max: 7, avg: 5 }
}
// Model rarity multipliers (rare models appreciate faster)
const MODEL_MULTIPLIERS: Record<string, number> = {
// Hermès
'birkin': 1.3, // Birkins appreciate 30% faster
'kelly': 1.2, // Kellys 20% faster
'constance': 1.1,
// Chanel
'classic flap': 1.25,
'boy': 1.1,
'19': 1.05,
// Louis Vuitton
'neverfull': 0.9, // Common models appreciate slower
'speedy': 0.85,
'capucines': 1.15
}
// Condition impact on historical price trajectory
const CONDITION_FACTORS: Record<string, number> = {
'New/Unused': 1.0,
'Excellent': 0.95,
'Very Good': 0.90,
'Good': 0.85,
'Fair': 0.75,
'Used': 0.90
}
interface HistoricalPoint {
year: number
price: number
marketCondition: string
}
/**
* Generate realistic historical prices for a handbag
*/
function generateHistoricalPrices(
currentPrice: number,
brand: string,
model: string,
condition: string,
yearsBack: number = 10
): HistoricalPoint[] {
const appreciation = BRAND_APPRECIATION[brand] || { min: 3, max: 8, avg: 5 }
// Find model multiplier
let modelMultiplier = 1.0
const modelLower = model.toLowerCase()
for (const [key, multiplier] of Object.entries(MODEL_MULTIPLIERS)) {
if (modelLower.includes(key)) {
modelMultiplier = multiplier
break
}
}
// Get condition factor
const conditionFactor = CONDITION_FACTORS[condition] || 0.9
// Calculate effective CAGR
const effectiveCAGR = appreciation.avg * modelMultiplier * conditionFactor
const currentYear = new Date().getFullYear()
const history: HistoricalPoint[] = []
// Work backwards from current price
for (let i = 0; i <= yearsBack; i++) {
const year = currentYear - i
const yearsFromNow = i
// Calculate price using compound interest formula: P = FV / (1 + r)^n
const historicalPrice = currentPrice / Math.pow(1 + effectiveCAGR / 100, yearsFromNow)
// Add market volatility (±2-5% random variation per year)
const volatility = 1 + (Math.random() * 0.05 - 0.025)
const priceWithVolatility = historicalPrice * volatility
// Market conditions based on year
let marketCondition = 'Normal Market'
if (year === 2020) marketCondition = 'COVID-19 Dip'
if (year === 2021) marketCondition = 'Post-COVID Surge'
if (year === 2022) marketCondition = 'Luxury Boom'
if (year >= 2023) marketCondition = 'Market Correction'
history.unshift({
year,
price: Math.round(priceWithVolatility),
marketCondition
})
}
return history
}
/**
* Calculate appreciation percentage from historical data
*/
function calculateAppreciation(history: HistoricalPoint[]): number {
if (history.length < 2) return 0
const firstPrice = history[0].price
const lastPrice = history[history.length - 1].price
return Math.round(((lastPrice - firstPrice) / firstPrice) * 100)
}
/**
* Update database with enhanced historical prices
*/
function updateDatabaseWithHistory() {
console.log('📊 Generating Enhanced Historical Prices')
console.log('=' .repeat(60))
const db = new Database(DB_PATH)
try {
// Add historical_prices column if it doesn't exist
try {
db.exec(`
ALTER TABLE listings
ADD COLUMN historical_prices TEXT
`)
console.log('✅ Added historical_prices column')
} catch (e) {
console.log('ℹ️ historical_prices column already exists')
}
// Get all active listings
const listings = db.prepare(`
SELECT id, price_usd, brand, title, condition
FROM listings
WHERE is_active = 1
LIMIT 1000
`).all() as Array<{
id: number
price_usd: number
brand: string
title: string
condition: string
}>
console.log(`\n📦 Processing ${listings.length} listings...\n`)
let updated = 0
const updateStmt = db.prepare(`
UPDATE listings
SET historical_prices = ?
WHERE id = ?
`)
for (const listing of listings) {
// Generate 10 years of historical data
const history = generateHistoricalPrices(
listing.price_usd,
listing.brand,
listing.title,
listing.condition || 'Used',
10
)
// Store as JSON
const historyJSON = JSON.stringify(history)
updateStmt.run(historyJSON, listing.id)
updated++
if (updated % 100 === 0) {
const appreciation = calculateAppreciation(history)
console.log(` ✓ ${updated} bags processed | Latest: ${listing.brand} (+${appreciation}%)`)
}
}
console.log(`\n✅ Successfully generated historical prices for ${updated} bags`)
// Show sample analysis
console.log('\n' + '='.repeat(60))
console.log('📈 Sample Historical Analysis')
console.log('='.repeat(60))
const samples = db.prepare(`
SELECT id, brand, title, price_usd, historical_prices
FROM listings
WHERE is_active = 1
AND historical_prices IS NOT NULL
ORDER BY price_usd DESC
LIMIT 5
`).all() as Array<{
id: number
brand: string
title: string
price_usd: number
historical_prices: string
}>
for (const sample of samples) {
const history = JSON.parse(sample.historical_prices) as HistoricalPoint[]
const appreciation = calculateAppreciation(history)
const oldestYear = history[0].year
const oldestPrice = history[0].price
console.log(`\n${sample.brand} - ${sample.title.substring(0, 40)}...`)
console.log(` ${oldestYear}: $${oldestPrice.toLocaleString()} → 2025: $${sample.price_usd.toLocaleString()}`)
console.log(` Appreciation: +${appreciation}% over ${history.length - 1} years`)
console.log(` CAGR: ${(appreciation / (history.length - 1)).toFixed(1)}% per year`)
}
console.log('\n' + '='.repeat(60))
console.log('✨ Historical price generation complete!')
console.log('='.repeat(60))
} catch (error) {
console.error('❌ Error generating historical prices:', error)
process.exit(1)
} finally {
db.close()
}
}
// Run the generator
updateDatabaseWithHistory()