101 lines
3.6 KiB
Dart
101 lines
3.6 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../transactions/providers/providers.dart';
|
|
import '../../transactions/data/models.dart';
|
|
|
|
class Insight {
|
|
final String title;
|
|
final String message;
|
|
final String type; // 'WARNING', 'SUCCESS', 'INFO'
|
|
|
|
Insight(this.title, this.message, this.type);
|
|
}
|
|
|
|
final insightProvider = Provider<Insight?>((ref) {
|
|
final transState = ref.watch(transactionProvider);
|
|
final budgetsState = ref.watch(budgetProvider);
|
|
final categoriesState = ref.watch(categoryProvider);
|
|
|
|
if (!transState.hasValue || !budgetsState.hasValue || !categoriesState.hasValue) {
|
|
return null;
|
|
}
|
|
|
|
final transactions = transState.value!;
|
|
final budgets = budgetsState.value!;
|
|
final categories = categoriesState.value!;
|
|
final now = DateTime.now();
|
|
|
|
final currentMonthTxs = transactions.where((t) => t.date.month == now.month && t.date.year == now.year).toList();
|
|
|
|
final totalIncome = currentMonthTxs.where((t) => t.type == 'INCOME').fold(0.0, (s, t) => s + t.amount);
|
|
final totalExpense = currentMonthTxs.where((t) => t.type == 'EXPENSE').fold(0.0, (s, t) => s + t.amount);
|
|
|
|
// Rule 1: Expenses > Income
|
|
if (totalExpense > totalIncome && totalIncome > 0) {
|
|
return Insight(
|
|
'Spending Alert',
|
|
'You have spent more than you earned this month! (Rs. ${totalExpense.toStringAsFixed(0)} vs Rs. ${totalIncome.toStringAsFixed(0)})',
|
|
'WARNING'
|
|
);
|
|
}
|
|
|
|
// Rule 2: Over Budget Categories
|
|
for (var budget in budgets) {
|
|
final spent = currentMonthTxs
|
|
.where((t) => t.type == 'EXPENSE' && t.categoryId == budget.categoryId)
|
|
.fold(0.0, (s, t) => s + t.amount);
|
|
|
|
if (spent > budget.monthlyLimit) {
|
|
final categoryName = categories.firstWhere((c) => c.id == budget.categoryId, orElse: () => Category(id: 0, name: 'Unknown')).name;
|
|
return Insight(
|
|
'Budget Exceeded',
|
|
'You have exceeded your $categoryName budget by Rs. ${(spent - budget.monthlyLimit).toStringAsFixed(0)}.',
|
|
'WARNING'
|
|
);
|
|
} else if (spent > budget.monthlyLimit * 0.9) {
|
|
final categoryName = categories.firstWhere((c) => c.id == budget.categoryId, orElse: () => Category(id: 0, name: 'Unknown')).name;
|
|
return Insight(
|
|
'Nearing Budget Limit',
|
|
'Careful! You have used ${(spent / budget.monthlyLimit * 100).toStringAsFixed(0)}% of your $categoryName budget.',
|
|
'WARNING'
|
|
);
|
|
}
|
|
}
|
|
|
|
// Rule 3: Highest spending category warning if > 40% of total
|
|
if (totalExpense > 0) {
|
|
final Map<int, double> spentByCategory = {};
|
|
for (var t in currentMonthTxs.where((t) => t.type == 'EXPENSE')) {
|
|
if (t.categoryId != null) {
|
|
spentByCategory[t.categoryId!] = (spentByCategory[t.categoryId!] ?? 0) + t.amount;
|
|
}
|
|
}
|
|
|
|
if (spentByCategory.isNotEmpty) {
|
|
final maxEntry = spentByCategory.entries.reduce((a, b) => a.value > b.value ? a : b);
|
|
if (maxEntry.value > totalExpense * 0.4) {
|
|
final categoryName = categories.firstWhere((c) => c.id == maxEntry.key, orElse: () => Category(id: 0, name: 'Unknown')).name;
|
|
return Insight(
|
|
'High Concentration',
|
|
'${(maxEntry.value / totalExpense * 100).toStringAsFixed(0)}% of your expenses this month went to $categoryName.',
|
|
'INFO'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default Positive Insight
|
|
if (totalIncome > 0 && totalExpense < totalIncome * 0.5) {
|
|
return Insight(
|
|
'Great Job!',
|
|
'You have saved over 50% of your income this month. Keep it up!',
|
|
'SUCCESS'
|
|
);
|
|
}
|
|
|
|
return Insight(
|
|
'On Track',
|
|
'Your finances are looking stable this month.',
|
|
'SUCCESS'
|
|
);
|
|
});
|