Developer Guide
- Introduction
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
Introduction
This document is the developer guide for Nuudle, an appointment scheduling app for nurses.
This developer guide serves to provide developers with an understanding on how Nuudle is designed.
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
This section describes the design & implementation of Nuudle using a top-down design.
Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
.puml
files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Main
has two classes called Main
and MainApp
. It is responsible for,
- 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 methods where necessary.
Commons
represents a collection of classes used by multiple other components.
The rest of the App consists of 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.
Each of the four components,
- defines its API in an
interface
with the same name as the Component. - exposes its functionality using a concrete
{Component Name}Manager
class (which implements the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component (see the class diagram given below) defines its API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class which implements the Logic
interface.
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
.
The sections below give more details of each component.
UI component
API :
Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, StatisticsDisplay
, AppointmentListPanel
, PatientListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class.
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. - Listens for changes to
Model
data so that the UI can be updated with the modified data.
Logic component
API :
Logic.java
-
LogicManager
implements theLogic
interface and uses theNuudleParser
class to parse the user command with theexecute
method. -
This results in a
Command
object which is then executed by theLogicManager
.
-
The command execution can affect the
Model
(e.g. adding or deleting a patient). -
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.
DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Model component
API : Model.java
The Model
,
- stores a
UserPref
object that represents the user’s preferences. - stores the patient book data and appointment book data.
- exposes an unmodifiable
ObservableList<Patient>
and an unmodifiableObservableList<Appointment>
that can be ‘observed’ e.g. the UI can be bound to these lists so that the UI automatically updates when the data in the list change. - does not depend on any of the other three components.
Tag
list in the PatientBook
, which Patient
references. This allows PatientBook
to only require one Tag
object per unique Tag
, instead of each Patient
needing their own Tag
object.Storage component
API : Storage.java
The Storage
component,
- can save
UserPref
objects in json format and read it back. - can save the patient book data in JSON format and read it back.
- can save the appointment data in JSON format and read it back.
- can save the appointment data in CSV format for archiving.
- can handle files with invalid data by removing only the corrupted/invalid data.
Common classes
Classes used by multiple components are in the seedu.patientbook.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
1. Done feature
[written by: Yang Yue]
The mark done feature allows users to mark a specific appointment in the appointment book as done using APPT_INDEX
to specify the appointment in the list of appointments.
1.1 Implementation
Command: done APPT_INDEX
Example Usage:
done 1
done 4
The mark appointments as Done feature is facilitated by the DoneCommand
, which extends the abstract class Command
, and
the DoneCommandParser
, which implements the Parser
interface. Both of these classes are part of the Logic
component.
The following is an example usage scenario on how the mark as done mechanism works in each step:
-
User types
done INDEX
into the app. -
The request is handled by
LogicManager#execute(String)
, which then calls and passes the input to theNuudleParser#parseCommand(String)
method. -
NuudleParser
detects the command worddone
in the input string and creates a newDoneCommandParser
to parse inputs according to the format specified forDoneCommand
. -
Input is parsed using the
DoneCommandParser#parse(String)
method, which also performs input validation. -
The
DoneCommandParser
creates a newDoneCommand
instance with the given index and returns it toNuudleParser
, which in turn returns it toLogicManager
. -
LogicManager
calls theDoneCommand#execute(Model)
method. -
DoneCommand
obtains a copy of theFilteredAppointmentList
by calling theModel#getFilteredAppointmentList()
method.
-
DoneCommand
returns the appointmenttoMark
in theFilteredAppointmentList
, if an appointment can be found by the given index. Otherwise, throw anAPPOINTMENT_DOES_NOT_EXISTS
exception. -
DoneCommand
creates another instance of this appointmentdoneAppointment
which has adone
status. -
DoneCommand
replaces thetoMark
with thedoneAppointment
by calling theModel#setAppointment(Appointment, Appointment)
. -
Lastly,
DoneCommand
creates aCommandResult
with aSuccessMessage
and returns it toLogicManager
.
The above process is shown in the following sequence diagram:
Diagram 1.1.1: Sequence diagram showcasing the Done Command process
The following activity diagram summarizes what happens when a user executes a new command:
Diagram 1.1.2: Activity diagram showcasing the Done Command execution flow
1.2 Design consideration
Aspect: Input format
-
Alternative 1 (current choice): Use
APPT_INDEX
to indicate the appointment to mark.- Pros: Easy to type. In this case, a user does not have to type in a long string to indicate the date and time. Easy to implement. We can find an appointment easily using the
APPT_INDEX
. Otherwise, we need to create aDateTimeLoader
to locate the appointment in the appointment list. - Cons: The input does not show the date and time, so users have to scroll down the list to check whether the index points to the right appointment.
- Pros: Easy to type. In this case, a user does not have to type in a long string to indicate the date and time. Easy to implement. We can find an appointment easily using the
-
Alternative 2: Use
d/DATE t/TIME
as input- Pros: Without checking the list, users can tell whether the input is the right appointment they want to mark.
- Cons: It takes more time to type
d/DATE t/TIME
comparing to a single numberINDEX
in alternative 1.
2. Data archiving
[written by: Lim Jin Hao]
The data archiving feature will archive all past appointments into an archive directory on starting the app. The appointment data will be archived according to their months and saved as a CSV file.
The data archived will be minimal and only contains the following columns: date
, startTime
, endTime
, isDone
, name
, phone
, address
and remark
.
2.1 Implementation
The archive mechanism is facilitated by CsvAppointmentArchive
which implements the AppointmentArchive
interface.
It is stored internally within the JsonAppointmentBookStorage
which in turn implements the AppointmentBookStorage
interface.
CsvAppointmentArchive
implements the following operations:
-
archivePastAppointments(..)
— Removes all past appointments from the appointment book and archive the past appointments in CSV files. -
saveAppointments(..)
— Saves the list ofCsvAdaptedAppointment
as a CSV file in the archive directory with the given filename. -
readAppointments(..)
— Reads the CSV file with the given filename and returns the data as a list ofCsvAdaptedAppointment>
CsvAdaptedAppointment
and CsvAdaptedPatient
are used to represent the CSV-adapted Appointment
and Patient
respectively.
Diagram 2.1.1: Sequence diagram showcasing the Data Archiving process
Given below is an example startup scenario, which focuses on how the archive mechanism behaves at each step.
-
The user launches the application with some existing appointment data.
-
The
MainApp
calls theStorage#readAppointmentBook()
method to get the appointment book with the existing appointment data. -
The appointment book is then passed to the
AppointmentArchive
through theStorage
andAppointmentBookStorage
by calling their respectivearchivePastAppointments(..)
methods. -
AppointmentArchive
then iterates through the appointment book and separates the appointments into a list of upcoming appointments, and a list of past appointments to be archived. -
For each past appointments, the
AppointmentArchive
creates a CSV-adapted version of the appointment,CSVAdaptedAppointment
. -
The
CSVAdaptedAppointment
will subsequently create a CSV-adapted version of the patient,CSVAdaptedPatient
to be stored together with the appointment.
-
For each
CsvAdaptedAppointment
in the same month, theAppointmentArchive
calls thesaveAppointments(..)
method to save them in a single CSV file. -
saveAppointments(..)
will then call theCsvUtil#serializeObjectToCsvFile(..)
method to serialise the appointments to CSV format and save them in a file with the following naming format:<YYYY>_<MMM>
(For eg.2019_MAY
) -
If the file already exists, the data will be appended to the bottom of the existing CSV file. Otherwise, a new file will be created with the CSV headers and the data of the archived past appointments.
-
The list of upcoming appointments will then be returned to the
MainApp
through the call stack and will be used by the system as the starting appointment book data. -
The
Ui
component will then call theLogic#getStorageStatus()
component to get the status message of the archive operation from theStorageManager
. -
The archive status message will then be displayed to the user via the GUI.
The retrieval of the status message is shown in the following sequence diagram:
Diagram 2.1.2: Sequence diagram showcasing the archive message retrieval process
The following activity diagram summarises how the Nuudle selects and archive past appointments:
Diagram 2.1.3: Activity diagram showcasing the Data Archiving execution flow
2.2 Design consideration:
Aspect: Type of data to save in CSV format
As the data is to be saved in a CSV format, the data attributes of the Java Object cannot have complex data type such as a set
, list
or map
.
CsvAdaptedAppointment
and CsvAdaptedPatient
classes are used to represent the archivable appointments and patient, so the consideration is to decide how and what data should be archived in the CSV file.
-
Alternative 1 (current choice): Only archive the necessary data and ignore certain data such as
Set<Tags>
and sensitive data such as the patient’sNric
.- Pros: Straightforward to implement. Easy to add and remove fields to be archived.
- Cons: Does not have the full appointment data and therefore
CsvAdaptedAppointment
cannot be used to recreateAppointment
.
-
Alternative 2: Archive all appointment-related data. For complex data, convert them to their string equivalent and have methods to convert them back to the original state.
- Pros: Full data is saved and therefore the actual
Appointment
can be recreated from the CSV data file. - Cons: We must ensure that the implementation of the conversion is correct and that the content of the data does not affect the conversion.
- Pros: Full data is saved and therefore the actual
3. Assign Feature
[written by: Zhang Wanlin]
The assign feature will allow the user to create a new appointment that is attached to a patient.
3.1 Implementation
The assign
feature is implemented to allow users to assign a specified patient into a specified appointment date and time. To avoid cyclic dependency, only an Appointment
has an attribute of Patient
object instead of Appointment
object and Patient
object refer to each other.
This feature creates a new Appointment instance, which is stored in an instance of UniqueAppointmentList
, which in turn is stored in the AppointmentBook
. These classes are part of the model
component.
The feature is supported by the AssignCommand
class which extends the abstract class Command
, and AssignmentCommandParser
which implements the Parser
interface. These classes are part of the logic
component.
The following class diagram showcases the relationship between the main classes that support this command and key attributes and methods:
Diagram 3.1.1: Class diagram for classes involved in the assign feature of the Logic component
Diagram 3.1.2: Class diagram for classes involved in the assign feature of the Model component
Here below is an example usage scenario and how the assign
feature works at each step:
-
User enters respective input into the app.
-
The input is handled by the
LogicManager#execute(String)
, which then calls and passes the input to theNuudleParser#parseCommand(String)
method.
-
NuudleParser
finds out the command wordassign
in the user input and creates anAssignCommandParser
to parse the input according to the format specified forAssignCommand
. -
AssignCommandParser
parses the user input and checks the input validation for correct types (eg.Integer
forIndex
and alphanumeric characters forName
) via theAssignCommandParser#parser(String)
method. -
AssignCommandParser#parse(String)
calls the constructor ofIndex
andDurationSupporter
, and creates a newIndex
instance and a newDurationSupporter
object with the user input. It creates a newAssignCommand
and passes theIndex
andDurationSupporter
to it. -
AssigCommand
returns the newCommand
instance to theAssignCommandParser
, which in turn returns it toLogicManager
. -
LogicManager
calls theAssignCommand#execute(Model)
method. -
The
AssigCommand#execute(Model)
method callsAssignCommand#createAppointment()
to create anAppointment
. -
This
Appointment
instance is added into theModel
viaModel#addAppointment()
. -
The
Model#updateFilteredAppointmentList()
calls to update thefilteredAppointmentList
in the Model, and meanwhile checks if theDate
andTime
of addedAppointment
overlaps with otherAppointment
in the list. -
Lastly, the
AssignCommand
creates aCommandResult
withMESSAGE_SUCCESS
, and returns it intoLogicManager
.
Diagram 3.1.3: Sequence diagram showcasing the Assign Command process
The following activity diagram summarises the general workflow for the Assign Command:
Diagram 3.1.4: Activity diagram showcasing the Assign Command execution flow
3.2 Design Considerations
Aspect: How the assign
command executes
-
Alternative 1 (current choice): Separate parsing from code execution
- Pros: Clear distinction between class responsibilities.
- Cons: More code, may increase coupling as objects are passed around between the classes.
-
Alternative 2: Parse and Execute in the same class
- Pros: Less code, less fields/objects are passed between classes.
- Cons: No separation between classes violates the Single Responsibility Principle. It also makes debugging harder since more functions are squeezed in one big class. Also, it may be harder for further developers to understand since the design would vary from the
Add
command forPatient
(adapted from AddressBookLevel3).
Aspect: How to store the Appointment
instances
-
Alternative 1 (current choice): Store in a separate
UniqueAppointmentList
class- Pros: It is easier to manage
Appointment
in a separate class since many additional methods can be implemented to empower the management. Thus, This is also beneficial for otherAppointment
related commands. - Cons. Another class would lead to more memory usage. Since the target user needs to keep the app running, this could be disadvantageous.
- Pros: It is easier to manage
-
Alternative 2: Store inside
Patient
instances, i.e. inUniquePatientList
.- Pros: Separating list is no longer needed and the usage of
UniquePatientList
would be enlarged. This might be better for hardware memory performance. - Cons: It hardens the issue of maintaining
Appointment
instances since the logic is that aPatient
could have multipleAppointment
but not the other way. As such, it would be harder forPatient
related commands (find
) to find thePatient
and all hisAppointment
at once.
- Pros: Separating list is no longer needed and the usage of
4. Edit Patient Feature
[written by: Xin Zhe]
The Edit Patient Feature allows the nurse to edit an existing Patient
in the patient book.
Appointment
which involves the patient will be updated accordingly.
4.1 Implementation
The Edit Patient Feature is facilitated by the EditCommand
, which extends the abstract class Command
,
and the EditCommandParser
, which implements the Parser
interface. All of these classes are part of the Logic
component.
This feature is supported by the UniquePatientList
which stores the patient
instances and the UniqueAppointmentList
which stores the appointment
instances. These classes are part of the model
component.
Additionally, a public static class EditPatientDescriptor
is nested in EditCommand
as a container class to store the details to edit the Patient
with.
It also implements the following operations:
-
EditCommand#createEditedPatient(Patient patientToEdit, EditPatientDescriptor editPatientDescriptor)
— Creates apatient
with the details ofpatientToEdit
edited witheditPatientDescriptor
.
Given below is an example usage scenario and how the edit mechanism behaves at each step.
1: The user types edit INDEX [n/NAME] [p/PHONE] [i/NRIC] [a/ADDRESS] [r/REMARK] [t/TAG]…
into Nuudle.
2: The request is handled by LogicManager#execute(String)
, which then calls and passes the input to the NuudleParser#parseCommand(String)
method.
3: NuudleParser
detects the command word edit
in the input string and creates a new EditCommandParser
to parse inputs according to the format specified for EditCommand
.
4: Input is parsed using the EditCommandParser#parse(String)
method, which also performs input validation. The method creates a EditPatientDescriptor
using the parsed inputs by calling the static constructor inside EditCommand
.
5: The EditCommandParser
creates a new EditCommand
instance with the given index and newly created EditPatientDescriptor
object and returns it to NuudleParser
, which in turn returns it to LogicManager
.
6: LogicManager
calls the EditCommand#execute(Model)
method.
7: EditCommand
obtains a copy of the FilteredPatientList
by calling the Model#getFilteredPatientList()
method. This is used to check if the patient
index supplied by the user exists in Nuudle.
8: EditCommand
creates a new patient
specified by the EditPatientDescriptor
by calling its own private static method EditCommand#createEditedPatient(Patient, EditPatientDescriptor)
.
9: EditCommand
checks whether there are duplicate patients in the patient book by calling Model#hasPatient(Patient)
method.
10: EditCommand
edits the patient
at a given index by calling Model#setPatient(Patient, Patient)
;
11: EditCommand
edits the appointment
in the UniqueAppointmentList
which contains the edited patient by calling Model#updateAppointmentsWithPatient(Patient, Patient)
method.
12: EditCommand
updates the filtered list by calling Model#updateFilteredPatientList(Predicate)
method.
13: EditCommand
updates the filtered list by calling Model#updateFilteredAppointmentList(Predicate)
method.
14: Lastly, EditCommand
creates a CommandResult
with SuccessMessage
and Patient
and returns it to LogicManager
.
The above process is shown in the following sequence diagram:
Diagram 4.1.1: Sequence diagram showcasing the Edit Command process
The following activity diagram summarises the general workflow for the Edit Command:
Diagram 4.1.2: Activity diagram showcasing the Edit Command execution flow
4.2 Design Considerations
Aspect: How the edit
command executes
-
Alternative 1 (current choice): Separate parsing from code execution
- Pros: Separate the responsibilities clearly between classes and adhere to the Single Responsibility Principle.
- Cons: Increases numbers of lines of code and may increase coupling as objects are passed around between the classes.
-
Alternative 2: Parse and Execute in the same class
- Pros: Fewer lines of code, less fields/objects are passed between classes which reduces the coupling.
- Cons: No separation between classes violates the Single Responsibility Principle. Compromises the readability of the code and increases the difficulty of debugging and maintaining the code base.
Aspect: How to update corresponding appointment
instance
-
Alternative 1 (current choice): Separate updating
appointment
from editingpatient
- Pros: Draws clear distinction between the responsibilities of
appointmentBook
andpatientBook
. Reduces the coupling. - Cons: Increases numbers of lines of code and increases the cost of testing since more tests have to be carried out.
- Pros: Draws clear distinction between the responsibilities of
-
Alternative 2: Update
appointment
together withedit
patient in one function- Pros: Fewer lines of code. Lower cost of testing.
- Cons: No separation between classes violates the Single Responsibility Principle. Compromises the readability of the code and increases the difficulty of debugging and maintaining the code base. Increases the coupling.
5. Remark feature
[written by: Low Ming Lim]
The remark feature allows users to add a custom note to a new or existing patient using the r/
tag in multiple commands.
This provides our users with the flexibility and freedom to store extra notes or bio data for a patient apart from the compulsory
fields such as name, phone number, etc.
5.1 Implementation
This feature creates a Remark
instance which is stored internally in Nuudle as a variable of a Patient
object
which is in turn stored in the PatientBook
. These classes are a part of the Model
component and are illustrated
in the class diagram below.
Diagram 5.1.1: Class diagram for classes involved in the remark feature of the Model component
Additionally, to facilitate greater convenience for our users, we have implemented our remark feature to support the following pathways:
-
Adding a remark via the
AddCommand
for a newPatient
:Command syntax:
add n/NAME i/NRIC p/PHONE_NUMBER a/ADDRESS [r/REMARK] [t/TAG]…
Example Usage:
add n/Betsy i/S9123456G t/friend a/NUS Utown p/1234567 r/Prefers Dr John
add n/John Doe i/S9730284G p/98765432 a/John street, block 123, #01-01 r/Regular customer
-
Adding a remark via the
RemarkCommand
for an existingPatient
:Command syntax:
remark INDEX [r/REMARK]
Example Usage:
remark 2 r/Has been visiting Dr John
remark 1 r/Can only converse in mandarin
-
Or via the
EditCommand
:Command syntax:
edit INDEX [n/NAME] [i/NRIC] [p/PHONE_NUMBER] [a/ADDRESS] [r/REMARK] [t/TAG]…
Example Usage:
edit 1 r/Can only converse in mandarin
edit 2 n/Betsy r/Has been visiting Dr John
Refer to the following activity diagram for a summary of the above pathways.
Diagram 5.1.2: Activity diagram showcasing available pathways to create a remark
This segment will focus on the implementation details for the RemarkCommand
pathway. The implementation for the
alternative EditCommand
pathway can be found in another segment of our Developer’s Guide while the implementation
for the AddCommand
pathway is inherited from the original AB3-Addressbook.
The addition of a remark via the remark command pathway is mainly facilitated by the RemarkCommand
class which
extends the abstract class Command
. A RemarkCommandParser
which implements the Parser
interface is required
to instantiate a RemarkCommand
from the user input. The classes mentioned above in this paragraph resides in our
logic
component.
5.2 Implementation Illustration
Diagram 5.2.1: Sequence diagram for the Remark Command
The following is a step by step illustration of how the remark command mechanism works for an example scenario based on the sequence diagram above:
-
The user executes the
remark 1 r/Can only converse in chinese
command to add the remarkCan only ...
to the first patient in the patient list currently displayed to the user. -
This request is handled by
LogicManager#execute(String)
, which then calls and passes the input over to theNuudleParser#parseCommand(String)
method. -
During the execution of the
NuudleParser#parseCommand(String)
method,NuudleParser
detects the command wordremark
in the input string and creates a newRemarkCommandParser
. -
The
RemarkCommandParser#parse(String)
method is then subsequently called to parse the rest of the input string according to the format specified forRemarkCommand
. Input validation for the appropriateIndex
andRemark
format is performed here by calling theParserUtil#parseIndex
andParserUtil#parseRemark
methods respectively.
The process described in step 4 is shown in the following sequence diagram:
Diagram 5.2.2: Sequence diagram for the Remark-ParserUtil interactions -
The
RemarkCommandParser
then instantiates a newRemarkCommand
with the appropriateIndex
andRemark
object. This newRemarkCommand
is then returned toNuudleParser
and subsequentlyLogicManager
at the end of theNuudleParser#parseCommand(String)
execution. -
LogicManager
proceeds to call theRemarkCommand#execute(Model)
method. -
RemarkCommand
obtains a copy of theFilteredPatientList
by calling theModel#getFilteredPatientList()
method. -
The
patientToEdit
is then identified from theFilteredPatientList
based on theIndex
provided. -
A new
editedPatient
is then created with the inputRemark
while the rest of its particulars are duplicated from thepatientToEdit
. -
After all is done, the
editedPatient
is set to replace thepatientToEdit
in theModel
via theModel#setPatient(Patient, Patient)
method. -
Model#updateFilteredPatientList
is then called to update theFilteredPatientList
displayed by the UI.
The above process is shown in the following sequence diagram:
Diagram 5.2.3: Sequence diagram for the Remark-Model interactions -
Lastly, the
RemarkCommand
creates aCommandResult
with aSuccessMessage
and returns it toLogicManager
. -
The
SuccessMessage
is then displayed to the user via the GUI.
The following activity diagram summarizes the above steps when a user uses the remark command pathway:
Diagram 5.2.4: Activity diagram showcasing the Remark Command execution flow
5.3 Design Considerations
Aspect: How the remark
command executes
-
Alternative 1 (current choice): Separate parsing from code execution
-
Pros: Responsibilities of classes are clearly distinguished. Adherence to the Single Responsibility Principle helps improve code cohesion. Changes to either the parser or execution will no longer affect each other.
-
Cons: Increases the code base, may increase coupling as objects are passed around between the classes. More tests have to be written for the respective classes, thus increasing the cost of testing.
-
-
Alternative 2: Parse and Execute in the same class
-
Pros: Size of code base is reduced. Fewer objects are passed between classes thereby reducing coupling.
-
Cons: Violates the Single Responsibility Principle and reduces code readability. Harder for team members to maintain the code base. Increases code cohesion. Code design will also differ from other commands such as
Add
andEdit
which are adapted from AddressBookLevel3.
-
6. Undo/redo feature
[maintained by: Yang Yue]
6.1 Implementation
The proposed undo/redo mechanism is facilitated by VersionedPatientBook
and VersionedAppointmentBook
. They extend PatientBook
and AppointmentBook
respectively with an undo/redo history, stored internally as an patientBookStateList
, appointmentBookStateList
and currentStatePointer
. Additionally, it implements the following operations:
-
VersionedPatientBook#commit()
— Saves the current patient book state in its history. -
VersionedAppointmentBook#commit()
— Saves the current appointment book state in its history. -
VersionedPatientBook#undo()
— Restores the previous patient book state from its history. -
VersionedAppointmentBook#undo()
— Restores the previous appointment book state from its history. -
VersionedPatientBook#redo()
— Restores a previously undone patient book state from its history. -
VersionedAppointmentBook#redo()
— Restores a previously undone appointment book state from its history.
These operations are exposed in the Model
interface as Model#commitPatientBook()
, Model#commitAppointmentBook()
, Model#undoPatientBook()
, Model#undoAppointmentBook()
, Model#redoPatientBook()
, and Model#redoAppointmentBook()
respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedPatientBook
and VersionedAppointmentBook
will be initialized with the initial patient book state and initial appointment book state, and the currentStatePointer
pointing to that single patient book state and appointment book state.
Diagram 6.1.1: Diagram showcasing the initial state
Step 2. The user executes delete 5
command to delete the 5th person in the patient book. The delete
command calls Model#commitPatientBook()
and Model#commitAppointmentBook()
, causing the modified states after the delete 5
command executes to be saved in the patientBookStateList
and appointmentBookStateList
, and the currentStatePointer
is shifted to the newly inserted state.
Diagram 6.1.2: Diagram showcasing the state after executing delete command
Step 3. The user executes add n/David …
to add a new person. The add
command also calls Model#commitPatientBook()
and Model#commitAppointmentBook()
, causing another modified states to be saved into the patientBookStateList
and appointmentBookStateList
.
Diagram 6.1.3: Diagram showcasing the state after executing add command
Model#commitPatientBook()
and Model#commitAppointmentBook()
, so the two book states will not be saved into the patientBookStateList
and appointmentBookStateList
.
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#commitPatientBook()
and Model#commitAppointmentBook()
, which will shift the currentStatePointer
once to the left, pointing it to the previous states, and restores the patient book and appointment book to that state.
Diagram 6.1.4: Diagram showcasing the state after executing undo command
currentStatePointer
is at index 0, pointing to the initial PatientBook state and initial AppointmentBook state, then there are no previous PatientBook and AppointmentBook states to restore. The undo
command uses Model#canUndoPatientBook()
and Model#canUndoAppointmentBook()
to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
The following sequence diagram shows how the undo operation works:
Diagram 6.1.5: Sequence diagram for undo command process
UndoCommand
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
The redo
command does the opposite — it calls Model#redoPatientBook()
and Model#redoAppointmentBook()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the patient book and appointment book to that state.
currentStatePointer
is at index appointmentBookStateList.size() - 1
and patientBookStateList.size() - 1
, pointing to the latest state, then there are no undone states to restore. The redo
command uses Model#canRedoAppointmentBook()
and Model#canRedoPatientBook()
to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
Step 5. The user then decides to execute the command list
. Commands that do not modify the patient book and appointment book, such as list
, will usually not call Model#commitPatientBook()
, Model#commitAppointmentBook()
, Model#undoPatientBook()
or Model#redoPatientBook()
. Thus, the patientBookStateList
and appointmentBookStateList
remains unchanged.
Diagram 6.1.6: Diagram showcasing the state after executing list command
Step 6. The user executes clear
, which calls Model#commitPatientBook()
and Model#commitAppointmentBook()
. Since the currentStatePointer
is not pointing at the end of the patientBookStateList
and appointmentBookStateList
, all states after the currentStatePointer
will be purged. Reason: It no longer makes sense to redo the add n/David …
command. This is the behavior that most modern desktop applications follow.
Diagram 6.1.7: Diagram showcasing the state after executing clear command
The following activity diagram summarizes what happens when a user executes a new command:
Diagram 6.1.8: Activity diagram showcasing the work flow of executing a new command
6.2 Design consideration:
Aspect: How undo & redo executes
-
Alternative 1 (current choice): Saves the entire appointment book and patient book.
- Pros: Easy to implement.
- Cons: May have performance issues in terms of memory usage.
-
Alternative 2: Individual command knows how to undo/redo by
itself.
- Pros: Will use less memory (e.g. for
delete
, just save the person being deleted). - Cons: We must ensure that the implementation of each individual command are correct.
- Pros: Will use less memory (e.g. for
7. Find Available Time Slots
[written by: Xin Zhe]
The Find Available Time Slots Feature allows the nurse to obtain the available time slots on a specified date along with the earliest available slot.
7.1 Implementation
Finding available time slots is facilitated by the AvailableCommand
, which extends the abstract class Command
,
and the AvailableCommandParser
, which implements the Parser
interface. All of these classes are part of the Logic
component.
This feature is supported by the UniqueAppointmentList
which stores the Appointment
instances, the ScheduleManager
which handles the scheduling-related operations, and TimeIntervalList
which stores the TimeInterval
instances. These classes are part of the model
component.
Given below is an example usage scenario and how the edit mechanism behaves at each step.
1: The user types avail d/12-Apr-2021
into Nuudle.
2: The request is handled by LogicManager#execute(String)
, which then calls and passes the input to the NuudleParser#parseCommand(String)
method.
3: NuudleParser
detects the command word avail
in the input string and creates a new AvailableCommandParser
to parse inputs according to the format specified for AvailableCommand
.
4: Input is parsed using the AvailableCommandParser#parse(String)
method, which also performs input validation and checks whether the input date is today. A DateMatchesPredicate
instance is created based on the parsed date.
5: The AvailableCommandParser
creates a new AvailableCommand
instance with boolean value isToday
and newly created DateMatchesPredicate
object and returns it to NuudleParser
, which in turn returns it to LogicManager
.
6: LogicManager
calls the AvailableCommand#execute(Model)
method.
7: AvailableCommand
updates the filtered list by calling Model#updateFilteredAppointmentList(Predicate)
method.
8: AvailableCommand
obtains a copy of the FilteredAppointmentList
by calling the Model#getFilteredAppointmentList()
method.
9: AvailableCommand
creates a timeSlotsMessage
by calling the Model#findAvailableTimeSlots(List<Appointment>, Boolean)
method.
10: Lastly, AvailableCommand
creates a CommandResult
with SuccessMessage
and timeSlotsMessage
and returns it to LogicManager
.
The above process is shown in the following sequence diagram:
Diagram 7.1.1: Sequence diagram showcasing the Available Command process
The following activity diagram summarises the general workflow for the Available Command:
Diagram 7.1.2: Activity diagram showcasing the Available Command execution flow
7.2 Design Considerations
Aspect: How the avail
command executes
-
Alternative 1 (current choice): Separate parsing from code execution
- Pros: Separate the responsibilities clearly between classes and adhere to the Single Responsibility Principle.
- Cons: Increases numbers of lines of code and may increase coupling as objects are passed around between the classes.
-
Alternative 2: Parse and Execute in the same class
- Pros: Fewer lines of code, less fields/objects are passed between classes which reduces the coupling.
- Cons: No separation between classes violates the Single Responsibility Principle. Compromises the readability of the code and increases the difficulty of debugging and maintaining the code base.
Aspect: How to obtain available timeInterval
instance
-
Alternative 1 (current choice): Create
TimeInterval
andTimeIntervalList
models to represent the time slots and createscheduleManager
model to handle operations related toTimeInterval
- Pros: Draws clear distinction between the responsibilities of
appointmentBook
andscheduleManager
. Reduces the coupling and adheres to the Single Responsibility Principle by creating multiple scheduling related models. - Cons: Increases numbers of lines of code and increases the cost of testing since more tests have to be carried out.
- Pros: Draws clear distinction between the responsibilities of
-
Alternative 2: Let
UniqueAppointmentList
handle the creation and filtering of available time slots- Pros: Fewer lines of code. Lower cost of testing.
- Cons: No separation between classes violates the Single Responsibility Principle. Compromises the readability of the code and
increases the difficulty of debugging and maintaining the code base. Increases the coupling.
UniqueAppointmentList
will handle objects from different classes and complicated operations which are not under its obligations.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile story:
Namise is a hard working nurse working at a popular dental clinic situated in town and gets appointment calls on an hourly basis. Swarmed with incoming calls, Namise has to make new appointments for new and existing patients while keeping track of the doctor’s schedule at the same time 😞. With the need to juggle multiple tasks at once, Namise is also prone to making careless mistakes in his work due to fatigue.
Being a tech-savvy nurse armed with a commendable experience in unix, Namise prefers to scribble down appointment schedules on paper while on call with his patients to maximise efficiency. This task is further exacerbated with the need to transfer these notes into an excel table manually later in the day.
Target user profile summary:
- Nurse working in a highly popular, small scale dental clinic
- Responsible for scheduling a large number of appointments daily
- Add new patients to the clinic records
- Do not entertain walk-ins and only operate on an appointment-basis
- Required to multi-task (create appointment arrangements with patients over the phone)
- Prone to carelessness due to the sheer number of appointments to handle
- Tech-savvy
- Prefers typing & wants to get things done quickly
- Tired of transferring appointment details from paper notes to excel
- Prefers desktop apps over other types
- Types fast
- Prefers typing to mouse interactions
- Reasonably comfortable using CLI apps
Value proposition:
Help nurses handle and schedule dental appointments for patients faster than a typical mouse/GUI driven app or excel scheduling
User stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * |
new user | see an overview of the commands that are available to me | familiarise myself with using the app command |
* * * |
nurse | view the entire patient list | assign them to their appointments |
* * * |
administrative nurse | add new patient into the system | assign an appointment to them |
* * * |
administrative nurse | delete the patient information | manage the patient list easily when it is needed |
* * * |
administrative nurse | search for patient by IC | locate a patient easily |
* * * |
nurse | create patient appointment | assign patient to a time slot for their appointment |
* * * |
nurse | delete an appointment | cancel an appointment |
* * * |
nurse | view the entire appointment list for the certain day | update my dentists of the itinerary for the day |
* * * |
nurse | mark the appointment as complete if the patient completes his/her appointment | have an accurate representation of the remaining appointments |
* * |
nurse | add diagnosis to a completed appointment | have a record of the patient’s visit |
* * |
administrative nurse | search for patient by phone number | locate a patient easily |
* * |
administrative nurse | search for patient by name | locate a patient easily |
* * |
administrative nurse | change the patient’s appointment if they call to postpone their appointment | update the appointment easily |
* * |
administrative nurse | edit patient’s details | keep the information of patients up-to-date |
* * |
nurse | get the available time slots for a day | inform my patient of the available times for that day |
* |
nurse | get the next available time slot | inform my patient of the next available timing should their original preferred choice be filled |
* * |
nurse handling multiple appointments | archive records of past appointments | review them in the future |
* |
nurse | record the patient’s existing medical condition | confirm if the medicine prescribed are suitable |
* |
nurse | check the patient’s drug allergy | confirm if the medicines prescribed are not in the list |
* |
nurse | get the number of appointments for the day | know how many appointments to expect today |
* |
nurse | get the number of appointments for the week | know how busy the clinic is for the week |
Use cases
(For all use cases below, the System is the Nuudle
and the Actor is the user
, unless specified otherwise)
Use case: UC01 - Add a patient
MSS
- User requests to add a patient to the list.
- Nuudle adds the patient.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
Use case: UC02 - Delete a patient
MSS
- User requests to list patients.
- Nuudle shows a list of patients.
- User requests to delete a specific patient in the list.
-
Nuudle deletes the patient.
Use case ends.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
3a1. Nuudle shows an error message.
Use case resumes at step 2.
-
Use case: UC03 - Edit a patient
MSS
- User requests to find a specific patient.
- Nuudle shows the list of patients with the given name.
- User requests to edit the patient information.
-
Nuudle changes the patient information.
Use case ends.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
3a. The given information for editing is invalid (including empty input).
-
3a1. Nuudle shows an error message.
Use case resumes at step 2.
-
Use case: UC04 - View patient record
MSS
- User requests to find a patient by name.
- Nuudle shows the list of patients with the requested name.
- User request to view patient record of a specific patient in the list.
-
Nuudle shows the list of records for that patient.
Use case ends.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
2a. The list is empty.
Use case ends.
Use case: UC05 - Add an appointment
MSS
- User requests to find an available time slot.
- Nuudle shows the available time slots.
- User requests to add an appointment to a specific time slot.
-
Nuudle adds the appointment to the list of appointment records.
Use case ends.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
3a. The given time slot is invalid (including empty input).
-
3a1. Nuudle shows an error message.
Use case resumes at step 2.
-
Use case: UC06 - Delete an appointment
MSS
- User requests to list appointments.
- Nuudle shows the list of appointments.
- User requests to delete a specific appointment in the list.
-
Nuudle deletes the appointment.
Use case ends.
Extensions
-
1a. The given index is invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
2a. The list is empty.
Use case ends.
Use case: UC07 - Change an appointment
MSS
- User requests to list all appointments.
- Nuudle shows a list of appointments.
- User requests to find an available time slot.
- Nuudle shows the available time slots.
- User requests to change a specific appointment to another time.
-
Nuudle changes the appointment.
Use case ends.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
2a. User has no current appointments.
-
2a1. Nuudle shows an error message.
Use case ends.
-
-
5a. The given time slot is invalid (including empty input).
-
5a1. Nuudle shows an error message.
Use case resumes at step 4.
-
-
5b. The given keywords are invalid.
-
5b1. Nuudle shows an error message.
Use case ends.
-
Use case: UC08 - Mark an appointment as complete
MSS
- User requests to list appointments.
- Nuudle shows the list of appointments.
- User requests to mark an appointment as done.
-
Nuudle marks the appointment as done.
Use case ends.
Extensions
-
3a. The given index is invalid.
-
3a1. Nuudle shows an error message.
Use case resumes at step 2.
-
Use case: UC09 - View appointments for today
MSS
- User requests for the appointments scheduled for today.
-
Nuudle shows a list of appointments scheduled for today.
Use case ends.
Extensions
-
1a. The given keywords are invalid.
-
1a1. Nuudle shows an error message.
Use case ends.
-
-
1b. The given date is invalid.
-
1b1. Nuudle shows an error message.
Use case ends.
-
-
2a. The list is empty.
Use case ends.
Use case: UC10 - Create an appointment for a new patient
MSS
- User requests to create a new patient (UC01).
- Nuudle creates the new patient.
- User requests for an available time slot on a preferred day.
- Nuudle shows the available time slots.
- User requests to add an appointment for the new patient.
-
Nuudle creates the appointment.
Use case ends.
Extensions
-
3a. The given date is invalid.
-
3a1. Nuudle shows an error message.
Use case resumes at step 2.
-
-
4a. No more time slot is available for that day.
-
4a1. Nuudle shows the next available time slot on the nearest day.
-
4a1a. User uses the suggested time slot.
Use case resumes at step 5.
-
4a1b. User does not use the suggested time slot.
Use case resumes at step 3.
-
-
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or above installed. - Should be able to hold up to 500 patients without a noticeable sluggishness in performance for typical usage.
- Should be able to hold up to 1000 upcoming appointments without a noticeable sluggishness in performance for typical usage.
- A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
Glossary
- Mainstream OS: Windows, Linux, Unix, OS-X
- CSV file: A file that stores data in Comma-Separated Values format (Using commas to separate values). It can be opened using Microsoft Excel.
- JSON file: A file that stores object data in JavaScript object notation format
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
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 patients and appointments. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Adjust the main split window to preferred position. 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, split window position and location is retained.
-
Deleting a patient
-
Deleting a patient while all patients are being shown
-
Prerequisites: List all patients using the
list
command. Multiple patients in the list. -
Test case:
delete 1
Expected: First patient is deleted from the list. Details of the deleted patient shown in the status message. -
Test case:
delete 0
Expected: No patient 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.
-
Creating an appointment
-
Checking the appointment status after an appointment creation
-
Prerequisites: List all patients using the
list
command. At least one patient in the list. No appointments for today. -
Create an appointment by using the
assign 1 d/today t/FUTURE_TIME
, whereFUTURE_TIME
is a time that is in the near future but still lies within the operating hours. Expected: An appointment is created for the first patient with the indicated details. The status is shown asUpcoming
. -
With the Nuudle app still running, go to your system settings and look for the
Date & Time
settings. -
Manually change the System Time to a time that lies within the appointment timeslot of the previously created appointment. You may have to temporarily turn off the
Set time automatically
features of your system.
Expected: The appointment status changes fromUpcoming
toOngoing
. It may take up to 30s for Nuudle to auto update. Alternatively, you can use thelist
command or click on the `appointment card to initiate a manual refresh. -
Now change the System Time to a time that is after the appointment timeslot of the previously created appointment.
Expected: The appointment status changes fromOngoing
toExpired
. -
Mark the previously created appointment as done, using the
done 1
command. Expected: The appointment’s status changed fromOngoing
toDone
.
-
Loading save data on launch
-
Dealing with missing/corrupted data files
-
Prerequisites: Data save location not modified. The
preference.json
file is not edited. Multiple patients in the list. -
Look for the patient JSON file at the default location:
.\data\patientbook.json
-
Remove the
Nric
field of the first patient in the file. Note the patient name. -
Launch the app by double-clicking the jar file.
The app safely launches with multiple patients. The patient with missing Nric field is not in the list.
-
Archiving data
-
Archiving past appointments
-
Prerequisites: Have at least one upcoming appointments and no expired appointments.
-
Locate the appointment JSON file at the default location:
.\data\appointmentbook.json
-
Change the
Date
field of an appointment to a past date, with monthmay
and year2019
Note the details of the appointment. -
Launch the app by double-clicking the jar file.
The app safely launches. The previously edited appointment is not in the appointment list. The Command Result display indicates that 1 appointment is archived. -
Locate the appointment CSV archive file at
.\data\archives\2019_MAY.csv
. The previously edited appointment should be reflected as an archived appointment.
-