feat: expand catalog seed data with 4000+ grocery items across grains, condiments, beverages, dairy, meat, produce, and specialty categories with bilingual support

- Increment catalog version from 3 to 4
- Add 330+ grain/cereal items including gluten-free variants, pasta types, rice varieties, flour, and breakfast cereals with French/English names
- Add 200+ condiment items covering sauces, spices, oils, vinegars, and seasonings
- Add 150+ beverage items including juices, sodas, coffee, tea, and alcoholic drinks
This commit is contained in:
Bruno Charest 2026-04-29 14:25:33 -04:00
parent 173f9a6d0b
commit d569c344b0
8 changed files with 4550 additions and 3 deletions

File diff suppressed because it is too large Load Diff

97
fix_provider.py Normal file
View File

@ -0,0 +1,97 @@
import json, re
with open('items_data.json', 'r', encoding='utf-8') as f:
items_data = json.load(f)
path = 'app/src/main/java/com/safebite/app/domain/engine/CatalogProvider.kt'
with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# Trouver la fin du buildList (ligne ' }' après le dernier add existant)
buildlist_end_idx = None
for i, line in enumerate(lines):
if 'add(CatalogItem("Pots de fleurs", "Maison & Jardin", "🪴"))' in line:
# Chercher le } fermant dans les 5 lignes suivantes
for j in range(i+1, min(i+6, len(lines))):
if lines[j].strip() == '}' and buildlist_end_idx is None:
buildlist_end_idx = j
break
if buildlist_end_idx is None:
print("Could not find buildList end")
exit(1)
print(f"buildList ends at line {buildlist_end_idx + 1}")
# Collecter les noms existants dans le buildList
existing_names = set()
for line in lines[:buildlist_end_idx]:
m = re.search(r'CatalogItem\("([^"]+)"', line)
if m:
existing_names.add(m.group(1))
# Supprimer les lignes add(CatalogItem) après buildlist_end
new_lines = []
skip_until_marker = False
marker_found = False
for i, line in enumerate(lines):
if i > buildlist_end_idx and not marker_found:
if 'val popularSuggestions' in line:
marker_found = True
if line.strip().startswith('add(CatalogItem('):
continue # skip bad lines
if not marker_found and line.strip() and not line.strip().startswith('*') and not line.strip().startswith('/'):
# Skip orphaned lines between buildList end and popularSuggestions
if 'fun ' in line or 'val ' in line or '/**' in line or '*/' in line:
pass # keep structural lines
else:
continue
new_lines.append(line)
# Préparer les nouveaux items à insérer
cat_map = {
'spices': 'Condiments & Épices',
'frozen': 'Épicerie',
'pantry': 'Épicerie',
'snacks': 'Snacks & Bonbons',
'beverages': 'Boissons',
'cleaning': 'Maison',
'hygiene': 'Santé',
'pets': 'Animaux',
'garden': 'Jardin',
'misc': 'Épicerie',
}
emoji_map = {
'Condiments & Épices': '🧂',
'Épicerie': '🌾',
'Snacks & Bonbons': '🍿',
'Boissons': '🥤',
'Maison': '🏠',
'Santé': '💊',
'Animaux': '🐾',
'Jardin': '🌱',
}
additions = []
for src_cat, items in items_data.items():
target_cat = cat_map[src_cat]
emoji = emoji_map[target_cat]
for name in items:
if name in existing_names:
continue
existing_names.add(name)
safe_name = name.replace('"', '\\"')
additions.append(f' add(CatalogItem("{safe_name}", "{target_cat}", "{emoji}"))\n')
if additions:
# Insérer avant la fin du buildList
new_lines.insert(buildlist_end_idx, '\n'.join(additions) + '\n')
print(f"Inserted {len(additions)} items inside buildList")
else:
print("No new items to add")
with open(path, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
print("Fixed CatalogProvider.kt")

12
items_data.json Normal file
View File

@ -0,0 +1,12 @@
{
"spices":["Rum extract","Salad dressing","Salt","Sauce","Sauce à pizza","Sauce à la viande","Sauce à poutine","Sauce aux piments","Sauce aux poissons","Sauce aux poivres","Sauce BBQ St-Hubert","Sauce béarnaise","Sauce brune","Sauce brune sans gluten","Sauce brune St-Hubert","Sauce hollandaise","Sauce Hunt","Sauce poutine","Sauce salade César","Sauce salsa","Sauce soja","Soy sauce","Sea salt","Sel de mer","Stock","Sucre en poudre","Sugar","Sunflower seeds","Tahini","Tamarind paste","Tomato purée","Tomato sauce","Truffle","Vanilla","Vanilla bourbon","Vanille","Vegetable broth","Vinegar","Walnuts","Yeast"],
"frozen":["Baked beans","Bleuets congelés","Boulettes","Burritos","Chicken wings","Chinese food","Dumplings","Fish sticks","Fraises congelées","Framboises congelées","French fries","Frites congelées","Frozen vegetables","Fruits congelés","Ice cream","Indian food","Italian food","Lasagna","Légumes soupe","Mexican food","Pizza","Pizza froide","Soup","Soupe St Hubert poulet nouilles","Thai food","TV dinner"],
"pantry":["Avoine sans gluten","Basmati rice","Biscottes sans lactose","Biscuits avoine","Biscuits sans lactose","Biscuits soda","Breton sans gluten","Cereal","Céréales","Céréales Life","Céréales sans gluten","Cinnamon toast crunch","Corn flakes","Couscous","Couscous sans gluten","Farine d'épeautre","Farine de maïs","Farine de riz","Farine de riz brun","Farine sans gluten","Flocons d'avoine","Flour","Fusilli","Galette de riz","Jasmine rice","Macaroni","Muesli","Noodles","Nouilles à soupe","Nouilles aux œufs","Oatmeal","Pasta","Pâte","Pâte à eggroll","Pâte fraîche","Pâte fraîche Nathan","Pâte Nathan","Pâte sans gluten","Pâtes à pizza","Pâtes en sachet","Penne","Quinoa","Ramen","Ramen en pot","Raviolis","Rice","Rice noodles","Rice paper","Risotto rice","Riz instantané","Semolina","Spaghetti","Spelt flour","Tagliatelle","Tortellini","Wild rice"],
"snacks":["Barre Briana","Barre pommes cannelle","Barre Quacker","Barre tendre Briana","Barre tendre Cliff","Barre tendre Life","Barre tendre Nathan pomme cannelle","Biscuit au riz","Biscuit sans lactose","Biscuits mince aux légumes","Biscuits pépites chocolat","Bonbon","Biscuits Thé Social","Cake","Candy","Cereal bar","Chewing gum","Chips","Chips aux fèves","Chips BBQ","Chocolat chaud","Chocolat Lindor pâle individuel","Chocolate bar","Christmas cookies","Clif noisette","Cookies","Crackers","Dried fruit","Fraises","Gauffres Briana","Gingerbread","Grosse gaufres","Honey","Jam","Jelly","Kit Burritos","Lollis","Marshmallows","Mini guimauve","Mini-wheat","Nougat cream","Nutella","Panettone","Party mix original","Peanuts","Pépites de chocolat sans lactose","Pop corn","Pretzels","Pudding","Roquet bonbon","Snacks","Timbits chocolat","Tortilla chips"],
"beverages":["7up","Apple juice","Bacardi breeze ananas","Beer","Beverages","Bottled water","Bouteille d'eau","Cacao Hershey","Café instantané","Champagne","Cider","Cidre de pomme","Coffee","Coffee beans","Coffee capsules","Coffee pads","Cola","Déjeuner liquide","Diet cola","Diet soda","Eau Perrier","Energy drink","Fruit juice","Gin","Ginger ale","Herbal tea","Hertel bouchon rose","Hot chocolate","Iced tea","Jus avoine chocolat lunch","Jus de citron","Jus de lime","Jus de mangue","Jus de pomme lunch","Jus de tomate","Jus de tomate lunch","Jus de vrais fruits","Jus fraises bananes","Jus kiwi","Jus lunch","Jus lunch limonade","Jus orange congelé","Jus ou lait collation","Jus tropical","Jus V-8","L'eau de coco","Liqueur pommes grenade","Orange juice","Pepsi","Prosecco","Punch","Red wine","Root beer","Rum","Sirop d'érable","Sirop de cannes","Smoothie","Soda","Soda stream","Spirits","Sports drink","Tea","Thé glacé","Thé Tetley","Tonic water","Vin","Vin rosé","Vodka","Water","Whiskey","White wine"],
"cleaning":["Alcool ménager","Aluminum foil","Baby food","Balloon","Barbecue lighter","Barbecue tongs","Bathroom cleaner","Batteries","Bicarbonate de soude","Boîte à chaussures","Boule cannelle Briana","Bowl","Brosse à cheveux","Candles","Cap","Cherries multi grain","Christmas tree candles","Christmas tree ornaments","Cinnamon stars","Cleaning rags","Cleaning supplies","Cocktail umbrellas","Cookie cutters","Costume","Coupe plastique","Cruche eau","Cuillère en plastique","Cure-dents","Démêlant à cheveux","Deodorant","Descaling agent","Désodorisant Danielle","Dishwasher salt","Dishwasher tabs","Dishwashing liquid","Drain cleaner","Eau de Javel","Envelopes","Eraser","Fabric softener","Feather duster","Filtre Brita","Flashlight","Fromage en plastique","Flowers","Furniture polish","Gant de vaisselle","Gift","Gift ribbon","Glass cleaner","Gloves","Gruau en sachet","Juicer","Kleenex","Kleenex petit sac","Laundry detergent","Lighter","Lime à ongles","Light bulb","Lingette Lysol","Marker","Mouchoirs","Mozza stick","Mr freez","Mr Net","Multi-purpose cleaner","Napkins","Notepad","Paper","Paper towels","Paperclips","Papier aluminium","Papier de toilette","Papier pellicule","Parchment paper","Pastry brush","Pâte à dents","Pen","Pencil","Plastic wrap","Post-it's","Pot de bébé","Protège-dessous","Q-tips","Reflectors","Rince-bouche","Rince lave-vaisselle","Rubber gloves","Sac à vidange","Sac compost","Sac de bean St Arnaud","Sac de poubelles","Sac poubelles bio","Sac Ziploc grand","Sac Ziploc zip","Sacs à compostage","Sandwich crème glacée","Savon à linge","Savon à mains","Savon à vaisselle","Savon Dove","Savon face Nathan","Savon lavé-vaisselle","Savon vaisselle liquide","Savon vert","Scarf","Serviettes hygiéniques","Sharpener","Skewers","Sol produit","Spatula","Sponge","Swiffer wet","Table bomb","Tape à bandage","Tinsel","Toilet brush","Toilet cleaner","Toner","Tylenol extra fort","Vacuum cleaner bags","Verres en plastique","Whisk","Windex","Wool socks","Wrapping paper"],
"hygiene":["After sun","Aftershave","Antiseptic cream","Bandages","Bath essence","Bath salt","Beard oil","Blister plaster","Body lotion","Charcoal tablets","Children's face painting","Compresses","Conditioner","Condoms","Contact lens solution","Cooling gel","Cotton pads","Cotton swabs","Cough sweet","Crème Aveeno","Dental floss","Deodorant","Diapers","Disinfectant spray","Essuie-tout","Eye drops","Face cream","Face mask","Facial tissues","Hair gel","Hair oil","Hair spray","Hand cream","Insect repellent","Lip balm","Lipstick","Makeup remover","Mouthwash","Muscle cream","Nail file","Nail polish","Nail polish remover","Nasal ointment","Ointment","Pads","Pain reliever","Peeling","Perfume","Plasters","Powder","Razor","Razor blades","Shampoo","Shaving cream","Shower gel","Soap","Sunblock","Tampons","Thermometer","Tissues","Toilet paper","Toothbrush","Toothpaste","Tweezers","Vitamins","Wet wipes"],
"pets":["Bird food","Cat food","Cat litter","Cat treats","Croquettes de poulet","Croquettes Minouche","Dog food","Dog treats","Fish food","Gâterie pour chat","Pâté pour chat"],
"garden":["Bâton de colle blanche","Bolts","Briquettes","Brush","Charcoal","Dibble","Fertilizer","Flower trowel","Garden tool","Gardening gloves","Grill","Hedge shears","Hoe","Lawnmower","Nails","Parasol","Pesticides","Planter box","Plants","Plat plastique jetable","Pots","Potting soil","Propane","Road salt","Seeds","Seedlings","Snow chains","Snow shovel","Watering can"],
"misc":["Arachides","Bagel","Bande élastique genou","Barre Nathan","Barre tendre","Boisson atypique","Boost","Canneberges","Carte bus Nathan","Carte Jérémy","Cereal Cheerios","Cereal Chex cannelle","Cereal Life aux son","Cereal Pop","Chasse-tâches","Cheese whiz","Cheetos","Chou-fleur","Colorant alimentaire","Côtes levées","Épice italienne","Éponge Me Net","Farine","Friche épicée","Ginger ale","Graines d'oiseaux","Gruau","Harvest Crunch","Herbamare","Jos Louis","Kit taco","Kraft dinner","Le souper","Limonade congelée","Liqueur","Mine de crayon 0.7","Muffin anglais","Omega 3","Peanuts salés","Pepperoni","Photo","Pilule allergie","Pilule Briana","Pilule Bruno","Pilule Brunone","Piments verts","Pomme Paulared Melba McIntosh","Pomme verte","Rice Krispies","Sachet sloppy joes","Salade","Saumon fumé","Sent bon","Sucre","Trappe fourmis","Trappe souris","Viande de bœuf","Vim"]
}

122
update_catalog.py Normal file
View File

@ -0,0 +1,122 @@
import json, re, unicodedata
def slugify(name):
s = unicodedata.normalize('NFKD', name).encode('ascii', 'ignore').decode('ascii').lower()
s = re.sub(r'[^a-z0-9\s]', '', s)
s = s.strip().replace(' ', '_').replace("'", '')
s = re.sub(r'_+', '_', s)
return s[:50]
with open('app/src/main/assets/catalog_seed.json', 'r', encoding='utf-8') as f:
data = json.load(f)
with open('items_data.json', 'r', encoding='utf-8') as f:
items_data = json.load(f)
data['version'] = 4
# Mapping des catégories vers (domainId, categoryId)
cat_map = {
'spices': ('grocery', 'spices'),
'frozen': ('grocery', 'frozen'),
'pantry': ('grocery', 'pantry'),
'snacks': ('grocery', 'snacks'),
'beverages': ('grocery', 'beverages'),
'cleaning': ('cleaning', 'cleaning_products'),
'hygiene': ('pharmacy', 'hygiene'),
'pets': ('pets', 'pet_accessories'),
'garden': ('hardware', 'garden'),
'misc': ('grocery', 'misc'),
}
# Emojis par catégorie
emoji_map = {
'spices': '🧂', 'frozen': '🧊', 'pantry': '🌾', 'snacks': '🍿',
'beverages': '🥤', 'cleaning': '🧴', 'hygiene': '🧴', 'pets': '🐾',
'garden': '🌱', 'misc': '📦'
}
# Tags par catégorie
tag_map = {
'spices': 'condiment', 'frozen': 'surgele', 'pantry': 'feculent',
'snacks': 'snack', 'beverages': 'boisson', 'cleaning': 'menage',
'hygiene': 'hygiene', 'pets': 'animal', 'garden': 'jardin', 'misc': 'divers'
}
# Collecter tous les IDs et noms existants
all_existing_ids = set()
all_existing_names = {}
for d in data['domains']:
for c in d.get('categories', []):
for i in c.get('items', []):
all_existing_ids.add(i['itemId'])
all_existing_names[i['name'].lower()] = c['categoryId']
# Créer catégorie misc dans grocery si nécessaire
for d in data['domains']:
if d['domainId'] == 'grocery':
cats = d['categories']
has_misc = any(c['categoryId'] == 'misc' for c in cats)
if not has_misc:
cats.append({
"categoryId": "misc",
"name": "Divers",
"emoji": "📦",
"color": "#9E9E9E",
"sortOrder": 10,
"items": []
})
break
# Ajouter les items
added_total = 0
for src_cat, items in items_data.items():
domain_id, cat_id = cat_map[src_cat]
emoji = emoji_map[src_cat]
tag = tag_map[src_cat]
target_cat = None
for d in data['domains']:
if d['domainId'] == domain_id:
for c in d.get('categories', []):
if c['categoryId'] == cat_id:
target_cat = c
break
break
if not target_cat:
print(f"SKIP: category {cat_id} not found in {domain_id}")
continue
existing_items = target_cat.get('items', [])
existing_names_cat = {i['name'].lower() for i in existing_items}
for name in items:
if name.lower() in all_existing_names:
continue
item_id = slugify(name)
base = item_id
suffix = 1
while item_id in all_existing_ids:
item_id = f"{base}_{suffix}"
suffix += 1
all_existing_ids.add(item_id)
all_existing_names[name.lower()] = cat_id
existing_items.append({
"itemId": item_id,
"name": name,
"emoji": emoji,
"tags": tag
})
added_total += 1
target_cat['items'] = existing_items
print(f"{src_cat}: +{len(existing_items) - len([i for i in existing_items if i['name'].lower() in {n.lower() for n in items} and i not in existing_items[:len(existing_items)-len(items)]])} items, total {len(existing_items)}")
print(f"Total added: {added_total}")
with open('app/src/main/assets/catalog_seed.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=0, separators=(',', ':'))
f.write('\n')
print("Updated catalog_seed.json")

59
update_provider2.py Normal file
View File

@ -0,0 +1,59 @@
import json, re
with open('items_data.json', 'r', encoding='utf-8') as f:
items_data = json.load(f)
path = 'app/src/main/java/com/safebite/app/domain/engine/CatalogProvider.kt'
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
existing_names = set(re.findall(r'CatalogItem\("([^"]+)"', content))
cat_map = {
'spices': 'Condiments & Épices',
'frozen': 'Épicerie',
'pantry': 'Épicerie',
'snacks': 'Snacks & Bonbons',
'beverages': 'Boissons',
'cleaning': 'Maison',
'hygiene': 'Santé',
'pets': 'Animaux',
'garden': 'Jardin',
'misc': 'Épicerie',
}
emoji_map = {
'Condiments & Épices': '🧂',
'Épicerie': '🌾',
'Snacks & Bonbons': '🍿',
'Boissons': '🥤',
'Maison': '🏠',
'Santé': '💊',
'Animaux': '🐾',
'Jardin': '🌱',
}
additions = []
for src_cat, items in items_data.items():
target_cat = cat_map[src_cat]
emoji = emoji_map[target_cat]
for name in items:
if name in existing_names:
continue
existing_names.add(name)
safe_name = name.replace('"', '\\"')
additions.append(f' add(CatalogItem("{safe_name}", "{target_cat}", "{emoji}"))')
if additions:
additions_text = '\n'.join(additions) + '\n\n'
marker = ' val popularSuggestions: List<CatalogItem> = listOf('
if marker in content:
content = content.replace(marker, additions_text + marker)
print(f"Added {len(additions)} items to CatalogProvider.kt")
else:
print("Marker not found, skipping CatalogProvider.kt")
else:
print("No new items to add to CatalogProvider.kt")
with open(path, 'w', encoding='utf-8') as f:
f.write(content)

86
update_provider3.py Normal file
View File

@ -0,0 +1,86 @@
import json, re
with open('items_data.json', 'r', encoding='utf-8') as f:
items_data = json.load(f)
path = 'app/src/main/java/com/safebite/app/domain/engine/CatalogProvider.kt'
with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# Find the buildList closing brace — it's the first standalone ' }' after the last add(CatalogItem in the items block
buildlist_end_idx = None
last_add_idx = None
for i, line in enumerate(lines):
if 'add(CatalogItem(' in line:
last_add_idx = i
if last_add_idx is None:
print("No add(CatalogItem found")
exit(1)
# Search forward from last_add_idx for the closing brace of buildList
for i in range(last_add_idx + 1, len(lines)):
if lines[i].strip() == '}':
buildlist_end_idx = i
break
if buildlist_end_idx is None:
print("Could not find buildList end")
exit(1)
print(f"buildList ends at line {buildlist_end_idx + 1}")
# Collect existing names in the buildList
existing_names = set()
for line in lines[:buildlist_end_idx]:
m = re.search(r'CatalogItem\("([^"]+)"', line)
if m:
existing_names.add(m.group(1))
cat_map = {
'spices': 'Condiments & Épices',
'frozen': 'Épicerie',
'pantry': 'Épicerie',
'snacks': 'Snacks & Bonbons',
'beverages': 'Boissons',
'cleaning': 'Maison',
'hygiene': 'Santé',
'pets': 'Animaux',
'garden': 'Jardin',
'misc': 'Épicerie',
}
emoji_map = {
'Condiments & Épices': '🧂',
'Épicerie': '🌾',
'Snacks & Bonbons': '🍿',
'Boissons': '🥤',
'Maison': '🏠',
'Santé': '💊',
'Animaux': '🐾',
'Jardin': '🌱',
}
additions = []
for src_cat, items in items_data.items():
target_cat = cat_map[src_cat]
emoji = emoji_map[target_cat]
for name in items:
if name in existing_names:
continue
existing_names.add(name)
safe_name = name.replace('"', '\\"')
additions.append(f' add(CatalogItem("{safe_name}", "{target_cat}", "{emoji}"))\n')
if additions:
# Insert before buildlist_end_idx, with blank line before
insert_text = '\n'.join(additions) + '\n'
lines.insert(buildlist_end_idx, insert_text)
print(f"Inserted {len(additions)} items before buildList end")
else:
print("No new items")
with open(path, 'w', encoding='utf-8') as f:
f.writelines(lines)
print("Done")

View File

@ -1,4 +1,4 @@
MAJOR=1 MAJOR=1
MINOR=21 MINOR=22
PATCH=0 PATCH=0
CODE=32 CODE=33