Maven Bookshelf challenge

Een app gebaseerd op data van Goodreads, boeken en reviews, waarmee mensen boeken kunnen zoeken en nieuwe boeken kunnen ontdekken.

Over dit project

Bronnen:

  • Demo: https://py.cafe/app/marie-anne/maven-bookshelf
    (Beperkte dataset van 1980–1990 vanwege beperkte online diskruimte, ca. 400 boeken en bijbehorende reviews. Het laden kan enkele seconden duren. Populaire auteurs in de jaren ’80: Terry Pratchett en Sue Grafton.)
  • GitHub: https://github.com/tigi/maven-bookshelf
    (De code werkt ook met de originele dataset, mits de twee initiële .csv-bestanden in de hoofdmap worden geplaatst.)

Opdracht

Ontwerp een tool die gebruikers helpt bij het samenstellen van hun ideale zomerleeslijst op basis van Goodreads-gegevens.

Basisidee

Een one-page webapplicatie waarin gebruikers enkele trefwoorden invoeren om een best passende boekenselectie te krijgen. Best passend heeft hierin twee betekenissen:

  1. Zoeken op b.v. auteur of titel levert een concreet resultaat op.
  2. Zoeken op 1 of meerdere trefwoorden levert resultaten die het meest relevant zijn voor die combinatie van trefwoorden. Hiervoor wordt AI-NLP ingezet.

Daarnaast was mijn idee dat het weinig zin heeft om te zoeken in een database met boeken als je er niet iets mee kunt. Daarom een download.

Het toepassen van NLP betekent dat je met behulp van AI een bepaald type model genereert dat de meest relevante resultaten teruggeeft op basis van enkele trefwoorden. Ik heb AI gebruikt om me een aanpak te geven die voor deze usecase het meest van toepassing was.

De suggestie:

Optie A – TF-IDF (snel en eenvoudig)

pythonCopyEditfrom sklearn.feature_extraction.text import TfidfVectorizer  
vectorizer = TfidfVectorizer(stop_words="english")  
tfidf_matrix = vectorizer.fit_transform(books_df["full_text"])

Optie B – Sentence Embeddings (betere semantiek)

pythonCopyEditfrom sentence_transformers import SentenceTransformer  
model = SentenceTransformer('all-MiniLM-L6-v2')  
embeddings = model.encode(books_df["full_text"], show_progress_bar=True)

Aanpak

Ik bouwde een eenvoudige testapp. Optie A bleek snel en effectief, optie B was te zwaar voor mijn laptop (voor de volledige dataset met alleen al 300Mb aan reviews). Bovendien waren de zoekresultaten met optie A beter. Dus koos ik voor A.

Gebruikte velden voor input (toen en nu) om een relevantie matrix samen te stellen:

pythonCopyEditbooks_df["text"] = (
    (books_df["original_title_lower"] + " ") +
    (books_df["genres_lower"] + " ") * 2 +
    books_df["description_lower"] + " " +
    (books_df["author_lower"] + " ") * 4 +
    books_df["review_text_lower"]
)

De kolom review_text_lower bevat een samengevoegde string van alle reviews per boek. De prioriteit per veld zou eigenlijk nu verwijderd kunnen worden aangezien ik later besloot om zoeken op auteur of titel een hogere prioriteit te geven dan zoeken op een willekeurige set van trefwoorden.

Lay-out en Design

De app volgt een standaard patroon dat vaak bij dit soort toepassingen wordt gebruikt. Niets bijzonders, gewoon functioneel. De app is responsief; voor kleine schermen zijn nog wat verbeteringen nodig (bijvoorbeeld het verbergen van bepaalde elementen via display: none). Op py.cafe is de titel aangepast vanwege de beperkte dataset. Die past dus niet helemaal meer :-).

De styling is gebaseerd op Dash Bootstrap Components en het Vizro-thema, met minimale aangepaste CSS. De gebruiker kan wisselen tussen licht- en donkermodus.

Bug: Op mobiele apparaten blijft het toetsenbord zichtbaar na invoer via Enter. Door boven het toetsenbord te tikken verdwijnt het alsnog. Misschien los ik dit binnenkort nog op, eigenlijk zou dit gedrag van de app standaard niet mogen voorkomen.

Waarom de genre-filter behouden?

Tijdens het testen bleek dat het selecteren van een genre verrassende en inspirerende resultaten oplevert. Hoewel het filter op kleine schermen uitgeschakeld zou kunnen worden (de app valt dan terug op de optie “alles”), biedt het voldoende meerwaarde om te behouden.

Waarom een CSV-download?

Online boeken kopen via een link is eenvoudig te realiseren, maar ik raad aan om lokale boekhandels of bibliotheken te steunen. De .csv-download maakt het eenvoudig om boeken te markeren als favoriet en een persoonlijke leeslijst op te slaan.

Een e-mailoptie overwoog ik kort, maar het is waarschijnlijk, gezien de plaats waar de demo is gehost, dat een mail door een spamfilter zou worden tegen gehouden.

Overwegingen: snelheid, middelen, kwaliteit en NLP

  • Toepassing van NLP vertraagt het zoekproces aanzienlijk en de op keyword gebaseerde matrix geeft niet altijd terug wat voor de hand ligt. Bijvoorbeeld: “Lee Child” (bekende thrillerauteur) verscheen niet in de top 20 zoekresultaten, wat leidde tot de beslissing om directe zoekacties op auteur en titel (via kolomwaarden) prioriteit te geven boven NLP.
  • Bij titelzoekacties is dit lastiger. Bijvoorbeeld: een NLP-zoekopdracht naar “Venetië” levert “Tod in Venedig” van Thomas Mann hoog in de lijst op (door de Duitse titel). Een directe titelzoekactie onderdrukt dat resultaat. Deze afwegingen zijn onderdeel van de huidige strategie.

Over reviews

De reviewteksten worden meegenomen in TF-IDF, maar krijgen in de interface weinig aandacht. Persoonlijk lees ik reviews alleen als een boek opvallend lage of hoge scores krijgt terwijl ik er heel anders over denk. Een gemiddelde beoordeling geeft vaak voldoende informatie. Ook algoritmische aanbevelingen (“u las/keek dit, dus misschien vindt u…”) blijken vaak onnauwkeurig of commercieel gestuurd.

Ontwikkelproces

ChatGPT hielp me op gang; Claude Opus 4 (via Openrouter.ai) nam het meeste tijdrovende werk voor zijn rekening. Totale kosten: ca. $8.

Ik werkte iteratief: per stap beschreef ik gewenste functionaliteit, voegde de code toe (na het opslaan van een vorige versie), en testte grondig. Bij lay-outwijzigingen leverde ik af en toe de volledige code opnieuw aan. Slechts twee keer vroeg ik om volledige gegenereerde code (kostbaarste deel).

Totale tijdsbesteding vanaf eerste idee tot finale versie: circa 16 uur.

Gebruikte tools

  • Dash Bootstrap Components + Vizro-thema
  • ChatGPT (gratis)
  • Claude Opus 4 (betaald via Openrouter.ai)
  • Spider
  • Logo-icoon via The Noun Project (met aangepaste kleuren)

Links

Resultaten