Thank you for your interest in contributing to ClinicIO!

By: W14-1      Since: Sept 2018      Licence: MIT

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:

  1. JDK 9 or later

    JDK 10 on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK 9.
  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > 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:

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD 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:

  1. Run the seedu.clinicio.MainApp and try a few commands

  2. 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,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

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:

  1. Configure the site-wide documentation settings in build.gradle, such as the site-name, to suit your own project.

  2. Replace the URL in the attribute repoURL in DeveloperGuide.adoc and UserGuide.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:

  1. Get some sense of the overall design by reading Section 3.1, “Architecture”.

  2. 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

Architecture
Figure 1. Architecture Diagram

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.

  • 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 {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.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

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.

SDforDeletePerson
Figure 3. Component interactions for 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.

SDforDeletePersonEventHandling
Figure 4. Component interactions for 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.

UiClassDiagram
Figure 5. Structure of the UI Component


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 the Model change.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

3.3. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

  1. Logic uses the AddressBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a patient) and/or raise events.

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

DeletePersonSdForLogic
Figure 7. Interactions Inside the Logic Component for the delete 1 Command

3.4. Model component

ModelClassDiagram
Figure 8. Structure of the 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.

ModelClassBetterOopDiagram

3.5. Storage component

StorageClassDiagram
Figure 9. Structure of the 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:

q1
q2

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:

q3
q4

Step 5: The user keeps adding patients until there are 5 patients in the queue. The mainQueue and preferenceQueue look like this:

q5

The end user only sees one queue:

q6

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.

  • 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.

UndoRedoStartingStateListDiagram

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.

UndoRedoNewCommand1StateListDiagram

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.

UndoRedoNewCommand2StateListDiagram
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.

UndoRedoExecuteUndoStateListDiagram
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:

UndoRedoSequenceDiagram

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.

UndoRedoNewCommand3StateListDiagram

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.

UndoRedoNewCommand4StateListDiagram

The following activity diagram summarizes what happens when a user executes a new command:

UndoRedoActivityDiagram

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 and VersionedClinicIo.

  • 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.

ApptClassDiagram
Figure 10. Appointment class diagram.

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.
AddApptSequenceDiagram
Figure 11. Appointment Construction Process in addappt Command.

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.

CancelApptSequenceDiagram
Figure 12. Sequence diagram of cancelappt.

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 the MedicineInventory contains the Medicine with name medicineName.

  • MedicineInventory#addMedicine(MedicineName medicineName, Medicine newMedicine) — Adds newMedicine to the MedicineInventory with its medicineName.

  • MedicineInventory#updateMedicineQuantity(MedicineName medicineName, MedicineQuantity newQuantity) — Updates the MedicineQuantity of the Medicine with name medicineName with the newQuantity in the MedicineInventory.

  • MedicineInventory#deleteMedicine(MedicineName medicineName) — Deletes the Medicine with name medicineName from the MedicineInventory.

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 each MedicineType.

    • Pros: Uses less memory.

    • Cons: Complicates implementation.

  • Alternative 2: Use different MedicineInventory for each MedicineType.

    • 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-prescribed Medicine.

    • Cons: Complicates implementation of logic and incorporation of data from Analytics.

  • Alternative 2 (current choice): Maintain a fixed amount of MedicineQuantity for each Medicine in the MedicineInventory.

    • 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.

AnalyticsClassDiagram

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 to AnalyticsDisplay.

    • contains SummaryData and CircularList objects. The latter stores VisualizationData 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 by Plot 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 as dataGroupsLabels.

      • dataGroupsLabels: the label for each group of data. Used in the legend of the chart. Must be the same in number as dataGroups.

  • 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:

  1. Analytics updates the appropriate statistics object with the latest appropriate lists (e.g. appointments) from ModelManager.

  2. The particular statistics object then calculates the summary for the various time periods mentioned above.

  3. The particular statistics object then calculates data for one or more visualizations and updates StatData.

  4. Analytics retrieves StatData from the statistics object and passes it to ModelManager.

  5. ModelManager raises an AnalyticsDisplayEvent containing StatData.

  6. AnalyticsDisplay retrieves StatData from AnalyticsDisplayEvent and passes it to AnalyticsPlot.

  7. AnalyticsPlot updates the summary user interface.

  8. AnalyticsPlot decides and plots the appropriate chart on the user interface.

patientstats Command

The following are the processes performed specifically by PatientStatistics:

  1. PatientStatistics calculates the summary of the number of patients.

  2. PatientStatistics calculates the number of patients for the following time periods in a day:

    • 9AM - 12PM, 12PM - 3PM, 3PM - 6PM, 6PM -9PM

  3. PatientStatistics updates StatData with this data for a vertical bar chart visualization.

  4. PatientStatistics calculates the total historical number of patients for each day in a week.

  5. PatientStatistics updates StatData 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:

  1. AppointmentStatistics calculates the summary of the number of appointments.

  2. AppointmentStatistics calculates the number of scheduled and available appointment slots for each day of the following week.

  3. AppointmentStatistics updates StatData object with this data for a stacked bar chart visualization.

  4. AppointmentStatistics calculates the number of scheduled appointments for each date in the current year.

  5. AppointmentStatistics updates StatData object with this data for a line chart visualization.

doctorstats Command

The following are the processes performed specifically by DoctorStatistics:

  1. DoctorStatistics calculates the summary of the average number of consultations per doctor.

  2. DoctorStatistics calculates the number of patients who prefer each doctor.

  3. DoctorStatistics updates StatData 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.

ExportSwimlaneDiagram

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 into ClinicIO_patientdata.csv.

  • exportpatientsappointments Command - exports all patients' appointment records into ClinicIO_patientsappointments .csv.

  • exportpatientsconsultations Command - exports all patients' consultation records into ClinicIO_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.

LoginClassDiagram
Figure 13. Class Diagram for the Staff.
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

LoginLogicSequenceDiagram
Figure 14. Sequence Diagram for the login Command for the logic component.


Model

LoginModelSequenceDiagram
Figure 15. Sequence Diagram for the login Command for the model component.


Commons

LoginCommonsSequenceDiagram
Figure 16. Sequence Diagram for the login Command for the commons component.


The following activity diagram summarizes what happens when a user executes LoginCommand:

LoginActivityDiagram
Figure 17. Activity Diagram for the 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 handle CommandBox 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

FindPatientLogicSequenceDiagram
Figure 18. Sequence Diagram for the findpatient Command for the logic component.


Model

FindPatientModelSequenceDiagram
Figure 19. Sequence Diagram for the findpatient Command for the model component.


Commons

FindPatientCommonsSequenceDiagram
Figure 20. Sequence Diagram for the 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 using LogsCenter.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.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 21. Saving documentation as PDF files in Chrome

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.
Table 1. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

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.
Table 2. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

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 docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

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 choose Run '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:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.clinicio.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.clinicio.commons.StringUtilTest

    2. 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

    3. 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 in src/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.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. 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:

  1. 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”.

  2. 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.
  1. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing clear, the user can also type c to remove all patients in the list.

    • Hints

    • Solution

      • Modify the switch statement in AddressBookParser#parseCommand(String) such that both the proper command word and alias can be used to execute the same intended command.

      • Add new tests for each of the aliases that you have added.

      • Update the user guide to document the new aliases.

      • See this PR for the full solution.

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.
  1. Add a removeTag(Tag) method. The specified tag will be removed from everyone in the ClinicIO.

    • Hints

      • The Model and the ClinicIO API need to be updated.

      • Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags?

      • Find out which of the existing API methods in ClinicIO and Person classes can be used to implement the tag removal logic. ClinicIO allows you to update a person, and Person allows you to update the tags.

    • Solution

      • Implement a removeTag(Tag) method in ClinicIO. Loop through each patient, and remove the tag from each patient.

      • Add a new API method deleteTag(Tag) in ModelManager. Your ModelManager should call AddressBook#removeTag(Tag).

      • Add new tests for each of the new public methods that you have added.

      • See this PR for the full solution.

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.
  1. Use different colors for different tags inside patient cards. For example, friends tags can be all in brown, and colleagues tags can be all in yellow.

    Before

    getting started ui tag before

    After

    getting started ui tag after
    • Hints

      • The tag labels are created inside the PersonCard constructor (new Label(tag.tagName)). JavaFX’s Label class allows you to modify the style of each Label, such as changing its color.

      • Use the .css attribute -fx-background-color to add a color.

      • You may wish to modify DarkTheme.css to include some pre-defined colors using css, especially if you have experience with web-based css.

    • Solution

      • You can modify the existing test methods for PersonCard 's to include testing the tag’s color as well.

      • See this PR for the full solution.

        • The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes.

  2. Modify NewResultAvailableEvent such that ResultDisplay can show a different style on error (currently it shows the same regardless of errors).

    Before

    getting started ui result before

    After

    getting started ui result after
  3. Modify the StatusBarFooter to show the total number of people in the ClinicIO.

    Before

    getting started ui status before

    After

    getting started ui status after
    • Hints

      • StatusBarFooter.fxml will need a new StatusBar. Be sure to set the GridPane.columnIndex properly for each StatusBar to avoid misalignment!

      • StatusBarFooter needs to initialize the status bar on application start, and to update it accordingly whenever the ClinicIO is updated.

    • Solution

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.
  1. 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 to Likes 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:

  1. Add a RemarkCommand that extends Command. Upon execution, it should just throw an Exception.

  2. Modify AddressBookParser to accept a RemarkCommand.

Tests:

  1. Add RemarkCommandTest that tests that execute() throws an Exception.

  2. Add new test method to AddressBookParserTest, which tests that typing "remark" returns an instance of RemarkCommand.

[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:

  1. Modify RemarkCommand to take in an Index and String and print those two parameters as the error message.

  2. Add RemarkCommandParser that knows how to parse two arguments, one index and one with prefix 'r/'.

  3. Modify AddressBookParser to use the newly implemented RemarkCommandParser.

Tests:

  1. Modify RemarkCommandTest to test the RemarkCommand#equals() method.

  2. Add RemarkCommandParserTest that tests different boundary values for RemarkCommandParser.

  3. 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:

  1. Add a Label with any random text inside PersonListCard.fxml.

  2. Add FXML annotation in PersonCard to tie the variable to the actual label.

Tests:

  1. 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:

  1. Add Remark to model component (you can copy from Address, remove the regex and change the names accordingly).

  2. Modify RemarkCommand to now take in a Remark instead of a String.

Tests:

  1. Add test for Remark, to test the Remark#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:

  1. Add getRemark() in Person.

  2. You may assume that the user will not be able to use the add and edit commands to modify the remarks field (i.e. the patient will be created without a remark).

  3. Modify SampleDataUtil to add remarks for the sample data (delete your clinicIo.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:

  1. Add a new Xml field for Remark.

Tests:

  1. 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:

  1. Add a new method withRemark() for PersonBuilder. This method will create a new Remark for the patient that it is currently building.

  2. Try and use the method on any sample Person in TypicalPersons.

[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:

  1. Modify PersonCard's constructor to bind the Remark field to the Person 's remark.

Tests:

  1. 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:

  1. Replace the logic in RemarkCommand#execute() (that currently just throws an Exception), with the actual logic to modify the remarks of a patient.

Tests:

  1. Update RemarkCommandTest to test that the execute() 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

  1. Actor(s) chooses to view a patients record

  2. 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

MSS

  1. Actor requests to find patients record

  2. 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

MSS

  1. Actor enters patient information

  2. Actor requests to add patient record to the system.

  3. 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

  1. Actor(s) request to retrieve patient’s record.

  2. System finds and displays patient’s record.

  3. Actor(s) request to edit patient’s record.

  4. Actor(s) selects field to edit and edits patient’s record.

  5. System requests confirmation of edit(s).

  6. Actor(s) confirm message.

  7. System updates patient’s record and displays edited record.

  8. 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

MSS

  1. Actor(s) request to retrieve a patient record.

  2. System find and display the patient record.

  3. Actor(s) request to delete patient record in the system.

  4. System request confirmation from Actor(s).

  5. Actor(s) confirm message.

  6. 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

  1. Actor request to add patient to queue.

  2. System finds and displays queues for each room.

  3. Actor selects queue and adds patient to queue.

  4. System displays confirmation message for queue assignment.

  5. Actor confirms queue assignment.

  6. 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

MSS

  1. Actor requests to view ClinicIO analytics.

  2. System retrieves values and computes all statistics.

  3. 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:

  1. Should work on any mainstream OS as long as it has Java 9 or higher installed.

  2. Should be able to hold up to 1000 concurrent users without a noticeable sluggishness in performance for typical usage.

  3. 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.

  4. Provide a simple, clean and minimalist GUI interface for all read queries.

  5. User can use command line for all write queries.

  6. Should have a simple tutorial section to help ramp up new users to the system.

  7. 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.

CLI

Command Line Interface

Create, Read, Update and Delete (CRUD)

These are the basic functions of a persistent database.

Doctor

A person who is qualified to treat people who are ill.

GUI

Graphical User Interface

Mainstream OS

Windows, Linux, Unix, OS-X

Medical Clinic

A clinic that deal with medical issues only.

Medical Clinic Receptionist

A person that is in charge of the front desk receptionist in a medical clinic.

Private contact detail

A contact detail that is not meant to be shared with others

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:

  1. Download the jar file and copy into an empty folder

  2. Double-click the jar file
    Expected: Shows the GUI with a set of sample contacts. The window will be full-screen.

    1. Saving window preferences

  3. Resize the window to an optimum size. Move the window to a different location. Close the window.

  4. 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:

  1. 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.

  2. 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:

  1. Test case: login r/receptionist n/Mary Jane pass/reception3
    Expected: User is login successfully and will show another window.

  2. 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:

  1. Test case: login r/doctor n/Adam Bell pass/doctor1
    Expected: User is login successfully and will show another window.

  2. Test case: login r/doctor n/Adam Bell pass/doctor1241
    Expected: An error message will show "Invalid login credentials. Please try again."