127 lines
3.8 KiB
Dart
127 lines
3.8 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',
|
|
);
|
|
});
|