Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
(consisting of classes Main
and MainApp
) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI
: The UI of the App.Logic
: The command executor.Model
: Holds the data of the App in memory.Storage
: Reads data from, and writes data to, the hard disk.Commons
represents a collection of classes used by multiple other components.
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
.
Each of the four main components (also shown in the diagram above),
interface
with the same name as the Component.{Component Name}Manager
class (which follows the corresponding API interface
mentioned in the previous point.For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PrescriptionListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI.
The UI
component uses the 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,
Logic
component.Model
data so that the UI can be updated with the modified data.Logic
component, because the UI
relies on the Logic
to execute commands.Model
component, as it displays Prescription
object residing in the Model
.API : Logic.java
Here's a (partial) class diagram of the Logic
component:
The sequence diagram below illustrates the interactions within the Logic
component, taking execute("delete 1")
API call as an example.
Note: 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.
How the Logic
component works:
Logic
is called upon to execute a command, it is passed to an PrescriptionListParser
object which in turn creates a parser that matches the command (e.g., DeleteCommandParser
) and uses it to parse the command.Command
object (more precisely, an object of one of its subclasses e.g., DeleteCommand
) which is executed by the LogicManager
.Model
when it is executed (e.g. to delete a prescription).CommandResult
object which is returned back from Logic
.Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
PrescriptionListParser
class creates an XYZCommandParser
(XYZ
is a placeholder for the specific command name e.g., AddCommandParser
) which uses the other classes shown above to parse the user command and create a XYZCommand
object (e.g., AddCommand
) which the BayMedsParser
returns back as a Command
object.XYZCommandParser
classes (e.g., AddCommandParser
, DeleteCommandParser
, ...) inherit from the Parser
interface so that they can be treated similarly where possible e.g, during testing.API : Model.java
The Model
component,
Prescription
objects (which are contained in a UniquePrescriptionList
object).PrescriptionList
objects to keep track of existing and completed prescriptions.Prescription
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Prescription>
that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.UserPref
object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref
objects.Model
represents data entities of the domain, they should make sense on their own without depending on other components)API : Storage.java
The Storage
component,
Storage
and UserPrefsStorage
, which means it can be treated as either one (if only the functionality of only one is needed).Model
component (because the Storage
component's job is to save/retrieve objects that belong to the Model
)Classes used by multiple components are in the seedu.address.commons
package.
This section describes some noteworthy details on how certain features are implemented.
The UI consists of a MainWindow
component that serves as the parent for other JavaFX components as explained here.
Upon initialising BayMeds for the first time, UIManager
will call the fillInnerParts
method of MainWindow
which also executes the scaleScreen
method to scale the UI according to the user's screen size.
The screen size is scaled strictly by height based on a 16:9 aspect ratio. The final size will then be saved in preferences.json
.
Aspect: Resizing the UI
The add prescription feature is facilitated by the AddCommandParser
.
Given below is an example usage scenario and how the add prescription mechanism behaves at each step.
Step 1. The user types the command add mn/Propranolol
. Upon pressing enter, the Ui
triggers the execute
method in Logic
, passing the input text to the PrescriptionListparser
in Logic
. The PrescriptionListParser
then checks the command type to determine which command parser to call.
Step 2. Upon checking that it is an add
command, the AddCommandParser
will be created to parse
the input text. It creates an argMultiMap
, which contains the mappings of each recognised prefix in the input text, and its associated value.
Note: If there are absent prefixes that are compulsory, it will throw a ParseException
error.
Step 3. The AddCommandParser
will then create a new Prescription
object using the argMultiMap
with the prefix fields and values present. Optional fields which were not provided will be initialised as null
. The AddCommandParser
subsequently returns a new AddCommand
object with the prescription to be added stored as an attribute.
Note: For the prescription's startDate
, it will be initialised to LocalDate.now()
if no start date is given, using the date of creation as the default startDate
value.
Step 4: Logic
then calls AddCommand
's execute
. This will call Model
's addPrescription
method, passing the prescription to be added. The Model
will interact with the in-memory PrescriptionList
, adding the prescription into it. Finally, the Model
sets the filteredPrescriptions
to show all prescriptions in the existing PrescriptionList
.
The following sequence diagram shows how the add
operation works.
The following activity diagram summarizes what happens when the user executes an add
command.
Design considerations:
Prescriptions may have an uneven consumption interval. For example, some prescriptions only needs to be consumed every Wednesday and Sunday. In such scenarios, users will need to input this prescription as 2 separate inputs with the same prescription name.
To cater for this, we are using every prescription's name
and startDate
to identify each prescription. Every Prescription
must therefore have both these fields. As such, if no start date was provided, it will be initialised to a default value.
To cater to fast typists, we have made most of the input fields optional, so that they only need to put in minimal data in order to successfully add a prescription. The only compulsory field is the medication name of the prescription.
The Edit Command is a fundamental feature of the BayMeds application, allowing users to modify the details of a prescription. It leverages the EditPrescriptionDescriptor
and follows a specific edit mechanism.
EditPrescriptionDescriptor
The EditPrescriptionDescriptor
is a crucial component for processing edit commands. It serves as a container for holding the new values that the user wishes to apply during the edit operation. This descriptor is essential for maintaining consistency and ensuring that only valid changes are made to a prescription.
The following class diagram shows the relationship between the EditPrescriptionDescriptor
and the EditCommand
class.
Shown is an example of the usage of the edit command, which shows the EditPrescriptionDescriptor in action.
Step 1. User initiates an edit command, e.g., edit 1 mn/UpdatedMedicationName d/3
.
Step 2. The EditCommandParser
processes the user's input, extracting the prescription index and the edits to be made.
Step 3. The EditCommandParser
then creates an instance of the EditPrescriptionDescriptor
, populating it with the provided changes. In the example above, the medication name (mn
) is updated to "UpdatedMedicationName," and the dosage (d
) is updated to "3".
Step 4. The EditCommandParser
then creates an instance of the EditCommand
class, passing in the prescription index and the EditPrescriptionDescriptor
instance.
Step 5. The EditCommand
class then calls the execute
method, which carries out checks to ensure that the prescription index is valid. If the prescription index is invalid, an error message is returned to the user.
Step 6. The 'EditCommand' class uses the EditPrescriptionDescriptor
instance and the Prescription to be edited to create a new Prescription with the edited fields.
Step 7. The edited prescription is then checked to ensure that it is valid. If the edited prescription is invalid, an error message is returned to the user.
Step 8. The EditCommand
class then calls the editPrescription
method in the Model
class, passing in the prescription index and the edited prescription.
Note: The untake feature is similar to the take feature but reverses the incrementing and decrementing of consumption count and stock respectively, given below is the guide for the take feature.
The take prescription feature is facilitated by the TakeCommandParser
.
Given below is an example usage scenario and how the take prescription mechanism behaves at each step.
Step 1. The user types the command take 1 d/4
. Upon pressing enter, the Ui
triggers the execute
method in Logic
, passing the input text to the PrescriptionListparser
in Logic
. The PrescriptionListParser
then checks the command type to determine which command parser to call.
Step 2. Upon checking that it is a take
command, the TakeCommandParser
will be created to parse
the input text. It creates an argMultiMap
, which contains the mappings of each recognised prefix in the input text, and its associated value.
Note: If no dosage is specified, the default dosage to take will be 1.
Step 3. The TakeCommandParser
subsequently returns a new TakeCommand
object with the index of the prescription to take and the dosage to take.
Step 4: Logic
then calls TakeCommand
's execute
. This will call Model
's getFilteredPrescriptionList
method and retrieve the prescription based on the index from the in-memory PrescriptionList
, necessary checks are done to check if the index is valid and there is enough stock.
TakeCommand
's executeTake is then called, incrementing the consumption count and decrementing the stock of the prescription. Finally, the completed status is set based on the consumption count and dosage of the prescription and the Model
sets the filteredPrescriptions
to show the prescription that was just taken from.
The following class diagram shows the classes associated with executing a take command.
Design considerations:
Prescriptions may have duplicate names but different start dates.
To cater for this, the take command uses the index instead of the name of the medication when executing.
The list today feature is facilitated by the ListTodayCommandParser
.
Given below is an example usage scenario and how the list today mechanism behaves at each step.
Step 1. Ui
and Logic
works similarly to when adding prescriptions. However, a ListTodayCommandParser
is created instead.
Note: If there are extra fields in the input, it will throw a ParseException
error.
Step 2. The ListTodayCommandParser
subsequently returns a new ListTodayCommand
object.
Step 3: Logic
then calls ListTodayCommand
's execute
. This will create a new IsTodayPredicate
object which is passed into the method call for Model
's updateFilteredPrescriptionList
.
Step 4: The Model
will then update the in-memory FilteredList<Prescription>
with the new predicate. Finally, the Model
sets the filteredPrescriptions
to show only today's prescription in the existing PrescriptionList
.
The following sequence diagram shows how the listToday
operation works.
The following activity diagram summarizes what happens when the user executes an listToday
command.
Design considerations:
As there are no input parameters for ListTodayCommand
, it is possible to implement this without ListTodayCommandParser
. However, ListTodayCommandParser
was created to utilise methods in argMultiMap
to check for extra fields.
The result of IsTodayPredicate
depends on the prescription's startDate
. It is intentional that monthly frequencies are based on increments of 30 days rather than the absolute day of each month.
To allow BayMeds to store a separate list of completed prescriptions to facilitate the listCompleted
feature, we have expanded on the existing model and storage components.
Apart from prescriptionList
, Model
also has an in-memory view of a completedPrescriptionList
. It has its own set of CRUD features for handling completedPrescriptionList
, and it handles completedPrescriptionList
similar to how it handles prescriptionList
.
Storage
also contains the implementation of the completedPrescriptionList
storage. The completed prescription list is stored in the file path data/completedPrescriptionList.json
, adjacent to the storage for prescriptionList
.
Upon start up of BayMeds or after every command, BayMeds will check through the list of current prescriptions against a new LocalDate.now()
to see if any of the end dates are beyond it (i.e. in the past). If it is, it will transfer the prescription over from one list to the other by deleting this prescription from the existing current prescriptionList
, and adding it to the the completedPrescriptionList
.
Design considerations:
We intentionally chose to store completed prescriptions in its own completedPrescriptionList.json
file, away from the original prescriptionList.json
. By separating them into two different storages, we help to enhance security of the data as if users were to accidentally manually delete one of the files, the other would still be in tact, and the user would not lose their data in its entirety.
The check prescription interaction feature is facilitated by the AddCommandParser
.
Given below is an example usage scenario and how the check prescription interaction mechanism behaves at each step.
Step 1. Ui
and Logic
works similarly to when adding prescriptions. It creates an argMultiMap
, which contains the mappings of each recognised prefix in the input text, and its associated value.
Note: If there are extra fields in the input, it will throw a ParseException
error.
Step 2. The AddCommandParser
subsequently returns a new AddCommand
object.
Step 3: Logic
then calls AddCommand
's execute
. The ModelManager will check through the Prescriptions in the PrescriptionList to ensure that there are no clashing Drugs.
Note: If the prescription to be added contains a drug that will clash with an existing prescription, an warning message is returned to the user. The prescription will still be added.
Step 4: The Model
will then update the in-memory FilteredList<Prescription>
with the new drugs.
The following object oriented domain model shows the class structure of the problem domain.
At the beginning of each day, BayMeds is able to automatically reset the consumption count
. This is so that you are able to track the consumption of your prescriptions each day, and that the consumption count will not be accumulated over multiple days.
The most recent date will be stored in the preferences.json
file. Upon start up of BayMeds or after every command, BayMeds will create a new LocalDate.now()
and compare it with the stored date
. If it is different, implying that it is a new day, the consumption count
will be reset and the stored date will be replaced with the new date.
Design considerations:
We considered implementing an alternative version, where users can call a reset
command, which will reset
the consumption counts of all the prescriptions. However, this would result in a very tedious task for the user of having to manually reset your consumption counts daily, which alot of users may forget. Thus, we went ahead with the above implementation, to ease the process of tracking prescription consumption.
Target user profile:
Value proposition:
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * | sickly patient | add prescriptions | manage additional prescriptions should I be prescribed them |
* * * | forgetful patient | mark the prescription as consumed or taken | not accidentally overdose on a certain prescription |
* * * | forgetful patient | list all my current prescriptions | track all the prescriptions I am currently on |
* * * | patient undergoing a tapering plan | edit prescriptions that I have added | adjust my dosage schedules easily |
* * * | patient who bought new medication | edit the number of pills I have of the prescription | accurately track the number of pills I have |
* * * | recovering patient | delete prescriptions | remove prescriptions that I no longer need to track |
* * * | clumsy patient | delete prescriptions | remove prescriptions that I erronously inserted |
* * * | patient or caregiver | list all the prescriptions to consume for the day | easily track which ones I/my patient need to consume today |
* * * | patient | list all prescriptions which I have completed in the past | show my healthcare providers my past prescriptions |
* * | patient or caregiver | note that important requirements | get reminded of them whenever I consume the prescription |
* * | patient | easily view how many pills I have remaining from a particular prescription I took in the past | check if I have enough to consume it again in the future should I need to |
* * | lazy patient or caregiver | only add in the prescription name without other details | easily add in prescriptions without knowing or remembering the accompanying details |
* * | buzy patient | easily check which pills are low in stock or expiring | get them replaced as soon as possible |
* * | clumsy patient | "unconsume" a prescription | unmark a prescription should I have accidentally marked it as consumed |
* * | patient with a lot of prescriptions | easily find prescriptions by keywords | view prescriptions even if I only remember part of the name |
* * | patient | track drugs that conflict with each prescription | track what drugs I should avoid for the prescription |
* * | patient visiting a pharmacist | easily view a list of drugs that conflicts with my current list of prescriptions | easily show the pharmacist what I should avoid |
* | busy patient | get notifications on my work desktop | get timely reminders even when I am preoccupied by work |
(For all use cases below, the System is the BayMeds
and the Actor is the user
, unless specified otherwise)
Use case: Edit a prescription
MSS
User requests to list prescriptions.
BayMeds shows a list of prescriptions as requested.
User requests to edit a specific prescription in the list with the edited details.
BayMeds edit the prescription.
Use case ends.
Extensions
1a. The given command is invalid.
1a1. BayMeds shows an error message.
Use case resumes at step 1.
2a. The list is empty.
Use case ends.
3a. The given prescription is not in the list.
3a1. BayMeds shows an error message.
Use case resumes at step 2.
3b. User did not give any edited details.
3b1. BayMeds shows an error message.
Use case resumes at step 2.
Use case: Delete a prescription
MSS
User requests to list prescriptions.
BayMeds shows a list of prescriptions as requested.
User requests to delete a specific prescription in the list.
BayMeds deletes the prescription.
Use case ends.
Extensions
1a. The given command is invalid.
1a1. BayMeds shows an error message.
Use case resumes at step 1.
2a. The list is empty.
Use case ends.
3a. The given prescription is not in the list.
3a1. BayMeds shows an error message.
Use case resumes at step 2.
Use case: List all prescriptions
MSS
User requests to list all prescriptions.
BayMeds shows a list of prescriptions as requested.
Use case ends.
Extensions
1a. The given command is invalid.
1a1. BayMeds shows an error message.
Use case resumes at step 1.
2a. The list is empty.
Use case ends.
Use case: List medications to be consumed today
MSS
User requests to list all remaining medications to be consumed today.
BayMeds shows a list of medications to be consumed today.
Use case ends.
Extensions
1a. The given command is invalid.
1a1. BayMeds shows an error message.
Use case resumes at step 1.
2a. The list is empty.
Use case ends.
Use case: Marking a particular prescription as taken
MSS
User requests to list prescriptions.
BayMeds shows a list of prescriptions as requested.
User indicates the number of doses consumed to mark the specified prescription.
BayMeds marks the specified prescription with the specified number of doses consumed.
BayMeds reduces the total number of pills of that prescription.
Use case ends.
Extensions
1a. The given command is invalid.
1a1. BayMeds shows an error message.
Use case resumes at step 1.
2a. The list is empty.
Use case ends.
3a. User did not indicate any dose quantity.
3b. User indicates a dose quantity that exceeds the total number of available pills in stock.
3b1. BayMeds shows an error message.
Use case resumes at step 2.
Use case: List conflicting drugs of a prescription
MSS
User requests to list prescriptions.
BayMeds shows a list of prescriptions as requested.
User requests to see conflicting drugs of a specified prescription.
BayMeds shows the conflicting drugs of that particular prescription.
Use case ends.
Extensions
1a. The given command is invalid.
1a1. BayMeds shows an error message.
Use case resumes at step 1.
2a. The list is empty.
Use case ends.
11
or above installed.Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
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 prescriptions. The window size may not be optimum.
Saving window preferences
Window size is automatically scaled strictly to a 16:9 ratio and saved upon start up. It will not be resizable.
If you are using a screen of a different size, delete the preferences.json
file and run BayMeds again.
Deleting a medication while all medications are being shown
Prerequisites: List all medications using the list
command. Multiple medications in the list.
Test case: delete 1
Expected: Aspirin is deleted from the list. Details of the deleted medication shown in the Result Display.
Test case: delete 9
Expected: No medication is deleted. Error details shown in the Result Display. List Display remains the same.
Other incorrect delete commands to try: delete
, delete x
, ...
Expected: Similar to previous.
Dealing with missing/corrupted data files
To simimulate a corrupted file, feel free to edit the data inside data/prescriptionList.json
and data/CompletedPrescriptionList.json
. You may change the format entirely, or input an invalid value for one of the fields.
By design, if there are issues with the stored data files, BayMeds will begin with an empty prescription list.
Given below are known issues and planned enhancements in the future.
take
more than the dosage stored in BayMeds, there will not be an error message to warn you. We will implement a fix for this in the future.Given below documents the tremendous efforts put in by each and every member of the BayMeds team.
We implemented a combined total of 14 command features in BayMeds, to ensure that users have a comprehensive way to interact with their prescription lists. Apart from the basic functionalities of adding, deleting, editing and listing prescriptions, we have also expanded the product with several features. Users can find prescriptions easily either by keyword, or those that they have to consume today. Users are also able to view a list of completed prescriptions. We have also implemented the underlying logic infrastructure to faciliate the tracking of conflicting drugs. We have also added filtering functionalities that filter prescriptions that are running low in stock or about to expire.
Apart from command related features, we have also implemented various implicit features, such as the automatic resetting of the consumption count. While we could have easily implemented an alternative version where users can just use the command reset, we decided to go above and beyond and make it much more convenient for users to track their prescriptions. By storing the relevant date information in the preferences.json, we enhanced the infrastructure to cater to the automatic date checking feature that occurs upon start up and after every command.
Another implicit feature is the conversion of every field into an optional field, with the exception of the name. This is so that fast typists, or anyone who would like a convenient way to type can very easily manage their prescriptions with just their medication name. This means that even if they are uncertain o various information, such as the end date of the prescription, or how many pulls they have left in total, they are still able to input the prescription and continue tracking it with minimal details. This also caters to people with prescriptions that are conusmed "as and when required", as it provides flexibility with the usage of Optionals
.
We also expanded on the existing storage and model infrastructure to allow for the managing, interacting and tracking of the list of completed prescriptions. While we could have simply combined the completed prescription list into the same json
as prescriptionList, we decided to put in the extra effort to separate them out, as mentioned in the design considerations section of the list completed feature. This involved adding new methods in both Model
and Storage
to interact with this completed prescriptions list.
We also went ahead to revamp the UI of BayMeds. In our attempt to improve the aesthetics and enhance the user interactions and user experience, we created an entirely new UI. Apart from making the commandResult
box more readable, we also included fields and their identifiers in the prescriptionCard
, organised in a neat format. Using our knowledge of the importance of colours in visual communication, we added a functionality to allow the UI to switch colours to indicate the consumption status of the prescription, using a more striking colour (red) to indicate that the prescription has yet to be consumed.
In line with efforts to enhance the aesthetics of the UI, we also proceeded to enhance the aesthetics of documentation. We adopted a consistent and aesthetic styling through the use of colours and icons, as well as formatting to improve the documentation from the original state to the new and improved BayMeds state.