tools/: Screenshot-Skript (headless Chromium) + Demo-Generator dauerhaft ablegen
Image bauen und in die Registry schieben / build (push) Successful in 6s

- tools/screenshot.sh: konsolidiert (--view today|history, --size, --out,
  --full); startet eigenen Server mit temporaerem Zustand, befuellt eine
  Session bzw. importiert Demo-Daten, schießt den Screenshot, raeumt auf
- tools/gen_demo.py: Demo-Daten-Generator (Pfade relativ zum Repo)
- tools/README.md: Nutzung
This commit is contained in:
AI-Agent committed 2026-10-05 14:23:46 +00:00
1 parent c7647ba962
commit 314f4f329e
3 files changed
+307

No files matched your search

+179
View File
@@ -0,0 +1,179 @@
#!/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()