/** * Shared taxonomy mapping for auction-house connectors (Bonhams, Christie's, Sotheby's, Catawiki). * Houses describe lots by department/category labels; we map label + title keywords to RareIndex * taxonomy slugs (data/taxonomy/categories.json). When nothing matches we return null and the * connector decides whether to keep the lot under a family slug or skip it — never invent attributes. */ const WATCH_BRANDS: Array<[RegExp, string, string]> = [ [/\brolex\b/i, 'rolex', 'Rolex'], [/\bpatek\b/i, 'patek_philippe', 'Patek Philippe'], [/\baudemars\b|\bap royal oak\b/i, 'audemars_piguet', 'Audemars Piguet'], [/\bomega\b/i, 'omega', 'Omega'], [/\brichard mille\b/i, 'other_watches', 'Richard Mille'], [/\bvacheron\b/i, 'other_watches', 'Vacheron Constantin'], [/\bcartier\b/i, 'other_watches', 'Cartier'], [/\btudor\b/i, 'other_watches', 'Tudor'], [/\bbreitling\b/i, 'other_watches', 'Breitling'], [/\biwc\b/i, 'other_watches', 'IWC'], [/\bjaeger[- ]lecoultre\b|\bjlc\b/i, 'other_watches', 'Jaeger-LeCoultre'], [/\blange\b/i, 'other_watches', 'A. Lange & Söhne'], [/\bbreguet\b/i, 'other_watches', 'Breguet'], [/\bblancpain\b/i, 'other_watches', 'Blancpain'], [/\bzenith\b/i, 'other_watches', 'Zenith'], [/\bgrand seiko\b/i, 'other_watches', 'Grand Seiko'], [/\bseiko\b/i, 'other_watches', 'Seiko'], [/\btag heuer\b|\bheuer\b/i, 'other_watches', 'TAG Heuer'], [/\bpanerai\b/i, 'other_watches', 'Panerai'], [/\bhublot\b/i, 'other_watches', 'Hublot'], [/\bf\.?\s?p\.?\s?journe\b/i, 'other_watches', 'F.P. Journe'], [/\bmb&f\b/i, 'other_watches', 'MB&F'], [/\bde bethune\b/i, 'other_watches', 'De Bethune'], [/\bgreubel\b/i, 'other_watches', 'Greubel Forsey'], [/\burwerk\b/i, 'other_watches', 'Urwerk'], [/\blongines\b/i, 'other_watches', 'Longines'], [/\bpiaget\b/i, 'other_watches', 'Piaget'], [/\bchopard\b/i, 'other_watches', 'Chopard'], [/\bvan cleef\b/i, 'other_watches', 'Van Cleef & Arpels'], [/\bbulgari\b|\bbvlgari\b/i, 'other_watches', 'Bulgari'], [/\bgirard[- ]perregaux\b/i, 'other_watches', 'Girard-Perregaux'], [/\bulysse nardin\b/i, 'other_watches', 'Ulysse Nardin'], [/\bfranck muller\b/i, 'other_watches', 'Franck Muller'], [/\bbaume\b/i, 'other_watches', 'Baume & Mercier'], ]; export function watchBrand(title: string): { slug: string; brand: string | null } { for (const [re, slug, brand] of WATCH_BRANDS) if (re.test(title)) return { slug, brand }; return { slug: 'other_watches', brand: null }; } /** Rolex-style reference numbers ("Ref. 116500LN", "REF 5711/1A-010", "126610LN"). */ export function watchReference(title: string): string | null { const m = title.match(/\b(?:ref(?:erence)?\.?\s*)([0-9]{3,6}[A-Z]{0,4}(?:[\/-][0-9A-Z]{1,6}){0,3})\b/i) ?? title.match(/\b([0-9]{5,6}(?:[A-Z]{1,3}|\/[0-9A-Z]{1,6}))\b/); return m ? m[1]!.toUpperCase() : null; } const HANDBAG = /\b(birkin|kelly|constance|hermès|hermes|chanel|louis vuitton|goyard|handbag|clutch|tote|mini bag|shoulder bag|pochette)\b/i; const SNEAKER = /\b(sneaker|air jordan|nike|yeezy|dunk|adidas|new balance|air max|off-white x)\b/i; const CARD = /\b(trading card|pokémon|pokemon|charizard|psa \d|bgs \d|cgc \d|topps|panini|upper deck|rookie card|magic: the gathering|yu-gi-oh)/i; const COMIC = /\b(comic|#\d+\s*\(|amazing spider-man|detective comics|action comics|x-men|batman #|superman #|cgc|cbcs)\b/i; const COMIC_STRONG = /\b(comic|amazing spider-man|detective comics|action comics|x-men #|batman #|superman #|incredible hulk #|fantastic four #|avengers #|spawn #)/i; const LEGO = /\blego\b/i; const FUNKO = /\bfunko\b|\bpop!\b/i; const VINYL = /\b(vinyl|lp record|acetate|test pressing|78 rpm|shellac)\b/i; const POSTER = /\b(poster|one[- ]sheet|lobby card|affiche)\b/i; const GUITAR = /\b(guitar|stratocaster|telecaster|les paul|gibson|fender|bass guitar|synthesizer|moog)\b/i; const PROP = /\b(screen[- ]used|prop|costume|worn by|production[- ]used|film-used|movie-used)\b/i; const AUTOGRAPH = /\b(signed|autograph|inscribed|hand-signed)\b/i; const JERSEY = /\b(jersey|game[- ]worn|match[- ]worn|match[- ]issued|game[- ]used|championship ring|olympic (?:gold|silver|bronze) medal|boxing gloves|signed ball|cricket bat|football boots)\b/i; const COIN = /\b(coin|sovereign|guinea|denarius|aureus|stater|tetradrachm|solidus|ducat|thaler|dollar 18|morgan|double eagle|mint state|pcgs|ngc|ms6\d|ms7\d|au5\d|proof)\b/i; const BANKNOTE = /\b(banknote|bank note|treasury note|currency note|specimen note|\bnote\b.*\bbank\b)/i; const MEDAL = /\b(medal|order of|decoration|campaign group|dso|mc group|victoria cross)\b/i; const STAMP = /\b(stamp|postal|cover|philatel|penny black|inverted)\b/i; const WHISKY = /\b(whisky|whiskey|bourbon|macallan|springbank|bowmore|ardbeg|yamazaki|hibiki|karuizawa|glenfiddich|dalmore|laphroaig|brora|port ellen)\b/i; const COGNAC = /\b(cognac|armagnac|calvados)\b/i; const RUM = /\b(rum|rhum)\b/i; const WINE = /\b(bottle|bottles|magnum|jeroboam|imperial|bordeaux|burgundy|champagne|château|chateau|domaine|romanée|romanee|petrus|lafite|latour|margaux|mouton|krug|dom pérignon|dom perignon|barolo|brunello|napa|screaming eagle|rhône|rhone|sauternes|port\b|vintage 19|vintage 20|cases? of \d|\d+\s*bts?\b|\bowc\b|\boc\b)/i; const FOSSIL = /\b(fossil|ammonite|trilobite|dinosaur|tooth|skull|megalodon|mammoth|skeleton)\b/i; const METEORITE = /\b(meteorite|pallasite|chondrite|lunar|martian)\b/i; const MINERAL = /\b(mineral|specimen|quartz|amethyst|tourmaline|fluorite|azurite|malachite|geode|crystal cluster|agate)\b/i; const MAP = /\b(map|atlas|globe|chart of|plan of)\b/i; const MANUSCRIPT = /\b(manuscript|letter signed|autograph letter|document signed|typed letter|archive of)\b/i; const CAMERA = /\b(leica|hasselblad|rolleiflex|camera|lens|nikon f|contax)\b/i; const CAR = /\b(coupe|coupé|roadster|cabriolet|convertible|saloon|sedan|spyder|spider|berlinetta|gt\b|chassis no|vin\b|ferrari|porsche|lamborghini|bentley|aston martin|jaguar|mercedes-benz|bugatti|maserati|alfa romeo|mclaren)\b/i; const MOTORCYCLE = /\b(motorcycle|motorbike|ducati|harley|triumph|norton|vincent|brough superior|bsa|moto guzzi|frame no)\b/i; const AUTOMOBILIA = /\b(mascot|badge|enamel sign|petrol pump|steering wheel|racing helmet|race suit|programme|dashboard|hood ornament)\b/i; const TOY = /\b(tin toy|tinplate|dinky|corgi|matchbox|hot wheels|teddy|steiff|barbie|action figure|star wars figure|transformers|he-man|g\.i\. joe|playmobil|doll\b)\b/i; const MODEL_CAR = /\b(1:18|1:43|1:64|1:24|diecast|die-cast|autoart|cmc|minichamps|kyosho)\b/i; const MODEL_TRAIN = /\b(märklin|marklin|hornby|lionel|bachmann|locomotive|gauge)\b/i; const VIDEO_GAME = /\b(nintendo|sega|playstation|xbox|game boy|gameboy|atari|sealed game|wata|vga \d|nes\b|snes\b|n64\b|famicom)\b/i; const JEWEL = /\b(ring|necklace|bracelet|brooch|earrings|pendant|diamond|sapphire|ruby|emerald|carat|cts?\b|tiara|van cleef|graff|harry winston|tiffany)\b/i; const GEMSTONE = /\b(unmounted|loose diamond|loose stone|gia certified|gia report|rough diamond|natural pearl)\b/i; const PEN = /\b(fountain pen|montblanc|namiki|pelikan|ballpoint|rollerball)\b/i; const LIGHTER = /\b(lighter|dunhill|zippo|s\.t\. dupont)\b/i; const PERFUME = /\b(perfume|parfum|fragrance|eau de)\b/i; const CLOCK = /\b(clock|chronometer|regulator|longcase|bracket clock|carriage clock)\b/i; const SILVER = /\b(silver|sterling|silver-gilt|vermeil|tea service|flatware|salver|tankard)\b/i; const GLASS = /\b(glass|lalique|baccarat|murano|daum|gallé|galle|crystal vase|paperweight)\b/i; const PORCELAIN = /\b(porcelain|meissen|sèvres|sevres|wedgwood|royal copenhagen|worcester|ceramic|earthenware|stoneware|faience|majolica|delft)\b/i; const SCI = /\b(microscope|telescope|sextant|astrolabe|orrery|barometer|slide rule|calculating|theodolite|octant)\b/i; const TYPEWRITER = /\btypewriter\b/i; const MILITARIA = /\b(helmet|uniform|bayonet|sword|dagger|insignia|regiment|militaria|flintlock|musket|cannon|armour|armor|epaulette)\b/i; const SPACE = /\b(apollo|nasa|space shuttle|flown|astronaut|cosmonaut|soyuz|gemini mission|mercury mission|lunar module)\b/i; const AVIATION = /\b(aircraft|aviation|propeller|cockpit|spitfire|concorde|airline)\b/i; const PHOTO = /\b(gelatin silver|photograph|silver print|c-print|chromogenic|platinum print|daguerreotype|albumen)\b/i; const CONTEMPORARY = /\b(banksy|kaws|murakami|hirst|koons|kusama|basquiat|haring|warhol|richter|hockney|kapoor|kiefer|condo|nara|stik|invader)\b/i; const ART = /\b(oil on canvas|oil on panel|acrylic on canvas|watercolour|watercolor|gouache|lithograph|screenprint|etching|engraving|woodcut|sculpture|bronze|drawing|pastel|ink on paper|mixed media|print\b|edition of)\b/i; const BOOK = /\b(first edition|first printing|edition|volumes|vols?\.|folio|octavo|quarto|bound|binding|signed copy|incunabula|hardback|hardcover|dust[- ]?jacket)\b/i; const ANIMATION = /\b(animation cel|production cel|celluloid|disney studio|storyboard|concept art)\b/i; const CHESS = /\b(chess set|chessmen|chess board)\b/i; const ANTIQUE_FURNITURE = /\b(commode|chair|table|cabinet|bureau|chest of drawers|armchair|mirror|console|settee|sofa|desk|bookcase|sideboard|screen|tapestry|carpet|rug)\b/i; const DESIGN = /\b(eames|prouvé|prouve|perriand|jeanneret|nakashima|noguchi|wegner|jacobsen|panton|memphis|sottsass|knoll|cassina|vitra|mid-century|scandinavian design)\b/i; /** Map a title to a slug given a loose department hint. Returns null when no confident match. */ export function slugFromTitle(title: string, hint: DeptHint = 'unknown'): string | null { const t = title; // Highly specific signals first (independent of hint) if (LEGO.test(t)) return 'lego_sets'; if (FUNKO.test(t)) return 'funko'; if (SNEAKER.test(t) && !/\bposter\b/i.test(t)) return 'sneakers'; if (METEORITE.test(t)) return 'meteorites'; if (hint === 'watches' || (/\b(wristwatch|watch|chronograph|pocket watch|tourbillon)\b/i.test(t) && hint !== 'jewelry')) { if (HANDBAG.test(t) && !/\bwatch|chronograph|wristwatch\b/i.test(t)) return 'luxury_handbags'; if (PEN.test(t) && !/\bwatch\b/i.test(t)) return 'pens'; if (LIGHTER.test(t) && !/\bwatch\b/i.test(t)) return 'lighters'; if (CLOCK.test(t) && !/\bwatch\b/i.test(t)) return 'clocks'; if (JEWEL.test(t) && !/\bwatch|chronograph|wristwatch\b/i.test(t)) return 'jewelry'; return watchBrand(t).slug; } if (hint === 'handbags' || HANDBAG.test(t)) return 'luxury_handbags'; if (hint === 'jewelry') return GEMSTONE.test(t) ? 'gemstones' : 'jewelry'; if (hint === 'wine') { if (WHISKY.test(t)) return 'whisky'; if (COGNAC.test(t)) return 'cognac'; if (RUM.test(t)) return 'rum'; return 'wine'; } if (WHISKY.test(t)) return 'whisky'; if (COGNAC.test(t)) return 'cognac'; if (hint === 'coins') { if (BANKNOTE.test(t)) return 'banknotes'; if (MEDAL.test(t) && !COIN.test(t)) return 'medals'; return 'coins'; } if (hint === 'stamps' || STAMP.test(t) && hint === 'books') return 'stamps'; if (hint === 'cars') return MOTORCYCLE.test(t) ? 'motorcycles' : AUTOMOBILIA.test(t) && !CAR.test(t) ? 'automotive_memorabilia' : 'automobiles'; if (hint === 'motorcycles') return 'motorcycles'; if (hint === 'automobilia') return 'automotive_memorabilia'; if (hint === 'comics' || COMIC_STRONG.test(t)) { if (ANIMATION.test(t)) return 'animation_art'; if (/\b(marvel|spider-man|x-men|avengers|hulk|fantastic four|iron man|captain america|thor|wolverine|daredevil)\b/i.test(t)) return 'marvel_comics'; if (/\b(dc comics|batman|superman|detective comics|action comics|wonder woman|flash|green lantern)\b/i.test(t)) return 'dc_comics'; if (/\b(manga|tankobon|shonen jump)\b/i.test(t)) return 'manga'; return 'independent_comics'; } if (hint === 'cards' || CARD.test(t)) { if (/pok[eé]mon/i.test(t)) return 'pokemon'; if (/magic: the gathering|\bmtg\b/i.test(t)) return 'magic_the_gathering'; if (/yu-gi-oh/i.test(t)) return 'yugioh'; if (/one piece/i.test(t)) return 'one_piece_card_game'; if (/lorcana/i.test(t)) return 'disney_lorcana'; if (/\b(basketball|nba|jordan|lebron|kobe)\b/i.test(t)) return 'basketball_cards'; if (/\b(baseball|mlb|mantle|ruth|topps 195|bowman 195)\b/i.test(t)) return 'baseball_cards'; if (/\b(football|nfl|brady|mahomes)\b/i.test(t)) return 'football_cards'; if (/\b(hockey|nhl|gretzky|mcdavid)\b/i.test(t)) return 'hockey_cards'; if (/\b(soccer|messi|ronaldo|mbapp|prizm world cup|panini fifa)\b/i.test(t)) return 'soccer_cards'; if (/\b(f1|formula 1|verstappen|hamilton)\b/i.test(t)) return 'f1_cards'; return hint === 'cards' ? 'other_tcg' : 'non_sport_cards'; } if (COMIC.test(t) && !CARD.test(t)) return /\b(marvel|spider-man|x-men|avengers|hulk)\b/i.test(t) ? 'marvel_comics' : /\b(batman|superman|dc comics)\b/i.test(t) ? 'dc_comics' : 'independent_comics'; if (hint === 'popular_culture' || hint === 'entertainment') { if (GUITAR.test(t)) return 'musical_instruments'; if (VINYL.test(t)) return 'music'; if (POSTER.test(t)) return 'movie_posters'; if (ANIMATION.test(t)) return 'animation_art'; if (PROP.test(t)) return 'movie_memorabilia'; if (VIDEO_GAME.test(t)) return 'video_games'; if (TOY.test(t)) return 'vintage_toys'; if (SPACE.test(t)) return 'space'; if (JERSEY.test(t)) return 'sports_memorabilia'; if (/\b(beatles|rolling stones|bowie|elvis|hendrix|queen|pink floyd|led zeppelin|tour|concert|gold disc|platinum disc|setlist|lyrics)\b/i.test(t)) return 'music_memorabilia'; if (/\b(star wars|harry potter|james bond|007|marvel|disney|lord of the rings|batman|indiana jones|jurassic|back to the future)\b/i.test(t)) return 'movie_memorabilia'; if (AUTOGRAPH.test(t)) return 'autographs'; return 'movie_memorabilia'; } if (hint === 'sports' || JERSEY.test(t)) return 'sports_memorabilia'; if (hint === 'toys') { if (MODEL_TRAIN.test(t)) return 'model_trains'; if (MODEL_CAR.test(t)) return 'model_cars'; if (VIDEO_GAME.test(t)) return 'video_games'; if (/\b(action figure|hot toys|sideshow|figuarts|nendoroid|mafex)\b/i.test(t)) return 'action_figures'; if (/\b(doll|barbie|blythe|bisque)\b/i.test(t)) return 'dolls'; if (/\b(plush|teddy|steiff|jellycat)\b/i.test(t)) return 'plush'; if (/\b(bearbrick|kaws|medicom|labubu|pop mart|kidrobot)\b/i.test(t)) return 'designer_toys'; return 'vintage_toys'; } if (hint === 'natural_history') { if (FOSSIL.test(t)) return 'fossils'; if (MINERAL.test(t)) return 'minerals'; return 'fossils'; } if (hint === 'science') return SCI.test(t) ? 'scientific_instruments' : TYPEWRITER.test(t) ? 'typewriters' : FOSSIL.test(t) ? 'fossils' : MINERAL.test(t) ? 'minerals' : 'scientific_instruments'; if (hint === 'books') { if (MAP.test(t)) return 'maps'; if (MANUSCRIPT.test(t)) return 'historical_documents'; if (PHOTO.test(t)) return 'photography'; return 'books'; } if (hint === 'photographs') return PHOTO.test(t) || !ART.test(t) ? 'photography' : 'art'; if (hint === 'prints') return CONTEMPORARY.test(t) ? 'contemporary_art' : 'art'; if (hint === 'contemporary') return 'contemporary_art'; if (hint === 'art') return CONTEMPORARY.test(t) ? 'contemporary_art' : PHOTO.test(t) ? 'photography' : 'art'; if (hint === 'clocks') return 'clocks'; if (hint === 'silver') return 'silver'; if (hint === 'glass') return 'glass_crystal'; if (hint === 'ceramics') return 'porcelain'; if (hint === 'militaria') return MEDAL.test(t) ? 'medals' : 'militaria'; if (hint === 'design') return 'design_furniture'; if (hint === 'furniture' || hint === 'decorative') { if (CLOCK.test(t)) return 'clocks'; if (SILVER.test(t)) return 'silver'; if (GLASS.test(t)) return 'glass_crystal'; if (PORCELAIN.test(t)) return 'porcelain'; if (DESIGN.test(t)) return 'design_furniture'; if (CHESS.test(t)) return 'chess'; return 'antiques'; } if (hint === 'cameras' || CAMERA.test(t)) return 'cameras'; if (hint === 'music') return GUITAR.test(t) ? 'musical_instruments' : VINYL.test(t) ? 'music' : 'music_memorabilia'; if (hint === 'movies') return POSTER.test(t) ? 'movie_posters' : PROP.test(t) ? 'movie_memorabilia' : ANIMATION.test(t) ? 'animation_art' : 'movie_memorabilia'; if (hint === 'space') return 'space'; if (hint === 'aviation') return AVIATION.test(t) ? 'aviation' : 'space'; if (hint === 'perfume') return 'perfume'; if (hint === 'pens') return PEN.test(t) ? 'pens' : LIGHTER.test(t) ? 'lighters' : 'pens'; if (hint === 'fashion') return HANDBAG.test(t) ? 'luxury_handbags' : SNEAKER.test(t) ? 'sneakers' : 'fashion_streetwear'; if (hint === 'video_games') return 'video_games'; if (hint === 'asian' || hint === 'tribal' || hint === 'antiquities') return 'antiques'; // No hint: broad keyword sweep if (WINE.test(t) && /\b(19|20)\d{2}\b/.test(t)) return 'wine'; if (COIN.test(t)) return 'coins'; if (STAMP.test(t)) return 'stamps'; if (JEWEL.test(t)) return 'jewelry'; if (CAR.test(t) && /\b(19|20)\d{2}\b/.test(t)) return 'automobiles'; if (VINYL.test(t)) return 'music'; if (POSTER.test(t)) return 'movie_posters'; if (GUITAR.test(t)) return 'musical_instruments'; if (VIDEO_GAME.test(t)) return 'video_games'; if (FOSSIL.test(t)) return 'fossils'; if (MINERAL.test(t)) return 'minerals'; if (MAP.test(t)) return 'maps'; if (BOOK.test(t)) return 'books'; if (PHOTO.test(t)) return 'photography'; if (CONTEMPORARY.test(t)) return 'contemporary_art'; if (ART.test(t)) return 'art'; if (CLOCK.test(t)) return 'clocks'; if (SILVER.test(t)) return 'silver'; if (PORCELAIN.test(t)) return 'porcelain'; if (GLASS.test(t)) return 'glass_crystal'; if (SCI.test(t)) return 'scientific_instruments'; if (MILITARIA.test(t)) return 'militaria'; if (ANTIQUE_FURNITURE.test(t)) return 'antiques'; return null; } export type DeptHint = | 'unknown' | 'watches' | 'handbags' | 'jewelry' | 'wine' | 'coins' | 'stamps' | 'cars' | 'motorcycles' | 'automobilia' | 'cards' | 'comics' | 'popular_culture' | 'entertainment' | 'sports' | 'toys' | 'natural_history' | 'science' | 'books' | 'photographs' | 'prints' | 'contemporary' | 'art' | 'clocks' | 'silver' | 'glass' | 'ceramics' | 'militaria' | 'design' | 'furniture' | 'decorative' | 'cameras' | 'music' | 'movies' | 'space' | 'aviation' | 'perfume' | 'pens' | 'fashion' | 'video_games' | 'asian' | 'tribal' | 'antiquities'; /** Map a free-text department / category label from any house to a DeptHint. */ export function hintFromLabel(label: string | null | undefined): DeptHint { if (!label) return 'unknown'; const l = label.toLowerCase(); if (/watch|horolog/.test(l)) return 'watches'; if (/handbag|luggage/.test(l)) return 'handbags'; if (/jewel|gem/.test(l)) return 'jewelry'; if (/wine|whisky|spirit|champagne/.test(l)) return 'wine'; if (/coin|numismat|banknote|medal/.test(l)) return 'coins'; if (/stamp|philatel|postal/.test(l)) return 'stamps'; if (/motor ?car|\bcars\b|automobile|motoring/.test(l)) return 'cars'; if (/motorcycle/.test(l)) return 'motorcycles'; if (/automobilia/.test(l)) return 'automobilia'; if (/trading card|sports card|pokémon|pokemon|tcg/.test(l)) return 'cards'; if (/comic|animation/.test(l)) return 'comics'; if (/popular culture|entertainment|rock|pop memorabilia|film memorabilia|music memorabilia/.test(l)) return 'popular_culture'; if (/sport/.test(l)) return 'sports'; if (/toy|model|doll|teddy/.test(l)) return 'toys'; if (/natural history|fossil|mineral|meteorite/.test(l)) return 'natural_history'; if (/scien|instrument|technology/.test(l)) return 'science'; if (/book|manuscript|map|atlas|travel|exploration|library/.test(l)) return 'books'; if (/photograph/.test(l)) return 'photographs'; if (/print|multiple|edition/.test(l)) return 'prints'; if (/contemporary|post-war|modern british|20th|21st|street art|urban art/.test(l)) return 'contemporary'; if (/impressionist|old master|painting|fine art|\bart\b|drawing|sculpture|picture/.test(l)) return 'art'; if (/clock|barometer/.test(l)) return 'clocks'; if (/silver|vertu|objects of vertu/.test(l)) return 'silver'; if (/glass|paperweight/.test(l)) return 'glass'; if (/ceramic|porcelain|pottery/.test(l)) return 'ceramics'; if (/arms|armour|armor|militar|medal/.test(l)) return 'militaria'; if (/design/.test(l)) return 'design'; if (/furniture|interiors|decorative|works of art|house sale|carpet|rug|tapestr/.test(l)) return 'furniture'; if (/camera/.test(l)) return 'cameras'; if (/music|instrument|guitar/.test(l)) return 'music'; if (/movie|film|cinema|poster/.test(l)) return 'movies'; if (/space|nasa/.test(l)) return 'space'; if (/aviation|aeronautic/.test(l)) return 'aviation'; if (/perfume|fragrance/.test(l)) return 'perfume'; if (/pen|writing instrument|lighter/.test(l)) return 'pens'; if (/fashion|sneaker|streetwear|couture|accessor/.test(l)) return 'fashion'; if (/video ?game/.test(l)) return 'video_games'; if (/asian|chinese|japanese|islamic|indian|himalayan|korean/.test(l)) return 'asian'; if (/tribal|african|oceanic|native american|pre-columbian/.test(l)) return 'tribal'; if (/antiquit|ancient/.test(l)) return 'antiquities'; return 'unknown'; } export function brandFromSlug(slug: string, title: string): string | null { if (['rolex', 'patek_philippe', 'audemars_piguet', 'omega', 'other_watches'].includes(slug)) return watchBrand(title).brand; if (slug === 'luxury_handbags') { const m = title.match(/\b(herm[èe]s|chanel|louis vuitton|dior|gucci|goyard|fendi|bottega veneta|prada|celine|loewe|saint laurent)\b/i); return m ? m[1]!.replace(/^herm[èe]s$/i, 'Hermès') : null; } if (slug === 'lego_sets') return 'LEGO'; if (slug === 'funko') return 'Funko'; return null; } /** "PSA 10", "CGC 9.8" etc. are handled by @rareindex/taxonomy; this extracts a LEGO set number. */ export function legoSetNumber(title: string): string | null { const m = title.match(/\b(\d{4,5})(?:-1)?\b/); return m ? m[1]! : null; } export function isBundleTitle(title: string): boolean { return /\b(collection of|group of|lot of|assorted|quantity of|\(\d{2,}\)|\d{2,}\s*(?:items|pieces|cards|bottles|coins)|set of \d{2,}|mixed lot|various)\b/i.test(title); } export function safeYear(title: string): number | null { const now = new Date().getUTCFullYear(); const m = title.match(/\b(1[5-9]\d{2}|20\d{2})\b/); if (!m) return null; const y = Number(m[1]); return y <= now ? y : null; }