From 21d819a52b3a7c7e8299b49dedbdb8be3eefb5d7 Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Mon, 3 Aug 2026 16:13:02 +0000 Subject: [PATCH] =?UTF-8?q?feat(watch):=20remonte=20les=20s=C3=A9ances=20d?= =?UTF-8?q?e=20renfo=20pour=20la=20compl=C3=A9tion=20auto=20de=20la=20rout?= =?UTF-8?q?ine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pendant natif de coach_sportif c12caef. À la détection d'un HKWorkout de type renforcement, POST /api/routine/auto-complete avec les faits bruts. Volontairement bête côté natif : aucune règle de correspondance ici. Fenêtre de 12 h depuis l'envoi explicite, tolérance de durée, choix du bloc — tout vit côté serveur et reste donc ajustable sans rebuild iOS. NON COMPILÉ — à inclure au prochain build Xcode. Co-Authored-By: Claude Opus 5 --- ios/App/App/CoachWorkoutObserver.swift | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/ios/App/App/CoachWorkoutObserver.swift b/ios/App/App/CoachWorkoutObserver.swift index 279b3c8..2601dce 100644 --- a/ios/App/App/CoachWorkoutObserver.swift +++ b/ios/App/App/CoachWorkoutObserver.swift @@ -335,7 +335,48 @@ public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin { } } + /// Remonte au serveur la fin d'une séance de renfo, qui décide seul si elle + /// correspond à une routine poussée dans l'app Exercice. + /// + /// Volontairement bête : on n'envoie que des faits bruts (type, durée). Les + /// règles de correspondance — fenêtre de 12 h depuis l'envoi explicite, + /// tolérance de durée — vivent dans `/api/routine/auto-complete`, donc + /// ajustables sans rebuild iOS. + private func reportToRoutineIfStrength(_ workout: HKWorkout) { + let activity = workout.workoutActivityType + guard activity == .functionalStrengthTraining + || activity == .traditionalStrengthTraining else { return } + + guard let url = URL(string: "https://coach.hypnotruck.ch/api/routine/auto-complete") else { return } + var req = URLRequest(url: url) + req.httpMethod = "POST" + req.setValue("application/json", forHTTPHeaderField: "Content-Type") + req.setValue("coach_web_token=\(CoachAuth.kCoachWebToken)", forHTTPHeaderField: "Cookie") + req.httpBody = try? JSONSerialization.data(withJSONObject: [ + "activity": "functionalStrengthTraining", + "duration_min": workout.duration / 60, + ]) + req.timeoutInterval = 15 + + URLSession.shared.dataTask(with: req) { data, _, error in + if let error = error { + NSLog("[CoachWorkoutObserver] auto-complete routine: %@", error.localizedDescription) + return + } + guard let data = data, + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } + if (json["matched"] as? Bool) == true { + let n = (json["done"] as? [String])?.count ?? 0 + NSLog("[CoachWorkoutObserver] routine '%@' cochée automatiquement (%d exercices)", + (json["moment"] as? String) ?? "?", n) + } else { + NSLog("[CoachWorkoutObserver] pas une routine (%@)", (json["reason"] as? String) ?? "?") + } + }.resume() + } + private func scheduleNotification(for workout: HKWorkout) { + reportToRoutineIfStrength(workout) let durationMin = Int(workout.duration / 60) var bodyParts: [String] = ["\(durationMin) min"] if let kcal = workout.totalEnergyBurned?.doubleValue(for: .kilocalorie()), kcal > 0 {