---
name: picture-to-json
description: Nutze diesen Skill, wenn der User den Batch-Run für seine Gym-Fotos triggert (z. B. "gym batch", "analysiere gym", "verarbeite Trainingsbilder"). Liest alle Fotos im Input-Ordner der NAS, extrahiert via Vision Gerät/Gewicht/Reps pro Foto, schreibt eine JSON-Datei pro Bild in raw_json/{Jahr}/{Monat}/{Tag}/, verschiebt das Foto ins archive/ und baut am Ende CSV + HTML Dashboard neu.
---

# Gym-Logging Skill — Batch-Modus

Batch-Verarbeitung aller Fotos im Input-Ordner. Kein User-Text, keine Mobile-Nutzung — der Lauf wird auf dem Mac getriggert, Claude arbeitet die Bilder nacheinander ab und räumt auf.

## Voraussetzung

SMB-Mount muss aktiv sein. Erst prüfen:

```bash
python3 skills/picture_to_json/gym_log.py --check-mount
```

Wenn Exit-Code nicht 0: **abbrechen** und dem User sagen, er soll seine NAS per SMB mounten. Nicht selbst mounten.

## Pfade

Alle NAS-Pfade sind relativ zu `GYM_DIR` (konfigurierbar über Env-Var `GYM_NAS_ROOT`, siehe `gym_log.py`):

| Rolle | Relativer Pfad |
|---|---|
| Input | `progress/bilder/` |
| Per-Foto-JSON | `progress/raw_json/{YYYY}/{MM}/{DD}/` |
| Archiv (nach Analyse) | `archive/bilder/{YYYY}/{MM}/{DD}/` |
| CSV + HTML Dashboard | `progress/summary/` |
| Schema | `skills/picture_to_json/example.json` |
| Referenzfoto (annotiert) | `skills/picture_to_json/example_photos/highlighted/PXL_20260410_100901494.jpg` |

Datum der Ordnerablage wird aus dem Dateinamen abgeleitet, nicht aus `datetime.now()`. Unterstützte Namenskonventionen:
- `PXL_YYYYMMDD_HHMMSS…` (Google Pixel Kamera)
- `YYYY-MM-DD HH.MM.SS…` oder `YYYY-MM-DD_HH.MM.SS…` (QNAP Mobile App)

Fallback bei unbekanntem Namen: File-`mtime`. Der Python-Helper übernimmt das selbst — du musst es nicht selbst parsen.

## Ablauf

1. **Mount prüfen.** Siehe oben. Bei Fehler abbrechen.

2. **Schema laden.** Lies `skills/picture_to_json/example.json`. Die Keys sind das Zielschema für jeden neuen Eintrag. Wenn sich dieses File ändert, passt sich dein Output automatisch an.

3. **Referenz ansehen.** Öffne `skills/picture_to_json/example_photos/highlighted/PXL_20260410_100901494.jpg`. Die roten Markierungen zeigen die drei relevanten Zonen auf Matrix-Ultra-Geräten: **Label** (oben), **Doppeldisplay** (rechts neben Label, zeigt Reps), **Gewichts-Splint** (unten am Stack).

4. **Dateien listen.** Lies den Input-Ordner `progress/bilder/` auf der NAS. Nimm nur Bilddateien (`.jpg`, `.jpeg`, `.png`, `.heic`, `.heif`, `.webp`, auch mit `.MP.` im Namen). Ignoriere Ordner, Punkt-Dateien (`.DS_Store`), und bereits archivierte Dateien (sollte keine geben).

5. **Wenn keine Fotos im Ordner:** Dem User melden und fragen, ob er stattdessen nur Summary neu bauen will (`python3 skills/picture_to_json/gym_log.py --rebuild-summary`). Fertig.

6. **Pro Foto:** Für jedes Bild folgende Sequenz durchlaufen:

   6a. **Foto als Overview ansehen.** Lies das Bild aus dem Input-Ordner. Nur kurz, um das Gerät grob einzuordnen — nicht den Stack aus dem Weitwinkel lesen.

   6a.1. **Zwei Crops erzeugen** über den Helper — Weitwinkelaufnahmen sind für das Lesen der kleinen Details zu grob. Der Crop-Schritt ist nicht optional, er ist der Kern der Präzision:

   ```bash
   python3 skills/picture_to_json/gym_log.py --crop "<photo-pfad>" --region label
   python3 skills/picture_to_json/gym_log.py --crop "<photo-pfad>" --region stack
   ```

   Output landet unter `/tmp/gym_crops/<stem>_label.jpg` und `/tmp/gym_crops/<stem>_stack.jpg`. Lies beide Crops mit dem Read-Tool — der `label`-Crop zeigt das Matrix-Label + das digitale Doppeldisplay groß, der `stack`-Crop zeigt den Spalt mit Pin und dual beschriftetem Stack (lbs links, kg rechts).

   Wenn der `stack`-Crop die Öse nicht klar zeigt, hole dir zusätzlich den engeren `stack_bottom`-Crop:

   ```bash
   python3 skills/picture_to_json/gym_log.py --crop "<photo-pfad>" --region stack_bottom
   ```

   Falls die Presets nicht passen (Gerät nicht zentriert, anderer Winkel), nutze Custom-Koordinaten:
   ```bash
   python3 skills/picture_to_json/gym_log.py --crop "<photo>" --x 0.35 --y 0.40 --w 0.30 --h 0.45
   ```

   6b. **Extraktion** mit dieser Anleitung (primär aus den Crops, Original nur zur Orientierung):

   > Analysiere das Foto eines Matrix-Ultra-Kraftgeräts und extrahiere:
   >
   > - **`machine`** (string) — Gerätelabel oben am Gerät, flach als "Matrix Ultra \<Typ\>" (z. B. `"Matrix Ultra Schulterpresse"`, `"Matrix Ultra Dependent Curl"`, `"Matrix Ultra Rotary Torso"`, `"Matrix Ultra Trizepsmaschine"`, `"Matrix Ultra Beinpresse"`). Label steht in orange/weiß auf dunklem Grund, oben auf dem Gerät. Falls Hersteller/Serie nicht sicher erkennbar, nutze nur den Gerätetyp.
   >
   > - **`weight_kg`** (number, 1 Nachkommastelle) — Eingestelltes Gewicht. **Anatomie des Matrix-Ultra-Stacks:**
   >   - Die vordere Verkleidung des Gewichtsstacks hat einen **langen vertikalen Spalt** (schmaler Durchblick über die ganze Höhe).
   >   - Im Spalt sitzt eine **Öse** — das ist der eigentliche Pin, markiert das aktuelle Hauptgewicht.
   >   - **Orientierung:** Stack-Beschriftung geht von OBEN nach UNTEN mit STEIGENDEN Zahlen. Also **20 lbs oben, 200 lbs unten**. NICHT umgekehrt.
   >   - **Links vom Spalt** stehen die **lbs-Werte** auf den Platten (20, 30, 40, …, 200).
   >   - **Rechts vom Spalt** stehen die **kg-Werte** auf denselben Platten (ganzzahlig gerundet: 9, 14, 18, 23, 27, 32, 36, 41, 45, 50, 54, 59, 63, 68, 72, 77, 82, 86, 91).
   >   - **Oben am Stack** befindet sich zusätzlich ein **Inkremental-Knauf** (drehbarer kleiner Dial), der Zusatzgewicht in 2.5/5/7.5 lbs hinzufügt. Wenn er sichtbar und ablesbar ist: Wert dazuaddieren.
   >
   >   **Extraktions-Reihenfolge (arbeite mit dem `stack`-Crop, nicht mit dem Original):**
   >   1. Finde den vertikalen Spalt im Crop. Ignoriere den Rest.
   >   2. Lokalisiere die Öse (Pin) im Spalt. Orientier dich dabei an der Zahlenreihe um sie herum, nicht an der relativen Höhe.
   >   3. Lies die **lbs-Zahl links** direkt neben der Öse. Das ist der primäre Wert.
   >   4. Lies die **kg-Zahl rechts** als Sanity-Check. lbs und kg müssen zur selben Platte gehören (z. B. 50 lbs neben 23 kg).
   >   5. Falls Add-on-Knauf oben einen Wert > 0 zeigt: lbs-Wert + Add-on addieren, bevor du konvertierst.
   >   6. **Snap** nur falls nötig auf nächsten 10-lbs-Schritt {20, 30, …, 200}. Wenn du die Zahl direkt lesen kannst, snap nicht — sie ist eh auf dem Gitter.
   >   7. × 0.4536 → kg, auf 1 Nachkommastelle runden.
   >   8. **Niemals** die Mitte des Stacks raten. Wenn der Crop die Öse nicht zeigt, hole dir `stack_bottom` oder einen Custom-Crop.
   >
   > - **`reps`** (integer) — Zahl im digitalen Doppeldisplay neben dem Label. Lies sie aus dem **`label`-Crop**, nicht aus dem Originalfoto. Reps können einstellig sein (z. B. 5, 8), zweistellig (z. B. 10, 12, 15) oder höher. **Rate nicht standardmäßig 10** — lies die tatsächliche Zahl oder setze confidence niedrig.
   >
   > - **`sets`** (integer) — Falls im Display sichtbar: ablesen. Sonst Default `3` (User-Konvention: immer 3 Sätze).
   >
   > - **`exercise`** (null) — Im Batch-Modus immer `null`. User ergänzt nachträglich manuell, falls nötig.
   >
   > - **`notes`** (null) — Im Batch-Modus immer `null`.
   >
   > - **`confidence`** (number 0.0–1.0) — Deine Selbsteinschätzung, gemittelt über alle Vision-Felder. Niedrig wenn: unscharf, schlechter Winkel, Label verdeckt, Display nicht lesbar.
   >
   > - **`timestamp`** (string) — ISO 8601 mit lokalem Offset, extrahiert aus dem Dateinamen:
   >   - `PXL_20260410_094330…` → `"2026-04-10T09:43:30+02:00"`
   >   - `2026-04-10 09.43.30…` → `"2026-04-10T09:43:30+02:00"`
   >   - Bei unbekanntem Namen: aktuelle lokale Zeit als Fallback

   6c. **Confidence-Check.** Wenn confidence < 0.4: Schreibe das Bild **nicht** zum Helper, sondern sammle es in einer Liste "unsichere Fotos" und bearbeite es am Ende des Runs mit dem User (siehe Schritt 8).

   6d. **Helper aufrufen** (für Fotos mit confidence ≥ 0.4):

   ```bash
   python3 skills/picture_to_json/gym_log.py \
     --photo "<input-pfad>/<dateiname>" \
     --json '<json-string>'
   ```

   Der Helper validiert gegen `example.json`, schreibt die JSON-Datei nach `raw_json/{Y}/{M}/{D}/<stem>.json` und verschiebt das Foto nach `archive/bilder/{Y}/{M}/{D}/<dateiname>`.

   Exit-Codes:
   - `0` — OK, Output zeigt Ziel-Pfade
   - `1` — NAS-Mount weg (sollte vorher nicht passieren, sonst Run abbrechen)
   - `2` — Schema-Verletzung (dein JSON stimmt nicht, korrigiere das eine Foto und retry)
   - `3` — I/O-Fehler (Foto nicht gefunden, etc.)

   Wenn ein einzelnes Foto fehlschlägt: Run nicht abbrechen, Fehler merken, nächstes Foto.

7. **Summary neu bauen** nach dem Loop (nur einmal, egal wie viele Fotos):

   ```bash
   python3 skills/picture_to_json/gym_log.py --rebuild-summary
   ```

   Das regeneriert `summary/workouts.csv` und `summary/dashboard.html` komplett aus `raw_json/`.

8. **Unsichere Fotos abklären** (nur falls welche in Schritt 6c gesammelt).

   Für jedes unsichere Foto: dem User die vermuteten Werte zeigen und fragen:

   > "Unsicher bei `<dateiname>`: `<machine>`, `<weight_kg>` kg, `<sets>×<reps>` (Confidence `<c>`). Was soll ich nehmen — bestätigen, korrigieren oder überspringen?"

   Nach User-Antwort: confidence auf 1.0 setzen, zurück zu Schritt 6d. Wenn "überspringen": Foto im Input-Ordner belassen, im Final-Report aufführen.

9. **Final-Report** an den User auf Deutsch, kompakt:

   > ✓ Batch fertig: `N` verarbeitet, `M` übersprungen
   >
   > - 2026-04-10 09:43 · Matrix Ultra Schulterpresse · 31.8 kg · 3×10 (conf 0.87)
   > - 2026-04-10 09:49 · Matrix Ultra Trizepsmaschine · 27.2 kg · 3×12 (conf 0.91)
   > - …
   >
   > Dashboard aktualisiert.

## Fehlerbehandlung

| Output vom Helper | Aktion |
|---|---|
| `ERROR: NAS nicht gemountet` | Run abbrechen, User soll mounten |
| `ERROR: Fehlende Felder` | JSON-Output für dieses Foto korrigieren, retry |
| `ERROR: Unerlaubte Felder` | Extra-Keys entfernen, retry |
| `ERROR: Feld 'X': erwartet Y` | Typ fixen (z. B. `reps` muss `int` sein), retry |
| `ERROR: Foto nicht gefunden` | Pfad stimmt nicht, prüfen und retry |
| `WARN: … war korrupt, Backup:` | Nur informativ, dem User weitergeben |

## Wichtige Regeln

- **Niemals** `datetime.now()` als `timestamp` verwenden. Immer aus dem Dateinamen, Fallback nur bei völlig unbekannten Namen.
- **Niemals** Fotos selbst löschen. Der Helper verschiebt sie sauber ins Archiv.
- **Niemals** mit confidence < 0.4 still durchschreiben.
- **Immer** am Ende Summary neu bauen, auch bei nur einem Foto (damit Dashboard aktuell bleibt).
- `exercise` und `notes` sind im Batch-Modus **immer** `null`.
