feat: enrichment scoring + API endpoint for homepage For You feed#5
Conversation
Building on Kristian's recs pipeline, this adds:
- Minerva enrichment scoring (income-aware pricing, life stage fit, engagement heat)
- API endpoint GET /api/pipelines/cio-property-recs/recs/{userId} for homepage carousel
- In-memory store for pre-computed recs served via API
Scoring: similarity 45% + popularity 15% + recency 10% + price match 10%
+ income match 8% + life stage fit 5% + engagement heat 2%
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@brianottomate is attempting to deploy a commit to the Wander Team on Vercel. A member of the Team first needs to authorize it. |
1. Engagement heat: base score now starts at 0.5 (neutral) instead of 0.3, so users with enrichment data but no recent activity aren't penalized vs users without enrichment data 2. Life stage couples boost: only triggers when numberOfChildren is explicitly 0, not when it's null/unknown (17% coverage gap) 3. Recs API auth: added ADMIN_TOKEN check matching the sibling POST route, removed store_stats leak from 404 response Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
||
| export function getStoredRecs(userId: string): StoredUserRecs | null { | ||
| return recsStore.get(userId) ?? null; | ||
| } |
There was a problem hiding this comment.
In-memory store empty across serverless function instances
High Severity
The recsStore module-level Map is populated by run() (triggered via POST /api/pipelines/cio-property-recs) and read by getStoredRecs() (called from GET .../recs/{userId}). On Vercel, these two routes are separate serverless function instances with independent module-scoped state. The recsStore populated during the POST invocation is invisible to the GET handler, so the recs API will always return 404.
Additional Locations (1)
| else if (enrichment.confirmedBookings >= 1) score += 0.1; | ||
|
|
||
| return Math.min(1, score); | ||
| } |
There was a problem hiding this comment.
Engagement heat score is constant per user, can't affect ranking
Medium Severity
engagementHeatScore only takes enrichment (a user-level input) and returns the same value for every candidate property in the ranking loop. Since all properties get the same additive constant, it cannot change the relative ordering after scored.sort(). The 2% weight budget allocated to SCORING.engagementHeat has no effect on which properties are recommended, while the similarity factor was reduced from 50% to 45% partly to accommodate it.
Additional Locations (1)
|
Good catches from Bugbot. Quick responses: In-memory store (HIGH) — Correct, the in-memory Income tier dead zones — Fixing in next commit. Tier mapping needs to cover all 7 levels. Engagement heat constant per user — True that it doesn't change relative property ordering within a single user. It was designed as a user-level confidence signal (active users get bolder recs via higher overall scores), but if the team prefers, I can remove it and give the 2% weight back to similarity. Unused export + negative limit — Fixing in next commit. |
- Income tiers now map to [2, 4, 5, 6, 7] covering full range without dead zones at budget/mid tiers - Limit param clamped to [1, 20] with NaN fallback to 6 - getRecsStoreStats no longer exported (unused outside module) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| const embeddings = await generatePropertyEmbeddings(properties); | ||
|
|
||
| console.log(" [3/5] Fetching user signals and search history..."); | ||
| console.log(" [3/6] Fetching user signals, search history, and enrichment..."); |
There was a problem hiding this comment.
Pipeline step counter inconsistency in log messages
Low Severity
Steps 1 and 2 still log [1/5] and [2/5] from the old 5-step pipeline, while steps 3–6 correctly log /6. The total step count is now 6 after adding the "Storing recs for API" step, so the first two log lines are inconsistent and misleading.


Summary
Building on Kristian's recs pipeline, this adds enrichment scoring from Minerva demographics and an API endpoint for the homepage.
What's new:
Enrichment scoring — 3 new factors from
analytics.customer_profiles:estimated_income_rangeto property price tiers so a $300/night guest doesn't see $2,000 homesAPI endpoint —
GET /api/pipelines/cio-property-recs/recs/{userId}serves pre-computed recs for the homepage "For You" carouselScoring comparison:
Building on previous work: The first "For You" feature (early 2025) surfaced important learnings — coverage needs to be high enough to reach most users, price matching matters so recommendations feel relevant, and the system should be decoupled from the search infrastructure so it can evolve independently. This version incorporates all of those learnings.
Results from email deployment (same engine):
Enrichment data coverage (verified via BigQuery)
Users without enrichment data still get good recs from the other 4 factors — enrichment scoring returns neutral (0.5) when data is missing.
To get across the finish line
for-you-carouselcreated on web-stagingTest plan
bun scripts/test-recs-pipeline.ts --phase=rank --emails=jae@wander.com— verify enrichment scoring applies to John's recscurl {deployed-url}/api/pipelines/cio-property-recs/recs/{user-id}— verify API returns ranked properties🤖 Generated with Claude Code
Note
Medium Risk
Changes recommendation ranking logic by adding new enrichment-based score factors and introduces an admin-token-protected API backed by an in-memory recs store, which could impact rec quality and availability if the pipeline hasn’t run in the current process.
Overview
Adds an admin-gated endpoint
GET /api/pipelines/cio-property-recs/recs/{userId}to serve precomputed “For You” homepage recommendations with an optionallimit(default 6, max 20).Updates the CIO property recs pipeline to fetch Minerva enrichment data (
analytics.customer_profiles) and incorporate three new scoring factors (income match, life-stage fit, engagement heat), reducing similarity weight from 50%→45%. The pipeline now also stores per-user recs in an in-memory map (includingis_cold_startandgenerated_at) for the new API to read.Written by Cursor Bugbot for commit 0c774af. This will update automatically on new commits. Configure here.