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_washoutTemporal 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.