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
interface
with the same name as the Component. - implements its functionality using a concrete
{Component Name}Manager
class (which follows the corresponding APIinterface
mentioned 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
Logic
component. - listens for changes to
Model
data so that the UI can be updated with the modified data. - keeps a reference to the
Logic
component, because theUI
relies on theLogic
to execute commands. - depends on some classes in the
Model
component, as it displaysPatient
object 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
Logic
is called upon to execute a command, it uses theAddressBookParser
class to parse the user command.-
AddressBookParser
categorises 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
Command
object (more precisely, an object of one of its subclasses e.g.,AddPatientCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to add a patient). - The result of the command execution is encapsulated as a
CommandResult
object 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
AddressBookParser
class creates anXYZCommandParser
(XYZ
is a placeholder for the specific command name e.g.,AddPatientCommandParser
) which uses the other classes shown above to parse the user command and create aXYZCommand
object (e.g.,AddPatientCommand
) which theAddressBookParser
returns back as aCommand
object. -
All
XYZCommandParser
classes (e.g.,AddPatientCommandParser
,DeletePatientCommandParser
, …) inherit from one of the three parser interfaces:BasicParser
,PatientParser
, orAppointmentParser
so that they be treated appropriately based on the type of command issued. - The three types of parsers (
BasicParser
,PatientParser
,AppointmentParser
) inherit directly from theParser
interface 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,
BasicCommand
deals with whole application-related operations,PatientCommand
deals with patient-related CRUD operations, andAppointmentCommand
deal with appointment-related CRUD operations.
The following is a list of commands that extend the three abstract classes:
-
BasicCommand
ExitCommand
ClearCommand
HelpCommand
-
PatientCommand
AddPatientCommand
EditPatientCommand
DeletePatientCommand
ListPatientCommand
FindPatientCommand
AddMedicalEntryCommandParser
DeleteMedicalEntryCommandParser
-
AppointmentCommand
AddAppointmentCommand
ArchiveAppointmentCommand
EditAppointmentCommand
DeleteAppointmentCommand
ListAppointmentsCommand
SortAppointmentsCommand
AddPrescriptionCommand
DeletePrescriptionCommand
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:
-
PatientCommandParser
AddPatientCommandParser
EditPatientCommandParser
DeletePatientCommandParser
FindPatientCommandParser
AddMedicalEntryCommandParser
DeleteMedicalEntryCommandParser
-
AppointmentParser
AddAppointmentCommandParser
EditAppointmentCommandParser
DeleteAppointmentCommandParser
AddPrescriptionCommandParser
DeletePrescriptionCommandParser
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
,Appointment
objects (which are contained inUniquePatientList
andUniqueAppointmentList
objects). - stores the currently ‘selected’
Patient
objects (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
UserPref
object that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPref
objects. - does not depend on any of the other three components (as the
Model
represents 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
,AppointmentBookStorage
andArchivedAppointmentBookStorage
andUserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Model
component (because theStorage
component’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
MedicalHistory
class composes anEntryList<Entry<MedicalEntry>>
class. - The
EntryList
class references theEntry<MedicalEntry>
through anArrayList
. - The
Entry<MedicalEntry>
class is an abstract class that is either aSome<MedicalEntry>
or anEmpty<Object>
class. - Each
Patient
class composes exactly oneMedicalHistory
class. -
MedicalEntry
is an inner static class ofMedicalHistory
- Each
MedicalEntry
has adescription
data attribute and adateOfRecord
data attribute. Thedescription
data is supplied by the user. ThedateOfRecord
is 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.
-
LogicManager
receives the user input which is parsed by theAddressBookParser
. - The
AddressBookParser
invokes thePatientBookParser
based on the regex pattern of the user input, splitting the user input intocommandWord
andarguments
. - The
PatientBookParser
invokes theAddMedicalEntryCommandParser
based on thecommandWord
, calling the methodparsePatientCommand
witharguments
as the method argument. -
AddMedicalEntryCommandParser
takes in the argument string and invokes anArgumentMultiMap
, which tokenizes thearguments
. - If the required
preamble
andPREFIX_MEDICAL
is present, theAddMedicalEntryCommandParser
will invoke theAddMedicalEntryCommand
after calling theparseMedicalHistory
method provided byParserUtil
, which returns aMedicalHistory
based on thedescription
data field. Thepreamble
identifies theIndex
of thePatient
to add the medical entry to, while the string afterPREFIX_MEDICAL
specifies thedescription
data field required for adding a newMedicalEntry
. -
LogicManager
calls theexecute
method of theAddMedicalEntryCommand
, which calls theaddMedicalHistory
of thePatient
specified by theIndex
. - The
AddMedicalEntryCommand
will then call the methodssetPatient
,updateAppointmentBook
,updateFilteredPatientList
andupdateFilteredAppointmentList
provided by theModel
, editing the patient’s medical history information. - The
AddMedicalEntryCommand
returns 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.
-
LogicManager
receives the user input which is parsed by theAddressBookParser
. - The
AddressBookParser
invokes thePatientBookParser
based on the regex pattern of the user input, splitting the user input intocommandWord
andarguments
. - The
PatientBookParser
invokes theDeleteMedicalEntryCommandParser
based on thecommandWord
, calling the methodparsePatientCommand
witharguments
as the method argument. -
DeleteMedicalEntryCommandParser
takes in the argument string and invokes anArgumentMultiMap
, which tokenizes thearguments
. - If the required
patientIndex
andmedicalIndex
is present, theDeleteMedicalEntryCommandParser
will invoke theDeleteMedicalEntryCommand
after calling theparseIndex
method provided byParserUtil
, which returns anIndex
to specify thepatient
and themedicalEntry
to be deleted. -
LogicManager
calls theexecute
method of theDeleteMedicalEntryCommand
, which calls thedeleteMedicalHistory
of thePatient
specified by theIndex
. - The
DeleteMedicalEntryCommand
will then call the methodssetPatient
,updateAppointmentBook
,updateFilteredPatientList
andupdateFilteredAppointmentList
provided by theModel
, editing the patient’s medical history information. - The
DeleteMedicalEntryCommand
returns 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 inJSONAdaptedAppointments
and 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 asJSONAdaptedAppointments
in 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 Patient s 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 thetarget
Appointment 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
ScheduledExecutorService
object schedules the taskAutoArchiveApmts
which implements theRunnable
interface. Every day at theModelManager.UPDATE_HOUR
th hour, theRunnable
object 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
Prescription
class contains fields recording Medicine, Volume and Duration. - Each
Appointment
class contains 0 or morePrescription
objects.
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'it
displays 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'it
adds the patient with necessary information.Use case ends.
Extensions
-
1a. Necessary details of patient are absent (name, address, phone number).
- 1a1.
Doc'it
shows an error message.
Use case resumes at step 1.
- 1a1.
-
1b. Patient details conflict with existing patient list.
- 1b1.
Doc'it
shows an error message.
Use case resumes at step 1.
- 1b1.
Use case: UC03 - Delete a patient
MSS
-
Doc'it
shows a list of patients. - User requests to delete a specific patient in the list.
-
Doc'it
deletes the patient.Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
2b. The given index is invalid.
-
2b1.
Doc'it
shows an error message.Use case resumes at step 2.
-
Use case: UC04 - List all appointments
MSS
- User requests to list appointments.
-
Doc'it
displays all appointments.Use case ends.
Extensions
-
1a. The list is empty.
Use case ends.
Use case: UC05 - Add an appointment
MSS
-
Doc'it
displays all patients. - User adds an appointment, matching the appointment to the specific patient.
-
Doc'it
adds 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'it
shows an error message.Use case resumes at step 2.
-
Use case: UC06 - Edit an appointment
MSS
-
Doc'it
displays all patients and appointments. - User edits an appointment, specifying new appointment details to replace old appointment details.
-
Doc'it
edits the appointment details.Use case ends.
Extensions
-
2a. The date of the appointment AND/OR the specified patient are invalid.
-
2a1.
Doc'it
shows an error message.Use case resumes at step 2.
-
Use case: UC07 - Delete an appointment
MSS
-
Doc'it
displays all appointments. - User requests to delete a specific appointment in the list.
-
Doc'it
deletes 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'it
shows an error message.Use case resumes at step 2.
-
Use case: UC08 - Archive an appointment
MSS
-
Doc'it
displays all appointments. - User requests to archive all appointments that are past its date.
-
Doc'it
archives 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'it
displays 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'it
displays 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'it
displays all patients. - User requests to add a medical entry to a specified patient.
-
Doc'it
adds the medical entry and records the date of entry.Use case ends.
Extension
- 2a. The given index is invalid.
- 2a1.
Doc'it
shows an error message. Use case resumes at step 1.
- 2a1.
Use case: UC12 - Delete a Medical Entry from a Patient
MSS
-
Doc'it
displays all patients. - User requests to delete a specified medical entry from a specified patient.
-
Doc'it
deletes the specified medical entry from the patient.Use case ends.
Extensions
-
2a. The given index specifying the medical entry is invalid.
-
2a1.
Doc'it
shows an error message.Use case resumes at step 1.
-
-
2b. The given index specifying the patient is invalid.
-
2b1.
Doc'it
shows an error message.Use case resumes at step 1.
-
-
2c. The patient specified has an empty medical history.
-
2c1.
Doc'it
shows an error message.Use case resumes at step 1.
-
Use case: UC13 - Find patient
MSS
-
Doc'it
displays all patients. - User requests to find patients whose names or medical histories match the given set of keywords.
-
Doc'it
lists 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'it
saves and writes all files. -
Doc'it
exits and closes.Use case ends.
Extensions
-
2a.
Doc'it
is unable to save file.-
2a1.
Doc'it
shows an error message.Use case resumes at step 1.
-
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or 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 list
command. 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 list
command. 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 list
command. 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 list
command. 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 list
command. 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'it
will start with a sample AddressBook and AppointmentBook with sample Patients and sample Appointments respectively.- If JSON files are corrupted,
Doc;it
will 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 list
command. 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 week
twice
Expected: No new prescription is created. Error messageOperation would result in duplicate prescriptions
is 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/Penicillin
twice
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.