Task 13: StyleAdapter with 5 cuisine templates (Italian, Latin, East Asian,
Eastern European, Mediterranean). Each template includes weighted method_bias
(sums to 1.0), element-filtered aromatics/depth/structure helpers, and
seasoning/finishing-fat vectors. StyleTemplate is a fully immutable frozen
dataclass with tuple fields.
Task 14: LLMRecipeGenerator for Levels 3 and 4. Level 3 builds a structured
element-scaffold prompt; Level 4 generates a minimal wildcard prompt (<1500
chars). Allergy hard-exclusion wired through RecipeRequest.allergies into
both prompt builders and the generate() call path. Parsed LLM response
(title, ingredients, directions, notes) fully propagated to RecipeSuggestion.
Task 15: User settings key-value store. Migration 012 adds user_settings
table. Store.get_setting / set_setting with upsert. GET/PUT /settings/{key}
endpoints with Pydantic SettingBody, key allowlist, get_session dependency.
RecipeEngine reads cooking_equipment from settings when hard_day_mode=True.
55 tests passing.
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""Pydantic schemas for the recipe engine API."""
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class SwapCandidate(BaseModel):
|
|
original_name: str
|
|
substitute_name: str
|
|
constraint_label: str
|
|
explanation: str
|
|
compensation_hints: list[dict] = Field(default_factory=list)
|
|
|
|
|
|
class RecipeSuggestion(BaseModel):
|
|
id: int
|
|
title: str
|
|
match_count: int
|
|
element_coverage: dict[str, float] = Field(default_factory=dict)
|
|
swap_candidates: list[SwapCandidate] = Field(default_factory=list)
|
|
missing_ingredients: list[str] = Field(default_factory=list)
|
|
directions: list[str] = Field(default_factory=list)
|
|
notes: str = ""
|
|
level: int = 1
|
|
is_wildcard: bool = False
|
|
|
|
|
|
class GroceryLink(BaseModel):
|
|
ingredient: str
|
|
retailer: str
|
|
url: str
|
|
|
|
|
|
class RecipeResult(BaseModel):
|
|
suggestions: list[RecipeSuggestion]
|
|
element_gaps: list[str]
|
|
grocery_list: list[str] = Field(default_factory=list)
|
|
grocery_links: list[GroceryLink] = Field(default_factory=list)
|
|
rate_limited: bool = False
|
|
rate_limit_count: int = 0
|
|
|
|
|
|
class RecipeRequest(BaseModel):
|
|
pantry_items: list[str]
|
|
level: int = Field(default=1, ge=1, le=4)
|
|
constraints: list[str] = Field(default_factory=list)
|
|
expiry_first: bool = False
|
|
hard_day_mode: bool = False
|
|
max_missing: int | None = None
|
|
style_id: str | None = None
|
|
tier: str = "free"
|
|
has_byok: bool = False
|
|
wildcard_confirmed: bool = False
|
|
allergies: list[str] = Field(default_factory=list)
|