#!/usr/bin/env python3 """Erzeugt demo-data.json (trainingsplan-export-v2) mit 12 Wochen realistischem Verlauf: Plateau-Phasen, Deload-Woche, ausgelassene Tage, Reps-Streuung, gelegentlich nicht abgehakte Saetze und Notizen - statt glattgebuegelter Steigerung.""" import json import os import random from datetime import date, timedelta BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) CFG = json.load(open(f"{BASE}/config.json", encoding="utf-8")) gym = next(a for a in CFG["activities"] if a["id"] == "gym") rng = random.Random(42) WEEK_COUNT = 12 WEEKS = ["Push", "Pull", "Legs"] def rep_value(target): if not target: return "10" lo = int(target.split("-")[0]) hi = int(target.split("-")[1]) if "-" in target else lo return (lo + hi) // 2 def week_dates(): """Montag/Mittwoch/Freitag; die letzte Woche endet VOR heute (alles Vergangenheit).""" today = date.today() last_mon = today - timedelta(days=(today.weekday() % 7) + 7) out = [] for week in range(WEEK_COUNT): base = last_mon - timedelta(weeks=WEEK_COUNT - 1 - week) out.append([base, base + timedelta(days=2), base + timedelta(days=4)]) return out def set_weight(base, session_idx, week_idx): """Realistische Gewichtskurve: Plateau-Phasen + Deload in Woche 8.""" w = base progress = session_idx // 2 # alle zwei Sessions +1 Stufe # Uebung-spezifische Rate: grosse Lifts 2.5er-Schritte, kleine 1.25er step = 2.5 if base >= 20 else 1.25 w += step * progress # Plateau: bei manchen Sessions bleibt das Gewicht trotz Fortschritt if rng.random() < 0.18: w -= step # Deload-Woche (Woche 8, idx 7): -12% if week_idx == 7: w = round(w * 0.88 / step) * step return max(0, round(w / step) * step) def build_gym_session(sid, d, day_key, day_cfg, session_idx, week_idx, note): entries = [] for ex in day_cfg["exercises"]: base = float(ex.get("startKg") or 0) w = set_weight(base, session_idx, week_idx) cfg = ex["sets"] count = len(cfg) if isinstance(cfg, list) else int(cfg) if week_idx == 7: count = max(1, count - 1) # Deload: ein Satz weniger reps_t = rep_value(ex.get("reps") or "") for i in range(count): reps = max(4, reps_t + rng.choice([-2, -1, 0, 0, 0, 1])) done = 1 weight = f"{w:g}" # Letzter Satz haengt mal (weniger Reps), selten ein Satz ausgelassen if i == count - 1 and rng.random() < 0.15: reps = max(3, reps - 2) if rng.random() < 0.05: done = 0 entries.append({ "exercise": f"{day_key}/{ex['name']}", "sort": i, "weight": weight, "reps": str(reps), "done": done, }) return { "id": sid, "activity": "gym", "day": day_key, "date": d.isoformat(), "start_time": rng.choice(["17:45", "18:00", "18:15", "19:00"]), "end_time": rng.choice(["18:50", "19:10", "19:20", "20:05"]), "note": note, "fields": {}, "status": "done", "entries": entries, } def build_run(sid, d, idx): km = round(rng.uniform(4.0, 7.2), 1) pace = rng.uniform(5.9, 7.1) # min/km pm, ps = int(pace), int(round((pace - int(pace)) * 60)) minutes = round(km * pace) return { "id": sid, "activity": "run", "day": "", "date": d.isoformat(), "start_time": rng.choice(["07:00", "07:15", "18:30"]), "end_time": "08:00", "note": rng.choice(["", "", "Lockerer Lauf", "Tempo-Runde", "Regen"]), "fields": { "distanceKm": f"{km:.1f}", "minutes": str(minutes), "pacePerKm": f"{pm}:{ps:02d}", "heartRate": str(rng.randint(140, 166)), "elevation": str(rng.randint(10, 55)), }, "status": "done", "entries": [], } def build_ride(sid, d, idx): km = rng.randint(14, 30) speed = rng.uniform(20.5, 25.5) minutes = round(km / speed * 60) return { "id": sid, "activity": "ride", "day": "", "date": d.isoformat(), "start_time": "09:00", "end_time": "10:00", "note": rng.choice(["", "Ausfahrt", "Mit Gegenwind"]), "fields": { "distanceKm": str(km), "minutes": str(minutes), "avgSpeed": f"{speed:.1f}", "heartRate": str(rng.randint(132, 158)), "elevation": str(rng.randint(45, 130)), }, "status": "done", "entries": [], } def main(): dates = week_dates() days = {d["key"]: d for d in gym["cycle"]["days"]} sessions = [] sid = 1 notes = { "Push": ["", "", "Brust fiel schwer", "Fokus Form, Kontrolle", ""], "Pull": ["", "Ruecken verkrampft", "", "Zugkraft gut", ""], "Legs": ["", "", "Deload: leichter", "Beine schwer, trotzdem durch", ""], } skipped = {(2, 0), (5, 2), (7, 0), (10, 1)} # Woche, Tag-Index (Mo/Di/Fr=0/1/2) for week in range(WEEK_COUNT): for di, day_key in enumerate(WEEKS): if (week, di) in skipped: continue d = dates[week][di] note = rng.choice(notes[day_key]) sid_s = 0 for s in sessions: if s["day"] == day_key: sid_s += 1 sessions.append(build_gym_session(sid, d, day_key, days[day_key], sid_s, week, note)) sid += 1 # Cardio: Mo abends laufen, So manchmal Fahrt sessions.append(build_run(sid, dates[week][0] + timedelta(days=0), week)) sid += 1 if rng.random() < 0.3: sessions.append(build_ride(sid, dates[week][2] + timedelta(days=2), week)) sid += 1 doc = { "format": "trainingsplan-export-v2", "exported_at": date.today().isoformat() + "T00:00:00", "config": CFG, "sessions": sessions, } out = f"{BASE}/demo-data.json" with open(out, "w", encoding="utf-8") as f: json.dump(doc, f, ensure_ascii=False, indent=1) acts = {} for s in sessions: acts[s["activity"]] = acts.get(s["activity"], 0) + 1 print(f"{out}: {len(sessions)} Sessions, {acts}") print("Zeitraum:", min(s["date"] for s in sessions), "bis", max(s["date"] for s in sessions)) notes_n = sum(1 for s in sessions if s["note"]) undone = sum(1 for s in sessions for e in s["entries"] if not e["done"]) print("mit Notiz:", notes_n, "| nicht abgehakte Saetze:", undone) if __name__ == "__main__": main()