In diesem Abschnitt werde ich einige Implementierungsdetails von Baker teilen. Da es sich wieder um Open Supply handelt, lade ich meine technischen Leser ein, den Code auf GitHub zu überprüfen. Einige Leser möchten vielleicht zu dem springen nächsten Abschnitt.
Die Anwendung ist minimalistisch mit einer einfachen 3-Ebenen-Architektur und quick vollständig in Python erstellt.
Es besteht aus folgenden Komponenten:
- Frontend: A Streamlit Die Benutzeroberfläche bietet Benutzern eine intuitive Plattform, um mit dem System zu interagieren, Rezepte abzufragen und Empfehlungen zu erhalten.
- Backend: Gebaut mit FastAPIDas Backend dient als Schnittstelle zur Bearbeitung von Benutzeranfragen und zur Bereitstellung von Empfehlungen.
- Motor: Die Engine enthält die Kernlogik zum Suchen und Filtern von Rezepten sowie deren Nutzung Mischling als Abfrage-Builder.
- Datenbank: Die Rezepte werden in einem gespeichert MongoDB Datenbank, die die von der Engine generierten Aggregationspipelines verarbeitet.
Backend-Setup
Das Backend wird in initialisiert app.py
wo FastAPI-Endpunkte definiert sind. Zum Beispiel:
from fastapi import FastAPI
from baker.engine.core import find_recipes
from baker.fashions.ingredient import Ingredientapp = FastAPI()
@app.get("/")
def welcome():
return {"message": "Welcome to the Baker API!"}
@app.put up("/recipes")
def _find_recipes(elements: listing(Ingredient), serving_size: int = 1) -> listing(dict):
return find_recipes(elements, serving_size)
Der /recipes
Der Endpunkt akzeptiert eine Zutatenliste und eine Portionsgröße und delegiert dann die Verarbeitung an die Engine.
Rezept-Engine-Logik
Das Herzstück der Anwendung liegt in core.py
innerhalb der engine
Verzeichnis. Es verwaltet Datenbankverbindungen und Abfragepipelines. Unten finden Sie ein Beispiel dafür find_recipes
Funktion:
# Imports and the get_recipes_collection operate will not be includeddef find_recipes(elements, serving_size=1):
# Get the recipes assortment
recipes = get_recipes_collection()
# Create the pipeline
pipeline = Pipeline()
pipeline = include_normalization_steps(pipeline, serving_size)
question = generate_match_query(elements, serving_size)
print(question)
pipeline.match(question=question).challenge(
embody=(
"id",
"title",
"preparation_time",
"cooking_time",
"original_serving_size",
"serving_size",
"elements",
"steps",
),
exclude="_id",
)
# Discover the recipes
end result = recipes.mixture(pipeline.export()).to_list(size=None)
return end result
def generate_match_query(elements: listing(Ingredient), serving_size: int = 1) -> dict:
"""Generate the match question."""
operands = ()
for ingredient in elements:
operand = {
"elements.title": ingredient.title,
"elements.unit": ingredient.unit,
"elements.amount": {"$gte": ingredient.amount / serving_size},
}
operands.append(operand)
question = {"$and": operands}
return question
def include_normalization_steps(pipeline: Pipeline, serving_size: int = 1):
"""Provides steps in a pipeline to normalize the elements amount within the db
The steps beneath normalize the portions of the elements within the recipes within the DB by the recipe serving measurement.
"""
# Unwind the elements
pipeline.unwind(path="$elements")
pipeline.add_fields({"original_serving_size": "$serving_size"})
# Add the normalized amount
pipeline.add_fields(
{
# "orignal_serving_size": "$serving_size",
"serving_size": serving_size,
"elements.amount": S.multiply(
S.subject("elements.amount"),
S.divide(serving_size, S.max((S.subject("serving_size"), 1))),
),
}
)
# Group the outcomes
pipeline.group(
by="_id",
question={
"id": {"$first": "$id"},
"title": {"$first": "$title"},
"original_serving_size": {"$first": "$original_serving_size"},
"serving_size": {"$first": "$serving_size"},
"preparation_time": {"$first": "$preparation_time"},
"cooking_time": {"$first": "$cooking_time"},
# "directions_source_text": {"$first": "$directions_source_text"},
"elements": {"$addToSet": "$elements"},
"steps": {"$first": "$steps"},
},
)
return pipeline
Die Kernlogik von Bäcker wohnt in der find_recipes
Funktion.
Diese Funktion erstellt dank monggregate eine MongoDB-Aggregationspipeline. Diese Aggregationspipeline umfasst mehrere Schritte.
Die ersten Schritte werden von der generiert include_normalization_steps
Funktion, die die Mengen der Zutaten in der Datenbank dynamisch aktualisiert, um sicherzustellen, dass wir Äpfel mit Äpfeln vergleichen. Dies erfolgt durch die Aktualisierung der Zutatenmengen in der Datenbank auf die vom Benutzer gewünschte Portion.
Anschließend wird die eigentliche Matching-Logik erstellt generate_match_query
Funktion. Dabei stellen wir sicher, dass die Rezepte nicht mehr erfordern, als der Benutzer für die betreffenden Zutaten zur Verfügung hat.
Schließlich filtert eine Projektion die Felder heraus, die wir nicht zurückgeben müssen.
Bäcker hilft Ihnen, ein besseres Schicksal für Ihre Zutaten zu finden, indem es Rezepte findet, die zu dem passen, was Sie bereits zu Hause haben.
Die App verfügt über eine einfache formularbasierte Oberfläche. Geben Sie Ihre Zutaten ein, geben Sie deren Mengen an und wählen Sie aus den verfügbaren Optionen die Maßeinheit aus.
Im obigen Beispiel suche ich nach einem Rezept für zwei Portionen um 4 Tomaten und 2 Karotten zu verwerten, die etwas zu lange in meiner Küche gestanden haben.
Bäcker habe zwei Rezepte gefunden! Wenn Sie auf ein Rezept klicken, können Sie die vollständigen Particulars anzeigen.
Bäcker Passt die Mengen im Rezept an die von Ihnen eingestellte Portionsgröße an. Wenn Sie beispielsweise die Portionsgröße von zwei auf vier Personen anpassen, berechnet die App die Zutatenmengen entsprechend neu.
Durch die Aktualisierung der Portionsgröße können sich auch die angezeigten Rezepte ändern. Bäcker stellt sicher, dass die vorgeschlagenen Rezepte nicht nur zur Portionsgröße, sondern auch zu den Zutaten und Mengen passen, die Sie zur Hand haben. Wenn Sie beispielsweise nur 4 Tomaten und 2 Karotten für zwei Personen haben, Bäcker Ich werde es vermeiden, Rezepte zu empfehlen, die 4 Tomaten und 4 Karotten erfordern.