Personalization

Giving context to the scroll.

Beauty listings are full of technically relevant products. Filters help — but filters are binary. They can tell you a product is "for dry skin." They can't tell you it's the wrong choice for your dry skin, because your concerns aren't equally weighted and your skin isn't a tag. The result: a user scrolls 296 moisturisers with no signal about which ones to trust, and no way to know when to stop.

The question wasn't how to build a better filter. It was: how do you make the scroll itself meaningful?

0–100fit score per SKU 50+expert attributes per product 3visible bands in every listing 88%of profiled purchases from top ~10% matches
Filters vs. score

Filters include or exclude. A score ranks and reasons.

A filter tagged "for dry skin" returns the same 80 products regardless of whether your dryness is more pressing than your sensitivity, or whether a product carries an ingredient your skin reacts to. Everything in the filtered set looks equally valid. The ranking is price or popularity — not fit.

A deterministic score changes that. It reads the user's full profile — concerns weighted by importance, not just present or absent — against 50+ expert-tagged attributes on every SKU. Positive fit adds points. Ingredient conflicts subtract. A hard incompatibility — a formula type contraindicated for the user's skin — overrides the score outright. The result is a continuous 0–100 signal per product that lets the listing mean something: products are ranked by how right they are for this user, not for users in general.

A score can say a product is wrong for you even when it carries every filter tag you selected.
Where the signal came from

A skin quiz that resolved to something precise.

Every beauty platform runs a quiz. Most map a few answers to broad tags. Ours resolved to a Baumann skin type — the 16-type dermatological classification — expressed as a code like DSPW that the system used to score every product in the catalog. Ten plain-language questions covered skin character, sensitivity, reactivity, concerns, and behaviour. No clinical vocabulary. No long-form intake. Specific enough to score against 50+ attributes per SKU.

That attribute depth is the part standard ecommerce teams rarely have. Knowing a product is "for dry skin" is easy. Knowing its comedogenicity, its ingredient conflicts for a sensitised skin type, its richness against a specific Baumann classification — that's what lets a score be right, and lets it say no with a reason.

Skin quiz question: Mostly my cheeks appear — four options from dry and cracky to oily
Plain-language questions a user can answer without clinical vocabulary. Step 1 of 10 — starts with skin character.
Skin quiz question about product-triggered breakouts — step 8 of 10
Sensitivity and reactivity captured directly. The answers that drive the "not for you" logic — not just preference, but skin response.
Skin quiz multi-select concern screen: oiliness, blackheads, dryness, dark circles, uneven tone, redness
Concerns selected and weighted — not a binary tag, but a priority layer on top of skin type. Ten steps resolve to a full Baumann profile.
The system

Deterministic. Every score explainable.

I chose deterministic scoring over a model because trust required explainability. When a product scores 96, the user can see why. When it scores zero, the reason is specific — not a black-box output. The logic was defined once and applied consistently: the fit bands, the conflict rules, the override conditions, and the cases where an incomplete profile meant no score should show at all rather than a misleading one.

01 · Profile Skin signals Baumann type (DSPW) concerns · weighted sensitivity · history 02 · Score 0 – 100 per SKU + fit adds points − conflict subtracts ⊘ hard block overrides ∅ incomplete = no score 03 · Listing bands Best for you surfaced first · with reasoning Good for you strong fit · concern-tagged Not for you shown · reason given No score profile incomplete
The listing

One scroll. Three bands. Context at every step.

As a profiled user moved through a category, the listing resolved into explicit bands. Best fits first — each with a score and a reason. Good fits below — tagged to the specific concern they addressed. Then, at the bottom: "Not for you" — shown, not hidden. The DSPW Baumann code stayed visible in the band header throughout, anchoring every product judgment to the same profile.

Scroll depth became meaningful. Users knew exactly where they were in the relevance gradient. The products they needed to look at were at the top. Everything below was context, not noise.

Best for you band — COSRX product scoring 96 with Why is it for me reasoning
Best for you. Score 96. Reasoning tied to the user's DSPW Baumann type — specific, not generic.
Good for you band — CeraVe 95 and COSRX Panthenol 94 with concern-specific badges
Good for you. Strong fits in the 90s, each tagged for the exact concern it solves — #1 for dryness, #1 for redness.
Not for you band — products with zero Kult Score visibly separated
Not for you. Shown explicitly rather than quietly demoted. The products the system steers away from — and the user knows why.
The deliberate twist

"Not for you" was a product call, not a technical output.

Showing low-fit products explicitly — including on hero SKUs from major brands — was a decision I made against real pushback. Brands didn't want a zero-score band on their listings. The counter-argument: a "not for you" only fires when a product could harm the user's skin. At that point, showing it is the right brand experience — it routes the user to a product from the same brand that actually fits, instead of a purchase they'll regret.

The deeper bet: a system that only ever says yes isn't trusted. Showing "not for you — and here's exactly why" is what made "best for you" worth believing. The metrics confirmed it. Demand concentrated tightly around high-fit products. "Not for you" products almost never sold to profiled users.

If the system can't say no, the yes means nothing.
At the product page

The band told you where. The PDP told you why.

A listing band gives a user the gradient. The product page gave them the specific argument — why this product works or doesn't work for their skin in particular. For a "not for you" product the panel named the actual conflicts, not a category label, so the user understood the call instead of just seeing a rejection.

Product page showing Not for you because panel with two specific skin conflicts named
The PDP names the conflicts specifically — sensitivity risk, formula richness — against the user's profile. Not a flag. A reason.
Beyond single products

The logic extended to routines and kits.

A routine's score derived from its constituent products judged as a set — not each item in isolation, but whether the combination made sense for the user's full profile. That mattered commercially: confidence is what makes someone willing to commit to a full regimen, not just a single item.

Cocktails for you routine card with a combined Kult Score
Routine cards surface a combined score — the set judged together, not just its parts.
What changed

Confidence concentrated demand.

Measured among profiled users in the weeks after launch — directional signal, not isolated A/B attribution. The strongest result wasn't that high-fit products performed well. It was that demand concentrated around them while low-fit products were meaningfully suppressed — exactly the pattern you'd expect if users trusted what the bands were telling them.

88%
of profiled-user purchases came from the top ~10% of score matches
<2.7%
conversion on products explicitly marked "not for you"
50%+
of skincare users completed the full skin quiz
38%
average transaction share from repeat customers

Filters tell you what a product is. A score tells you whether it's right for you — and it has to be willing to say when it isn't.

Case study · Kult
Selected work
← All work