PROJECT: CLInic


1. Introduction

This Project Portfolio Page documents my contributions to the development of CLInic, the product of a team of five students from NUS School Of Computing as part of the CS2103T Software Engineering module. The main features of the application are coded in Java while the GUI is handled by JavaFX, HTML and CSS.

CLInic is a desktop application made to manage and expedite operations in clinics. The main impetus behind CLInic was to distil the CLI’s efficiency of typing commands, as its cryptic walls of text was intimidating to the general, non tech-savvy populous. Hence, CLInic marries the simplistic design and efficient command issuing mechanism of the CLI with an intuitive Graphical User Interface (GUI). This culminated in an application that is easy for anyone to use, all while providing the powerful functionalities and efficiencies offered by a CLI.
Below are some of the key features provided by CLInic:

  • Allows users to manage patient and medicine information

  • Allows users to easily generate documents to be issued by a clinic like Medical Certificates efficiently

  • Provides intuitive workarounds to normally arduous but common administrative tasks such as restocking and dispensing medicine

I played a major role in the development of the Document class, the relevant Receipt, MedicalCertificate and ReferralLetter classes that extend it and the relevant patient management operations that are required for them to work.

2. Summary of contributions

  • Major enhancement: Implemented the Document class, its accompanying HTML template and extending classes, which includes the Receipt, MedicalCertificate and ReferralLetter classes.

    • What it does: It takes in the information of a document that is to be printed by the clinic and generates a HTML file for it.

    • Justification: This functionality simplifies the generation of documents that are required by the clinic. This makes the administrative tasks in the clinic easier and faster, boosting the productivity of its users, translating into shorter wait times at the clinic for the patients.

    • Highlights: This functionality allows for seamless transfer of information from doctors to receptionists of the clinic during consultation, which when coupled with the fast retrieval of information from the application’s database of Patient and Medicine information, makes for efficient generation of relevant documents.

  • Minor enhancements:

    • Improved and helped with the implementation of ServedPatient and ServedPatientListManager, which are intermediate abstractions(placeholders) that help to 'finalise' the information to be used for the generation of Document.

    • Integrated MedicalCertificate with the DocumentAddContentCommand.

    • Designed the HTML template which all the classes that extend Document use to format their information.

  • Code contributed: View the code I have written for the project here!

  • Other contributions:

    • Enhancements to features:

      • Wrote tests for various features to increase coverage from 59% to 63% (Pull requests: #97, #98, #104)

    • Community:

      • Reviewed PRs and provided constructive feedback: (Pull requests: #110, #119, #120)

    • Documentation:

      • Wrote the introduction to the User Guide (Pull Request: #134)

      • Added many examples in the User Guide to demonstrate how various commands work. (Pull Request: #138)

      • Standardised the formatting of the User Guide and corrected any incorrect use of language. (Pull Requests: #139, #172, #174)

3. Contributions to the User Guide

Given below are the sections I have contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

3.1. Implementation of the Patient Record Management System


Adding a patient: add

Did someone just walk in, wanting to see the doctor?
Add him to CLInic’s patient database using the add command!
Alias: a
Format: add n/NAME ic/IC_NUMBER p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​

Pro tip(s):
  • A patient can have any number of tags (including 0).

  • Mandatory parameters can be keyed in any order.

Important:
Omission of any mandatory parameters will result in the failure of adding the patient to the database.

Upon successful execution, you should see the patient with his details added to the patient list on the left.

Examples:

  • add n/Clinton Law ic/S9638902L p/97947435 e/clinton@cs2103t.com a/Tembusu College
    Adds a patient named Clinton Law, with IC number S9638902L, phone number 97947435, email clinton@cs2103t.com and address of Tembusu College to CLInic’s patient database, as shown below.

AddEx1
Figure 1. After executing the add command specified above and left clicking Clinton’s patient card in the patient list on the left, indicated by the black cursor.

Editing a patient: edit

Uhoh! Made a mistake keying in a patient’s particulars? Or perhaps a patient had recently changed his address?
Fret not! Simply update his particulars using the edit command!
Alias: e
Format: edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​

Thing(s) to note:
  • The INDEX refers to the index number shown in the displayed patient list. It must be a non zero, positive integer (eg. 1, 2, 3…​)!

  • At least one of the optional parameters must be provided.

  • Existing values will be overwritten by the newly input values of the corresponding field.

  • You cannot edit the record of a patient that is currently in the queue.

Pro tip(s):
You can remove all of a patient’s tags by typing the t/ prefix without specifying any tags.

Upon successful execution, you should see the patient’s details updated in the patient list on the left.

Examples:

  • edit 5 p/98534228 e/clawyq@cs2101.com
    Updates the phone number and e-mail address of the 5th patient in the list to 98534228 and clawyq@cs2101.com respectively, as shown below.

EditEx1
Figure 2. Before and after executing the edit command specified above.

4. Contributions to the Developer Guide

Given below are the sections I have contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Document

Implementation of Document

The Document abstract class represents all the documents that can be issued by the clinic. The Receipt, MedicalCertificate and ReferralLetter concrete classes, which respective commands will be referred to henceforth as document related commands, all inherit from it. It can be thought of as a formatter that specifies and organises the common information that all these documents must have with the help of the DocumentTemplate.html template file.
Shown below is a class diagram illustrating how Document, Receipt, MedicalCertificate and ReferralLetter relate to one another.

DocumentClassDiagram
Figure 3. Class diagram representing how the Document and document related classes are implemented.

As the formatter which directs how the documents are built from the DocumentTemplate.html template, the Document abstract class itself cannot be instantiated. The Document abstract class has the following key features:

  • contains the name and icNumber attributes, extracted from the ServedPatient from which the Document was constructed from.

  • has common methods like generateContent to standardise the way in which the contents of the Document are formatted.

  • has the formatInformation abstract method which the extending classes have to implement.

The following activity diagram summarises what happens internally when a document related command is executed.

DocumentActivityDiagram
Figure 4. Flow of activities when a document related command is executed. Only the methods that are implemented within Document itself are labelled in the diagram.

As seen in the above activity diagram, upon executing a document related command, the corresponding document will be constructed from the ServedPatient object specified by the index that was passed in with the command, as detailed in the following steps.

Step 1. The resulting document related object will call the generateDocument method, which will first make a unique fileName for the file to be created, identified by the type of Document, ServedPatient’s name and icNumber.

private String makeFileName() {
    return (fileType + FILE_NAME_DELIMITER + "For" + FILE_NAME_DELIMITER + name.toString()
        + FILE_NAME_DELIMITER + icNumber.toString())
        .replaceAll("\\s", FILE_NAME_DELIMITER)
        .replaceAll("(_)+", FILE_NAME_DELIMITER);
}

The FILE_NAME_DELIMITER is abstracted so as to allow users to change the delimiter to a string of their choice. It is used as a means of separating fields of information in file names.

Step 2. The DocumentTemplate.html file is then converted into a string. It is a template with placeholder values prefixed by a $ that will be replaced with the actual information pertaining to the ServedPatient’s consultation.
Shown below is the code snippet that converts the DocumentTemplate HTML file into a string.

private String convertHtmlIntoString() {
    StringBuilder contentBuilder = new StringBuilder();
    try {
        BufferedReader in = new BufferedReader(new FileReader(COMPLETE_TEMPLATE_PATH));
        String str;
        while ((str = in.readLine()) != null) {
            contentBuilder.append(str).append("\n");
        }
        in.close();
    } catch (IOException e) {
        System.out.println(TEMPLATE_LOCATE_FAILURE_ERROR_MESSAGE);
    }
    return contentBuilder.toString();
}

Step 3. The generateContent method will then be called within the writeContentsIntoDocumentTemplate method. The generateContent method will construct a HashMap of string to string key-value pairs. The keys are the aforementioned placeholder strings prefixed by $ found in the HTML template, while the values are the corresponding information that are to substitute the placeholders in the HTML template(keys). The writeContentsIntoDocumentTemplate method will then use the key-value pairs found in the HashMap created by the generateContent method to replace the placeholder values in the HTML template.

private String writeContentsIntoDocumentTemplate() {
    String htmlContent = convertHtmlIntoString();
    String title = fileType + " for " + this.name;
    htmlContent = htmlContent.replace("$title", title);
    HashMap<String, String> fieldValues = this.generateContent();
    for (Map.Entry<String, String> entry : fieldValues.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        htmlContent = htmlContent.replace(key, value);
    }
    return htmlContent;
}

private HashMap<String, String> generateContent() {
    HashMap<String, String> informationFieldPairs = new HashMap<>();
    informationFieldPairs.put(HEADER_PLACEHOLDER, generateHeaders());
    informationFieldPairs.put(NAME_PLACEHOLDER, name.toString());
    informationFieldPairs.put(ICNUMBER_PLACEHOLDER, icNumber.toString());
    informationFieldPairs.put(CONTENT_PLACEHOLDER, formatInformation());
    return informationFieldPairs;
}

Step 4. Now that the HTML template is a string with the placeholder values replaced by the correct information, a BufferedOutputStream is used to write the updated string into the HTML file, concluding the generation of the Document HTML file.

Design Considerations

Aspect: Implementation of the Document
  • Alternative 1 (current choice): Use an abstract class to implement Document.

    • Pros:

      • Allows inheriting classes to use a standardised method to format common information while granting them the flexibility of structuring the contents unique to its document type by means of abstract methods.

    • Cons:

      • Prevents inheriting classes from becoming an Enum as Java does not support multiple inheritance, since all enums implicitly inherit from Enum.

      • More computationally expensive than an interface due to the overhead incurred looking up inherited methods and various class members.

  • Alternative 2: Use an interface to implement Document.

    • Pros:

      • Can convert inheriting classes to enums, ensuring that the inputs fed to these classes are valid.

Enums can restrict the period of medical leave to be counted in only days/weeks. This prevents absurd scenarios like issuing a Medical Certificate that excuses a person from work/school for 10 years!

  • Cons:

    • Unable to standardise the way inheriting classes implement the generateHeader method.

  • Potentially allow the inheriting classes to omit certain header fields that are relevant to the printing of a document, such as the date and time of the consultation.

  • Information might be formatted differently.

4.2. Implementation of the Receipt class


Implementation of Receipt

The Receipt class structure is as follows:

  • Contains a Map<Medicine, MedicineQuantity> attribute to record the quantity dispensed of each type of Medicine to the ServedPatient from which the Receipt was constructed from.

  • Contains a HashSet<Service> attribute to record the Services administered to the patient.

  • Contains a totalPrice attribute which stores the total price of all the Services and Medicines received by the patient during the consultation.

Users can generate a Receipt for a specific ServedPatient by invoking the ReceiptCommand accompanied by the ServedPatient 's index in the ServedPatientList. The below sequence diagram illustrates how the ReceiptCommand works.

ReceiptSequenceDiagram
Figure 5. Sequence diagram for ReceiptCommand.

When the ReceiptCommand is executed, it constructs a new Receipt object and extracts the relevant information from the ServedPatient specified by the index.
Shown below is how the Map<Medicine, MedicineQuantity> of a ServedPatient is unpacked to sieve out the individual table entries for the cost of different medicines.

    private String unpackMedicineAllocation(Map<Medicine, QuantityToDispense> medicineAllocated) {
        StringBuilder stringBuilder = new StringBuilder();
        for (Map.Entry<Medicine, QuantityToDispense> entry : medicineAllocated.entrySet()) {
            Medicine medicine = entry.getKey();
            String medicineName = medicine.getMedicineName().toString();
            int quantity = entry.getValue().getValue();
            float pricePerUnit = Float.parseFloat(medicine.getPricePerUnit().toString());
            float totalPriceForSpecificMedicine = pricePerUnit * quantity;
            increaseTotalPriceBy(totalPriceForSpecificMedicine);
            stringBuilder.append("<tr><td>")
                    .append(medicineName)
                    .append(super.HTML_TABLE_DATA_DIVIDER)
                    .append(quantity)
                    .append(super.HTML_TABLE_DATA_DIVIDER)
                    .append(String.format("%.02f", pricePerUnit))
                    .append(super.HTML_TABLE_DATA_DIVIDER)
                    .append(String.format("%.02f", totalPriceForSpecificMedicine))
                    .append("</td></tr>");
        }
        return stringBuilder.toString();
    }