← back to Watches
utils/omega-museum-curator.js
340 lines
/**
* Omega Museum Curator
* Finds the 100 most expensive museum-quality Omega watches from real auction records
* Sources: Sotheby's, Christie's, Phillips, Antiquorum
*/
import { chromium } from 'playwright';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class OmegaMuseumCurator {
constructor() {
this.browser = null;
this.context = null;
this.museumPieces = [];
this.outputDir = path.join(__dirname, '../museum');
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
}
async init() {
if (!this.browser) {
this.browser = await chromium.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
this.context = await this.browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 }
});
}
}
/**
* Scrape Sotheby's auction records for museum-quality Omegas
*/
async scrapeSothebys() {
await this.init();
const page = await this.context.newPage();
console.log('\n🏛️ Searching Sotheby\'s auction archives...');
try {
const searchUrl = 'https://www.sothebys.com/en/results?from=0&size=100&query=omega%20watch&f_auction_date_sort=desc';
await page.goto(searchUrl, { waitUntil: 'networkidle', timeout: 30000 });
await page.waitForSelector('.lot-card', { timeout: 10000 });
const results = await page.evaluate(() => {
const items = [];
const cards = document.querySelectorAll('.lot-card');
cards.forEach(card => {
const titleEl = card.querySelector('h3, .lot-title');
const priceEl = card.querySelector('.estimate, .price, .sold-for');
const imageEl = card.querySelector('img');
const linkEl = card.querySelector('a');
if (titleEl && priceEl) {
const title = titleEl.textContent.trim();
const priceText = priceEl.textContent.trim();
const priceMatch = priceText.match(/[\d,]+/g);
const price = priceMatch ? parseInt(priceMatch[priceMatch.length - 1].replace(/,/g, '')) : null;
if (title.toLowerCase().includes('omega') && price && price > 5000) {
items.push({
title,
price,
image: imageEl?.src || null,
url: linkEl?.href || null,
source: 'Sotheby\'s',
estimatedValue: price
});
}
}
});
return items;
});
console.log(`✓ Found ${results.length} Omega watches at Sotheby's`);
this.museumPieces.push(...results);
} catch (error) {
console.error(`✗ Sotheby's scraping failed:`, error.message);
} finally {
await page.close();
}
}
/**
* Scrape Christie's auction records
*/
async scrapeChristies() {
await this.init();
const page = await this.context.newPage();
console.log('\n🏛️ Searching Christie\'s auction archives...');
try {
const searchUrl = 'https://www.christies.com/en/results?keyword=omega%20watch&sort=soldPrice-desc';
await page.goto(searchUrl, { waitUntil: 'networkidle', timeout: 30000 });
await page.waitForSelector('[data-testid="lot-card"]', { timeout: 10000 });
const results = await page.evaluate(() => {
const items = [];
const cards = document.querySelectorAll('[data-testid="lot-card"]');
cards.forEach(card => {
const titleEl = card.querySelector('h3, [class*="title"]');
const priceEl = card.querySelector('[class*="price"], [class*="estimate"]');
const imageEl = card.querySelector('img');
const linkEl = card.querySelector('a');
if (titleEl && priceEl) {
const title = titleEl.textContent.trim();
const priceText = priceEl.textContent.trim();
const priceMatch = priceText.match(/[\d,]+/g);
const price = priceMatch ? parseInt(priceMatch[priceMatch.length - 1].replace(/,/g, '')) : null;
if (title.toLowerCase().includes('omega') && price && price > 5000) {
items.push({
title,
price,
image: imageEl?.src || null,
url: linkEl?.href || null,
source: 'Christie\'s',
estimatedValue: price
});
}
}
});
return items;
});
console.log(`✓ Found ${results.length} Omega watches at Christie's`);
this.museumPieces.push(...results);
} catch (error) {
console.error(`✗ Christie's scraping failed:`, error.message);
} finally {
await page.close();
}
}
/**
* Create museum collection of top 100 pieces
*/
createMuseum() {
console.log('\n🏛️ Curating museum collection...');
// Sort by price (highest first)
const sorted = this.museumPieces
.filter(p => p.price && p.price > 0)
.sort((a, b) => b.price - a.price);
// Take top 100
const top100 = sorted.slice(0, 100);
const museum = {
title: 'Omega Watch Museum - Top 100 Most Expensive Pieces',
description: 'Museum-quality Omega watches from prestigious auction houses',
curatedAt: new Date().toISOString(),
totalPieces: top100.length,
totalValue: top100.reduce((sum, p) => sum + p.price, 0),
sources: ['Sotheby\'s', 'Christie\'s', 'Phillips', 'Antiquorum'],
collection: top100.map((piece, index) => ({
rank: index + 1,
...piece,
category: this.categorize(piece.title),
era: this.determineEra(piece.title),
rarity: this.determineRarity(piece.price, index)
}))
};
// Save museum data
const museumPath = path.join(this.outputDir, 'omega-museum-top-100.json');
fs.writeFileSync(museumPath, JSON.stringify(museum, null, 2));
console.log(`✓ Museum collection saved: ${museumPath}`);
// Generate HTML museum
this.generateMuseumHTML(museum);
return museum;
}
categorize(title) {
const lower = title.toLowerCase();
if (lower.includes('speedmaster')) return 'Speedmaster';
if (lower.includes('seamaster')) return 'Seamaster';
if (lower.includes('constellation')) return 'Constellation';
if (lower.includes('de ville')) return 'De Ville';
if (lower.includes('railmaster')) return 'Railmaster';
if (lower.includes('flightmaster')) return 'Flightmaster';
return 'Other';
}
determineEra(title) {
if (title.match(/19[45]\d/)) return 'Vintage (1940s-1950s)';
if (title.match(/196\d/)) return 'Classic (1960s)';
if (title.match(/197\d/)) return 'Modern Vintage (1970s)';
if (title.match(/198\d/)) return 'Contemporary (1980s)';
if (title.match(/199\d/)) return 'Recent (1990s)';
if (title.match(/20[01]\d/)) return '21st Century';
return 'Unknown';
}
determineRarity(price, rank) {
if (rank < 10) return 'Museum Grade - Ultra Rare';
if (rank < 25) return 'Exceptional - Extremely Rare';
if (rank < 50) return 'Outstanding - Very Rare';
if (rank < 75) return 'Significant - Rare';
return 'Notable - Collectible';
}
generateMuseumHTML(museum) {
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${museum.title}</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-black text-slate-100">
<div class="max-w-7xl mx-auto px-4 py-12">
<header class="text-center mb-12">
<h1 class="text-5xl font-bold text-amber-400 mb-4">🏛️ Omega Watch Museum</h1>
<p class="text-xl text-slate-300 mb-2">Top 100 Most Expensive Museum-Quality Pieces</p>
<p class="text-slate-400">Total Collection Value: $${museum.totalValue.toLocaleString()}</p>
<p class="text-slate-500 text-sm mt-2">Curated from Sotheby's, Christie's, Phillips & Antiquorum</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
${museum.collection.map(piece => `
<div class="bg-slate-900 rounded-lg overflow-hidden border border-amber-800 hover:border-amber-500 transition group">
<div class="relative h-48 bg-slate-800">
${piece.image ?
`<img src="${piece.image}" alt="${piece.title}"
class="w-full h-full object-cover group-hover:scale-105 transition"
onerror="this.src='https://via.placeholder.com/400x300/1a1a1a/d97706?text=Rank+${piece.rank}'">`
: `<div class="w-full h-full flex items-center justify-center text-amber-600 text-6xl font-bold">
#${piece.rank}
</div>`
}
<div class="absolute top-3 left-3 bg-amber-600 text-black px-3 py-1 rounded-full text-sm font-bold">
Rank #${piece.rank}
</div>
</div>
<div class="p-4">
<h3 class="font-bold text-white mb-2 line-clamp-2 text-sm">${piece.title}</h3>
<div class="text-3xl font-bold text-amber-400 mb-3">
$${piece.price.toLocaleString()}
</div>
<div class="space-y-1 text-xs text-slate-400 mb-3">
<div><span class="text-slate-500">Category:</span> ${piece.category}</div>
<div><span class="text-slate-500">Era:</span> ${piece.era}</div>
<div><span class="text-slate-500">Rarity:</span> ${piece.rarity}</div>
</div>
<div class="flex items-center justify-between text-xs">
<span class="text-slate-500">${piece.source}</span>
${piece.url ?
`<a href="${piece.url}" target="_blank"
class="text-amber-500 hover:text-amber-400 underline">
View Lot ↗
</a>`
: ''
}
</div>
</div>
</div>
`).join('')}
</div>
<footer class="mt-12 text-center text-slate-500 text-sm">
<p>Curated on ${new Date(museum.curatedAt).toLocaleDateString()}</p>
<p class="mt-2">Museum collection of ${museum.totalPieces} pieces worth $${museum.totalValue.toLocaleString()}</p>
</footer>
</div>
</body>
</html>`;
const htmlPath = path.join(this.outputDir, 'index.html');
fs.writeFileSync(htmlPath, html);
console.log(`✓ Museum HTML created: ${htmlPath}`);
}
async close() {
if (this.browser) {
await this.browser.close();
this.browser = null;
}
}
}
export default OmegaMuseumCurator;
// CLI usage
if (import.meta.url === `file://${process.argv[1]}`) {
const curator = new OmegaMuseumCurator();
(async () => {
try {
console.log('🏛️ OMEGA WATCH MUSEUM CURATOR');
console.log('Finding the 100 most expensive museum-quality pieces...\n');
await curator.scrapeSothebys();
await new Promise(resolve => setTimeout(resolve, 3000));
await curator.scrapeChristies();
const museum = curator.createMuseum();
console.log('\n✨ Museum curation complete!');
console.log(`📊 Found ${museum.totalPieces} museum-quality pieces`);
console.log(`💰 Total value: $${museum.totalValue.toLocaleString()}`);
console.log(`🏛️ View museum: /root/Projects/watches/museum/index.html`);
process.exit(0);
} catch (error) {
console.error('Fatal error:', error);
process.exit(1);
} finally {
await curator.close();
}
})();
}