PROJECT: Souschef


Overview

Souschef is a desktop smart cooking sidekick, offering personalised guidance every step of the way. From recipe recommendations just for you, to meal planning and inventory management, SousChef has everything you need to improve life in the kitchen.

Our users interact via CLI and GUI created with JavaFX. Written in Java, experience what many are enjoying.

Summary of contributions

  • Code contributed: [Functional code]

  • Major enhancement: added the ability to manage and manipulate recipes

    • Multi-line Add Recipe Functionality

      • What it does: Allow users to add a recipe in progression using multi-line command .

      • Justification: The multi-line add recipe command improves the product by providing a intuitive yet convenient way to create a recipe entry as opposite to forcing users to input a lengthy recipe entry containing a number of instructions into a single command.

      • Highlights: This enhancement requires the handling of a incomplete recipe data when the add is being executed in progression by the user. The challenges faced include morphing address book into a nested recipe data structure that stores instructions which stores ingredients. Secondly, the analysis and design to create a in-progress recipe instance that allows the appending of more recipe details when the user contributes to the incomplete recipe. Thirdly, the retention and accessing of this incomplete recipe in order to appending the details and ultimately create an actual recipe instance to be stored in the model.

    • All-field Recipe Search

      • What it does: Retrieve recipes with name, difficulty, cooktime or ingredients that matches the keywords by the user.

      • Justification: This feature improves the application because users can now narrow down their wanted recipes based other parameters such as difficulty, cooktime and ingredients rather than restricting their search to just the name of the recipe.

      • Highlights: Ingredients are being stored in each instructions of a recipe, as such to support the search of ingredients, the design of a recipe data to provide ease of access to all ingredients is required.

    • Other Associated Functionality implemented: Edit Recipe

  • Project management:

    • Setting up of GitHub, Travis, AppVeyor, coveralls, codacy

    • Managed releases v1.1 - v1.4 (4 releases) on GitHub

    • Maintaining of Github issue tracker (allocate assignee, labels) and milestones

  • General/Team Enhancements:

    • Morphing the AB4 into a SousChef (Pull requests #28, #29, #31)

    • Revamp model component to allow extension (Pull requests #46, #55)

  • Documentation:

    • Updating README, AboutUs and user docs that are not specific to a feature: #6, #26

  • Community:

    • PRs reviewed (with non-trivial review comments and suggestions): #52, #64, #139

    • Reported bugs and suggestions for other teams in the class (examples: 1, 2, 3)

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.

Features

In Sous Chef, there are 2 types of command: universal commands and context-unique commands.

The universal commands can be used throughout the application and perform application-wide action. You can identify a universal command from its "-" convention followed by a keyword (e.g. -exit).

The context-unique commands on the other hand are only valid for that context. (e.g. Recipes Commands (Only applicable in recipe context) can only be used in -recipe context). These commands contains only a action key word followed by the required parameters, if any. (e.g. select INDEX)

Command Format Convention

  • Words in UPPER_CASE are the parameters to be supplied by the user e.g. in add n/NAME c/COOKTIME d/DIFFICULTY [t/TAG], NAME, COOKTIME and DIFFICULTY and TAG are parameters are to be replaced: add n/Fried Chicken d/2 c/20M t/Fastfood.

  • Items in square brackets are optional (i.e. can be omitted).

  • Items with ​ after them can be used multiple times e.g. [t/TAG]…​ can be used 1 or more: t/Japanese, t/Halal t/Seafood etc. With [] it can also be omitted totally too.

Universal Commands

View help: -help

To view the help menu containing all the universal commands.
Format:
-help

View history: -history

To view previous commands entered.
Format:
-history

Switch to recipe context: -recipe

Switch to recipe context and display recipes.
See Recipes Commands (Only applicable in recipe context) to view commands for recipe context.
Format:
-recipe

Switch to ingredient manager context: -ingredient

Switch to ingredient manager context and display ingredient manager, which helps stock tracking of ingredient that the user currently have.
See [Ingredient Manager Commands (Only applicable in ingredient context)] to view commands for ingredient manager context.
Format:
-ingredient

Switch to favourites context: -favourite

Switch to favourites context and display favourites list, which keeps a list of all the users' favourite recipes.
See [Favourite Recipe Commands (Only applicable in favourites context)] to view commands for favourites context.
Format:
-favourite

Switch to ingredient-recipe query context: -cross

Switch to ingredient-recipe query context and where you can sort, filter the recipe list by name of ingredients included and get information of needed amounts of ingredients.
See [Ingredient-Recipe Query Commands (Only applicable in cross context)] to view commands for ingredient-recipe query context.
Format:
-cross

Switch to meal planner context: -mealplanner

Switch to meal planner context and display the planned meals for breakfast, lunch and dinner for previously planned days.
See [Meal Planner Commands (Only applicable in meal planner context)] to view commands for meal planner context.
Format:
-mealplanner

Switch to health plan context: -healthplan

Switch to health plan context and display health plan set by the user and the days added into the plan which is tied to meal plans.
See [Health Plan Commands (Only applicable in health plan context)] to view commands for health plan context.
Format:
-healthplan

Exit application: -exit

Format:
-exit

Recipes Commands (Only applicable in recipe context)

List recipes: list

Show all recipes.
Format:
list

Add a recipe: add cont end

Add new recipe.
Format:
add n/NAME c/COOKTIME d/DIFFICULTY [t/TAG]…​
cont i/INSTRUCTION…​ [c/COOKTIME]
cont…​
end


INSTRUCTION:
TEXT…​ [#INGREDIENT_NAME AMOUNT SERVING_UNIT]…​

Full set of commands add cont end must be performed for the adding of recipe to be completed.

  • This is a multi-line command. (i.e. add, cont and end must be in a seperate command entry)

  • Command starts with add and should include name, cook time and difficulty. Tags can be added as required.

    • NAME should come with any alphanumeric characters.

    • COOKTIME should have a integer value each with a postfix of H/M/S. A mixture of up to 2 postfixes are allowed i.e. H & M or M & S. Valid examples: 1H15M, 30M20S, 35M.

    • DIFFICULTY should range from 1 to 5.

    • TAG should come with any alphanumberic character.

  • Subsequence lines must start with cont and should only include details on one instructional step each.

    • Details to be included are instruction text, instruction exclusive cook time (optional) and ingredients (optional).

    • Instruction will be saved in the same sequence as it is inputted.

    • Ingredients can be embedded into instruction text via #INGREDIENT_NAME AMOUNT SERVING_UNIT.

      • Compound INGREDIENT_NAME is acceptable. e.g. Bleached Wheat Flour.

      • AMOUNT accepts both integer and decimal. Decimal must come with a leading 0 for values less than 1. e.g. 0.25

      • SERVING_UNIT should be single-worded. e.g. gram, g, ml.

  • User can perform other commands and continue adding instructions as required. cont command need not be continuous.

  • To overwrite existing recipe that has yet been added (i.e. end command not used), simply use the add command and enter a new recipe details as desired.

  • The end command record and save the recipe.

Recipes containing the same name, difficulty and cooktime are considered as duplicates and is not be allowed.

Examples:

  • add n/Chicken Rice d/2 c/45M
    cont i/Clean and cut #chicken 1.2 kg.
    cont i/Put the chicken in #boiled water 900 ml for 10 mins. c/10M
    cont i/Remove the chicken and put #soy sauce 100 ml.
    cont i/Cook for another 20 mins. c/20M
    end

Edit a recipe: edit

Edit new recipe.
Format:
edit INDEX [n/NAME] [c/COOKTIME] [d/DIFFICULTY] [t/TAG]…​
or
edit INDEX s/STEP i/INSTRUCTION [c/COOKTIME]

INSTRUCTION:
TEXT…​ [#INGREDIENT_NAME AMOUNT SERVING_UNIT]…​

  • Attributes included are to be edited on a replacement basis (not concatenation e.g. tags are being replaced and not added)

  • INDEX should be the index number of the recipe displayed.

  • There are 2 types of edit command.

    • First: Used to edit recipe generic information namely name, cook time, difficulty and tags.

      • NAME should come with any alphanumeric characters.

      • COOKTIME should have a integer value each with a postfix of H/M/S. A mixture of up to 2 postfixes are allowed i.e. H & M or M & S. Valid examples: 1H15M, 30M20S, 35M.

      • DIFFICULTY should range from 1 to 5.

      • TAG should come with any alphanumberic character.

    • Second: Used to edit/add a single instruction of/to that recipe.

      • STEP refers to the instruction number of the recipe. It should be existing instruction to replace the instruction or a number higher than highest existing STEP to add. (e.g. 6 existing instruction steps, use s/7 to add instead of replace)

      • INSTRUCTION must be included and should come with any alphanumeric characters. Ingredients can be added using #INGREDIENT_NAME AMOUNT SERVING_UNIT as required.

      • COOKTIME should have a integer value each with a postfix of H/M/S. A mixture of up to 2 postfixes are allowed i.e. H & M or M & S. Valid examples: 1H15M, 30M20S, 35M.

Examples:

  • edit 1 c/20M t/Asian t/Staple

  • edit 1 s/2 i/Pour #water 300 ml into the mix.

Display recipe details: select

View a recipe and its details from the list.
Format:
select INDEX

  • INDEX should be the index number of the recipe displayed.

  • All serving unit will be converted (approximately) to our common serving unit "gram" for display.

Search recipes: find

Show recipes related to the keyword(s). Keywords include but not limited to cuisines (Indian, Japanese), dietary types (Vegetarian, Halal), ingredients (egg, broccoli), preparation time (30M, 1H40M) and difficulty (1, 2, …​, 5).
Format:
find KEYWORD…​

  • KEYWORD is case insensitive.

  • The order of keywords does not matter.

Examples:

  • find rice asian 3

  • find korean kimchi staple

Delete recipe: delete

Delete a recipe and its details from the list.
Format:
delete INDEX

  • INDEX should be the index number of the recipe displayed.

Activate cook-mode [coming in V2.0]: cook

A cook mode that provides step-by-step guidance to aid real-time cooking.
Format:
cook INDEX

  • INDEX should be the index number of the recipe displayed.

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.

Model component

ModelComponent
Figure 1. Structure of the Model Component

The ModelSet,

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

  • stores the Sous Chef application data.

    • Contains multiple Model each in-charge of a feature’s data.

  • shares a single instance of VersionedAppContent to ensure single version of truth.

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

The Model,

  • each represents a feature-unique data.

    • ensures data abstraction for each feature.

  • is reusable as model is now generic.

  • exposes an unmodifiable ObservableList<T extends UniqueType> 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.

Recipe Management

Multi-line Recipe Adding

Current Implementation

As the adding of recipes deal with lengthy input parameters from the basic details such as name to a list of instructions, single-line add command proves to be inefficient and often providing an inconvenient user experience. To allow flexibility, adding of recipes have been implemented as a multi-line command fitting into this CLI application.

Multi-line add function consists of 3 main commands: add cont end. The add command requires user input of the recipe basic information such as Name, Difficulty, CookTime and the optional Tag. The cont command requires user input of the recipe Instruction. Each cont command allows only 1 Instruction. Again this is to break down the lengthy input parameter of Instruction. Lastly, with the end command the Recipe is being recognised and is added to the Model.

The following is the sequence diagram of the add command:

recipe add add

Once user’s input is being parsed, a RecipeBuilder is created instead of a Recipe instance. This RecipeBuilder will be stored in History when the command is being executed.

Repeating use of add will replace existing RecipeBuilder rendering the previous data unsaved until user complete the add recipe command.

The following is the sequence diagram of the cont command:

recipe add cont

Upon parsing the user’s input that contains Instruction, a Instruction instance will be created. This Instruction is to be passed to the command for execution. The execution process once again make use of the History to append the Instruction into RecipeBuilder.

The following is the sequence diagram of the end command:

recipe add end

Upon receiving the end command, Recipe will be built from the RecipeBuilder. The parsing process checks to ensure that the Recipe built contains at least a single Instruction to prevent adding of Recipe without Instruction. With a valid Recipe, a AddCommand will be created and the RecipeBuilder in History will be cleared (i.e. set to null). This AddCommand when executed will add the Recipe into Model.

Why it is implemented that way:

Due to the nature of a multi-line add command where the Recipe is incomplete together with the inability to execute the add function immediately, 2 mechanisms namely, a builder and a historical storage, have to be put in place. The builder works effectively in managing the data of a partial Recipe while allowing continuous modification. The historical storage, History in this context, ensures that lossless partial Recipe data. Moreover, since History interaction with commands has already been well establish in Logic component and a partial Recipe is indeed a representation the historical commands, storing in History becomes a dominant choice.

Alternatives:
  • Adding of incomplete recipe to the model and edit that recipe with the subsequent commands.

    • Pros - Make use of existing add and edit command without the need of additional implementation.

    • Cons - Model contains incomplete recipe that violates the validity of recipe data. This might be exploited and in the process the application can be filled with incomplete recipes.

The all-field recipe search matches keywords with details of Recipe. Each keyword goes through the matching process against the Name, Tag, Difficulty, CookTime and even IngredientPortion from each Instruction of every recipe. Such search feature provides a complex querying, rather than a superficial search on merely recipes' name.

Current Implementation

The following recipe class diagram shows the data structure of Recipe to support searching:

recipe class diagram

Other than the basic Recipe details accessible via the getters(), nested attribute such as IngredientPortion can post difficulty for the search function since Recipe and IngredientPortion is not associated initially. To allow direct access to all IngredientPortion of that recipe, a tabulateIngredients() method is added. This method stores all IngredientPortion in the HashMap of ingredients attribute by searching through all Instruction. As Recipe is immutable, the generating of the ingredients is done in the constructor of Recipe (i.e. tabulateIngredients() is called in the constructor). This allow the direct accessing all attributes required for the search to completed.

To implement this feature, recipe Model is implemented with FilteredList. This list takes in a predicate to filter away recipes that does not meet the requirement.

The desired search keywords, once parsed as List of String will be turned to Stream. Each keyword is checked against a recipe Name for any matching word, Difficult for exact matching value, Tag for any matching word, and Ingredient for any matching IngredientDefinition. All keywords must return true (i.e. matching at least one attribute of the recipe) before the recipe is deemed fit to be kept in the FilteredList.

The following code snippet shows how the predict determines each recipe:

public boolean test(Recipe recipe) {
    if (keywords.size() == 0) {
        return false;
    }
    return (keywords.stream()
            .allMatch(keyword -> {
                return StringUtil.containsWordIgnoreCase(recipe.getName().fullName, keyword)
                        || recipe.getCookTime().toString().toLowerCase().equalsIgnoreCase(keyword.toLowerCase())
                        || recipe.getDifficulty().toString().equals(keyword)
                        || recipe.getTags().stream()
                        .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword))
                        || recipe.getIngredients().keySet().stream().anyMatch(ingredientDefinition -> StringUtil
                        .containsWordIgnoreCase(ingredientDefinition.toString(), keyword));
            }));
}

The following sequence diagram shows how the keywords are being parsed to feed into the predicate:

recipe search seq
Why it is implemented that way:

Due to the existing data structure implemented by model, to access the attributes within a recipe, the system needs to use its getter and perform matching with its keywords. Hence, a filter via predicate testing is deemed fit.

Alternatives:
  • Tag saving recipes that uses it.

    • Pros - Search done more effectively as the system only need to display all recipes stored in a tag

    • Cons - Bi-directional association, increase difficulty in maintaining data integrity

  • Saving IngredientPortion directly in recipe instead of individual instruction.

    • Pros - Remove the need to search through the initial every instruction to gather the ingredients for every new instance of recipe. Speed up the recipe add and read process.

    • Cons - Since ingredients are aggregated from instruction, by allowing IngredientPortion to be a direct attribute of recipe imply a possibility of mismatch between the ingredients in the instruction and the ingredients in the recipe. (e.g. User entering ingredients that are not used by any instruction)