Developer Guide
Table of Contents
- Acknowledgements
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
Acknowledgements
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
.puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Architecture

The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main has two classes called Main and MainApp. It is responsible for,
- At app launch: Initializes the components in the correct sequence, and connects them up with each other.
- At shut down: Shuts down the components and invokes cleanup methods where necessary.
Commons represents a collection of classes used by multiple other components.
The rest of the App consists of four components.
-
UI: The UI of the App. -
Logic: The command executor. -
Model: Holds the data of the App in memory. -
Storage: Reads data from, and writes data to, the hard disk.
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.

Each of the four main components (also shown in the diagram above),
- defines its API in an
interfacewith the same name as the Component. - implements its functionality using a concrete
{Component Name}Managerclass (which follows the corresponding APIinterfacementioned in the previous point.)
For example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component’s being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.

The sections below give more details of each component.
UI component
The API of this component is specified in Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PatientListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI.
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
Logiccomponent. - listens for changes to
Modeldata so that the UI can be updated with the modified data. - keeps a reference to the
Logiccomponent, because theUIrelies on theLogicto execute commands. - depends on some classes in the
Modelcomponent, as it displaysPatientobject residing in theModel.
Displaying Patient and Appointment Panels
The main UI parts of the MainWindow to display Patient and Appointment information are PatientListPanel and
AppointmentListPanel, each holding a number of PatientCard’s and AppointmentCard’s.

In the PatientCard part, we can see all the details that we store of the Patient.

In the AppointmentCard part, appointments occurring today have a highlighted ‘TODAY’ indicator to help clinic staff
easily identify appointments occurring today. Non-essential patient details are also omitted.
We can also toggle between the Archive and Upcoming tabs to view Appointments that are upcoming or have been
archived. This is also the reason why MainWindow component holds two AppointmentListPanel’s - One for
upcoming appointments, and the other for archived appointments.
A multi-panel display is used because the user should be able to look for an Appointment’s patient details without swapping back and forth between the Patient list and Appointments list. The tab display is used as archived appointments are likely to be referred to less than upcoming ones. The decision to separate the two into two tabs reduces visual clutter significantly and allows the user to view the more important information immediately.
Logic component
API : Logic.java
Here’s a (partial) class diagram of the Logic component:

How the Logic component works:
- When
Logicis called upon to execute a command, it uses theAddressBookParserclass to parse the user command.-
AddressBookParsercategorises the command according to its format (using RegEx) and hands it off to either one ofBasicAddressBookParser,PatientBookParser, orAppointmentBookParser.
-
- The chosen parser then parses the command and returns a
Commandobject (more precisely, an object of one of its subclasses e.g.,AddPatientCommand) which is executed by theLogicManager. - The command can communicate with the
Modelwhen it is executed (e.g. to add a patient). - The result of the command execution is encapsulated as a
CommandResultobject which is returned back fromLogic.
The Sequence Diagram below illustrates the interactions within the Logic component for the execute("pt delete 1") API call. The illustration has been split into two parts: (1) Parse User Input, (2) Execute command.
Parse User Input

PatientBookParser and DeletePatientCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Execute command

Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:
[comment]:

How the parsing works:
- When called upon to parse a user command, the
AddressBookParserclass creates anXYZCommandParser(XYZis a placeholder for the specific command name e.g.,AddPatientCommandParser) which uses the other classes shown above to parse the user command and create aXYZCommandobject (e.g.,AddPatientCommand) which theAddressBookParserreturns back as aCommandobject. -
All
XYZCommandParserclasses (e.g.,AddPatientCommandParser,DeletePatientCommandParser, …) inherit from one of the three parser interfaces:BasicParser,PatientParser, orAppointmentParserso that they be treated appropriately based on the type of command issued. - The three types of parsers (
BasicParser,PatientParser,AppointmentParser) inherit directly from theParserinterface so that they can be treated similarly where possible e.g, during testing.
Breakdown of Commands
In the original AB3, all commands extend the Command abstract class.
Doc'it supports three types of commands – general, patient-related, and appointment-related. As such, we introduce three abstract classes BasicCommand, PatientCommand, and AppointmentCommand in place of Command.
As the names suggest,
BasicCommanddeals with whole application-related operations,PatientCommanddeals with patient-related CRUD operations, andAppointmentCommanddeal with appointment-related CRUD operations.
The following is a list of commands that extend the three abstract classes:
-
BasicCommandExitCommandClearCommandHelpCommand
-
PatientCommandAddPatientCommandEditPatientCommandDeletePatientCommandListPatientCommandFindPatientCommandAddMedicalEntryCommandParserDeleteMedicalEntryCommandParser
-
AppointmentCommandAddAppointmentCommandArchiveAppointmentCommandEditAppointmentCommandDeleteAppointmentCommandListAppointmentsCommandSortAppointmentsCommandAddPrescriptionCommandDeletePrescriptionCommand
This taxonomy of commands is further reflected on the Parser’s side as well.
Parser
The Parser interface is broken into three sub-interfaces: BasicParser, PatientParser, and AppointmentParser, for the parsers related to application-related commands, patient-related commands, and
appointment-related commands respectively. For all commands under PatientParser and AppointmentParser (ones that require
any form of extra user input), we have a specific parser that tokenises the command:
-
PatientCommandParserAddPatientCommandParserEditPatientCommandParserDeletePatientCommandParserFindPatientCommandParserAddMedicalEntryCommandParserDeleteMedicalEntryCommandParser
-
AppointmentParserAddAppointmentCommandParserEditAppointmentCommandParserDeleteAppointmentCommandParserAddPrescriptionCommandParserDeletePrescriptionCommandParser
Model component
API : Model.java

The Model component,
- stores the address book data and appointment book data (both upcoming and archived) i.e., all
Patient,Appointmentobjects (which are contained inUniquePatientListandUniqueAppointmentListobjects). - stores the currently ‘selected’
Patientobjects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<Patient>that can be ‘observed’ e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. - stores a
UserPrefobject that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPrefobjects. - does not depend on any of the other three components (as the
Modelrepresents data entities of the domain, they should make sense on their own without depending on other components)
Storage component
API : Storage.java

The Storage component,
- can save address book data, appointment book data, and user preference data in json format, and read them back into corresponding objects.
- inherits from
AddressBookStorage,AppointmentBookStorageandArchivedAppointmentBookStorageandUserPrefStorage, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Modelcomponent (because theStoragecomponent’s job is to save/retrieve objects that belong to theModel)
Common classes
Classes used by multiple components are in the seedu.addressbook.commons package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Medical History
Class Implementation details
- The
MedicalHistoryclass composes anEntryList<Entry<MedicalEntry>>class. - The
EntryListclass references theEntry<MedicalEntry>through anArrayList. - The
Entry<MedicalEntry>class is an abstract class that is either aSome<MedicalEntry>or anEmpty<Object>class. - Each
Patientclass composes exactly oneMedicalHistoryclass. -
MedicalEntryis an inner static class ofMedicalHistory - Each
MedicalEntryhas adescriptiondata attribute and adateOfRecorddata attribute. Thedescriptiondata is supplied by the user. ThedateOfRecordis automatically generated based on the date the medical entry was recorded.
Below is a class diagram illustrating the relationship between Patient and MedicalHistory. Note: other details within the Model component are omitted.

Design Considerations
| Alternative Considered | Current implementation | Rationale for current implementation |
|---|---|---|
Using a list of medical entries as an attribute of a Patient class |
Multiple or zero MedicalEntry objects can be stored by a single MedicalHistory. Each Patient class has exactly one MedicalHistory reference. |
Patient and MedicalHistory share a whole-part relationship: when a Patient object is destroyed, the corresponding MedicalHistory object is also destroyed. If the Patient does not have any medical records, this means that the MedicalHistory is empty, which is reflected by a single EMPTY_MEDICAL_HISTORY (instantiation of a MedicalHistory object with an EntryList<Entry<MedicalEntry>> containing exactly one Entry that is an Empty). Hence, there is a 1…1 multiplicity relationship between Patient and MedicalHistory, as one patient can have exactly one medical history. Following OOP Principles, it is more apt to encapsulate medical history records (or medical entries) as a MedicalHistory class than using a collection of medical entries, e.g. List<MedicalEntry>. |
Using null to reflect an empty MedicalHistory for patients that do not have any recorded medical history |
An empty MedicalHistory object is instantiated (EMPTY_MEDICAL_HISTORY), and this object is referenced in every Patient object that does not have any medical history records. |
It is not a mistake for a patient to have zero medical history records. If we were to use null to reflect an empty medical history, there would be many scenarios where by running a blanket command on a patient, NullPointerException would be thrown at runtime if we do not check for null. Hence, as part of our defensive programming efforts, we created a single EMPTY_MEDICAL_HISTORY object that is an instantiation of a MedicalHistory object with an EntryList<Entry<MedicalEntry>> containing exactly one Entry that is an Empty. This allows us to bypass NullPointerException due to the additional null safety built in and exploit polymorphism. |
Using the Optional interface to reflect an Entry
|
Entry class is implemented to reflect an Entry that is either Some or Empty, such that all null or empty inputs into the factory of method generate an Entry.Empty, and every other input generates an Entry.Some
|
When the Optional::of takes in null, a runtime exception is thrown. But null is a valid input. While Optional provides an ofNullable method, we wanted to take an active defensive programming approach. We thus constructed the Entry class, whereby there is only one factory of method, where null inputs are taken care of. |
Using the List interface to reflect a list of entries |
EntryList class is implemented to reflect an EntryList that contains the Entry
|
We want to limit the methods available for an EntryList to only add, delete, size, get, toStream. Each Entry and their position within the EntryList should not be modifiable. After all, at our current stage of production, we do not want the MedicalEntry to have a different order from their time of input. Iterator functions of the List interface allow for modification of contents of an Entry at a specified position in the List, which violates this invariant. As part of defensive programming, we thus chose to create an EntryList class that exposes only the immutable functions of a List. |
Having MedicalEntry as a separate class from MedicalHistory
|
MedicalEntry is an inner static class of MedicalHistory
|
A MedicalEntry can only exist if there is a MedicalHistory. There should be no instantiation of a MedicalEntry without a MedicalHistory instantiated. We also do not want MedicalEntry to implement any interface that MedicalHistory does not. Thus, we opted to have MedicalEntry as an inner class of MedicalHistory. |
Add Medical Entry feature
Overview
The Add Medical Entry feature allows users to add medical entries into the MedicalHistory of a Patient. Each new MedicalEntry must have the data field description, which is the description of the medical record (e.g. diabetes).
Below is a class diagram of the components involved in the Add Medical Entry feature.

Implementation details of feature
The Add Medical Entry feature is implemented via the AddMedicalEntryCommand, which is supported by the AddMedicalEntryCommandParser. The AddMedicalEntryCommandParser implements the PatientParser interface.
-
LogicManagerreceives the user input which is parsed by theAddressBookParser. - The
AddressBookParserinvokes thePatientBookParserbased on the regex pattern of the user input, splitting the user input intocommandWordandarguments. - The
PatientBookParserinvokes theAddMedicalEntryCommandParserbased on thecommandWord, calling the methodparsePatientCommandwithargumentsas the method argument. -
AddMedicalEntryCommandParsertakes in the argument string and invokes anArgumentMultiMap, which tokenizes thearguments. - If the required
preambleandPREFIX_MEDICALis present, theAddMedicalEntryCommandParserwill invoke theAddMedicalEntryCommandafter calling theparseMedicalHistorymethod provided byParserUtil, which returns aMedicalHistorybased on thedescriptiondata field. Thepreambleidentifies theIndexof thePatientto add the medical entry to, while the string afterPREFIX_MEDICALspecifies thedescriptiondata field required for adding a newMedicalEntry. -
LogicManagercalls theexecutemethod of theAddMedicalEntryCommand, which calls theaddMedicalHistoryof thePatientspecified by theIndex. - The
AddMedicalEntryCommandwill then call the methodssetPatient,updateAppointmentBook,updateFilteredPatientListandupdateFilteredAppointmentListprovided by theModel, editing the patient’s medical history information. - The
AddMedicalEntryCommandreturns aCommandResult, which will be returned to theLogicManager.
Below is a sequence diagram illustrating the interactions between the Logic and Model components when the user inputs pt ma 1 m/diabetes command. Note that the full command string has been abbreviated to .... The illustration has been split into two parts: (1) Parse User Input, (2) Execute command.
Parse User Input

PatientBookParser and AddMedicalEntryCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Execute a:AddMedicalEntryCommand

The following activity diagram summarises what happens within AddMedicalEntryCommandParser when the user executes an Add Medical Entry command.

Design considerations
| Alternative Considered | Current implementation | Rationale for current implementation |
|---|---|---|
Implementing a MedicalHistoryBookParser to invoke the AddMedicalEntryCommandParser
|
Having PatientBookParser invoke AddMedicalEntryCommandParser
|
Since MedicalHistory is an attribute of Patient, it makes sense to use the PatientBookParser. It also takes more effort to implement a new Parser that requires an entirely new command word prefix to add a MedicalEntry. |
Delete Medical Entry feature
Overview
The Delete Medical Entry feature allows users to delete medical entries from the MedicalHistory of a Patient.
Below is a class diagram of the components involved in the Delete Medical Entry feature.

Implementation details of feature
The Delete Medical Entry feature is implemented via the DeleteMedicalEntryCommand, which is supported by the DeleteMedicalEntryCommandParser. The DeleteMedicalEntryCommandParser implements the PatientParser interface.
-
LogicManagerreceives the user input which is parsed by theAddressBookParser. - The
AddressBookParserinvokes thePatientBookParserbased on the regex pattern of the user input, splitting the user input intocommandWordandarguments. - The
PatientBookParserinvokes theDeleteMedicalEntryCommandParserbased on thecommandWord, calling the methodparsePatientCommandwithargumentsas the method argument. -
DeleteMedicalEntryCommandParsertakes in the argument string and invokes anArgumentMultiMap, which tokenizes thearguments. - If the required
patientIndexandmedicalIndexis present, theDeleteMedicalEntryCommandParserwill invoke theDeleteMedicalEntryCommandafter calling theparseIndexmethod provided byParserUtil, which returns anIndexto specify thepatientand themedicalEntryto be deleted. -
LogicManagercalls theexecutemethod of theDeleteMedicalEntryCommand, which calls thedeleteMedicalHistoryof thePatientspecified by theIndex. - The
DeleteMedicalEntryCommandwill then call the methodssetPatient,updateAppointmentBook,updateFilteredPatientListandupdateFilteredAppointmentListprovided by theModel, editing the patient’s medical history information. - The
DeleteMedicalEntryCommandreturns aCommandResult, which will be returned to theLogicManager.
Below is a sequence diagram illustrating the interactions between the Logic and Model components when the user inputs pt md 1 i/1 command. Note that the full command string has been abbreviated to .... The illustration has been split into two parts: (1) Parse User Input, (2) Execute command.
Parse User Input

PatientBookParser and DeleteMedicalEntryCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Execute d:DeleteMedicalEntryCommand

The following activity diagram summarises what happens within DeleteMedicalEntryCommandParser when the user executes a DeleteMedicalEntry command.

Design Considerations
| Alternative Considered | Current implementation | Rationale for current implementation |
|---|---|---|
Implementing a MedicalHistoryBookParser to invoke the DeleteMedicalEntryCommandParser
|
Having PatientBookParser invoke DeleteMedicalEntryCommandParser
|
Since MedicalHistory is an attribute of Patient, it makes sense to use the PatientBookParser. It also takes more effort to implement a new Parser that requires an entirely new command word prefix to delete a MedicalEntry. |
Appointment composed of a Valid Patient when added, loaded and stored
How Appointment is implemented

Each Appointment in memory contains a reference to a valid Patient object. To ensure this valid reference is maintained while the app is running and between different running instances, Appointment is stored in JSON with the index of the Patient in the corresponding AddressBook.
Major changes involved to implement this feature:
- Add a new appointment
- Load appointments on app launch
- Save appointments after every command
- Delete a patient that has made an appointment
Add a new Appointment
Overview
AddAppointmentCommand#execute() gets patient at the given index in the address book to create a new appointment referencing that patient.
Detailed Implementation
The user executes apmt add i/1 d/2021-10-19 1800 to add an appointment to the first patient of the address book. The apmt add command calls Model#getFilteredPatientList()to receive a list of patients and gets the Patient object at the inputted index. A new Appointment of that patient is instantiated, and the AddAppointmentCommand calls Model#addAppointment() to add this appointment to the appointment book. A CommandResult is instantiated and returned.

Design considerations
Aspect: How Appointments are instantiated
| Design Choice | Justification | Pros | Cons |
|---|---|---|---|
| Appointment is composed of a Patient (current choice) | Appointment can only be instantiated with a Patient, and without Patients, Appointments cannot exist. Hence, for an appointment to be instantiated, it requires a reference to the related Patient object. | Need to locate corresponding Patient before Appointment can be instantiated. Thus, AddressBook must be loaded to memory before AppointmentBook. |
|
| Patient and Appointment have an association such that Patient has a link to Appointment and Appointment only requires date and time to instantiate. | - | Able to load AppointmentBook without loaded AddressBook. |
Load Appointments on App Launch
Overview
- The app first loads address book, then passes the address book as argument to
Storage#readAppointmentBook(). -
Storage#readAppointmentBook()gets the corresponding patient from the patient index inJSONAdaptedAppointmentsand instantiates appointments.
Detailed Implementation
The user launches the application. MainApp runs MainApp#initModelManager to initialize the model. First, the address book of patients is loaded to memory in StorageManager#readAddressBook(). Referencing the order of patients in this loaded address book, StorageManager#readAppointmentBook() loads the appointment book. Under Storage, the JSON file is loaded to JsonAdaptedAppointment object and its JsonAdaptedAppointment#toModelType() is executed. JsonAdaptedAppointment#toModelType() runs AddressBook#getPatientOfIndex() to get the patient of the appointment at the index loaded from the JSON file. The Appointment object is then instantiated.

Save Appointments after every command
Overview
- The app runs
LogicManager#saveAppointmentBook(). -
LogicManager#saveAppointmentBook()gets the index of the patient referenced by the appointment, that is to be stored asJSONAdaptedAppointmentsin JSON file.
Detailed Implementation
After every command that the user makes, appointments are saved. In LogicManager#executes, after every command is executed, LogicManager calls StorageManager#saveAppointmentBook, passing in the appointment book and address book from Model as arguments. In converting model-type Appointments to JSONAdaptedAppointment, AddressBook#getIndexOfPatient() is called to get the corresponding index of the patient for storage.

The diagram below is a more in-dcannnepth look at how JSONAdaptedAppointment is instantiated.

Design considerations
Aspect: How Appointments are loaded and saved
| Design Choice | Justification | Pros | Cons |
|---|---|---|---|
Save Appointment as the index of corresponding patient in AddressBook and datetime. (current choice) |
The order of AddressBook does not change when saving or loading AppointmentBook. The order of AddressBook is saved each time AppointmentBook is saved. |
Order of the AddressBook is important. If the order of patients is changed in the json file, the appointments will become incorrect. |
|
Implement a hash or Universally Unique Identifier (UUID) to for each Patient and Appointment object. Save Appointment with Patient UUID and save Patient with Appointment UUID. |
- | Changing the order of appointments and patients in saved JSON file will not change affect loading of data. |
Delete Patient that has made an Appointment
Overview
Delete all Appointments containing that Patient object when Patient object is deleted.
Detailed Implementation
The user executes pt delete 1 to delete the first patient in the address book. The patient is deleted and the corresponding appointments and archive appointments with that patient are deleted. The pt delete command calls AddressBook#deleteAppointmentsWithPatient() to delete all appointments to that patient before deleting the patient.

Design considerations
Aspect: When a patient that has appointment is deleted
| Design Choice | Justification | Pros | Cons |
|---|---|---|---|
| Delete all appointments that the patient has (current choice) |
Appointments is a class that is instantiated with a Patient object. When that corresponding Patient object is deleted, the Patients appointment objects should be deleted as well so there will be no reference to deleted Patient objects. |
||
| Delete patient without deleting the patient’s appointments | - | Past appointment data of Patient object can be kept as archives in the system. Patient object is not truly deleted and can be restored if needed. |
Archiving an Appointment
Overview
A user is able to archive an appointment when the appointment is expired, i.e. the patient has either missed his/her appointment or already attended the scheduled appointment. In this case, the appointment should be archived, so that clinic staff are able to view what medicine was prescribed to the patient during previous appointments.
Implementation Details

Users may archive specific appointments manually to remove visual clutter. This is done through the ArchiveAppointmentCommand.
The above sequence diagram displays how the archive command works. The parsing mechanism has been abstracted out from the above diagram as it has been covered in previous diagrams. An example input can be viewed in our User guide. It first retrieves the Appointment to archive from the appointment index parsed through the user input, removes the appointment from upcoming appointments, and adds it to archived appointments.
Archiving is facilitated by the ArchivedAppointmentBook. As opposed to the regular AppointmentBook, it does not allow
users to directly modify the data of appointments as archived data should not be edited. Hence, the following operations
have the private access modifier:
-
ArchivedAppointmentBook#setAppointment(Appointment target, Appointment editedAppointment)- edits thetargetAppointment to be replaced witheditedAppointment. -
ArchivedAppointmentBook#removeAppointment(Appointment key)- removes the target Appointmentkey.
The reason these methods exist in the class is so to support the methods ArchivedAppointmentBook#updatePatient(Patient target, Patient editedPatient)
and ArchivedAppointmentBook#removePatient(Patient target), which are called to accurately reflect any updates/removals of patient
details.

In the Storage component, the addition of ArchivedAppointmentBook also necessitates the implementation of a separate storage system
for archived appointments. This forms under ArchivedAppointmentBookStorage, alongside AddressBookStorage and AppointmentBookStorage.
The .json file for archived appointments is named ‘archivedappointmentbook.json’.
Design Considerations
| Alternative Considered | Current implementation | Rationale for current implementation |
|---|---|---|
Implementing archived appointments as a second UniqueAppointmentList attribute under the AppointmentBook class |
Have a separate class ArchivedAppointmentBook
|
Having a separate class ArchivedAppointmentBook separates the two types of appointments better to facilitate data management. It ties in better with our Storage framework, and archivedappointmentbook.json files can be easily used by the user, instead of having to split one appointmentbook.json files into two segments. |
Auto-Archiving Feature
The archiving implementation involves scanning through all appointments in a day and comparing it to
the current date and time of the user system. If the current date and time is 24 hours ahead of the scheduled
appointment time (24-hour buffer), i.e. by our definition, expired, the appointment is automatically archived. This auto-archiving implementation is handled
by the ModelManager class in two ways.

-
Upon initialisation of the application, the application automatically archives expired appointments (24-hours past their scheduled time). This is called through
ModelManager#archivePastAppointments(). -
A
ScheduledExecutorServiceobject schedules the taskAutoArchiveApmtswhich implements theRunnableinterface. Every day at theModelManager.UPDATE_HOURth hour, theRunnableobject executes theModelManager#archivePastAppointments()method.
In the case where there are many scheduled appointments, this saves the user trouble of archiving past appointments when they are already over.
Recording a Patient’s Prescription feature
During appointments, the doctor can provide prescription of drugs for patients. Recording this information together with appointment information helps clinic staff to keep track of prescriptions given to a patient. Past prescriptions can also be viewed with past appointments.
How Prescription is implemented
Prescription derives from the original Tags class from AB3 and is modified with extra fields and checks.
- Each
Prescriptionclass contains fields recording Medicine, Volume and Duration. - Each
Appointmentclass contains 0 or morePrescriptionobjects.

The implementation of the Prescription class is done with a Prescription class. The Prescription class keep records of the medicine given, volume of medicine, and the duration which the medicine is taken.
Prescription objects are composed under Appointment objects, and will be deleted along with the parent Appointment object.
Within each Appointment class, a collection of these Prescription objects are stored.
The following commands are available from the Appointment class to interact with Prescription objects.
-
addPrescription(Prescription prescription)- adds a new prescription for that appointment. -
deletePrescription(String medicineName)- removes an existing prescription based on the name of the medicine.
Reason for implementation of Prescription
Prescription and Appointment forms a whole-part relationship and hence Prescription is suitable to be stored as a field of Appointment.
Prescription will also be deleted when appointment is deleted due to this whole-part relationship. As an Appointment can have multiple Prescription objects, the multiplicity is many to one.
Prescription commands
The flow of how a command for prescription is processed is shown in the diagram below.

Add Prescription command
The command structure for add prescription command follows the sequence diagram below.

Delete Prescription command sequence
The command structure for delete prescription command follows the sequence diagram below.

General Prescription command sequence
When execute() is called upon the prescriptionCommand object, the prescriptionCommand object checks if the appointment to be targeted
exists by calling the getAppointmentBook() function of the model, which returns a list of available appointments. Once verified that
the appointment to be targeted exists, the respective add/delete prescription command is called in the Model object. The Model object
then checks for the validity of the prescription command by checking for existence of the same prescription in the targeted appointment.
Once the check has been done, the prescription in question is added/removed and a CommandResult is returned.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- Authorised staff from small family clinics (both receptionist and doctors)
- Clinic staff from clinics that lack access to cloud and are undeveloped compared to hospitals
- Clinic staff from young clinics that do not have technological capabilities
- Prefer a type-first experience, and can type fast
Value proposition:
- Provide a centralised platform for clinic staff to manage patient records, reducing man hours from managing paper documents
Today, small family clinics often record patient information using paper application forms. Such recording of patient information results in the clinic having a messy and inefficient patient record information; the family clinic will have to flip through files and documents in order to find the patient. Doc’it aims to provide a centralised platform for authorised staff from small family clinics to view, update and onboard patients. With Doc’it, small family clinics are able to reduce man hours from managing paper documents, and translate these ‘saved’ hours into providing better frontline service to patients, focusing on what they do best.
User stories
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
* * * |
clinic staff | create new patient details | add data of new patients in our clinic |
* * * |
clinic staff | add medical history to patient details | have a background on the patient |
* * * |
clinic staff | view patient details | better prescribe medication and treatments to my out-patients |
* * * |
clinic staff | delete patient details | manage patients’ need for privacy when patients no longer visit the clinic |
* * * |
clinic staff | view the prescriptions currently prescribed to my patients | provide correct prescriptions to them |
* * * |
clinic staff | create new appointments from patients | track upcoming appointments |
* * * |
clinic staff | view appointments from patients | know at-a-glance what are the upcoming appointments and the patients involved |
* * * |
clinic staff | delete appointments from patients | re-organise appointments when either side cancels their appointments |
* * * |
clinic staff | have appointments moved to appointment history | separate what are the upcoming appointments from past appointments |
* * * |
clinic staff | view past appointments | track their medical history |
* * |
clinic staff | edit appointments from patients | ensure appointment records are up-to-date |
* * |
clinic staff | modify patient details | ensure that any changes in their information are up-to-date |
* * |
clinic staff | edit prescriptions | ensure my patients’ current prescriptions are up-to-date |
* * |
clinic staff | view the duration of prescriptions | decide whether they should be given additional prescriptions |
* * |
clinic staff | move prescriptions to prescription history when patients have finished that prescription | |
* * |
clinic staff | view the prescription history of patients | know what drugs my patients have taken before |
* * |
clinic staff | cannot edit past prescriptions | prevent erroneous edits |
* * |
clinic staff | verify that the patient has paid for his appointment | record that the patient has paid the bills |
* * |
clinic staff | input the bill and price of the patient appointment | record the amount that the patient has paid |
* * |
clinic staff | prevent editing past appointments | ensure past records cannot be incorrectly modified |
* * |
clinic staff | filter patients by name | pull up patient records easily when requested |
* * |
clinic staff | filter patients by the date of their appointment | know the list of patients per appointment day |
* * |
clinic staff | filter patients by their prescriptions | gauge the demand of prescription_drug needed by the clinic |
* * |
new clinic staff | go through a tutorial at the beginning | familiarise myself with how the app works and what it can do |
* * |
new clinic staff | type a command to get all available commands and how they work | easily refer to the commands I can make |
* * |
clinic staff | purge all current data | delete any experimental records |
* * |
long term clinic staff | use shortened command aliases | speed up my work |
* * |
clinic staff | easily make changes to the patient records without too many commands | my work can be done faster |
* * |
clinic staff | view information on an appealing GUI | view information that is neatly organised |
* * |
clinic staff | share a single patient database with other clinic staff | ensure all changes are synced |
{More to be added}
Use cases
(For all use cases below, the System is the Doc'it and the Actor is the user, unless specified otherwise)
Use case: UC01 - List all patients
MSS
- User requests to list patients.
-
Doc'itdisplays all patients.Use case ends.
Extensions
-
1a. The list is empty.
Use case ends.
Use case: UC02 - Add a patient
MSS
- User requests to add a patient.
-
Doc'itadds the patient with necessary information.Use case ends.
Extensions
-
1a. Necessary details of patient are absent (name, address, phone number).
- 1a1.
Doc'itshows an error message.
Use case resumes at step 1.
- 1a1.
-
1b. Patient details conflict with existing patient list.
- 1b1.
Doc'itshows an error message.
Use case resumes at step 1.
- 1b1.
Use case: UC03 - Delete a patient
MSS
-
Doc'itshows a list of patients. - User requests to delete a specific patient in the list.
-
Doc'itdeletes the patient.Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
2b. The given index is invalid.
-
2b1.
Doc'itshows an error message.Use case resumes at step 2.
-
Use case: UC04 - List all appointments
MSS
- User requests to list appointments.
-
Doc'itdisplays all appointments.Use case ends.
Extensions
-
1a. The list is empty.
Use case ends.
Use case: UC05 - Add an appointment
MSS
-
Doc'itdisplays all patients. - User adds an appointment, matching the appointment to the specific patient.
-
Doc'itadds the appointment and associates it to the patient.Use case ends.
Extensions
-
2a. The date of the appointment AND/OR the specified patient are invalid.
-
2a1.
Doc'itshows an error message.Use case resumes at step 2.
-
Use case: UC06 - Edit an appointment
MSS
-
Doc'itdisplays all patients and appointments. - User edits an appointment, specifying new appointment details to replace old appointment details.
-
Doc'itedits the appointment details.Use case ends.
Extensions
-
2a. The date of the appointment AND/OR the specified patient are invalid.
-
2a1.
Doc'itshows an error message.Use case resumes at step 2.
-
Use case: UC07 - Delete an appointment
MSS
-
Doc'itdisplays all appointments. - User requests to delete a specific appointment in the list.
-
Doc'itdeletes the appointment and removes the appointment association from the originally associated patient.Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
2a. The given index is invalid.
-
2a1.
Doc'itshows an error message.Use case resumes at step 2.
-
Use case: UC08 - Archive an appointment
MSS
-
Doc'itdisplays all appointments. - User requests to archive all appointments that are past its date.
-
Doc'itarchives all appointments that are past its date.Use case ends.
Extensions
-
2a. There are no appointments in the Appointment panel.
Use case ends.
-
2b. No appointments are past its date.
Use case ends.
Use case: UC09 - Add a prescription
MSS
- User requests to list appointments.
-
Doc'itdisplays all appointments. - User adds a prescription, matching the prescription to the specific appointment by index.
-
Doc’it adds the prescription and associates it to the appointment.
Use case ends.
Extensions
3a. The index of the appointment is invalid.
- 3a1. Doc’it shows an error message. Use case resumes at step 3.
- 3b. The input field of medicine name, volume or duration is invalid.
- 3a1. Doc’it shows an error message. Use case resumes at step 3.
Use case: UC010 - Delete a prescription
MSS
- User requests to list appointments.
-
Doc'itdisplays all appointments. - User deletes a prescription, matching the prescription to the specific appointment by index and medicine name.
- Doc’it deletes the prescription
Extensions
- 3a. The index of the appointment is invalid.
- 3a1. Doc’it shows an error message. Use case resumes at step 3.
- 3b. The input field of medicine name is invalid.
- 3a1. Doc’it shows an error message. Use case resumes at step 3.
Use case: UC11 - Add a Medical Entry to Patient
MSS
-
Doc'itdisplays all patients. - User requests to add a medical entry to a specified patient.
-
Doc'itadds the medical entry and records the date of entry.Use case ends.
Extension
- 2a. The given index is invalid.
- 2a1.
Doc'itshows an error message. Use case resumes at step 1.
- 2a1.
Use case: UC12 - Delete a Medical Entry from a Patient
MSS
-
Doc'itdisplays all patients. - User requests to delete a specified medical entry from a specified patient.
-
Doc'itdeletes the specified medical entry from the patient.Use case ends.
Extensions
-
2a. The given index specifying the medical entry is invalid.
-
2a1.
Doc'itshows an error message.Use case resumes at step 1.
-
-
2b. The given index specifying the patient is invalid.
-
2b1.
Doc'itshows an error message.Use case resumes at step 1.
-
-
2c. The patient specified has an empty medical history.
-
2c1.
Doc'itshows an error message.Use case resumes at step 1.
-
Use case: UC13 - Find patient
MSS
-
Doc'itdisplays all patients. - User requests to find patients whose names or medical histories match the given set of keywords.
-
Doc'itlists a filtered list of patients that satisfy the keywords given by the user.Use case ends.
Use case: UC14 - Exit program
MSS
- User requests to exit the program.
-
Doc'itsaves and writes all files. -
Doc'itexits and closes.Use case ends.
Extensions
-
2a.
Doc'itis unable to save file.-
2a1.
Doc'itshows an error message.Use case resumes at step 1.
-
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11or above installed. - Should be able to hold up to 1000 patients without noticeable sluggishness in performance for typical usage.
- A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
- Should not require an external installer or launcher.
- Data should be stored locally in an easily-editable text file.
- Should not depend on any cloud-based remote server.
- Data should not be stored in an external Database Management System (DBMS) or data warehouse.
- Clinic staff inexperienced in using computers should be able to easily use Doc’it.
- The GUI should work well and not result in resolution-related inconveniences to the user for screen resolutions 1920x1080 and higher, and screen scale at 100% and 125%.
- The GUI should be usable, defined as the ability to carry out all functions, even if resolution is not optimal, for resolutions 1280x720 and higher and screen scale at 150%.
Glossary
| Term | Definition |
|---|---|
| Mainstream OS | Windows, Linux, Unix, OS-X |
| Appointment | A scheduled consult between a patient and the clinic’s doctor at an exact date and time. The doctor may or may not prescribe medication. |
| Archive | Storage for data that is non-urgent, e.g. appointment records that are past their date. |
| Patient Record | A record of a patient’s name, phone number, address, email and medical history. |
| Prescription | The issued medication/treatment for a patient along with a duration and volume. |
| Expired Appointment | An appointment that is 24-hours past its scheduled time. |
| Medical History | A patient’s set of recorded medical complications from the past. |
| Medical Entry | A specific past health complication of a patient. There can be multiple medical entries within a patient’s medical history. |
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
Launch and shutdown
-
Initial launch
- Download the jar file and copy into an empty folder
- Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
Deleting a patient
-
Deleting a patient while all patients are being shown
- Prerequisites: List all patients using the
pt listcommand. Multiple patients in the list. - Test case:
pt delete 1
Expected: First patient is deleted from the list. Details of the deleted patient shown in the status message. - Test case:
pt delete 0
Expected: No patient is deleted. Error details shown in the status message. - Other incorrect patient delete commands to try:
pt delete,delete 1,pt delete x,...(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all patients using the
-
Deleting a patient that has an appointment
- Prerequisites: Add an appointment to that patient at index 1 e.g.
apmt add i/1 d/2022-12-31 1200 - Test case:
delete 1
Expected: First patient is deleted from Patient Panel. Patient’s appointments in Appointment panel is deleted as well.
- Prerequisites: Add an appointment to that patient at index 1 e.g.
Adding a patient
-
Adding a patient
- Test case:
pt add n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 m/diabetes.
Expected: Patient is added to the end of the list. Details of the added patient shown in the status message. - Test case:
pt add p/98765432 e/johnd@example.com.
Expected: No patient is added. Error details shown in the status message. - Other incorrect patient add commands to try:
pt add,add 1,pt add m/diabetes,...Expected: Similar to previous.
- Test case:
Editing a patient
- Editing a patient
- Prerequisites: List all patients using the
pt listcommand. Multiple patients in the list. - Test case:
pt edit 1 n/Joshen Lim
Expected: Name of first patient is edited. Details of the edited patient shown in the status message. - Test case:
pt edit 1 n/
Expected: No edit is executed. Error details shown in the status message. - Other incorrect patient add commands to try:
pt edit,edit 1 n/Joshen Lim,pt edit x,...(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all patients using the
Adding a Medical Entry to a Patient’s Medical History
- Adding a medical Entry
- Prerequisites: List all patients using the
pt listcommand. Multiple patients in the list. - Test case:
pt ma 1 m/diabetes
Expected: Medical History of first patient has the new medical entry added to the back of the list. The medical entry should have the corresponding date recorded. Details of the patient shown in the status message. - Test case:
pt ma 1 i/
Expected: No medical entry is added. Error details shown in the status message. - Other incorrect patient add commands to try:
pt ma,ma 1 m/high blood pressure,pt ma x m/lovesick,...(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all patients using the
Deleting a Medical Entry from a Patient’s Medical History
- Deleting a medical entry from a patient’s non-empty medical history
- Prerequisites: List all patients using the
pt listcommand. Multiple patients in the list. - Test case:
pt md 1 i/1
Expected: Medical Entry of the specified index within the Medical History of first patient is deleted. If there are other entries within the Medical History, the list of entries will re-index from 1. If there are no other entries within the Medical History, “No medical history recorded.” is shown on the Patient Card. Details of the patient shown in the status message. - Test case:
pt md 1 i/
Expected: No medical entry is deleted. Error details shown in the status message. - Other incorrect patient add commands to try:
pt md,md 1 i/1,pt md x i/1,pt md 1 i/x,...(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all patients using the
- Deleting a medical entry from a patient’s with an empty medical history
- Prerequisites: List all patients using the
pt listcommand. Multiple patients in the list. - Test case:
pt md 1 i/1
Expected: Nothing is changed. Status message reflects that there is “No medical history record to delete from the patient”. - Test case:
pt md 1 i/
Expected: Nothing is changed. Error details shown in the status message. - Other incorrect patient add commands to try:
pt md,md 1 i/1,pt md x i/1,pt md 1 i/x,...(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all patients using the
Saving data
- Dealing with missing/corrupted data files
- If JSON files are missing,
Doc'itwill start with a sample AddressBook and AppointmentBook with sample Patients and sample Appointments respectively.- If JSON files are corrupted,
Doc;itwill start with a blank address book and blank appointment book.
- If JSON files are corrupted,
- Save appointment
- Prerequisite: Create an appointment e.g.
apmt add i/1 d/2022-12-31 1200 - Test case: Close and reopen the application
Expected: Appointments reference the same patients as previous session before it was closed.
- Prerequisite: Create an appointment e.g.
- If JSON files are missing,
Adding an appointment
Prerequisites: Use sample addressbook or the same list of patients from index 1 to 2.
- Test case:
apmt add i/1 d/2999-12-31 2359
Expected: New appointment added in Upcoming tab of Appointments panel for Patient 1 on 31 Dec 2999 2359. - Test case: Conduct test case 1, then enter
apmt add i/2 d/2999-12-31 2359
Expected: New appointment added in Upcoming tab of Appointments panel for Patient 2 on 31 Dec 2999 2359 even if test case 1 appointment exists. - Test case:
apmt add i/1 d/2000-1-1 0000
Expected: New appointment added in Archived tab of Appointments panel for Patient 1 on 1 Jan 2000 0000. - Test case: Conduct test case 3, then enter
apmt add i/2 d/2000-1-1 0000
Expected: New appointment added in Archived tab of Appointments panel for Patient 2 on 1 Jan 2000 0000 even if test case 3 appointment exists. - Test case:
apmt add i/1 d/2039-2-29 1200
Expected: No new appointment is created because 2039 is not a leap year and Feb 2039 only has 28 days. Error message shown.
Editing an appointment test cases are similar to adding an appointment except for an additional appointment index to be specified before Patient Index and Datetime.
Deleting an appointment
-
Deleting an appointment while all appointments are being shown
- Prerequisites: List all appointments in the Upcoming tab using the
apmt listcommand. At least one appointment in list. - Test case:
apmt delete 1
Expected: First appointment is deleted from the list. Details of the deleted appointment shown in the status message. - Test case:
apmt delete 0
Expected: No appointment is deleted. Error details shown in the status message. - Other incorrect delete commands to try:
apmt delete,apmt delete x,...(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all appointments in the Upcoming tab using the
Adding a prescription
Prerequisites: All test cases below must be independent and fulfills these assumptions:
All appointments listed using the apmt list command. 5 appointments shown in the list. All appointments have no prescriptions.
- Test case:
apmt pa 1 n/Penicillin v/400 ml d/2 times a week
Expected: First appointment now contains a prescription labelpenicillin | 400ml | 2 times a week. - Test case: enter
apmt pa 1 n/Penicillin v/400 ml d/2 times a weektwice
Expected: No new prescription is created. Error messageOperation would result in duplicate prescriptionsis shown. - Test case:
apmt pa 1 n/PenicillinPenicillinPenicillin v/400 ml d/2 times a week
Expected: No new prescription is created. Error messageMedicine name can only be 20 characters long.is shown. - Test case:
apmt pa 1 n/Penicillin v/400000000000000000000 ml d/2 times a week
Expected: No new prescription is created. Error messageVolume can only be 20 characters long.is shown. - Test case:
apmt pa 1 n/Penicillin v/400 ml d/99999999999999999999999999999999 times a week
Expected: No new prescription is created. Error messageDuration can only be 40 characters long.is shown. - Test case:
apmt pa 0 n/Penicillin v/400 ml d/2 times a week
Expected: No new prescription is created. Error messageIndex is not a non-zero unsigned integer.is shown. - Test case:
apmt pa 6 n/Penicillin v/400 ml d/2 times a week
Expected: No new prescription is created. Error messageThe appointment index provided is invalid.is shown.
Deleting a prescription
Prerequisites: All test cases below must be independent and fulfills these assumptions:
All appointments listed using the apmt list command. Multiple appointments shown in the list.
First appointment has a single prescription penicillin | 400ml | 2 times a week. All other appointments have no prescriptions.
- Test case:
apmt pd 1 n/Penicillin
Expected: First appointment now do not contain any prescriptions. - Test case: enter
apmt pa 0 n/Penicillintwice
Expected: No new prescription is created. Error messageIndex is not a non-zero unsigned integer.is shown. - Test case:
apmt pa 1 n/Panadol
Expected: No new prescription is created. Error messageMedicine name not found in prescription list.is shown.