Files
boehmitools-main/plugins/tandoor-ai-import/app/models.py
T
2026-07-24 21:37:03 +02:00

198 lines
7.3 KiB
Python

from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
class WarningItem(BaseModel):
model_config = ConfigDict(extra="forbid")
severity: Literal["info", "warning", "blocking"]
code: str
message: str
class IngredientSpec(BaseModel):
model_config = ConfigDict(extra="forbid")
food_name: str = Field(min_length=1, max_length=200)
preferred_food_id: int | None = Field(default=None, ge=1)
create_food: bool = False
food_plural_name: str | None = Field(default=None, max_length=200)
amount: float = Field(ge=0)
amount_max: float | None = Field(default=None, ge=0)
unit_name: str | None = Field(default=None, max_length=80)
preferred_unit_id: int | None = Field(default=None, ge=1)
create_unit: bool = False
unit_plural_name: str | None = Field(default=None, max_length=80)
note: str = Field(default="", max_length=500)
no_amount: bool = False
original_text: str = Field(default="", max_length=1000)
@model_validator(mode="after")
def validate_values(self) -> "IngredientSpec":
if self.amount_max is not None and self.amount_max < self.amount:
raise ValueError("amount_max darf nicht kleiner als amount sein.")
if self.no_amount and self.amount != 0:
raise ValueError("Bei no_amount=true muss amount 0 sein.")
if self.create_food and self.preferred_food_id is not None:
raise ValueError(
"create_food und preferred_food_id dürfen nicht gleichzeitig gesetzt sein."
)
if self.create_unit and self.preferred_unit_id is not None:
raise ValueError(
"create_unit und preferred_unit_id dürfen nicht gleichzeitig gesetzt sein."
)
if self.create_unit and not self.unit_name:
raise ValueError("create_unit benötigt unit_name.")
return self
class StepSpec(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str = Field(min_length=1, max_length=200)
instruction: str = Field(min_length=1, max_length=12000)
time: int = Field(default=0, ge=0, le=10080)
ingredients: list[IngredientSpec]
class RecipeSpec(BaseModel):
model_config = ConfigDict(extra="forbid")
schema_version: Literal[1] = 1
name: str = Field(min_length=1, max_length=250)
description: str = Field(default="", max_length=2000)
source_url: str = Field(min_length=1, max_length=2000)
image_url: str | None = Field(default=None, max_length=2000)
servings: float = Field(default=1, gt=0, le=10000)
servings_text: str = Field(default="", max_length=100)
working_time: int = Field(default=0, ge=0, le=10080)
waiting_time: int = Field(default=0, ge=0, le=10080)
keywords: list[str] = Field(default_factory=list, max_length=30)
confidence: Literal["high", "medium", "low"]
warnings: list[WarningItem] = Field(default_factory=list, max_length=50)
steps: list[StepSpec] = Field(min_length=1, max_length=50)
class AnalyzeRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
url: str = Field(min_length=3, max_length=2000)
class RecipeRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
recipe: RecipeSpec
class ImportRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
recipe: RecipeSpec
import_image: bool = True
force_duplicate: bool = False
RECIPE_JSON_SCHEMA = {
"type": "object",
"additionalProperties": False,
"required": [
"schema_version",
"name",
"description",
"source_url",
"image_url",
"servings",
"servings_text",
"working_time",
"waiting_time",
"keywords",
"confidence",
"warnings",
"steps",
],
"properties": {
"schema_version": {"type": "integer", "enum": [1]},
"name": {"type": "string"},
"description": {"type": "string"},
"source_url": {"type": "string"},
"image_url": {"type": ["string", "null"]},
"servings": {"type": "number"},
"servings_text": {"type": "string"},
"working_time": {"type": "integer"},
"waiting_time": {"type": "integer"},
"keywords": {"type": "array", "items": {"type": "string"}},
"confidence": {"type": "string", "enum": ["high", "medium", "low"]},
"warnings": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": False,
"required": ["severity", "code", "message"],
"properties": {
"severity": {
"type": "string",
"enum": ["info", "warning", "blocking"],
},
"code": {"type": "string"},
"message": {"type": "string"},
},
},
},
"steps": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": False,
"required": ["name", "instruction", "time", "ingredients"],
"properties": {
"name": {"type": "string"},
"instruction": {"type": "string"},
"time": {"type": "integer"},
"ingredients": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": False,
"required": [
"food_name",
"preferred_food_id",
"create_food",
"food_plural_name",
"amount",
"amount_max",
"unit_name",
"preferred_unit_id",
"create_unit",
"unit_plural_name",
"note",
"no_amount",
"original_text",
],
"properties": {
"food_name": {"type": "string"},
"preferred_food_id": {"type": ["integer", "null"]},
"create_food": {"type": "boolean"},
"food_plural_name": {"type": ["string", "null"]},
"amount": {"type": "number"},
"amount_max": {"type": ["number", "null"]},
"unit_name": {"type": ["string", "null"]},
"preferred_unit_id": {"type": ["integer", "null"]},
"create_unit": {"type": "boolean"},
"unit_plural_name": {"type": ["string", "null"]},
"note": {"type": "string"},
"no_amount": {"type": "boolean"},
"original_text": {"type": "string"},
},
},
},
},
},
},
},
}