Hypothesis Engine

What It Does

The hypothesis engine watches a user’s suspected foods and looks for patterns in the sensitivity category graph. When multiple suspected foods share a category, it suggests additional foods in that category that the user hasn’t thought to test.

This surfaces hidden variables the user didn’t know to test — and is the app’s primary differentiator from a simple food journal.

The Core Logic

when user_suspect_foods changes:
  for each sensitivity_category:
    suspected_in_category = foods where:
      - user suspects them
      - they have membership in this category (any severity)
    
    if count(suspected_in_category) >= 2:
      candidates = foods where:
        - high severity in this category
        - NOT already suspected by user
        - NOT currently in active washout_window for this category
      
      for each candidate:
        create hypothesis_suggestion(
          user: user,
          suggested_food: candidate,
          reason_category: category,
          status: :pending
        )

Example Walkthrough

User suspects: avocado, red wine, leftover chicken.

Category analysis:

  • Avocado → histamine (high), salicylate (medium)
  • Red wine → histamine (high), salicylate (high), DAO blocker
  • Leftover chicken → histamine (high)

Histamine count: 3 suspects → threshold met ✓

Candidates (high histamine, not yet suspected, no active washout):

  • Aged cheese
  • Sauerkraut
  • Spinach
  • Eggplant

Suggestions created: “We noticed avocado, red wine, and leftover chicken are all high histamine. Want to add aged cheese to your test plan?”

Salicylate count: 2 suspects (avocado + red wine) → threshold met ✓

Candidates (high salicylate, not yet suspected):

  • Strawberries
  • Citrus
  • Most spices

Suggestions created for salicylate category too.

Washout Window Check

Critically, the engine does NOT suggest a new food during an active washout window for its category. If histamine is currently in washout, all histamine-category suggestions are queued but not surfaced until the washout clears.

This prevents the user from adding histamine-category foods during a period when the model is specifically trying to clear histamine from the experimental slate.

active_washout = WashoutWindow.where(
  meal_plan: user.current_meal_plan,
  sensitivity_category: category,
  start_date: ..Date.today,
  end_date: Date.today..
).exists?
 
next if active_washout

Temporal Lag Awareness

Different sensitivity categories produce symptoms on different timescales (see Temporal Lag: The Missing Variable). The hypothesis engine needs to account for this when evaluating whether a suspected food’s symptoms align with its expected category:

Fast-onset categories (histamine, glutamate, capsaicin): Symptoms appearing within 0-2 hours of the meal are consistent with these categories. If a user suspects a food but their symptoms are consistently delayed 12+ hours, the engine should consider whether the food’s other category memberships (lectins, salicylates) better explain the pattern.

Delayed categories (lectins, salicylates): Symptoms appearing 6-48 hours post-exposure are consistent. If a user’s symptoms are immediate, the hypothesis engine should weight immediate-onset categories (histamine, capsaicin) higher.

For the MVP, this means the engine should present temporal context alongside its suggestions: “Avocado, red wine, and leftover chicken are all high histamine. Histamine reactions typically appear within 1-2 hours of eating. Does that match your experience?” This helps the user confirm or reject the category hypothesis and can redirect the engine’s analysis.

For v0.2+, the engine can cross-reference the user’s logged symptom onset times against the expected temporal profiles of each category, automatically adjusting category confidence scores.

Suggestion Lifecycle

pending  →  accepted  →  added to meal plan + test schedule
         →  rejected   →  dismissed, not re-surfaced

Accepted suggestions trigger the meal plan generator to schedule the new food with proper washout spacing.

Negative Hypotheses

The engine should also generate “clear” signals — categories where the data suggests the user is NOT sensitive. If a user has eaten 5+ high-histamine foods across 15+ days with no histamine-category symptom correlation after FDR correction, the engine should say: “Your data doesn’t show a histamine pattern so far. We can reduce histamine restrictions in your meal plan to free up testing bandwidth for other categories.”

This is important because:

  • It prevents unnecessary dietary restriction (a real harm)
  • It accelerates the testing cycle by letting the user focus resources on more promising hypotheses
  • It aligns with the distinction between HIT and MCAS — dietary histamine elimination that doesn’t help suggests the problem isn’t dietary histamine

Population-Level Extension (v0.4)

Once aggregate data exists (opt-in users), the hypothesis engine can be trained on population patterns rather than just individual category membership:

“Users who react to avocado + red wine also commonly react to spinach (87% overlap in our dataset).”

This goes beyond category membership to actual co-occurrence patterns in real user data. Far more powerful than static category rules.

Statistical note on population patterns

Population-level co-occurrence analysis requires careful handling. The co-occurrence could reflect shared category membership (which the engine already captures) or genuine biological subtypes within a category (which would be novel). Distinguishing these requires conditional independence testing: “Do users who react to A also react to B, controlling for shared category membership?” This is technically a Bayesian network structure learning problem and is genuinely interesting, but requires substantial data (hundreds of users) to estimate reliably.

UI Presentation

The user sees: “We noticed a pattern in your suspected foods. Want to test aged cheese?”

They do NOT see: “3 high-histamine foods detected. Histamine category threshold met. Generating candidates…”

The science is the engine. The UI is the car.