fridge-vision / scripts /convert_recipes_csv_to_json.py
NoNameWrath's picture
Deploy to HuggingFace Spaces
c02c6ce
Raw
History Blame Contribute Delete
6.43 kB
"""
Convert recipes CSV to JSON with intelligent ingredient normalization.
Uses semantic similarity to normalize ingredient names and improve recipe matching.
"""
import json
import pandas as pd
import re
from pathlib import Path
from typing import List, Dict, Set
from difflib import SequenceMatcher
# YOLO food classes from the trained model
YOLO_CLASSES = {
'apple', 'banana', 'blueberry', 'bread', 'brinjal', 'butter', 'cabbage',
'capsicum', 'carrot', 'cheese', 'chicken', 'chocolate', 'corn', 'cucumber',
'egg', 'flour', 'fresh cream', 'ginger', 'green beans', 'green chilly',
'green leaves', 'lemon', 'meat', 'milk', 'mushroom', 'potato', 'shrimp',
'strawberry', 'sweet potato', 'tomato'
}
# Ingredient synonym mappings for better matching
INGREDIENT_SYNONYMS = {
'tomato': ['tomatoes', 'cherry tomato', 'roma tomato', 'beefsteak tomato'],
'carrot': ['carrots', 'grated carrot'],
'potato': ['potatoes', 'mashed potato'],
'onion': ['onions', 'red onion', 'yellow onion', 'white onion'],
'garlic': ['garlic cloves', 'minced garlic', 'garlic powder'],
'egg': ['eggs', 'egg yolk', 'egg white'],
'milk': ['whole milk', 'skim milk', 'evaporated milk'],
'cheese': ['cheddar', 'mozzarella', 'parmesan', 'cream cheese', 'feta'],
'chicken': ['chicken breast', 'chicken thigh', 'ground chicken'],
'meat': ['beef', 'pork', 'lamb', 'ground meat'],
'bread': ['white bread', 'wheat bread', 'whole grain bread'],
'butter': ['unsalted butter', 'salted butter'],
'flour': ['all-purpose flour', 'wheat flour', 'rice flour'],
'green beans': ['string beans', 'snap beans'],
'cucumber': ['cucumbers', 'english cucumber'],
'mushroom': ['mushrooms', 'button mushroom', 'cremini'],
'lemon': ['lemons', 'lemon juice', 'lemon zest'],
'corn': ['sweet corn', 'corn kernels'],
'fresh cream': ['heavy cream', 'whipped cream', 'sour cream'],
}
def normalize_ingredient(ingredient: str) -> str:
"""
Normalize ingredient name by:
1. Removing quantities and units
2. Converting to lowercase
3. Finding semantic matches with YOLO classes
"""
# Remove quantities (e.g., "2 cups", "1 tbsp")
normalized = re.sub(r'^\d+\s*(?:\d+/\d+)?\s*(?:cup|tbsp|tsp|ml|l|oz|lb|g|kg|x)?s?\.?\s*', '', ingredient.strip(), flags=re.IGNORECASE)
normalized = normalized.lower().strip()
# Remove common words
remove_words = ['of', 'the', 'a', 'and', 'or', 'finely', 'chopped', 'diced', 'sliced',
'grated', 'minced', 'whole', 'fresh', 'dried', 'ground', 'powder']
words = normalized.split()
words = [w for w in words if w not in remove_words and len(w) > 1]
normalized = ' '.join(words).strip()
# Try exact match first
if normalized in YOLO_CLASSES:
return normalized
# Try synonym matching
for yolo_class, synonyms in INGREDIENT_SYNONYMS.items():
if normalized in synonyms or any(normalized in syn for syn in synonyms):
return yolo_class
# Try fuzzy matching
for yolo_class in YOLO_CLASSES:
ratio = SequenceMatcher(None, normalized, yolo_class).ratio()
if ratio > 0.7: # 70% similarity threshold
return yolo_class
return normalized
def extract_and_normalize_ingredients(ingredients_str: str) -> List[str]:
"""Extract and normalize ingredients from CSV string."""
if not ingredients_str or str(ingredients_str).lower() == 'nan':
return []
# Split by comma
ingredients = [ing.strip() for ing in str(ingredients_str).split(',')]
# Normalize each ingredient
normalized = []
for ing in ingredients:
norm = normalize_ingredient(ing)
if norm and len(norm) > 1 and norm not in normalized: # Avoid duplicates
normalized.append(norm)
return normalized
def convert_recipes_csv_to_json(csv_path: str, output_path: str, num_recipes: int = 10000):
"""Convert recipes CSV to JSON format."""
print(f"📖 Reading CSV from {csv_path}...")
df = pd.read_csv(csv_path)
print(f"Total recipes in CSV: {len(df)}")
# Take subset
df_subset = df.head(num_recipes)
print(f"Processing {len(df_subset)} recipes...")
recipes_list = []
valid_count = 0
for idx, row in df_subset.iterrows():
try:
# Extract and normalize ingredients
ingredients = extract_and_normalize_ingredients(row.get('ingredients', ''))
if ingredients: # Only include recipes with ingredients
recipe = {
"recipe_id": idx,
"name": str(row['title']).strip(),
"ingredients": ingredients,
"url": str(row['url']).strip() if 'url' in row else "",
"difficulty": "medium",
"prep_time_mins": 30,
"servings": 4,
"description": f"Recipe with {len(ingredients)} ingredients"
}
recipes_list.append(recipe)
valid_count += 1
if (idx + 1) % 1000 == 0:
print(f" ✓ Processed {idx + 1} recipes ({valid_count} valid)")
except Exception as e:
continue
print(f"\n✅ Converted {valid_count} recipes with valid ingredients")
# Save to JSON
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'w') as f:
json.dump(recipes_list, f, indent=2)
print(f"💾 Saved to {output_path}")
print(f"\n📊 Sample recipe:")
print(json.dumps(recipes_list[0], indent=2))
# Stats
all_ingredients = set()
for recipe in recipes_list:
all_ingredients.update(recipe['ingredients'])
print(f"\n📈 Stats:")
print(f" - Total recipes: {len(recipes_list)}")
print(f" - Unique ingredients: {len(all_ingredients)}")
print(f" - Avg ingredients per recipe: {sum(len(r['ingredients']) for r in recipes_list) / len(recipes_list):.1f}")
return recipes_list
if __name__ == "__main__":
# Paths
csv_path = Path(__file__).parent.parent / "Recipe" / "recipes_1M_shortened.csv"
output_path = Path(__file__).parent.parent / "data" / "recipes.json"
# Convert
convert_recipes_csv_to_json(str(csv_path), str(output_path), num_recipes=10000)