126 lines
4.8 KiB
Dart
126 lines
4.8 KiB
Dart
import 'package:get/get.dart';
|
|
import '../models/gps_coordinate.dart';
|
|
import '../models/workout.dart';
|
|
import '../models/workout_segment.dart';
|
|
import '../utils/geo_utils.dart';
|
|
|
|
/// خدمة التحليل الذكي (Smart Analytics Service)
|
|
/// مسؤولة عن تقسيم المسار إلى أجزاء، كشف التوقفات، وتحليل أنماط الأداء.
|
|
class AnalyticsService extends GetxService {
|
|
/// تحليل المسار وتقسيمه إلى أجزاء (Segments)
|
|
/// يعتمد على تغيرات السرعة، الارتفاع، أو الاتجاه.
|
|
List<WorkoutSegment> analyzeSegments(
|
|
List<GpsCoordinate> coords, WorkoutType type) {
|
|
if (coords.length < 5) return [];
|
|
|
|
List<WorkoutSegment> segments = [];
|
|
int startIdx = 0;
|
|
int segmentCounter = 0;
|
|
|
|
// 1. تحديد مرحلة الإحماء (Warm-up)
|
|
// نعتبر أول 10% من النقاط أو أول 500 متر إحماء إذا كانت السرعة متدرجة
|
|
int warmupEnd = (coords.length * 0.1).toInt().clamp(5, 50);
|
|
segments.add(_createSegment(
|
|
coords, 0, warmupEnd, segmentCounter++, SegmentType.warmup));
|
|
startIdx = warmupEnd;
|
|
|
|
// 2. تحليل النقاط الوسطى لكشف التوقفات أو التسارعات
|
|
for (int i = startIdx + 1; i < coords.length - 5; i++) {
|
|
final current = coords[i];
|
|
// كشف التوقف (Stop detection)
|
|
// إذا كانت السرعة شبه صفرية لمسافة معينة
|
|
if (current.speed != null && current.speed! < 0.3) {
|
|
int stopEnd = i;
|
|
while (stopEnd < coords.length - 1 &&
|
|
(coords[stopEnd].speed ?? 0) < 0.3 &&
|
|
stopEnd - i < 20) {
|
|
// توقف بحد أقصى 20 نقطة لتجنب المقاطع الضخمة
|
|
stopEnd++;
|
|
}
|
|
|
|
if (stopEnd - i > 3) {
|
|
// توقف حقيقي لأكثر من 10 ثواني تقريباً
|
|
// إغلاق المقطع السابق إذا كان هناك مسافة
|
|
if (i > startIdx) {
|
|
segments.add(_createSegment(
|
|
coords, startIdx, i, segmentCounter++, SegmentType.main));
|
|
}
|
|
segments.add(_createSegment(
|
|
coords, i, stopEnd, segmentCounter++, SegmentType.stop));
|
|
i = stopEnd;
|
|
startIdx = i;
|
|
}
|
|
}
|
|
|
|
// كشف الميول (Elevation detection)
|
|
// يمكن التوسع هنا لكشف uphill/downhill
|
|
}
|
|
|
|
// 3. تحديد مرحلة التبريد (Cool-down)
|
|
int cooldownStart = (coords.length * 0.9).toInt();
|
|
if (cooldownStart > startIdx) {
|
|
segments.add(_createSegment(
|
|
coords, startIdx, cooldownStart, segmentCounter++, SegmentType.main));
|
|
segments.add(_createSegment(coords, cooldownStart, coords.length - 1,
|
|
segmentCounter++, SegmentType.cooldown));
|
|
} else {
|
|
segments.add(_createSegment(coords, startIdx, coords.length - 1,
|
|
segmentCounter++, SegmentType.main));
|
|
}
|
|
|
|
return segments;
|
|
}
|
|
|
|
WorkoutSegment _createSegment(List<GpsCoordinate> coords, int start, int end,
|
|
int index, SegmentType type) {
|
|
double dist = 0;
|
|
double maxSpd = 0;
|
|
double elevChange = 0;
|
|
|
|
for (int i = start + 1; i <= end; i++) {
|
|
dist += GeoUtils.haversineDistance(coords[i - 1].latitude,
|
|
coords[i - 1].longitude, coords[i].latitude, coords[i].longitude);
|
|
double s = coords[i].speed ?? 0;
|
|
if (s > maxSpd) maxSpd = s;
|
|
|
|
if (coords[i].altitude != null && coords[i - 1].altitude != null) {
|
|
elevChange += (coords[i].altitude! - coords[i - 1].altitude!);
|
|
}
|
|
}
|
|
|
|
int duration =
|
|
coords[end].timestamp.difference(coords[start].timestamp).inSeconds;
|
|
|
|
return WorkoutSegment(
|
|
segmentIndex: index,
|
|
type: type,
|
|
distanceMeters: dist,
|
|
durationSeconds: duration,
|
|
avgSpeedMps: duration > 0 ? dist / duration : 0,
|
|
maxSpeedMps: maxSpd,
|
|
elevationChange: elevChange,
|
|
startCoordIndex: start,
|
|
endCoordIndex: end,
|
|
);
|
|
}
|
|
|
|
/// حساب "التقارير الذكية"
|
|
/// مثال: "سرعتك في أول 2 كم تحسنت بنسبة 12%"
|
|
String generateInsight(List<Workout> history) {
|
|
if (history.length < 2) return "ابدأ تمرينك الأول للحصول على تحليلات!";
|
|
|
|
// منطق بسيط للمقارنة بين آخر تمرينين
|
|
final latest = history[0];
|
|
final previous = history[1];
|
|
|
|
if (latest.avgSpeedMps > previous.avgSpeedMps && previous.avgSpeedMps > 0) {
|
|
double diff =
|
|
((latest.avgSpeedMps - previous.avgSpeedMps) / previous.avgSpeedMps) *
|
|
100;
|
|
return "أداء رائع! سرعتك المتوسطة تحسنت بنسبة ${diff.toStringAsFixed(1)}% مقارنة بآخر مرة.";
|
|
}
|
|
|
|
return "استمر في التدريب! الاستمرارية هي مفتاح التطور.";
|
|
}
|
|
}
|