Thank you for your interest in contributing to ClinicIO!
By: W14-1
Since: Sept 2018
Licence: MIT
- About this guide
- 1. Introduction
- 2. Installation Guide
- 3. Design
- 4. Implementation
- 5. Documentation
- 6. Testing
- 7. Dev Ops
- Appendix A: Suggested Programming Tasks to Get Started
- Appendix B: Product Scope
- Appendix C: User Stories
- Appendix D: Use Cases
- Appendix E: Non Functional Requirements
- Appendix F: Glossary
- Appendix G: Instructions for Manual Testing
About this guide
This guide provides an overview of ClinicIO and walks you through the installation process. In addition, this guide provides in-depth documentation for all features and cover everything you need to know when developing ClinicIO.
This guide assume that readers has certain developers knowledge with Java and JavaFX.
If you are new to development, you can try working step-by-step tutorial from Oracle:
If you are new to contributing to ClinicIO, head over to Introduction to have an overview of our project.
1. Introduction
ClinicIO is a desktop app to enable you to manage a clinic with ease and efficiency. You can use ClinicIO to manage patient records, queues, medical inventory, appointments as well as view detailed statistics.
Go to the Section 2, “Installation Guide” to get started on install ClinicIO.
2. Installation Guide
This section is designed to help you to setup ClinicIO development environment.
Prerequisites
Before you start setting up the project, here are the list of things that you need to install:
-
JDK
9
or laterJDK 10
on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK9
. -
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.1. Instructions to setup the project in your computer
Follow these steps to setup ClinicIO development environment in your local 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 -
Open a console and run the command
gradlew processResources
(Mac/Linux:./gradlew processResources
). It should finish with theBUILD SUCCESSFUL
message.
This will generate all resources required by the application and tests.
2.2. Verifying the setup
Please ensure that you verify that the project environment is able to run as expected before developing. You can verify by doing the following:
-
Run the
seedu.clinicio.MainApp
and try a few commands -
Run the tests to ensure they all pass.
2.3. Configurations to do before writing code
Well done. You are done setting up the project environment. Before you start developing, here are the list of configurations that you need to make:
2.3.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.3.2. Updating documentation to match your fork
After forking the repo, the documentation will still have the SE-EDU branding.
If you plan to develop this fork as a separate product (i.e. instead of contributing to ClinicIO
), 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.3.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) |
You are now ready to start coding. You can do the following to get started:
-
Get some sense of the overall design by reading Section 3.1, “Architecture”.
-
Take a look at Appendix A, Suggested Programming Tasks to Get Started.
3. Design
This section is designed to help you to understand the overall design of ClinicIO.
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 .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture .
|
Main
has only one class called 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 method where necessary.
Commons
represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.
-
EventsCenter
: This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design) -
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.
Events-Driven nature of the design
The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1
.
delete 1
command (part 1)
Note how the Model simply raises a AddressBookChangedEvent when the ClinicIO data are changed, instead of asking the Storage to save the updates to the hard disk.
|
The diagram below shows how the EventsCenter
reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.
delete 1
command (part 2)
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.
|
The sections below give more details of each component.
3.2. UI component
This section introduces you to the UI component of ClinicIO’s architecture.
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PatientListPanel
, StatusBarFooter
, PatientDetailsDisplayPanel
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 does the following:
-
Executes user commands using the
Logic
component. -
Binds itself to some data in the
Model
so that the UI can auto-update when data in theModel
change. -
Responds to events raised from various parts of the App and updates the UI accordingly.
3.3. Logic component
API :
Logic.java
-
Logic
uses theAddressBookParser
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 patient) and/or raise events. -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API call.
delete 1
Command3.4. Model component
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores the ClinicIO data.
-
exposes an unmodifiable
ObservableList<E>
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. -
does not depend on any of the other three components.
As a more OOP model, we can store a Tag list in ClinicIO , which Patient can reference. This would allow Address Book to only require one Tag object per unique Tag , instead of each Patient needing their own Tag object. An example of how such a model may look like is given below. |
3.5. Storage component
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. -
can save the ClinicIO data in xml format and read it back.
3.6. Common classes
Classes used by multiple components are in the seedu.clinicio.commons
package.
4. Implementation
This section describes some noteworthy details on how certain features are implemented.
4.1. Queue feature
This feature allows the user to perform operations related to the queue. In particular, it provides the functionality to assign a patient into the queue, remove a patient from the queue and show the list of patients in the queue.
4.1.1. Current Implementation
The Queue feature contains several operations to indirectly manipulate the two queues underlying the patientQueue
.
The two queues are mainQueue
and preferenceQueue
, both of which are hidden from the end user. To the end user, there
exists only one queue. When a patient gets assigned to the queue without any preferred doctor, the patient will be inserted into
the mainQueue
. In the scenario where a patient has a preferred doctor, the patient will be inserted into the
preferenceQueue
.
When a room is available, the system will look for the first patient in the preferenceQueue
whose preferred doctor is in the room.
It will then compare this patient with the frontmost patient from the mainQueue
on their arrival time. The patient who arrives
earlier will get to consult the doctor.
Both queues are composed of java#ArrayList
.
This allows the system to handle the situation where a patient in the middle of the queue decides to leave the queue.
The implemented operations for Queue are:
-
enqueuepatient
Command - Assigns a patient into the queue. -
dequeuepatient
Command - Removes a patient from anywhere in the queue. -
showqueue
Command - Shows a list of all patients in the queue.
Given below is an example usage scenario and how the queue-related operations behave at each step.
Step 1: The user lists all the patients using the list
command. All patients in the ClinicIO record are be displayed.
Step 2: The user executes enqueuepatient 5
command to assign the 5th patient in the list into the queue. This patient has no preferred doctor. Now the queue has 1 patient. Underlying the queue, the patient is assigned into mainQueue
. The preferenceQueue
remains empty:
Step 3: The user finds all patients with names Damith
using find Damith
command. All patients with names Damith
in the ClinicIO record will be displayed.
Step 4: The user executes enqueuepatient 2
to assign the 2nd patient whose name contains Damith
into the queue. This patient has a preferred doctor. Now the queue has 2 patients. Underlying the queue, this patient is assigned into preferenceQueue
:
Step 5: The user keeps adding patients until there are 5 patients in the queue. The mainQueue
and preferenceQueue
look like this:
The end user only sees one queue:
Step 6: The user lists all patients in the queue by using showqueue
command. All patients currently waiting in the queue are displayed.
4.1.2. Design Considerations
Aspect: How enqueuepatient command executes
-
Alternative 1 (current choice): Inserts the patient into one of the two underlying queues.
-
Pros: Easier to implement. Slightly faster than the other alternative.
-
Con: May have performance issue in terms of memory usage.
-
-
Alternative 2: Inserts the patient into only one queue.
-
Pro: Uses less memory as only one data structure is needed.
-
Con: Worse time complexity than the current implementation.
-
Aspect: How dequeuepatient command executes
-
Alternative 1 (current choice): Looks for the queue (mainQueue or preferenceQueue) from which the patient is to be removed. Then searches for the patient and removes from the queue.
-
Pro: Slightly faster than the other alternative.
-
Con: May have performance issue in terms of memory usage.
-
-
Alternative 2: Naively looks for the patient in the queue, assuming Alternative 2 of
enqueuepatient
command is used (only one underlying queue).-
Pros: Uses less memory. Easy to implement as only one naive search is required.
-
Con: Worse time complexity than the current implementation.
-
Aspect: How showqueue command executes
-
Shows a list of patients filtered according to
Patient#isQueuing()
which is basically the queuing status of the patient.
Aspect: Data structures to support the queue-related commands
-
Alternative 1 (current choice): Uses two ArrayLists to store the patients.
-
Pros: Easier to implement. Provides more functionalities compared to Queues/LinkedList.
-
Con: Uses more memory than using only one ArrayList.
-
-
Alternative 2: Uses one ArrayList to store the patients.
-
Pro: Uses less memory than Alternative 1.
-
Con: Worse time complexity when looking for a particular patient.
-
-
Alternative 3: Uses Queue/LinkedList
-
Pro: Easy to implement. Very fast operation for popping the frontmost patient.
-
Con: Limited functionalities. Removing a patient from the middle of the data structure requires extra codes.
-
4.2. Undo/Redo feature
4.2.1. Current Implementation
The undo/redo mechanism is facilitated by VersionedClinicIo
.
It extends ClinicIO
with an undo/redo history, stored internally as an clincIoStateList
and currentStatePointer
.
Additionally, it implements the following operations:
-
VersionedClinicIo#commit()
— Saves the current ClinicIO state in its history. -
VersionedClinicIo#undo()
— Restores the previous ClinicIO state from its history. -
VersionedClinicIo#redo()
— Restores a previously undone ClinicIO state from its history.
These operations are exposed in the Model
interface as Model#commitClinicIo()
, Model#undoClinicIo()
and Model#redoClinicIo()
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 VersionedClinicIo
will be initialized with the initial ClinicIO state, and the currentStatePointer
pointing to that single Clinic IO state.
Step 2. The user executes delete 5
command to delete the 5th patient in the ClinicIO. The delete
command calls
Model#commitClinicIo()
, causing the modified state of the ClinicIO after the delete 5
command executes to be
saved in the clinicIoStateList
, and the currentStatePointer
is shifted to the newly inserted ClinicIO state.
Step 3. The user executes add n/David …
to add a new patient. The add
command also calls Model#commitClinicIo()
,
causing another modified ClinicIO state to be saved into the clinicIoStateList
.
If a command fails its execution, it will not call Model#commitClinicIo() , so the ClinicIO state will not be saved
into the clinicIoStateList .
|
Step 4. The user now decides that adding the patient was a mistake, and decides to undo that action by executing the
undo
command. The undo
command will call Model#undoClinicIo()
, which will shift the currentStatePointer
once to
the left, pointing it to the previous ClinicIO state, and restores the ClinicIO to that state.
If the currentStatePointer is at index 0, pointing to the initial ClinicIO state, then there are no previous ClinicIO
states to restore. The undo command uses Model#canUndoClinicIo() 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:
The redo
command does the opposite — it calls Model#redoClinicIo()
, which shifts the
currentStatePointer
once to the right, pointing to the previously undone state, and restores the ClinicIO to that state.
If the currentStatePointer is at index clinicIoStateList.size() - 1 , pointing to the latest ClinicIO state, then
there are no undone ClinicIO states to restore. The redo command uses Model#canRedoAddressBook() 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 ClinicIO, such as list
,
will usually not call Model#commitClinicIo()
, Model#undoClinicIo()
or Model#redoClinicIo()
. Thus, the
clinicIoStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitClinicIo()
. Since the currentStatePointer
is not pointing
at the end of the clinicIoStateList
, all ClinicIO states after the currentStatePointer
will be purged. We designed
it this way because it no longer makes sense to redo the add n/David …
command. This is the behavior that
most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
4.2.2. Design Considerations
Aspect: How undo & redo executes
-
Alternative 1 (current choice): Saves the entire ClinicIO.
-
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 patient being deleted). -
Cons: We must ensure that the implementation of each individual command are correct.
-
Aspect: Data structure to support the undo/redo commands
-
Alternative 1 (current choice): Use a list to store the history of ClinicIO states.
-
Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
-
Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both
HistoryManager
andVersionedClinicIo
.
-
-
Alternative 2: Use
HistoryManager
for undo/redo-
Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.
-
Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as
HistoryManager
now needs to do two different things.
-
4.3. Appointment feature
The Appointment feature provides users the ability to schedule future appointments, view daily appointment schedules and to cancel them.
4.3.1. Current Implementation
The Appointment feature contains multiple operations to indirectly manipulate the UniqueAppointmentList
.
The implemented operations include:
-
addappt
Command - Adds appointments to the appointment schedule. -
listappt
Command - Lists the appointment schedule of the date specified. -
`listallappt`Command - Lists all appointments.
-
cancelappt
Command - Cancels appointments found within the appointment schedule.
The class diagram below illustrates the interactions between the Appointment class and associated classes.
Each Appointment
object consists of a Date
, Time
, Patient
and Optional<Staff>
.Patient
contains an
Optional<Staff>#preferredDoctor
and is assigned to the Appointment
object. The
UniqueAppointmentList
contains 0 or more Appointment
s.
The current implementation of Appointment
has the Patient
object as a separate entity from the Patient
s in
UniquePatientList
. This means that the Patient
object only exists within the context of the Appointment
object,
or according to the diagram, would be destroyed if the Appointment
object is destroyed (composition). Likewise, the
Optional<Staff>
would be destroyed if the Appointment
object is destroyed too.
In future implementations, i.e. v2.0, the Patient
object will be checked if it exists within the Model#UniquePatientList
before constructing the Appointment
object. This ensures that the Patient
is registered before making an
appointment.
4.3.2. addappt
Command
The addappt
command behaves similarly to the add
command used for Patient
and Doctor
. The command takes in the
parameters required to construct Date
, Time
and takes in Patient
details. Patient parameters will be simplified
upon the complete implementation of the Patient
class in v2.0. The image below shows how the Appointment
object is
constructed.
The image does not include the method calls to construct a Patient object.
|
After the construction of the appointment, the appointment is added into the UniqueAppointmentList
found in the
ClinicIo
class. The changes done to the list are then 'saved' via the commitClinicIo
method.
This means that this command can be undone or redone.
4.3.3. listappt
Command
The listappt
command searches for appointments that land on the date that was entered by the user.
The filtered appointments are found in ModelManager
. The list is instantiated by filtering the UniqueAppointmentList
using AppointmentContainsDatePredicate
which is created from the Date
argument supplied by the user.
4.3.4. listallappt
Command
The listallappt
behaves similarly to listappt
with the exception that it does not take in any arguments.
Instead, it automatically executes ListAllApptCommand
with the predicate SHOW_ALL_FILTERED_APPOINTMENTS
.
updateFilteredAppointmentList()
is called and the entire list of appointments is shown to the user.
4.3.5. cancelappt
Command
cancelappt
simply takes in the index of the target appointment to cancel according to the displayed appointment list.
The index is parsed using the parseIndex()
and is used in execute()
. A filtered
appointment list (lastShownList
) is retrieved and the index is used to get the targetAppointment
.
The cancelAppointment()
method, under Model
, is then called on the targetAppointment
. The process of cancellation
has two parts, cancelAppointment
and removeAppointment
. ClinicIO#cancelAppointment
simply changes the status of the appointment and
adds it to the Patient
s AppointmentHistory
(not shown). ClinicIO#removeAppointment
actually removes the appointment from the
UniqueAppointmentList
.
Due to the current composition relationship between the Appointment class and Patient class, the additional tasks
of changing appointment status and addition into the patient’s appointment history seems redundant. In v2.0, the usage of
pre-existing patients to make appointments will make these additional tasks more useful.
|
4.3.6. Design Considerations
Aspect: How to store Date and Time fields
-
Alternative 1 (current choice): Store as integers
-
Pros: Makes it easier to calculate timings and clashes between multiple dates and times.
-
Cons: Requires additional code to store and interact with other common methods that rely on Strings.
-
-
Alternative 2: Store as Strings
-
Pros: Makes it easier to implement.
-
Cons: Requires additional code to convert into integers when carrying out calculating methods.
-
Aspect: How to cancel appointments
-
Alternative 1 (current choice): Differentiate between cancellation and deletion
-
Pros: Reduces the occurrences of directly manipulating a Patient’s history. It also upholds the Separation of Concerns Principle.
-
Cons: Violates Don’t Repeat Yourself Principle.
-
-
Alternative 2: Lump deletion into cancellation
-
Pros: Makes it easier to implement and prevents redundancy in code.
-
Cons: Violates Separation of Concerns Principle.
-
Aspect: How to display appointments
-
Alternative 1 (current choice): Display appointments in a tab
-
Pros: Creates an intuitive and easily navigable screen to access appointments.
-
Cons: Decreases the efficiency of CLI by having to use GUI inputs.
-
-
Alternative 2: Have appointments appear when using an appointment command
-
Pros: Keeps the onscreen clutter at a minimum and stays in line with the CLI concept.
-
Cons: Increases difficulty in freely accessing appointments.
-
4.4. MedicineInventory
4.4.1. Current Implementation
The MedicineInventory
extends ClinicIO
with a medicine inventory. The MedicineInventory
provides the user with an
organised system to manage the medicines in the clinic.
It implements the following operations:
-
MedicineInventory#hasMedicine(MedicineName medicineName)
— Checks if theMedicineInventory
contains theMedicine
with namemedicineName
. -
MedicineInventory#addMedicine(MedicineName medicineName, Medicine newMedicine)
— AddsnewMedicine
to theMedicineInventory
with itsmedicineName
. -
MedicineInventory#updateMedicineQuantity(MedicineName medicineName, MedicineQuantity newQuantity)
— Updates theMedicineQuantity
of theMedicine
with namemedicineName
with thenewQuantity
in theMedicineInventory
. -
MedicineInventory#deleteMedicine(MedicineName medicineName)
— Deletes theMedicine
with namemedicineName
from theMedicineInventory
.
These operations are exposed in the Model
interface as Model#hasMedicine(MedicineName medicine)
,
Model#addMedicine(MedicineName medicineName, Medicine medicine)
, Model#updateMedicine(MedicineName target,
MedicineQuantity newQuantity)
, and Model#deleteMedicine(MedicineName medicine)
respectively.
Given below is an example usage scenario and how the MedicineInventory
behaves at each step.
Step 1. The user launches the ClinicIO application. The empty MedicineInventory
will be initialized at the same time
as the ClinicIO
.
Step 2. The user executes addmed mn/Paracetamol mt/tablet ed/2 ld/8 mp/0.05 mq/1000
command to add a new Medicine
to
the MedicineInventory
. The addmed
command creates a new Medicine
newMedicine
with the fields specified by the
command and calls Model#addMedicine(Paracetamol)
. This adds a new entry into MedicineInventory
.
Step 3. The user execute listmed
command to view the list of all Medicine`s in the `MedicineInventory
.
Step 4. The user executes increasemed 1 mq/500
command to increase the current MedicineQuantity
of the Medicine
at
index 1
in the Medicine List
by 500
units. The increasemed
command first checks if the MedicineInventory
contains a Medicine
at index 1
. Next, the increasemed
command adds 500
to the current MedicineQuantity
, and
calls Model#updateMedicine(Paracetamol, increasedQuantity)
. This updates the MedicineQuantity
data attribute of the
Medicine
with MedicineName
Paracetamol
in the MedicineInventory
with the latest quantity increasedQuantity
.
In the example outlined in Step 4, if the Model#hasMedicine(Paracetamol) returns false, the MedicineInventory does
not contain a Medicine with MedicineName Paracetamol , and will not call
Model#updateMedicine(Paracetamol, increasedQuantity) . This prevents the user from adding extra quantity to a
Medicine that does not exist in the MedicineInventory .
|
Step 5. The user executes decreasemed 1 q/20
command to decrease the current MedicineQuantity
of the Medicine
at
index 1
in the Medicine List
by 20
units. The decreasemed
command first checks if the MedicineInventory
contains a Medicine
at index 1
. Next, the decreasemed
command subtracts 20
from the current MedicineQuantity
,
and calls Model#updateMedicine(Paracetamol, decreasedQuantity)
. This updates the MedicineQuantity
data attribute of
the Medicine
with MedicineName
Paracetamol
in the MedicineInventory
with the latest quantity decreasedQuantity
.
In the example outlined in Step 5, if the current value of the MedicineQuantity of the Medicine with MedicineName
Paracetamol is less than 20 , the MedicineInventory does not contain enough quantity of Medicine with
MedicineName Paracetamol to prescribe to the Patient , and will not call Model#updateMedicine(Paracetamol,
decreasedQuantity) . This prevents the user from getting a negative value for MedicineQuantity of a Medicine
in the MedicineInventory .
|
Step 6. The user executes deletemed 1
command to delete the Medicine
at index 1
in the Medicine List
. The
deletemed
command first checks if the MedicineInventory
contains a Medicine
at index 1
. Next, it calls
Model#deleteMedicine(Paracetamol)
to delete the Medicine
with MedicineName
Paracetamol
from the
MedicineInventory
. Now the MedicineInventory
is empty.
In the example outlined in Step 6, if the Model#hasMedicine(Paracetamol) returns false, the MedicineInventory does
not contain a Medicine with MedicineName Paracetamol , and will not call Model#deleteMedicine(Paracetamol,
increasedQuantity) . This prevents the user from deleting a Medicine that does not exist in the MedicineInventory .
|
4.4.2. Design Considerations
Aspect: How to implement different protocols for Medicine
with different MedicineType
when managing the MedicineInventory
-
Alternative 1 (current choice): Use one
MedicineInventory
and implement different protocols for eachMedicineType
.-
Pros: Uses less memory.
-
Cons: Complicates implementation.
-
-
Alternative 2: Use different
MedicineInventory
for eachMedicineType
.-
Pros: Uses more memory.
-
Cons: Eases implementation of logic for commands.
-
4.4.3. Aspect: How to ensure that the MedicineInventory
has sufficient MedicineQuantity
of each Medicine
at all times
-
Alternative 1: Design a predictive algorithm using data from
Analytics
.-
Pros: Ensures that
MedicineInventory
will not have too large a surplus of un-prescribedMedicine
. -
Cons: Complicates implementation of logic and incorporation of data from
Analytics
.
-
-
Alternative 2 (current choice): Maintain a fixed amount of
MedicineQuantity
for eachMedicine
in theMedicineInventory
.-
Pros: Enables logic implementation to be easier and more straightforward.
-
Cons: Introduces the risk of ending up with too large a surplus of un-prescribed
Medicine
.
-
4.5. Analytics feature
4.5.1. Implementation
The following class diagram represents the relationships between classes in the analytics
package.
The abstract Statistics
class specifies methods for the computation and retrieval of two groups of statistics. The
first group consists of statistics to be displayed as a summary while the second is for ones that are visualized. Each
of the statistics classes inherit from Statistics
. They are encapsulated in Analytics
which acts as a
wrapper class for the various statistics and employs the delegation pattern. Each of the statistics classes may depend
on`DateUtil` and TimeUtil
. These classes contain utility methods for date and time information such as the number of
occurrences in a certain date or time period. The occurrences can be of events such as appointments.
Data Management
Each of the statistics classes depend on the following classes for storing data:
-
StatData
:-
acts as a structure to encapsulate all statistics data to be displayed for simpler transport from
Analytics
toAnalyticsDisplay
. -
contains
SummaryData
andCircularList
objects. The latter storesVisualizationData
objects. -
interfaces directly with the statistics classes to receive data.
-
used by
AnalyticsDisplay
to obtain fields required to create summary and visualizations.
-
-
SummaryData
:-
acts as a structure to encapsulate information required to create summaries.
-
contains the following fields:
-
title
: the title of the summary information. -
summaryElements
: the texts and their accompanying values.
-
-
-
CircularList
:-
used for storing
VisualizationData
objects. Allows visualizations to be cycled backwards and forwards on the user interface.
-
-
VisualizationData
:-
acts a structure to encapsulate information required to create visualizations.
-
contains the following fields:
-
id
: the id of the chart. Used as an identifier to apply Cascading Style Sheet (CSS) to style each chart individually. -
type
: the type of the chart. May be either one of a vertical bar chart, horizontal bar chart, vertically stacked bar chart or a line chart. -
isContinuous
: indicates if the chart is continuous or categorical. Used byPlot
to create the appropriate chart. -
chartTitle
: the title of the chart. -
xTitle
: the title of the horizontal axis if the chart is vertical or the vertical axis if horizontal. -
yTitle
: the title of the vertical axis if the chart is vertical or the horizontal axis if horizontal. -
dataGroups
: the groups of data points to be plotted. Must be the same in number asdataGroupsLabels
. -
dataGroupsLabels
: the label for each group of data. Used in the legend of the chart. Must be the same in number asdataGroups
.
-
-
-
Tuple
:-
used to store pairs of values as a convenient means by which to transport data points.
-
used mostly within a
List
, allowing order to be preserved.
-
4.5.2. Processes
This section describes the processes that take place in order to display analytics.
All Statistics Commands
The following apply to all the different types of statistics:
-
Analytics
updates the appropriate statistics object with the latest appropriate lists (e.g. appointments) fromModelManager
. -
The particular statistics object then calculates the summary for the various time periods mentioned above.
-
The particular statistics object then calculates data for one or more visualizations and updates
StatData
. -
Analytics
retrievesStatData
from the statistics object and passes it toModelManager
. -
ModelManager
raises anAnalyticsDisplayEvent
containingStatData
. -
AnalyticsDisplay
retrievesStatData
fromAnalyticsDisplayEvent
and passes it toAnalyticsPlot
. -
AnalyticsPlot
updates the summary user interface. -
AnalyticsPlot
decides and plots the appropriate chart on the user interface.
patientstats
Command
The following are the processes performed specifically by PatientStatistics
:
-
PatientStatistics
calculates the summary of the number of patients. -
PatientStatistics
calculates the number of patients for the following time periods in a day:-
9AM - 12PM
,12PM - 3PM
,3PM - 6PM
,6PM -9PM
-
-
PatientStatistics
updatesStatData
with this data for a vertical bar chart visualization. -
PatientStatistics
calculates the total historical number of patients for each day in a week. -
PatientStatistics
updatesStatData
with this data for a vertical bar chart visualization.
apptstats
Command
By default, 24 appointment slots are available in day. |
The following are the processes performed specifically by AppointmentStatistics
:
-
AppointmentStatistics
calculates the summary of the number of appointments. -
AppointmentStatistics
calculates the number of scheduled and available appointment slots for each day of the following week. -
AppointmentStatistics
updatesStatData
object with this data for a stacked bar chart visualization. -
AppointmentStatistics
calculates the number of scheduled appointments for each date in the current year. -
AppointmentStatistics
updatesStatData
object with this data for a line chart visualization.
doctorstats
Command
The following are the processes performed specifically by DoctorStatistics
:
-
DoctorStatistics
calculates the summary of the average number of consultations per doctor. -
DoctorStatistics
calculates the number of patients who prefer each doctor. -
DoctorStatistics
updatesStatData
object with data for a horizontal bar chart visualization.
4.5.3. Design Considerations
Aspect: Computing statistics
-
Alternative 1 (current choice): Compute statistics only upon request
-
Pro: This reduces unnecessary computational workload.
-
Con: There is greater computational workload upon request of the statistics which may result in the user having to wait longer (if at all) to view them.
-
-
Alternative 2: Compute statistics every time there is a change in the underlying data
-
Pro: There is no chance of the user having to wait to view statistics.
-
Con: This increases the computational workload which may provide little benefit if statistics are not viewed frequently by the user.
-
Aspect: User interface for the different types of statistics
-
Alternative 1 (current choice): Single, predetermined user interface
-
Pros: It is consistent, making it easier for users to process the information. It easier to implement due to the reusability of code.
-
Con: It lowers flexibility by mandating all statistics types to only have a summary and visualization component.
-
-
Alternative 2: Customized user interface for each type of statistic
-
Pro: It allows displays of quantitative information that are better suited for each individual statistics type. This results in greater flexibility even in the future as users may have changing analytics needs.
-
Cons: It may lower design consistency, forcing users to readjust each time they view it. It is more difficult to implement.
-
-
Alternative 3: Allow users to pick and choose which statistics they want to view.
-
Pro: This provides the greatest flexibility as users have total control in which statistics they view.
-
Cons: The burden is placed on the user having to decide which statistics they want to view. It is more difficult to implement as a consistent user interface needs to be designed to accomodate all the possible combinations.
-
4.6. Export feature
The export feature allows users to extract patient related data out of the program, into Comma Separated Values(CSV) format.
4.6.1. Current Implementation
The following swimlane diagram represents the workflow of the export functionality.
The ExportPatientData
class contains static
methods for filtering and organizing the relevant data. These methods
depend on a list of patients. It then passes the data to ExportUtil
in order to write to a CSV file. ExportUtil
also
checks the validity of the data to be written. The names and locations of the CSV files are predetermined by
ExportPatientData
. All exported files are stored in the root folder of the program.
The following operations are implemented:
-
exportpatients
Command - exports all patients' personal information intoClinicIO_patientdata.csv
. -
exportpatientsappointments
Command - exports all patients' appointment records intoClinicIO_patientsappointments .csv
. -
exportpatientsconsultations
Command - exports all patients' consultation records intoClinicIO_patientsconsultations.csv
.
4.6.2. Design Considerations
Aspect: File format
-
Alternative 1 (current choice): CSV
-
Pros: It is human editable and has a simple schema, making it easier to understand. It is supported on all target platforms. Additionally, it is easier to implement.
-
Con: It does not allow complex relationships. This may result in repetitive data.
-
-
Alternative 2: XLS (Native Excel format)
-
Pros: It allows more complex relationships between data to be stored. Data can also be stored in the form of charts and graphs.
-
Cons: It is less widely supported and consumes more memory. It is also more difficult to implement.
-
Aspect: File location and naming
-
Alternative 1 (current choice): File location and naming are predetermined within the program and they are to be stored in the root folder.
-
Pros: Users can easily find it as it is in the same location as the program and has a standard name. It also results in a quicker operation and is easier to implement.
-
Con: It reduces flexibility as another step is required if the user needs it in another directory.
-
-
Alternative 2: File location and naming are specifiable by user.
-
Pro: This would give users more control.
-
Cons: It results in a slower operation due to the time taken to enter filename and location. It is also more difficult to implement.
-
4.7. User Authentication
ClinicIO ensures appropriate user access rights are issued by authenticating users at login.
4.7.1. Current Implementation
The User Authentication feature comprises of commands as below:
-
login
Command: Allow users to login. -
logout
Command: Allow users to logout.
The following class diagram below illustrates the association between the Staff class and its associated class.
login
Command
The login
Command contains multiple operations to manipulate the UniqueStaffList
indirectly.
The implemented operations include:
-
ClinicIo#hasStaff(): Check if the staff record exists inside ClinicIO
-
ClinicIo#getStaff(): Retrieve the staff record from the
UniqueStaffList
-
Password#verifyPassword(): Check if password entered by user matches the staff record that was found in the
UniqueStaffList
These operations are exposed in the Model
interface as Model#checkStaffCredentials()
.
HashUtil
is used to manage the encryption and verification of password.
These are operations inside HashUtil
:
-
HashUtil#hashToString(): Hash the password
-
HashUtil#verifyPassword(): Check if the password matches the hashed password
Given below is a scenario to illustrate the login authentication at each step:
Step 1: The user executes the login
command by specifying the role, name and password. The login
command determines the type of role entered by the user.
Step 2: The login
command calls Model#hasStaff()
to ensure that this staff record exist inside ClinicIO.
Step 3: Once ClinicIO check if staff exists, the login
command call Model#checkStaffCred()
to retrieve the doctor found in UniqueStaffList
.
Step 4: The login
command verifies a password using HashUtil#verifyPassword()
with the given doctor’s password and password entered by the user.
Step 5: To indicate successful completion of verification, the login
command raises a LoginSuccessEvent
.
The following sequence diagrams shows how the login works:
Logic
login
Command for the logic component.
Model
login
Command for the model component.
Commons
login
Command for the commons component.
The following activity diagram summarizes what happens when a user executes LoginCommand
:
login
Command
logout
Command
The logout
command post a LogoutClinicIoEvent
to notify the UI that you want to log out of ClinicIO. It uses
the EventCenter
to communicate between components without requiring the components to register with
each other explicitly.
Here is a step-by-step on how logout
command notify MainWindow
that you want to log out of ClinicIo:
Step 1: The logout
command post a new LogoutClinicIoEvent
to the EventsCenter
.
Step 2: The eventBus
post the newly created LogoutClinicIoEvent
(from Step 1) to make the event available to any listeners in EventBus
.
Step 3: ClinicIo will listen for handleLogoutClinicIoEvent()
to receive the event and update the user session status.
4.7.2. Design Considerations
Aspect: Retrieval of password
-
Alternative 1 (current choice): Create a
PasswordPrefixFormatter
to handleCommandBox
whenever user type 'pass/' prefix.-
Pros: More secure for being able to mask password.
-
Cons: Complex as it will require certain manipulation of the password in order to mask password.
-
-
Alternative 2: Open an additional alert window to prompt the user for a password.
-
Pros: Greater security as the entire password will be censored.
-
Cons: Require a new window to be opened. This will consume time depending on the system. In addition, this will detract from the user experience due to the shift in focus required.
-
Aspect: Keep track of user session
-
Alternative 1 (current choice): Have a
UserSession
class to handle user session-
Pros : Allow developers to handle user session easily.
-
Cons : Too troublesome as user will to login to ClinicIO at every startup.
-
-
Alternative 2: Save user session state in a file.
-
Pros: More convenience for users as they does not need to login on every startup.
-
Cons: Less secure as users, who understand how ClinicIO works, could edit the role of the user to have unauthorised access to ClinicIO.
-
4.8. Patient Management feature
ClinicIO provides a centralised patient management system to allow you to manage the clinic’s patient record more efficiently. This patient management is the equivalent of Create, Read, Update and Delete (CRUD).
4.8.1. Current Implementation
The Patient Management feature contains multiple operations to indirectly manipulate the UniquePatientList
. The implemented operations include:
-
addpatient
: Add patient in ClinicIO. -
listpatient
: Show all the patients in the ClinicIO record. -
findpatient
: Find the patient from the patient list.
addpatient
Command
The addpatient
command behaves similarly to the add
. The command takes in parameters that required to construct patients Nric
.
The AddPatientCommandParser
can parse command as usual despite not having a list of medical problems and medications, allergies and a preferred doctor.
Furthermore, addpatient
command requires user to be login as a receptionist in order to add patient. If the preDr
prefix is being specify, user must ensure
that the preferredDoctor
exists inside the staff records.
Here is a step-by-step on how addpatient
command execute:
Step 1: The user executes the addpatient
command.
Step 2: The addpatient
command calls UserSession#isLoginAsReceptionist()
to ensure that user is logged in as a receptionist.
Step 3: Once the verfication has been made, the addpatient
command call Model#hasPatient()
to avoid duplicate patient being added.
Step 4: Once the verfication has been made, the addpatient
command call Model#hasStaff()
and preferredDoctor.getRole().equals(DOCTOR)
to check if the preferred doctor is a doctor in ClinicIO.
Step 5: Once the verfication has been made, the addpatient
command call Model#addPatient
to add patient to the patient record.
Step 6: The addpatient
command raises a SwitchTabEvent
to notify ui to switch tabs.
Step 7: The addpatient
command call Model#commitClinicIo()
to finalise the changes made to the patient record.
Step 8: The addpatient
command provide the success message to indicate that patient has been added to ClinicIO.
The following code snippet shows how addpatient
Command is executed:
@Override
public CommandResult execute(Model model, CommandHistory history)
throws CommandException {
requireNonNull(model);
if (!UserSession.isLoginAsReceptionist()) { (1)
throw new CommandException(MESSAGE_NOT_LOGGED_IN_AS_RECEPTIONIST);
} else if (model.hasPatient(toAdd)) { (2)
throw new CommandException(MESSAGE_DUPLICATE_PATIENT);
} else if ((toAdd.getPreferredDoctor().isPresent())
&& (!isPreferredDoctor(model, toAdd.getPreferredDoctor().get()))) { (3)
throw new CommandException(MESSAGE_NO_DOCTOR_FOUND);
}
model.addPatient(toAdd); (4)
model.switchTab(0);
model.commitClinicIo();
return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); }
}
/**
* Check if the staff is the preferred doctor.
* @param model The current model in ClinicIO
* @param preferredDoctor Patient's preferred doctor
* @return True if the staff is a doctor found in ClinicIO.
*/
public boolean isPreferredDoctor(Model model, Staff preferredDoctor) {
requireNonNull(preferredDoctor);
boolean hasStaff = model.hasStaff(preferredDoctor);
boolean isDoctor = preferredDoctor.getRole().equals(DOCTOR);
return hasStaff && isDoctor;
}
1 | Check if user is login as receptionist. |
2 | Check if there is an existing patient record. |
3 | Check if the preferred doctor exist in the staff record. |
4 | Add patient to ClinicIO. |
listpatient
Command
The listpatient
Command retrieve all patients in the UniquePatientList
by creating a FilteredAppointmentList and display all patients in the PatientListPanel
.
Here is a step-by-step on how listpatient
command execute:
Step 1: The user executes the listpatient
command.
Step 2: The listpatient
command calls UserSession#isLogin()
to ensure that user is logged in.
Step 3: Once the verfication has been made, the listpatient
command call Model#updateFilteredPatientList()
to retrieve all patients found in UniqueStaffList
.
Step 4: The listpatient
command raises a SwitchTabEvent
to notify ui to switch tabs.
findpatient
Command
The findpatient
Command find patient with the name as keywords using PatientNameContainsKeywordsPredicate
.
Here is a step-by-step on how findpatient
command execute:
Step 1: The user executes the findpatient
command by specifying all the names to search.
Step 2: The findPatient
command calls UserSession#isLogin()
to ensure that user is logged in.
Step 3: Once the verfication has been made, the findpatient
command call Model#updateFilteredPatientList()
to retrieve all patients found in UniqueStaffList
containing keywords in PatientNameContainsKeywordsPredicate
.
Step 4: The findpatient
command raises a SwitchTabEvent
to notify ui to switch tabs.
The following sequence diagram shows how findpatient
command works:
Logic
findpatient
Command for the logic component.
Model
findpatient
Command for the model component.
Commons
findpatient
Command for the commons component.
4.8.2. Design Considerations
Aspect: Manipulate the list
There are various places to execute command. Here are some options:
-
Alternative 1 (current choice): Handle at the model component
-
Pros : Prevent logic from being heavy of data manipulaiton.
-
Cons : Too challenging to ensure all necessary methods are at the model.
-
-
Alternative 2: Handle at the logic component
-
Pros: Easy to implement by retrieving the
UniquePatientList
to do manipulation of data -
Cons: Loses the purpose of having model and logic components. The logic will be loaded with codes while model will be light on codes.
-
4.9. 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.10, “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.10. Configuration
Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json
).
5. Documentation
This section is designed for you to see how docuementation is being managed in ClinicIO.
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.1. Editing Documentation
You can visit 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.2. Publishing Documentation
You can visit UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
5.3. 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.4. 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.5. 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.6. 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
This section is designed for you to understand
the type of testing that our project uses and ways to run our tests.
6.1. Running Tests
There are three ways to run tests.
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. |
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 allTests
(Mac/Linux:./gradlew clean allTests
)
See UsingGradle.adoc for more info on how to run tests using Gradle. |
Method 3: Using Gradle (headless)
Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.
To run tests in headless mode, open a console and run the command gradlew clean headless allTests
(Mac/Linux: ./gradlew clean headless allTests
)
6.2. Types of tests
We have two types of tests:
-
GUI Tests - These are tests involving the GUI. They include,
-
System Tests that test the entire App by simulating user actions on the GUI. These are in the
systemtests
package. -
Unit tests that test the individual components. These are in
seedu.clinicio.ui
package.
-
-
Non-GUI Tests - These are tests not involving the GUI. They include,
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.clinicio.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.clinicio.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.clinicio.logic.LogicManagerTest
-
6.3. Troubleshooting Testing
Problem: HelpWindowTest
fails with a NullPointerException
.
-
Reason: One of its dependencies,
HelpWindow.html
insrc/main/resources/docs
is missing. -
Solution: Execute Gradle task
processResources
.
7. Dev Ops
This section is designed to guide you through on the Dev Ops tools and practices that is used to manage
this project.
7.1. Build Automation
We use Gradle to perform 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. User Interface Previews
When a pull request has visual changes to the user interface (UI), you can use git fetch upstream
pull/ID/head:NEW_BRANCH_NAME
to obtain the changes locally in a new branch and see what the UI will look like when the
pull request is merged. See local pull requests
for more details.
7.6. 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.7. Managing Dependencies
A project often depends on third-party libraries. For example, ClinicIO depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)
Appendix A: Suggested Programming Tasks to Get Started
Suggested path for new programmers:
-
First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in Section A.1, “Improving each component”.
-
Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. Section A.2, “Creating a new command:
remark
” explains how to go about adding such a feature.
A.1. Improving each component
Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work).
Logic
component
Scenario: You are in charge of logic
. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases.
Do take a look at Section 3.3, “Logic component” before attempting to modify the Logic component.
|
-
Add a shorthand equivalent alias for each of the individual commands. For example, besides typing
clear
, the user can also typec
to remove all patients in the list.
Model
component
Scenario: You are in charge of model
. One day, the logic
-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the ClinicIO, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command.
Do take a look at Section 3.4, “Model component” before attempting to modify the Model component.
|
-
Add a
removeTag(Tag)
method. The specified tag will be removed from everyone in the ClinicIO.
Ui
component
Scenario: You are in charge of ui
. During a beta testing session, your team is observing how the users use your ClinicIO application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn’t prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last patient in the list. Your job is to implement improvements to the UI to solve all these problems.
Do take a look at Section 3.2, “UI component” before attempting to modify the UI component.
|
-
Use different colors for different tags inside patient cards. For example,
friends
tags can be all in brown, andcolleagues
tags can be all in yellow.Before
After
-
Modify
NewResultAvailableEvent
such thatResultDisplay
can show a different style on error (currently it shows the same regardless of errors).Before
After
-
Modify the
StatusBarFooter
to show the total number of people in the ClinicIO.Before
After
Storage
component
Scenario: You are in charge of storage
. For your next project milestone, your team plans to implement a new feature of saving the ClinicIO to the cloud. However, the current implementation of the application constantly saves the ClinicIO after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the ClinicIO storage.
Do take a look at Section 3.5, “Storage component” before attempting to modify the Storage component.
|
-
Add a new method
backupAddressBook(ReadOnlyAddressBook)
, so that the ClinicIO can be saved in a fixed temporary location.
A.2. Creating a new command: remark
By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app.
Scenario: You are a software maintainer for ClinicIO
, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible remark
field for each contact, rather than relying on tags alone. After designing the specification for the remark
command, you are convinced that this feature is worth implementing. Your job is to implement the remark
command.
A.2.1. Description
Edits the remark for a patient specified in the INDEX
.
Format: remark INDEX r/[REMARK]
Examples:
-
remark 1 r/Likes to drink coffee.
Edits the remark for the first patient toLikes to drink coffee.
-
remark 1 r/
Removes the remark for the first patient.
A.2.2. Step-by-step Instructions
[Step 1] Logic: Teach the app to accept 'remark' which does nothing
Let’s start by teaching the application how to parse a remark
command. We will add the logic of remark
later.
Main:
-
Add a
RemarkCommand
that extendsCommand
. Upon execution, it should just throw anException
. -
Modify
AddressBookParser
to accept aRemarkCommand
.
Tests:
-
Add
RemarkCommandTest
that tests thatexecute()
throws an Exception. -
Add new test method to
AddressBookParserTest
, which tests that typing "remark" returns an instance ofRemarkCommand
.
[Step 2] Logic: Teach the app to accept 'remark' arguments
Let’s teach the application to parse arguments that our remark
command will accept. E.g. 1 r/Likes to drink coffee.
Main:
-
Modify
RemarkCommand
to take in anIndex
andString
and print those two parameters as the error message. -
Add
RemarkCommandParser
that knows how to parse two arguments, one index and one with prefix 'r/'. -
Modify
AddressBookParser
to use the newly implementedRemarkCommandParser
.
Tests:
-
Modify
RemarkCommandTest
to test theRemarkCommand#equals()
method. -
Add
RemarkCommandParserTest
that tests different boundary values forRemarkCommandParser
. -
Modify
AddressBookParserTest
to test that the correct command is generated according to the user input.
[Step 3] Ui: Add a placeholder for remark in PersonCard
Let’s add a placeholder on all our PersonCard
s to display a remark for each patient later.
Main:
-
Add a
Label
with any random text insidePersonListCard.fxml
. -
Add FXML annotation in
PersonCard
to tie the variable to the actual label.
Tests:
-
Modify
PersonCardHandle
so that future tests can read the contents of the remark label.
[Step 4] Model: Add Remark
class
We have to properly encapsulate the remark in our Person
class. Instead of just using a String
, let’s follow the conventional class structure that the codebase already uses by adding a Remark
class.
Main:
-
Add
Remark
to model component (you can copy fromAddress
, remove the regex and change the names accordingly). -
Modify
RemarkCommand
to now take in aRemark
instead of aString
.
Tests:
-
Add test for
Remark
, to test theRemark#equals()
method.
[Step 5] Model: Modify Person
to support a Remark
field
Now we have the Remark
class, we need to actually use it inside Person
.
Main:
-
Add
getRemark()
inPerson
. -
You may assume that the user will not be able to use the
add
andedit
commands to modify the remarks field (i.e. the patient will be created without a remark). -
Modify
SampleDataUtil
to add remarks for the sample data (delete yourclinicIo.xml
so that the application will load the sample data when you launch it.)
[Step 6] Storage: Add Remark
field to XmlAdaptedPerson
class
We now have Remark
s for Patient
s, but they will be gone when we exit the application. Let’s modify XmlAdaptedPerson
to include a Remark
field so that it will be saved.
Main:
-
Add a new Xml field for
Remark
.
Tests:
-
Fix
invalidAndValidPersonAddressBook.xml
,typicalPersonsClinicIo.xml
,validAddressBook.xml
etc., such that the XML tests will not fail due to a missing<remark>
element.
[Step 6b] Test: Add withRemark() for PersonBuilder
Since Patient
can now have a Remark
, we should add a helper method to PersonBuilder
, so that users are able to create remarks when building a Person
.
Tests:
-
Add a new method
withRemark()
forPersonBuilder
. This method will create a newRemark
for the patient that it is currently building. -
Try and use the method on any sample
Person
inTypicalPersons
.
[Step 7] Ui: Connect Remark
field to PersonCard
Our remark label in PersonCard
is still a placeholder. Let’s bring it to life by binding it with the actual remark
field.
Main:
-
Modify
PersonCard
's constructor to bind theRemark
field to thePerson
's remark.
Tests:
-
Modify
GuiTestAssert#assertCardDisplaysPerson(…)
so that it will compare the now-functioning remark label.
[Step 8] Logic: Implement RemarkCommand#execute()
logic
We now have everything set up… but we still can’t modify the remarks. Let’s finish it up by adding in actual logic for our remark
command.
Main:
-
Replace the logic in
RemarkCommand#execute()
(that currently just throws anException
), with the actual logic to modify the remarks of a patient.
Tests:
-
Update
RemarkCommandTest
to test that theexecute()
logic works.
A.2.3. Full Solution
See this PR for the step-by-step solution.
Appendix B: Product Scope
Target user profile:
-
Currently a receptionist at a medical clinic
-
Not tech savvy
-
Prefers desktop apps over other types
-
Can type fast
-
Prefer typing over mouse input
-
Reasonably comfortable using CLI apps
-
Able to handle multiple doctors
Value proposition:
-
Allow users to view data that serves as feedback on the functioning of a clinic.
-
Allow users to focus on attending to patients and providing a better experience.
-
Avoid critical errors stemming from inaccurate or incomplete paper records.
-
Improve visibility of a clinic’s patient record and patient management process.
Appendix C: User Stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
user |
login |
have access to confidental information relating to ClinicIO |
|
medical clinic receptionist |
view all patients record |
see medical details of all patients |
|
medical clinic receptionist |
view a patient record |
carry out tasks which require a patient’s details |
|
medical clinic receptionist |
find a patient record |
register patients for appointments |
|
medical clinic receptionist |
add a patient record |
store patient details to make future tasks more convenient |
|
medical clinic receptionist |
edit a patient record |
ensure a patient’s details are up to date |
|
medical clinic receptionist |
delete a patient record |
ensure records of only active patients are kept |
|
medical clinic receptionist |
assign a patient to a queue for any room |
consultation can take place systematically and fairly |
|
medical clinic receptionist |
assign a queue number for each patient |
patient can be called for consultation using his/her queue number |
|
medical clinic receptionist |
assign a patient to a queue based on his/her nric |
I can quickly assign him/her |
|
medical clinic receptionist |
view the length of the queue |
the logistic department can be informed to accomodate visitors |
|
medical clinic receptionist |
assign a patient with specific request to an individual queue for a room |
patients can begin consultation quickly |
|
medical clinic receptionist |
view current patient number in the respective rooms |
estimate the waiting for remaining patients |
|
medical clinic receptionist |
view the current waiting queue |
be informed of the number of waiting patients and report as necessary |
|
medical clinic receptionist |
remove a patient from waiting queue |
update the estimated waiting time for remaining patients |
|
new user |
see the app’s tutorial |
refer to instructions in case I am unsure of how to do something |
|
medical clinic receptionist |
export patient records in a human readable format |
report this data to doctors or management |
|
medical clinic receptionist |
register a patient |
assign patient to the waiting queue to consult a doctor |
|
doctor |
receive patient specific alerts upon their registration |
be informed about the patient |
|
medical clinic receptionist |
view analytics |
improve inefficiencies in the clinic’s processes |
|
medical clinic receptionist |
alert patients via email |
send them appointment reminders |
|
medical clinic receptionist |
patient medical information to the respective doctor |
inform a doctor of their patient’s medical history |
|
medical clinic receptionist |
prioritize doctors based on patient preference |
enable patients to feel comfortable |
|
medical clinic receptionist |
update the drug inventory |
ensure it is updated |
|
medical clinic receptionist |
group patients based on their diagnosis |
analyze patient trends |
|
medical clinic receptionist |
record medications prescribed to patients |
keep track of a patient’s drug history |
|
careless user |
undo any command |
revert any mistakes |
|
medical clinic receptionist |
request for a specific doctor for consultation |
enable patients to feel comfortable |
|
medical clinic receptionist |
create an appointment |
book patients for consultations in advance |
|
medical clinic receptionist |
cancel an appointment |
cancel appointments if a patient cannot make the date |
|
medical clinic receptionist |
view a list of appointments |
enable receptionists to find free appointment slots |
|
medical clinic receptionist |
reschedule appointments |
change appointment bookings in case of an error |
|
medical clinic receptionist |
have a calendar view of appointments |
provide easier viewing of the appointment schedule |
Appendix D: Use Cases
(For all use cases below, the System is the program, unless specified otherwise)
Use case: View a patient’s record
Actors: Medical Clinic Receptionist, Doctor
Pre-condition: patient record must exist
MSS
-
Actor(s) chooses to view a patients record
-
System retrieve and display the patients record details.
Use case ends.
Extensions
-
2a. The system cannot find the patient’s record.
-
System displays an error message. "No record found."
Use case ends
-
D.1. Use case: Find patients record
Actor: Medical Clinic Receptionist
MSS
-
Actor requests to find patients record
-
System retrieve and display patient records relevant to search information.
Use case ends.
Extensions
-
2a. No search information entered.
-
System displays an error message. "No search information found."
Use case ends
-
-
3a. System cannot find patient record.
-
System displays an error message. "No record found."
Use case ends
-
D.2. Use case: add a patient record
Actor: Medical Clinic Receptionist
MSS
-
Actor enters patient information
-
Actor requests to add patient record to the system.
-
System display a successful message. “Patient added successfully.”
Use case ends.
Extensions
-
2a. No information entered
-
System displays an error message. "Please enter required information."
Use case ends
-
-
3a. Invalid information
-
System displays an error message. "Invalid information entered".
Use case resumes at step 1
-
D.3. Use case: Edit patient record
Actor: Medical Clinic Receptionist
Pre-condition: Patient’s record exists
MSS
-
Actor(s) request to retrieve patient’s record.
-
System finds and displays patient’s record.
-
Actor(s) request to edit patient’s record.
-
Actor(s) selects field to edit and edits patient’s record.
-
System requests confirmation of edit(s).
-
Actor(s) confirm message.
-
System updates patient’s record and displays edited record.
-
System displays success message: “Record edited successfully”.
Use case ends.
Extensions
-
2a. The system cannot find the requested patient’s record.
-
System displays an error message. "No record found."
Use case ends.
-
D.4. Use case: Delete patient record
Actor: Medical Clinic Receptionist
MSS
-
Actor(s) request to retrieve a patient record.
-
System find and display the patient record.
-
Actor(s) request to delete patient record in the system.
-
System request confirmation from Actor(s).
-
Actor(s) confirm message.
-
System delete patient record and displays a successful message. “Patient deleted successfully.”
Use case ends.
Extensions
-
2a. The system cannot find the requested patient’s record.
-
System displays an error message. "No record found."
Use case ends.
-
D.5. Use case: Assign patient to a different queue for each room
Actor: Medical Clinic Receptionist
Pre-condition: Patient is registered.
MSS
-
Actor request to add patient to queue.
-
System finds and displays queues for each room.
-
Actor selects queue and adds patient to queue.
-
System displays confirmation message for queue assignment.
-
Actor confirms queue assignment.
-
System adds patient to queue and returns queue numb
Use case ends.
Extensions
-
1a. The system cannot find the requested patient’s record.
-
1a.1 System displays an error message. "No record found."
-
1a.2 System displays prompt to add patient
-
1a.3 Actor adds patient records
-
1a.4 Use case: Add patient record.
-
1a.5 Repeat MSS
Use case ends.
-
D.6. Use case: View ClinicIO analytics
Actor: Medical Clinic Receptionist
MSS
-
Actor requests to view ClinicIO analytics.
-
System retrieves values and computes all statistics.
-
System displays numerical summary and visualizations.
Use case ends.
Appendix E: Non Functional Requirements
This section describe other requirements that is not stated in the user guide.
These are the list of non-functional requirements:
-
Should work on any mainstream OS as long as it has Java
9
or higher installed. -
Should be able to hold up to 1000 concurrent users 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.
-
Provide a simple, clean and minimalist GUI interface for all read queries.
-
User can use command line for all write queries.
-
Should have a simple tutorial section to help ramp up new users to the system.
-
Should be able to access offline in order to ensure smooth operations daily.
Appendix F: Glossary
This section is designed to provide you with definitions for terms that are used in this document below.
Appendix G: 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. |
G.1. Launch and Shutdown
You are launch the program initially
Instructions:
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a set of sample contacts. The window will be full-screen.-
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.
G.2. Adding a patient
You are adding a new patient to ClinicIO
Prerequisites: You must be login as a receptionist.
Instructions:
-
Test case:
addpatient n/Steve Rogers ic/S8231545C p/91021231 e/captain@america.com a/NYC, Mahattan Street alrg/dust
Expected: Patient record is added to ClinicIO. The list will update immediately after adding patient. -
Test case:
addpatient n/Steve Johnson ic/S8231545C p/91021231 e/captain@america.com a/NYC, Mahattan Street alrg/dust preDr/Dr Lee
Expected: No patient is added and a error message will show "The preferred doctor is not found."
G.3. Login as a receptionist
You are login to ClinicIO as a receptionist.
Prerequisites: None.
Instructions:
-
Test case:
login r/receptionist n/Mary Jane pass/reception3
Expected: User is login successfully and will show another window. -
Test case:
login r/receptionist n/Mary Jane pass/reception151
Expected: An error message will show "Invalid login credentials. Please try again."
G.4. Login as a doctor
You are login to ClinicIO as a doctor.
Prerequisites: None.
Instructions:
-
Test case:
login r/doctor n/Adam Bell pass/doctor1
Expected: User is login successfully and will show another window. -
Test case:
login r/doctor n/Adam Bell pass/doctor1241
Expected: An error message will show "Invalid login credentials. Please try again."