YesJo.au ← Back to portfolio
Recommendation engine · case study

The mood-board engine that picks the right product for every slot.

Behind Gia's AI Design Studio is a deterministic product-selection algorithm. Upload a bathroom photo and it composes a complete, on-style, on-budget board — floor tile, wall tile, vanity, tapware and all — straight from a live Shopify catalogue, then locks the AI render to exactly what it chose.

Live Shopify catalogue 8 admin strategy levers Deterministic & reproducible Pure function · fully unit-tested
01

The selection pipeline

Each board is built slot-by-slot. A product has to survive every stage below before it lands in the cart — and the whole chain is a pure function, so the same room always yields the same board.

👁

Vision reads the room

The detected scope (floor-only, bathtub, toilet…) and vanity count decide which fixture slots even exist for this space.

🧩

Slot gating

Admin rules — always, if-detected, if-space-permits, never — choose which slots to fill and which to defer.

Eligibility filter

Available, enabled, correctly slotted, and has a renderable hero image — drops the ~21% of rows with no Shopify photo before they can break a board.

🎨

Style match

Classifier tags (admin overrides win). Weight 1.0 if it matches only this style, 0.6 if multi-style — so one SKU can't headline every board.

💰

Budget tier

Standard / premium / luxury, sliced by price percentile within the slot pool — never globally — so every tier keeps real choice.

🎯

Weighted scoring

Eight strategy levers multiply onto each product's base weight: margin, stock, vendor-mix, conversion-learning, aspirational anchor and more.

🎲

Seeded shuffle

A weighted random draw seeded on analysisId+style+slot: reproducible, cacheable, yet varied across boards.

🎭

Palette lock

The chosen products become a strict palette + must-avoid list fed verbatim into the image render — the "after" can't drift off the cart.

02

Eight strategy levers

Store owners tune the board's commercial behaviour with sliders (0–100) — no redeploy. Each lever is a transparent multiplier on a product's weight, so the effect is explainable and testable.

Featured×5

Hand-picked hero products pin to the top of their slot when they match the style. Outranks every other lever.

Margin weight×1+mw

Biases toward high-margin SKUs and suppresses low-margin ones, scaled by the slider.

Aspirational anchorup to ×1.5

Ramps weight toward the top 40% of the price pool — a "show them the dream" lever.

Inventory boostnew ↑

Newly-arrived products (≤30 days) get a linear-decay bump so fresh stock surfaces first.

Stock level±

Boosts well-stocked SKUs (≥20 units), suppresses nearly-out ones (≤5) — clears overstock, protects fulfilment.

Vendor mix · diverse×0.4

Penalises a brand already on the board so the look spans suppliers (or matched ×1.6 to keep one brand).

Conversion learningup to ×2.5

Rewards real engagement — (clicks + 3×saves) / impressions — once a product has ≥20 impressions.

Aesthetic coherence±

Scores metal, temperature and vibe against the customer's chosen references; clashing finishes are pushed down.

03

Budget tiers are relative, not fixed

A "luxury" tap in a budget catalogue shouldn't be a $90 mixer just because the catalogue is cheap. Tiers are percentile bands computed inside each slot's own pool at pick-time — and they overlap, so a board never empties a tier out.

Standard0–50th percentile
Premium40–85th percentile
Luxury70–100th percentile
cheapest in slotmost expensive in slot

Overlapping bands mean each (style × slot × tier) triple still has ~30% of the pool to choose from — and if a filter would empty the pool, it gracefully degrades instead of returning a blank slot.

04

Random where it helps, deterministic where it counts

Weighted, seeded sampling

Selection uses Efraimidis–Spirakis weighted random sampling: every candidate gets a sort key, and the highest keys win. Higher weight → statistically more likely to top the draw, but never guaranteed — so boards stay varied instead of always returning the single heaviest product.

The randomness comes from Mulberry32, seeded on analysisId + styleId + slot. Same inputs reproduce the exact same board — so results are cacheable, debuggable and unit-testable — while different slots and styles each get their own fresh draw.

The core

// weighted key per candidate
key = rng() ^ (1 / weight)

// rng seeded per board-slot
rng = mulberry32(
  hash(analysisId
    + styleId
    + slot)
)

// sort desc, take N
picks = sortByKey(cands).slice(0,N)

See it for real

The engine drives Gia's Design Studio — upload a room, get a styled, costed board in seconds. It's one of the live demos on the YesJo portfolio.