# COROS-MCP — Historie/Backfill + Parser-Pitfalls (verifiziert 2026-08-18) Ergänzt zu `coros-mcp-data-shapes.md`. Alle Befunde sind reale `call-tool`-Antworten, nicht geraten. ## Health-Historie: days-Parameter + Server-Limit `queryDailyHealthData` akzeptiert im Schema einen `days`-Parameter (integer, default 7). ```bash coros-mcp call-tool --tool queryDailyHealthData --arguments-json '{"days":30}' coros-mcp call-tool --tool queryDailyHealthData --arguments-json '{"days":365}' ``` **WICHTIG (verifiziert 2026-08-18):** Egal ob `days:30`, `days:90` oder `days:365` — die COROS-API liefert **maximal ~53 Health-Tage** zurück (z.B. nur `2026-05-16 .. 2026-08-15`, 53 Blöcke). Das ist ein **SERVER-Limit von COROS**, kein Parser- oder Client-Fehler. Mehr Health-Historie gibt es über diese API nicht; für längere Geschichte bleibt nur der Sport/FIT-Pfad. Auch bei "alles was geht" lohnt kein Versuch über 53 hinaus zu holen — wiederhole `sportBackfill` dafür nicht. ## Sport-Historie: querySportRecords mit Datumsbereich `querySportRecords` akzeptiert (anders als Health) **`startDate`/`endDate` im Format `yyyyMMdd`** (kein `days`) und `sportTypeCodes`-Array: ```bash coros-mcp call-tool --tool querySportRecords --arguments-json \ '{"startDate":"20250501","endDate":"20260815","sportTypeCodes":[65535]}' ``` - `sportTypeCodes: [65535]` = alle Sportarten (sonst numerische Codes, nie Namen). - **Sport-Historie reicht deutlich weiter zurück als Health** (1 Jahr+). 20 Records über 15 Monate bestätigt. - Antwort enthält je Workout: `N. — YYYY-MM-DD`, `Time Window: startTimestamp=.. | endTimestamp=..`, `Duration`, `Avg HR: bpm`, `Calories: kcal`, `Distance: km`, `LabelId: `, `SportType: `. ## 🔥 PITFALL: COROS-`content[0].text` enthält LITERALES `\n`, KEINE echten Newlines (Wichtigster Fund — ohne Normalisierung matcht **nichts**.) `querySportRecords` (und teils andere) liefern im Text **literale 2-Zeichen-Sequenz `\n` (ein Backslash + n)**, keine echten Zeilenumbrüche. `splitlines()` / `repr()` in einem Launch zeigen das nicht zuverlässig. **Debug-Befund (2026-08-18):** ```python text = d['content'][0]['text'] '\n' in text # False (kein echter Newline) '\\n' in text # True (literales Backslash-n vorhanden) ``` **Fix — normalisiere VOR jedem Zeilen-Parsing:** ```python text = text.replace('\\n', '\n') lines = text.splitlines() # erst jetzt funktioniert splitlines() ``` Übersehen → alle Regex gegen `splitlines()` finden 0 Zeilen (kein Fehler, nur 0 Treffer). Achte auf die richtige Backslash-Tiefe: Ersetze das **1-Backslash-`\n`-Literal**, nicht doppel. ## ⏱️ Dauer-Format ist uneinheitlich: HH:MM:SS vs MM:SS vs SS COROS-`Duration:`-Werte kommen **gemischt**: - `1:00:14` → 1h = **60 min** (3 Segmente) - `2:03:36` → 2h03m = **123 min** (3 Segmente) - `51:35` → **51 min** (2 Segmente — NICHT 51std35!) - `0:22` → 22 Sek → 0 min (2 Segmente) **Fehlerhafte Regex** `(\d+):(\d+)(?::(\d+))?` macht `51:35` fälschlich zu `51*60+35 = 3095 min` (absurd 63h). **Robust:** ```python m = re.search(r'Duration:\s*([\d:]+)', block) seg = [int(x) for x in m.group(1).split(':')] if len(seg) == 3: # HH:MM:SS dauer_min = seg[0]*60 + seg[1] elif len(seg) == 2: # MM:SS dauer_min = seg[0] else: # SS dauer_min = seg[0] // 60 ``` ## Sport-Daten → SEPARATE Tabelle (nicht health_records) Ein Tag kann **mehrere Workouts** haben → passt NICHT in `health_records` (1 Zeile/Tag). Eigene Tabelle: ```sql CREATE TABLE IF NOT EXISTS sport_records ( label_id TEXT PRIMARY KEY, -- COROS-LabelId je Workout (natürlicher PK) datum TEXT NOT NULL, -- yyyyMMdd sportart TEXT NOT NULL, sport_type_code INTEGER, -- COROS SportType-Code dauer_min INTEGER, avg_hr INTEGER, kalorien INTEGER, distanz_km REAL, created_at TEXT DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_sport_datum ON sport_records(datum); ``` **Idempotent:** `INSERT OR IGNORE ... ON CONFLICT(label_id) DO NOTHING` — Workouts sind nach Import unveränderlich, kein UPDATE nötig. (Direkter DB-Write, da `health_records`-API createRule leer = nur Superuser schreibt; siehe `pocketbase-deployment`.) ## Reihenfolge bei Backfill in bestehende DB 1. Wenn schon fehlerhafte Records mit dem alten Dauer-Parser geschrieben wurden → Flushing die Tabelle (`DELETE FROM sport_records`) oder per label_id neu überschreiben, DANN Neu-Load mit korrektem Parser. 2. Immer vorher DB-Backup (Backup-Regel): `cp /opt/pocketbase/pb_data/data.db data.db.bak-`.