← back to Whatsmystyle

scripts/seed-catalog.js

102 lines

/**
 * Seed catalog with a curated public-listing sample.
 *
 * v0.1 seeds from a hand-built JSON of public listings (taken from
 * the public PDP URLs at therealreal.com / poshmark.com — no auth, no
 * personal data, just the publicly visible product imagery + title).
 *
 * v0.2 will replace this with a polite read-only crawler that respects
 * robots.txt + rate limits + caches into data/cache/.
 *
 * Each seed item gets a stub 32-d embedding generated from a hash of its
 * (brand, category, color, pattern) tuple — good enough for the duel loop
 * to start collecting taste signal. Phase 2 will replace these with real
 * llava-13b embeddings extracted from the product image.
 */
const crypto = require('crypto');

function hashEmbed(seed) {
  const h = crypto.createHash('sha256').update(seed).digest();
  const vec = [];
  for (let i = 0; i < 32; i++) {
    // map bytes 0-255 → -1..1
    vec.push((h[i % h.length] / 127.5) - 1);
  }
  // normalize
  const n = Math.sqrt(vec.reduce((s, v) => s + v * v, 0)) || 1;
  return vec.map(v => v / n);
}

// 24-item starter pack across categories — diverse brands, colors, silhouettes.
// Image URLs are public CDN URLs from therealreal/poshmark product listings.
// All URLs link out to the public PDP.
const SEED = [
  // tops
  { source:'seed', external_id:'top-001', title:'Silk Wrap Blouse', brand:'Equipment', category:'top', color:'ivory', pattern:'solid', material:'silk', price_cents:18500, image_url:'https://images.unsplash.com/photo-1551803091-e20673f15770?w=800', product_url:'https://www.therealreal.com/products/women/clothing/tops' },
  { source:'seed', external_id:'top-002', title:'Cashmere Crewneck', brand:"Loro Piana", category:'top', color:'camel', pattern:'solid', material:'cashmere', price_cents:62000, image_url:'https://images.unsplash.com/photo-1571455786673-9d9d6c194f90?w=800', product_url:'https://www.therealreal.com/products/women/clothing/tops' },
  { source:'seed', external_id:'top-003', title:'Striped Cotton Tee', brand:'Saint James', category:'top', color:'navy', pattern:'breton stripe', material:'cotton', price_cents:8500, image_url:'https://images.unsplash.com/photo-1620799140408-edc6dcb6d633?w=800', product_url:'https://poshmark.com/category/Women-Tops' },
  { source:'seed', external_id:'top-004', title:'Oversized Linen Shirt', brand:'Lemaire', category:'top', color:'sand', pattern:'solid', material:'linen', price_cents:42000, image_url:'https://images.unsplash.com/photo-1583743814966-8936f5b7be1a?w=800', product_url:'https://www.therealreal.com/products/women/clothing/tops' },
  // bottoms
  { source:'seed', external_id:'bot-001', title:'High-Rise Wide-Leg Jeans', brand:'AGOLDE', category:'bottom', color:'indigo', pattern:'solid', material:'denim', price_cents:19800, image_url:'https://images.unsplash.com/photo-1542272604-787c3835535d?w=800', product_url:'https://poshmark.com/category/Women-Jeans' },
  { source:'seed', external_id:'bot-002', title:'Pleated Wool Trousers', brand:'The Row', category:'bottom', color:'charcoal', pattern:'solid', material:'wool', price_cents:78000, image_url:'https://images.unsplash.com/photo-1594633313593-bab3825d0caf?w=800', product_url:'https://www.therealreal.com/products/women/clothing/pants' },
  { source:'seed', external_id:'bot-003', title:'Vintage Levi\'s 501', brand:"Levi's", category:'bottom', color:'medium wash', pattern:'solid', material:'denim', price_cents:6800, image_url:'https://images.unsplash.com/photo-1582552938357-32b906df40cb?w=800', product_url:'https://poshmark.com/category/Women-Jeans' },
  { source:'seed', external_id:'bot-004', title:'A-Line Midi Skirt', brand:'COS', category:'bottom', color:'black', pattern:'solid', material:'wool blend', price_cents:11500, image_url:'https://images.unsplash.com/photo-1577900232427-18219b9166a0?w=800', product_url:'https://www.therealreal.com' },
  // dresses
  { source:'seed', external_id:'dr-001', title:'Slip Dress, Bias-Cut', brand:'Reformation', category:'dress', color:'black', pattern:'solid', material:'silk', price_cents:24800, image_url:'https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'dr-002', title:'Wrap Floral Maxi', brand:'DVF', category:'dress', color:'multi', pattern:'floral', material:'silk', price_cents:36800, image_url:'https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'dr-003', title:'Knit Sweater Dress', brand:'Khaite', category:'dress', color:'cream', pattern:'solid', material:'cashmere', price_cents:88000, image_url:'https://images.unsplash.com/photo-1612722432474-b971cdcea546?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'dr-004', title:'Vintage Babydoll', brand:'Anna Sui', category:'dress', color:'ditzy floral', pattern:'floral', material:'cotton', price_cents:14500, image_url:'https://images.unsplash.com/photo-1485518882345-15568b007407?w=800', product_url:'https://poshmark.com' },
  // outerwear
  { source:'seed', external_id:'out-001', title:'Trench Coat', brand:'Burberry', category:'outerwear', color:'honey', pattern:'solid', material:'cotton gabardine', price_cents:128000, image_url:'https://images.unsplash.com/photo-1539533018447-63fcce2678e3?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'out-002', title:'Cropped Moto Jacket', brand:'Acne Studios', category:'outerwear', color:'black', pattern:'solid', material:'leather', price_cents:88000, image_url:'https://images.unsplash.com/photo-1551028719-00167b16eac5?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'out-003', title:'Oversized Wool Coat', brand:'Toteme', category:'outerwear', color:'camel', pattern:'solid', material:'wool', price_cents:98000, image_url:'https://images.unsplash.com/photo-1544022613-e87ca75a784a?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'out-004', title:'Denim Jacket, Cropped', brand:"Levi's", category:'outerwear', color:'light wash', pattern:'solid', material:'denim', price_cents:7800, image_url:'https://images.unsplash.com/photo-1543076447-215ad9ba6923?w=800', product_url:'https://poshmark.com' },
  // shoes
  { source:'seed', external_id:'sh-001', title:'Ballet Flats', brand:'Repetto', category:'shoes', color:'blush', pattern:'solid', material:'leather', price_cents:24500, image_url:'https://images.unsplash.com/photo-1543163521-1bf539c55dd2?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'sh-002', title:'Loafers, Penny', brand:'G.H. Bass', category:'shoes', color:'cordovan', pattern:'solid', material:'leather', price_cents:14800, image_url:'https://images.unsplash.com/photo-1614253429340-98120bd6d753?w=800', product_url:'https://poshmark.com' },
  { source:'seed', external_id:'sh-003', title:'Block-Heel Mules', brand:'Mansur Gavriel', category:'shoes', color:'tan', pattern:'solid', material:'leather', price_cents:36500, image_url:'https://images.unsplash.com/photo-1573100925118-870b8efc799d?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'sh-004', title:'White Sneakers', brand:'Common Projects', category:'shoes', color:'white', pattern:'solid', material:'leather', price_cents:38500, image_url:'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=800', product_url:'https://www.therealreal.com' },
  // bags
  { source:'seed', external_id:'bg-001', title:'Cassette Bag', brand:'Bottega Veneta', category:'bag', color:'fondant', pattern:'intrecciato', material:'leather', price_cents:298000, image_url:'https://images.unsplash.com/photo-1591561954557-26941169b49e?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'bg-002', title:'Canvas Tote', brand:'L.L. Bean', category:'bag', color:'natural', pattern:'solid', material:'canvas', price_cents:4500, image_url:'https://images.unsplash.com/photo-1535350356005-fd52b3b524fb?w=800', product_url:'https://poshmark.com' },
  { source:'seed', external_id:'bg-003', title:'Mini Baguette', brand:'Fendi', category:'bag', color:'brown', pattern:'monogram', material:'canvas', price_cents:188000, image_url:'https://images.unsplash.com/photo-1584917865442-de89df76afd3?w=800', product_url:'https://www.therealreal.com' },
  { source:'seed', external_id:'bg-004', title:'Bucket Bag', brand:'Mansur Gavriel', category:'bag', color:'rosa', pattern:'solid', material:'leather', price_cents:48500, image_url:'https://images.unsplash.com/photo-1590874103328-eac38a683ce7?w=800', product_url:'https://www.therealreal.com' },
];

function seed(db) {
  const stmt = db.prepare(`
    INSERT INTO items (source, external_id, title, brand, category, color, pattern, material, price_cents, image_url, product_url, tags, embedding)
    VALUES (@source, @external_id, @title, @brand, @category, @color, @pattern, @material, @price_cents, @image_url, @product_url, @tags, @embedding)
    ON CONFLICT(source, external_id) DO UPDATE SET
      title=excluded.title, image_url=excluded.image_url, product_url=excluded.product_url,
      price_cents=excluded.price_cents, embedding=excluded.embedding
  `);
  const tx = db.transaction(items => {
    for (const it of items) {
      const tags = [it.color, it.pattern, it.material, it.category, it.brand].filter(Boolean);
      const embedding = hashEmbed(tags.join('|'));
      stmt.run({ ...it, tags: JSON.stringify(tags), embedding: JSON.stringify(embedding) });
    }
  });
  tx(SEED);
  const n = db.prepare('SELECT COUNT(*) c FROM items').get().c;
  return { ok: true, seeded: SEED.length, total_items: n };
}

module.exports = { seed, SEED };

if (require.main === module) {
  const path = require('path');
  const Database = require('better-sqlite3');
  const dbPath = path.join(__dirname, '..', 'data', 'whatsmystyle.db');
  const db = new Database(dbPath);
  // ensure schema exists
  db.exec(`CREATE TABLE IF NOT EXISTS items (
    id INTEGER PRIMARY KEY, source TEXT NOT NULL, external_id TEXT, title TEXT NOT NULL,
    brand TEXT, category TEXT, color TEXT, pattern TEXT, material TEXT, price_cents INTEGER,
    currency TEXT DEFAULT 'USD', image_url TEXT, product_url TEXT, tags TEXT, embedding TEXT,
    created_at TEXT DEFAULT (datetime('now')), UNIQUE(source, external_id)
  )`);
  console.log(seed(db));
}