Patient Manager Developer Guide

Table of Contents

  1. Introduction
  2. Setting up the project in your computer
  3. Design & Implementation
    1. Architecture
      1. Interaction Among Architecture Components
    2. UI Component
    3. Logic Component
    4. Model Component
    5. Storage Component
    6. Exception Component
    7. Common Classes
  4. Implementation
    1. Parsing User Input
    2. Initializing Command Class
    3. Adding Patients
    4. Loading Patients
    5. Adding Medical Records to Patients
    6. Retrieving a Patient’s Medical Records
    7. Exception Handling
    8. Organization of the Model Component
  5. Appendix A: Product scope
    1. Target user profile
    2. Value proposition
  6. Appendix B: User Stories
  7. Appendix C: Non-Functional Requirements
  8. Appendix D: Glossary
  9. Appendix E: Instructions for Manual Testing
    1. Launch, Help and Shutdown
    2. Adding and Loading Patients
    3. Adding, Viewing and Deleting a Patient’s Visit Records
    4. Saving Data
  10. Appendix F: Command Summary

Introduction

Patient Manager is a Command Line Interface (CLI) application for general practitioners (GPs) who work in polyclinics to manage their patient list. This includes managing patient list, recording/retrieval of past record of visit, and some other features listed below.

With the Patient Manager, GPs will be able to reduce paperwork and have a more efficient way to organize the records of their patients.

Setting up the project in your computer

First, fork this repo, and clone the fork into your computer.

If you plan to use IntelliJ IDEA (highly recommended):

  1. Configure the JDK: Follow the guide IntelliJ IDEA: Configuring the JDK @SE-EDU/guides and ensure IntelliJ is configured to use JDK 11.
  2. Import the project as a Gradle project: Follow the guide IntelliJ IDEA: Importing a Gradle project @SE-EDU/guides to import the project into IDEA.

    :information_source: Note: Importing a Gradle project is slightly different from importing a normal Java project.

  3. Verify the setup: Run seedu.duke.PatientManager and try a few commands.
  4. Run the tests to ensure they all pass.

Application Design

Architecture

The Architecture Diagram shown above gives a high-level explanation of Patient Manager. Given below is a brief overview of each component.

Main contains the class PatientManager.

This class is responsible for:

Commons contains constants and functions that are shared across multiple classes.

UI is responsible for displaying the all messages generated by Patient Manager to the screen.

Logic parses and executes commands.

Model contains the data of Patient Manager in memory.

Storage reads data from, and writes data to, the hard disk.

Interaction Among Architecture Components

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

The sections below give more details for each component.

UI Component

API: Ui.java

The UI component implements methods for:

Logic Component

API: Parser.java and Command.java

  1. Logic uses the Parser class to tokenize and parse the user command.
  2. This creates a Command object which is then executed by the PatientManager class via the execute() method.
  3. The command execution can affect the Model (e.g. adding a patient).
  4. Within the execute() method, the Command object can instruct the Ui to perform certain actions, such as displaying the command output to the screen

Detailed explanations of the implementation of each Command subclass can be found in Section 4: Implementation.

Model Component

API: Data.java

Data

A Patient contains:

A Record contains:

Storage Component

API: Storage.java

The Storage component is responsible for:

  1. Storage is first initialized with the SortedMap<String, Patient> from the Data class during object creation.
  2. After initialization, the save(SortedMap<String, Patient> patientData) method can be called to save the records to a file. The path of the output file is specified by the variable, FILE_PATH, in the Constants class.
  3. The reverse process is the load() method. This method reads the contents from the file located at FILE_PATH, and returns a SortedMap<String, Patient>, which can be loaded into the Data constructor during program initialization.

Exception Component

API: BaseException.java and its subclasses

BaseException.java:

Each subclass of BaseException:

More details on the specific implementation of each subclass can be found at Section 4.3: Exception Handling.

Common Classes

There are two common classes, Constants and Common.

seedu.duke.Constants class stores constants used by multiple classes. This includes help and exception messages, magic numbers, delimiter for save file parsing, etc.

seedu.duke.Common class have a number of static methods shared by multiple command classes. For example, it includes isValidID() for checking the validity of an NRIC/FIN number.

Implementation

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

Parsing User Input

The parser is one of the core components in charge of parsing all user input commands into program-understandable commands and arguments. For the ease of expansion of this program’s functionality as well as for its testability, reflection is used to invoke commands.

First is the initialization of this parser. A Ui instance and a Data instance is passed and stored. This is important as these two will be passed to logic components (command classes) later.

Then, we can parse a user-input string by passing it to parse(). We use an example of this:

record 01/05/2021 /s coughing, fever /p panadol Paracetamol 500mg*20

This is broken into a few steps:

  1. Check whether the command contains any forbidden characters. Currently, they are these characters:
    ~ ` % # @ !
    
  2. Initialize an empty hashmap, called arguments.
  3. Tokenize using any number of consecutive white spaces.
  4. Taken out the first token as command, i.e. record. Push it into the hash map using key command. Create a new empty list with default key payload.
  5. Check if next token starts with /. No, so we add it to the list: list = ['payload'].
  6. Check if next token starts with /. Yes, so we concatenate all tokens in the list to one string use delimiter ` ` (empty whitespace). Put it into the hash map using the key payload. Reset the list, and set new key to s (the part after this /).
  7. Repeat same process, we have list = ['coughing,']
  8. Repeat same process, we have list = ['coughing,', 'fever']
  9. Same process, coughing, fever is pushed into arguments hash map with key s. Reset the list, and new key set to p.

At the end, we have an argument hashmap like this:

Key Value
command record
payload 01/05/2021
s coughing, fever
p Panadol Paracetamol 500 mg*20

Initializing Command Class

Continuing from the command parsing above. Next step is the initialization of a command class. Since we have command record, the program finds a class called RecordCommand under the module seedu.duke.command (first character being capitalized, then concatenated with ‘Command’).

Since this is a valid command, this class exists. If the class does not exist, it means the command is not yet implemented by this program.

After finding the command class, it is initialized with (ui, data, arguments). ui and data are the two references passed in when initializing the parser, and the arguments is the hash map we just obtained by parsing the input. The result of the initialization (i.e. the instance of the command class) is returned.

Since all command classes implements the abstract method execute(), the main loop just need to execute this method to call out the actual logic of this command.

:information_source: Note: Since we are tokenizing the user input with any number of white spaces and concatenate all tokens belong to the same key back using single whitespace, the number of white spaces input has no effect on the actual arguments being parsed. For example, the following two input has exactly the same result after being parsed.

record 01/05/2021 /s coughing, fever
record 01/05/2021 /s coughing,                 fever

Adding Patients

Adding of patients is implemented via AddCommand, which is created by the Parser.parse() method. As per Section 4.1: Parsing User Input, the arguments to the command are stored in a HashMap<String, String> and passed to the AddCommand during initialization.

Below is a sequence diagram when the user executes the command add S1234567D. For clarity, arguments are excluded from some function invocations.

Internally, the addPatient method will check if the requested patient exists, and throw an error if the patient already exists. Otherwise,Data will create a new Patient object, and add that to the HashMap<String, Patient> of registered patients.

Loading Patients

Loading of patients is implemented via LoadCommand, which is created by the Parser.parse() method. As per Section 4.1: Parsing User Input, the arguments to the command are stored in a HashMap<String, String> and passed to the LoadCommand during initialization.

Below is a sequence diagram when the user executes the command add S1234567D. For clarity, arguments are excluded from some function invocations.

Internally, the loadPatient method will check if the requested patient exists, and throw an error if the patient does not exist. Otherwise,Data will update the current loaded patient to the requested patient.

Adding Medical Records to Patients

Adding medical records to a patient is implemented via RecordCommand, which is created by the Parser.parse() method. As per Section 4.1: Parsing User Input, the arguments to the command are stored in a HashMap<String, String> and passed to the RecordCommand during initialization.

Below is a sequence diagram when the user executes the command record /s coughing. For clarity, arguments are excluded from some function invocations.

Internally, the addRecord method will first verify that there is a loaded patient, and it will also verify that at least one of the three fields (symptoms, diagnosis and prescription) is not null and not blank, before adding the medical record(s) to the patient.

Retrieving a Patient’s Medical Records

Loading of patients is implemented via the RetrieveCommand, which is created by the Parser.parse() method. As per Section 4.1: Parsing User Input, the arguments to the command are stored in a HashMap<String, String> and passed to the RetrieveCommand during initialization.

Below is a sequence diagram when the user executes the command retrieve. For clarity, arguments are excluded from some function invocations.

Internally, the getRecords method will first verify that there is a loaded patient before trying to load their records.

Exception Handling

All unexpected behaviour encountered by Patient Manager is signalled and handled with exceptions. Since the generic Exception is too broad, we have created a few custom exception classes to relay exception information.

BaseException.java:

InvalidInputException.java

StorageException.java

UnknownException.java

During invocation of an exception, there are two ways to invoke:

throw new InvalidInputException(InvalidInputException.Type.EMPTY_STRING);
// e is a Throwable, e.g. a captured exception in a try-catch block
// for this UNKNOWN_COMMAND, the e should be of type ClassNotFoundException
throw new InvalidInputException(InvalidInputException.Type.UNKNOWN_COMMAND,e);

If a second argument is passed, it is called the cause of the exception. For example, the user’s wrong input triggers ClassNotFoundException, and then this exception is captured in Parser which then causes InvalidInputException.

This cause is stored for debugging purposes, and it will not be printed out to the user. The implementation of this facilitates breakpoint debugging during development.

Organization of the Model Component

In the Model component, the Data class acts as a Facade for the Patient and Record classes. As such, if a command requires to make some modifications to the Patient or Record as part of its execution, it will have to do so via method(s) implemented in the Data class. However, since the Patient and Record are implemented seperately from the Data class, all their methods are still exposed as public methods. In theory, one could still bypass the Data class and directly interface Patient and Record classes.

One alternative solution to prevent this is to implement Patient and Record as nested classes within the Data class, and then making all their methods private. This would allow Data to access their methods while preventing other classes from doing the same. However, since Java does not have support for seperately defining and defining classes, all the definitions would have to be included in the Data class, making the codebase much larger and harder to read.

As such, we have opted to seperate these three classes individual files, and rely on the developers’ to exercise their due discretion to not directly interface with the Patient and Record classes, but implement and utilize the necessary methods in the Data class.

Appendix A: Product scope

Target user profile

The target users for this application are general practitioners (GP) who work in clinics. They are keen to reduce the paperwork that is required of them during consultation sessions, so that they may focus more on the consultation itself. Also, they would like to have a more efficient way to organize the records of their patients.

Value proposition

Through Patient Manager, general practitioners are able to manage patients faster than a typical mouse/GUI driven app. The typical paperwork, such as recording of symptoms, diagnoses and prescriptions, are greatly reduced through digital input.

Appendix B: User Stories

Version As a … I want to … So that I can …
v1.0 GP in a polyclinic add a new patient record a patient
v1.0 GP in a polyclinic view the list of patients track the list of patients
v1.0 GP in a polyclinic select a specific patient’s records access the patient’s records
v1.0 GP in a polyclinic add new record for a patient refer to them during future consultations
v1.0 GP in a polyclinic retrieve the patient’s past records refer to them during the current consultation
v1.0 new User view list of available commands refer to them if I have any problems
v2.0 GP in a polyclinic delete a patient remove patients are no longer required to be tracked
v2.0 GP in a polyclinic delete a patient’s records remove records that I no longer need
v2.0 GP in a polyclinic know if I entered an invalid Patient ID make sure no mistake is made recording the patient’s ID
v2.0 GP in a polyclinic load and save existing data work on the data on another device

Appendix C: Non-Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.
  2. Should be able to hold up to 1000 patients 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. The data should be stored locally and should be in a human editable text file.
  5. The application should work without requiring an installer.
  6. The application should be at most 100 MB in size.
  7. The application should not rely on any remote server, or database management system.

Appendix D: Glossary

Appendix E: Instructions for Manual Testing

Launch, Help and Shutdown

  1. Initial launch
    1. Download PatientManager.jar and copy into an empty folder.
    2. Open a terminal/Command Prompt (cmd)/PowerShell. A Windows 10 OS’ screenshot is here:
    3. Execute java -jar PatientManager.jar to start the Patient Manager.
      Expected: Shows the welcome message as shown below
  2. View help
    1. Test case: help
      Expected: Application prints out a help message containing a list of valid commands and how to use them.
    2. Test case: help add
      Expected: Application prints out a help message explaining only the add command.
  3. Exiting
    1. Test case: exit
      Expected: Application prints goodbye message and exits. All data will be saved to pm.save in the same folder as PatientManager.jar.

Adding and Loading Patients

  1. Adding a new patient
    1. Test case: add S1234567D
      Expected: Application adds patient to the list and shows:
      ----------------------------------------------------------------------
      Patient S1234567D has been added!
      ----------------------------------------------------------------------
      
  2. Loading a patient’s records
    1. Prerequisite: Patients have already been added (in this case, S1234567D has already been added).
    2. Test case: load S1234567D
      Expected: Application loads S1234567D’s records and shows:
      ----------------------------------------------------------------------
      Patient S1234567D's data has been found and loaded.
      ----------------------------------------------------------------------
      
  3. Deleting a patient
    1. Prerequisite: Patients have already been added (in this case, S1234567D has already been added).
    2. Test case: delete /p S1234567D
      Expected: Application deletes patient S1234567D and shows:
      ----------------------------------------------------------------------
      Patient S9841974H has been deleted!
      ----------------------------------------------------------------------
      

Adding, Viewing and Deleting a Patient’s Visit Records

  1. Adding visit records
    1. Prerequisite: Patient’s records have already been loaded.
    2. Test case: record 30/03/2021 /s coughing, runny nose, fever /d flu /p panadol, cetirizine
      Expected: Application adds details to patient’s visit record and shows:
      ----------------------------------------------------------------------
      Added new record to patient S1234567D:
             
      Symptom: coughing, runny nose, fever
      Diagnosis: flu
      Prescription: panadol, cetirizine
      
      ----------------------------------------------------------------------
      
  2. Viewing visit records
    1. Prerequisite: Patient’s records have already been loaded.
    2. Test case: retrieve
      Expected: Application shows details of all the patient’s past visits:
      ----------------------------------------------------------------------
      Here are S1234567D's records:
      30/03/2021:
      Symptoms:
          coughing, runny nose, fever
      Diagnoses:
          flu
      Prescriptions:
          panadol, cetirizine
      
      ----------------------------------------------------------------------
      
  3. Deleting visit records
    1. Prerequisite: Patient’s records have already been loaded.
    2. Test case: delete /r 30/03/2021
      Expected: Application deletes record dates 30/03/2021 and shows:
      ----------------------------------------------------------------------
      Record for 30/03/2021 has been deleted!
      ----------------------------------------------------------------------
      

Saving Data

  1. Missing data files
    1. Delete the file pm.save, which should be in the same folder as PatientManager.jar.
    2. Launch the app with java -jar PatientManager.jar.
    3. Expected: Application should start up without any data.

Appendix F: Command Summary

Listed below are all currently implemented commands in alphabetical order. For a more detailed explanation and input/output samples, please refer to the User Guide.

Command Usage
add add IC_NUMBER
current current
delete(patient) delete [/p IC_NUMBER]
delete(record) delete [/r DATE]
exit exit
help help [OPTIONAL_COMMAND]...
list list
load load IC_NUMBER
record record [DATE] [/s SYMPTOM] [/d DIAGNOSIS] [/p PRESCRIPTION]
retrieve retrieve [DATE]