From b15867273cc5593c65047fe4e2ec3f16d1d281be Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Tue, 22 Sep 2026 19:32:40 +0000 Subject: [PATCH] =?UTF-8?q?Le=20d=C3=A9tail=20natif=20expose=20les=20activ?= =?UTF-8?q?it=C3=A9s=20HealthKit=20de=20la=20s=C3=A9ance,=20en=20lecture?= =?UTF-8?q?=20seule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit À la demande explicite de Sylvain (22/09), après la perte des pas fins côté montre. Apple documente que les HKWorkoutActivity peuvent « represent the active and rest periods during interval training », mais ne dit pas que l'app Exercice en écrit une par étape d'un exercice personnalisé. Ce champ `activities` (début, fin, durée, type, nombre d'événements, métadonnées en clair) permet de le constater dans un brut. Le serveur ne s'en sert pas : rien n'est stocké ni affiché tant que la donnée n'a pas été vue. Syntaxe vérifiée (swiftc -parse), build Xcode à faire sur le Mac mini. Co-Authored-By: Claude Fable 5.1 --- ios/App/App/CoachHealthRoute.swift | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ios/App/App/CoachHealthRoute.swift b/ios/App/App/CoachHealthRoute.swift index 31ee899..aedac4a 100644 --- a/ios/App/App/CoachHealthRoute.swift +++ b/ios/App/App/CoachHealthRoute.swift @@ -423,6 +423,38 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { out["average_mets"] = (mets.doubleValue(for: unit) * 10).rounded() / 10 } + // Activités de la séance — lecture seule, pour VOIR ce qu'Exercice écrit. + // Apple : « you can use workout activities to […] represent the active + // and rest periods during interval training. All HKWorkout instances + // have at least one associated HKWorkoutActivity » (doc HKWorkoutActivity, + // lue le 22/09/2026). Apple ne dit PAS que l'app Exercice en écrit une + // par étape d'un exercice personnalisé : c'est ce que ce champ permet + // de constater. Le serveur ne s'en sert pas tant que ce n'est pas vu. + if #available(iOS 16.0, *) { + let iso = ISO8601DateFormatter() + iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + var activities: [[String: Any]] = [] + for a in workout.workoutActivities { + var row: [String: Any] = [ + "start": iso.string(from: a.startDate), + "duration_s": (a.duration * 10).rounded() / 10, + "activity_type": Int(a.workoutConfiguration.activityType.rawValue), + "location": Int(a.workoutConfiguration.locationType.rawValue), + "events": a.workoutEvents.count, + ] + if let end = a.endDate { row["end"] = iso.string(from: end) } + // Métadonnées en clair uniquement (String/Number/Bool) : une + // HKQuantity ne se sérialise pas, et on ne veut rien deviner. + var plain: [String: Any] = [:] + for (k, v) in a.metadata ?? [:] { + if v is String || v is NSNumber { plain[k] = v } + } + if !plain.isEmpty { row["metadata"] = plain } + activities.append(row) + } + out["activities"] = activities + } + // Statistiques agrégées sur la fenêtre exacte de la séance. let samplePredicate = HKQuery.predicateForObjects(from: workout) var requests: [(key: String, id: HKQuantityTypeIdentifier, unit: HKUnit, option: HKStatisticsOptions, scale: Double)] = []