Use cases

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 yet

Overview

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

HeapDBPersonal health
EntityOne person (me)
EventsMeals (and, by extension, sleep, medication, exercise)
FeaturesIngredients: beans, onion, dairy, coffee, ...
DosePortion size (attrs["portion"], default 1.0)
Effects + kernelsbloating: log-normal, median 2 h, sigma 0.9 (peaks a few hours after eating, long tail)
FeedbackAn elimination test: confirm the meal that did it, reject the others
Rulescause_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=True also 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 toolchain

The 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 dairy and coffee in 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+dairy flagged inseparable. 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.

Benchmarks