> ## Documentation Index
> Fetch the complete documentation index at: https://docs.closient.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dietary Filter Search

> Search for products constrained by stored dietary, allergen, religious, or sourcing preferences.

Search that respects the user's persistent trait preferences — allergens they
must avoid, dietary stances (vegan, kosher, halal), religious constraints,
and sourcing preferences (organic, fair-trade, local).

## When to use

* User has a preference profile (explicitly set or inferred) and searches
  without restating "and no nuts please" each time.
* Any recipe / meal / grocery workflow where preferences should be defaulted.

## Preferences model

Closient tracks a user's `TraitPreference` list — each entry is:

* `trait` — lower-snake-case slug, e.g. `peanuts` (allergen), `vegan`
  (dietary), `kosher` (religious). Pass directly into search constraints.
* `stance` — `AVOID` / `REQUIRE` / `PREFER` / `NEUTRAL`
* `category` — ALLERGEN / DIETARY / RELIGIOUS / SOURCING

## Flow (end-to-end)

### 1. Read user preferences

```
GET /account/api/v1/preferences
Authorization: Bearer <token>
```

Returns the caller's full preference list as JSON:

```jsonc theme={null}
[
  {"trait": "peanuts", "trait_label": "Peanuts", "category": "ALLERGEN", "stance": "AVOID"},
  {"trait": "vegan",   "trait_label": "Vegan",   "category": "DIETARY",  "stance": "REQUIRE"},
  {"trait": "organic", "trait_label": "Organic", "category": "SOURCING", "stance": "PREFER"}
]
```

Fresh accounts return `200` + `[]`. Read-only — writes still happen
through the dashboard's HTMX form at `/preferences/`.

### 2. Translate preferences into constraints

| Category  | Stance  | Goes into                |
| --------- | ------- | ------------------------ |
| ALLERGEN  | AVOID   | `exclude_allergens`      |
| DIETARY   | REQUIRE | `require_certifications` |
| RELIGIOUS | REQUIRE | `require_certifications` |
| SOURCING  | REQUIRE | `require_certifications` |
| ANY       | PREFER  | rank boost (client-side) |
| ANY       | NEUTRAL | ignored                  |

### 3. Run a constrained search

The unified surface accepts the three constraint sets directly — either as
list query params on `GET /search/api/v1/search` or as `constraints.*`
keys on `POST /search/api/v1/search/session`:

```
GET /search/api/v1/search?q=oat+milk
  &exclude_allergens=peanuts
  &require_certifications=vegan
  &require_certifications=organic
```

```
POST /search/api/v1/search/session
{
  "query": "oat milk",
  "latitude": 30.267,
  "longitude": -97.743,
  "constraints": {
    "exclude_allergens":       ["peanuts"],
    "require_certifications":  ["vegan", "organic"],
    "avoid_substances":        ["sucralose"]
  }
}
```

The session route also picks up the same slugs from natural-language
phrasing — `"avoid peanuts"`, `"vegan certified"`, `"no sucralose"` —
so unauthenticated callers without a stored preference list still get
the filter for free.

### 4. Read the trait fields on each result

`SearchResultOut` carries the fields inline so no per-candidate
round-trip is needed:

* `certifications: string[]` — certification slugs the product holds
  (unions `CertificationAssignment` codes with non-allergen `Trait`
  links — DIETARY / RELIGIOUS / SOURCING).
* `contains_allergens: string[]` — allergen slugs detected on the
  product (unions allergen-typed substance composition with explicit
  ALLERGEN `Trait` links).
* `substance_flags: string[]` — substances that matched the current
  request's `avoid_substances` filter, so the UI can show "flagged
  because X".
* `allergen_data_confidence: "high" | "medium" | "low"` —
  * `high`: explicit substance composition matched (we *know* what's in it).
  * `medium`: explicit allergen `Trait` link only (asserted, not compositional).
  * `low`: neither signal — treat missing data as *not* clean.

## Guidance for agents

* **AVOID always beats PREFER** — never recommend something that fails an
  AVOID constraint.
* **Trust `high`/`medium` confidence; question `low`.** When the user has
  a hard avoid constraint and the row's confidence is `low`, drop the
  candidate or ask the user instead of recommending blindly.
* **Surface the rationale** — "Showing Product X because it's certified
  vegan and avoids peanuts per your preferences."
* **Ask about gaps**, don't silently guess. If the user hasn't set a
  preference relevant to a tricky product (e.g. glycerin for strict vegans),
  ask before deciding for them.

## Related skills

* `local-product-search` — the base search this skill wraps
* `find-alternative` — when a result fails a constraint, use this to substitute
