Expand Doris Kitchen import, failures, and tag curation

This commit is contained in:
Fizzlepoof
2026-05-17 04:07:05 +00:00
parent e2470ff7c9
commit 165e772946
18 changed files with 1268 additions and 80 deletions

View File

@@ -236,12 +236,118 @@ def coerce_text(value: Any) -> str:
return unescape(str(value)).strip()
def normalize_whitespace(value: str) -> str:
cleaned = re.sub(r'\s+', ' ', value or '').strip()
return re.sub(r'(?<=[.!?])(?=[A-Z])', ' ', cleaned)
def split_paragraphs(value: str) -> list[str]:
parts: list[str] = []
for chunk in re.split(r'\n\s*\n|\r\n\r\n', value or ''):
cleaned = normalize_whitespace(chunk)
if cleaned:
parts.append(cleaned)
return parts
def extract_instruction_steps(value: Any) -> list[str]:
steps: list[str] = []
def visit(node: Any) -> None:
if node is None:
return
if isinstance(node, str):
for chunk in re.split(r'\n+', node):
cleaned = normalize_whitespace(chunk)
if cleaned:
steps.append(cleaned)
return
if isinstance(node, list):
for item in node:
visit(item)
return
if isinstance(node, dict):
if node.get('@type') == 'HowToSection':
name = normalize_whitespace(coerce_text(node.get('name')))
if name:
steps.append(f'{name}:')
visit(node.get('itemListElement'))
return
if node.get('@type') in {'HowToStep', 'ListItem'}:
text = normalize_whitespace(coerce_text(node.get('text') or node.get('name') or node.get('item')))
if text:
steps.append(text)
elif node.get('itemListElement') is not None:
visit(node.get('itemListElement'))
return
if node.get('itemListElement') is not None:
visit(node.get('itemListElement'))
return
text = normalize_whitespace(coerce_text(node.get('text') or node.get('name')))
if text:
steps.append(text)
visit(value)
deduped: list[str] = []
seen: set[str] = set()
for step in steps:
key = step.lower()
if key in seen:
continue
seen.add(key)
deduped.append(step)
return deduped
def build_description(recipe_node: dict[str, Any]) -> str:
intro_parts = split_paragraphs(coerce_text(recipe_node.get('description')))
intro = intro_parts[0] if intro_parts else ''
notes = intro_parts[1:] if len(intro_parts) > 1 else []
steps = extract_instruction_steps(recipe_node.get('recipeInstructions'))
parts: list[str] = []
if intro:
parts.append(intro)
if steps:
rendered_steps = []
step_number = 1
for step in steps:
if step.endswith(':'):
rendered_steps.append(step)
else:
rendered_steps.append(f'{step_number}. {step}')
step_number += 1
parts.append('\n'.join(rendered_steps))
elif intro_parts[1:]:
parts.append('\n'.join(intro_parts[1:]))
if notes:
parts.append('Notes:\n- ' + '\n- '.join(notes))
return '\n\n'.join(part for part in parts if part).strip()
def slugify_tag(value: str) -> str:
value = value.strip().lower()
value = re.sub(r'[^a-z0-9]+', '-', value)
return value.strip('-')[:48]
def extract_image_url(value: Any) -> str | None:
if isinstance(value, str):
cleaned = value.strip()
return cleaned or None
if isinstance(value, list):
for item in value:
found = extract_image_url(item)
if found:
return found
return None
if isinstance(value, dict):
for key in ('url', 'contentUrl'):
found = extract_image_url(value.get(key))
if found:
return found
return None
def normalize_keywords(value: Any) -> list[str]:
out: list[str] = []
if isinstance(value, str):
@@ -294,15 +400,7 @@ def split_ingredient(line: str) -> tuple[str, str]:
def normalize_recipe(url: str, recipe_node: dict[str, Any], title_fallback: str | None = None) -> dict[str, Any]:
name = coerce_text(recipe_node.get('name')) or (title_fallback or url)
description_parts: list[str] = []
desc = coerce_text(recipe_node.get('description'))
instructions = recipe_node.get('recipeInstructions')
instructions_text = coerce_text(instructions)
if desc:
description_parts.append(desc)
if instructions_text:
description_parts.append(instructions_text)
description = '\n\n'.join(part for part in description_parts if part).strip()
description = build_description(recipe_node)
ingredients = recipe_node.get('recipeIngredient') or []
items = []
@@ -348,6 +446,9 @@ def normalize_recipe(url: str, recipe_node: dict[str, Any], title_fallback: str
'items': items,
'tags': deduped_tags[:20],
}
image_url = extract_image_url(recipe_node.get('image'))
if image_url:
payload['photo'] = image_url
cook_time = parse_duration_minutes(recipe_node.get('cookTime'))
prep_time = parse_duration_minutes(recipe_node.get('prepTime'))
total_time = parse_duration_minutes(recipe_node.get('totalTime'))