PROJECT: Cooking Papa


Overview

Cooking Papa is a desktop cookbook application used to manage recipes and ingredients for cooking. It consists of a Cookbook containing recipes, an Inventory containing a users ingredients, and a Cart containing ingredients that a user needs to buy. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 15 kLoC.

Summary of contributions

  • Major enhancement: added the ability to search for recipes based on the ingredients in the inventory

    • What it does: allows the user to find recipes that match the inventory ingredients best, sorted by the similarity to the inventory ingredients.

    • Justification: This feature improves the product significantly because a user may not know what to cook with their ingredients, and the app should provide a convenient way to find the most suitable recipes to cook with the available ingredients.

    • Highlights: This enhancement required an in-depth analysis of design alternatives. The implementation too was challenging as it required a robust way of handling ingredient quantities.

    • Credits: Apache Commons Math (The Fraction class was used in implementing ingredient quantities)

  • Other enhancements:

    • Contributed to basic features involving ingredients, including adding and removing ingredients from recipes, and the inventory and cart

    • Contributed to a feature that added the ability to add ingredients from a recipe to the cart when a user wants get ingredients for a new recipe

    • Contributed to a feature that added the ability to move ingredients from the cart to the inventory after a user has finished shopping

  • Code contributed: Link

  • Other contributions:

    • Project management:

      • Helped the team to set up and maintain the forking workflow in this project

      • Managed the milestones v1.2 and v1.3 on Github

    • Enhancements to existing features:

      • Handled arithmetic of ingredient quantities to allow adding and removing ingredients with the same units (Pull request #60)

      • Set up the structure of the new commands and parsers (Pull request #82)

      • Integrated new classes to the architecture components of the app (Pull requests #96, #114)

      • Wrote tests for the Inventory and Cart, as well as Ingredient-related code in the Model(Pull requests #60, #78, #230)

      • Fixed a major GUI bug that caused Linux users not to be able to see the components of the app window properly (Pull request #234)

    • Documentation:

      • Wrote the Recipe Commands section and the Edit Recipe Commands section of the user guide

      • Updated UML class diagrams of the Logic, Model and Storage components from the Address Book Level 3 diagrams in the developer guide

      • Added manual testing instructions for adding and removing ingredients in a recipe

      • Added user stories

    • Community:

    • Tools:

Contributions to the User Guide

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

Recipe Commands (by Goh Ka Hui)

Recipe commands are commands that change an existing recipe in the cookbook, which can be indicated using the index of the recipe. You can use these commands to to add and remove ingredients or steps from a recipe in the cookbook. You may also tag your recipes to make it easier for you to find them.

Add an ingredient to a recipe

This command allows you to add ingredients to an existing recipe in the cookbook.

You can increase the quantity of an ingredient that is already in the recipe, as long as you provide a quantity with the same unit. Otherwise, Cooking Papa will not add the ingredient.

Ingredient quantities are case-sensitive, but ingredient names are not, when you adding to an existing ingredient in a recipe. If you indicate multiple ingredient names or quantities, Cooking Papa will only add an ingredient with the last name and quantity indicated in your command.

  • Format: recipe INDEX add ingredient i/INGREDIENT q/QUANTITY

  • Examples:

Command Result

recipe 1 add ingredient i/Garlic q/3 cloves

Adds 3 cloves of garlic to the first recipe’s list of ingredients.
Sample command feedback:
New ingredient added for Aglio olio: 2 cloves Garlic

recipe 2 add ingredient i/Eggs q/2

Adds 2 eggs to the second recipe’s list of ingredients.
Sample command feedback:
New ingredient added for Carbonara: 2 Eggs

Remove an ingredient from a recipe

This command allows you to remove ingredients from an existing recipe in the cookbook.

If you wish to remove an ingredient from the recipe entirely, do not indicate any quantity. Cooking Papa will then remove all of the specified ingredient from the recipe.

If you want to reduce the quantity of an ingredient that is already in the recipe, provide a quantity with the same unit as in the recipe. The value must be less than that of the corresponding ingredient in the recipe. Otherwise, Cooking Papa will not remove the ingredient.

  • Format: recipe INDEX remove ingredient i/INGREDIENT [q/QUANTITY]

  • Examples:

Command Result

recipe 1 remove ingredient i/Eggs

Removes all eggs from the list of ingredients in recipe 1.
Sample command feedback:
All Eggs removed from recipe 1

recipe 2 remove ingredient i/Milk q/200 ml

Removes 200 ml of milk from the list of ingredients in recipe 2, provided that the original quantity of milk in the recipe is greater than 200 ml.
Sample command feedback:
200 ml milk removed from recipe 2

Add a preparation step to a recipe

This command allows you to add a step to an existing recipe in the cookbook.

For this command, you have to specify the index of the step that you wish to add, which must be positive, and at most 1 more than the current number of steps currently in your chosen recipe. For example, if there are currently 5 steps in the recipe you are trying to add to, you can only choose an index from 1 to 6.

This command pushes back the original step at your specified step index and any subsequent steps, if any.

You can use next as a substitute for the largest possible step index, to add a step to the end of the selected recipe’s list of steps.

  • Format: recipe INDEX add step x/STEP_INDEX s/STEP_DESCRIPTION

  • Example:

Command Result

recipe 1 add step x/1 s/Bring the water to boil.

Adds "Bring water to boil." to recipe 1 as the first step.
Sample command feedback:
New step added for Aglio olio: Bring the water to boil.

recipe 1 add step x/next s/Serve the pasta topped with the remaining cheese.

Adds "Serve the pasta topped with the remaining cheese." to recipe 1 as the last step.
Sample command feedback:
New step added for Aglio olio: Serve the pasta topped with the remaining cheese.

Remove a preparation step from a recipe

This command allows you to remove a step to an existing recipe in the cookbook.

Similar to the previous command, you have to specify the index of the step that you wish to add, which must be positive, and at most 1 more than the current number of steps currently in your chosen recipe.

In this case, you cannot use next as a substitute for the largest step index.

  • Format: recipe INDEX remove step x/STEP_INDEX

  • Example:

Command Result

recipe 1 remove step x/1

Removes step 1 in recipe 1.
Sample command feedback:
Step deleted for Aglio olio: Bring water to boil.

Add a tag to a recipe

This command allows you to add a tag to an existing recipe in the cookbook. Only one tag is accepted at a time. If you provide multiple steps, Cooking Papa will only add the last tag in your command.

  • Format: recipe INDEX add tag t/TAG

  • Example:

Command Result

recipe 1 add tag t/Pasta

Adds a tag "Pasta" to the recipe 1.

Remove a tag from a recipe

This command allows you to remove a tag from an existing recipe in the cookbook. Only one tag is accepted at a time. If you provide multiple steps, Cooking Papa will only remove the last tag in your command.

  • Format: recipe INDEX remove tag t/TAG

  • Example:

Command Result

recipe 1 remove tag t/Spicy

Removes the tag "Spicy" from the recipe 1.


Example Scenario

With all these recipe commands, you are equipped to modify any recipe in the cookbook to your liking! Below outlines a scenario where you might find these commands useful. Imagine that you have just added a new recipe for instant ramen via the cookbook add recipe command, which now has the index 3.

RecipeCommand0
Figure 1. Cooking Papa with the recipe for instant ramen at index 3.

You have provided the name and description for the recipe, but have yet to add ingredients and steps to the recipe. Here are the steps you can follow to modify the recipe:

Step 1: To add an ingredient to the instant ramen recipe, type recipe 3 add ingredient i/Instant ramen q/1 packet into the command box and press Enter on the keyboard. Cooking Papa indicates that cucumber has been added to the recipe.

RecipeCommand1
RecipeCommand2

Step 2: Repeat step 1 for all the ingredients you need for the instant ramen.

Step 3: To add the first step the instant ramen recipe, type recipe 3 add step x/next s/Add the oil, garlic, and ginger to a small sauce pot and saute over medium for about one minute. into the command box and press Enter on the keyboard. Cooking Papa indicates that the new step has been added to the recipe.

RecipeCommand3
RecipeCommand4

Step 4: Repeat step 3 for all the steps, following chronological order.

Step 5: After adding the ingredients and steps, you can also add tags to the recipe so that you can search for it easily in future. Type recipe 3 add tag t/Easy t/Healthy to add the tags "Easy" and "Healthy" to the recipe. Upon pressing Enter, Cooking Papa indicates that the tags have been added.

RecipeCommand5
RecipeCommand6

Step 6: The recipe is finally ready for use. Type cookbook view recipe 3 into the command box to view the recipe and and press Enter. Cooking Papa shows the full recipe for instant ramen, and you can now use it to prepare your meal.

RecipeCommand7

Step 7: After trying the recipe, if you find that there was too much of ginger for your liking, type recipe 3 remove ingredient i/Grated ginger q/1/2 tsp into the command box and press Enter to remove half a teaspoon of grated ginger from the recipe. Cooking Papa indicates that half a teaspoon of grated ginger has been removed from the recipe.

RecipeCommand8
RecipeCommand9

Edit Recipe Commands [v2.0] (by Goh Ka Hui)

The following commands allow you to edit the details of the recipes that are currently in the cookbook.

Edit an ingredient in a recipe

This command allows you to edit an ingredient in an existing recipe in the cookbook. It replaces the original quantity of the ingredient with the new quantity that you specify in the command.

  • Format: recipe INDEX edit i/INGREDIENT q/QUANTITY

  • Examples:

Command Result

recipe 1 edit i/Eggs q/2

Changes the quantity of eggs to 2 in the first recipe’s list of ingredients.

recipe 2 edit i/Milk q/100 ml

Changes the quantity of milk to 100 ml in the second recipe’s list of ingredients.

Edit a preparation step in a recipe

This command allows you to edit a step in an existing recipe in the cookbook.

  • Format: recipe INDEX edit step x/STEP_INDEX s/STEP_DESCRIPTION

  • Example:

Command Result

recipe 1 edit step x/2 s/Fry the eggs

Changes preparation step 2 to 'Fry the eggs' in recipe 1

Contributions to the Developer Guide

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

Logic component (by Goh Ka Hui)

LogicClassDiagram
Figure 2. Structure of the Logic component

API : Logic.java

  1. Logic uses the CookingPapaParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a recipe).

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

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("cookbook remove recipe 2") API call.

CookbookRemoveRecipeSequenceDiagram
Figure 3. Interactions inside the Logic component for the cookbook remove recipe 2 command

Model component (by Goh Ka Hui)

ModelClassDiagram
Figure 4. Structure of the Model component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Cookbook data.

  • exposes an unmodifiable ObservableList<Recipe> 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 the Inventory and Cart data.

  • exposes an unmodifiable ObservableList<Recipe> each for both Inventory and Cart, 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.

  • does not depend on any of the other three components.

As a more OOP model, we can store a Tag list in Cookbook, which Recipe can reference. This would allow Cookbook to only require one Tag object per unique Tag, instead of each Recipe needing their own Tag object. An example of how such a model may look like is given below.

BetterModelClassDiagram

Storage component (by Goh Ka Hui)

StorageClassDiagram
Figure 5. Structure of the Storage component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save Cookbook data in json format and read it back.

  • can save Inventory data in json format and read it back.

  • can save Cart data in json format and read it back. === Search for recipes based on ingredients in the inventory (by Goh Ka Hui) The user may use this command to search for recipes that they can cook using the ingredients available in their inventory. This feature was implemented to address users' need of easily finding a recipe based on the ingredients they have. With this feature, users can whip up a meal without having to go grocery shopping if they are short of time. This feature sorts recipes by how much the inventory fulfils their ingredient requirements, and filters out recipes whose ingredient requirements are not met at all. Users can immediately see at the top of the cookbook the recipes that their ingredients are most suitable for preparing. A user can use this feature by typing the command: cookbook search inventory.

Implementation

The comparison between the ingredients a recipe requires and the ingredients in the inventory is facilitated by the RecipeInventoryIngredientsSimilarityComparator. It extends Comparator<Recipe> and stores the inventory being used for ingredient comparison. Additionally, it implements the method calculateSimilarity(), which accepts a Recipe and a ReadOnlyInventory as parameters, and returns a double value between 0 and 1 (both inclusive) that represents the proportion of the recipe’s ingredient requirements that are fulfilled.

The following class diagram summarizes how the RecipeInventoryIngredientsSimilarityComparator interacts with Recipe and Inventory:

CookbookSearchByInventoryClassDiagram
Figure 6. Class diagram for RecipeInventoryIngredientsSimilarityComparator

The calculateSimilarity() method first calculates the proportion of ingredient quantity fulfilled by the inventory for each ingredient that the recipe requires. For example, if one of the ingredients required by a recipe is 4 eggs and the inventory contains 2 eggs, the proportion fulfilled for this particular ingredient is 0.5. This is done for all the ingredients in the recipe. If the units of an ingredient in the recipe does not match that of the same ingredient in the inventory, the proportion will be set at 0.5 by default. An example is when the recipe requires 1 cup flour and the inventory contains 200 g flour.

When the proportion fulfilled has been calculated for each ingredient, the values for each ingredient are summed up and divided by the number of ingredients to obtain the average. In the case where the recipe does not have any ingredients added to it yet, the calculateSimilarity() method will return 0, indicating no similarity to the inventory ingredients. This is because it is likely that recipes with no ingredients have just been added by the user, and the ingredients have not been added yet. If the user is using this feature to search for a recipe to cook, they would probably not be interested in seeing a recipe that they have not added ingredients for yet. This is implemented via a guard clause as shown in the following code snippet:

if (recipe.getIngredients().size() == NO_INGREDIENTS) {
    return ZERO_SIMILARITY;
}

The following activity diagram shows a possible flow of events for a user using this feature:

CookbookSearchByInventoryActivityDiagram
Figure 7. Activity diagram for the cookbook search inventory command

The following sequence diagram summarizes how objects interact when a user executes the command, with more focus on how the command is parsed in the `Logic`component:

CookbookSearchByInventorySequenceDiagram
Figure 8. Sequence diagram for CookbookSearchByInventoryCommand

Design considerations

Aspect 1: Weighting of each ingredient
Table 1. Design considerations for weighing of each ingredient
Design A (Current choice): Every ingredient is weighted equally Design B: More important ingredients are given a larger weigting

Description

The similarity of a recipe’s ingredients to an inventory’s ingredients is calculated by taking the mean of the proportions calculated for each ingredient, with equal weighting given to all ingredients.

The similarity of a recipe’s ingredients to an inventory’s ingredients is calculated by taking the weighted mean of the proportions calculated for each ingredient, with larger weightings given to more important ingredients ingredients.

Pros

Gives a good rough estimate of the proportion of ingredient requirements fulfilled for a recipe, and straightforward to implement.

May give a better gauge of the proportion of ingredient requirements fulfilled for a recipe, by accounting for the importance of the ingredient. For example, beef would be an important ingredient for a steak recipe, but garnishes might be considered less important as they can be substituted more easily.

Cons

Does not account for the importance of the ingredient in the recipe

Difficult to judge the importance of the ingredient, and complicated to implement categorisation of the types ingredients and their relative importance.

Design A was chosen as it provided a fair estimate of the similarity between the recipe and inventory ingredients, with a simple implementation. The cons for Design B were deemed to outweigh the pros, especially since the importance of an ingredient in a recipe could be rather subjective.

Aspect 2: Handling ingredients with different units
Table 2. Design consideration for handling ingredients with different units
Design A (Current choice): Use a default similarity value of 0.5 Design B: Convert the units

Description

The similarity value of an ingredient with different units in the recipe and the inventory is treated as 0.5.

The similarity value of an ingredient with different units in the recipe and the inventory is calculated by converting the units, such that the proportion of the recipe ingredient in the inventory can be determined.

Pros

Simple to implement.

Able to calculate the proportion of the recipe ingredient fulfilled by the inventory, even when dealing with different units.

Cons

Unable to calculate the proportion of the recipe ingredient fulfilled by the inventory when dealing with different units, and can only give a fixed default value of 0.5.

More complicated to implement as it requires CookingPapa to recognise the units in both the recipe and inventory and be able to convert between them. Some units such as cup may also not have a standard conversion factor.

Design A was chosen due to time constraints, as handling the conversion between different units would take time away from developing other parts of the application. Given more time, Design B will be implemented to handle conversion for standard units, such as between g and kg, but Design A would still have to be used for units with non-standard conversion factors.