Public Access
feat(meals): enforce three lower-calorie dinners with media
This commit is contained in:
@@ -147,7 +147,7 @@ class PlannerConfigOverride(BaseModel):
|
||||
time_full_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
recency_full_weeks: Optional[int] = Field(default=None, ge=0)
|
||||
top_k: Optional[int] = Field(default=None, ge=1)
|
||||
set_size: Optional[int] = Field(default=None, ge=1)
|
||||
set_size: Optional[int] = Field(default=None, ge=3, le=3)
|
||||
p_protein: Optional[float] = Field(default=None, ge=0)
|
||||
p_cuisine: Optional[float] = Field(default=None, ge=0)
|
||||
|
||||
@@ -392,24 +392,6 @@ class ShoppingListResponse(BaseModel):
|
||||
by_aisle: dict[str, List[ShoppingListItem]]
|
||||
|
||||
|
||||
# Sprint 12: Spoonacular external recipe search. The hit shape is
|
||||
# what the webui shows in the "Search the web" panel; the import
|
||||
# request is what the import button POSTs.
|
||||
class RecipeSearchHit(BaseModel):
|
||||
external_id: str
|
||||
external_source: str = "spoonacular"
|
||||
name: str
|
||||
image_url: Optional[str] = None
|
||||
source_url: Optional[str] = None
|
||||
prep_time_minutes: Optional[int] = None
|
||||
cook_time_minutes: Optional[int] = None
|
||||
servings: Optional[int] = None
|
||||
cuisine_tags: List[str] = Field(default_factory=list)
|
||||
dietary_tags: List[str] = Field(default_factory=list)
|
||||
protein_type: Optional[str] = None
|
||||
calories_per_serving: Optional[int] = None
|
||||
|
||||
|
||||
class RecipeImportRequest(BaseModel):
|
||||
external_id: str
|
||||
external_source: str = "spoonacular"
|
||||
|
||||
@@ -13,6 +13,10 @@ class GenerateRequest(BaseModel):
|
||||
week_start_date: date
|
||||
|
||||
|
||||
class PublicGenerateRequest(BaseModel):
|
||||
week_start_date: date
|
||||
|
||||
|
||||
class RegenerateRequest(BaseModel):
|
||||
family_profile_id: UUID
|
||||
week_start_date: date
|
||||
|
||||
@@ -19,7 +19,7 @@ class PlannerConfigOverride(BaseModel):
|
||||
time_full_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
recency_full_weeks: Optional[int] = Field(default=None, ge=0)
|
||||
top_k: Optional[int] = Field(default=None, ge=1)
|
||||
set_size: Optional[int] = Field(default=None, ge=1)
|
||||
set_size: Optional[int] = Field(default=None, ge=3, le=3)
|
||||
p_protein: Optional[float] = Field(default=None, ge=0)
|
||||
p_cuisine: Optional[float] = Field(default=None, ge=0)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
class RecipeIngredientRef(BaseModel):
|
||||
ingredient_id: UUID
|
||||
name: Optional[str] = Field(default=None, description="resolved name from Ingredient table")
|
||||
qty: float = Field(gt=0)
|
||||
unit: Optional[str] = Field(default=None, max_length=50)
|
||||
notes: Optional[str] = None
|
||||
@@ -66,10 +67,13 @@ class RecipeUpdate(BaseModel):
|
||||
|
||||
class RecipeRead(RecipeBase):
|
||||
id: UUID
|
||||
prep_time_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
cook_time_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
protein_type: Optional[str] = None
|
||||
external_source: Optional[str] = None
|
||||
external_id: Optional[str] = None
|
||||
discovery_reason: Optional[str] = None
|
||||
side_dishes: List[SideDish] = Field(default_factory=list)
|
||||
side_dishes: Optional[List[SideDish]] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class RecipeImportListItem(BaseModel):
|
||||
id: UUID
|
||||
source: str
|
||||
external_id: str
|
||||
source_url: str
|
||||
source_revision: str
|
||||
name: str | None
|
||||
status: str
|
||||
acquired_at: datetime
|
||||
reviewed_at: datetime | None
|
||||
reviewed_by: str | None
|
||||
recipe_id: UUID | None
|
||||
|
||||
|
||||
class RecipeImportListResponse(BaseModel):
|
||||
items: list[RecipeImportListItem]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
has_next: bool
|
||||
|
||||
|
||||
class RecipeImportDetail(BaseModel):
|
||||
id: UUID
|
||||
source: str
|
||||
external_id: str
|
||||
source_url: str
|
||||
source_revision: str
|
||||
license_name: str
|
||||
license_url: str
|
||||
attribution: str
|
||||
raw_payload: dict[str, Any]
|
||||
payload_sha256: str
|
||||
normalized_recipe: dict[str, Any]
|
||||
validation_report: dict[str, Any]
|
||||
status: str
|
||||
rejection_reason: str | None
|
||||
recipe_id: UUID | None
|
||||
acquired_at: datetime
|
||||
reviewed_at: datetime | None
|
||||
reviewed_by: str | None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class RecipeImportRejectRequest(BaseModel):
|
||||
reason: str = Field(min_length=1, max_length=1000)
|
||||
|
||||
@field_validator("reason")
|
||||
@classmethod
|
||||
def normalize_reason(cls, value: str) -> str:
|
||||
cleaned = value.strip()
|
||||
if not cleaned:
|
||||
raise ValueError("reason must not be blank")
|
||||
return cleaned
|
||||
Reference in New Issue
Block a user