Personal health: why am I bloated?
Bloated at 16:00. Which of today’s meals most likely did it, given what has bothered you before?
AvailableNo benchmark yetOverview
This is where HeapDB started, and it's our template for personal-data questions. You have one person's stream of meals, sleep, medication or exercise, and a symptom you ask about later. The answer isn't "the last thing you ate". It's the meal that falls in the right lag window and contains ingredients that have bothered you before.
We built it for people (and apps) keeping a food and symptom diary who want a ranked, explained shortlist to test with an elimination diet. It isn't a diagnosis.
=== Day 10, bloated at 16:00 ===
1. meal [coffee,dairy] w=0.8 score=0.4811
lag 1.50h -> kernel 0.845 x weight 0.8 x link 0.712 [noisy-OR of dairy=0.59 (a=8.24, b=5.76), coffee=0.30 (a=5.98, b=14.02)]
2. meal [beans,onion,rice] w=1.5 score=0.2491
lag 3.00h -> kernel 0.402 x weight 1.5 x link 0.413 [... inseparable]That's the output of heapdb-core's examples/bloating.py. On day 0 the folk prior "beans cause bloating" put lunch first; ten days of feedback flipped it to the latte, and to dairy rather than coffee.
How it maps
| HeapDB | Personal health |
|---|---|
| Entity | One person (me) |
| Events | Meals (and, by extension, sleep, medication, exercise) |
| Features | Ingredients: beans, onion, dairy, coffee, ... |
| Dose | Portion size (attrs["portion"], default 1.0) |
| Effects + kernels | bloating: log-normal, median 2 h, sigma 0.9 (peaks a few hours after eating, long tail) |
| Feedback | An elimination test: confirm the meal that did it, reject the others |
| Rules | cause_types={"meal"}: only meals can cause bloating |
Design notes
- A shortlist to test, not a diagnosis. A ranked association from your own diary is a hypothesis to test with an elimination diet, and to discuss with a doctor.
- Priors carry the first weeks. One person produces a handful of confirmations a week, so links move gradually from the priors you register.
- Privacy.
forget_entity(entity)removes a person's events, segments, links and calibration records (scrub_global=Truealso subtracts what they contributed to the shared layer). Per-entity encryption keys, an audit log and on-device defaults are on the roadmap.
Setup
pip install heapdb
curl -O https://raw.githubusercontent.com/heapdb/heapdb-core/main/examples/bloating.py
python bloating.py # needs only heapdb; no clone or Rust toolchainThe core of it:
from heapdb import HeapDB, LogNormalKernel
H = 3600.0
db = HeapDB(bucket_seconds=900)
db.register_event_type("meal", weight=lambda attrs: attrs.get("portion", 1.0))
db.register_effect(
"bloating",
kernel=LogNormalKernel(median=2 * H, sigma=0.9),
priors={"beans": (4, 1), "onion": (2, 2), "dairy": (1, 3)},
cause_types={"meal"},
)
db.insert("me", 13.0 * H, "meal", {"beans", "onion", "rice"}, attrs={"portion": 1.5})
latte = db.insert("me", 14.5 * H, "meal", {"coffee", "dairy"}, attrs={"portion": 0.8})
for c in db.top_causes("me", "bloating", at=16 * H, k=3, explain=True):
print(c.rank, c.event.short(), round(c.score, 3), c.explanation)
db.feedback("me", "bloating", 16 * H, confirmed_event_id=latte.id)Explanation
- Kernel, not recency. With a median lag of 2 hours, a meal 1.5 hours before scores 0.845 and one 8.5 hours before scores 0.043. The most recent meal is not automatically the answer.
- Proportional credit. Confirming the latte credits
dairyandcoffeein proportion to how well each already explains the bloating (day 0: coffee 0.50, dairy 0.63), instead of +1 to both. - Joint causes. While coffee and dairy have only ever arrived together, HeapDB reports them as one cause
coffee+dairyflaggedinseparable. Once a dairy-free black coffee has been eaten and rejected, they separate: dairy 0.59, coffee 0.30. - Calibration.
calibration_report("bloating")compares mean predicted score with the observed confirm rate per bin, with a Brier score.