#!/usr/bin/env python3 """ analyze_history.py — one-off health/calorie review of the meal-history.json log. Reads the rolling 7-day meal log and prints, per meal and per day: - estimated kcal and protein (g) - daily totals + averages across the window - a rough micronutrient read (fibre, sat fat, vit C, vit A, calcium) - whether each day meets the protein / fibre / sat-fat targets Nutrition is ESTIMATE-grade (see references/nutrition-db.json). Each DB item carries its canonical `unit` and `g_per_unit`; the script converts the LOGGED qty+unit to grams, then scales — so a veg logged in 'g' and one logged in 'count' both resolve correctly. Condiments, oils and cooking fat are NOT in the log, so real meals run ~80-120 kcal higher per cooked meal. This is a review tool, not a send gate. It does NOT modify any file. Usage: python3 scripts/analyze_history.py # whole logged window python3 scripts/analyze_history.py --days 4 # last N days only """ import json, os, argparse from datetime import datetime SKILL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) FIELDS = ["kcal", "protein", "fibre", "satfat", "vitC", "vitA", "calcium"] def load(): with open(os.path.join(SKILL_DIR, "meal-history.json")) as f: hist = json.load(f) with open(os.path.join(SKILL_DIR, "references", "nutrition-db.json")) as f: db = json.load(f) entries = hist["entries"] if isinstance(hist, dict) else hist return entries, db def to_grams(name, qty, unit, db): spec = next((db["items"][k] for k in db["items"] if k.lower() == name.lower()), None) if not spec: return 0.0 if unit in ("g", "ml", "kg"): return qty * (1000 if unit == "kg" else 1) # count/can/serving/portion: grams = qty * g_per_unit of the canonical unit return qty * spec.get("g_per_unit", 100) def item_nutrition(name, qty, unit, db): spec = next((db["items"][k] for k in db["items"] if k.lower() == name.lower()), None) if not spec: return {f: 0 for f in FIELDS} grams = to_grams(name, qty, unit, db) factor = grams / spec["g_per_unit"] # g_per_unit == grams per one canonical unit return {f: spec.get(f, 0) * factor for f in FIELDS} def main(): ap = argparse.ArgumentParser() ap.add_argument("--days", type=int, default=0, help="limit to last N days (0 = all)") args = ap.parse_args() entries, db = load() entries = sorted(entries, key=lambda e: e.get("date", "")) if args.days: dates = sorted({e["date"] for e in entries})[-args.days:] entries = [e for e in entries if e["date"] in dates] by_day = {} for e in entries: by_day.setdefault(e["date"], []).append(e) grand = {f: 0 for f in FIELDS} day_proteins = [] print("=" * 78) print("MEAL-HISTORY HEALTH REVIEW (estimated; condiments/oil not logged => +~80-120 kcal/meal)") print("=" * 78) for d in sorted(by_day): day_tot = {f: 0 for f in FIELDS} veg = set() print(f"\n--- {d} ({by_day[d][0].get('weekday','?')}) ---") for e in by_day[d]: mtot = {f: 0 for f in FIELDS} for it in e.get("items", []): n = item_nutrition(it["name"], it["qty"], it.get("unit", "count"), db) for f in FIELDS: mtot[f] += n[f] if it["name"] in db.get("veg_names", []): veg.add(it["name"]) for f in FIELDS: day_tot[f] += mtot[f] print(f" {e['meal']:6} {e['name']}") print(f" ~{mtot['kcal']:.0f} kcal | {mtot['protein']:.0f} g protein | " f"fibre {mtot['fibre']:.0f} | satfat {mtot['satfat']:.0f} | " f"vitC {mtot['vitC']:.0f} | vitA {mtot['vitA']:.0f} | Ca {mtot['calcium']:.0f}") for f in FIELDS: grand[f] += day_tot[f] day_proteins.append(day_tot["protein"]) t = db["targets"] p_ok = "OK" if day_tot["protein"] >= t["protein_per_day_g"] else "LOW" sf = "HIGH" if day_tot["satfat"] > t["satfat_per_day_g"] else "ok" print(f" DAY TOTAL ~{day_tot['kcal']:.0f} kcal | protein {day_tot['protein']:.0f} g [{p_ok}] | " f"fibre {day_tot['fibre']:.0f} | satfat {day_tot['satfat']:.0f} [{sf}] | " f"veg types {len(veg)}") n = len(by_day) or 1 t = db["targets"] print("\n" + "=" * 78) print(f"WINDOW AVERAGE/DAY (over {len(by_day)} day(s)):") print(f" kcal ~{grand['kcal']/n:.0f}") print(f" protein {grand['protein']/n:.0f} g (target >= {t['protein_per_day_g']}) " f"-> days meeting: {sum(1 for p in day_proteins if p >= t['protein_per_day_g'])}/{len(day_proteins)}") print(f" fibre {grand['fibre']/n:.0f} g (target ~{t['fibre_per_day_g']})") print(f" satfat {grand['satfat']/n:.0f} g (target <= {t['satfat_per_day_g']})") print(f" vitC {grand['vitC']/n:.0f} mg (RDA ~{t['vitC_mg']})") print(f" vitA {grand['vitA']/n:.0f} mcg (RDA ~{t['vitA_mcg']})") print(f" calcium {grand['calcium']/n:.0f} mg (RDA ~{t['calcium_mg']})") print("=" * 78) print("Not medical advice — defer to the user's clinical/dietitian team.") if __name__ == "__main__": main()