By: Team-F13-2
Since: Sep 2019
Licence: MIT
- 1. Introduction
- 2. Setting up
- 3. Design
- 4. Implementation
- 5. Documentation
- 6. Testing
- 7. Dev Ops
- Appendix A: Product Scope
- Appendix B: User Stories
- Appendix C: Use Cases
- Appendix D: Non Functional Requirements
- Appendix E: Glossary
- Appendix F: Instructions for Manual Testing
1. Introduction
$aveNUS is a desktop financial planning app that allows NUS students to plan their meals according to the amount of budget they set aside per meal in NUS. With $aveNUS, you can more effectively manage your expenses, save your favourite meal options within NUS, edit meal options, and even receive recommendations for meals that suit your budget.
This Developer Guide allows other developers to understand the design principles as well as implementation decisions behind $aveNUS. With this guide, developers can understand and contribute to $aveNUS easily.
The table below provides a quick summary of the symbols and formatting used in the guide.
|
Command that can be typed into the command box |
|
Success execution of command |
|
Tips that might be useful |
|
Additional information that is good to know |
|
Important pointers to take note |
2. Setting up
2.1. Prerequisites
-
JDK
11
or above -
IntelliJ IDE
IntelliJ by default has Gradle and JavaFx plugins installed.
Do not disable them. If you have disabled them, go toFile
>Settings
>Plugins
to re-enable them.
2.2. Setting up $aveNUS in your computer
-
Fork this repo, and clone the fork to your computer
-
Open IntelliJ (if you are not in the welcome screen, click
File
>Close Project
to close the existing project dialog first) -
Set up the correct JDK version for Gradle
-
Click
Configure
>Project Defaults
>Project Structure
-
Click
New…
and find the directory of the JDK
-
-
Click
Import Project
-
Locate the
build.gradle
file and select it. ClickOK
-
Click
Open as Project
-
Click
OK
to accept the default settings.
2.3. Verifying the setup
-
Run the
seedu.savenus.Main
and try a few commands -
Run the tests to ensure they all pass.
2.4. Configurations to do before writing code
2.4.1. Configuring the coding style
This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,
-
Go to
File
>Settings…
(Windows/Linux), orIntelliJ IDEA
>Preferences…
(macOS) -
Select
Editor
>Code Style
>Java
-
Click on the
Imports
tab to set the order-
For
Class count to use import with '*'
andNames count to use static import with '*'
: Set to999
to prevent IntelliJ from contracting the import statements -
For
Import Layout
: The order isimport static all other imports
,import java.*
,import javax.*
,import org.*
,import com.*
,import all other imports
. Add a<blank line>
between eachimport
-
Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.
2.4.2. Updating documentation to match your fork
After forking the repo, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level3
repo.
If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level3
), you should do the following:
-
Configure the site-wide documentation settings in
build.gradle
, such as thesite-name
, to suit your own project. -
Replace the URL in the attribute
repoURL
inDeveloperGuide.adoc
andUserGuide.adoc
with the URL of your fork.
2.4.3. Setting up CI
Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.
After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).
Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your Personal fork. |
Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based) |
2.4.4. Getting started with coding
When you are ready to start coding, we recommend that you get some sense of the overall design by reading about $aveNUS’s architecture.
3. Design
3.1. Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
The .puml files used to create diagrams in this document can be found in the diagrams folder.
Refer to the Using PlantUML guide to learn how to create and edit diagrams.
|
-
At app launch: Initializes the components in the correct sequence, and connects them up with each other.
-
At shut down: Shuts down the components and invokes cleanup method where necessary.
Commons
represents a collection of classes used by multiple other components.
The following class plays an important role at the architecture level:
-
LogsCenter
: Used by many classes to write log messages to the App’s log file.
The rest of the App consists of four components.
Each of the four components
-
Defines its API in an
interface
with the same name as the Component. -
Exposes its functionality using a
{Component Name}Manager
class.
For example, the Logic
component (see the class diagram given below) defines it’s API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
delete 1
commandThe sections below give more details of each component.
3.2. UI component
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, FoodListPanel
, PurchaseListPanel
, SavingsHistoryPanel
, StatusBarFooter
, etc. All these, including the MainWindow
, inherit from the abstract UiPart
class.
There are pop-up windows that appear when user calls the InfoCommand
or HelpCommand
which is a new window with their own stages. The CSS files for each pop-up windows is taken from the MainWindow
which ensures that they always match the theme of the MainWindow
should the user decide to switch the theme while either pop-up window is open.
The UI
component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The UI
component,
-
Executes user commands using the
Logic
component. -
Responds to the user’s keyboard and mouse input.
-
Pop-up windows respond to the buttons on the menu tab.
-
Button on the menu tab which acts as substitute for keyboard input for single-word commands.
-
Each usage of the menu tab buttons will update the Food list accordingly.
-
Listens for changes to
Model
data so that the UI can be updated with the modified data.
3.3. Logic component
API :
Logic.java
-
Logic
uses theSaveNusParser
class to parse the user command. -
This results in a
Command
object which is executed by theLogicManager
. -
The command execution can affect the
Model
(e.g. adding a food item). -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
. -
In addition, the
CommandResult
object can also instruct theUi
to perform certain actions, such as displaying help to the user.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API call.
delete 1
Command
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
|
3.4. Model component
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores the Menu data.
-
stores the Wallet data.
-
stores the Info data.
-
stores the SavingsAccount data.
-
stores the PurchaseHistory data.
-
exposes the
RemainingBudget
andDaysToExpire
that can be 'observed' e.g. the UI can be bound to these values so that the UI automatically updates when the data in theWallet
change. -
exposes the
Savings
that can be 'observed' e.g. the UI can be bound to these values so that the UI automatically updates when the data in theSavingsAccount
changes. -
exposes an unmodifiable
ObservableList<Food>
,ObservableList<Savings>
andObservableList<Purchase>
that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in either of the lists change. -
does not depend on any of the other three components.
3.4.1. Food component in Model
The Food
,
-
stores information on
Name
,Price
andCategory
which are compulsory fields. -
stores information on
Description
,Opening Hours
andRestrictions
which are optional fields. -
stores information
Tag
which are retrieved from theUniqueTagList
.
As a more OOP model, we can store a Tag list in Menu , which Food can reference.
This would allow Menu to only require one Tag object per unique Tag ,
instead of each Food needing their own Tag object.
|
3.4.2. Design considerations for Food-related Models
When implementing Food-related models, it is important not to add duplicate foods. As the user only requires to enter the name, price and category as they are compulsory fields, we need to ensure that this does not happen.
For example, you would not want to add Chicken Rice twice into the food list. As a result, our team went through several alternatives to deal with this consideration.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
|
|
We chose alternative 1 because there can be multiple instances of Food
with different Name
but same Category
. For
example, an instance of Chicken Rice from a Chinese stall may be different from an instance of Chicken Rice from an Indian
stall. As a result, we would want to be able to add these 2 food items into the food list.
3.5. Storage component
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. -
can save the
Menu
data in json format and read it back. -
can save the
Wallet
data in json format and read it back. -
can save the
PurchaseHistory
data in json format and read it back. -
can save the
SavingsHistory
data in json format and read it back.
3.6. Common classes
Classes used by multiple components are in the seedu.savenus.commons
package.
4. Implementation
This section describes some noteworthy details on how certain features are implemented.
4.1. Logging
We are using java.util.logging
package for logging. The LogsCenter
class is used to manage the logging levels and logging destinations.
-
The logging level can be controlled using the
logLevel
setting in the configuration file (See Section 4.2, “Configuration”) -
The
Logger
for a class can be obtained usingLogsCenter.getLogger(Class)
which will log messages according to the specified logging level -
Currently log messages are output through:
Console
and to a.log
file.
Logging Levels
-
SEVERE
: Critical problem detected which may possibly cause the termination of the application -
WARNING
: Can continue, but with caution -
INFO
: Information showing the noteworthy actions by the App -
FINE
: Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size
4.2. Configuration
Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json
).
4.3. Sorting feature
The Sorting feature allows users to sort their food items based on certain FIELD
and DIRECTION
. The FIELD
and
DIRECTION
are as followed:
The FIELD and DIRECTION can be entirely in Upper or Lower Case.
|
First and foremost, users will be able to sort the food items based on their default ordering.
The default ordering is based on ascending price, name and then category. This is done using the default
command.+
Not only that, they will be able to implement their own custom comparator using makesort
. From this, they should be able to use customsort
and autosort`.
customsort
sorts the food items based on the custom comparator, where autosort
sorts the food items every time there is an edit to the food list.
4.3.1. Classes for sorting feature in Model
The Sorting feature was implemented with a new set of classes introduced to the Model.
From the model, the CustomSorter
stores the comparator for autosort
and makesort
.
From which, you will need to call makesort FIELD DIRECTION
to create the custom comparator. The CustomSorter
contains a FoodComparator
.
The FoodComparator
stores fields which will be needed for various Food
to be compared.
The DefaultComparator
helps to sort food items based on their natural ordering. This is called via the default
command.
When default
is called, the food list will be sorted according to ascending category, ascending name and ascending price.
Detailed below are the design considerations taken into account when engineering the DefaultComparator
and FoodComparator
classes.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
|
|
As a result, we have chosen alternative 1. By specifying the specific fields in the body of constructor in
the DefaultComparator
, it makes it easier for future developers to create their own form of DefaultComparator
.
Not only that, it saves time and reduces duplicate code.
4.3.2. Creation of new Custom Comparator
The user may wish to create a new custom comparator. This can be done with the makesort
command.
The sequence diagram for interactions between the Logic, Model and Storage components when a user executes the makesort
command is shown below.
The user may want to create a new custom comparator. This can be done with the makesort
command.
The fields are stored in the FoodComparator
in the CustomSorter object, which stores the necessary fields
as a list.
The current implementation for creating a new CustomSorter
is done by overwriting the existing CustomSorter
, with a new
CustomSorter
with the desired fields.
The command is read as a text string from the command box in the UI and then is executed by calling MainWindow#executeCommand(), which passes this string (named commandText) to the Logic component by calling Logic#execute(Model model).
The following activity diagram below summarizes how the save command works
If fields are given, the original CustomSorter is overridden by a new CustomSorter with new fields. Else, the original CustomSorter is overridden by a new CustomSorter with no fields.
4.3.3. Sorting of the Food list
After the customization of the Custom Comparator, the user can now call the customsort
command to sort the food items based
on the fields specified. For example, if the fields specified are PRICE ASC NAME DESC
, the system will sort the food items in order
of ascending price. If two items have the same price, they will be ranked according to their names using lexicographic
comparison.
If two foods have the same price, they will compared using lexicographical ordering based on their names. If they are lexicographically similar, their ranks do not change.
Name |
Price ($) |
Ranking (Price) |
Overall Ranking |
Chicken Rice |
2.80 |
2 |
2 |
Nasi Lemak |
2.80 |
2 |
3 |
Fried Rice |
4.00 |
4 |
4 |
Dim Sum |
1.80 |
1 |
1 |
From the table above, we can see Dim Sum will be ranked at the top as it is the cheapest. On the other hand, Fried Rice will be ranked at the bottom as it is the most expensive food item.
As Chicken Rice and Nasi Lemak have the same price, they will compared using lexicographical ordering based on their names. Chicken Rice still has a higher ranking than Nasi Lemak as Chicken Rice is lexicographically smaller than Nasi Lemak.
4.4. Savings feature
The Savings feature encompasses a Savings Account feature and a Savings History feature, both of which work together to allow the user to track his/her savings effectively.
The Savings Account feature allows users to save a specified amount of money from their wallet into their savings account using the save AMOUNT
command, where AMOUNT
is a user defined amount of money to deposit into the user’s savings account.
The feature also allows the user to withdraw money from his/her savings account, direcly into his/her $aveNUS wallet. This is accomplished using the withdraw AMOUNT
command.
Meanwhile, the Savings History feature is also introduced to allow $aveNUS users to keep track of all their savings and withdrawals. This is available by clicking the "Savings" tab located below the Savings Account and Wallet display.
Also, users can choose to view their savings or withdrawals only, by calling the show TO_DISPLAY
command. This allows the user to have a quick overview of his savings/withdrawals history to decide
if he/she has been saving enough or not. TO_DISPLAY
must either be savings
/
withdrawals
/both
, whereby the TO_DISPLAY
input is case-insensitive, i.e. show SaVinGs
and show savings
are valid inputs.
4.4.1. Classes for Savings Account feature in Model
The Savings Account feature was implemented with the introduction of a new set of classes into the Model. A new SavingsAccount
class encapsulates all the methods and classes
related to this feature. The SavingsAccount
object is stored in $aveNUS and it exposes only a read-only interface, ReadOnlySavingsAccount
, to allow other components to retrieve
the retrieve information within the user’s SavingsAccount
, while maintaining data integrity.
SavingsAccount
contains a single instance of the CurrentSavings
class, a class we implemented to allow greater abstraction of the user’s current savings value.
CurrentSavings
is only called once throughout the application and is modified with package-protected methods within the class itself.
This allows the CurrentSavings
itself to be unmodifiable in other components in the application except within the SavingsAccount
itself.
Within CurrentSavings
, there is an unmodifiable Money
class. Once a CurrentSavings
object is created with a fixed value, it cannot be changed.
The SavingsAccount
Class Diagram below shows the relationship between the classes mentioned above.
4.4.2. Classes for Savings History feature in Model
Also, the Savings History feature was implemented with another set of classes introduced to the Model. It includes a new SavingsHistory
class, which contains all the methods
related to this feature. The SavingHistory
object exposes only a read-only interface, ReadOnlySavingsHistory
, to allow other components within the application to retrieve the savings
history of the user.
Within the SavingsHistory
object, it contains all the methods to interact with the SavingsHistoryList
object. Hence,
Each SavingsHistory
object only has ONE unmodifiable SavingsHistoryList
object.
The SavingsHistoryList
class acts as an abstraction of a list, which sole purpose is to store all Savings
made by the user.
Withdrawals and savings made by the user are both objects of the class Savings
. Within the Savings
class, there are 3 unmodifiable attributes:
-
Money
which is a class that contains the value of theSavings
made. It is a negative value if a withdrawal is made, and a positive value if a saving was made. -
TimeStamp
which is a class that contains the time in which theSavings
object was created, i.e. what time the saving/withdrawal was made by the user. -
isWithdraw
which is a boolean that istrue
if theSavings
object is a withdrawal, and false if the object is a saving.
The SavingsHistory
Class Diagram is shown below to reflect the interactions above.
4.4.3. Making a saving in $aveNUS
When a user requests for a saving to be made in the application by calling the save AMOUNT
command, the following processes
will occur internally within the application:
-
The
AMOUNT
specified by the user is deducted from the user’s wallet. -
The
AMOUNT
specified by the user is added into the user’s saving account. -
The saving is reflected in the savings history.
If the user enters an AMOUNT that is negative, a ParseException is thrown. This exception is thrown from ParserUtil#parseSavings(savings) .
|
Deduction of savings amount from user’s wallet
A Savings
object is instantiated in the SaveCommand
with the user’s input. It contains information about how much Money
was saved, and the TimeStamp
of the savings made. The Money
object contains the amount of savings made, and the TimeStamp
object contains the time and date the saving was made by the user.
When SaveCommand#execute()
is called, model#depositInSavings(savingsAmount)
is called. Within the Model
of $aveNUS, the user’s
wallet is checked. If the user attempts to save more money than he/she has in his/her wallet, they will be prompted that the command
is invalid.
If the check passes, the saving amount is deducted from the user’s account within the Model
.
Adding of savings amount into user’s savings account.
Subsequently after the savings amount is deducted from the wallet, savingsAccount#addToSavings(savings)
is called in the Model
class. This method call will result in the addition of the savings into the SavingsAccount
.
Adding of the savings into the user’s saving history
After the savings is added into the user’s savings account, model#addToHistory(savingsAmount)
is called, and the details
of the savings are added into the SavingsHistoryList
, found within the SavingsHistory
object.
The following sequence diagram shows how the making of a saving works:
The following activity diagram summarises what happens when a user executes a SaveCommand
:
Design Considerations
Detailed below are the design considerations taken into account when engineering the
model#depositInSavings(savingsAmount)
function.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Make
|
Make
|
4.4.4. Making a withdrawal from user’s savings account.
Similar to making a saving, the following steps occur when the user requests for a withdrawal.
-
The
AMOUNT
specified by the user is deducted from the user’s savings account. -
The
AMOUNT
specified by the user is added into the user’s wallet. -
The withdrawal is reflected in the savings history.
If the user enters an AMOUNT that is negative, a ParseException is thrown. This exception is thrown from ParserUtil#parseSavings(savings) .
|
Deduction of withdrawal amount from user’s savings account.
A Savings
object is instantiated in the WithdrawCommand
with the user’s input. Because a withdrawal is subtracted from the savings account, the withdrawal is a Savings
object
recorded with a negative value.
It contains information about how much Money
was withdrawn, and the TimeStamp
of the withdrawal made. The Money
object contains the amount to withdraw, and the TimeStamp
object contains the time and date the withdrawal was made by the user.
When WithdrawCommand#execute()
is called, model#withdrawFromSavings(savings)
is called. Within the Model
of $aveNUS, the user’s
savings account is checked. If the user attempts to withdraw more money than he/she has in the savings account, they will be prompted that the command
is invalid.
Once the check is passed, a check is done in the wallet described in the section below.
Adding of savings amount into user’s wallet.
Before the savings amount is deducted from the savings account, a check is done to make sure that the addition of the withdrawal into the user’s wallet will not result in the budget amount in the wallet to be great than $1,000,000.
Only when this check is passed, the savings amount is added into the user’s wallet, using the command wallet#setRemainingBudget(newRemainingBudget)
.
The savings amount is subsequently deducted from the savings account using the command savingsAccount#deductFromSavings(savings)
. Note again that the savings
is of a negative value since this is a withdrawal.
Adding of the withdrawals into the user’s saving history
After a successful deduction from the savings account, model#addToHistory(savingsAmount)
is called, and the details
of the savings are added into the SavingsHistoryList
, found within the SavingsHistory
object.
Design Considerations
Detailed below are the design considerations taken into account when engineering the
Savings
class.
Alternative 1 | Alternative 2 (Chosen Implementation) |
---|---|
Make a
|
Make a
|
4.4.5. Viewing only the savings/withdrawals made by the user.
Upon the request of the user to display only the savings he/she makes using, for example, show savings
, a
ShowCommand
object is created. It is then executed in the application’s Logic
, and this returns
a CommandResult
which is instantiated with two booleans: showSavingsOnly
and showWithdrawalsOnly
.
Within the MainWindow
class of the application, these booleans are checked upon the calling of mainWindow#executeCommand(commandText)
.
In the example of the show savings
input by the user, showSavingsOnly
will be true
and showWithdrawalsOnly
will be false
.
This leads to the call to ReadOnlySavingsHistory#getSavingsOnly()
method within the ReadOnlySavingsHistory
class. This call
returns an unmodifiable list of savings made by the user thus far.
Finally, the savings history panel in the MainWindow
class is updated with this unmodifiable list. The result
is that the user will see only his savings being displayed within the application’s savings history panel.
4.5. Budget Tracking feature
The Budget Tracking feature allows users to manage their budget for food expenditure. It keeps the user updated with regards to the amount as well as the duration left for their current budget. Budget information is also used in the application’s recommendation system to suggest appropriate food items within a user’s budget.
4.5.1. Classes for Budget Tracking feature in Model
The Budget Tracking feature was implemented with the following classes.
RemainingBudget
Also, in order to avoid possible loss of precision errors, a Money class is used. This design decision is discussed below.
|
DaysToExpire
Due to development constraints, we have decided to only update the budget duration every time the application is started up instead of constantly monitoring the actual time to update this property. |
Design considerations
Below are some design considerations when implementing Budget-related models.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Creating a
|
Making use of Java’s primitive
|
We chose alternative 1 mainly due to the fact that the loss of precision is extremely apparent to the user. From our testing, after the user buys 3 or 4 items, the precision error adds up resulting in a difference of 1 cent between the expected and actual budget amount. This may confuse the user and is a bug that we wish to avoid.
4.5.2. Setting a Budget
A budget can be set using the budget
command which takes a budget amount, and an optional budget duration.
For example, budget 100 10
will set the user’s budget, with $100.00 for 10 days.
The sequence diagram for interactions between the Logic, Model and Storage components when a user executes the buy 1
command is shown below.
budget 100 10
commandThe following activity diagram below summarizes how budget
commands works
budget
command4.6. Purchase Tracking feature
The Purchase Tracking feature allows users to monitor their food expenditure by keeping track of their purchase history.
Whenever a user’s executes the buy
command, the corresponding food will be added as a purchase to the purchase history.
A user’s purchase history is displayed in the UI, similar to the food menu, and is updated every time a change is made to it.
4.6.1. Classes for Purchase Tracking feature in Model
The Purchase Tracking feature was implemented with the following classes.
Design considerations
Below are some design considerations when implementing Purchase-related models.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Storing the purchased food item as a
|
Creating an additional immutable
|
We chose alternative 1 due to the fact that Food
is implemented as a immutable class.
Therefore, we are able to get the required from the Food
object the user selected to construct the new purchased Food
object.
And at the same time, ensure the data integrity of that information.
Also, by encapsulating the Food
object within the Purchase
object and making it a private field, we are able to ensure that the purchased Food
is not modified in an unintended manner.
4.6.2. Buying a Food Item
The user may want to buy a food item and record the purchase. This can be done with the buy
command.
After the users enters a buy
command, for example, buy 1
which buys the first item in the displayed list.
The sequence diagram for interactions between the Logic, Model and Storage components when a user executes the buy 1
command is shown below.
buy 1
commandThe following activity diagram below summarizes how buy
commands works
buy
command4.7. Recommendation feature
The recommendation feature allows users to generate a list of personalized recommendations using the recommend
command.
The recommendations are tailored to each user based on the factors below:
Users are able to add their liked and disliked categories, tags and locations into the app using the like
and dislike
command. The recommendation system will then take into account these preferences, in addition to their purchase history,
to generate a more accurate list of recommendations. Users will then able to find the food that they are likely to enjoy
more accurately.
4.7.1. Classes for recommendation feature in Model
With the addition of the recommendation feature, a new set of classes were implemented to support the feature.
A singleton class RecommendationSystem
encapsulates the methods and classes related to this feature.
This RecommendationSystem
class implements the interface Recommender
which specifies the behaviour of the system,
and include the ability to generate recommendation values.
Additionally, the RecommendationSystem
class also contains a class UserRecommendations
, which include the likes and
dislikes of the user. The functionality of UserRecommendations
is specified by the Recommendations
interface,
encompassing the ability to add and remove likes and dislikes, among other features.
The UserRecommendations
object contain 6 sets, namely:
The items stored in the sets are first converted to lowercase before adding them so that a case-insensitive comparison can be done more easily.
The class diagram below illustrates the relationship between the classes.
4.7.2. Adding likes and dislikes
The feature of adding the user’s liked and disliked categories, tags and locations was introduced to support the recommendation system.
The UserRecommendations
class stores the list of user likes and dislikes as a set in lowercase to prevent duplicates.
Furthermore, the user’s likes and dislikes are integrated with Storage
for the user’s convenience.
The sequence diagram below shows how a sample like
command is executed:
The following activity diagram below summarizes how the like
command works:
A similar execution sequence is performed for a dislike command by replacing instances of like with dislike and vice versa.
4.7.3. Displaying recommendations
After customizing the user’s likes and dislikes, users can obtain a personalized list of recommendations
using the recommend
command.
Each food is passed to the recommendation system and it calculates the recommendation value based on the tables below:
Condition 1 | Condition 2 | Bonus |
---|---|---|
Food tags match the user’s liked tags |
1 or more tags |
+0.05 and +0.03 per matching tag |
3 or more tags |
+0.10 and +0.03 per matching tag |
|
5 or more tags |
+0.25 and +0.03 per matching tag |
|
Food category matches the user’s liked categories |
N/A |
+0.15 |
Food location matches the user’s liked locations |
N/A |
+0.10 |
Food tags match the user’s tags in purchase history |
N/A |
+0.01 per matching tag |
Food category matches the user’s categories in purchase history |
N/A |
+0.02 |
Food location matches the user’s locations in purchase history |
N/A |
+0.03 |
Food purchase is found in user’s purchase history |
2 or more purchases |
+0.10 |
5 or more purchases |
+0.30 |
|
10 or more purchases |
+0.50 |
Condition 1 | Condition 2 | Penalty |
---|---|---|
Food price is out of the user’s current budget |
N/A |
Removes the item from the recommendation results |
Food tags match the user’s disliked tags |
1 or more tags |
-0.10 and -0.10 per matching tag |
2 or more tags |
-0.30 and -0.10 per matching tag |
|
3 or more tags |
-0.50 and -0.10 per matching tag |
|
Food category matches the user’s disliked categories |
N/A |
-0.40 |
Food location matches the user’s disliked locations |
N/A |
-0.30 |
Food purchase is found in user’s purchase history |
Within a time period of <2 days |
Applies a decreasing penalty from -10 which diminishes to 0 after 2 days |
The activity diagram below shows how a sample Food is passed into RecommendationSystem
to output a
recommendation value:
RecommendationSystem#calculateRecommendation
The calculated recommendation values are used to sort the food items when the recommend
command
is executed. Food with similar recommendation values are sorted based on their price.
4.7.4. Design Considerations
Detailed below are the design considerations taken into account when engineering the Recommendation System.
Design Considerations of RecommendationSystem
class
Explained below are our considerations when designing the RecommendationSystem
class.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Implement
|
Implement
|
Design Considerations of RecommendationSystem#calculateRecommendation()
function.
Explained below are our considerations when designing the RecommendationSystem#calculateRecommendation()
function.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Declare a static method
|
Have a field (e.g.
|
4.8. Alias feature
The alias feature allows users to generate their own unique aliases for commands in $aveNUS. This can be
done using the alias
command.
4.8.1. Classes for alias feature in Model
With the addition of the alias feature, a new set of classes were implemented to support the feature.
A class AliasList
encapsulates methods and classes related to this feature. It contains a list of
AliasPair
which stores the alias word, if any, mapped to the command word in the AliasList
.
Additionally, an AliasChecker
is implemented to help check the validity of the AliasPair
in the
AliasList
. Finally, a CommandWordAdder
was implemented such that on the creation of a new AliasList
,
all command words in $aveNUS will have their mapped alias words removed, being replaced by an empty String.
The class diagram below illustrates the relationship between the classes.
4.8.2. Mapping an alias word to a command word.
The main command behind the alias feature is alias
. The user can assign an alias word to a command word
in $aveNUS, as long as the alias word is alphanumeric.
For example, alias sort s
sets the alias word for the command word sort
to become s
.
The sequence diagram below shows how a sample alias
command is executed.
The following activity diagram below shows how the alias
command works.
4.9. Info Feature
The info feature allows users to view more information about a particular command. This can be done through calling the
info
command.
4.9.1. Classes for info feature in Model
In order to support the info
command, new classes were added that correspond to each command in the app.
This is to allow for easy access and also to better organize the information to be displayed for each commands.
These classes only has public fields without any methods as it is only needed by InfoWindow
to access the information
Here are some example classes: AddInfo
, AliasInfo
, AutoSortInfo
, BudgetInfo
Each of these classes contains these fields:
-
Command Word
-
Information
-
Usage example
-
Expected Output
Each of these fields have to be manually typed as it acts like an in-built user guide for the users who do not have access to internet connection, but would still like to know about the command.
4.9.2. Opening info card about a command
Command word to open up a new InfoWindow
is info
. Users are able to view the InfoWindow
for each and every command
as long as the command exist within the app (There exist the class for such info card in the model).
For example, info add
will open up a new InfoWindow
about the command add
.
The sequence window below shows how a sample info
command is executed:
info
commandThe following activity diagram shows how the info
command works.
info
command4.9.3. Design Considerations
Detailed below are our considerations when creating the Info feature.
Design considerations of InfoCommand
class
Explained below are our consideration when designing the InfoCommand
class.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Returning a specific message for each input command to create specific
|
Simply returning the input command’s output message.
|
We decided to go with alternative 1 and implemented InfoCommand
class to just return a specific message for each
and every input command. Not only that this is much safer but this also allows similar commands to have standardized
output message e.g. like
command and dislike
command.
The hassle of having to create new field and object to be returned for every new command added is mitigated by the fact that we will not have to worry about similar output message for the commands resulting in a bug in the info feature.
Additionally, this implementation also allows a unique output message to be displayed on the ResultDisplay
which
definitely adds more personality to the program and allows a better, more informative feedback to user to be displayed.
Basically the benefits of this implementation as mentioned, greatly outweighs the cost it takes to implement it and subsequent command addition would be much easier as it’s just repeated routine.
Design considerations on how to display the command’s information
Explained below are our consideration when designing on the best way to display the command’s information.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Creating
|
Displaying the information on the
|
We decided to go with alternative 1 where we just create a new window called InfoWindow
to display the information.
Firstly, this allows us to format the information in a way to make it easy for the users to understand. Secondly, we
are able to fit so much more information in the new window and even possibly image guides which would definitely
help users understand the commands better.
Displaying the information on the ResultDisplay
simply would not be helpful enough to the users especially with
information-heavy commands such as add
and edit
which would definitely not fit the small ResultDisplay
screen.
4.10. Theme Feature
The theme feature allows users to change the look of the app. This can be done through calling the
theme
command.
4.10.1. Changing the theme of the app
Command word to change the theme is theme
. Currently there are only two themes available which are light and dark
theme. As such, the only valid commands are theme dark
or theme light
.
The sequence window below shows how a sample theme
command is executed:
theme
commandThe following activity diagram shows how the theme
command works.
theme
command4.10.2. Design Considerations
Detailed below are our considerations when creating the Theme feature.
Design considerations on how to implement the theme change
Explained below are our consideration when designing method to change the theme.
Alternative 1 (Chosen Implementation) | Alternative 2 |
---|---|
Using CSS styling and just simply changing the
|
Creating a class that automates the theme change according to color scheme.
|
We decided to go with alternative 1 and utilize StyleSheet
instead of creating a class that automates the theme change.
This greatly improve the customization between themes and much easier to implement. THe greater customization option
allows for different themes to look distinct and also look better.
This implementation also allows for better organization of the themes which makes it easy to keep track of the different themes available. The space taken up by repeated components is not that heavy since the app itself is light.
5. Documentation
5.1. Introduction
We use asciidoc for writing documentation.
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. |
5.2. Editing Documentation
See UsingGradle.adoc to learn how to render .adoc
files locally to preview the end result of your edits.
Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc
files in real-time.
5.3. Editing Diagrams
See UsingPlantUml.adoc to find out how to create and update the UML diagrams in the developer guide.
5.4. Publishing Documentation
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
5.5. Converting Documentation to PDF format
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.
Here are the steps to convert the project documentation files to PDF format.
-
Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the
docs/
directory to HTML format. -
Go to your generated HTML files in the
build/docs
folder, right click on them and selectOpen with
→Google Chrome
. -
Within Chrome, click on the
Print
option in Chrome’s menu. -
Set the destination to
Save as PDF
, then clickSave
to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.
5.6. Site-wide Documentation Settings
The build.gradle
file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.
Attributes left unset in the build.gradle file will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
The name of the website. If set, the name will be displayed near the top of the page. |
not set |
|
URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar. |
not set |
|
Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. |
not set |
5.7. Per-file Documentation Settings
Each .adoc
file may also specify some file-specific asciidoc attributes which affects how the file is rendered.
Asciidoctor’s built-in attributes may be specified and used as well.
Attributes left unset in .adoc files will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
Site section that the document belongs to.
This will cause the associated item in the navigation bar to be highlighted.
One of: * Official SE-EDU projects only |
not set |
|
Set this attribute to remove the site navigation bar. |
not set |
5.8. Site Template
The files in docs/stylesheets
are the CSS stylesheets of the site.
You can modify them to change some properties of the site’s design.
The files in docs/templates
controls the rendering of .adoc
files into HTML5.
These template files are written in a mixture of Ruby and Slim.
Modifying the template files in |
6. Testing
6.1. Running Tests
There are two ways to run tests.
Method 1: Using IntelliJ JUnit test runner
-
To run all tests, right-click on the
src/test/java
folder and chooseRun 'All Tests'
-
To run a subset of tests, you can right-click on a test package, test class, or a test and choose
Run 'ABC'
Method 2: Using Gradle
-
Open a console and run the command
gradlew clean test
(Mac/Linux:./gradlew clean test
)
See UsingGradle.adoc for more info on how to run tests using Gradle. |
6.2. Types of tests
We have three types of tests:
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.savenus.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.savenus.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g.seedu.savenus.logic.LogicManagerTest
6.3. Troubleshooting Testing
Problem: Keyboard and mouse movements are not simulated on macOS Mojave, resulting in GUI Tests failure.
-
Reason: From macOS Mojave onwards, applications without
Accessibility
permission cannot simulate certain keyboard and mouse movements. -
Solution: Open
System Preferences
, clickSecurity and Privacy
→Privacy
→Accessibility
, and check the box besideIntellij IDEA
.
Accessibility
permission is granted to IntelliJ IDEA
7. Dev Ops
7.1. Build Automation
See UsingGradle.adoc to learn how to use Gradle for build automation.
7.2. Continuous Integration
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
7.3. Coverage Reporting
We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.
7.4. Documentation Previews
When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.
7.5. Making a Release
Here are the steps to create a new release.
-
Update the version number in
MainApp.java
. -
Generate a JAR file using Gradle.
-
Tag the repo with the version number. e.g.
v0.1
-
Create a new release using GitHub and upload the JAR file you created.
7.6. Managing Dependencies
A project often depends on third-party libraries. For example, $aveNUS depends on the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
-
Include those libraries in the repo (this bloats the repo size)
-
Require developers to download those libraries manually (this creates extra work for developers)
Appendix A: Product Scope
Target user profile:
-
Has a need to manage a significant number of food items
-
Has trouble tracking expenditure over a period of time
-
Wants to know how much money they have saved
-
Wants meal recommendations within their specified budget
-
Prefer desktop apps over other types
-
Can type fast
-
Prefers typing over mouse input
-
Reasonably comfortable using CLI apps
-
Able to read graphs and data.
Value proposition: manage expenditure and get recommendations faster than a typical mouse/GUI driven app
Appendix B: User Stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a / an … | I want to … | So that I can… |
---|---|---|---|
|
forgetful user |
track my expenditure for the day using the app |
know how much I have already spend in the day and plan for the remaining meals of the day. |
|
greedy user |
have reminders when I am about to hit the limits I set for my expenditure |
plan my budget better for the weeks ahead. |
|
meticulous user |
sort the food items based on categories |
see which items are the best or worst based on certain categories. |
|
new user |
view more information about command |
learn how to use the app and its features. |
|
user |
add a food item by specifying the item, price, description and category |
update the food items that are available to me if I find new food item options within NUS. |
|
user |
know the total amount I have spent |
plan my finances for the remaining days of the month. |
|
user |
only be able to see what I can afford for meals and beverages |
save time scrolling through meals that fit my budget. |
|
user |
possess the ability to update existing food entries |
update the existing food entries if there are changes in their prices/descriptions. |
|
advanced user |
shorten my commands |
type faster and more efficiently. |
|
careful user |
have a calendar function |
keep track of the progress of my spending for the current month |
|
japanese food lover |
prioritise Japanese food options over other similarly priced products |
find specific food types of our choice/cravings we have. |
|
lazy user |
have autocomplete |
find food items without having to type long keywords. |
|
lazy user |
load and save data from other computers |
transfer data to an application onto another desktop. |
|
lazy user |
obtain a recommended meal plan according to a specified daily allowance |
save the time of having to plan for my meals for the day. |
|
non-tech savvy user |
have an easier way to understand how the works eg. through a video |
use the app effectively without having to read long user guides. |
|
slow user |
a guided tutorial to bring me through the basic functionality of the program |
become more familiar with the program before I start using. |
|
smart user |
put aliases to the commands available in the application |
personalise the app and use it more effectively. |
|
user |
add my savings of the month into a customised fund |
purchase rewards/gifts/items that I require when I have saved enough for them. |
|
user |
know the opening and closing timings of the food stores in NUS |
closed shops are not recommended to me to prevent me from wasting time to travel to these shops. |
|
user with dietary/religious restrictions |
exclude meals that do not fulfil my dietary requirements |
reduce my options to only meals that I can consume. |
|
dyslexic user |
an app with easy to read font |
use the app comfortably with being hindered by my reading disabilities. |
|
forgetful user |
save specific meal sets to reuse |
save time on inputting my meals daily. |
|
user |
add the birthdays of my friends |
set up reminders to buy gifts for my friends. |
|
user |
receive sample suggestions and examples to understand how to use the program. |
understand how to user the application effectively. |
|
user who hates travelling |
sort places from the nearest to the furthest from my current location |
find food places that are easy for me to get to. |
|
user who loves to customize things |
have a theme changing function of the app from a list of themes available |
personalise the app to a theme that I like. |
|
user with ADHD |
an app with simple commands and UI |
use it comfortably without major distractions. |
Appendix C: Use Cases
(For all use cases below, the System is the Menu
and the Actor is the user
, unless specified otherwise)
Use case: Add food item
MSS
-
User requests to add food item, providing details such as price, description, category, location and so on.
-
$aveNUS adds the food item.
Use case ends.
Extensions
-
1a. The details are given in the wrong format or mandatory fields are omitted.
-
1a1. $aveNUS diplays an error message.
Use case resumes at step 1.
-
Use case: Delete food item
MSS
-
User requests to delete a specific food item in the observable food list.
-
$aveNUS deletes the food item.
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
3a1. $aveNUS shows an error message.
Use case resumes at step 2.
-
Use case: Search food item
MSS
-
User requests to search for a food item, providing a search query.
-
$aveNUS shows a list of filtered food items according to their provided query.
Use case ends.
Extensions
-
2a. User requests to add food item to their expenditure from the search results.
-
2a1. $aveNUS adds the food item to their expenditure.
Use case ends.
-
-
2b. User requests to search without providing any query.
-
2b1. $aveNUS displays the normal ordering of food items.
Use case ends.
-
Use case: Add likes / dislikes
MSS
-
User requests to add likes or dislikes, providing the list of liked categories, tags and locations.
-
$aveNUS adds the likes or dislikes into the system.
Use case ends.
Extensions
-
1a. The user attempts to add a like that already exists as a dislike, or vice versa.
-
1a1. $aveNUS shows an error message.
Use case ends.
-
-
1b. The given categories, tags or locations are invalid.
-
1b1. $aveNUS shows an error message.
Use case ends.
-
Use case: Display recommendations
MSS
-
User requests to list recommendations.
-
$aveNUS shows a list of recommended food items based on their specified budget.
-
User requests to add a specific food item in the list into purchased food items.
-
$aveNUS adds the food item into list of purchased food items.
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
3a1. $aveNUS shows an error message.
Use case resumes at step 2.
-
Use case: Save money into a savings account
MSS
-
User requests to add savings to Savings Account.
-
$aveNUS adds the list of savings in the Savings History display tab.
Use case ends.
Extensions
-
1a. The wallet is empty.
-
1a1. $aveNUS shows an error message to tell the user to topup the wallet.
-
1a2. $aveNUS displays Savings History display tab.
-
1a3. $aveNUS displays an empty command box.
Use case ends.
-
Use case: Set budget
MSS
-
User requests to set budget.
-
$aveNUS calculates the daily budget based on the specified weekly budget.
Use case ends.
Extensions
-
1a. The budget set is invalid (such as a negative number).
-
1a1. $aveNUS shows an error message.
Use case resumes at step 1.
-
Use case: Set Aliases
MSS
-
User requests to set an alias, providing a command word and an alias word.
-
$aveNUS checks if the command and alias words are valid, and sets the alias word of the command word.
Use case ends.
Extensions
-
1a. The alias word given happens to be a command word.
-
1a1. $aveNUS shows an error message.
Use case resumes at step 1.
-
-
1b. The command word given is not a valid command word of $aveNUS.
-
1b1. $aveNUS shows an error message.
Use case resumes at step 1.
-
Appendix D: Non Functional Requirements
-
Should work on any mainstream OS that has JDK 11 installed.
-
Should be usable by user with novice computing experience.
-
Should be able to respond to user input within 2 seconds.
-
Should be able to run fullscreen without any UI issues.
-
Should be able to read easily by users.
Appendix F: Instructions for Manual Testing
Given below are instructions to test the app manually.
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing. |
F.1. Launch and Shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a set of sample food items. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
F.2. Adding a Food Item
-
Adding a Food Item
-
Prerequisites: The food list in the menu has to be empty.
-
Test case:
add n/Chicken Rice p/5.00 c/Chinese
Expected: A new food item is shown in the food list. Details of the food item will be shown to the user. -
Test case:
add n/Chicken Rice p/5.00 c/Chinese
Expected: An error message will appear. The duplicate food item will not be added as the food item has been already added from the first test case. -
Test case:
add n/Chicken Rice p/5.00
Expected: An error message will appear. The food needs to have the compulsory field ofCATEGORY
.
-
F.3. Deleting a Food Item
-
Deleting a Food Item
-
Prerequisites: There has to be a food item in the menu for the user to delete.
-
Test case:
delete 1
Expected: First food item is deleted from the food list. A confirmation message will be shown to indicate successful deletion. -
Test case:
delete 0
Expected: No food item is deleted. Error details shown in the status message. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size)
Expected: Similar to previous.
-
F.4. Adding like and dislikes
-
Adding likes and dislikes
-
Test case:
like c/chinese t/spicy l/location
Expected: The provided categories, tags and locations are added into the like list. Details of the list of likes and dislikes are shown in the status message. -
Test case:
dislike c/chinese
(after running the above command)
Expected: The dislike is not added because the categorychinese
already exists in the user’s likes.
-
F.5. Removing likes and dislikes
-
Removing likes and dislikes
-
Prerequisites: There has to be a like or dislike in the system for the user to remove.
-
Test case:
removelike c/chinese
Expected: The categorychinese
is deleted from the user’s likes. Details of the list of likes and dislikes are shown in the status message. -
Test case
removelike c/chinese
(after running the above command)
Expected: The command fails because there is no morechinese
category present in the user’s likes.
-
F.6. Clearing likes and dislikes
-
Clearing likes and dislikes
-
Prerequisites: Optimally, there should be likes or dislikes present in the system for the user to remove. However, the command still works even if there are no likes or dislikes present.
-
Test case:
removelike ALL
Expected: All likes are cleared. -
Test case
removedislike ALL
Expected: All dislikes are cleared.
-
F.7. Obtaining a list of recommendations
-
Obtaining a list of recommendations
-
Prerequisites: Budget and days to expiry of the budget are set by the user. The budget should also be set high enough such that the user is able to purchase food items when the recommendation system calculates the optimal daily budget.
-
Test case:
recommend
Expected: A list of recommendations tailored to the user will appear, taking into account factors such as the user’s liked and disliked categories, tags and locations and so on. All factors affecting the recommendation system’s recommendation value are documented in the User Guide and Developer Guide.
-