| """ |
| Metabolic Forensics β N-of-1 biosignal evidence engine. |
| |
| Skeleton on synthetic data. The deterministic evidence pipeline is real; |
| the LLM narration layer is stubbed (plug in a local llama.cpp model where |
| marked). No personal data ships in this repo β privacy by construction. |
| """ |
| import numpy as np |
| import pandas as pd |
| import gradio as gr |
|
|
| |
| |
| |
| |
| def make_demo_data(days: int = 46, seed: int = 7) -> pd.DataFrame: |
| rng = np.random.default_rng(seed) |
| dates = pd.date_range("2026-04-19", periods=days, freq="D") |
| late_meal = rng.random(days) < 0.35 |
| glucose_spike = 110 + late_meal * 45 + rng.normal(0, 8, days) |
| |
| hrv = 55 - late_meal * 9 + rng.normal(0, 6, days) |
| recovery = np.clip(70 - late_meal * 18 + rng.normal(0, 10, days), 1, 100) |
| alertness = np.clip(75 - late_meal * 15 + rng.normal(0, 12, days), 1, 100) |
| return pd.DataFrame({ |
| "date": dates, |
| "late_meal": late_meal.astype(int), |
| "glucose_peak": glucose_spike.round(0), |
| "hrv": hrv.round(0), |
| "recovery": recovery.round(0), |
| "morning_alertness": alertness.round(0), |
| }) |
|
|
| DF = make_demo_data() |
|
|
| |
| |
| |
| |
| |
| QUESTIONS = { |
| "What precedes my low-recovery mornings?": ("recovery", "low", "late_meal"), |
| "What precedes my glucose spikes?": ("glucose_peak", "high", "late_meal"), |
| "What precedes my low-alertness mornings?":("morning_alertness", "low", "late_meal"), |
| } |
|
|
| def forensics(question: str): |
| metric, direction, candidate = QUESTIONS[question] |
| df = DF.copy() |
| thr = df[metric].quantile(0.25 if direction == "low" else 0.75) |
| event = df[metric] <= thr if direction == "low" else df[metric] >= thr |
|
|
| with_cand = event & (df[candidate] == 1) |
| base_rate = df[candidate].mean() |
| event_rate = df.loc[event, candidate].mean() |
| lift = (event_rate / base_rate) if base_rate else float("nan") |
|
|
| |
| counterex = df[event & (df[candidate] == 0)] |
|
|
| evidence = { |
| "question": question, |
| "n_days": len(df), |
| "n_event_days": int(event.sum()), |
| "candidate_signal": candidate, |
| "candidate_present_on_event_days_pct": round(100 * event_rate, 0), |
| "candidate_baseline_pct": round(100 * base_rate, 0), |
| "lift": round(lift, 2), |
| "n_counterexamples": len(counterex), |
| } |
|
|
| |
| |
| |
| |
| narration = ( |
| f"**Observed association** β on your {evidence['n_event_days']} " |
| f"'{question.split('my ')[-1].rstrip('?')}' days, " |
| f"`{candidate}` was present {evidence['candidate_present_on_event_days_pct']:.0f}% " |
| f"of the time vs a {evidence['candidate_baseline_pct']:.0f}% baseline " |
| f"(**{evidence['lift']}Γ lift**).\n\n" |
| f"**Counterexamples** β but {evidence['n_counterexamples']} of those days had " |
| f"NO `{candidate}`, so this is a tendency, not a law. Don't overfit.\n\n" |
| f"**Next experiment** β deliberately vary `{candidate}` for 7 days and watch " |
| f"whether '{metric}' separates. That turns correlation into something testable." |
| ) |
| plot_df = df[["date", metric]].rename(columns={metric: "value"}) |
| return narration, plot_df, evidence |
|
|
| with gr.Blocks(title="Metabolic Forensics", theme=gr.themes.Soft()) as demo: |
| gr.Markdown( |
| "# π©Έ Metabolic Forensics\n" |
| "*N-of-1 biosignal **evidence engine** β forensics, not coaching. " |
| "Running on synthetic demo data.*" |
| ) |
| q = gr.Dropdown(list(QUESTIONS), value=list(QUESTIONS)[0], label="Forensic question") |
| btn = gr.Button("Investigate", variant="primary") |
| out_md = gr.Markdown() |
| out_plot = gr.LinePlot(x="date", y="value", label="Metric over your history") |
| out_json = gr.JSON(label="Raw evidence (what the model is allowed to narrate)") |
| btn.click(forensics, q, [out_md, out_plot, out_json]) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|