Skip to content

RFD: Weekly Forecast — Implementation Analysis

RFD: Weekly Forecast — Implementation Analysis

Section titled “RFD: Weekly Forecast — Implementation Analysis”

Status: Research
Author: Steve Hollinger
Date: 2026-04-07
PRD: Weekly Forecast PRD
Epic: PLT-536

The Weekly Forecast is a personalized shopping list generated inside the AI Rewards Assistant that predicts what users will buy each week and surfaces the highest-value earning opportunities. This document scopes what it takes to build Phase 1 (MVP) using only existing components — offer-shelf, product-card, and text — with no new UI widget development.

Key finding: ~80% of the capability exists today. The forecast is delivered as a structured episode composed of text blocks + existing offer-shelf/product-card components. The main new work is (1) forecast generation logic in consumer-graph-worker and (2) an episode template that assembles the forecast from existing building blocks.

CapabilityServiceStatusNotes
Purchase historyNeo4j via consumer-graph-workerReadyPURCHASED relationships with timestamps, interval, likelihood
Repurchase predictionsconsumer-graph-worker schedulerReadyCandidate scoring with probability, expected value
Product enrichmentconsumer-context-serviceReadyFPS + FIDORA + Button (name, image, price, merchant)
Active offersOffer Guardian via rover-mcpReadyFull offer catalog, eligibility via Neli
Fetch Shop availabilityButton merchant service via CCSReadyIsButtonCommissioned, PPD points, merchant data
Offer search (semantic)Offer Search ML via CCS/rover-mcpReadyMatch products to offers
Location/retailer dataLIDAR via CCS/rover-mcpReadyNearby retailers with distance
Category taxonomyCategory service (Python sidecar)Ready2740 categories, UUID hierarchy
User profile/timezoneProfile service via consumer-graph-workerReadyTimezone for shopping day detection

2.2 UI Components (all exist today, no new components)

Section titled “2.2 UI Components (all exist today, no new components)”
ComponentHow It’s Used in Forecast
offer-shelfGroups of 3+ items that have active offers — renders as scrollable shelf with offer details
product-cardIndividual high-value items (stacked tier) get their own card with full enrichment
text (episode body)Section headers, points summary, tier explanations, call-to-action
prompt-suggestionLanding page entry point: “See your weekly forecast”
Episode systemForecast is a persistent episode with structured content

2.3 Delivery Infrastructure (exists today)

Section titled “2.3 Delivery Infrastructure (exists today)”
CapabilityServiceStatus
Push notificationsnotification-serviceReady
Episode creationconsumer-agent builder APIReady
DM deliveryconsumer-graph-workerReady
Feature flagsFeature FlipperReady

3. Forecast Episode Structure (no new components)

Section titled “3. Forecast Episode Structure (no new components)”

The forecast is delivered as a consumer-agent episode assembled from text + existing components:

Episode: "Your Weekly Forecast"
├── Text: "Here's your personalized shopping forecast for this week.
│ You could earn up to 12,450 points."
├── Text: "Best Deals — Offer + Fetch Shop"
├── offer-shelf: [offer_id_1, offer_id_2, offer_id_3] ← stacked tier items
│ (renders existing offer shelf with images, points, "Offer + Shop" badge)
├── Text: "Active Offers"
├── offer-shelf: [offer_id_4, offer_id_5, offer_id_6] ← offer-only tier items
│ (renders existing offer shelf)
├── Text: "Available on Fetch Shop"
├── product-card: {fido_id, title, price, ppd_points, merchant} ← shop-only item
├── product-card: {fido_id, title, price, ppd_points, merchant} ← shop-only item
├── Text: "Tap any item to activate its offer or shop through Fetch."

Why this works: The offer-shelf already renders offer images, point values, and activation CTAs. The product-card already renders product images, prices, and Shop links. Text provides the narrative structure. No new rendering code needed on iOS/Android.

Maps directly onto the existing repurchase candidate pipeline:

Neo4j (purchase graph)
→ get_repurchase_candidates query (already exists)
→ Filter: probability > 0.6 (configurable threshold)
→ Enrich via CCS (product metadata, offers, Shop availability)
→ Tier classification:
Stacked = offer_active AND is_on_fetch_shop
Offer = offer_active AND NOT is_on_fetch_shop
Shop = NOT offer_active AND is_on_fetch_shop
None = excluded from forecast
→ Sort by tier priority, then expected_value
→ Build episode from text + offer-shelf + product-card components
→ Persist forecast to DynamoDB + create episode

What already exists: The scheduler’s BatchEnrichCandidates + EvaluateEligibility pipeline does steps 1-3. The CCS enrichment already returns OfferActive, IsOnFetchShop, PPDPoints, OfferPoints.

ConcernOwnerRationale
Forecast generationconsumer-graph-workerAlready has Neo4j access, enrichment pipeline, scheduler
Forecast persistenceDynamoDB (new table){env}-weekly-forecasts, keyed by user_id + week
Forecast delivery (push)consumer-graph-worker schedulerExtend existing notification pipeline
Forecast episode creationconsumer-agentExisting episode builder with text + offer-shelf + product-card
Forecast retrievalconsumer-context-serviceNew REST endpoint for re-hydration
Weekly (per-user shopping day):
consumer-graph-worker ForecastHandler
→ Neo4j: get candidates for user (existing query)
→ CCS: batch enrich (existing)
→ Tier classify + rank (new, ~50 lines)
→ consumer-agent: create episode with:
- text blocks (summary, section headers)
- offer-shelf components (stacked + offer tiers, using real offer IDs)
- product-card components (shop-only tier)
→ DynamoDB: persist forecast metadata (for refresh/analytics)
→ notification-service: push "Your weekly forecast is ready — earn up to X points"
On user tap:
iOS app deeplink → rover-agent → consumer-agent
→ GET episode (existing)
→ Renders text + offer-shelf + product-card (all existing components, zero new UI)

Using fields already available in the CCS enrichment response:

func classifyTier(e *ProductEnrichResponse) string {
hasOffer := e.OfferActive
onShop := e.IsOnFetchShop
switch {
case hasOffer && onShop:
return "stacked" // Best: offer + Shop points
case hasOffer:
return "offer" // Good: offer points anywhere
case onShop:
return "shop" // OK: Shop points only
default:
return "" // Excluded
}
}

Points calculation (already in enrichment response):

  • Stacked: OfferPoints + PPDPoints
  • Offer only: OfferPoints
  • Shop only: PPDPoints

New Neo4j query (lightweight, run once per user per month):

MATCH (u:User {userId: $userId})-[p:PURCHASED]->()
WHERE p.lastPurchaseDate > datetime() - duration('P90D')
WITH u, datetime(p.lastPurchaseDate).dayOfWeek AS dow, count(*) AS freq
RETURN dow, freq
ORDER BY freq DESC
LIMIT 1

Generate forecast 2 days before the user’s peak shopping day. Cache in DynamoDB user profile.

GapEffortDetails
ForecastHandler~3 daysNew handler in consumer-graph-worker (like NotificationHandler). Reuses Neo4j queries + CCS enrichment. Builds episode from text + existing components.
Episode template builder~1 dayAssembles text blocks + offer-shelf + product-card into a forecast episode via consumer-agent builder API.
Shopping day detection~1 dayNeo4j query for peak day-of-week. Cache result.
DynamoDB forecast table~0.5 day{env}-weekly-forecasts. PK=user_id, SK=week_start. Metadata for refresh/analytics.
CCS forecast endpoint~1 dayGET /v1/users/{user_id}/forecast — reads DynamoDB, returns forecast metadata.
Push notification template~0.5 day”Your weekly forecast is ready — earn up to X,XXX points this week”
Feature flagTrivialweekly_forecast_enabled in Feature Flipper
Testing + staging~2 daysEnd-to-end with sample users
  • Neo4j purchase graph + repurchase candidate queries
  • CCS product enrichment (FPS, FIDORA, Button, Offer Guardian, Neli)
  • offer-shelf component (renders offer-tier items as-is)
  • product-card component (renders shop-only items as-is)
  • Episode system (persistence, history)
  • Notification-service (push delivery)
  • Profile service (timezone)
  • Category resolver (display names)
  • LLM short title generation (concise product names in text)
  • DynamoDB infrastructure (already have short-title table pattern)
Days
ForecastHandler3
Episode template1
Shopping day detection1
DynamoDB + CCS endpoint1.5
Push template + flag1
Testing2
Total Phase 1 MVP~9-10 days

Push notification:

Your weekly forecast is ready Earn up to 12,450 points on 8 items you’re likely to buy this week

Episode content (markdown text + existing components):

Since text supports markdown, the forecast episode uses formatted text for structure, emphasis, and readability — no new components needed for headers, tables, or summaries.

# Your Weekly Forecast
**Week of April 7** · Based on your purchase history
You could earn up to **12,450 points** this week across 8 items.
| Tier | Items | Points |
|------|-------|--------|
| Offer + Shop | 3 | 5,200 |
| Active Offers | 4 | 4,750 |
| Fetch Shop | 2 | 2,500 |
---
## Best Deals — Offer + Shop ⭐
*These items have an active offer AND are available on Fetch Shop for maximum points.*

[offer-shelf: 3 stacked-tier items with real offer IDs]

## Active Offers
*Buy these anywhere and scan your receipt to earn.*

[offer-shelf: 4 offer-only items with real offer IDs]

## Available on Fetch Shop
*No active offer, but you can earn points buying through Fetch Shop.*

[product-card: Shop-only item 1] [product-card: Shop-only item 2]

---
*Tap any item to activate its offer or add it to your Fetch Shop cart.*
  1. Forecast size: Suggest 8-15 items max. Offer-shelf needs min 3 per shelf.
  2. Cold start: Users with <4 receipts — skip forecast or use popular items + active offers?
  3. Relationship to repurchase nudges: Should forecast supersede individual nudges? Or coexist? Recommend coexist initially — nudges are daily, forecast is weekly.
  4. Manual refresh: User says “refresh my forecast” in chat → consumer-agent re-runs forecast generation for that user via CCS? Or just re-query the persisted forecast?
  5. Offer expiration mid-week: Stale offers in the episode. Options: (a) ignore for MVP, (b) re-hydrate offer-shelf on episode open (rover-agent already does this for product-card enrichment).
RiskImpactMitigation
Neo4j load from per-user forecast generationMediumBatch during off-peak (3 AM UTC). Same pattern as nudges.
CCS enrichment volumeMediumExisting caches (FPS, FIDORA, Button). Batch enrich.
Offer-shelf with expired offersLowOffer-shelf already handles missing/expired offers gracefully.
Episode size (8-15 components)LowExisting episode rendering handles this.

Build Phase 1 as a new ForecastHandler in consumer-graph-worker, composing episodes from text + offer-shelf + product-card — all existing components. No new UI development required. The forecast is structurally just a richer version of the repurchase nudge episode, with multiple items grouped by earning tier instead of a single product.

Estimated timeline: ~9-10 days to first forecast delivered in staging.