← back to Handbag Authentication
api/retail-price-database.js
251 lines
/**
* Retail Price Database
* Scrapes and maintains original MSRP for luxury handbags
* Sources: Fashionphile archives, PurseBlog, official brand sites
*/
const axios = require('axios');
const cheerio = require('cheerio');
class RetailPriceDatabase {
constructor() {
// Hardcoded database of known retail prices (continuously updated)
this.knownRetailPrices = {
// HERMÈS (USD retail prices)
'Hermes Birkin 25': { msrp: 10400, year: 2024, material: 'Togo leather' },
'Hermes Birkin 30': { msrp: 11200, year: 2024, material: 'Togo leather' },
'Hermes Birkin 35': { msrp: 12100, year: 2024, material: 'Togo leather' },
'Hermes Kelly 25': { msrp: 10200, year: 2024, material: 'Epsom leather' },
'Hermes Kelly 28': { msrp: 10600, year: 2024, material: 'Epsom leather' },
'Hermes Kelly 32': { msrp: 11400, year: 2024, material: 'Togo leather' },
'Hermes Constance 18': { msrp: 8500, year: 2024, material: 'Epsom leather' },
'Hermes Constance 24': { msrp: 9200, year: 2024, material: 'Epsom leather' },
'Hermes Picotin 18': { msrp: 3200, year: 2024, material: 'Clemence leather' },
'Hermes Picotin 22': { msrp: 3600, year: 2024, material: 'Clemence leather' },
'Hermes Evelyne 16': { msrp: 3100, year: 2024, material: 'Clemence leather' },
'Hermes Evelyne 29': { msrp: 3700, year: 2024, material: 'Clemence leather' },
'Hermes Evelyne 33': { msrp: 4100, year: 2024, material: 'Clemence leather' },
'Hermes Garden Party 30': { msrp: 3400, year: 2024, material: 'Negonda leather' },
'Hermes Garden Party 36': { msrp: 3800, year: 2024, material: 'Negonda leather' },
'Hermes Bolide 27': { msrp: 8200, year: 2024, material: 'Epsom leather' },
'Hermes Bolide 31': { msrp: 8800, year: 2024, material: 'Epsom leather' },
// CHANEL (USD retail prices)
'Chanel Classic Flap Small': { msrp: 9500, year: 2024, material: 'Lambskin' },
'Chanel Classic Flap Medium': { msrp: 10200, year: 2024, material: 'Lambskin' },
'Chanel Classic Flap Jumbo': { msrp: 11100, year: 2024, material: 'Lambskin' },
'Chanel Boy Bag Small': { msrp: 6200, year: 2024, material: 'Calfskin' },
'Chanel Boy Bag Medium': { msrp: 7400, year: 2024, material: 'Calfskin' },
'Chanel Boy Bag Large': { msrp: 7900, year: 2024, material: 'Calfskin' },
'Chanel 19 Small': { msrp: 6800, year: 2024, material: 'Lambskin' },
'Chanel 19 Medium': { msrp: 7900, year: 2024, material: 'Lambskin' },
'Chanel 19 Large': { msrp: 8700, year: 2024, material: 'Lambskin' },
'Chanel Gabrielle Small': { msrp: 4200, year: 2024, material: 'Calfskin' },
'Chanel Gabrielle Medium': { msrp: 4700, year: 2024, material: 'Calfskin' },
'Chanel Gabrielle Large': { msrp: 5200, year: 2024, material: 'Calfskin' },
'Chanel Deauville Tote': { msrp: 3800, year: 2024, material: 'Canvas' },
'Chanel GST Grand Shopping Tote': { msrp: 3200, year: 2023, material: 'Caviar', discontinued: true },
// LOUIS VUITTON (USD retail prices)
'Louis Vuitton Speedy 25': { msrp: 1760, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Speedy 30': { msrp: 1900, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Speedy 35': { msrp: 2040, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Neverfull MM': { msrp: 2030, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Neverfull GM': { msrp: 2170, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Alma BB': { msrp: 2100, year: 2024, material: 'Epi leather' },
'Louis Vuitton Alma PM': { msrp: 2300, year: 2024, material: 'Epi leather' },
'Louis Vuitton Pochette Metis': { msrp: 2840, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Capucines BB': { msrp: 6300, year: 2024, material: 'Taurillon leather' },
'Louis Vuitton Capucines MM': { msrp: 7050, year: 2024, material: 'Taurillon leather' },
'Louis Vuitton Onthego MM': { msrp: 3100, year: 2024, material: 'Monogram canvas' },
'Louis Vuitton Onthego GM': { msrp: 3350, year: 2024, material: 'Monogram canvas' },
// GUCCI (USD retail prices)
'Gucci Marmont Small': { msrp: 2490, year: 2024, material: 'Matelasse leather' },
'Gucci Marmont Medium': { msrp: 2980, year: 2024, material: 'Matelasse leather' },
'Gucci Marmont Large': { msrp: 3490, year: 2024, material: 'Matelasse leather' },
'Gucci Dionysus Small': { msrp: 2850, year: 2024, material: 'Suede' },
'Gucci Dionysus Medium': { msrp: 3200, year: 2024, material: 'Suede' },
'Gucci Jackie 1961 Small': { msrp: 3100, year: 2024, material: 'Leather' },
'Gucci Jackie 1961 Medium': { msrp: 3600, year: 2024, material: 'Leather' },
'Gucci Ophidia GG Small': { msrp: 1850, year: 2024, material: 'GG Supreme canvas' },
'Gucci Ophidia GG Medium': { msrp: 2300, year: 2024, material: 'GG Supreme canvas' },
// DIOR (USD retail prices)
'Dior Lady Dior Small': { msrp: 5800, year: 2024, material: 'Lambskin' },
'Dior Lady Dior Medium': { msrp: 6500, year: 2024, material: 'Lambskin' },
'Dior Lady Dior Large': { msrp: 7200, year: 2024, material: 'Lambskin' },
'Dior Saddle Bag': { msrp: 3900, year: 2024, material: 'Oblique canvas' },
'Dior Book Tote Small': { msrp: 3500, year: 2024, material: 'Embroidered canvas' },
'Dior Book Tote Medium': { msrp: 3900, year: 2024, material: 'Embroidered canvas' },
'Dior Book Tote Large': { msrp: 4300, year: 2024, material: 'Embroidered canvas' },
// PRADA (USD retail prices)
'Prada Galleria Small': { msrp: 3150, year: 2024, material: 'Saffiano leather' },
'Prada Galleria Medium': { msrp: 3750, year: 2024, material: 'Saffiano leather' },
'Prada Galleria Large': { msrp: 4200, year: 2024, material: 'Saffiano leather' },
'Prada Re-Edition 2005': { msrp: 1350, year: 2024, material: 'Nylon' },
'Prada Cleo': { msrp: 2950, year: 2024, material: 'Brushed leather' },
'Prada Cahier': { msrp: 3200, year: 2024, material: 'Calf leather' },
// FENDI (USD retail prices)
'Fendi Baguette': { msrp: 3590, year: 2024, material: 'Leather' },
'Fendi Peekaboo Small': { msrp: 4790, year: 2024, material: 'Leather' },
'Fendi Peekaboo Medium': { msrp: 5490, year: 2024, material: 'Leather' },
'Fendi First Small': { msrp: 2490, year: 2024, material: 'Leather' },
'Fendi First Medium': { msrp: 2990, year: 2024, material: 'Leather' }
};
}
/**
* Find retail price for a bag based on title/description
*/
findRetailPrice(title, brand = null) {
const titleLower = title.toLowerCase();
// Try exact matches first
for (const [key, data] of Object.entries(this.knownRetailPrices)) {
if (titleLower.includes(key.toLowerCase())) {
return {
...data,
modelName: key,
confidence: 'high'
};
}
}
// Try partial matches by extracting key terms
const models = [
'birkin', 'kelly', 'constance', 'picotin', 'evelyne', 'garden party', 'bolide',
'classic flap', 'boy bag', 'chanel 19', 'gabrielle', 'deauville',
'speedy', 'neverfull', 'alma', 'pochette metis', 'capucines', 'onthego',
'marmont', 'dionysus', 'jackie', 'ophidia',
'lady dior', 'saddle', 'book tote',
'galleria', 're-edition', 'cleo', 'cahier',
'baguette', 'peekaboo', 'first'
];
for (const model of models) {
if (titleLower.includes(model)) {
// Find size if present
const sizes = ['25', '30', '35', '18', '22', '16', '29', '33', '27', '31',
'small', 'medium', 'large', 'bb', 'pm', 'mm', 'gm'];
let matchedSize = null;
for (const size of sizes) {
if (titleLower.includes(size)) {
matchedSize = size;
break;
}
}
// Try to find matching entry
const brandPrefix = brand || this.detectBrand(title);
const searchKey = matchedSize
? `${brandPrefix} ${model} ${matchedSize}`
: `${brandPrefix} ${model}`;
for (const [key, data] of Object.entries(this.knownRetailPrices)) {
if (key.toLowerCase().includes(searchKey.toLowerCase())) {
return {
...data,
modelName: key,
confidence: 'medium'
};
}
}
}
}
return null;
}
/**
* Detect brand from title
*/
detectBrand(title) {
const brands = ['Hermes', 'Chanel', 'Louis Vuitton', 'Gucci', 'Dior', 'Prada', 'Fendi'];
const titleLower = title.toLowerCase();
for (const brand of brands) {
if (titleLower.includes(brand.toLowerCase()) ||
titleLower.includes(brand.replace(' ', '').toLowerCase())) {
return brand;
}
}
return null;
}
/**
* Scrape current retail prices from Fashionphile price guide
*/
async scrapeFashionphilePriceGuide(brand, model) {
try {
const url = `https://www.fashionphile.com/guide/${brand.toLowerCase()}-${model.toLowerCase().replace(/\s+/g, '-')}`;
const response = await axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
timeout: 15000
});
const $ = cheerio.load(response.data);
// Extract MSRP if available
const msrpText = $('.retail-price, .msrp, [class*="retail"]').text();
const msrpMatch = msrpText.match(/\$?([\d,]+)/);
const msrp = msrpMatch ? parseInt(msrpMatch[1].replace(/,/g, '')) : null;
return {
brand,
model,
msrp,
source: 'fashionphile_guide',
lastUpdated: new Date().toISOString()
};
} catch (error) {
console.error('Error scraping Fashionphile price guide:', error.message);
return null;
}
}
/**
* Get all retail prices for a brand
*/
getBrandRetailPrices(brand) {
const results = {};
for (const [key, data] of Object.entries(this.knownRetailPrices)) {
if (key.startsWith(brand)) {
results[key] = data;
}
}
return results;
}
/**
* Calculate depreciation/appreciation
*/
calculateValueChange(retailPrice, currentPrice) {
if (!retailPrice || !currentPrice) return null;
const change = currentPrice - retailPrice;
const changePercent = ((change / retailPrice) * 100).toFixed(1);
return {
retailPrice,
currentPrice,
change,
changePercent: parseFloat(changePercent),
appreciation: change > 0,
status: change > 0 ? 'Appreciated' : change === 0 ? 'Holding value' : 'Depreciated'
};
}
}
module.exports = RetailPriceDatabase;