diff --git a/data/SavingsFile.txt b/data/SavingsFile.txt index 7a538556d8..e69de29bb2 100644 --- a/data/SavingsFile.txt +++ b/data/SavingsFile.txt @@ -1 +0,0 @@ -Salary | 200.00 diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 6b6722cd0b..a158a6e7cc 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -462,20 +462,26 @@ Format: `print budget` * This feature provides an overview of the expenses distribution across different categories. * A horizontal bar graph showing the percentage of total expenses attributed to each category. * It highlights the category with the highest expenses, the one with the lowest (excluding categories with no expenses), -* and lists any categories where no expenses have been recorded. +and lists any categories where no expenses have been recorded. * Categories are Housing, Groceries, Utility, Transport, Entertainment, and Others. Example of usage: `get expenses insights` +Example of Expected Output: +![GetExpenseInsights.png](userguideimages%2FGetExpenseInsights.png) + ### Get Graphical Insights for savings: `get savings insights` * This feature offers a comprehensive look at how your savings are allocated across various categories. * A horizontal bar graph showing the percentage of total savings attributed to each category. * It highlights the category with the highest savings, the one with the lowest (excluding categories with no savings), -* and lists any categories where no savings have been added. +and lists any categories where no savings have been added. * Categories are Salary, Investments, Gifts, and Others Example of Usage: `get savings insights` +Example of Expected Output: +![GetSavingsInsights.png](userguideimages%2FGetSavingsInsights.png) + ### Saving the data diff --git a/docs/team/jasraa.md b/docs/team/jasraa.md index 7f57744700..03c3c87b84 100644 --- a/docs/team/jasraa.md +++ b/docs/team/jasraa.md @@ -49,10 +49,19 @@ within the command line interface, eliminating the need for external tools or vi [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=jasraa&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) #### Enhancements to existing features -(to be updated) +1. Wrote Junit tests for Edit Expenses, Edit Savings, Get Expenses Insight and Get Savings Insights +2. Implemented Bug fixes for "Edit Expenses", "Edit Savings", "Storage", "Get Expenses Insights" and +"Get Savings Insights" #### Contributions to the UG -(to be updated) +Added documentation for the features `edit expense`, `edit savings`, `get expenses insight` +and `get savings insights` #### Contributions to the DG -(to be updated) \ No newline at end of file +Added diagrams and documentation for the features `edit expense`, `edit savings`, `get expenses insight` +and `get savings insights` + +#### Community +1. Communicated with teammates for ideation and enhancement of existing features. +2. Provided DG Peer Review Comments for another team. [CS2113-T15-3 SplitLiang](https://github.com/nus-cs2113-AY2324S2/tp/pull/47) +3. Reported bugs for another team during PE-D. [CS2113-T15-1 LongAh](https://github.com/AY2324S2-CS2113-T15-1/tp/releases) \ No newline at end of file diff --git a/docs/userguideimages/GetExpenseInsights.png b/docs/userguideimages/GetExpenseInsights.png new file mode 100644 index 0000000000..79d2138b7a Binary files /dev/null and b/docs/userguideimages/GetExpenseInsights.png differ diff --git a/docs/userguideimages/GetSavingsInsights.png b/docs/userguideimages/GetSavingsInsights.png new file mode 100644 index 0000000000..09b0260f40 Binary files /dev/null and b/docs/userguideimages/GetSavingsInsights.png differ diff --git a/src/main/java/seedu/budgetbuddy/commons/SavingList.java b/src/main/java/seedu/budgetbuddy/commons/SavingList.java index 02765b9f7e..68015a4740 100644 --- a/src/main/java/seedu/budgetbuddy/commons/SavingList.java +++ b/src/main/java/seedu/budgetbuddy/commons/SavingList.java @@ -2,12 +2,12 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; - import java.util.logging.Level; import java.util.logging.Logger; -import java.util.List; import java.util.stream.Collectors; +import java.util.Collections; import seedu.budgetbuddy.Ui; import seedu.budgetbuddy.exception.BudgetBuddyException; @@ -251,65 +251,91 @@ public void reduceSavings(int index, double amount) { } } + public double calculateTotalSavings() { + double totalSavings = 0; + try { + for (Saving saving : savings) { + if (saving.getAmount() < 0) { + throw new IllegalArgumentException("Savings should not be negative"); + } + totalSavings += saving.getAmount(); + } + } catch (IllegalArgumentException e) { + LOGGER.log(Level.WARNING, "Negative savings amount detected", e); + } + + assert totalSavings >= 0 : "Total savings should be non-negative"; + + return totalSavings; + } + /** * Analyzes and displays insights into the saved amounts across different categories. * It prints out the highest and lowest savings categories and lists categories with no savings. * A bar graph representing the distribution of savings is also displayed. */ public void getSavingsInsights() { - findTotalSavings(); // Make sure total savings are updated - - if (initialAmount == 0) { + double totalSavings = calculateTotalSavings(); + if (totalSavings == 0) { System.out.println("No savings to display."); return; } - printSavingsDistribution(); + Map sumsByCategory = calculateSumsByCategory(); - // Calculate the highest savings value - double highestSavings = savings.stream() - .mapToDouble(Saving::getAmount) - .max().orElse(0); + // Calculate the highest savings + double highestSavings = Collections.max(sumsByCategory.values()); + // Calculate the lowest savings + double lowestSavings = sumsByCategory.values().stream() + .filter(amount -> amount > 0) + .min(Double::compare) + .orElse(0.0); - // Identify the categories with the highest savings - List highestCategories = savings.stream() - .filter(s -> s.getAmount() == highestSavings) - .map(Saving::getCategory) - .collect(Collectors.toList()); + List highestCategories = getSavingsCategoriesByAmount(sumsByCategory, highestSavings); + List lowestCategories = getSavingsCategoriesByAmount(sumsByCategory, lowestSavings); - // Calculate the lowest savings value excluding the highest if it's the only value - double lowestSavings = savings.stream() - .filter(s -> !highestCategories.contains(s.getCategory())) - .mapToDouble(Saving::getAmount) - .min().orElse(0); + // Print the distribution graph + ui.printDivider(); + printSavingsDistribution(sumsByCategory, totalSavings); + ui.printDivider(); - // Identify the categories with the lowest savings, excluding those with no savings - List lowestCategories = savings.stream() - .filter(s -> s.getAmount() == lowestSavings && lowestSavings != 0) - .map(Saving::getCategory) - .collect(Collectors.toList()); + // Print insights + System.out.println("Highest Savings Category: " + formatCategoryList(highestCategories)); + System.out.println("Lowest Savings Category: " + formatCategoryList(lowestCategories)); + System.out.println("Categories with no savings added: " + + formatCategoryList(getNoSavingsCategories(sumsByCategory))); + ui.printDivider(); + } - // If lowestSavings is 0, then this list should be empty - if (lowestSavings == 0) { - lowestCategories.clear(); - } + private List getSavingsCategoriesByAmount(Map sumsByCategory, double amount) { + return sumsByCategory.entrySet().stream() + .filter(entry -> entry.getValue() == amount) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } - // Identify categories with no savings - List noSavingsCategories = categories.stream() - .filter(c -> savings.stream().noneMatch(s -> s.getCategory().equals(c))) + private List getNoSavingsCategories(Map sumsByCategory) { + return categories.stream() + .filter(category -> !sumsByCategory.containsKey(category) || sumsByCategory.get(category) == 0) .collect(Collectors.toList()); + } - // Add categories with zero amount saved - noSavingsCategories.addAll(savings.stream() - .filter(s -> s.getAmount() == 0) - .map(Saving::getCategory) - .collect(Collectors.toList())); + /** + * Prints a distribution of savings as a horizontal bar graph. + * Each category's bar length is proportional to its percentage of the total savings. + */ + private void printSavingsDistribution(Map sumsByCategory, double totalSavings) { + double maxPercentage = sumsByCategory.values().stream() + .mapToDouble(amount -> (amount / totalSavings) * 100) + .max() + .orElse(100); - ui.printDivider(); - System.out.println("Highest Savings Category: " + formatCategoryList(highestCategories)); - System.out.println("Lowest Savings Category: " + formatCategoryList(lowestCategories)); - System.out.println("Categories with no savings added: " + formatCategoryList(noSavingsCategories)); - ui.printDivider(); + for (String category : categories) { + double percentage = (sumsByCategory.getOrDefault(category, 0.0) / totalSavings) * 100; + int barLength = (int) (percentage / (maxPercentage / 50)); + String bar = "[" + "#".repeat(Math.max(0, barLength)) + "]"; + System.out.println(String.format("%-15s: %6.2f%% %s", category, percentage, bar)); + } } /** @@ -337,26 +363,11 @@ private Map calculateSumsByCategory() { private String formatCategoryList(List categories) { if (categories.isEmpty()) { return "None"; + } else if (categories.size() == 1) { + return categories.get(0); } else { - return String.join(", ", categories.subList(0, categories.size() - 1)) - + (categories.size() > 1 ? " and " : "") + categories.get(categories.size() - 1); - } - } - - /** - * Prints a distribution of savings as a horizontal bar graph. - * Each category's bar length is proportional to its percentage of the total savings. - */ - private void printSavingsDistribution() { - Map sumsByCategory = calculateSumsByCategory(); - double totalSavings = sumsByCategory.values().stream().mapToDouble(Double::doubleValue).sum(); - - for (String category : categories) { - Double sum = sumsByCategory.getOrDefault(category, 0.0); - double percentage = (sum / totalSavings) * 100; - int barLength = (int) (percentage / (100.0 / 50)); // Assuming a bar max length of 50 characters - String bar = "[" + "#".repeat(Math.max(0, barLength)) + "]"; - System.out.println(String.format("%-15s: %6.2f%% %s", category, percentage, bar)); + String allButLast = String.join(", ", categories.subList(0, categories.size() - 1)); + return allButLast + " and " + categories.get(categories.size() - 1); } }