Inferred interests
Preferences vs inferred chips, Admin Users filters, and plain-language examples.
11 - Inferred interests
Audience: PMs and growth using Admin → Users.
Examples: live tenant driffle-dev, scored with now = 2026-08-09 00:00 UTC.
Related: Glossary · Events & analytics · Worked examples · Playbook
Deep engineering (APIs, CronJob, code paths) lives in the engine docs. This guide explains what the chips mean, how strength is calculated, and how to read real users.
Why this exists
Personalization today mostly uses raw interaction history at recommend time. Inferred interests are a simpler, human-readable layer:
- “This shopper currently leans game and giftcard.”
- Visible in Admin next to declared Preferences.
- Refreshed on a schedule (or via Run Inference), so CRM / growth can inspect cohorts without writing ClickHouse.
v1 scope: Admin display + ops. Ranking does not consume these chips yet.
Preferences vs inferred interests
| Column in Admin | Meaning | Source |
|---|---|---|
| Preferences | What the site told us the user likes | Client / SDK upsert (onboarding, account settings) |
| Inferred interests | What behavior suggests right now | Nightly / manual job from last 30 days of interactions |
- Inference never overwrites preferences.
- Many catalog users are stubs created from events — preferences empty, inferred chips may still fill in.
- Use the InfoTips on the Users table headers if the two columns feel ambiguous.
What you’ll see in Admin
- Users → column Inferred interests (up to five category chips).
- Open a user → same chips + last refresh time.
- Run Inference → start a full refresh (button disables while a run is active).
- Inference runs → status, progress (% / users updated / tenants), history.
Blank inferred cell usually means one of:
- No known-user activity in 30 days (or only anonymous
sess_…traffic). - Activity exists, but every category scored below 2.0 (too weak or too old) — we do not write an empty list.
- Catalog items missing a category (those events contribute nothing).
How strength is calculated (PM-readable)
Each interaction adds points to that item’s category:
points = event weight × type importance × recency fade
Type importance
| Event type | Multiplier | Intuition |
|---|---|---|
| purchase | 4 | Strongest intent |
| rating | 3 | Explicit taste |
| cart | 2 | Serious consideration |
| view (and other) | 1 | Browse / weak signal |
Event weight is almost always 1 in driffle-dev.
Recency fade (7-day half-life)
Interest halves every 7 days of age:
| Age of event | Fade factor | Plain English |
|---|---|---|
| 0 days (today) | 1.00 | Full credit |
| ~1 day | ~0.91 | Almost full |
| 7 days | 0.50 | Half credit |
| 14 days | 0.25 | Quarter credit |
| ~28–30 days | ~0.05–0.06 | Nearly gone |
Formula: fade = 0.5 ^ (age_days / 7).
What we keep
- Sum points per category (join each item → its catalog category).
- Drop categories with total < 2.0.
- Keep at most the top 5 remaining labels.
- Only known users (real ids, not
sess_…) with ≥1 event in 30 days. - If nothing clears 2.0 → leave the user unchanged (no empty stamp).
Filtering Users by activity (separate from chips)
On Users, Activity finds funnel cohorts in the last 30 days (current tenant):
| Filter | Who shows up |
|---|---|
| Purchases in last 30 days | ≥1 purchase |
| Cart but no purchase (30d) | ≥1 cart, 0 purchases |
| More than 3 views (30d) | Strict view count > 3 |
Use filters to find people; use inferred chips to see which categories they lean toward.
Worked examples (driffle-dev)
All math below uses live ClickHouse events + Mongo item categories, with now = 2026-08-09 00:00 UTC.
Example A — User 4189506: three categories (step-by-step)
Recent (~1 day old) funnel on three SKUs. Fade at ~1.05 days ≈ 0.901.
Category game (item 9880032)
| Events (weight 1) | Calculation | Points |
|---|---|---|
| 1× purchase | 1 × 4 × 0.901 | 3.61 |
| 3× cart | 3 × (2 × 0.901) | 5.41 |
| 6× view | 6 × (1 × 0.901) | 5.41 |
| Category total | ≈ 14.42 |
Category gift (item 9929073)
| Events | Calculation | Points |
|---|---|---|
| 1× purchase | 4 × 0.901 | 3.61 |
| 3× cart | 3 × 2 × 0.901 | 5.41 |
| 4× view | 4 × 0.901 | 3.60 |
| Category total | ≈ 12.62 |
Category giftcard (item 9972554) — just clears the bar
| Events | Calculation | Points |
|---|---|---|
| 1× cart | 2 × 0.901 | 1.80 |
| 2× view | 2 × 0.901 | 1.80 |
| Category total | ≈ 3.60 |
Decision
| Category | Score | ≥ 2.0? | Kept? |
|---|---|---|---|
| game | 14.42 | yes | yes (#1) |
| gift | 12.62 | yes | yes (#2) |
| giftcard | 3.60 | yes | yes (#3) |
Inferred interests: ["game", "gift", "giftcard"].
Growth read: multi-vertical shopper with fresh funnel activity — not a single-category buyer.
Example B — User 2139520: one dominant category
Almost all recent events are on gift-card SKUs (e.g. 9885466): 3 purchases + 22 carts + 44 views in the window.
Largest single contributions (age ≈ 1.17d, fade ≈ 0.89):
| Event | Calculation | Points (each) |
|---|---|---|
| purchase | 4 × 0.89 | ≈ 3.56 |
| cart | 2 × 0.89 | ≈ 1.78 |
| view | 1 × 0.89 | ≈ 0.89 |
Summed across the giftcard category → ≈ 81.0. No other category clears 2.0.
Inferred interests: ["giftcard"].
Growth read: concentrated affinity; messaging around gift cards is safer than assuming “games” taste.
Example C — User 694956: two strong categories
| Category | Rough makeup (30d) | Total score | Kept? |
|---|---|---|---|
game | 14 purchases + 8 carts + 16 views (mostly <1 day old) | ≈ 83.5 | yes |
giftcard | 1 purchase + 3 carts + 5 views (~1.2 days old) | ≈ 12.8 | yes |
Illustrative giftcard slice (item 9893126, fade ≈ 0.887):
| Event | Calculation | Points |
|---|---|---|
| 1× purchase | 4 × 0.887 | 3.55 |
| 3× cart | 3 × 2 × 0.887 | 5.32 |
| 5× view | 5 × 0.887 | 4.44 |
| Subtotal | ≈ 13.3 (whole-user giftcard ≈ 12.8 after exact ages) |
Inferred interests: ["game", "giftcard"].
Example D — User alice: history exists, chips stay empty
Events are mostly 24–30 days old. Fade at 29.5 days:
0.5 ^ (29.5 / 7) ≈ 0.054
So a purchase that would be worth 4 today is only worth:
4 × 0.054 ≈ 0.22
| Category | Approx. total | ≥ 2.0? |
|---|---|---|
| game | ≈ 1.47 | no |
| game point | ≈ 1.42 | no |
| account | ≈ 0.75 | no |
| dlc / gift / giftcard | < 0.4 each | no |
Result: candidate for the job (has 30d events), but no write — Admin shows blank inferred interests until fresher activity lifts a category over 2.0.
Growth read: “Interacted in the last month” ≠ “currently interested.” Recency matters as much as volume.
Mini checklist when a user looks “wrong”
- Are they a known id (not
sess_…)? - Any events in the last 30 days?
- Do touched items have a category in catalog?
- Is most activity older than ~2 weeks (fade crushing the score)?
- Did the last Inference run succeed?
What this is not (v1)
| Expectation | Reality |
|---|---|
| Drives recommend / search ranking | Not yet |
| Replaces preferences | No — parallel fields |
| Every active user gets chips | No — below-threshold stays blank |
| Auto-expires chips for dormant users | Not yet |
Handy questions for engineering
- When did the last inference run finish?
- For user X: inactive window, anonymous id, missing categories, or below 2.0?
- Should activity filters be stamped onto users for faster lists? (issue #187)
