Skip to content

Commit

Permalink
Merge pull request #93 from jacobp24/sue_13
Browse files Browse the repository at this point in the history
Sue 13
  • Loading branch information
jacobp24 authored Mar 11, 2024
2 parents 74c8792 + 8c5cf2f commit 0817992
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 157 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ jobs:
# If there are any other steps in order for your tests to
# run successfully, you can add those steps here!

# Next step: run pylint. Anything less than 10/10 will fail.
#- name: Lint with pylint
# run: |
# pylint bookworm/**/*.py
# # Next step: run pylint. Anything less than 10/10 will fail.
# - name: Lint with pylint
# run: |
# pylint bookworm/*.py
# pylint bookworm/**/*.py


# Next step: run the unit tests with code coverage.
- name: Unit tests
Expand Down
2 changes: 1 addition & 1 deletion bookworm/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# EMPTY FILE
# EMPTY FILE
123 changes: 94 additions & 29 deletions bookworm/app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
import base64
import streamlit as st
import streamlit.components.v1 as components
"""
UI Module for Bookworm App
FUNCTIONS
========
display_avg_ratings_slider():
Displays average ratings slider; returns input.
display_num_ratings_slider():
Displays slider for number of ratings; returns input.
display_search_mode_ui():
Displays text box for user to enter desired search method; returns input.
display_search_value_ui(search_mode):
Displays text box for user to enter query to search; retrieves user input.
display_genre_dropdown():
Displays drop down menu for genre selection; retrieves value; returns input.
main()
Displays UI; gathers user feedback; executes serach.
"""


import base64
import streamlit as st
#import streamlit.components.v1 as components
try:
from search_wrapper import search_wrapper
except ImportError:
Expand Down Expand Up @@ -49,18 +73,38 @@ def local_css(file_name):

# DEFINE EACH UI ELEMENT AS A SEPARATE FUNCTION
def display_avg_ratings_slider():
"""
Displays average ratings slider, values 0 to 10. Returns user input.
Return value
User's selected average ratings, as a float.
"""
return st.slider("Exclude Books with Average Ratings Lower than:",
min_value=0.0, max_value=10.0,
value=0.0, step=0.5, key="Ave. Ratings Slider",
help="Set the minimum average rating.")

def display_num_ratings_slider():
"""
Displays slider for number of ratings; returns user input.
Return value
User's selected number of ratings, as an int.
"""
return st.slider("Exclude Books that have been rated by fewer than:",
min_value=0, max_value=50,
value=0, step=1, key="Num. Ratings Slider",
help="Set the minimum number of ratings.")

def display_search_mode_ui():
"""
Display text box for user to enter desired search method; returns input.
Return value
User's search mode selection, as a string.
"""

help_text_string = "Choose how you want to prioritize your search."
selection = st.selectbox("Search Mode", [None] + SEARCH_MODES,
help=help_text_string, key="search_mode")
Expand All @@ -74,37 +118,52 @@ def display_search_mode_ui():
return search_mode

def display_search_value_ui(search_mode):
""" Display text box for user to enter query to search; returns input.
Return value
User's search query, as a string.
"""

if search_mode == "Genre":
return display_genre_dropdown()
else:
display_str = search_mode
if display_str in ["Author1", "Author2"]:
display_str = display_str[:-1]
return st.text_input(f"Input your favorite {display_str}",
display_str = search_mode
if display_str in ["Author1", "Author2"]:
display_str = display_str[:-1]
return st.text_input(f"Input your favorite {display_str}",
key="search_val")

def display_genre_dropdown():
"""
Displays drop down menu for genre selection; returns user input.
Return value
User's genre selection as a string.
"""
help_text_string = "Select your favorite genre."
genre_pick = st.selectbox("Favorite Genre", [None] + GENRES,
help=help_text_string)
if genre_pick == "Other":
return st.text_input("Describe your favorite genre", key="other_genre")
else:
return genre_pick

def display_search_button():
return genre_pick

def display_search_button(disabled=False):
"""
Displays button to initate search.
Return value
Boolean indicating whether user has clicked search.
"""
return st.button("Search Now", key="search_now",
help="Click to initiate search.", type="primary",
disabled=False, use_container_width=False)
disabled=disabled, use_container_width=False)

def execute_query(search_mode, search_value, min_ave_rating, min_num_ratings):
st.write(f"Searching for books using {search_mode}, value: {search_value}, "
f"min average rating: {min_ave_rating}, "
f"min number of ratings: {min_num_ratings}")

def main():
# Display header banner with stock image of books
"""
Displays UI; gathers user feedback; executes serach.
"""

# Display header banner with stock image of books
st.image("images/books_banner.png", use_column_width=True)

title_image = "images/butterfly.png"
Expand All @@ -113,7 +172,8 @@ def main():
st.markdown(
f"""
<div class="container">
<img class="title-img" src="data:image/png;base64,{base64.b64encode(open(title_image, "rb").read()).decode()}">
<img class="title-img" src="data:image/png;base64,
{base64.b64encode(open(title_image, "rb").read()).decode()}">
<p class="title-text">The Bookish Butterfly</p>
</div>
""",
Expand All @@ -122,7 +182,7 @@ def main():


# Add introductory text
st.write("Welcome to The Bookish Butterfly! An literary guide designed for"
st.write("Welcome to The Bookish Butterfly! A literary guide designed for"
"bookworms, aiding in the exploration of new books with tailored"
"preferences. Simply choose how you want to prioritize your"
"search, input your favorite book, author, plot, or genre. "
Expand All @@ -138,16 +198,21 @@ def main():
min_ave_rating = display_avg_ratings_slider()
min_num_ratings = display_num_ratings_slider()

search_button = display_search_button()


if search_val not in ["", None]:
st.write("Click 'Search Now' when ready.")
if search_button:
try:
results = search_wrapper(search_mode, search_val, min_ave_rating, min_num_ratings)
col_to_show = ["book_title", "author", "Book-Rating", "RatingCount"]
st.write(results[col_to_show])
except ValueError as e:
st.write(f"{str(e)}")
search_button = display_search_button()
if not search_button:
st.write("Click 'Search Now' when ready.")
else:
with st.spinner('Searching...'):
try:
results = search_wrapper(search_mode, search_val,
min_ave_rating, min_num_ratings)
col_to_show = ["book_title", "author", "Book-Rating",
"RatingCount"]
st.write(results[col_to_show])
except ValueError as e:
st.write(f"{str(e)}")

main()
31 changes: 31 additions & 0 deletions bookworm/data/test_data/test_genre.csv

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions bookworm/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"""

import os

import ast
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
Expand Down Expand Up @@ -151,7 +150,7 @@ def query_to_index(df, query, columns, vectorizer=None, num_idx=1):
"""
Maps query to the closest book index via keyword search.
Maps query based to the closes book in dataframe (df)
Maps query based to the closest book in dataframe (df)
based on keyword search using given vectorizer(default
is TfidfVectorizer). Then returns the index of that book
in the dataframe.
Expand Down
43 changes: 22 additions & 21 deletions bookworm/search_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
filter_ratings(results, min_ave_ratings, min_num_rating)
Filters serach results by user ratings prefrences.
select_search(df, search_mode, search_value, min_ave_rating,
select_search(search_mode, search_value, min_ave_rating,
min_num_ratings, num_book=10)
Selects and implements search based on user search mode.
Expand Down Expand Up @@ -53,6 +53,19 @@ def assemble_data(path1, path2, path3, path4):
df = pd.concat([df1, df2, df3, df4], ignore_index=True)
return df

def assemble_embeddings_data():
"""
Functions that assembles the data with embeddings
"""
# Create file paths

path_root = "data/complete_w_embeddings/complete_w_embeddings.csv"
path1 = path_root + "_part_1.csv"
path2 = path_root + "_part_2.csv"
path3 = path_root + "_part_3.csv"
path4 = path_root + "_part_4.csv"
return assemble_data(path1, path2, path3, path4)

# Filter
def filter_ratings(results, min_ave_ratings, min_num_rating):

Expand Down Expand Up @@ -87,13 +100,11 @@ def filter_ratings(results, min_ave_ratings, min_num_rating):
results_filtered = subset_df
return results_filtered

def select_search(df, search_mode, search_value, num_books=10):
def select_search(search_mode, search_value, num_books=10):
"""
Selects and implements search based on user search mode.
Parameters:
df: pandas.DataFrame
The dataframe to perform the search on.
search_mode: str
The mode of search ('Author2', 'Title', 'Plot', 'Genre').
search_value: str
Expand All @@ -103,16 +114,19 @@ def select_search(df, search_mode, search_value, num_books=10):
Returns:
pandas.DataFrame
A dataframe of filtered search results.
A dataframe of search results.
"""

if search_mode == "Author2":
df = pd.read_csv("data/complete_w_ratings.csv")
results = search.author2_search(df, search_value,
num_books=max(num_books * 2, 20))
elif search_mode == "Title":
df = assemble_embeddings_data()
results = search.semantic_search(df, search_value, ["book_title"],
num_books=max(num_books * 2, 20))
elif search_mode == "Plot":
df = assemble_embeddings_data()
results = search.plot_semantic_search(df, search_value,
num_books=max(num_books * 2, 20))
elif search_mode == "Genre":
Expand All @@ -122,7 +136,9 @@ def select_search(df, search_mode, search_value, num_books=10):
genre_df = pd.read_csv("data/genre.csv")
results = search.genre_search(genre_df, search_value,
num_books=max(num_books * 2, 20))

else: # Default to keyword search on all columns for other modes
df = pd.read_csv("data/complete_w_ratings.csv")
results = search.keyword_search(df, search_value,
num_books=max(num_books * 2, 20))

Expand All @@ -145,24 +161,9 @@ def search_wrapper(search_mode, search_value, min_ave_rating,
Returns
A dataframe of filtered search results.
"""
# assemble data
try:
path_root = "bookworm/data/complete_w_embeddings/complete_w_embeddings.csv"
path1 = path_root + "_part_1.csv"
path2 = path_root + "_part_2.csv"
path3 = path_root + "_part_3.csv"
path4 = path_root + "_part_4.csv"
df = assemble_data(path1, path2, path3, path4)
except FileNotFoundError:
path_root = "data/complete_w_embeddings/complete_w_embeddings.csv"
path1 = path_root + "_part_1.csv"
path2 = path_root + "_part_2.csv"
path3 = path_root + "_part_3.csv"
path4 = path_root + "_part_4.csv"
df = assemble_data(path1, path2, path3, path4)

# search
results = select_search(df, search_mode, search_value, num_books)
results = select_search(search_mode, search_value, num_books)

#filter
results_filtered = filter_ratings(results, min_ave_rating, min_num_ratings)
Expand Down
28 changes: 17 additions & 11 deletions bookworm/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@

import pandas as pd


from app import main, display_avg_ratings_slider, display_num_ratings_slider, \
display_search_mode_ui, display_search_value_ui, \
display_genre_dropdown, display_search_button, execute_query
try:
from app import (main, display_avg_ratings_slider,
display_num_ratings_slider, display_search_mode_ui,
display_search_value_ui, display_genre_dropdown,
display_search_button)
except ImportError:
from bookworm.app import (main, display_avg_ratings_slider,
display_num_ratings_slider, display_search_mode_ui,
display_search_value_ui, display_genre_dropdown,
display_search_button)


class TestStreamlitUI(unittest.TestCase):
Expand Down Expand Up @@ -103,13 +109,13 @@ def test_display_search_button(self):
result = display_search_button()
self.assertTrue(result)

def test_execute_query(self):
"""Test execute_query function."""
with patch('streamlit.write') as mock_write:
execute_query("Title", "The Great Gatsby", 8.0, 20)
mock_write.assert_called_once_with(
"Searching for books using Title, value: The Great Gatsby, "
"min average rating: 8.0, min number of ratings: 20")
# def test_execute_query(self):
# """Test execute_query function."""
# with patch('streamlit.write') as mock_write:
# execute_query("Title", "The Great Gatsby", 8.0, 20)
# mock_write.assert_called_once_with(
# "Searching for books using Title, value: The Great Gatsby, "
# "min average rating: 8.0, min number of ratings: 20")


if __name__ == '__main__':
Expand Down
Loading

0 comments on commit 0817992

Please sign in to comment.