← back to Handbag Auth Nextjs
scripts/match-handbag-names.js
176 lines
/**
* Match Japanese handbag model names to English catalog
* Maps "エルメス バーキン" → "Birkin" etc.
*/
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
// Japanese to English model name mapping
const MODEL_MAPPINGS = {
// Hermès
'バーキン': 'Birkin',
'ケリー': 'Kelly',
'コンスタンス': 'Constance',
'エブリン': 'Evelyne',
'ピコタン': 'Picotin',
'リンディ': 'Lindy',
'ボリード': 'Bolide',
'ガーデンパーティ': 'Garden Party',
'エールバッグ': 'Herbag',
// Chanel
'クラシックフラップ': 'Classic Flap',
'ボーイバッグ': 'Boy Bag',
'マトラッセ': 'Classic Flap',
'デヴィル': 'Deauville',
'ガブリエル': 'Gabrielle',
'ココハンドル': 'Coco Handle',
'チェーンウォレット': 'Wallet on Chain',
'カンボン': 'Cambon',
// Louis Vuitton
'ネヴァーフル': 'Neverfull',
'スピーディ': 'Speedy',
'ポシェット': 'Pochette',
'アルマ': 'Alma',
'ネオノエ': 'NeoNoe',
'カプシーヌ': 'Capucines',
'キーポル': 'Keepall',
'ツイスト': 'Twist',
'ドーフィーヌ': 'Dauphine',
// Goyard
'サンルイ': 'Saint Louis',
'アルトワ': 'Artois',
'ベルヴェデール': 'Belvedere',
'アンジュー': 'Anjou',
'サイゴン': 'Saigon',
// Celine
'ラゲージ': 'Luggage',
'ベルトバッグ': 'Belt Bag',
'トリオンフ': 'Triomphe',
'クラシックボックス': 'Classic Box',
'カバ': 'Cabas',
// Prada
'ガレリア': 'Galleria',
'サフィアーノ': 'Saffiano',
'カナパ': 'Canapa',
'ダブルバッグ': 'Double Bag',
'カイエ': 'Cahier',
// Add more as needed...
}
// Brand name normalization
const BRAND_MAPPINGS = {
'エルメス': 'HERMÈS',
'シャネル': 'CHANEL',
'ルイヴィトン': 'LOUIS VUITTON',
'ルイ・ヴィトン': 'LOUIS VUITTON',
'ゴヤール': 'GOYARD',
'セリーヌ': 'CELINE',
'プラダ': 'PRADA',
'ディオール': 'DIOR',
'ボッテガヴェネタ': 'BOTTEGA VENETA',
'ボッテガ・ヴェネタ': 'BOTTEGA VENETA',
'サンローラン': 'SAINT LAURENT',
'イヴサンローラン': 'SAINT LAURENT',
'ロエベ': 'LOEWE',
'バレンシアガ': 'BALENCIAGA',
'ミュウミュウ': 'MIU MIU',
'フェンディ': 'FENDI',
'ジバンシー': 'GIVENCHY',
'ジバンシィ': 'GIVENCHY',
'ヴァレンティノ': 'VALENTINO',
'バーバリー': 'BURBERRY',
'コーチ': 'COACH',
'ケイトスペード': 'KATE SPADE',
'トリーバーチ': 'TORY BURCH',
'マルベリー': 'MULBERRY',
}
async function matchHandbagNames() {
console.log('🔄 Starting handbag name matching...\n')
try {
// Get all listings
const listings = await prisma.listing.findMany({
select: {
id: true,
brand: true,
model: true,
title: true,
},
})
console.log(`📊 Found ${listings.length.toLocaleString()} total listings\n`)
let updated = 0
let brandMatched = 0
let modelMatched = 0
for (const listing of listings) {
let needsUpdate = false
const updates = {}
// Match brand name
if (listing.brand) {
for (const [japanese, english] of Object.entries(BRAND_MAPPINGS)) {
if (listing.brand.includes(japanese)) {
updates.brand = english
needsUpdate = true
brandMatched++
break
}
}
}
// Match model name in model field or title
const searchText = `${listing.model || ''} ${listing.title || ''}`
for (const [japanese, english] of Object.entries(MODEL_MAPPINGS)) {
if (searchText.includes(japanese)) {
// Extract size if present (e.g., "25", "30", "35")
const sizeMatch = searchText.match(/(\d{2,3})(cm|センチ)?/)
const size = sizeMatch ? ` ${sizeMatch[1]}` : ''
updates.model = english + size
needsUpdate = true
modelMatched++
break
}
}
// Update if changes were made
if (needsUpdate) {
await prisma.listing.update({
where: { id: listing.id },
data: updates,
})
updated++
if (updated % 1000 === 0) {
console.log(`✅ Updated ${updated.toLocaleString()} listings...`)
}
}
}
console.log('\n✅ Name matching complete!')
console.log(` 📝 Total listings: ${listings.length.toLocaleString()}`)
console.log(` 🔄 Updated: ${updated.toLocaleString()}`)
console.log(` 🏷️ Brand matches: ${brandMatched.toLocaleString()}`)
console.log(` 📦 Model matches: ${modelMatched.toLocaleString()}`)
} catch (error) {
console.error('❌ Error:', error)
throw error
} finally {
await prisma.$disconnect()
}
}
// Run the script
matchHandbagNames()