import csv from datetime import datetime, timedelta from collections import defaultdict # Load CGM data def load_cgm(filepath): data = {} with open(filepath) as f: reader = csv.DictReader(f) for row in reader: dt = datetime.strptime(row['Date/Time'], '%Y-%m-%d %H:%M:%S') bg = float(row['Blood Glucose (mg/dL)']) data[dt] = bg return data cgm_mar6 = load_cgm('/home/ben/ava/ben/Blood-Glucose-2026-03-06-2026-03-06.csv') cgm_mar7_14 = load_cgm('/home/ben/downloads/Blood Glucose-2026-03-07-2026-03-14.csv') cgm = {**cgm_mar6, **cgm_mar7_14} # Parse meal log meals_text = open('/home/ben/ava/ben/health/meal-log.md').read() meals = [] current_date = None for line in meals_text.strip().split('\n'): line = line.strip() if line.startswith('# '): date_str = line[2:].strip() # Parse month and day try: current_date = datetime.strptime(date_str + ' 2026', '%B %d %Y') except: try: current_date = datetime.strptime(date_str + ' 2026', '%b %d %Y') except: current_date = None elif line and current_date: # Try to parse time parts = line.split(' ', 1) if ':' in parts[0]: try: time_parts = parts[0].split(':') h, m = int(time_parts[0]), int(time_parts[1]) meal_dt = current_date.replace(hour=h, minute=m) food = parts[1] if len(parts) > 1 else '' meals.append((meal_dt, food)) except: pass # For each day with CGM data, compute metrics print("=" * 80) print("MEAL LOG + CGM OVERLAY ANALYSIS (Mar 6-14)") print("=" * 80) for day_offset in range(6, 15): day = datetime(2026, 3, day_offset) day_str = day.strftime('%B %d') # Get CGM readings for this day day_readings = [(dt, bg) for dt, bg in sorted(cgm.items()) if dt.date() == day.date()] if not day_readings: continue # Get meals for this day day_meals = [(dt, food) for dt, food in meals if dt.date() == day.date()] bgs = [bg for _, bg in day_readings] mean_bg = sum(bgs) / len(bgs) min_bg = min(bgs) max_bg = max(bgs) # Compute time in range (70-140) tir = sum(1 for bg in bgs if 70 <= bg <= 140) / len(bgs) * 100 # Compute time above 140 above_140 = sum(1 for bg in bgs if bg > 140) / len(bgs) * 100 # First and last meal times if day_meals: first_meal = min(dt for dt, _ in day_meals) last_meal = max(dt for dt, _ in day_meals) eating_window = (last_meal - first_meal).total_seconds() / 3600 else: eating_window = None # Overnight/fasting glucose (midnight to first meal, or 4am-7am) fasting_readings = [(dt, bg) for dt, bg in day_readings if 4 <= dt.hour <= 7] fasting_avg = sum(bg for _, bg in fasting_readings) / len(fasting_readings) if fasting_readings else None print(f"\n{'─' * 60}") print(f" {day_str} (a {day.strftime('%A')})") print(f"{'─' * 60}") print(f" CGM: mean={mean_bg:.0f} min={min_bg:.0f} max={max_bg:.0f} TIR={tir:.0f}% >140={above_140:.0f}%") if fasting_avg: print(f" Fasting avg (4-7am): {fasting_avg:.0f}") if day_meals: print(f" Eating window: {eating_window:.1f}h ({first_meal.strftime('%H:%M')} → {last_meal.strftime('%H:%M')})") # Estimate fasting hours since previous day's last meal prev_day = day - timedelta(days=1) prev_meals = [(dt, food) for dt, food in meals if dt.date() == prev_day.date()] if prev_meals: prev_last = max(dt for dt, _ in prev_meals) fast_hours = (first_meal - prev_last).total_seconds() / 3600 print(f" Fast before first meal: {fast_hours:.1f}h (since {prev_last.strftime('%H:%M')} prev day)") print(f" Meals:") for dt, food in sorted(day_meals): # Find peak glucose within 2h of meal meal_readings = [(rdt, bg) for rdt, bg in day_readings if dt <= rdt <= dt + timedelta(hours=2)] if meal_readings: peak_bg = max(bg for _, bg in meal_readings) peak_time = max(meal_readings, key=lambda x: x[1])[0] # Pre-meal glucose (closest reading before meal) pre_readings = [(rdt, bg) for rdt, bg in day_readings if rdt <= dt and (dt - rdt).total_seconds() < 900] pre_bg = pre_readings[-1][1] if pre_readings else None if pre_bg: delta = peak_bg - pre_bg print(f" {dt.strftime('%H:%M')} {food[:60]}") print(f" pre={pre_bg:.0f} → peak={peak_bg:.0f} (+{delta:.0f}) at {peak_time.strftime('%H:%M')}") else: print(f" {dt.strftime('%H:%M')} {food[:60]}") print(f" peak={peak_bg:.0f} at {peak_time.strftime('%H:%M')}") else: print(f" {dt.strftime('%H:%M')} {food[:60]}") else: print(f" No meals logged") # Now compute cross-day patterns print(f"\n{'=' * 80}") print("CROSS-DAY PATTERNS") print("=" * 80) # Collect per-day summaries day_summaries = [] for day_offset in range(6, 15): day = datetime(2026, 3, day_offset) day_readings = [(dt, bg) for dt, bg in sorted(cgm.items()) if dt.date() == day.date()] if not day_readings: continue day_meals = [(dt, food) for dt, food in meals if dt.date() == day.date()] bgs = [bg for _, bg in day_readings] fasting_readings = [(dt, bg) for dt, bg in day_readings if 4 <= dt.hour <= 7] fasting_avg = sum(bg for _, bg in fasting_readings) / len(fasting_readings) if fasting_readings else None if day_meals: first_meal = min(dt for dt, _ in day_meals) last_meal = max(dt for dt, _ in day_meals) eating_window = (last_meal - first_meal).total_seconds() / 3600 num_meals = len(day_meals) else: eating_window = None num_meals = 0 first_meal = None # Estimate carb-heaviness carb_heavy_words = ['bread', 'toast', 'pancake', 'syrup', 'rice', 'pasta', 'spaghetti', 'potato', 'sweet potato', 'croissant', 'cake', 'cookie', 'ice cream', 'pita', 'burrito', 'tortilla', 'honey', 'chocolate', 'cornbread', 'macaroni', 'mamaliga', 'cobbler', 'pudding', 'fig', 'popcorn', 'hash brown', 'roll', 'granola'] all_foods = ' '.join(food.lower() for _, food in day_meals) carb_items = sum(1 for word in carb_heavy_words if word in all_foods) # Previous day's last meal for fasting calc prev_day = day - timedelta(days=1) prev_meals = [(dt, food) for dt, food in meals if dt.date() == prev_day.date()] if prev_meals and first_meal: prev_last = max(dt for dt, _ in prev_meals) overnight_fast = (first_meal - prev_last).total_seconds() / 3600 else: overnight_fast = None day_summaries.append({ 'date': day, 'mean': sum(bgs)/len(bgs), 'max': max(bgs), 'min': min(bgs), 'tir': sum(1 for bg in bgs if 70 <= bg <= 140) / len(bgs) * 100, 'above140': sum(1 for bg in bgs if bg > 140) / len(bgs) * 100, 'fasting_avg': fasting_avg, 'eating_window': eating_window, 'num_meals': num_meals, 'overnight_fast': overnight_fast, 'carb_items': carb_items, }) # Print correlation-style analysis print("\nDay-by-day summary:") print(f"{'Date':>8} {'Mean':>5} {'Max':>5} {'TIR%':>5} {'>140%':>5} {'Fast':>5} {'EatWin':>6} {'#Meals':>6} {'Carbs':>5} {'OvnFast':>7}") for s in day_summaries: fast_str = f"{s['fasting_avg']:.0f}" if s['fasting_avg'] else " -" ew_str = f"{s['eating_window']:.1f}" if s['eating_window'] else " -" of_str = f"{s['overnight_fast']:.1f}" if s['overnight_fast'] else " -" print(f" Mar {s['date'].day:>2} {s['mean']:>5.0f} {s['max']:>5.0f} {s['tir']:>5.0f} {s['above140']:>5.0f} {fast_str:>5} {ew_str:>6} {s['num_meals']:>6} {s['carb_items']:>5} {of_str:>7}") # Correlations (simple) print("\nKey patterns:") # Overnight fast vs fasting glucose with_fast = [(s['overnight_fast'], s['fasting_avg']) for s in day_summaries if s['overnight_fast'] and s['fasting_avg']] if with_fast: print(f"\n Overnight fast length vs fasting glucose ({len(with_fast)} days):") for f, g in sorted(with_fast): print(f" {f:.1f}h fast → {g:.0f} mg/dL fasting glucose") # Eating window vs daily mean with_ew = [(s['eating_window'], s['mean']) for s in day_summaries if s['eating_window']] if with_ew: print(f"\n Eating window vs daily mean ({len(with_ew)} days):") for ew, m in sorted(with_ew): print(f" {ew:.1f}h window → {m:.0f} mean") # Carb count vs daily max with_carb = [(s['carb_items'], s['max'], s['date'].day) for s in day_summaries] if with_carb: print(f"\n Carb item count vs daily max:") for c, mx, d in sorted(with_carb, key=lambda x: x[0]): print(f" {c} carb items (Mar {d}) → max {mx:.0f}") # Number of meals vs mean and TIR print(f"\n # Meals vs TIR and mean:") for s in sorted(day_summaries, key=lambda x: x['num_meals']): print(f" {s['num_meals']} meals (Mar {s['date'].day:>2}) → mean {s['mean']:.0f}, TIR {s['tir']:.0f}%, max {s['max']:.0f}")