import type { SeedProduct } from "./categories"; import type { BestBuyProduct } from "./fetch-bestbuy"; import type { AmazonData } from "./scrape-amazon"; export interface RetailerPrice { store: string; price: number; url: string; isBest: boolean; } export interface UnifiedProduct { id: string; name: string; category: string; rating: number; reviewCount: number; image: string | null; prices: RetailerPrice[]; description: string; } function slugify(name: string): string { return name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); } export function matchProducts( seeds: SeedProduct[], bestBuyData: Map, amazonData: Map, ): UnifiedProduct[] { const products: UnifiedProduct[] = []; for (const seed of seeds) { const bb = bestBuyData.get(seed.name); const amz = amazonData.get(seed.name); const prices: RetailerPrice[] = []; if (bb) { const bbPrice = bb.salePrice ?? bb.regularPrice; if (bbPrice > 0) { prices.push({ store: "Best Buy", price: bbPrice, url: bb.url, isBest: false, }); } } if (amz?.price) { prices.push({ store: "Amazon", price: amz.price, url: amz.url, isBest: false, }); } if (prices.length === 0) continue; const minPrice = Math.min(...prices.map((p) => p.price)); for (const p of prices) { p.isBest = p.price === minPrice; } const rating = bb?.customerReviewAverage ?? amz?.rating ?? 0; const reviewCount = Math.max( bb?.customerReviewCount ?? 0, amz?.reviewCount ?? 0, ); const image = amz?.image ?? bb?.image ?? null; const description = bb?.longDescription ?? ""; if (rating === 0 && reviewCount === 0) continue; products.push({ id: slugify(seed.name), name: seed.name, category: seed.category, rating: Math.round(rating * 10) / 10, reviewCount, image, prices, description, }); } return products; }