diff --git a/src/App.php b/src/App.php index c8dda75..abdef4e 100644 --- a/src/App.php +++ b/src/App.php @@ -338,7 +338,7 @@ final class App sort($dates, SORT_STRING); if ($dates === []) { - throw new RuntimeException('Der Import enthielt keine unterstützten Health-Daten.'); + throw new RuntimeException('Der Import enthielt keine unterstützten Health-Daten. ' . $this->healthPayloadSummary($payload, $metrics, $workouts)); } $totalItems = max(1, $this->countHealthImportItems($metricImport, $workoutImport)); @@ -468,25 +468,107 @@ final class App private function healthMetricsFromPayload(array $payload): array { - return array_values(array_filter( - is_array($payload['metrics'] ?? null) ? $payload['metrics'] : [], - 'is_array' - )); + $metrics = []; + $this->collectHealthMetrics($payload, $metrics); + + return $metrics; } private function healthWorkoutsFromPayload(array $payload): array { + if (is_array($payload['data'] ?? null)) { + $nested = $this->healthWorkoutsFromPayload($payload['data']); + if ($nested !== []) { + return $nested; + } + } + if (is_array($payload['workouts'] ?? null)) { return array_values(array_filter($payload['workouts'], 'is_array')); } + if ($this->looksLikeHealthWorkout($payload)) { + return [$payload]; + } + if (array_is_list($payload) && isset($payload[0]) && is_array($payload[0])) { - return array_values(array_filter($payload, 'is_array')); + return array_values(array_filter($payload, fn (array $item): bool => $this->looksLikeHealthWorkout($item))); } return []; } + private function collectHealthMetrics(array $payload, array &$metrics): void + { + if (is_array($payload['metrics'] ?? null)) { + foreach ($payload['metrics'] as $metric) { + if (is_array($metric)) { + $this->collectHealthMetrics($metric, $metrics); + } + } + } + + if (is_array($payload['data'] ?? null)) { + $this->collectHealthMetrics($payload['data'], $metrics); + } + + if (array_is_list($payload)) { + foreach ($payload as $item) { + if (is_array($item)) { + $this->collectHealthMetrics($item, $metrics); + } + } + + return; + } + + $name = trim((string) ($payload['name'] ?? ($payload['metric'] ?? ($payload['metricName'] ?? ($payload['type'] ?? ''))))); + if ($name === '') { + return; + } + + $data = is_array($payload['data'] ?? null) + ? $payload['data'] + : (is_array($payload['records'] ?? null) ? $payload['records'] : []); + + if ($data === [] && (isset($payload['qty']) || isset($payload['value']) || isset($payload['date']) || isset($payload['startDate']))) { + $data = [$payload]; + } + + if ($data !== []) { + $metrics[] = ['name' => $name, 'data' => array_values(array_filter($data, 'is_array'))]; + } + } + + private function looksLikeHealthWorkout(array $payload): bool + { + return isset($payload['start'], $payload['duration']) + || isset($payload['startDate'], $payload['duration']) + || isset($payload['workoutActivityType']) + || isset($payload['workoutType']); + } + + private function healthPayloadSummary(array $payload, array $metrics, array $workouts): string + { + $keys = array_slice(array_keys($payload), 0, 8); + $metricNames = []; + foreach (array_slice($metrics, 0, 5) as $metric) { + if (is_array($metric) && trim((string) ($metric['name'] ?? '')) !== '') { + $metricNames[] = trim((string) $metric['name']); + } + } + + $parts = ['Erkannt: ' . count($metrics) . ' Metriken, ' . count($workouts) . ' Workouts']; + if ($keys !== []) { + $parts[] = 'Top-Level: ' . implode(', ', $keys); + } + if ($metricNames !== []) { + $parts[] = 'Metriken: ' . implode(', ', $metricNames); + } + + return implode('. ', $parts) . '.'; + } + private function healthEventsFromMetrics(array $metrics): array { $steps = []; @@ -724,7 +806,7 @@ final class App private function healthSleepHours(array $point): float { - foreach (['totalSleep', 'asleep', 'qty', 'inBed'] as $key) { + foreach (['totalSleep', 'asleep', 'qty', 'value', 'inBed'] as $key) { if (is_numeric($point[$key] ?? null)) { return max(0.0, min(24.0, (float) $point[$key])); }