Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query ingredients directly in database #24

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from cookit_frontend.image import resize_image, pil_to_buffer
from cookit_frontend.page_elements import *
from cookit_frontend.utils import INGREDIENTS, LEVELS
from cookit_frontend.query import find_recipes_in_db
from cookit_frontend.query import find_recipes_in_db, open_db_connection


page_decorators()
local_css("style.css")
Expand All @@ -13,8 +14,8 @@

uploaded_file = page_pic_uploader()

#Option to upload jpg/ png image that will be used from the model

# establish db connection and keep it open in the cache
db_conn = open_db_connection()

if uploaded_file:
resized_file = resize_image(uploaded_file)
Expand All @@ -23,9 +24,11 @@
with col1:
st.write("")
with col2:
info = st.info("Your photo is being processed - this may take a few seconds")
gif_runner = st.image("frontend_img/giphy.gif")
ingredients, scores, bboxes = get_predictions(pil_to_buffer(resized_file))
gif_runner.empty()
info.empty()
with col3:
st.write("")

Expand All @@ -50,10 +53,7 @@
#difficulty = st.selectbox("How difficult should your recipe be?", LEVELS)

if st.button('get recipes'):
#recipes = get_recipes(f"{ingredients_selected_formatted}, {must_haves_formatted}",
# exclusions, cuisines_formatted, diet)

recipes = find_recipes_in_db(ingredients_selected + must_haves, exclusions)
recipes = find_recipes_in_db(db_conn, ingredients_selected + must_haves, exclusions)

if len(recipes) > 0:
show_recipes(recipes, 3)
Expand Down
119 changes: 58 additions & 61 deletions cookit_frontend/query.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,88 @@
import psycopg2
import psycopg2.extras
from psycopg2.extensions import connection
import os
import pandas as pd
import streamlit as st
from cookit_frontend.utils import BASICS


## Schema of DB
# CREATE TABLE COOKIT_RECIPES
# (ID INT PRIMARY KEY NOT NULL,
# TITLE TEXT NOT NULL,
# DIFFICULTY TEXT,
# PREPTIME INT,
# NUMBER_OF_INGREDIENTS INT,
# INGREDIENTS TEXT[] NOT NULL,
# CUISINE TEXT NOT NULL,
# CALORIES TEXT,
# LINK TEXT NOT NULL,
# PICTURE_URL TEXT NOT NULL,
# INSTRUCTIONS TEXT);

def import_data():
print("IMPORT DATA FROM DB")
# Connection to database and return an DataFrame called recipes

try:
DB_PASSWORD = os.environ['DB_PASSWORD']
DB_USER = os.environ['DB_USER']
DB_HOST = os.environ['DB_HOST']
except:
print("ERROR: Couldn't find all requested env variables for DB connection!")



# Caches the connection and avoids a re-connection every time the user changes something
# Seen in https://discuss.streamlit.io/t/prediction-analysis-and-creating-a-database/3504
@st.cache(hash_funcs={connection: id}, show_spinner=False)
def open_db_connection():
conn = psycopg2.connect(database="d1hsr1c7nk56dl",
user="iadkkqrgljveni",
host="ec2-3-230-61-252.compute-1.amazonaws.com",
user=DB_USER,
host=DB_HOST,
port="5432",
password=DB_PASSWORD)
print("Connection established")
return conn

query = """
SELECT *
FROM cookit_recipes
"""
recipes = pd.read_sql(query, conn)
print("DONE WITH IMPORTING DATA FROM DB")

return recipes


def processing_recipes():
# Add "ingredients_joined" to recipes df in order to query the recipes
recipes = import_data()

ingredients_joined = []
for row in recipes.ingredients:
ingredients_joined.append(" ".join(row))

recipes["ingredients_joined"] = ingredients_joined
recipes["ingredients_joined"] = recipes["ingredients_joined"].apply(str.lower)

return recipes
def query_recipes(conn, include_ingredients, exclude_ingredients):
# Add wildcard operators to list elements
include_ingredients = [f"%{x}%" for x in include_ingredients]
exclude_ingredients = [f"%{x}%" for x in exclude_ingredients]

# Storing the database here in a global variable is not very elegant, but workaround for the moment.
recipes = processing_recipes()
# RealDictCursor enables us to access the fetched rows in dict style like recipes['ingredients']
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)

def query_recipes(params_dict):
# query the database and return a list of series (containing the information regarding the recipes)
global recipes

queried_results_list = []
# TODO: There should be a faster way to get the results from the dataframe
for index, row in recipes.iterrows():
include_condition = all(x in recipes.ingredients_joined[index] for x in params_dict["includeIngredients"])
exclude_condition = any(x in recipes.ingredients_joined[index] for x in params_dict["excludeIngredients"])
#difficulty_condition = recipes.difficulty[index] == params_dict["difficulty"]

#if include_condition and not exclude_condition and difficulty_condition:
if include_condition and not exclude_condition:
queried_results_list.append(row)

return queried_results_list
# the ILIKE makes sure we are searching case-insensitive
# there may be other possibilities, but making a string of the array and search for all given
# ingredients is the most straightforward approach so far
query = """
SELECT ID, TITLE, PREPTIME, INGREDIENTS, CUISINE, LINK, PICTURE_URL, DIFFICULTY
FROM cookit_recipes
WHERE ARRAY_TO_STRING(cookit_recipes.INGREDIENTS, ',') ILIKE ALL (%(include_ingredients)s)
AND NOT
ARRAY_TO_STRING(cookit_recipes.INGREDIENTS, ',') ILIKE ANY (%(exclude_ingredients)s)
"""
cur.execute(query, {'include_ingredients': include_ingredients,
'exclude_ingredients': exclude_ingredients})
return cur.fetchall()


def get_missing_ingredients(recipe_ingredients, ingredients):
available_list = []
for ingr in ingredients + BASICS:
available_list += [x for x in recipe_ingredients if ingr.lower() in x.lower()]
available_list += [
x for x in recipe_ingredients if ingr.lower() in x.lower()
]
missing = list(set(recipe_ingredients) - set(available_list))

return missing


def find_recipes_in_db(ingredients, exclusions):
params_dict = {
"includeIngredients": [i.lower() for i in ingredients],
"excludeIngredients": [i.lower() for i in exclusions],
#"difficulty": difficulty.lower()
}
# Return a list of dictionaries containing information regarding recipes
queried_results_list = query_recipes(params_dict)
def find_recipes_in_db(conn, ingredients, exclusions):
""" Return a list of dictionaries containing information regarding recipes """
queried_results_list = query_recipes(conn, ingredients, exclusions)

formated_dict_list = []

for recipe in queried_results_list:
missing_ingredients = get_missing_ingredients(recipe['ingredients'], params_dict['includeIngredients'])
missing_ingredients = get_missing_ingredients(recipe['ingredients'], ingredients)
formated_dict = {
"image": recipe["picture_url"],
"sourceUrl": recipe["link"],
Expand All @@ -91,14 +92,10 @@ def find_recipes_in_db(ingredients, exclusions):
"missedIngredients": missing_ingredients,
"cuisine": recipe["cuisine"],
"difficulty": recipe["difficulty"],
"instructions": recipe["instructions"],
"calories": recipe["calories"],
"ingredients": recipe["ingredients"]
}

formated_dict_list.append(formated_dict)

# sort by missing ingredients ascending
resorted_list = sorted(formated_dict_list, key=lambda k: k['missedIngredientCount'])

return resorted_list