Back

AURAFIND

Describe a scent in plain words and get ranked matches, with the reasons shown.

Recommendation engine

What it's for

Someone describes what they want in plain English and a budget, and wants to know why they were shown what they were shown.

What it is

Plain-language requests become structured preferences, then a catalogue is scored out of 100 on budget, notes, style, occasion, season and performance, and grouped into best overall, budget, premium and hidden-gem picks, each with its reasons.

How it works

  1. Request A description in plain language 01
  2. Preferences gpt-4.1-mini with a JSON schema, or a rule-based fallback 02
  3. Catalogue Supabase, or a local 100-fragrance file 03
  4. Scoring Out of 100, with a visible breakdown 04
  5. Picks Best overall, budget, premium, hidden gem 05

Engineering decisions

The LLM only parses the request; it never picks products, so it can't recommend one that doesn't exist.
Works with no API keys at all, through the fallback parser and the local catalogue.
Every recommendation shows how its score was made.

Built with

Next.jsTypeScriptSupabaseOpenAI Responses API

Case study

“Something warm for evenings, under £80, but I can’t stand oud.”

A language model understands that sentence far better than any set of dropdowns. Let it choose the bottle, though, and sooner or later it recommends one that doesn’t exist, at a price it invented, with notes it made up. In a shop that’s a refund and a customer who doesn’t come back.

Two jobs, one of them risky

So the model does exactly one job: turn that sentence into structured preferences against a fixed JSON schema. Budget, notes wanted, notes refused, occasion, season.

It never sees the catalogue and never picks a product. Selection happens in ordinary code over real rows, which means the system physically cannot recommend a fragrance that isn’t in the database.

How a bottle gets ranked

Each candidate is scored out of 100:

CriterionWeight
Budget fit25
Notes matched25
Style20
Occasion10
Season10
Longevity and projection10

Disliked notes aren’t a deduction, they’re an exclusion. Someone who says “no oud” means it, and no amount of scoring well elsewhere should be able to outvote that.

Results come back grouped as best overall, budget, premium and hidden gem, each showing the reasons behind its score. That’s only possible because the weights are fixed and legible: a vector similarity score would rank just as fast and explain nothing, and “explain nothing” is not an option when you’re asking someone to spend £80.

Why it runs without an API key

With no credentials, a rule-based fallback parser handles the request and a local 100-fragrance file stands in for the database. The app still works.

Partly that makes it genuinely demoable. Mostly it keeps the non-AI path honest: if the fallback rots, you find out immediately, instead of discovering during an outage that the “graceful degradation” never worked.

What would make it better

The weights were chosen by hand and the catalogue is 100 fragrances assembled for the build. Both are fine for a prototype and neither is evidence of anything.

The evaluation that is missing

The weights in the scoring function are reasoned, not fitted. Nobody has clicked on anything, so there is no evidence that “best overall” is the pick a person would have chosen.

The fix is small and specific: log which of the four cards gets opened, and after about thirty sessions the ranking either agrees with people or it does not. Until that exists this is a system with a defensible design and no proof, and saying so is more useful than implying otherwise.