By: Team W9-02 (HealthHub)      Since: Jan 2019      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9

    • The Java Development Kit, or JDK for short, provides the necessary existing libraries and implementations that we have used in our implementation of HealthHub. The current version of HealthHub needs at least JDK version 9 to support the full functionality it has to offer.

      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 is an Integrated Development Environment(IDE) that provides the necessary tools and available plugins needed to compile, build and run the project.

      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.

Don’t have Intellij or JDK installed? Click on the following links to download them below:

1.2. Setting up the project in your 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. For Mac/Linux environments, run the command ./gradlew processResources instead. It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

  9. Open MainWindow.java and check for any code errors

    1. Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully

    2. To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select Add '--add-modules=…​' to module compiler options for each error

  10. Repeat this for the test folder as well (e.g. check HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

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

  2. Run the tests to ensure they all pass.

1.4. Configurations to do before writing code

1.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  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.

1.4.2. Getting started

When you have set up your environment successfully for building and running HealthHub, you are ready to get started on coding to contribute to the project. But before that, you may consider familiarising yourself with HealthHub through the following:

2. Design

2.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. The following class plays an important role at the architecture level:

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

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

How the architecture components interact with each other

The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1.

logicseqdiagram
Figure 2. Component interactions for delete request 1 command

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 3. Structure of the UI Component

API : UI Package

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, RequestListPanel, InfoPanel, HealthWorkerListPanel, StatusBarFooter, etc.

All of these, including the MainWindow, inherit from the UiPart class, which is an abstract class containing methods for handling the loading of FXML files.

The UI component uses the JavaFX UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Listens for changes to the Model data so that the UI can be updated with the modified data dynamically.

2.3. Logic component

logicclassdiag
Figure 4. Structure of the Logic Component

API : Logic.java

  1. Logic uses the HealthHubParser 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 request) or sometimes only affecting the display (eg. listing all requests).

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

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as printing out the result message in the command line.

2.4. Model component

modelUmlDiagram
Figure 5. Structure of the Model Component

API : Model.java

The Model,

  • stores the Request Book data.

  • stores the HealthWorker Book data.

  • Both HealthWorkerBook and RequestBook stores multiple previous states in the form of ReadOnlyHealthWorkerBook and ReadOnlyRequestBook.

  • A UserPrefs object denoting the user settings for the application.

As a more OOP model, we store a list of Request objects in Request Book, which stores the details of every request, including the patient’s details, request date and status. In addition, each Request also stores a Set of Conditions of the patient.
Similarly for HealthWorker, the list of HealthWorker is stored in a HealthWorkerBook, and each Specialisation that the HealthWorker has is stored in a Set that is contained in the Skills class.

2.5. Storage component

StorageClassDiagram
Figure 6. 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 Request, HealthWorker Book data in json format and read it back.

The storage class converts the object data of Request and HealthWorker by converting the objects into a json object of strings which will be stored in the .json file. When reading the file, the json library passes the respective strings into their java object constructors to recreate the objects.

2.6. Common classes

Classes used by multiple components are in the seedu.addressbook.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

The add, edit and delete command have been tweaked to include addition of and Request and HealthWorker.

CommandMode Enum class was introduced in v1.1 to allow for add, edit and delete different types of objects respective to the different CommandMode Enums. The various CommandMode enums are: CommandMode.HEALTHWORKER CommandMode.REQUEST CommandMode.OTHERS CommandMode.INVALID

Each CommandMode enum is matched by it’s own corresponding set of keywords, which are string equivalents of the enum counterparts that the user can input to specify the type of object he/she wishes to add. For example:

  • The command string add healthworker n/Dog Terr that is entered by the user contains the keyword healthworker after the add command word, which is mapped to it’s CommandMode equivalent in AddCommandParser.

The CommandMode enum also has a Set<HealthWorker field which contains mappings from a set of keywords to it’s respective CommandMode values for translation of user input into it’s corresponding CommandMode equivalents.

Then implementation for CommandMode is as follows:

public enum CommandMode {

    HEALTH_WORKER,
    REQUEST,
    OTHERS,
    INVALID;

    public static final String MODE_HEALTHWORKER = "1";
    public static final String MODE_REQUEST = "2";
    public static final String MODE_OTHERS = "4";
    public static final String MODE_INVALID = "5";

    private static final Set<String> healthWorkerModes = new HashSet<>(Arrays.asList("healthworker", "h", "1"));
    private static final Set<String> requestModes = new HashSet<>(Arrays.asList("request", "r", "2"));

    /**
     * Method that checks for the corresponding CommandMode given a string input.
     * Returns INVALID if input string does not match any of the given modes.
     */
    public static CommandMode checkMode(String input) {
        if (healthWorkerModes.contains(input)) {
            return CommandMode.HEALTH_WORKER;
        } else if (requestModes.contains(input)) {
            return CommandMode.REQUEST;
        }

        return CommandMode.INVALID;
    }
}

These CommandModes provide flexbility and functionality to switch between parsing of different HealthWorker and Request objects.

  • Each of the add, edit and delete command is eventually parsed through the AddCommandParser, EditCommandParser and DeleteCommandParser classes respectively, which returns an AddCommand, EditCommand and DeleteCommand object respectively.

3.1. Add feature

The add command allows for users to register new HealthWorker and Request objects into HealthHub.

The format for the add command is as follows:

add <keyword> <parameters>…​

When the user inputs an add command, the add command is eventually passed into an AddCommandParser object that handles the argument fields after the add keyword in the command.

The AddCommandParser first checks the first keyword in the string for a valid CommandMode string equivalent. if the keyword does not contains a valid string representation of any CommandMode enum, then a ParseException is thrown by the AddCommandParser. This initial sequence of steps is also repeated in the following EditCommandParser and DeleteCommandParser objects.

After checking for valid keyword, the AddCommandParser object checks for the following compulsory information, along with regular expressions checking for validity of each field:

3.1.1. Adding a HealthWorker

Format: add healthworker/h/1 <parameters>…​

  • name field prepended by the n/ prefix. Valid inputs of name field will be represented and stored as a Name class object.

    • name field can only contain alphanumeric characters, and cannot be an empty string.

  • phone field prepended by the p/ prefix. Valid inputs of phone field will be represented and stored as a phone class object.

    • phone contains only 8 numeric digits, of which the first must start with a 6,8 or 9.

  • nric field prepended by the i/ prefix. Valid inputs of nric field will be represented and stored as a Nric class object.

    • nric field must contains a 9 character string, starting with either a S or T, followed by 7 numeric digits and a alphabet letter. All letters used must be capitalized

  • organization field prepended by the o/ prefix. Valid inputs of organization field will be represented and stored as a Organization class object.

    • The organization field must contain only alpha numeric characters, and contain at least 2 characters.

  • specialisation field prepended by the s/ prefix. All valid specialisation fields will be parsed into it’s respective Specialisation enum equivalent, collected and stored as a Set<Specialisation> in the Skills class object.

    • The values in the specialisation field will be only be accepted if they correspond to specific mappings to an existing Specialisation enum that are registered in HealthHub. For information on the valid Specialisation mappings, visit <appendix for Specialisation num>

AddCommandParser first checks for the presence of the prefixes for each of the compulsory fields mentioned above. If any of them are missing, or if any invalid fields as per the conditions mentioned above, then the command is invalid and a ParseException is thrown.

If the keyword denoted maps to CommandMode.HEALTHWORKER, then AddCommandParser constructs a HealthWorker object representing the health worker that is just added with the respective Name, Phone, Nric, Organization and Skills objects parsed from the user input, and returns an AddCommand contain the constructed person to be added.

If multiple fields of the same prefix are added, the last value of the last field will be the one accepted for all fields except the specialisation field.

Upon a successful add command, the new HealthWorker or Request constructed from the user entered fields will be added to the end of the UniqueHealthWorkerList.

Valid Specialisations

The Specialisation field is an Enum type in Java, and supports only the enum fields that are specified in the Specialisation class itself.

The various available specialisation enum values of the Specialisation class is as follows in the implementation of Specialisation:

public enum Specialisation {

    GENERAL_PRACTICE,
    CARDIOLOGY,
    ENDOCRINOLOGY,
    NEUROLOGY,
    ORTHOPAEDIC,
    PAEDIATRIC,
    GYNAECOLOGY,
    UROLOGY,
    PATHOLOGY,
    HAEMATOLOGY,
    PHYSIOTHERAPY,
    OCCUPATIONAL_THERAPY,
    ANAESTHESIOLOGY;

    ...
}

3.2. Add Request feature

3.2.1. Current Implementation

The add request command allows the LogicManager to create a new request and add it to the list of requests. Adding a new request requires the patient’s name, patient’s phone number, patient’s address, patient’s NRIC number and the patient’s conditions. The format of the add request command is add request n/NAME p/PHONE i/NRIC a/ADDRESS dt/DATETIME c/CONDITION.

The following sequence shows the sequence when the add command is execute by the LogicManager:

addrequest

Figure 3.2.1.1 Sequence Diagram for add request command

From the diagram above:

  1. LogicManager’s execute is called when the administrator keys in `add request and it calls upon parseCommand of HealthHubParser to parse the command

  2. HealthHubParser will initialize AddCommandParser and invoke the method parse to further parse request command

  3. parse will be invoked and passed the parameters of the add command.

  4. If all the arguments of the add commands are valid, AddRequestCommand will be returned to the LogicManager

  5. LogicManger will then calls the method execute method of AddRequestCommand

  6. AddRequestCommand will call addRequest passing Request as an argument to Model and after calls commitRequestBook method from Model

  7. A CommandResult will be returned at the end.

3.2.2. Design Consideration

Aspect: Data Structure for the list of Requests
  • Alternative 1 (Current Approach): Using a UniqueRequestList

    • Pros: The comparison is not as rigid. You can create requests with similar names/phones as long as it passes a less stringent criteria.

    • Cons: You need additional overhead. You need to create an extra class and function to check.

  • Alternative 2: Using a Set

    • Pros: Do not need an extra function. You can use use equals for comparison

    • Cons: It is a strict check and as a result you will not be allowed to create requests with same phone/same name etc.

Aspect: Data Structure of Request
  • Alternative 1 (Current Approach): All parameters in Request are abstracted out to have a class of its own (with the exception of the HealthWorker).

    • Pros: It adheres to the Single Responsibility Principles (SRP) and the Separation of Concerns (SoC) as each parameter checks whether it is valid

    • Cons: Many different classes are created which increases the complexity of the code

  • Alternative 2: Store all parameters of Request as String

    • Pros: Easy to implement.

    • Cons: Violates SRP as one single class will need to check if the arguments are valid.

The RequestStatus class within the Request object utilises the Java Enum to ensure type safety in user inputs.

The implementation of the RequestStatus class is as follows:

    private enum Status {
        PENDING,
        ONGOING,
        COMPLETED
    }

     public RequestStatus(String status) {
         this.requestState = Status.valueOf(status);
     }

     public RequestStatus() {
         this(Status.PENDING.name());
     }

Through this, any user String that is passed in as an argument for the RequestStatus field is automatically type checked, ensuring that there are no invalid request statuses entered.

3.3. Edit feature

The edit command allows users to modify details of existing HealthWorker or Request objects should the information need to be updated.

The format for the edit command is as follows:

edit <keyword> <index> <parameter> [<parameter>]…​

Similar to the add command, the edit command arguments are first checked passed into the EditCommandParser class instance, where it evaluates for a valid first keyword and argument prefixes and values. Additionally, the <index> field is also checked against the existing list of HealthWorker or Request to ensure that the provided <index> is a valid <index> in the list.

The argument field after the first keyword denoting the specified CommandMode must not be empty, and must contain at least one valid prefix and a valid value for the respective field to be edited. The prefixes and validity follows the same conditions as stated in the add command.

Similar to the add command, only the last value will be accepted for fields with the same prefixes.

If any specialisation fields are entered, the current Skills object of the HealthWorker object being edited will be entirely replaced by a new Set of Specialisation that corresponds to valid specialisation fields enteed by the user.

3.3.1. Editing HealthWorker

Upon parsing the argument string into EditCommandParser, it first checks that the <index> field provided is non-negative, and not greater than the size of the UniqueHealthWorkerList.

After checking for a valid CommandMode.HEALTHWORKER keyword after the edit command word, EditCommandParser then tokenizes the argument string after the keyword into a ArgumentMultiMap object that stores the prefix to values mapping for each field specified. If no prefixes, or invalid prefixes are specified, or if invalid values for any prefix is input, then EditCommandParser throws a ParseException, and displays an error message to the terminal.

If all values specified are valid and at least one field to specify is provided, the EditCommandParser parses the ArgumentMultiMap containing the values to be updated and it’s respective prefixes into an EditHealthWorkerDescriptor object. EditCommandParser then passes the EditHealthWorkerDescriptor and the HealthWorker object fetched from the HealthWorkerBook using the specified valid <index> into a EditHealthWorkerCommand object and returned.

In the EditHealthWorkerCommand, each HealthWorker related field prefix is checked for it’s value in EditHealthWorkerDescriptor, and if a value exists, it replaces the current value of the corresponding field in the HealthWorker object. Else, the field in HealthWorker is unchanged.

Upon a successful edit command, the HealthWorker or Request object that has had it’s fields edited will immediately reflect the changes in details on the GUI.

3.4. Delete feature

Using the delete command, the user can remove HealthWorker and Request objects from the system.

The format for the delete command is as follows:

delete <keyword> <index>

The delete command takes in only two arguments after the delete command word: the <keyword> corresponding to the respective CommandMode enum, as well as the <index> of the HealthWorker or Request object to be deleted, as indicated in the UniqueHealthWorkerList or UniqueRequestList displayed in the GUI.

The <index> entered by the user is one-based, while it is converted to it’s zero-based format in HealthHub during the deletion process. This ensures that non-technical people who use HealthHub do not need to adjust to zero-based indexing, and can continue using one-based indexing which they are more familiar with.

If the user enters an invalid <index> field, such as a negative index or an index that is greater than the size of the UniqueHealthWorkerList or UniqueRequestList, then a ParseException will be thrown, and an error message will be displayed to the user.

Upon successful delete of a valid HealthWorker or Request, the removed entity will be immediately reflected on the GUI, and be removed from view.

3.5. Assign Request feature

The assign request feature allows an administrator to assign a request to a particular healthworker.

3.5.1. Current Implementation

The following sequence diagram shows the sequence flow from the LogicManager to the ModelManager when a user enters a assign request command:

assignreq

Figure 3.3.1.1 Sequence diagram to illustrate Logic component interactions for assign request command.

assignReqToHw

Figure 3.3.1.2 Sequence diagram to illustrate Logic and Model component interactions for AssignRequestCommand.

addHwRequestDates

Figure 3.3.1.3 Sequence diagram illustrates addition of the relevant Date objects into the TreeSet<Date> of the healthWorker.

checkConflictingRequests

Figure 3.3.1.4 Sequence diagram illustrates interaction between AssignRequestCommand and healthWorkerAppointments

  1. When LogicManager receives the execute command, it calls the parseCommand method in RequestBookParser.

  2. RequestBookParser will receive assign as the command and instantiate AssignRequestCommandParser to further parse the command.

  3. If the arguments specified in the assign command are invalid, a AssignCommand will be created and returned back to the LogicManager.

  4. LogicManager will proceed to call the execute command of AssignCommand

  5. AssignRequestCommand will proceed to call the getFilteredRequestList method of Model.

  6. Iterate through the requestIds, if valid, add the request corresponding to that index to the set of Request.

  7. Iterates through all the requests already assigned to that healthWorker. If there is a conflict of schedule (i.e Requests assigned to that healthWorker are less than 2 hours apart), a CommandException is thrown.

  8. Writes the updated request to the RequestBook in ModelManager, if all the request timings are valid.

3.5.2. Design Consideration

Aspect: Algorithm in ensuring no clashing requests assigned to healthworkers - Data Structure and implementation
  • Alternative 1 (current choice): Using a Balanced Binary Search Tree (java TreeSet) to keep track of the RequestDate of each request attended to by a particular healthWorker.

    • Reasoning: Since the allowed time interval between 2 consecutive requests should be at least 2 hours, we can take advantage of of this by only keeping track of the start time of requests. Hence we can utilise the ceiling(), contains() and floor() methods of the TreeSet, which runs effeciently in O(log N) time (where N is the number of requests). Here is the code snippet that implements this:

    // Note: healthWorkerAppointments is the TreeSet that stores the appointment dates
    Date date = request.getRequestDate().getDate();
    calendar.setTime(date);
    calendar.add(Calendar.HOUR_OF_DAY, -MIN_REQUEST_DURATION); // MIN_REQUEST_DURATION = 2 hours
    Date lowerLimit = calendar.getTime();
    calendar.add(Calendar.HOUR_OF_DAY, 2 * MIN_REQUEST_DURATION);
    Date upperLimit = calendar.getTime();

    if (healthWorkerAppointments.contains(date) || (healthWorkerAppointments.lower(date) != null
          && healthWorkerAppointments.lower(date).after(lowerLimit))
          || (healthWorkerAppointments.higher(date) != null
          && healthWorkerAppointments.ceiling(date).before(upperLimit))) {
              throw new CommandException(Messages.MESSAGE_HEALTHWORKER_OCCUPIED_CANNOT_ASSIGN);
    }

    healthWorkerAppointments.add(date);
assignRegionCheck

Figure 3.3.1.5 Shows visualisation of time ranges that are valid.

The above mentioned code snippet checks that there are not clashing requests that fall within the red region, as depicted in Figure 3.3.1.5.

  • Alternative 2: For each request to be assigned, manually iterate through the Request list to ensure that there are not clashing dates. Pros: Easy to implement Cons: Slow - in the worst case, if all the requests get assigned at once, this operation will run in O(n^2) time.

Aspect: Relationship between Request and Healthworker
  • Alternative 1 (current choice): The Request holds a unique identifier of the HealthWorker (i.e the NRIC) assigned to it.

    • Pros: Simplification of implementation.

    • Pros: Cleaner implementation, low level of dependencies on other modules. This also makes it easier to test, and easier to maintain.

    • Cons: Makes it difficult to obtain all details of the healthWorker assigned from a Request object. The healthworker with the unique NRIC will have the be queried from the healthWorkerList

  • Alternative 2: The Request holds a HealthWorker object and the HealthWorker keeps track of a list of `Request`s.

    • Pros: Can quickly access details of a HealthWorker from a Request, and can quickly access the Requests assigned to a HealthWorker from the Request.

    • Cons: Increases complexity of code base, adding additional dependencies to the code. This makes the code base more verbose and more difficult to maintain.

3.6. List Feature

The list command allows the user to be able to display all HealthWorker or Request objects stored in the current application.

3.6.1. Listing HealthWorker objects

  • Format: list healthworker/h/1

  • Displays all HealthWorker objects already stored in the current application, and the following details:

    • name - Name of the HealthWorker

    • nric - NRIC of the HealthWorker

    • organization - Organization of the HealthWorker

    • skills - Skills and Specialisations that the HealthWorker object possesses

3.6.2. Listing Request objects

  • Format: list request/r/2

  • Displays all Request objects already stored in the current application, and the following details:

    • name - Name of the patient that made the request in Request object.

    • address - Address of the patient

    • condition - Condition of the patient that made the request.

3.7. Filter Feature

With the filter command, users can provide specific parameters that act as conditions for HealthHub to filter the UniqueHealthWorkerList and UniqueRequestList by. Using these parameters, HealthHub displays only the HealthWorker or Request whose fields contains the specified parameters.

Command Format: filter <keyword> <prefix>/<parameter> [<prefix>/<parameter>] …​

The <prefix> and <parameters> specified in the command descriptions below follow the same valid prefix and parameter formats as the add, edit and delete commands

3.7.1. Filtering HealthWorker

The command format for filtering HealthWorker is:

filter healthworker/h/1 <prefix>/<parameter> [<prefix>/<parameter>]…​

The fields in HealthWorker that the user can filter by are:

  • name prepended by n/ prefix

  • organisation prepended by the o/ prefix

  • specialisation prepend by the s/ prefix

If multiple specialisation fields are provided, HealthHub will filter the UniqueHealthWorkerList by displaying HealthWorker that contains both specified `specialisations

HealthHub filters the respective list of HealthWorker and Request by checking if the corresponding field contains the specified parameter as a substring.
For example entering the command filter h n/John displays a list of HealthWorker whose name equals to John, or contains John as a substring(E.g Johnny, Johnson).

3.7.2. Filtering HealthWorker

The filter command allows users to display only HealthWorker or Request whose fields matches the <parameter> specified in the filter command.

Format: filter healthworker/h/1 <prefix>/<parameter> [<prefix/<parameter>] …​

  • The filter command checks for HealthWorker whose field specific to the <specific> contains or matches the <parameter> provided.

  • Example: filter h n/John changes the UI to display only HealthWorker whose name is John, or contains John (Johnny, Johnson, …​)

Upon entering the filter command, the filter command word is stripped from the input and the argument fields are passed into the FilterCommandParser class. The regular expressions for checking the fields for name, organisation and skills follows the same format as mentioned in add command. If invalid parameters are specified by the user, or if an invalid keyword was specified that does not correspond to any CommandMode enum, then FilterCommandParser throws a ParseException and displays an error message to the user.

If valid inputs are provided, FilterCommandParser tokenizes the argument string without the keyword using ArgumentTokenizer object, mapping each parameter to it’s respective prefix in an ArgumentMultiMap object. FilterCommandParser then creates a Predicate object using the parameter values in ArgumentMultiMap for each filter condition, and composes them into a single Predicate using the and function in Predicate in conjuction with stream and reduce methods from Java’s Function library. The Predicate object composed is then passed into FilterHealthWorkerCommand, which sets the predicate for the HealthWorker list in the GUI to display only HealthWorker whose fields matches the Predicate set.

Design Considerations

During the implementation of the filter command, we decide between two alternative implementations of managing the Predicates created from multiple criteria input by the user.

The following table states the considerations for performance as well as future implementations and enhancements to HealthHub that were taken into account:

Design Using a List of Predicate Composed Predicate using and method

How it works

Each user input condition will be parsed into Predicate, which are stored in an List. Pros: Easier to manage each single Predicate as they are kept separate Cons: Need to rework existing GUI implementation for filter as it only uses a single Predicate Object

Each user input will be parsed into a Predicate object, and all the Predicate formed will be composed into a single Predicate object. Pros: Ease of existing and future implementations as functionality only depends on Predicate Cons: Unable to specify actions for each Predicate

Implementation

User inputs are first parsed into Predicate equivalents, and then added to an ArrayList. FilterCommandParser then takes the existing UniqueHealthWorkerList, checks for HealthWorker whose fields matches the list of Predicate using allMatch method from the Predicate library, and then returns an ObservableList containing those HealthWorker.

User inputs are parsed into Predicate equivalents, which are then composed into a single Predicate containing the logic of all the composed Predicates using the and method from Predicate library. The composed Predicate is then set as the filter condition into the existing ObservableList of HealthWorker in the UI component.

Ultimately, the decision was made to implement the filter command by composing the Predicate that were formed from the user inputs into a single Predicate using a combination of Java’s Function library methods stream and reduce, as well as the and method used in Java’s Predicate library.
This is because maintain a List of Predicate in HealthHub requires an unnecessary rework of many existing components in HealthHub in order to fit the new implementation of using a List, such as replacing Predicate in methods involved with List<Predicate>.

In addition, by returning a single Predicate object, ease of implementation of methods in filter command, as well as any future or existing enhancements to filter reduces the complexity of code as the function needs only to depend on Predicate and not List, hence observing the Law of Demeter.

Current Implementation

The following sequence diagram shows the flow of events when the filter h s/physio command is entered by the user to display only HealthWorker objects that possess expertise in physiotherapy:

FilterHealthWorkerSD

Figure 3.6.1.1: Sequence diagram illustrating the interactions between the Logic and Model components when filter command is called.

  1. When the user inputs the full command filter h s/physio, the LogicManager object takes in the command string, and passes it into HealthHubParser using the parseCommand method.

  2. HealthHubParser identifies the command word in the input string, which is filter, and identifies the appropriate CommandParser object to parse the user command.

  3. HealthHubParser strips the filter command word off the user input, and passes the remaining argument string in to a new FilterCommandParser object as corresponding to the filter command word.

  4. FilterCommandParser then checks for a valid CommandMode that corresponds to the first keyword in the argument string. In this case, the keyword h corresponds to the CommandMode.HEALTHWORKER value. FilterCommandParser then creates a FilterHealthWorkerCommand with the Predicate constructed from the fields remaining in the argument string. For full detail on the implementation of the FilterCommandParser logic, see the sequence diagram in figure 3.6.1.2 below.

  5. FilterCommandParser returns the FilterHealthWorkerCommand object to HealthHubParser, which calls the execute method of FilterHealthWorkerCommand.

  6. FilterHealthWorkerCommand uses Java 8’s Predicate and Function libraries to condense all the supplied List<Predicate> into a single Predicate using the and, reduce and stream method calls. After which, it calls the updateFilteredHealthWorker method in Model to set the condensed Predicate on the UniqueHealthWorkerBook in Model, displaying only the HealthWorker that evaluates the set Predicate to true on the GUI.

FilterCommandParserLogicSD

Figure 3.6.1.2 Sequence Diagram that details the interaction that happens within the FilterCommandParser class when it’s parse() method is called.

3.8. Undo/Redo feature

3.8.1. Current Implementation

The undo/redo mechanism is facilitated by VersionedBook. There are two extensions of it. VersionedHealthWorkerBook extends HealthWorkerBook and VersionedRequestBook extends RequestBook. Both contain an undo/redo history, stored internally as an healthWorkerBookStateList or requestBookStateList and currentStatePointer. Additionally, it implements the following operations:

  • VersionedBook#commit() — Saves the current request/healthworker book state in its history.

  • VersionedBook#undo() — Restores the previous request/healthworker book state from its history.

  • VersionedBook#redo() — Restores a previously undone request/healthworker book state from its history.

These operations are exposed in the Model interface as Model#commit(), Model#undo() and Model#redo() respectively.

The feature also makes use ModifyCommandHistory to keep track of the commands that modified the books. It contains currentStatePointer and a list of CommandType enums to differenciate the type of command to undo or redo. Similar to CommandMode, the various CommandType enums are:

  • CommandType.HEALTHWORKER_COMMAND

  • CommandType.REQUEST_COMMAND

  • CommandType.HEALTHWORKER_AND_REQUEST_COMMAND

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 VersionedRequestBook and VersionedHealthWorkerBook will be initialized with the initial state, and the currentStatePointer for each VersionedBook pointing to that single book state. Since no modify command has been called, the initial list of commands in ModifyCommandHistory is empty and the initial currentStatePointer of ModifyCommandHistory is initialized to -1.

initrb
inithb

Step 2. The user executes delete r 5 command to delete the 5th request in request book. The delete r command calls Model#commit(REQUEST_COMMAND), causing the modified state of the request book after the delete r 5 command executes to be saved in the requestBookStateList, and the currentStatePointer is shifted to the newly inserted request book state. Since the request book is modified, the enum REQUEST_COMMAND is added to the modifyCommandHistory list within the ModifyCommandHistory class and the currentStatePointer is now pointing at the most recent command. The VersionedHealthWorkerBook is unaffected.

rbafterdelete
mchafterdelete

Step 3. The user executes add h n/David …​ to add a new healthworker. The add h command also calls Model#commit(HEALTHWORKER_COMMAND), causing a modified health worker book state to be saved into the healthWorkerBookStateList and the currentStatePointer is shifted to the new health worker book state. The enum HEALTHWORKER_COMMAND is added into the modifyCommandHistory list of the ModifyCommandHistory class. The VersionedRequestBook is unaffected.

hbafteradd
mchafteradd
If a command fails its execution, it will not call Model#commit(), so the book state will not be saved into the healthWorkerBookStateList or requestBookStateList.

Step 4. The user now decides that adding the health worker was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undo(), which will first obtain the type of command that needs to be undone by caling ModifyCommandHistory#getUndoCommand(). In this case HEALTHWORKER_COMMAND is returned, and hence will call undo on VersionedHealthWorkerBook. It will shift the currentStatePointer once to the left, pointing it to the previous health worker book state, and restores the health worker book to that state. The currentStatePointer of the ModifyCommandHistory also gets shifted once to the left, pointing it to the previous command.

hbafterundo
mchafterundo
If the currentStatePointer of both VersionedHealthWorkerBook and VersionedRequestBook is at index 0, pointing to the initial book state for both books, then there are no previous states to restore. The undo command uses Model#canUndo() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.
As of version 1.4, the only command that has modifies both the health worker book and the request book is EditHealthWorkerCommand. It is the only command that has CommandType.HEALTHWORKER_AND_REQUEST_COMMAND.

The user first enters the undo command in the command line interface. The logic manager processes the command as a string and checks using the AddressBookParser is the string is a valid command. In this case, the parser sees that the command string matches the undo command string and hence returns an undo command to the LogicManager which then calls execute() to execute the command. The execute() method calls Model#undo() in which the model checks the ModifyCommandHistory for the correct VersionedBook that needs to be undone and then calls VersionedBook#undo(). Upon a successful undo, the UndoCommand will return a successful result to the LogicManager which will then the "Undo Success!" message will be displayed on the command line interface.

The following sequence diagram shows how the undo operation works:

UndoRedoSeqDiagram

The redo command does the opposite — it calls Model#redo(), which first obtains the command type by calling ModifyCommandHistory#getRedoCommand() and based on the returned command type it shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the respective book to that state.

If the currentStatePointer of both VersionedBook s are pointing to the latest state, then there are no undone book states to restore. The redo command uses Model#canRedo() 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 r. Commands that do not modify the books, such as list r, will not call Model#commit(), Model#undo() or Model#redo(). Thus, the VersionedBook s and ModyfiCommandHistory do not change.

rbafterlist

Step 6. The user executes delete h 1, which calls Model#commit(HEALTHWORKER_COMMAND). Since the currentStatePointer is not pointing at the end of the healthWorkerBookStateList, all health worker book 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.

hbafterdel
mchafteroverride

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

UndoRedoActDiagram

3.8.2. Design Considerations

Aspect: How undo & redo executes
Implementation Saves the entire book (Current implementation) Individual command knows how to undo/redo by itself.

Pros

Less prone to bugs since the we are switching between different versions of the books.

Will use less memory (e.g. for delete, just save the person being deleted).

Cons

May have performance issues in terms of memory usage especially for large numbers of health workers and requests.

Every command will have their own implementation of undo and some of them are slow hence it causes performance issues. (e.g. for clr, all requests that were deleted have to be added back into the list, which will be quite slow if there is a large number of requests).

Aspect: Data structure to support the undo/redo commands
Implementation Use a list to store the history of book states.(Current implementation) Use HistoryManager for undo/redo

Pros

Undo and redo commands runs faster since it only involves the switching of the state pointer.

Supports multiple books with the HistoryManager alone since we are undoing from the command history.

Cons

Need multiple VersionedBook s. One or each type of book.

HistoryManager keeps a record of all commands, even if they are invalid or commands that does not call Model#commit(). Traversing these irrelevant commands to find the next undoable command will take a long time especially if size of history is large.

Aspect: Data structure to handle multiple VersionedBooks
Implementation Use a command history to keep track of the type of book that was modified It is represented as a list of CommandType.(Current implementation) Use a list of pairs. Each state is represented as a pair which stores the currentStatePointer of each book.

Pros

Easily scalable to include more than two books. As the developer can simply add an extra CommandType and extra cases for switch statements.

Supports commands that change multiple books at once since it keeps track of all states.

Cons

Unable to scale as well if there are many commands that modifies multiple books at once.

Keeping multiple integers takes up more memory as compared to keeping a single enum.

3.9. Set Reminder Feature

The setreminder command gives the user an option to set a reminder with a customised message. The reminder is in the form of an AlertDialog and is scheduled to display on the UI by a TimerTask at time t + current time, where t is time in the format hh:mm:ss.

3.9.1. Current Implementation

The following sequence diagram shows the sequence flow from when the SetReminderCommandParser receives the command and parses it into a SetReminderCommand object.

SetReminderSequenceDiagram

Figure 3.9.1 shows the sequence diagram for the different method invocation calls between the classes

  1. When SetReminderCommandParser receives the parse command, it calls the static method ArgumentTokenizer.tokenize to extract the arguments from the user input string.

  2. The extractArguments constructs and returns an ArgumentMultimap based on the arguments passed in.

  3. SetReminderCommandParser checks if there are arguments specified under prefixes PREFIX_TIME and PREFIX_MESSAGE.

  4. If it cannot find those arguments, it throws a ParseException to the calling program.

  5. If arguments are available, it calls the getValue method of ArgumentMultimap to return the values

  6. After getting the values, the SetReminderCommandParser checks for the validity of the values and if true, calls the constructor of SetReminderCommand

3.10. Statistics Feature

The statistics mechanism is facilitated by Statistics class, which is a public Java class containing a private static TreeMap data structure and a private static LinkedHashMap data structure. The TreeMap contains key-value pairs, with the names of health conditions as keys and the number of occurrences of each health condition as corresponding values. The key-value pairs in the TreeMap are ordered in lexicographical order, whereas the LinkedHashMap is ordered in descending order by value.

The statistics feature is called every time the user decides to use any request-related commands (such as add request, edit r, delete r, undo, redo, clr) since statistics need to keep track of every change in the frequency of all health conditions. Therefore, the Statistics class also carries out the following operations:

  • updateStatistics(): updates statistics with new health condition-related data after add or edit commands are called by the user.

  • deleteStatistics(): updates statistics by specifically removing the health condition-related data that need to be removed after the user calls the delete function.

  • sortStatistics(): sorts statistics by value in descending order of the number of occurrences of each health conditions, and puts the ordered key-value pairs into sortedMap

  • undoRedoStatistics(): clears statistics and sortedMap completely and uses an ObservableList of all requests in the request book to extract all the health conditions and their related number of occurrences. These health conditions and their statistics are then added back to statistics and sortedMap

  • clearStatistics(): renders both statistics and sortedMap empty after the user calls for the clr operation

  • getConditionStatistics(): when the user specifically wants to know the number of occurrences for a particular health condition

Given below is an example of a usage scenario and how the statistics command is executed at each step:

  1. Starting up HealthHub: The user launches HealthHub for the first time. The statistics and sortedMap will be initialized as an empty TreeMap and an empty LinkedHashMap respectively.

  2. Issuing Add Request Command: The user executes add request n/John Doe i/S8974421C p/98765432 a/123 Brick Road, #01-01 dt/01-01-2019 14:50:00 c/Diabetes c/Physiotherapy command to add John Doe to the request book. The aforementioned command also calls updateStatistics(), which takes in a Set<Condition> containing Diabetes Condition and Physiotherapy Condition, and adds an occurrence of one each to statistics. sortStatistics() is then subsequently called to order the health conditions by value in sortedMap.

  3. Issuing Edit Request Command: The user executes edit r 1 c/Diabetes command to make changes to the first request on the request panel in HealthHub. The aforementioned command also calls deleteStatistics(), which removes the statistics of health conditions of the first request in statistics. updateStatistics() is then subsequently called to add the new health condition (which in this case is just Diabetes) to statistics. sortStatistics() is lastly called to order the health conditions by value in sortedMap.

  4. Issuing Undo Request Command: The user executes undo command to reverse his earlier edit r 1 c/Diabetes command. The aforementioned commands calls undoRedoStatistics(). This method first clears statistics and sortedMap, and then takes an ObservableList of all requests from the request book. All health conditions are retrieved from the requests, and are used to update statistics and sortedMap.

The following sequence shows how the statistics operation works when called by LogicManager:

statistics command sequence

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

statistics activity diagram

3.10.1. Design Considerations

Aspect Alternative 1 Alternative 2

Choice of data structure for statistics and sortedMap

statistics: use TreeMap

sortedMap: use LinkedHashMap

Advantages:

1. Very fast retrieval of information with the TreeMap; guaranteed O(lgn) time complexity for containsKey, get, put, remove operations.

2. TreeMap entries are sorted in the natural ordering of its keys.

3. Having 2 separate data structures (1 to contain alphabetically arranged health conditions and 1 to contain sorted conditions by value) aids in the quicker retrieval of data

Disadvantages:

1. Space costly to have 2 separate data structures containing the same information but in different orders

Use any other reasonable data structure like an ArrayList or a LinkedList

Advantages:

1. Very easy to implement and understand

2. Space-efficient to have only 1 list of health conditions and their corresponding number of occurrences

Disadvantages:

1. Will need to create another wrapper class around Conditions and their number of occurrences since lists do not allow for key-value pairing

2. Retrieval of information for common operations like getting statistics for a particular condition may not be very fast

Decision to go with Alternative 1: The team prioritized speed over space since in hospitals, should they want to implement HealthHub, will not have an issue with having memory constraints. But they would definitely want to retrieve data as fast as possible. Moreover, Alternative 1 seems far more scalable for future developments

Aspect Alternative 1 Alternative 2

How statistics executes

The system saves an internal copy of statistics in its storage like a StatisticsBook, modelled after RequestBook or HealthWorkerBook

Advantages:

1. When HealthHub is closed and reopened, the old statistics will still exist.

Disadvantages:

1. Takes up memory space since statistics will now need to be stored as .json files

2. Speed of retrieval will be lower since HealthHub will need to dig into its storage for data

Make the statistics class a static class.

Advantages:

1. Easier to implement; entirely removes the need for making storage files

2. Speed of retrieval of data will be faster since it does not constantly need to keep digging into storage files

Disadvantages:

1. When HealthHub is closed and reopened, the old statistics will disappear since they have not been stored.

Decision to go with Alternative 2: Since RequestBook was already being stored in the storage component of HealthHub, I found it redundant and space-inefficient to have to create another StatisticsBook to store statistical data, since all of my statistical information can be found from the requests stored in RequestBook. I made sure that whenever request-related commands were being executed (add, edit, delete, undo, redo, clr) statistics was also being updated in tandem so that the statistical data was always accurate in real-time.

To address the issue of losing statistical data upon closing down and reopening HealthHub, I have ensured that statistics and sortedMap are initialized with latest data from the RequestBook during the init() method in the MainApp.

3.11. Autocomplete Feature for CommandBox

The UI component, CommandBox, comes with a TextField component in AB4. For Health Hub, the TextField is replaced with an AutoCompleteTextField class that extends from the parent class, TextField.

In the AutoCompleteTextField class, it adds an additional listener to the textProperty() of TextField class, to register for when a text input is being sensed and generates the suggestion box in the form of a ContextMenu to the user.

AutoCompleteTextFieldSMD

Figure 3.11.1 shows the state machine diagram for the AutoCompleteTextField behaviour when it listens for a user input.

The searching of which commands to display to the user as suggestions is done by iterating the suggestions data structure, which is a TreeSet, then checking against the user’s input if any of the suggestions starts with the user’s text.

3.11.1. Aspect: Data structure to store the list of commands

Implementation Use a TreeSet data structure Use a SuffixTree data structure

Pros

Implements the NavigatableSet interface and guarantees that elements are stored in sorted order, also allows for O(log n) time complexity for dynamic searching of elements.

Made for features like autocompletion, allows particularly fast implementation of String operations.

Cons

Elements contained within the TreeSet data have to be a Comparable or a Comparator needs to be supplied. In this case, since we are only concerned with Strings, we do not need an extra step.

Suffix Trees are difficult to comprehend → difficult to implement.

4. 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.1, “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.1. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

5. Documentation

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

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

5.2. Publishing Documentation

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

Testing is an important component of the development pipeline for HealthHub. With each new feature that is implemented, new tests for the fresh feature must also be included to ensure robustness and validation of the feature. In addition, regression testing using existing tests already implemented is also extensively done to ensure that the application functions without unintended side effects. In following sections, we will be elaborating on how you can adopt the testing suite implementation that is in place in HealthHub.

6.1. Running Tests

For the testing phase in HealthHub, we will be using a testing framework called JUnit that is specialised to run tests for software built in Java.

Not sure how JUnit works? Head over to the JUnit website and take a look at their User Guide at this link: https://junit.org/junit5/docs/current/user-guide/

There are three ways to run tests in HealthHub.

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.address.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.address.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.address.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.address.logic.LogicManagerTest

6.3. Generation of test files

We use classes located in the seedu.address.jsonfilegenerator folder to generate JSON test files.
Developers are free to modify the code within the classes to generate healthworker/request book containing the respective data they need for tests by using the HealthWorkerBuilder and RequestBuilder classes
All test file generator classes contains a main function and hence can be ran independently by compiling the file.

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

As HealthHub grows larger as a software development project due to addition of new features or overhauling of existing ones, we have also deployed the use of various build automation tools in order to efficiently integrate the components of HealthHub together during the build process. This allows of greater efficiency and scalability when introducing new implementations into the project.

7.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation on your local machine for efficient project builds.

7.2. Continuous Integration

We use Travis CI and https://www.appveyor .com/[AppVeyor] to perform Continuous Integration on our projects to allow for more seamless and parallel coding performance between team members. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

7.3. Coverage Reporting

Coverage represents the portion of the software codebase that has been tested for using various testing frameworks, such as JUnit. This allows the development to track how extensive the testing of new features are to ensure that their features are correctly implemented with minimal bugs.

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

7.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

7.5. Making a Release

Here are the steps to create a new release.

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

With a huge number of different components in HealthHub, the project also includes use of various third party libraries to efficiently support new and existing features in HealthHub, some of which includes for example the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:

  1. Include those libraries in the repo (this bloats the repo size)

  2. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

Target user profile:

  • Administrative staff in hospitals and healthcare organizations that also manage requests from and services for home-care patients

  • Administrative staff that are comfortable with typing on a CLI, as well as a GUI.

  • Aims to streamline the performance of keeping records of home-care patients, and also assigning health staff to requests using technological means.

Value proposition: manage requests from home-care patients, and handle assignment of healthstaff for these requests much faster and efficient than traditional methods of pen and paper documentation.

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* * *

user

Check the details of new or unassigned requests

See what requests have yet to be assigned a health worker and what type of request it is

* * *

user

Assign a health worker to an existing request

Accept a pending request by assigning a new health worker who has the skillset to fufill the request at the time of the request

* * *

user

Complete a request that is currently ongoing

Label the request as completed once the health worker completes his/her task

* * *

user

Register a new health worker

* * *

user

delete a healthworker

remove healthworkers that are no longer in our hospital database

* * *

user

find a healthworker by name

locate details of persons without having to go through the entire list

* * *

user

Check the professional details of existing health workers

Identify which health worker has the appropriate skillsets and certificates to fufill the corresponding requests

* * *

user

Register a new request

Key in a new request from a patient with the specific request and patient details

* *

user handling many requests at once

Identify the number of requests and their proportion

Immediately see which type of requests have the most demand

* *

user

filter requests by type

Perform assignment of staff of tasks of a specific type

* *

user

Delete a request

Remove a request that is no longer valid or cancelled

Appendix C: Use Cases

(For all use cases below, the System is the HealthHub and the Actor is the user, unless specified otherwise)

Use case: Add health worker

  1. User wants to register a new health worker.

  • 1.a. User enters the relevant health worker details into the system.

  • 2. Application registers the new health worker

    • Use case ends.

  • 3a. The same health worker already exists inside the Application.

    • 3a1. Application shows an error message.

      Use case ends.

Use case: Delete health worker

MSS

  1. User requests to list health workers

  2. Application shows a list of health workers

  3. User requests to delete a specific health worker in the list

  4. Application deletes the person

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Application shows an error message.

      Use case resumes at step 2.

Use case: Add request

  1. User wants to register a new request.

  • 1.a. User enters the relevant request details into the system.

  • 2. Application registers the new request

    Use case ends.

  • 3a. The same request already exists inside the Application.

    • 3a1. Application shows an error message.

      Use case ends.

Use case: Assign requests to worker

  1. User wants to view the list of open requests.

    • 1.a. User does not know command for listing requests:

    • 1.a1. User enters the help command to look up the command for listing all requests.

  2. Application displays list of open requests.

  3. User wants to assign a health worker to a request.

    • 3.a. No free health worker during the specified time slot for assignment:

      • 3.a1 Request is left open.

    • Use case ends.

    • 3.b.No health worker with the necessary skillsets/certifications:

    • 3.b1. Request is left open. Use case ends.

  4. Free health worker that has the necessary certifications and with the closest proximity to the patient address will be assigned to the request.

    Use case ends.

Use case: Delete requests

  1. User wants to delete a request that is expired or invalid.

    • 1.a. Specified request is not in Application

    • Use case ends.

  2. Application deletes the specified request.

    • Use case ends.

Appendix D: 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 persons 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.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

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

Appendix F: Instructions for Manual Testing

In case of circumstances where manual testing needs to be done for HealthHub, this section provides instructions on how to get started on manual testing for the application.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

    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 size may not be optimum.

  2. Saving window preferences

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

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

F.2. Adding a `HealthWorker

  • Enter a valid add healthworker command with all compulsory fields containing valid values. For example, you may try add healthworker n/Dog Terr p/92837710 i/S98817471Z o/NUH s/GENERAL_PRACTICE s/ORTHOPAEDIC
    Message showing the successful addition of the HealthWorker will be displayed in the command terminal, as well as the details of the newly added HealthWorker. The newly added HealthWorker is now reflected immediately in the GUI shown at the end of the list on the right.

  • Enter an invalid add healthworker command. Some examples include:

    • Missing fields: add healthworker p/92837710 i/S98817471Z o/NUH s/GENERAL_PRACTICE s/ORTHOPAEDIC` (Missing name)
      An error message representing invalid command format will be displayed on the command terminal, as well as the valid command format for the add healthworker command.

    • Invalid fields: add healthworker n/Dog Terr p/92837710 i/A111Z o/NUH s/GENERAL_PRACTICE s/ORTHOPAEDIC` (Invalid NRIC format)
      An error message is displayed that tells the user of the incorrect format for a field whose value does not follow the correct format. If there are multiple, the incorrect format of the first field whose value is invalid is shown.

F.3. Editing a HealthWorker

  • Enter a valid edit healthworker command with a valid index, and at least one field to edit with a valid value. For example, you may try edit healthworker 1 p/91234567 n/John Doe
    A status message that denotes the successful modification of the details of an existing HealthWorker is shown on the command terminal. In addition, the modified details of the `HealthWorker is immediately reflected on the list in the GUI.

  • Enter an invalid edit healthworker command. Some examples include:

    • Invalid index: edit healthworker -1 n/John
      An error message displays which informs the user that the index provided is invalid.

    • No field specified: `edit healthworker 1 `
      An error message displays informing the user that at least one field to edit must be provided.

    • Invalid field: edit healthworker 1 p/helloworld
      An error message displays informing the user that the specific field specified containing invalid values does not conform the field format. If multiple invalid fields are entered, the first invalid one will be chosen as the invalid format.

F.4. Deleting a HealthWorker

  • Deleting a HealthWorker while all HealthWorker are listed:

    1. Prerequisites: List all HealthWorker using the list healthworker command. Multiple HealthWorker in the list.

    2. Test case: delete healthworker 1
      Expected: First HealthWorker is deleted from the list. Details of the deleted HealthWorker shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete healthworker 0
      Expected: No HealthWorker is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: delete, delete healthworker`, delete healthworker x (where x is larger than the list size), delete healthworker y (where y is negative)
      Expected: Similar to previous.

  • Deleting a HealthWorker after a valid filter healthworker command is issued:

    1. Prerequisite: Enter the command filter healthworker s/physiotherapy to display only HealthWorker that specialise in physiotherapy. Other valid variants of the filter healthworker command may be used in place of this.

    2. Test case: delete healthworker 1
      Expected: First HealthWorker is deleted from the filtered list. Details of the deleted HealthWorker shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete healthworker 0
      Expected: No HealthWorker is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: delete, delete healthworker`, delete healthworker x (where x is larger than the filtered list size), delete healthworker y (where y is negative)
      Expected: Similar to previous.

F.5. Saving data

  1. Dealing with missing/corrupted data files

    1. If data file is missing, the app will retrieve a list of sample data internally. It will be saved upon exit.

    2. If any of the data is corrupted, the app will start with empty books, adding a health worker or request will overwrite the corrupted file.

F.6. Undoing a command

  1. Undo a command that has been executed

    1. Prerequisites: A command that modifies the data has been executed already, for example delete r 1.

    2. Test case: undo
      Expected: The request has been deleted will appear back inside the list in its original position.

    3. Test case: undo without any prerequisites
      Expected: The application will show an error message: No more commands to undo!

F.7. Redoing a command

  1. Redo an undone command

    1. Prerequisites: A successful undo command have to be executed already.

    2. Test case: redo
      Expected: The application will redo the undone command.

    3. Test case: redo without prerequisites
      Expected: The application will show an error message: No more commands to redo!