- ID
- d23a3fd8-ca8e-4d98-b3ac-05817a29ca43
BE-7103 Add exclude_nevers filter to PairingList API to ensure deterministic results for PDP recipe recommendations
Tasks
DONE Investigate adding exclude_nevers filter to PairingList API
- ID
- 82590e7d-db8f-e15c-4be1-4bf5624e5a85
DONE Add exclude_nevers filter to PairingList API
- ID
- 70d8d55e-8486-dadc-87b9-fea0b03d24ff
Description
https://hungryroot.slack.com/archives/C02N30ZNRSQ/p1770163383165189
Note
This will be customer specific so it will be necessary to bypass cache. This
also will need to likely change on v2 AND v3 API endpoints.
Background
We're launching a new feature (behind a feature flag) that displays recipes
containing a specific item at the bottom of Product Detail Pages (PDPs). When a
PDP opens, we'll fire a request to the pairings v2 endpoint like:
/api/v2/pairings/?product_ids=783&limit=8&is_omnivore=1&origin=1&inventory_date=2026-02-02&has_inventory=true&offset=0&sideload=tags
This returns pairings filtered by the user's dietary restrictions, origin,
inventory availability, and limited to 8 results.
Problem
Currently, we filter out recipes containing ingredients the customer has marked
as "Never" (via the Periodicity model) client-side. This creates a
non-deterministic user experience:
We request 8 pairings from the API
Some may contain "never'd" ingredients
After client-side filtering, we could end up with 0 to 8 items
Empty or sparse recipe sections provide a poor UX
Proposed Solution
Add a new optional exclude_nevers boolean filter to the PairingFilter filterset
that:
Requires customer_id to be present (similar to is_favorite/is_recent filters)
Excludes pairings where any ingredient is in the customer's "never" set
Uses the existing pattern:
queryset.exclude(ingredients__in=customer.get_never_products())
This allows the API to return a deterministic list of N pairings that are
guaranteed to not contain any never'd ingredients.
Existing Patterns to Follow
The codebase already implements this filtering in several places:
discovery_home/views/v3/collection.py:332
qs = qs.exclude(ingredients__in=self._product_nevers) # Exclude pairings with ingredients marked "never".
app/rest/pairing_swaps.py:178-179
never_set = customer.get_never_products()
similar_pairings = similar_pairings.exclude(ingredients__in=never_set)
Technical Considerations
Caching Impact: When exclude_nevers=true, the diet-group cache will need to be
bypassed (similar to is_favorite/is_recent) since results are customer-specific.
The per-user decorator cache (@dynamic_cache_response) will still apply.
Traffic Concerns: Every PDP open will trigger this endpoint. We should:
Monitor endpoint performance after rollout via feature flag
Consider if the existing caching layers are sufficient
Evaluate if exclude_nevers requests need a separate cache strategy or rate
limiting
Query Performance: The exclude(ingredients__in=never_set) pattern requires a
subquery or join. Consider:
Prefetch optimization for the ingredients relationship (already done in
_base_queryset)
Adding distinct() if needed to avoid duplicates from the join
Acceptance Criteria
[ ] New exclude_nevers boolean filter on PairingFilter
[ ] Requires customer_id parameter (raise ValidationError if missing)
[ ] Correctly excludes pairings with any never'd ingredients
[ ] Bypasses diet-group cache when filter is active
[ ] Unit tests covering filter behavior
[ ] Integration test verifying deterministic results
[ ] OpenAPI parameter documentation
API Usage Example
GET /api/v2/pairings/?product_ids=783&limit=8&customer_id=12345&exclude_nevers=true&has_inventory=true&inventory_date=2026-02-02&origin=1
Out of Scope
Feature flag implementation for the PDP recipe section (handled separately)
Client-side changes to use the new filter