Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents
minLevel1
maxLevel2
outlinefalse
typelist
printablefalse

Including ingredients, meal types, cuisines, dietary restrictions, short recipes, easy recipes.

...

Recipe Filtering

We need to refine our logic now for retrieving only those recipes that satisfy specific filters. We already introduced a recipeFiltered/3 predicate for Request a Recommendation but there it did not have to do any serious work yet. We only introduced the base case without any filters (an empty list of filters) but now need to deal with the case where we have some filters that a user provided. The basic idea to define a recursive clause for recipeFiltered/3 is simple: apply the first filter in the list to the recipes and recursively filter the remaining recipes using the remaining filters. We provide you below with the recursive clause that you should add below the base clause that we added earlier in the recipe_selection.pl file:

...

Inspect this rule carefully and make sure that you understand what it is doing.

Applying filters: Ingredient,meal type, and cuisine

After you add the recursive clause for recipesFiltered/3 above, you will see that Eclipse will start to complain that the applyFilter/4 predicate has not been defined. We have reduced the problem of filtering recipes to the problem of applying a single filter to a set of (remaining) recipes. The idea is that the applyFilter(+ParamName, +Value, +RecipeIDs, -FilteredRecipes) filters the RecipeIDs provided as input by means of the feature ParamName using a specific Value and returns the recipes that satisfy this feature in the output argument FilteredRecipes. The parameter name ParamName refers to a feature that a recipe should have, for example, it should be of a certain cuisine. The specific value Value of the recipe feature specifies what exact feature request is made, for example, the cuisine should be Chinese. For each feature, we need to write a separate applyFilter/4 rule. For now, we need to provide a rule for the ‘ingredient' feature, the 'mealType' feature, and the 'cuisine' feature. This information is found in recipe_database.pl.

If we want to check that a recipe uses an ingredient, we need to be able to check whether the ingredient is included in the ingredient list of that recipe. For ingredient types, things are slightly more complicated. To do both checks, we need a rule, hasIngredient(RecipeID, Ingr). The hasIngredient(RecipeID, Ingr) rule should succeed if the recipe with identifier RecipeID uses the ingredient(type) Ingr. The first Prolog rule for hasIngredient is designed to determine if a specific ingredient is used in a given recipe. It simply checks if Ingr is included in the ingredient list of recipe RecipeID by using the ingredient/2 predicate (see the recipe_database.pl file).

The bodies of the rules can be designed as exact copies of each other. You must find all (Hint for what Prolog built-in predicate you should use) recipes from the list that you start with that satisfy the filter and return these recipes in the output argument list. Add your rules to the recipe_selection.pl file at the location indicated in the file; there you will also find the heads of the rules that you need to define. The rule for filtering on cuisine is very similar too but instead of the hasIngredient/2 predicate you should use the cuisine/2 predicate (see the recipe_database.pl for examples).

...

Checking that a recipe uses an ingredient

If we want to check that a recipe uses an ingredient, we need to be able to check whether the ingredient is included in the ingredient list of that recipe. For ingredient types, things are slightly more complicated. To do both checks, we will add two rules for defining the hasIngredient(RecipeID, Ingr) predicate that succeed if the recipe with identifier RecipeID uses the ingredient (type) Ingr. Both rules need to be added to the ingredient_hiearchy.pl file (at a location indicated in that file).

The first Prolog rule for hasIngredient is designed to determine if a specific ingredient is used in a given recipe. It simply checks if Ingr is included in the ingredient list of recipe RecipeID by using the ingredient/2 predicate (see the recipe_database.pl file). The second rule is designed to determine if a recipe uses an ingredient type. It assumes that Ingr is a type of ingredient (such as meat). To check whether a recipe uses that ingredient type, however, we need to find a specific ingredient such as steak that is an example instance of the type (only those are directly associated with a recipe in the recipe database). Another example would be apple as an instance of the type fruit. We do not have to specify all of these relations as a lot of the work has already done for you and you can use the typeIngredient/2 predicate in the ingredient_hiearchy.pl file (check out the typeIngredient/2 facts in that file). Now use that predicate in combination with the ingredient/2 predicate to define the second rule. Add your rules to the ingredient_hierarchies.pl file.

Applying filters: Ingredient, meal type, and cuisine

After you add the recursive clause for recipesFiltered/3 above, you will see that Eclipse will start to complain that the applyFilter/4 predicate has not been defined. We have reduced the problem of filtering recipes to the problem of applying a single filter to a set of (remaining) recipes. The idea is that the applyFilter(+ParamName, +Value, +RecipeIDs, -FilteredRecipes) filters the RecipeIDs provided as input by means of the feature ParamName using a specific Value and returns the recipes that satisfy this feature in the output argument FilteredRecipes. The parameter name ParamName refers to a feature that a recipe should have, for example, it should be of a certain cuisine. The specific value Value of the recipe feature specifies what exact feature request is made, for example, the cuisine should be Chinese. For each feature, we need to write a separate applyFilter/4 rule. For now, we need to provide a rule for the ‘ingredient' feature (use the same name for the feature as the name of the corresponding parameter name used in the addFilter intent in your intent and slot classifier), and the 'cuisine' feature. The body of the first rule can be designed with the hasIngredient/2 predicate. You must find all (Hint for what Prolog built-in predicate you should use) recipes from the list that you start with that satisfy the filter and return these recipes in the output argument list. Add your rules to the recipe_selection.pl file at the location indicated in the file; there you will also find the heads of the rules that you need to define. The rule for filtering on cuisine is very similar too but instead of the hasIngredient/2 predicate you should use the cuisine/2 predicate (see the recipe_database.pl for examples). Lastly, the same can be done for mealType/2.

Applying filters: dietary restriction

When you inspect the recipe_database.pl file, you will see that there are also no facts in the database that suggest that a recipe is vegan or spicy, for example (two restrictions that are included in the dietaryRestrction entity in the Dialogflow agent). As for the ingredientType entity, we need to add some logic to enable the agent to conclude from the basic knowledge stored in the database about a recipe that is satisfies a dietary restriction. We will use the typeIngredient/2 predicate for this too, again.

Filtering on dietary restrictions

The basic idea to implement the logic for checking a dietary restriction is to check whether each ingredient of a recipe meets that dietary restriction. It is clear, for example, that a recipe that uses chicken, is not a vegetarian recipe. The reasoning is simple: Vegetarians do not eat any kind of meat, chicken is a kind of meat, so any recipe that uses chicken is not a vegetarian recipe. If we can retrieve this information about chicken somehow, then we can implement this logic for our agent too. When you inspect the ingredient_hierarchies.pl file, you will see that it includes facts that say exactly that. We find, for example, typeIngredient('chicken drumsticks', 'meat'). We can use these facts to conclude that various kinds of chicken and other ingredients are meat. There are no facts, however, to conclude that an ingredient fits in a vegetarian or vegan diet. Instead, you will find facts indicating that certain ingredients are non-vegetarian and non-vegan. Of course, assuming that these lists of facts are complete (we give no guarantees, you might want to check that), we can then also conclude that all ingredients that are not explicitly listed as non-vegetarian should be vegetarian. Let’s first add some rules that implement this logic so that the agent can conclude that an ingredient is vegetarian, too. In the ingredient_hierarchies.pl file, at the designated location, add, for example:

Code Block
typeIngredient(Ingredient, 'vegetarian') :-
	not(typeIngredient(Ingredient, 'non-vegetarian')), !.

Also add rules for the pescatarian and vegan dietary restriction. That enables our agent to handle at least three of the six dietary restrictions that we added to the dietaryRestriction entity of the Dialogflow agent. We will get back to the other three later. For now, you should assume that we can use the predicate typeIngredient/2 to check if an ingredient meets a dietary restriction. We want to implement a rule now that checks that all the ingredients in a list meet a dietary restriction, such as 'meat' or 'vegan'. You should implement an ingredientsMeetDiet/2 predicate in the recipe_selection.pl file that for any given list of ingredients checks that each one of these ingredients meets a dietary restriction. In other words, write code for the body of the recursive clause for the following rule:

Code Block
ingredientsMeetDiet([ Ingredient | Rest ], DietaryRestriction) :-

Of course, you will also need to add a base clause for this predicate with the empty list as first argument. Should this base clause succeed or fail for a dietary restriction?

Next, we introduce one more helper predicate to check that a recipe meets a dietary restriction. The idea is to find all the ingredients of a certain recipe with identifier RecipeID and feed the resulting list in the ingredientsMeetDiet/2 predicate that we just defined to check that all these ingredients satisfy a DietaryRestriction. In the recipe_selection.pl file, add code for the body of the following rule:

Code Block
diet(RecipeID, DietaryRestriction) :-

Finally, using the diet/2 predicate, we can define a rule for applying a dietary restriction filter. You are asked to define applyFilter('dietaryrestriction', Value, RecipeIDsIn, RecipeIDsOut), similar to how rules for other filters have been defined.

Extending the logic of ingredient hierarchies

As we mentioned above, there is still information missing to implement the logic for all dietary restrictions, and also for food categories (you were already asked to add the pasta food category, but what about noodles, for example?). For the dietary restrictions, you should add typeIngredient/2 facts that can facilitate the processing of filters for lactose-free (no dairy), gluten-free (no gluten), and spicy recipes. For all three, use a simple approach that only looks at the individual ingredients that are used in a recipe. That is, for a lactose-free recipe, no dairy products should be used; for a gluten-free recipe, no products with gluten should be used; and, for a spicy recipe, the recipe should have (at least) one ingredient that is spicy (the hasIngredient/2 predicate should come to mind here). As before, we can use one fact to deduce the other, and, deduce, for example, that if an ingredient has no gluten it must be gluten-free. We can define such relations using the same type of rule as we have seen above:

Code Block
typeIngredient(Ingredient, 'gluten-free') :-
	not(typeIngredient(Ingredient, 'gluten')), !.

This rule, however, still assumes that the typeIngredient(Ingredient, ‘gluten') facts are given somehow. We provide an example of what that should look like for the 'gluten' facts:

Expand
titletypeIngredient(Ingredient, 'gluten').

typeIngredient('barley squashes', 'gluten').
typeIngredient('beer', 'gluten').
typeIngredient('lager', 'gluten').
typeIngredient('stout', 'gluten').
typeIngredient('stout', 'gluten').
typeIngredient('barley', 'gluten').
typeIngredient('couscous', 'gluten').
typeIngredient('bulgar wheat', 'gluten').
typeIngredient('dinkel', 'gluten').
typeIngredient('einkorn', 'gluten').
typeIngredient('emmer wheat', 'gluten').
typeIngredient('farro', 'gluten').
typeIngredient('freekeh', 'gluten').
typeIngredient('khorasan wheat', 'gluten').
typeIngredient('pearl barley', 'gluten').
typeIngredient('rye', 'gluten').
typeIngredient('semolina', 'gluten').
typeIngredient('spelt', 'gluten').
typeIngredient('triticale', 'gluten').
typeIngredient('wheat', 'gluten').
typeIngredient('wheat flour', 'gluten').
typeIngredient('rye flour', 'gluten').
typeIngredient('barley flour', 'gluten').
typeIngredient('plain flour', 'gluten').
typeIngredient('self raising flour', 'gluten').
typeIngredient('flour', 'gluten').
typeIngredient('oats', 'gluten').
typeIngredient('oat milk', 'gluten').
typeIngredient('biscuits', 'gluten').
typeIngredient('bread', 'gluten').
typeIngredient('cake', 'gluten').
typeIngredient('chapattis', 'gluten').
typeIngredient('crackers', 'gluten').
typeIngredient('muffins', 'gluten').
typeIngredient('pastry', 'gluten').
typeIngredient('pizza', 'gluten').
typeIngredient('muesli', 'gluten').
typeIngredient('pasta', 'gluten').
typeIngredient('noodles', 'gluten').
typeIngredient('batter', 'gluten').
typeIngredient('soy sauce', 'gluten').
typeIngredient('liquorice', 'gluten').
typeIngredient('bread crumbs', 'gluten').

For the lactose-free and spicy features, you should add the relevant facts. We recommend using information that you can find online and tools to automate your approach to compiling these lists.

Even though some of the recipe features that we have looked at require careful analysis, those that require more work than simply inspecting basic facts in the recipe database were all based on reasoning about individual ingredients. That is, we verified whether an ingredient is of a particular type, either a type of food or a type of dietary restriction. If you like a challenge, you can also consider other recipe features that consider more global features of a recipe, such as a recipe being low-carb or cheap. How would you define the logic for such filters? What kind of information would you need to add to the database to be able to define features like these?

Dialogue Patterns

Requesting a recipe feature

...

For the featureRemovalRequest intent, you should add a simple text/2 fact. Use as response text, for example, "Can you have a look again and remove one of your recipe requirements?".

Visuals

We want to differentiate what we show to a user depending on the number of recipes that still meet the user’s requests. The basic idea is that we should not show a large number of recipes to a user but we can show recipe details when the number of remaining recipes becomes sufficiently small (we chose <16, see also above). As a consequence, we want to create two different versions of the a50recipeSelect page that we created for Request a Recommendation : one that just shows the feature requests made when there are more than 15 recipes that meet these requests, and another for when there are less that 16 which shows the recipe details (titles and pictures) for all of the remaining recipes.

...