Workout Analytics Service
The Workout Analytics Service provides comprehensive performance analysis and insights for OpenLift users, processing workout data to generate meaningful metrics, trends, and performance insights.
Overviewβ
The Workout Analytics Service handles:
- Workout performance metrics calculation and analysis
- Long-term progress trend analysis and visualization
- Volume, intensity, and frequency tracking
- Comparative performance analysis (personal bests, historical comparisons)
- Advanced analytics including periodization analysis
- Data-driven insights for training optimization
Key Featuresβ
π Comprehensive Performance Metricsβ
- Volume Tracking: Total volume, volume per muscle group, volume progression
- Intensity Analysis: Average RPE, intensity distribution, RPE trends
- Frequency Monitoring: Training frequency per muscle group and exercise type
- Density Calculations: Work-to-rest ratios, training density optimization
π Trend Analysis & Visualizationβ
- Progress Tracking: Long-term strength and performance trends
- Periodization Analysis: Training cycle effectiveness and adaptation
- Plateau Detection: Identify stagnation points and breakthrough opportunities
- Comparative Analysis: Performance against personal records and goals
π― Personalized Insightsβ
- Goal-Oriented Metrics: Track progress toward specific fitness goals
- Weakness Identification: Highlight lagging muscle groups or movement patterns
- Training Load Management: Balance training stress and recovery
- Performance Predictions: Forecast potential improvements and timelines
Architectureβ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Client App β β Workout β β Database β
β β β Analytics β β β
β β’ Charts/Graphs ββββββ β’ Metric Calc ββββββ β’ Workout Data β
β β’ Progress View β β β’ Trend Analysisβ β β’ Calculated β
β β’ Insights β β β’ Data Proc β β Metrics β
β β’ Comparisons β β β’ Insights Gen β β β’ Aggregations β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
Service Responsibilitiesβ
β Workout Analytics Service Handlesβ
- Workout data processing and metrics calculation
- Performance trend analysis and statistical calculations
- Data aggregation and summarization
- Progress visualization data preparation
- Comparative analysis (personal bests, goal tracking)
- Training load and recovery analysis
- Insight generation and interpretation
β Workout Analytics Service Does NOT Handleβ
- Raw workout data collection (handled by Workout History Service)
- Workout planning or program creation
- Real-time workout guidance or coaching
- Social features or comparison with other users
- Nutrition tracking or recovery recommendations
Core Data Modelsβ
Workout Metricsβ
interface WorkoutMetrics {
workoutId: string;
userId: string;
date: Date;
// Volume metrics
totalVolume: number; // weight Γ reps sum
volumeByMuscleGroup: Record<string, number>;
volumeByExercise: Record<string, number>;
// Intensity metrics
averageRpe: number;
maxRpe: number;
rpeDistribution: Record<number, number>; // RPE value β count
intensityScore: number; // Weighted intensity calculation
// Frequency and density
totalSets: number;
totalReps: number;
exerciseCount: number;
muscleGroupCount: number;
// Time metrics
totalDuration: number; // minutes
workingTime: number; // time actually lifting
restTime: number;
trainingDensity: number; // work-to-rest ratio
// Performance indicators
personalRecords: PersonalRecord[];
volumeRecords: VolumeRecord[];
completionRate: number; // percentage of planned work completed
}
Progress Trendsβ
interface ProgressTrend {
userId: string;
exerciseId: string;
period: 'weekly' | 'monthly' | 'quarterly';
// Trend data points
dataPoints: TrendDataPoint[];
// Statistical analysis
trend: 'increasing' | 'decreasing' | 'stable' | 'volatile';
trendStrength: number; // 0-1, confidence in trend direction
correlationCoefficient: number;
// Progress metrics
totalImprovement: number; // % improvement over period
averageSessionImprovement: number;
plateauPeriods: PlateauPeriod[];
// Forecasting
projectedImprovement: number; // Next period prediction
confidenceInterval: [number, number];
calculatedAt: Date;
validUntil: Date;
}
interface TrendDataPoint {
date: Date;
value: number; // weight, volume, reps, etc.
sessionCount: number; // number of sessions contributing to this point
confidence: number; // 0-1, confidence in this data point
}
Performance Insightsβ
interface PerformanceInsight {
id: string;
userId: string;
type: 'strength_gain' | 'plateau_identified' | 'imbalance_detected' | 'goal_progress';
// Insight content
title: string;
description: string;
recommendation?: string;
priority: 'low' | 'medium' | 'high';
// Supporting data
dataVisualization?: {
chartType: 'line' | 'bar' | 'pie' | 'scatter';
data: any[];
config: any;
};
// Metrics
confidenceScore: number; // 0-1
impactScore: number; // Expected impact on user's goals
// Metadata
generatedAt: Date;
expiresAt?: Date;
viewedBy: boolean;
actionTaken?: string;
}
Key Operationsβ
Get Workout Analytics Dashboardβ
query GetWorkoutAnalytics(
$userId: ID!,
$timeframe: TimeframeInput!,
$metrics: [AnalyticsMetricType!]
) {
workoutAnalytics(
userId: $userId,
timeframe: $timeframe,
metrics: $metrics
) {
summary {
totalWorkouts
totalVolume
averageRpe
trainingFrequency
}
trends {
volumeProgression {
dataPoints {
date
value
}
trend
improvement
}
strengthProgression {
exercise
dataPoints {
date
maxWeight
}
personalRecords
}
}
muscleGroupAnalysis {
muscleGroup
totalVolume
frequency
lastTrained
recommendedAction
}
insights {
id
type
title
description
priority
confidenceScore
}
}
}
Get Exercise Performance Analysisβ
query GetExerciseAnalytics(
$userId: ID!,
$exerciseId: ID!,
$weeks: Int = 12
) {
exerciseAnalytics(
userId: $userId,
exerciseId: $exerciseId,
weeks: $weeks
) {
exercise {
id
name
}
performanceMetrics {
currentMax: maxWeight
volumeTrend {
dataPoints {
date
volume
}
improvement
}
strengthTrend {
dataPoints {
date
maxWeight
}
improvement
}
rpeProgression {
dataPoints {
date
averageRpe
}
}
}
personalRecords {
type
value
date
isRecent
}
insights {
plateauDetected
recommendedProgression
consistencyScore
}
}
}
Generate Performance Reportβ
query GeneratePerformanceReport(
$userId: ID!,
$reportType: ReportType!,
$period: ReportPeriod!
) {
performanceReport(
userId: $userId,
reportType: $reportType,
period: $period
) {
reportId
generatedAt
period
summary {
totalWorkouts
volumeImprovement
strengthGains
consistencyScore
}
highlights {
achievements
personalRecords
milestones
}
areas_for_improvement {
laggingMuscleGroups
inconsistentExercises
plateauedMovements
}
recommendations {
training_adjustments
focus_areas
next_goals
}
}
}
Integration Patternsβ
Flutter Analytics Dashboardβ
class WorkoutAnalyticsService {
final GraphQLClient _client;
// Get comprehensive analytics dashboard data
Future<WorkoutAnalyticsDashboard> getDashboard({
required String userId,
TimeframeInput timeframe = const TimeframeInput.lastMonth(),
}) async {
const query = '''
query GetWorkoutAnalytics(
\$userId: ID!,
\$timeframe: TimeframeInput!
) {
workoutAnalytics(userId: \$userId, timeframe: \$timeframe) {
summary {
totalWorkouts
totalVolume
averageRpe
trainingFrequency
}
trends {
volumeProgression {
dataPoints {
date
value
}
trend
improvement
}
strengthProgression {
exercise
dataPoints {
date
maxWeight
}
}
}
muscleGroupAnalysis {
muscleGroup
totalVolume
frequency
recommendedAction
}
insights {
id
title
description
priority
confidenceScore
}
}
}
''';
final result = await _client.query(QueryOptions(
document: gql(query),
variables: {
'userId': userId,
'timeframe': timeframe.toJson(),
},
cachePolicy: CachePolicy.cacheFirst,
));
return WorkoutAnalyticsDashboard.fromJson(result.data!['workoutAnalytics']);
}
// Get specific exercise performance analysis
Future<ExerciseAnalytics> getExerciseAnalytics({
required String userId,
required String exerciseId,
int weeks = 12,
}) async {
const query = '''
query GetExerciseAnalytics(
\$userId: ID!,
\$exerciseId: ID!,
\$weeks: Int
) {
exerciseAnalytics(
userId: \$userId,
exerciseId: \$exerciseId,
weeks: \$weeks
) {
performanceMetrics {
currentMax
volumeTrend {
dataPoints {
date
volume
}
improvement
}
strengthTrend {
dataPoints {
date
maxWeight
}
improvement
}
}
personalRecords {
type
value
date
}
insights {
plateauDetected
consistencyScore
}
}
}
''';
final result = await _client.query(QueryOptions(
document: gql(query),
variables: {
'userId': userId,
'exerciseId': exerciseId,
'weeks': weeks,
},
));
return ExerciseAnalytics.fromJson(result.data!['exerciseAnalytics']);
}
}
Analytics Chart Componentsβ
class ProgressChart extends StatelessWidget {
final List<TrendDataPoint> dataPoints;
final String title;
final String unit;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 16),
// Chart widget (using fl_chart or similar)
SizedBox(
height: 200,
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
spots: dataPoints
.asMap()
.entries
.map((entry) => FlSpot(
entry.key.toDouble(),
entry.value.value,
))
.toList(),
isCurved: true,
colors: [Theme.of(context).primaryColor],
barWidth: 2,
),
],
titlesData: FlTitlesData(
bottomTitles: SideTitles(
showTitles: true,
getTitles: (value) {
if (value.toInt() < dataPoints.length) {
final date = dataPoints[value.toInt()].date;
return DateFormat('MM/dd').format(date);
}
return '';
},
),
leftTitles: SideTitles(
showTitles: true,
getTitles: (value) => '${value.toInt()}$unit',
),
),
),
),
),
// Progress indicator
const SizedBox(height: 8),
Row(
children: [
Icon(
dataPoints.isNotEmpty &&
dataPoints.last.value > dataPoints.first.value
? Icons.trending_up
: Icons.trending_down,
color: dataPoints.isNotEmpty &&
dataPoints.last.value > dataPoints.first.value
? Colors.green
: Colors.red,
),
const SizedBox(width: 4),
Text(
_calculateImprovement(),
style: TextStyle(
color: dataPoints.isNotEmpty &&
dataPoints.last.value > dataPoints.first.value
? Colors.green
: Colors.red,
),
),
],
),
],
),
),
);
}
String _calculateImprovement() {
if (dataPoints.length < 2) return 'Insufficient data';
final improvement = ((dataPoints.last.value - dataPoints.first.value) /
dataPoints.first.value * 100);
return '${improvement.toStringAsFixed(1)}% ${improvement >= 0 ? 'improvement' : 'decline'}';
}
}
Analytics Calculationsβ
Volume Metrics Calculationβ
class VolumeCalculator {
calculateWorkoutVolume(workout: WorkoutHistoryEntry): VolumeMetrics {
let totalVolume = 0;
const volumeByMuscleGroup: Record<string, number> = {};
const volumeByExercise: Record<string, number> = {};
workout.completedTimeline.forEach(timelineItem => {
timelineItem.exercises.forEach(exercise => {
let exerciseVolume = 0;
exercise.sets.forEach(set => {
if (!set.skipped) {
const setVolume = set.weight * set.reps;
exerciseVolume += setVolume;
totalVolume += setVolume;
}
});
volumeByExercise[exercise.exerciseId] = exerciseVolume;
// Aggregate by muscle groups
const muscleGroups = this.getMuscleGroupsForExercise(exercise.exerciseId);
muscleGroups.forEach(muscleGroup => {
volumeByMuscleGroup[muscleGroup] =
(volumeByMuscleGroup[muscleGroup] || 0) + exerciseVolume;
});
});
});
return {
totalVolume,
volumeByMuscleGroup,
volumeByExercise,
calculatedAt: new Date(),
};
}
}
Trend Analysisβ
class TrendAnalyzer {
analyzeTrend(dataPoints: TrendDataPoint[]): TrendAnalysis {
if (dataPoints.length < 3) {
return {
trend: 'insufficient_data',
trendStrength: 0,
correlationCoefficient: 0,
};
}
// Calculate linear regression
const regression = this.calculateLinearRegression(dataPoints);
// Determine trend direction
let trend: 'increasing' | 'decreasing' | 'stable';
if (regression.slope > 0.1) trend = 'increasing';
else if (regression.slope < -0.1) trend = 'decreasing';
else trend = 'stable';
// Calculate trend strength based on R-squared
const trendStrength = Math.abs(regression.rSquared);
return {
trend,
trendStrength,
correlationCoefficient: regression.rSquared,
slope: regression.slope,
intercept: regression.intercept,
};
}
private calculateLinearRegression(
dataPoints: TrendDataPoint[]
): RegressionResult {
const n = dataPoints.length;
const x = dataPoints.map((_, index) => index);
const y = dataPoints.map(point => point.value);
const sumX = x.reduce((sum, val) => sum + val, 0);
const sumY = y.reduce((sum, val) => sum + val, 0);
const sumXY = x.reduce((sum, val, i) => sum + val * y[i], 0);
const sumXX = x.reduce((sum, val) => sum + val * val, 0);
const sumYY = y.reduce((sum, val) => sum + val * val, 0);
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
// Calculate R-squared
const yMean = sumY / n;
const ssTotal = y.reduce((sum, val) => sum + Math.pow(val - yMean, 2), 0);
const ssRes = y.reduce((sum, val, i) =>
sum + Math.pow(val - (slope * x[i] + intercept), 2), 0);
const rSquared = 1 - (ssRes / ssTotal);
return { slope, intercept, rSquared };
}
}
Insight Generationβ
Performance Insights Engineβ
class InsightGenerator {
generateInsights(
analytics: WorkoutAnalytics,
userGoals: UserGoals
): PerformanceInsight[] {
const insights: PerformanceInsight[] = [];
// Check for strength gains
const strengthInsights = this.analyzeStrengthGains(analytics);
insights.push(...strengthInsights);
// Check for muscle group imbalances
const imbalanceInsights = this.analyzeMuscleGroupBalance(analytics);
insights.push(...imbalanceInsights);
// Check goal progress
const goalInsights = this.analyzeGoalProgress(analytics, userGoals);
insights.push(...goalInsights);
// Check for plateaus
const plateauInsights = this.analyzePlateaus(analytics);
insights.push(...plateauInsights);
// Sort by priority and confidence
return insights.sort((a, b) =>
(b.impactScore * b.confidenceScore) -
(a.impactScore * a.confidenceScore)
);
}
private analyzeStrengthGains(analytics: WorkoutAnalytics): PerformanceInsight[] {
const insights: PerformanceInsight[] = [];
analytics.exerciseAnalytics.forEach(exercise => {
const improvementRate = exercise.strengthTrend.improvement;
if (improvementRate > 0.15) { // 15% improvement
insights.push({
type: 'strength_gain',
title: `Strong Progress on ${exercise.exerciseName}`,
description: `You've improved by ${(improvementRate * 100).toFixed(1)}% over the last month`,
recommendation: 'Keep up the great work! Consider gradually increasing volume or intensity.',
priority: 'high',
confidenceScore: exercise.strengthTrend.trendStrength,
impactScore: this.calculateImpactScore(exercise.exerciseId, userGoals),
generatedAt: new Date(),
});
}
});
return insights;
}
}
Event System Integrationβ
Analytics Eventsβ
interface AnalyticsEvents {
'analytics.metrics_calculated': {
userId: string;
workoutId: string;
metrics: WorkoutMetrics;
};
'analytics.insight_generated': {
userId: string;
insightType: string;
priority: string;
confidenceScore: number;
};
'analytics.plateau_detected': {
userId: string;
exerciseId: string;
plateauWeeks: number;
previousBest: number;
};
'analytics.personal_record': {
userId: string;
exerciseId: string;
recordType: string;
newValue: number;
previousValue?: number;
};
}
Performance Optimizationβ
Data Aggregation Strategyβ
class AnalyticsDataManager {
// Pre-calculate and cache common analytics queries
Future<void> preComputeAnalytics(String userId) async {
final now = DateTime.now();
// Pre-compute popular timeframes
await _computeAndCacheAnalytics(
userId,
TimeframeInput.lastWeek(),
'last_week',
);
await _computeAndCacheAnalytics(
userId,
TimeframeInput.lastMonth(),
'last_month',
);
await _computeAndCacheAnalytics(
userId,
TimeframeInput.lastQuarter(),
'last_quarter',
);
}
// Incremental updates for real-time analytics
Future<void> updateAnalyticsForNewWorkout(
String userId,
String workoutId,
) async {
// Only recalculate affected metrics
final workout = await workoutService.getWorkout(workoutId);
final newMetrics = await metricsCalculator.calculateMetrics(workout);
// Update cached aggregations incrementally
await _updateCachedMetrics(userId, newMetrics);
// Trigger insight regeneration if needed
await _triggerInsightUpdate(userId);
}
}
Related Servicesβ
Dependenciesβ
- Workout History Service: Source workout data for analysis
- Exercise Service: Exercise metadata for analysis context
- User Management Service: User goals and preferences for insights
Consumersβ
- Coaching Service: Analytics-driven coaching recommendations
- Progression Playbook Service: Performance-based progression rules
- Effective Workout Service: Analytics for workout effectiveness
- Recovery Service: Training load analysis for recovery recommendations
API Documentationβ
For detailed GraphQL schema and usage examples, see:
- Workout Analytics API documentation: coming soon
Supportβ
For workout analytics questions:
- Review the Workout Analytics API documentation
- Test analytics queries in GraphQL Playground
- Check analytics calculations and trend analysis logic
- Contact the data team for advanced analytics questions