212 lines
5.9 KiB
Dart
212 lines
5.9 KiB
Dart
import '../models/bean.dart';
|
|
import '../models/machine.dart';
|
|
import '../models/recipe.dart';
|
|
import '../models/journal_entry.dart';
|
|
import '../models/drink.dart';
|
|
|
|
class CoffeeStatistics {
|
|
final int totalBeans;
|
|
final int totalMachines;
|
|
final int totalRecipes;
|
|
final int totalJournalEntries;
|
|
final int totalDrinks;
|
|
final double averageRating;
|
|
final int preferredBeans;
|
|
final String mostUsedRoastLevel;
|
|
final List<Map<String, dynamic>> coffeeConsumptionTrend;
|
|
final List<Drink> topRatedDrinks;
|
|
final List<Map<String, dynamic>> recentActivity;
|
|
final Map<String, dynamic> monthlyStats;
|
|
|
|
CoffeeStatistics({
|
|
required this.totalBeans,
|
|
required this.totalMachines,
|
|
required this.totalRecipes,
|
|
required this.totalJournalEntries,
|
|
required this.totalDrinks,
|
|
required this.averageRating,
|
|
required this.preferredBeans,
|
|
required this.mostUsedRoastLevel,
|
|
required this.coffeeConsumptionTrend,
|
|
required this.topRatedDrinks,
|
|
required this.recentActivity,
|
|
required this.monthlyStats,
|
|
});
|
|
}
|
|
|
|
class StatisticsService {
|
|
static Future<CoffeeStatistics> getStatistics(
|
|
List<Bean> beans,
|
|
List<Machine> machines,
|
|
List<Recipe> recipes,
|
|
List<JournalEntry> journalEntries,
|
|
List<Drink> drinks,
|
|
) async {
|
|
try {
|
|
// Calculate average rating
|
|
final averageRating = drinks.isNotEmpty
|
|
? drinks.fold(0.0, (sum, drink) => sum + drink.rating) / drinks.length
|
|
: 0.0;
|
|
|
|
// Find most used roast level
|
|
final roastLevelCounts = <String, int>{};
|
|
for (final bean in beans) {
|
|
roastLevelCounts[bean.roastLevel.toString().split('.').last] =
|
|
(roastLevelCounts[bean.roastLevel.toString().split('.').last] ??
|
|
0) +
|
|
1;
|
|
}
|
|
|
|
final mostUsedRoastLevel =
|
|
roastLevelCounts.entries
|
|
.fold<MapEntry<String, int>?>(
|
|
null,
|
|
(max, entry) =>
|
|
max == null || entry.value > max.value ? entry : max,
|
|
)
|
|
?.key ??
|
|
'MEDIUM';
|
|
|
|
// Calculate coffee consumption trend (last 6 months)
|
|
final coffeeConsumptionTrend = _calculateConsumptionTrend(journalEntries);
|
|
|
|
// Get top rated drinks
|
|
final topRatedDrinks = List<Drink>.from(drinks)
|
|
..sort((a, b) => b.rating.compareTo(a.rating))
|
|
..take(5);
|
|
|
|
// Recent activity
|
|
final recentActivity = _getRecentActivity(
|
|
beans,
|
|
machines,
|
|
recipes,
|
|
journalEntries,
|
|
drinks,
|
|
);
|
|
|
|
// Monthly stats
|
|
final monthlyStats = _calculateMonthlyStats(journalEntries);
|
|
|
|
return CoffeeStatistics(
|
|
totalBeans: beans.length,
|
|
totalMachines: machines.length,
|
|
totalRecipes: recipes.length,
|
|
totalJournalEntries: journalEntries.length,
|
|
totalDrinks: drinks.length,
|
|
averageRating: (averageRating * 10).round() / 10,
|
|
preferredBeans: beans.where((bean) => bean.preferred).length,
|
|
mostUsedRoastLevel: mostUsedRoastLevel,
|
|
coffeeConsumptionTrend: coffeeConsumptionTrend,
|
|
topRatedDrinks: topRatedDrinks,
|
|
recentActivity: recentActivity,
|
|
monthlyStats: monthlyStats,
|
|
);
|
|
} catch (error) {
|
|
throw Exception('Error calculating statistics: $error');
|
|
}
|
|
}
|
|
|
|
static List<Map<String, dynamic>> _calculateConsumptionTrend(
|
|
List<JournalEntry> journalEntries,
|
|
) {
|
|
final now = DateTime.now();
|
|
final months = <Map<String, dynamic>>[];
|
|
|
|
for (int i = 5; i >= 0; i--) {
|
|
final date = DateTime(now.year, now.month - i, 1);
|
|
final monthName = _getMonthName(date);
|
|
|
|
final count = journalEntries.where((entry) {
|
|
final entryDate = entry.date;
|
|
return entryDate.month == date.month && entryDate.year == date.year;
|
|
}).length;
|
|
|
|
months.add({'month': monthName, 'count': count});
|
|
}
|
|
|
|
return months;
|
|
}
|
|
|
|
static List<Map<String, dynamic>> _getRecentActivity(
|
|
List<Bean> beans,
|
|
List<Machine> machines,
|
|
List<Recipe> recipes,
|
|
List<JournalEntry> journalEntries,
|
|
List<Drink> drinks,
|
|
) {
|
|
final activities = <Map<String, dynamic>>[];
|
|
|
|
// Add recent journal entries
|
|
for (final entry in journalEntries.take(5)) {
|
|
activities.add({
|
|
'date': entry.date,
|
|
'type': 'Journal',
|
|
'description': 'Enjoyed ${entry.drink.name}',
|
|
});
|
|
}
|
|
|
|
// Add recent beans
|
|
for (final bean in beans.take(3)) {
|
|
activities.add({
|
|
'date': bean.roastedDate,
|
|
'type': 'Bean',
|
|
'description':
|
|
'Added ${bean.name} from ${bean.originCountry ?? 'Unknown'}',
|
|
});
|
|
}
|
|
|
|
activities.sort(
|
|
(a, b) => (b['date'] as DateTime).compareTo(a['date'] as DateTime),
|
|
);
|
|
return activities.take(10).toList();
|
|
}
|
|
|
|
static Map<String, dynamic> _calculateMonthlyStats(
|
|
List<JournalEntry> journalEntries,
|
|
) {
|
|
final now = DateTime.now();
|
|
final currentMonth = now.month;
|
|
final currentYear = now.year;
|
|
|
|
final currentMonthCount = journalEntries.where((entry) {
|
|
return entry.date.month == currentMonth && entry.date.year == currentYear;
|
|
}).length;
|
|
|
|
final lastMonth = currentMonth == 1 ? 12 : currentMonth - 1;
|
|
final lastYear = currentMonth == 1 ? currentYear - 1 : currentYear;
|
|
|
|
final lastMonthCount = journalEntries.where((entry) {
|
|
return entry.date.month == lastMonth && entry.date.year == lastYear;
|
|
}).length;
|
|
|
|
final growth = lastMonthCount > 0
|
|
? (((currentMonthCount - lastMonthCount) / lastMonthCount) * 100)
|
|
.round()
|
|
: 0;
|
|
|
|
return {
|
|
'currentMonth': currentMonthCount,
|
|
'lastMonth': lastMonthCount,
|
|
'growth': growth,
|
|
};
|
|
}
|
|
|
|
static String _getMonthName(DateTime date) {
|
|
const months = [
|
|
'Jan',
|
|
'Feb',
|
|
'Mar',
|
|
'Apr',
|
|
'May',
|
|
'Jun',
|
|
'Jul',
|
|
'Aug',
|
|
'Sep',
|
|
'Oct',
|
|
'Nov',
|
|
'Dec',
|
|
];
|
|
return '${months[date.month - 1]} ${date.year.toString().substring(2)}';
|
|
}
|
|
}
|