GALP CELLAR — Wine Uniqueness · Final Design

B2 UX + Option E schema
What's changing — in one sentence: Wine stays a timeless label. A new WineVintage collection stores per-year grape blend + notes. AuctionItem now references (wineId, vintage), which resolves to a WineVintage row when one exists. The product page at /wines/:wineId aggregates all vintages under one label header (B2 UX).
No duplication of label fields Vintage-specific grapes supported One extra collection One extra populate on detail pages

1. UX — Wine Product Page /wines/:wineId

Opus One Winery

Opus One · ALL VINTAGES

Napa Valley, USA Red Blend 4 vintages available · 11 active bottles

Opus One's flagship Bordeaux-style blend. Each vintage shows the year's specific grape breakdown and active auctions.

2018

Blend: Cab Sauv 81% · Merlot 7% · Cab Franc 6% · Petit Verdot 4% · Malbec 2%
3 active · $400 — $450
cellarmaster_jp · 750ml · Cellared$450.00 USD
whitfield · 750ml · Professional storage$420.00 USD
dubois_fr · 750ml · Original case$400.00 USD

2019

Blend: Cab Sauv 79% · Merlot 9% · Cab Franc 6% · Petit Verdot 4% · Malbec 2%
1 active · $350
tanaka_tokyo · 750ml · Cellared since release$350.00 USD

2017

Blend: Cab Sauv 80% · Merlot 8% · Cab Franc 7% · Petit Verdot 3% · Malbec 2%
2 active · from $380
dubois_fr · 750ml · Temperature-controlled$410.00 USD
whitfield · 750ml · Original case$380.00 USD

2015

Blend: Cab Sauv 82% · Merlot 6% · Cab Franc 7% · Petit Verdot 3% · Malbec 2%
5 active · from $520
cellarmaster_jp · 750ml · Cellared$680.00 USD
…and 4 more

2. DB — Option E

Legend: PK · FK · new
producers
_id
name
country
region
description
website
unique(name, country)
wines (= LABEL)
_id
name
producerId
regionId
wineType
description
unique(name, producerId)
→ no 4-tuple needed
wineVintages NEW
_id
wineId
vintage (Number)
grapeVarietyIds[]
notes (optional)
unique(wineId, vintage)
auctionItems
_id
wineId → wine label
vintage (Number — pairs with wineId to resolve WineVintage)
sellerId
buyerId
startingPrice, currentBidAmount, …
bottleSize, storageConditions, quantity, images[]
status, timestamps
no change to indexes
How it resolves:
AuctionItem (wineId=W1, vintage=2018)
→ label = Wine W1
→ blend = WineVintage where wineId=W1 AND vintage=2018

If no WineVintage row exists for that pair, fall back to showing the label's default info (blend unknown for that year).
Unchanged: producers, grapeVarieties, regions. These are already normalized reference tables and don't move.

3. How reads work (no row duplication)

Wine product page /wines/:wineId

// Page needs: label info + all vintages + all active auctions per vintage 1. Wine.findById(wineId) .populate('producerId') .populate('regionId') → 1 doc (the label) 2. WineVintage.find({ wineId }) .populate('grapeVarietyIds') → N vintage rows 3. AuctionItem.find({ wineId, status: { $in: [Active, Sold] } }) → auctions 4. Group auctions by vintage in memory, join with step-2 WineVintage rows by vintage number. → render

Marketplace list, filter panel, card views

// Algolia stays the search/list index — no change. // Its record per AuctionItem already denormalizes: // labelName, producerName, regionName, vintage, grapeNames[] // // On label edit OR wineVintage edit → trigger a scoped reindex // via the existing queue handlers (same pattern as catalog:approve).

Auction detail page

// One populate chain — no duplication, no snapshot. AuctionItem.findById(id) .populate({ path: 'wineId', → label populate: [ { path: 'producerId' }, { path: 'regionId' }, ] }) // Then one extra lookup for the vintage-specific blend: WineVintage.findOne({ wineId, vintage }) .populate('grapeVarietyIds') → blend for this year

4. Sell Wizard

List a bottle

Three layers: the wine label, the vintage details, and your specific bottle.

STEP 1
Pick the wine label
Autocomplete → Producer + Label name. If not found: "Add new wine to catalog" (goes to pending approval).
STEP 2
Vintage you're selling
If this (wine, vintage) pair has a WineVintage row, show its blend and let seller confirm. Otherwise prompt for grape breakdown — creates a new pending WineVintage.
STEP 3
Your specific bottle
Bottle size, storage conditions, quantity, images, starting price, end date — AuctionItem fields.

5. Change summary

✓ UNCHANGED
+ NEW
~ MODIFIED