Initial: Mesh-Skills, Configs, Scripts
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# 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. <Sportart> — YYYY-MM-DD`, `Time Window: startTimestamp=.. | endTimestamp=..`, `Duration`, `Avg HR: <n> bpm`, `Calories: <n> kcal`, `Distance: <x> km`, `LabelId: <Zahl>`, `SportType: <code>`.
|
||||
|
||||
## 🔥 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-<ts>`.
|
||||
Reference in New Issue
Block a user