Goal
For every push notification, the engagement funnel must nest strictly:
opened ≥ viewed ≥ engaged ≥ converted, where each stage is a subset of the prior — every viewer is an opener, every engager is a viewer, every converter is an engager.
Today the funnel is broken in two independent ways, both visible in live data:
viewed > opened — content is counted from organic visitors who never opened the push. The content query is scoped only by guideId/offerId/giftcardId + time window, not by who opened (queryEngagementForReport.ts:210-237). The doc’s audience-rewards guide: 5,685 viewed vs 1,076 opened. engaged > viewed — viewedContent and engagedContent are independent booleans set by different event types (queryEngagementForReport.ts:249-294); an offer.tappedShopOnline flips engaged without ever flipping viewed. Live cruise push (taskId e3c0526a) right now: engagedContent: 495 vs viewedContent: 292.