Org Web Adapter

hungryroot/jira/be_7103_add_exclude_nevers_filter_to_pairinglist_api_to_ensure_deterministic_results_for_pdp_recipe_recommendations.org

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

workprojectapiv2hungryrootpdpbe7103ep528sprint103
ID
82590e7d-db8f-e15c-4be1-4bf5624e5a85

DONE Add exclude_nevers filter to PairingList API

workprojectapiv2hungryrootpdpbe7103ep528sprint103
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