diff --git a/app/api/routers/batch_catalog.py b/app/api/routers/batch_catalog.py index 6a597e0..837fdc8 100644 --- a/app/api/routers/batch_catalog.py +++ b/app/api/routers/batch_catalog.py @@ -17,14 +17,20 @@ now the only admin way in. What made it the survivor is the unit of work: five files are one batch with one id, so the question a colleague actually asks - "did the drop land?" - has one answer rather than five. -WHY THE NETWORK STAGES DEFAULT OFF HERE ---------------------------------------- -Turning both on for a single file is a considered trade. At twenty files it is -thousands of outbound requests and, for the image stage, a Playwright subprocess -that can burn three minutes on its own - on a single-vCPU container that is also -serving the API. So a batch opts IN to those stages; it does not opt out. -`USE_OLLAMA` is false in production anyway, which makes `use_llm` a no-op there -and the honest default obvious. +WHY IMAGES DEFAULT OFF HERE AND THE LLM DEFAULTS ON +--------------------------------------------------- +Image search for a single file is a considered trade. At twenty files it is +thousands of outbound requests and a Playwright subprocess that can burn three +minutes on its own - on a single-vCPU container that is also serving the API. +So a batch opts IN to that stage; it does not opt out. + +`use_llm` is different and defaults ON: it gates only the description written +in stage 2 for rows the sheet left blank, which is the one field a shopper +reads and a store almost never supplies. It is cheap to leave on because +`ollama_service._ensure_client` caches its reachability probe per batch and +`store_catalog_pipeline.LlmBreaker` stops calling after three consecutive +misses, so with `USE_OLLAMA` false (production today) the cost is one warning +per file and the rows fall back to a factual template. Note that these defaults bind THIS router only. The open upload endpoint runs itself and takes its two flags from `UPLOAD_AUTORUN_FETCH_IMAGES` and @@ -145,7 +151,7 @@ async def preview_catalog_batch(files: List[UploadFile] = File(...)) -> dict: dependencies=[Depends(require_admin)]) async def ingest_catalog_batch( files: List[UploadFile] = File(...), - use_llm: bool = False, + use_llm: bool = True, fetch_images: bool = False, ) -> BatchOut: """Stage the files, queue the batch, and return an id to poll. @@ -325,7 +331,8 @@ class InboxStartRequest(InboxSelection): # Chosen HERE, not by the sender - see the note on the POST handler in # uploads.py. These commit the host to outbound work, so the decision # belongs to the person who can see what the machine is already doing. - use_llm: bool = False + # The LLM is on by default for the reason in the module docstring. + use_llm: bool = True fetch_images: bool = False # Who runs it. "inprocess" is this container's worker thread and is the # default, so an existing client that never sends the field is unaffected. diff --git a/app/api/routers/user_products.py b/app/api/routers/user_products.py index 02086cb..95ce891 100644 --- a/app/api/routers/user_products.py +++ b/app/api/routers/user_products.py @@ -124,11 +124,14 @@ def _normalize_header(raw: Any) -> str: _EXACT_HEADERS: Dict[str, str] = { - # brand + # brand. "productbrand" is what a camel-cased ProductBrand header becomes + # after _normalize_header, and is listed so the mapping does not depend on + # the keyword rules' ordering. "brand": "brand", "brand name": "brand", "brands": "brand", "company": "brand", "manufacturer": "brand", "company name": "brand", + "product brand": "brand", "productbrand": "brand", # product name - "product": "product_name", "product name": "product_name", + "product": "product_name", "product name": "product_name", "productname": "product_name", "product name variant": "product_name", "product variant": "product_name", "variant": "product_name", "name": "product_name", "item": "product_name", "item name": "product_name", "product title": "product_name", @@ -138,18 +141,27 @@ _EXACT_HEADERS: Dict[str, str] = { # category "category": "category", "categories": "category", "cat": "category", "product category": "category", "segment": "category", - # price + # price. Two columns, two meanings: `selling_price` is what the shop + # charges - a retail / sale / selling price, or a bare "Price" - and + # `final_selling_price` is the tax-inclusive ceiling (MRP, "final price"). + # A sheet that carries only one of them still fills both, because + # vector_store.upsert_brand_products copies selling -> final when final is + # blank. Before this split every price header landed in final_selling_price + # and selling_price was NULL for every uploaded row. "price range": "price_range", "range": "price_range", "mrp range": "price_range", - "price": "final_selling_price", "final price": "final_selling_price", - "final price rs": "final_selling_price", "final selling price": "final_selling_price", - "selling price": "final_selling_price", "mrp": "final_selling_price", - "rate": "final_selling_price", "amount": "final_selling_price", - "cost": "final_selling_price", "unit price": "final_selling_price", + "selling price": "selling_price", "sellingprice": "selling_price", + "retail price": "selling_price", "retailprice": "selling_price", + "sale price": "selling_price", "sp": "selling_price", + "price": "selling_price", "unit price": "selling_price", "rate": "selling_price", + "final price": "final_selling_price", "final price rs": "final_selling_price", + "final selling price": "final_selling_price", "mrp": "final_selling_price", + "amount": "final_selling_price", "cost": "final_selling_price", # identifiers "barcode": "barcode", "barcode gtin ean": "barcode", "bar code": "barcode", "gtin": "barcode", "ean": "barcode", "upc": "barcode", "ean13": "barcode", "hsn": "hsn_code", "hsn code": "hsn_code", "hsn sac": "hsn_code", - "sku": "product_sku", "product sku": "product_sku", "sku code": "product_sku", + "sku": "product_sku", "product sku": "product_sku", "productsku": "product_sku", + "sku code": "product_sku", "fssai": "fssai_license", "fssai license": "fssai_license", "fssai license number": "fssai_license", "fssai number": "fssai_license", # text / media @@ -173,7 +185,13 @@ _KEYWORD_RULES: Tuple[Tuple[str, Tuple[str, ...]], ...] = ( ("barcode", ("barcode", "bar code", "gtin", "ean", "upc")), ("product_sku", ("sku",)), ("price_range", ("price range", "range")), - ("final_selling_price", ("final price", "selling price", "price", "mrp", "rate", "cost")), + # Order is load-bearing: "final selling price" contains "selling price", + # and every price header contains "price", so the final-price forms must + # be claimed before the generic ones. "sp" is deliberately exact-only - as + # a substring it is inside "spice", "display" and "sponge". + ("final_selling_price", ("final price", "final selling price", "mrp")), + ("selling_price", ("selling price", "retail price", "sale price", "price", "rate")), + ("final_selling_price", ("cost",)), ("image_url", ("image", "photo", "picture", "url", "link")), ("description", ("description", "desc", "detail")), ("category", ("category", "segment")), @@ -346,6 +364,7 @@ def row_to_request(row: Dict[str, Any], mapping: _ColumnMapping) -> AddProductRe product_sku=_text(row, mapping, "product_sku"), hsn_code=_text(row, mapping, "hsn_code"), final_selling_price=_number(row, mapping, "final_selling_price"), + selling_price=_number(row, mapping, "selling_price"), barcode=_text(row, mapping, "barcode"), image_url=_text(row, mapping, "image_url"), ) @@ -528,8 +547,9 @@ def _build_product_dict(req: AddProductRequest, brand_parent: str, price_range = req.price_range if not price_range: - if req.final_selling_price: - price_range = f"₹{req.final_selling_price}" + sheet_price = req.final_selling_price or req.selling_price + if sheet_price: + price_range = f"₹{sheet_price}" elif sample_existing.get("price_range"): price_range = sample_existing.get("price_range") else: diff --git a/app/core/catalog_engine.py b/app/core/catalog_engine.py index 1aa3cfb..f952522 100644 --- a/app/core/catalog_engine.py +++ b/app/core/catalog_engine.py @@ -91,7 +91,8 @@ def generate_product_highlights(product: Dict[str, Any], brand: str) -> List[str highlights.append(f"Available in {size} - {price}") elif size: highlights.append(f"Available in {size}") - elif isinstance(v, str) and v.strip(): + elif isinstance(v, str) and v.strip() and v.strip().lower() != "standard": + # "Standard" is the pipeline's no-size sentinel, not a pack size. highlights.append(f"Available in {v}") # Quality indicators from description diff --git a/app/core/store_catalog_pipeline.py b/app/core/store_catalog_pipeline.py index eb63382..cd5f02f 100644 --- a/app/core/store_catalog_pipeline.py +++ b/app/core/store_catalog_pipeline.py @@ -47,7 +47,7 @@ import asyncio import logging import re from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple from app.infrastructure.settings import ( ENABLE_BARCODE_LOOKUP, @@ -132,8 +132,16 @@ _ENRICHABLE = ( "selling_price", "image_url", "image_urls", "size_variants", ) +# The pack size a store sheet usually carries inside the product name itself - +# "India Gate Basmati Rice 1kg", "Fortune Sunflower Oil 1L", "Amul Milk 1 +# Litre". The match is kept as written in the name (case and spacing), so the +# size a shopper sees on the card is the size the store typed. Longer unit +# spellings precede their prefixes because alternation is first-match-wins. _SIZE_IN_TITLE = re.compile( - r"\b(\d+(?:[.,]\d+)?)\s*(kg|kgs|g|gm|gms|gram|grams|ml|l|ltr|litre|liters?|pack|pcs|n)\b", + r"\b(\d+(?:[.,]\d+)?)\s*" + r"(kgs|kg|gms|gm|grams|gram|mg|g|" + r"mls|ml|litres|litre|liters|liter|ltrs|ltr|lt|l|" + r"pieces|piece|pcs|pc|nos|pack|n)\b", re.I, ) @@ -276,24 +284,71 @@ def stage_1_brand_and_fssai(row: Dict[str, Any]) -> Dict[str, Any]: # --------------------------------------------------------------------------- # Stage 2 - Row intake (replaces AI discovery) # --------------------------------------------------------------------------- -def stage_2_row_intake(row: Dict[str, Any], *, use_llm: bool = True) -> Dict[str, Any]: +@dataclass +class LlmBreaker: + """Stop asking the LLM once it has stopped answering. + + `ollama_service._generate` swallows every exception and returns "", so a + slow or dead Ollama never raises - it just costs up to + OLLAMA_TIMEOUT_SECONDS x retries per row and yields nothing. On a + 2000-row sheet that is days. Three misses in a row and the rest of the + batch goes straight to the factual fallback description; one success + resets the count, so a single hiccup does not switch the LLM off. + """ + limit: int = 3 + consecutive_misses: int = 0 + tripped: bool = False + + def record(self, ok: bool) -> None: + if ok: + self.consecutive_misses = 0 + return + self.consecutive_misses += 1 + if self.consecutive_misses >= self.limit: + self.tripped = True + + +# The generated description is stored in a TEXT column, but a 1.5b model does +# not reliably honour the length it is asked for and the catalog card shows a +# short paragraph, not an essay. +_MAX_LLM_DESCRIPTION_CHARS = 300 + + +def stage_2_row_intake(row: Dict[str, Any], *, use_llm: bool = True, + breaker: Optional[LlmBreaker] = None) -> Dict[str, Any]: """The spreadsheet row *is* the product. Only fill a missing description. The LLM call is best-effort and optional: with Ollama unreachable the row - keeps an empty description and later stages still work. + keeps an empty description here and `_to_storage_row` supplies a factual + template at storage time, so later stages still work. + + DESCRIPTION ONLY. This stage used to copy the LLM's `size_variants` into a + row that had none, which is how "Naga Maida" acquired a 500g/1kg/5kg it + was never sold in. A pack size is a fact about the physical product and + comes from the sheet or the product name (stage 4), never from a guess. """ if not _blank(row.get("description")) or not use_llm: return row + if breaker is not None and breaker.tripped: + return row + ok = False try: from app.services.ollama_service import fetch_product_details - details = fetch_product_details(row.get("brand", ""), row.get("product_name", "")) + sheet_sizes = [str(s).strip() for s in (row.get("size_variants") or []) if str(s).strip()] + title_size = _SIZE_IN_TITLE.search(row.get("product_name") or "") + size = sheet_sizes[0] if sheet_sizes else (title_size.group(0).strip() if title_size else None) + details = fetch_product_details( + row.get("brand", ""), row.get("product_name", ""), + category=row.get("category") or None, size=size, + ) if details and details.get("description"): - row["description"] = str(details["description"]).strip() - if _blank(row.get("size_variants")) and details and details.get("size_variants"): - row["size_variants"] = list(details["size_variants"]) + row["description"] = str(details["description"]).strip()[:_MAX_LLM_DESCRIPTION_CHARS] + ok = True except Exception as exc: # noqa: BLE001 - enrichment is best-effort logger.debug("LLM enrichment skipped for %r: %s", row.get("product_name"), exc) + if breaker is not None: + breaker.record(ok) return row @@ -442,30 +497,32 @@ def _sizes_for(row: Dict[str, Any]) -> List[str]: if match: return [match.group(0).strip()] - # A COMMODITY WITH NO WEIGHT COLUMN GETS ONE UNSIZED ROW, NOT THREE MADE-UP - # ONES. default_size_variants() invents a plausible set - 100g/250g/500g - - # and for packaged goods that is a reasonable guess at what a brand sells. - # For loose produce it is not: a shop sells apples by the kilo at whatever - # the customer asks for, so "Apple 250g" is a product that does not exist. + # NO SIZE ANYWHERE MEANS ONE UNSIZED ROW, NOT THREE MADE-UP ONES - for + # every brand. This used to fall through to default_size_variants() for + # branded rows, which invents a plausible set (1kg/5kg/25kg for rice) and + # so turned "India Gate Basmati Rice" into three products that the sheet + # never listed and the shop may never have stocked. It was also the + # mechanism behind the catalogue drift the integrator reported: the + # invented set is keyed on the resolved category, so the same product + # ingested twice with the category resolved differently produced two + # disjoint size sets, two sets of image_ids, and a re-scrape that looked + # like the old rows were deleted. A pack size is recorded only when the + # sheet or the product name states it. # - # It is also the mechanism behind the catalogue drift the integrator - # reported: the invented set is keyed on the resolved category, so the same - # product ingested twice with the category resolved differently produces - # two disjoint size sets, two sets of image_ids, and a re-scrape that looks - # like the old rows were deleted. Keeping produce out of that from the - # start is cheaper than repairing it later. # "Standard" and not "": an empty size fails validate_size ("size/pack is - # missing") and stage 4 would drop the row, which is the rejection this - # whole area exists to prevent. "Standard" is also what _to_storage_row - # already substitutes for a blank size and what the seeded base list uses, - # so an uploaded "Apple" deduplicates onto the seeded "Apple" instead of - # creating a second row. - if row.get("brand") == OWN_PRODUCTS_BRAND: - return ["Standard"] - - return list(price_estimator.default_size_variants( - row.get("category") or "", row.get("product_name") or "" - )) + # missing") and stage 10 would penalise the row for a fact nobody had. It is + # the pipeline's internal no-size sentinel, understood by the validator, + # the price estimator and the SKU resolver. What reaches the table depends + # on the brand - see _to_storage_row: Own Products keeps "Standard" so an + # uploaded "Apple" deduplicates onto the seeded "Apple" (same image_id); + # a branded row stores an empty size_variants and an unsuffixed name. + if row.get("brand") != OWN_PRODUCTS_BRAND: + # Worth a line in the job report for a packaged good; for loose + # produce an absent size is the normal case and would only be noise. + row.setdefault("_notes", []).append( + "no pack size in the sheet or the product name; stored without one" + ) + return ["Standard"] def stage_4_explode_sizes(row: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], List[str]]: @@ -519,8 +576,14 @@ def stage_5_pricing(row: Dict[str, Any]) -> Dict[str, Any]: if not _blank(row.get("price_range")): return row - if not _blank(row.get("final_selling_price")): - price = float(row["final_selling_price"]) + # Either price the sheet gave is a real price for this pack; the final + # (tax-inclusive) one is preferred when both are present because the band + # is what the card shows a shopper. + sheet_price = row.get("final_selling_price") + if _blank(sheet_price): + sheet_price = row.get("selling_price") + if not _blank(sheet_price): + price = float(sheet_price) lo = int(round(price * (1 - _SHEET_PRICE_BAND))) hi = int(round(price * (1 + _SHEET_PRICE_BAND))) elif row.get("brand") == OWN_PRODUCTS_BRAND: @@ -537,7 +600,84 @@ def stage_5_pricing(row: Dict[str, Any]) -> Dict[str, Any]: # --------------------------------------------------------------------------- # Stage 6 - Images # --------------------------------------------------------------------------- -def stage_6_images(row: Dict[str, Any], *, enabled: bool = True) -> Dict[str, Any]: +class ImageSearchCache: + """One image search per PRODUCT, shared by every pack size of it. + + Stage 4 turns one source row into one row per size, and this stage used to + search once per resulting row. The query was the same each time - the size + is not in it - but the providers behind `find_all_image_urls` are live and + not deterministic, so "Dabur Honey" asked four times came back four ways: + the 225g row drew an Amazon photo of Dabur Honey, the 1kg row drew nothing + usable. Same product, four different images, three of them wrong or blank. + + Keyed on `image_corroboration.search_key`, which strips a trailing pack + size from the name, so sheet rows that carry the size inside the name + ("India Gate Basmati Rice 1kg" / "... 5kg") share as well. The cached value + is the RANKED candidate list; each size still runs its own gate, which + ignores size tokens and so reaches the same verdict for every sibling. + + `existing` maps a search key to an already-stored eligible primary for + the same product under the same brand, so a re-ingest that finds nothing + new can inherit the photo a sibling size already has - after the gate. + """ + + def __init__(self, existing_rows: Optional[Sequence[Dict[str, Any]]] = None) -> None: + self.candidates: Dict[tuple, List[str]] = {} + self.existing: Dict[tuple, str] = {} + for prior in existing_rows or (): + url = prior.get("image_url") + if not url or not str(url).startswith("http"): + continue + key = image_corroboration.search_key(prior.get("product_name") or "", + prior.get("brand") or "") + self.existing.setdefault(key, str(url)) + + +def _adopt_sibling_image(row: Dict[str, Any], brand: str, cache: ImageSearchCache) -> bool: + """Reuse the eligible primary of another pack size of the same product. + + Only ever through the gate: the sibling's URL is corroborated against THIS + row's title before it is adopted, so a wrong image stored under one size + cannot propagate to the rest. A sibling is the same product in a different + pack, which is what `search_key` means, and the same photo on every pack is + what a retailer shows too. + """ + key = image_corroboration.search_key(row.get("product_name") or "", brand) + url = cache.existing.get(key) + if not url: + return False + choice = image_corroboration.choose_primary([url], row.get("product_name") or "", brand) + if choice.primary is None: + return False + row["image_url"] = choice.primary + row["image_urls"] = [choice.primary] + row.setdefault("_notes", []).append("image adopted from another pack size of this product") + return True + + +def _image_cache_for(rows: Sequence[Dict[str, Any]]) -> ImageSearchCache: + """The run's shared image cache, primed with what each brand already holds. + + Reads each destination brand once (stage 11 reads it again for the merge; + two reads of a small table beat threading the rows through five stages). + A read failure primes nothing - the search still runs. + """ + existing: List[Dict[str, Any]] = [] + for brand in sorted({r.get("brand") for r in rows if r.get("brand")}): + if brand == OWN_PRODUCTS_BRAND: + continue + try: + for prior in get_products_by_brand(brand) or []: + prior = dict(prior) + prior.setdefault("brand", brand) + existing.append(prior) + except Exception as exc: # noqa: BLE001 - priming is an optimisation + logger.warning("Could not read existing images for %s: %s", brand, exc) + return ImageSearchCache(existing) + + +def stage_6_images(row: Dict[str, Any], *, enabled: bool = True, + cache: Optional[ImageSearchCache] = None) -> Dict[str, Any]: if not _blank(row.get("image_urls")) or not enabled: return row try: @@ -580,33 +720,61 @@ def stage_6_images(row: Dict[str, Any], *, enabled: bool = True) -> Dict[str, An # drops the "-plant -tree -fish -botanical" Wikimedia exclusion block # that fights every produce query. Without it this stage recreates the # exact defect the Own Products image repair exists to undo. - candidates = find_all_image_urls( - product_name, brand=brand or None, max_results=24, produce=is_produce - ) - if candidates: - best = catalog_engine._select_best_images( - candidates, product_name, brand, max_images=10 + # A pack size of this product that is already in the table may carry + # the photo. Take it - through the gate - before spending a search: + # the same product in a different pack is the same photo, every size + # then agrees, and a re-ingest costs no network. See _adopt_sibling_image. + if cache is not None and not is_produce and _adopt_sibling_image(row, brand, cache): + return row + + # One search per product, not per pack size - see ImageSearchCache. + # The query is the size-less name: a photo's filename or alt text + # almost never carries the pack size, and a size token in the query + # only steers the engines towards pages LISTING that size, which for + # "Honey 1kg" was every other brand's 1kg honey. + key = image_corroboration.search_key(product_name, brand) + base_name = image_corroboration.SIZE_TAIL.sub("", product_name).strip() or product_name + if cache is not None and key in cache.candidates: + best = cache.candidates[key] + else: + candidates = find_all_image_urls( + base_name, brand=brand or None, max_results=24, produce=is_produce ) - if best: - # `_select_best_images` ORDERS candidates; it does not judge - # whether any of them is this product. Its scoring awards points - # for the brand appearing in the URL, so for a brand that is - # also a personal name it actively rewarded the wrong photo - - # `Anil_Kapoor_2019.jpg` outscored everything and became - # `image_url`. choose_primary decides what may be promoted. - # - # The list is still stored in full. A product whose only - # candidates are uncorroborated keeps them for review; it just - # does not get one of them presented as fact. - choice = image_corroboration.choose_primary( - best, product_name, brand or "" + best = catalog_engine._select_best_images( + candidates, base_name, brand, max_images=10 + ) if candidates else [] + if cache is not None: + cache.candidates[key] = list(best) + + if best: + # `_select_best_images` ORDERS candidates; it does not judge + # whether any of them is this product. Its scoring awards points + # for the brand appearing in the URL, so for a brand that is + # also a personal name it actively rewarded the wrong photo - + # `Anil_Kapoor_2019.jpg` outscored everything and became + # `image_url`. choose_primary decides what may be promoted. + # + # ONLY ELIGIBLE CANDIDATES ARE STORED. The rejects used to be kept + # in `image_urls` "for review" - but the product card falls back to + # `image_urls[0]` when `image_url` is empty and the modal shows the + # list as a gallery, so the review pile was what the shopper saw: + # four foreign honeys for "Dabur Honey 1kg", a mosquito repellent + # for "Dabur Honey 500g". A reject is logged, not displayed. + choice = image_corroboration.choose_primary( + best, product_name, brand or "" + ) + row["image_urls"] = list(choice.eligible) + row["image_url"] = choice.primary + if choice.rejected: + row.setdefault("_notes", []).append( + f"{len(choice.rejected)} image candidate(s) rejected: " + "they do not name this product" ) - row["image_urls"] = list(choice.ordered) - row["image_url"] = choice.primary - if choice.primary is None: - row.setdefault("_notes", []).append( - f"no primary image: {choice.reason}" - ) + if choice.primary is None: + row.setdefault("_notes", []).append( + f"no primary image: {choice.reason}" + ) + except Exception as exc: # noqa: BLE001 - an image is not worth the row # `warning`, not `debug`: at the default log level a debug line is # invisible, so a row that silently lost its images looked identical to @@ -731,18 +899,50 @@ def build_image_id(brand: str, product_name: str, size: str) -> str: return f"{_sanitize_name(brand)}_{slug}" +def _fallback_description(brand: str, name: str, size: str, category: str) -> str: + """What the card says when neither the sheet nor the LLM said anything. + + Factual by construction - only facts the row already holds: the product, + its pack size, its category, its maker. Deliberately NOT + `catalog_engine.generate_detailed_description`, which pads any product + into eight hundred characters of "premium quality" and "exceptional + taste" that nobody verified. An honest short line beats a confident essay. + """ + parts = [name.strip()] + if size and size.lower() not in name.lower(): + parts.append(f"{size} pack") + if category and category.lower() not in ("general", "uncategorized"): + parts.append(category) + if brand: + # resolve_parent_brand hands back the alias KEY ("amul"), which is a + # lookup token rather than a name. Title-case only that form; a brand + # written with its own casing ("India Gate", "P&G") is left alone. + parts.append(f"by {brand.title() if brand.islower() else brand}") + return ", ".join(p for p in parts if p) + "." + + def _to_storage_row(row: Dict[str, Any]) -> Dict[str, Any]: """Project an enriched row onto the brand-table columns.""" name = row.get("product_name") or row.get("title") or "" - size = row.get("size") or "Standard" - display = name if size.lower() in name.lower() else f"{name} {size}".strip() - category = row.get("category") or "General" own = row.get("brand") == OWN_PRODUCTS_BRAND + # "Standard" is the pipeline's internal no-size sentinel (see _sizes_for). + # Own Products keeps it in the table: the seeded base list uses it and an + # uploaded "Apple" has to build the same image_id as the seeded "Apple". + # A branded row has no such counterpart, so the sentinel is dropped here + # and the row is stored the way the sheet described it - "India Gate + # Basmati Rice", size_variants [], image_id without a size suffix - rather + # than as "India Gate Basmati Rice Standard". + raw_size = row.get("size") or "Standard" + size = raw_size if (own or raw_size != "Standard") else "" + display = name if (not size or size.lower() in name.lower()) else f"{name} {size}".strip() + category = row.get("category") or "General" # "Apple 500g from Own Products." is a sentence nobody wrote and nobody # wants, and "Own Products" is a bucket rather than a maker, so the # template reads as a false provenance claim. A commodity keeps whatever # description the sheet gave, including none. - description = row.get("description") or (None if own else f"{display} from {row.get('brand')}.") + description = row.get("description") or ( + None if own else _fallback_description(row.get("brand") or "", display, size, category) + ) # The embedding text. Interpolating a null description and the bucket name # would embed the literal "Own Products ... None", so a commodity is # described to the vector index by what actually identifies it. @@ -759,7 +959,7 @@ def _to_storage_row(row: Dict[str, Any]) -> Dict[str, Any]: "image_url": row.get("image_url"), "image_urls": list(row.get("image_urls") or []), "price_range": row.get("price_range"), - "size_variants": [size], + "size_variants": [size] if size else [], "providers": list(row.get("providers") or []), "fssai_license": row.get("fssai_license"), "product_sku": row.get("product_sku"), @@ -1001,9 +1201,16 @@ def run_pipeline( progress(1, STAGE_NAMES[0], 0, len(prepared)) prepared = [stage_1_brand_and_fssai(r) for r in prepared] + breaker = LlmBreaker() if use_llm else None for index, row in enumerate(prepared, start=1): - stage_2_row_intake(row, use_llm=use_llm) + stage_2_row_intake(row, use_llm=use_llm, breaker=breaker) progress(2, STAGE_NAMES[1], index, len(prepared)) + if breaker is not None and breaker.tripped: + result.warnings.append( + f"LLM descriptions stopped after {breaker.limit} consecutive failures " + "(Ollama unreachable or too slow); the remaining rows use the factual " + "template description." + ) progress(3, STAGE_NAMES[2], 0, len(prepared)) prepared = [stage_3_title_category(r) for r in prepared] @@ -1031,8 +1238,9 @@ def run_pipeline( for index, row in enumerate(exploded, start=1): stage_5_pricing(row) progress(5, STAGE_NAMES[4], index, len(exploded)) + image_cache = _image_cache_for(exploded) if fetch_images else None for index, row in enumerate(exploded, start=1): - stage_6_images(row, enabled=fetch_images) + stage_6_images(row, enabled=fetch_images, cache=image_cache) progress(6, STAGE_NAMES[5], index, len(exploded)) for index, row in enumerate(exploded, start=1): stage_7_sku(row) diff --git a/app/services/brand_discovery.py b/app/services/brand_discovery.py index afc1110..4b7ea02 100644 --- a/app/services/brand_discovery.py +++ b/app/services/brand_discovery.py @@ -107,6 +107,7 @@ from app.infrastructure.settings import ( ) from app.services import active_brands, brand_store, ollama_service, retail_presence from app.services.brand_registry import ( + get_brand_store_domain, get_fssai_license, get_known_sub_brands, resolve_parent_brand, @@ -449,19 +450,29 @@ def _existing_title_index(brand: str) -> Dict[str, str]: # --------------------------------------------------------------------------- # Source A - Open Food Facts (ground truth) # --------------------------------------------------------------------------- +class OpenFactsUnavailable(RuntimeError): + """Open Food Facts could not be consulted - as opposed to consulted and + found empty. `discover_brand_products` turns this into a warning that says + so, because "OFF has nothing for this brand" is a claim about the brand + and "OFF was down" is not.""" + + def _from_open_facts(brand: str, *, refresh: bool = False) -> List[Dict[str, Any]]: """Real products for `brand`, with a real GTIN and often a real pack size. - `off_bulk.fetch_brand_corpus` is already disk-cached, retry-wrapped and - paced at 2s between pages, and `scripts/backfill_barcodes_from_off.py` - already imports it from outside the enrichment package, so reaching for it - here is an established pattern rather than a new one. + `off_bulk.fetch_brand_corpus_result` is disk-cached, retry-wrapped and + paced at 6s between 100-row pages. Raises `OpenFactsUnavailable` when the fetch + did not complete and produced nothing; anything else is returned as-is, + an empty list meaning OFF genuinely has no products under this brand tag. """ try: - hits = off_bulk.fetch_brand_corpus(brand, refresh=refresh) + result = off_bulk.fetch_brand_corpus_result(brand, refresh=refresh) except Exception as exc: # noqa: BLE001 - a dead OFF must not kill discovery logger.warning("Open Food Facts lookup failed for %r: %s", brand, exc) - return [] + raise OpenFactsUnavailable(str(exc) or exc.__class__.__name__) from exc + if result.error and not result.hits: + raise OpenFactsUnavailable(result.error) + hits = result.hits out: List[Dict[str, Any]] = [] for hit in hits or (): @@ -759,10 +770,10 @@ def _resolve_sizes(candidate: Dict[str, Any], title: str, category: str, which is what makes `image_id` reproducible - the name is half of it. The LLM's list is a guess and comes last. Only when all three are empty is - the cell left blank, which hands the decision to stage 4's - `default_size_variants` - and that invents 100g/250g/500g, which - `_sizes_for`'s own comment identifies as the mechanism behind observed - catalogue drift. Leaving it blank is the last resort, not the default. + the cell left blank; stage 4 then stores ONE unsized row (it no longer + invents 100g/250g/500g - see `_sizes_for`, which names that invention as + the mechanism behind observed catalogue drift). Blank is honest, but a + real size is what makes the row a distinct pack, so it is worth finding. """ in_title = _TITLE_SIZE_RE.search(title or "") if in_title: @@ -850,25 +861,21 @@ def _build_product(brand: str, candidate: Dict[str, Any], *, max_sizes: int, # semantic search. # # A blank description is honest and already handled: `_to_storage_row` - # substitutes " from ." exactly as it does for a store - # sheet that left the column empty. + # substitutes a short factual line (name, size, category, brand) exactly + # as it does for a store sheet that left the column empty. description = (candidate.get("description") or "").strip() - # A BARCODE IDENTIFIES ONE PACK, SO IT ONLY SURVIVES A KNOWN PACK SIZE. - # With no real size, stage 4 falls back to `default_size_variants` and - # invents 100g/250g/500g - and because every column is copied into each - # exploded variant, one real GTIN would be stamped onto three packs, two of - # which do not exist. That is a worse error than a blank barcode: it is - # wrong data that looks authoritative, and `nutrition_by_barcode` would - # happily resolve all three to the same product. + # A BARCODE IDENTIFIES ONE PACK. That used to mean dropping it whenever no + # pack size was known, because stage 4 then invented 100g/250g/500g and + # every column is copied into each exploded variant - one real GTIN + # stamped onto three packs, two of which did not exist. Stage 4 now stores + # a single unsized row when it has no size, and one GTIN on one row is + # exactly what a GTIN means, so the barcode survives; the row is flagged + # so a reviewer knows the pack size is still unknown. barcode = candidate.get("barcode") notes: List[str] = [] if barcode and not sizes: - notes.append( - "barcode dropped: no real pack size is known, and stage 4 will " - "invent several - a GTIN names one pack, not three" - ) - barcode = None + notes.append("pack size unknown: the barcode names one pack, but its size was not found") shape = {"title": title, "category": category_hint, "size_variants": sizes, "description": description} @@ -929,12 +936,25 @@ def discover_brand_products( registry_terms = _registry_terms(brand) fssai = get_fssai_license(brand) - off_candidates = _from_open_facts(brand, refresh=refresh_corpus) if use_openfacts else [] - if use_openfacts and not off_candidates: - warnings.append( - "Open Food Facts returned nothing for this brand. Every row below " - "rests on the language model alone - review them individually." - ) + # Three distinct outcomes, three distinct warnings. "OFF has nothing under + # this brand tag" is a fact about the brand; "OFF could not be reached" is + # a fact about the network and must not be reported as the former - that + # is how Naga (2 real rows on OFF) was being shown as unknown to it. + off_candidates: List[Dict[str, Any]] = [] + if use_openfacts: + try: + off_candidates = _from_open_facts(brand, refresh=refresh_corpus) + except OpenFactsUnavailable as exc: + warnings.append( + f"Open Food Facts could not be reached ({exc}). Nothing was " + "cached, so the next preview will try again; the rows below " + "come from the brand's shop and the language model only." + ) + else: + if not off_candidates: + warnings.append( + "Open Food Facts has no products tagged with this brand." + ) off_keys = {} for candidate in off_candidates: key = _normalise_title(brand, candidate["title"]) @@ -951,12 +971,26 @@ def discover_brand_products( if store_candidates: logger.info("%s: %d products from the brand's own shop", brand, len(store_candidates)) elif use_store and not off_candidates: + # Say what was actually checked. An unregistered storefront is not a + # storefront that "has nothing" - nobody has looked. + store_domain = get_brand_store_domain(brand) + if store_domain: + warnings.append( + f"{store_domain} is registered as this brand's shop but its " + "catalogue has not been fetched - run " + f"scripts/backfill_brand_stores.py --brand \"{brand}\" to load it." + ) + else: + warnings.append( + "No verified storefront is registered for this brand. If it " + "has its own online shop, add it to " + "brand_registry.BRAND_STORE_DOMAINS and run " + "scripts/backfill_brand_stores.py to populate a real catalogue." + ) + if not off_candidates and not store_candidates: warnings.append( - "Neither Open Food Facts nor a brand storefront has anything for " - "this brand. Every row below rests on the language model alone - " - "review them individually. If this brand has its own online shop, " - "adding it to brand_registry.BRAND_STORE_DOMAINS and running " - "scripts/backfill_brand_stores.py will populate a real catalogue." + "Every row below rests on the language model alone - review them " + "individually." ) diff --git a/app/services/brand_store.py b/app/services/brand_store.py index 9214cb0..08a4324 100644 --- a/app/services/brand_store.py +++ b/app/services/brand_store.py @@ -11,9 +11,12 @@ never heard of there is nothing to verify, and the catalogue is whatever `qwen2.5:1.5b` invents. Measured 2026-09-10, Open Food Facts hits: Udhaiyam 0, Tenali Double Horse 0, -Gopuram 0, Double Horse 14, and Naga 2 - where both "Naga" rows are a UK / -Bangladeshi pickle brand, not the Tamil Nadu one. Regional South Indian brands -are simply not in that database. +Gopuram 0, Double Horse 14. (An earlier note here said Naga's two rows were a +UK / Bangladeshi pickle brand - that was the old free-text `brands:Naga` +Search-a-licious query matching "Mr Naga" and "Bombay Naga Jhal". Under the +exact `brands_tags=naga` filter the v2 API returns two real Naga Limited rows, +Sooji 500 g and Maida 500 g, both on the 890 GS1 prefix - see off_bulk.py.) +Regional South Indian brands are still thin in that database. They are, however, on their own shop, and modern storefronts publish their whole catalogue as structured JSON: diff --git a/app/services/enrichment/barcode/sources/off_bulk.py b/app/services/enrichment/barcode/sources/off_bulk.py index 429ecf1..d396082 100644 --- a/app/services/enrichment/barcode/sources/off_bulk.py +++ b/app/services/enrichment/barcode/sources/off_bulk.py @@ -10,34 +10,50 @@ resulting cascade "misses far more often than it hits". That module is wired into the live enrichment pipeline and is deliberately NOT touched by this file. This module inverts the problem: fetch a brand's **entire** OFF catalogue in one -or two requests from the Search-a-licious endpoint, cache it on disk, then match -every one of our products against that corpus offline. A whole-catalogue -backfill costs ~5 HTTP requests instead of ~600, and re-tuning the similarity -threshold costs zero network because the corpus is cached. +or two requests from the v2 search API, cache it on disk, then match every one +of our products against that corpus offline. A whole-catalogue backfill costs +~5 HTTP requests instead of ~600, and re-tuning the similarity threshold costs +zero network because the corpus is cached. -Nothing here is imported by the running app - the only consumer is -`scripts/backfill_barcodes_from_off.py`. Everything except `fetch_brand_corpus` -is a pure function so it can be unit-tested without network or database. +`fetch_brand_corpus` is the one network function here and is shared by the +backfill scripts, `product_grounding`, `post_ingest_barcodes` and +`brand_discovery`. Everything else is a pure function so it can be unit-tested +without network or database. -ENDPOINT NOTES (verified empirically, 2026-09) - GET https://search.openfoodfacts.org/search - ?q=brands:amul AND countries_tags:"en:india" +ENDPOINT NOTES (verified empirically, 2026-09-11) + GET https://world.openfoodfacts.org/api/v2/search + ?brands_tags=amul + &countries_tags=en:india &fields=code,product_name,quantity,... - &page_size=250 - * The double quotes around "en:india" are REQUIRED. Without them the query - parses as a bare term and silently returns count=0 rather than erroring. - * page_size up to 1000 is accepted; 250 keeps responses small. + &page_size=100&page=1 + * `brands_tags` is an EXACT match on the brand's tag slug ("naga", not + "Naga"), which is what a brand catalogue needs. The earlier Search-a-licious + query `q=brands:Naga` was a free-text match: it returned "Mr Naga" and + "Bombay Naga Jhal" - other companies - and MISSED the real Naga rows, + because Search-a-licious reads a separate Elasticsearch index that was + stale for them (barcode 8906011830068 was still filed brandless under + Kuwait). The v2 API reads the live product database. Measured on the same + day: Amul 216 products here vs 140 there; Naga 2 vs 0. + * `page_count` in this API is the number of products ON THIS PAGE, not the + number of pages, and `page_size` in the RESPONSE is what the server + actually applied (a larger request is capped to 100 without complaint). + Paginate from `count` / that served `page_size`. + * OFF rate-limits all search endpoints to 10 requests/minute per IP. + PAUSE_SECONDS keeps a multi-page brand under that. * `world.openfoodfacts.org` intermittently serves an HTML "Page temporarily - unavailable" page with a 200 status, so every response is content-type - checked before parsing. + unavailable" page with a 200 status, and plain 503s under load, so every + response is status- and content-type-checked before parsing, and a failed + fetch is never written to the cache as an empty corpus. """ from __future__ import annotations import json import logging +import math import os import re import time +from dataclasses import dataclass, field from pathlib import Path from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple @@ -63,21 +79,50 @@ from app.services.quantity_utils import quantities_match logger = logging.getLogger(__name__) -SEARCH_URL = "https://search.openfoodfacts.org/search" +SEARCH_URL = "https://world.openfoodfacts.org/api/v2/search" # OFF's usage policy requires a contactable custom User-Agent; requests sent # with the default python-requests agent are treated as anonymous crawling. USER_AGENT = "BrandCatalogRAG/1.0 (suriya@tenext.in)" FIELDS = "code,product_name,product_name_en,brands,quantity,countries_tags" -PAGE_SIZE = 250 -MAX_PAGES = 10 # 2500 products per brand is far beyond any real brand -PAUSE_SECONDS = 2.0 # search endpoints are the strictly-limited ones +PAGE_SIZE = 100 # the v2 API caps this at 100 and silently serves that +MAX_PAGES = 25 # 2500 products per brand is far beyond any real brand +PAUSE_SECONDS = 6.0 # 10 search requests/minute is OFF's published limit + +# Bumped when the cache file's meaning changes. Files written before this key +# existed came from the Search-a-licious endpoint; an EMPTY one of those is +# more likely to be that endpoint's stale index (or a swallowed 503) than a +# real absence, so it is re-fetched. A non-empty one is real data and is kept. +CACHE_SCHEMA = 2 # backend/app/services/enrichment/barcode/sources/off_bulk.py -> backend/ _BACKEND_DIR = Path(__file__).resolve().parents[5] CACHE_DIR = _BACKEND_DIR / "data" / "cache" / "off_brand_corpus" + +class OffUnavailable(requests.exceptions.ConnectionError): + """OFF answered but could not serve (429 / 5xx). + + Subclasses ConnectionError so `retry.with_retry` treats it exactly like a + dropped socket - a few seconds later the same request usually succeeds - + without widening the retry set for every other barcode source. + """ + + +@dataclass +class CorpusFetch: + """What `fetch_brand_corpus_result` learned. + + `error` is None when OFF answered every page, even if it answered with + nothing - that is a real "this brand is not on Open Food Facts". When + `error` is set the fetch did not complete and NOTHING was cached, so a + caller can say "could not be reached" instead of "has nothing". + """ + hits: List[Dict[str, Any]] = field(default_factory=list) + error: Optional[str] = None + from_cache: bool = False + # Pack sizes embedded in a product name ("Marie Gold 250g", "Butter 1L"). # Mirrors the unit list in scripts/backfill_nutrition_from_barcodes.py, widened # with the count-based units this catalog also uses. @@ -122,84 +167,149 @@ def _cache_path(slug: str, cache_dir: Optional[Path] = None) -> Path: return (cache_dir or CACHE_DIR) / f"{slug}.json" +def brand_tag_slug(brand: str) -> str: + """The brand as Open Food Facts tags it: lowercase, runs of anything that + is not a letter or digit collapsed to one hyphen. "Hindustan Unilever" -> + "hindustan-unilever", "P&G" -> "p-g", "Naga" -> "naga". OFF matches + `brands_tags` case-insensitively, but sending the slug form is what its + own site does and avoids depending on that.""" + return re.sub(r"[^a-z0-9]+", "-", (brand or "").lower()).strip("-") + + @with_retry(max_attempts=3, min_wait=2.0, max_wait=10.0) -def _get_page(brand: str, country: Optional[str], page: int) -> Dict[str, Any]: - """One page of OFF search results. Raises on transport errors (retried by - the decorator); returns an empty result dict for any response that is not - parseable JSON, which is how the HTML "temporarily unavailable" page and - any future error page are absorbed without killing the run.""" - query = f"brands:{brand}" +def _get_page(brand: str, country: Optional[str], page: int) -> Optional[Dict[str, Any]]: + """One page of OFF v2 search results. + + Raises on transport errors and on 429 / 5xx (both retried by the + decorator); returns None for any other response that is not parseable + JSON - an HTML "temporarily unavailable" page, a 4xx, a truncated body. + None means "this page FAILED", which the caller must keep distinct from a + page that parsed fine and simply held no products. + """ + params: Dict[str, Any] = { + "brands_tags": brand_tag_slug(brand), + "fields": FIELDS, + "page_size": PAGE_SIZE, + "page": page, + } if country: - # The quotes are load-bearing - see the module docstring. - query += f' AND countries_tags:"en:{country}"' + params["countries_tags"] = f"en:{country}" resp = requests.get( SEARCH_URL, - params={"q": query, "fields": FIELDS, "page_size": PAGE_SIZE, "page": page}, + params=params, headers={"User-Agent": USER_AGENT, "Accept": "application/json"}, timeout=BARCODE_LOOKUP_TIMEOUT_SECONDS, ) + if resp.status_code == 429 or resp.status_code >= 500: + raise OffUnavailable(f"HTTP {resp.status_code} from Open Food Facts") if resp.status_code != 200: logger.warning("OFF search returned HTTP %s for brand %r page %s", resp.status_code, brand, page) - return {} + return None if "json" not in (resp.headers.get("content-type") or "").lower(): logger.warning("OFF search returned non-JSON (%s) for brand %r - " "the service is probably serving an error page", resp.headers.get("content-type"), brand) - return {} + return None try: payload = resp.json() except ValueError as e: logger.warning("OFF search returned unparseable JSON for brand %r: %s", brand, e) - return {} - return payload if isinstance(payload, dict) else {} + return None + return payload if isinstance(payload, dict) else None -def fetch_brand_corpus(brand: str, - country: Optional[str] = None, - refresh: bool = False, - cache_dir: Optional[Path] = None) -> List[Dict[str, Any]]: - """Every OFF product for `brand`, from disk cache unless `refresh`. +def _read_cache(path: Path, brand: str) -> Optional[List[Dict[str, Any]]]: + """Cached hits, or None when the cache must not be trusted: unreadable, or + an empty file from before CACHE_SCHEMA existed (see that constant).""" + try: + cached = json.loads(path.read_text(encoding="utf-8")) + except Exception as e: # noqa: BLE001 - a corrupt cache must not be fatal + logger.warning(" Ignoring unreadable OFF cache %s: %s", path.name, e) + return None + hits = cached.get("hits") or [] + if not hits and cached.get("schema") != CACHE_SCHEMA: + logger.info(" Ignoring empty pre-v%s OFF cache for %r - re-fetching", + CACHE_SCHEMA, brand) + return None + logger.info(" OFF corpus for %r: %d product(s) (cached %s)", + brand, len(hits), cached.get("fetched_at_human", "?")) + return hits + + +def fetch_brand_corpus_result(brand: str, + country: Optional[str] = None, + refresh: bool = False, + cache_dir: Optional[Path] = None) -> CorpusFetch: + """Every OFF product for `brand`, from disk cache unless `refresh`, plus + whether the fetch actually completed. Hits with no usable product name are dropped here rather than at match time (6 of 146 Amul hits, 1 of 233 Britannia hits) - a nameless hit can never clear a name-similarity threshold, so carrying it forward only inflates the - corpus. Returns [] rather than raising when OFF is unreachable, so one bad - brand does not abort a multi-brand backfill. + corpus. + + A fetch that fails part-way returns what it got with `error` set and + writes NO cache file. Caching a failure as `hits: []` is how "Open Food + Facts has nothing for this brand" was being asserted for brands OFF had + simply been too busy to answer about. """ country = BARCODE_COUNTRY_TAG if country is None else (country or None) slug = re.sub(r"[^a-z0-9]+", "_", brand.lower()).strip("_") path = _cache_path(slug, cache_dir) if not refresh and path.exists(): - try: - cached = json.loads(path.read_text(encoding="utf-8")) - hits = cached.get("hits") or [] - logger.info(" OFF corpus for %r: %d product(s) (cached %s)", - brand, len(hits), cached.get("fetched_at_human", "?")) - return hits - except Exception as e: # noqa: BLE001 - a corrupt cache must not be fatal - logger.warning(" Ignoring unreadable OFF cache %s: %s", path.name, e) + cached = _read_cache(path, brand) + if cached is not None: + return CorpusFetch(hits=cached, from_cache=True) hits: List[Dict[str, Any]] = [] + error: Optional[str] = None page = 1 - while page <= MAX_PAGES: + total_pages = 1 + while page <= min(total_pages, MAX_PAGES): if page > 1: time.sleep(PAUSE_SECONDS) - payload = _get_page(brand, country, page) - batch = payload.get("hits") or [] + try: + payload = _get_page(brand, country, page) + except requests.exceptions.RequestException as e: + # Retries exhausted (OffUnavailable is a ConnectionError too). + error = str(e) or e.__class__.__name__ + break + if payload is None: + error = "Open Food Facts returned an unusable response" + break + batch = payload.get("products") or [] hits.extend(h for h in batch if isinstance(h, dict) and (h.get("product_name") or "").strip()) - page_count = payload.get("page_count") or 0 - if page >= page_count or not batch: + if page == 1: + # `page_count` is products-on-this-page in the v2 API; the real + # page total is `count` over the page size the SERVER applied - + # it silently caps the requested size (250 asked, 100 served for + # Amul), so dividing by PAGE_SIZE under-pages. + count = payload.get("count") or 0 + served = payload.get("page_size") or len(batch) or PAGE_SIZE + try: + total_pages = max(1, math.ceil(int(count) / int(served))) + except (TypeError, ValueError, ZeroDivisionError): + total_pages = 1 + if not batch: break page += 1 + if error: + logger.warning(" OFF corpus for %r: fetch failed after %d usable product(s) - %s " + "(nothing cached)", brand, len(hits), error) + return CorpusFetch(hits=hits, error=error) + path.parent.mkdir(parents=True, exist_ok=True) tmp = path.with_suffix(".json.tmp") tmp.write_text(json.dumps({ + "schema": CACHE_SCHEMA, + "endpoint": SEARCH_URL, "brand": brand, + "brand_tag": brand_tag_slug(brand), "country": country, "fetched_at": time.time(), "fetched_at_human": time.strftime("%Y-%m-%d %H:%M:%S"), @@ -208,7 +318,20 @@ def fetch_brand_corpus(brand: str, os.replace(tmp, path) logger.info(" OFF corpus for %r: %d usable product(s) fetched", brand, len(hits)) - return hits + return CorpusFetch(hits=hits) + + +def fetch_brand_corpus(brand: str, + country: Optional[str] = None, + refresh: bool = False, + cache_dir: Optional[Path] = None) -> List[Dict[str, Any]]: + """`fetch_brand_corpus_result(...).hits` - the list-only form every + backfill and ingestion caller uses. Returns [] rather than raising when + OFF is unreachable, so one bad brand does not abort a multi-brand run; + callers that need to tell "unreachable" from "empty" use the result form. + """ + return fetch_brand_corpus_result(brand, country=country, refresh=refresh, + cache_dir=cache_dir).hits # --------------------------------------------------------------------------- diff --git a/app/services/enrichment/hsn_gst/models.py b/app/services/enrichment/hsn_gst/models.py index e31a5c6..d44c936 100644 --- a/app/services/enrichment/hsn_gst/models.py +++ b/app/services/enrichment/hsn_gst/models.py @@ -243,6 +243,16 @@ def _round2(value: Optional[float]) -> Optional[float]: return round(value, 2) +def _held_number(value: object) -> Optional[float]: + """A price the row already carries, or None for blank / unparseable.""" + if value is None or (isinstance(value, str) and not value.strip()): + return None + try: + return float(value) + except (TypeError, ValueError): + return None + + def enrich_pricing_fields(product: dict) -> Dict[str, object]: """Compute the pricing fields added by this stage for one catalog row. @@ -253,8 +263,24 @@ def enrich_pricing_fields(product: dict) -> Dict[str, object]: `cost_price`, `profit_before_tax` and `profit_after_tax` are not derivable from the data this pipeline generates, so they stay None (exactly as the existing enriched exports store them). + + A PRICE THE ROW ALREADY HOLDS WINS. A store sheet that says "Retail Price + 155" has stated a fact; the band derived from it is Rs143-167, and this + stage used to read the band's ceiling back as "selling_price = 167" and + hand that to `EnrichmentStage.apply`, which overwrites a held value with + a different one (it only refuses to BLANK one). Held prices are therefore + returned as None here so `apply` keeps them, the base for the tax is the + held selling price, then the held final price, and only then the band. """ - selling_price = _extract_selling_price(product.get("price_range")) + held_selling = _held_number(product.get("selling_price")) + held_final = _held_number(product.get("final_selling_price")) + if held_selling is not None: + selling_price = held_selling + elif held_final is not None: + selling_price = held_final + else: + selling_price = _extract_selling_price(product.get("price_range")) + gst = product.get("gst_percent") tax_amount = None final_selling_price = None @@ -263,10 +289,10 @@ def enrich_pricing_fields(product: dict) -> Dict[str, object]: final_selling_price = _round2(selling_price + tax_amount) return { - "selling_price": _round2(selling_price), + "selling_price": None if held_selling is not None else _round2(selling_price), "cost_price": None, "tax_amount": tax_amount, - "final_selling_price": final_selling_price, + "final_selling_price": None if held_final is not None else final_selling_price, "profit_before_tax": None, "profit_after_tax": None, } diff --git a/app/services/enrichment/post_ingest_barcodes.py b/app/services/enrichment/post_ingest_barcodes.py index 9506349..f49cd5f 100644 --- a/app/services/enrichment/post_ingest_barcodes.py +++ b/app/services/enrichment/post_ingest_barcodes.py @@ -76,7 +76,7 @@ logger = logging.getLogger(__name__) # Matches scripts/backfill_barcodes_from_off.py, which measured it. DEFAULT_MIN_SIMILARITY = 0.88 -BARCODE_SOURCE = "openfoodfacts_bulk (search.openfoodfacts.org)" +BARCODE_SOURCE = "openfoodfacts_bulk (world.openfoodfacts.org/api/v2)" def _rows_needing_a_barcode(cur, table: str) -> List[Dict[str, Any]]: diff --git a/app/services/image_corroboration.py b/app/services/image_corroboration.py index cf05abf..813a2a6 100644 --- a/app/services/image_corroboration.py +++ b/app/services/image_corroboration.py @@ -63,7 +63,7 @@ import sqlite3 import threading import time from contextlib import closing -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path from typing import Iterable, List, Optional, Sequence from urllib.parse import urlparse @@ -175,6 +175,23 @@ def distinctive_tokens(product_name: str, brand: str) -> List[str]: return out +def _token_in(token: str, lowered_url: str) -> bool: + """Does the URL carry this word, allowing for the plural on either side? + + The catalogue names "Aachi Appalams" and "Aachi Pickles"; the photo is + `Aachi-Appalam-100-g-1.webp`. A whole-token substring test rejected the + correct image for the plural and then promoted an opaque Amazon URL over + it. The singular is tried as well - only for tokens long enough that + stripping the "s" leaves a real word ("gems" -> "gem" is fine; "kgs" never + gets here, size tokens are removed upstream). + """ + if token in lowered_url: + return True + if len(token) > 4 and token.endswith("s") and token[:-1] in lowered_url: + return True + return False + + def search_key(product_name: str, brand: str) -> tuple: """Collapse a trailing pack size so sizes of one product share a lookup. @@ -213,7 +230,7 @@ def corroborate(url: str, product_name: str, brand: str) -> Corroboration: distinctive = distinctive_tokens(product_name, brand) if distinctive: - hit = next((t for t in distinctive if t in lowered), None) + hit = next((t for t in distinctive if _token_in(t, lowered)), None) if hit: return Corroboration(True, f"url names {hit!r}") return Corroboration( @@ -272,6 +289,26 @@ _lock = threading.Lock() _initialized = False _CACHE_TTL_SECONDS = 30 * 24 * 3600 +# Open*Facts allows 100 product reads a minute per IP. A brand ingestion or a +# re-gate asks about every distinct barcode it meets, and the Dabur run issued +# about a hundred in two minutes - the tail was throttled, and a throttled +# lookup fails open, which is how a Kellogg's honey got past the gate for a +# moment. Pacing the calls keeps the gate answering instead of guessing. +_OFF_MIN_INTERVAL_SECONDS = 0.65 +_OFF_LOOKUP_ATTEMPTS = 2 +_OFF_RETRY_SLEEP_SECONDS = 3.0 +_off_last_call = 0.0 +_off_pace_lock = threading.Lock() + + +def _pace_off_lookup() -> None: + global _off_last_call + with _off_pace_lock: + wait = _OFF_MIN_INTERVAL_SECONDS - (time.monotonic() - _off_last_call) + if wait > 0: + time.sleep(wait) + _off_last_call = time.monotonic() + def _connect() -> sqlite3.Connection: _DB_PATH.parent.mkdir(parents=True, exist_ok=True) @@ -366,29 +403,58 @@ def openfacts_product_matches_brand(url: str, brand: str, *, timeout: int = 10) if not tokens: return True - key = f"{api_host}:{barcode}:{(brand or '').lower()}" + # "v2:" - entries written before the fail-open verdicts stopped being + # cached are ignored rather than trusted; they age out with the TTL. + key = f"v2:{api_host}:{barcode}:{(brand or '').lower()}" cached = _cache_get(key) if cached is not None: return cached - verdict = True - try: - resp = requests.get( - f"https://{api_host}/api/v2/product/{barcode}.json", - params={"fields": "brands,product_name"}, - timeout=timeout, - headers={"User-Agent": _BROWSER_UA}, - ) + # ONLY A REAL ANSWER IS CACHED. The fail-open True for a lookup that did + # not happen - a 429 or 503 from Open*Facts, a non-JSON body - used to be + # written to the cache too, for thirty days. A Dabur ingestion issued a + # hundred lookups in two minutes, the tail of them were throttled, and + # Kellogg's "Miel Pops", a Toblerone and a Nature Valley bar were filed as + # Dabur products; "Dabur Honey 1kg" then showed the Kellogg's honey. A + # throttled lookup still fails open for THIS call, but the next call asks + # again. + payload = None + for attempt in range(_OFF_LOOKUP_ATTEMPTS): + _pace_off_lookup() + try: + resp = requests.get( + f"https://{api_host}/api/v2/product/{barcode}.json", + params={"fields": "brands,product_name"}, + timeout=timeout, + headers={"User-Agent": _BROWSER_UA}, + ) + except Exception: # noqa: BLE001 - a lookup failure must not reject a good image + return True if resp.ok: - product = (resp.json() or {}).get("product") or {} - haystack = ( - f"{product.get('brands') or ''} {product.get('product_name') or ''}" - ).lower() - if haystack.strip(): - verdict = any(t in haystack for t in tokens) - except Exception: # noqa: BLE001 - a lookup failure must not reject a good image + try: + payload = resp.json() or {} + except ValueError: + return True + break + if resp.status_code == 429 or resp.status_code >= 500: + # Throttled or unwell. One paced retry is cheap and turns most of + # these into a real answer; a second failure fails open, uncached. + time.sleep(_OFF_RETRY_SLEEP_SECONDS * (attempt + 1)) + continue + return True + if payload is None: return True + product = payload.get("product") or {} + if not product and payload.get("status") == 0: + # Open*Facts positively says: no such barcode. Nothing to compare + # against, and asking again will not change that - cache the open verdict. + _cache_set(key, True) + return True + haystack = f"{product.get('brands') or ''} {product.get('product_name') or ''}".lower() + if not haystack.strip(): + return True + verdict = any(t in haystack for t in tokens) _cache_set(key, verdict) return verdict @@ -398,11 +464,25 @@ def openfacts_product_matches_brand(url: str, brand: str, *, timeout: int = 10) # --------------------------------------------------------------------------- @dataclass class PrimaryChoice: - """The outcome of choosing a product's `image_url` from its candidates.""" + """The outcome of choosing a product's `image_url` from its candidates. + + `ordered` is every candidate, best first. `eligible` is the subset that + may be SHOWN - tiers 1 and 2 - and is what the store pipeline persists as + `image_urls`. The two differ for a reason that was learned the hard way: + tier 3 was kept in `image_urls` "for review, never promoted", but the + product card falls back to `image_urls[0]` whenever `image_url` is empty + and the product modal shows the whole list as a gallery. So for "Dabur + Honey 1kg" the gate correctly withheld the primary - every candidate was + another company's honey - and the UI displayed those very honeys anyway. + A candidate the gate would not promote must not be stored where the UI + will promote it. + """ primary: Optional[str] ordered: List[str] reason: str + eligible: List[str] = field(default_factory=list) + rejected: List[str] = field(default_factory=list) def choose_primary( @@ -424,7 +504,10 @@ def choose_primary( catalog_engine's comment protects, where dropping uncorroborated candidates would leave real products with no image at all. 3. The URL could have named the product and did not - a human-authored - Commons filename, say. Kept in `image_urls`, never promoted. + Commons filename, say - or an Open*Facts photo whose barcode belongs + to another brand. Returned in `ordered` and `rejected`, never in + `eligible`, and the store pipeline does not persist it (see + PrimaryChoice for why "kept for review" was not safe). Nothing eligible means `primary is None`. A blank image renders as the brand monogram, which is honest; another company's product is not, and it @@ -466,12 +549,16 @@ def choose_primary( tier3.sort(key=looks_like_person_photo) ordered = tier1 + tier2 + tier3 + eligible = tier1 + tier2 if tier1: - return PrimaryChoice(tier1[0], ordered, "url names the product") + return PrimaryChoice(tier1[0], ordered, "url names the product", + eligible=eligible, rejected=tier3) if tier2: - return PrimaryChoice(tier2[0], ordered, "opaque path on a known image host") + return PrimaryChoice(tier2[0], ordered, "opaque path on a known image host", + eligible=eligible, rejected=tier3) return PrimaryChoice( None, ordered, "no candidate names this product; primary withheld rather than guessed", + eligible=eligible, rejected=tier3, ) diff --git a/app/services/image_search.py b/app/services/image_search.py index 189b87c..244b14a 100644 --- a/app/services/image_search.py +++ b/app/services/image_search.py @@ -114,11 +114,44 @@ def _query_openfacts(query: str, max_results: int) -> list: return [] +def _openfacts_product_is_brand(product: dict, brand: Optional[str]) -> bool: + """Does this Open*Facts record belong to `brand`? + + Open*Facts' free-text search matches on the NAME, not the brand: asked for + "Dabur Honey 1kg" it answered with a UK, a French, a Swiss and a Spanish + honey, every one a real front-of-pack photo of somebody else's product. + The record says who made it - `brands` / `brands_tags` - and that field + used to be thrown away here, leaving a per-URL API round trip downstream + (`image_corroboration.openfacts_product_matches_brand`) as the only thing + standing between those photos and the catalogue. Check it at the source. + + Fails OPEN on a record with no brand at all: an unlabelled record is not + evidence of another brand, and the downstream gate still runs. + """ + if not brand: + return True + from app.services.image_corroboration import brand_tokens + + tokens = brand_tokens(brand) + if not tokens: + return True + tags = product.get("brands_tags") or [] + haystack = " ".join([str(product.get("brands") or "")] + [str(t) for t in tags]).lower() + if not haystack.strip(): + return True + return any(t in haystack for t in tokens) + + def find_images_openfacts(title: str, brand: Optional[str] = None, max_results: int = 20) -> list: """Query the Open *Facts family of open product databases for real product photos. No API key required. Falls back from a brand+title query to a title-only query if the combined query is too specific to - match anything (small/regional brand name variants are a common case).""" + match anything (small/regional brand name variants are a common case). + + Only records whose own `brands` field names our brand are used - see + `_openfacts_product_is_brand`. The title-only fallback makes this filter + load-bearing: "Honey 1kg" matches every honey on the site. + """ if not USE_OPEN_FACTS: return [] @@ -126,12 +159,12 @@ def find_images_openfacts(title: str, brand: Optional[str] = None, max_results: if not query: return [] - products = _query_openfacts(query, max_results) + products = [p for p in _query_openfacts(query, max_results) if _openfacts_product_is_brand(p, brand)] if not products and brand and title: # Combined "brand + title" query found nothing - retry with just # the title, since Open*Facts' free-text search is exact-ish and # brand naming conventions vary (e.g. "Dettol" vs "Reckitt Dettol"). - products = _query_openfacts(title, max_results) + products = [p for p in _query_openfacts(title, max_results) if _openfacts_product_is_brand(p, brand)] urls: List[str] = [] for product in products: diff --git a/app/services/ollama_service.py b/app/services/ollama_service.py index dd51716..b53bcd9 100644 --- a/app/services/ollama_service.py +++ b/app/services/ollama_service.py @@ -332,16 +332,29 @@ def fetch_brand_catalog_exhaustive(brand: str, max_products: int = 300) -> Dict[ return {"brand": brand, "products": products_out} -def fetch_product_details(brand: str, product_title: str) -> Dict[str, Any] | None: +def fetch_product_details(brand: str, product_title: str, + category: str | None = None, + size: str | None = None) -> Dict[str, Any] | None: """Get details for a single product: description, image_url, pricing fields. Returns a dict with keys: description, image_url, size_variants, price_ranges, price_range, provider_examples. + + `category` and `size` are optional context. A 1.5b model asked about + "Naga Maida" alone will happily describe a curry; told it is a 500g pack + in Flours & Grains it describes refined wheat flour. Both are facts the + caller already holds, so they cost nothing to pass. """ if not _ensure_client(): return None + context = f"Brand: {brand}\nProduct: {product_title}\n" + if category: + context += f"Category: {category}\n" + if size: + context += f"Pack size: {size}\n" user_prompt = ( - f"Brand: {brand}\nProduct: {product_title}\n" - "Return strictly JSON with keys: description, image_url?, size_variants?, price_ranges?, price_range?, provider_examples?.\n" - "description must be <=160 chars, concise and factual." + context + + "Return strictly JSON with keys: description, image_url?, size_variants?, price_ranges?, price_range?, provider_examples?.\n" + "description: 1-2 sentences, <=220 chars, factual - what the product is, " + "its form and its typical use. No marketing adjectives, no claims you cannot know." ) text = _generate(SYSTEM_PROMPT, user_prompt) if not text: diff --git a/app/services/retail_presence.py b/app/services/retail_presence.py index 0c31d02..0ec5a6b 100644 --- a/app/services/retail_presence.py +++ b/app/services/retail_presence.py @@ -5,7 +5,7 @@ WHY THIS FILE EXISTS -------------------- Open Food Facts answers "does this product exist" for food, and answers it well. It does not answer it for anything else: `off_bulk` queries only -`search.openfoodfacts.org`, so a toothpaste or a detergent has no +Open Food Facts' food database, so a toothpaste or a detergent has no product-discovery source at all and its entire catalogue is language-model output. Measured 2026-09-10, India-tagged coverage on the sibling databases is 2-13 products per non-food brand against Britannia's 218 on OFF - real, but diff --git a/data/brand_image_repair_backup_20260911_152831.json b/data/brand_image_repair_backup_20260911_152831.json new file mode 100644 index 0000000..b1cb51f --- /dev/null +++ b/data/brand_image_repair_backup_20260911_152831.json @@ -0,0 +1,1588 @@ +{ + "created_at": "2026-09-11T15:28:31.312074", + "db_host": "31.97.228.132", + "db_name": "pgvector", + "tables": { + "brand_dabur": [ + { + "image_id": "dabur_dabur_chyawanprash_500g", + "product_name": "Dabur Chyawanprash 500g", + "image_url": "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "image_urls": [ + "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-SPER1-000280/dabur-odomos-naturals-mosquito-repellent-spray-100ml_dabur-odomos-naturals-mosquito-repellent-spray-100ml--TM-SPER1-000280_1.png?width=320", + "https://sinin.com.bd/wp-content/uploads/2023/01/Dabur-Odomos-Naturals-Non-Sticky-Mosquito-Repellent-Cream.jpg", + "https://australiaherbalstore.com/uploads/products/Dabur_Odomos_Naturals_Mosquito_Repellant_Lotion1.jpg", + "https://i5.walmartimages.com/seo/Dabur-Odomos-Mosquito-Repellent-Lotion-Natural-60ml-Clinically-Tested-for-Full-Day-Protection_4ef81766-f6d7-4049-9d42-f9655d6a512a.7be63d46fbe85f826f6a78e9c50d1fdb.jpeg?odnHeight=573&odnWidth=573&odnBg=FFFFFF" + ] + }, + { + "image_id": "dabur_odonil_jasmine_mist_50g", + "product_name": "Odonil Jasmine Mist 50g", + "image_url": "https://thedesifood.com/media/Dabur-Odonil-Air-Freshener-Blocks-Jasmine-Mist-48-g-1.webp", + "image_urls": [ + "https://thedesifood.com/media/Dabur-Odonil-Air-Freshener-Blocks-Jasmine-Mist-48-g-1.webp", + "https://thedesifood.com/media/Dabur-Odonil-Air-Freshener-Blocks-Jasmine-Mist-48-g-2.webp", + "https://thedesifood.com/media/Dabur-Odonil-Air-Freshener-Blocks-Jasmine-Mist-48-g-6.webp", + "https://thedesifood.com/media/Dabur-Odonil-Air-Freshener-Blocks-Jasmine-Mist-48-g-4.webp", + "https://bk.shajgoj.com/storage/2024/10/daburodonil-jasminemist75gm.jpg", + "https://www.officedel.com/wp-content/uploads/2023/04/Odonil-Jasmine-Mist4-630x630.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/K49244/dabur-odonil-room-spray-jasmine-fresh-220ml-6.1-1671743443.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/K49244/dabur-odonil-room-spray-jasmine-fresh-220ml-6.3-1671743439.jpg", + "https://thedesifood.com/media/Dabur-Odonil-Room-Spray-Jasmine-Fresh-150-ml-3.webp", + "https://thedesifood.com/media/Dabur-Odonil-Room-Spray-Jasmine-Fresh-150-ml-1.webp" + ] + }, + { + "image_id": "dabur_real_250g", + "product_name": "real 250g", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/c/c5/Dabur_Real_Active.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "image_urls": [ + "https://upload.wikimedia.org/wikipedia/commons/c/c5/Dabur_Real_Active.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Mixed-Fruit-1000-ml-5.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Mixed-Berries-1000-ml-1.webp", + "https://m.media-amazon.com/images/I/718s5Hnzt-L._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71734im6rkL._SL1120_.jpg", + "https://images.openbeautyfacts.org/images/products/800/184/176/8946/front_es.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/934/925/400/6187/front_en.11.400.jpg", + "https://images.openbeautyfacts.org/images/products/922/007/959/7664/front_en.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/922/007/959/7725/front_en.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/750/103/970/1553/front_en.3.400.jpg" + ] + }, + { + "image_id": "dabur_real_500g", + "product_name": "real 500g", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/c/c5/Dabur_Real_Active.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "image_urls": [ + "https://upload.wikimedia.org/wikipedia/commons/c/c5/Dabur_Real_Active.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Mixed-Fruit-1000-ml-5.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Mixed-Berries-1000-ml-1.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Mixed-Fruit-1000-ml-3.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Mango-1000-ml-1.webp", + "https://m.media-amazon.com/images/I/718s5Hnzt-L._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71734im6rkL._SL1120_.jpg", + "https://m.media-amazon.com/images/I/71ipOyxATBL._SL1500_.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/4421/front_en.7.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/705/0671/front_en.3.400.jpg" + ] + }, + { + "image_id": "red_chyawanprash_f122a959", + "product_name": "Red Chyawanprash 100g", + "image_url": "https://www.bbassets.com/media/uploads/p/l/1200007586_3-dabur-chyawanprash-3x-immunity-action-builds-strength-stamina-overall-health-red.jpg", + "image_urls": [ + "https://www.bbassets.com/media/uploads/p/l/1200007586_3-dabur-chyawanprash-3x-immunity-action-builds-strength-stamina-overall-health-red.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-HECA1-000071/DABUR-CHYAWANPRASH-1-KG_dabur-chyawanprash-1-kg--TM-HECA1-000071_1.webp?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-PANG1-000242/dabur-chyawanprash-awaleha-2000-gm_dabur-chyawanprash-awaleha-2000-gm--TM-PANG1-000242_1.png?width=320" + ] + }, + { + "image_id": "dabur_dabur_honey_500g", + "product_name": "Dabur Honey 500g", + "image_url": "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "image_urls": [ + "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-SPER1-000280/dabur-odomos-naturals-mosquito-repellent-spray-100ml_dabur-odomos-naturals-mosquito-repellent-spray-100ml--TM-SPER1-000280_1.png?width=320", + "https://sinin.com.bd/wp-content/uploads/2023/01/Dabur-Odomos-Naturals-Non-Sticky-Mosquito-Repellent-Cream.jpg", + "https://australiaherbalstore.com/uploads/products/Dabur_Odomos_Naturals_Mosquito_Repellant_Lotion1.jpg", + "https://i5.walmartimages.com/seo/Dabur-Odomos-Mosquito-Repellent-Lotion-Natural-60ml-Clinically-Tested-for-Full-Day-Protection_4ef81766-f6d7-4049-9d42-f9655d6a512a.7be63d46fbe85f826f6a78e9c50d1fdb.jpeg?odnHeight=573&odnWidth=573&odnBg=FFFFFF" + ] + }, + { + "image_id": "lal_tail_meswak_329704a5", + "product_name": "Lal Tail Meswak 20g", + "image_url": "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2/static/img/product/351538/dabur-lal-tail-200-ml_1_display_1456380335_1867178e.jpg", + "image_urls": [ + "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2/static/img/product/351538/dabur-lal-tail-200-ml_1_display_1456380335_1867178e.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-000884/DABUR-LAL-TAIL-LIQUID--200-ML_dabur-lal-tail-liquid-200-ml--TM-LINT1-000884_1.webp?width=320", + "https://egmedi.com/storage/product_media/dabur-lal-tail-100-ml-product-images-o490262128-p490262128-0-202208301723.webp", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000463/dabur-lal-tail-100-ml_dabur-lal-tail-100-ml--TM-OIIL1-000463_1.png", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000448/dabur-lal-tail-500ml_dabur-lal-tail-500ml--TM-OIIL1-000448_5.png?width=320", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000195/DABUR-LAL-TAIL-50-ML_dabur-lal-tail-50-ml--TM-OIIL1-000195_1.webp?width=320", + "https://cdn01.pharmeasy.in/dam/products_otc/X59870/dabur-lal-tail-ayurvedic-baby-massage-oil-with-free-dabur-honey-50g-bottle-of-100ml-3-1700658276.jpg?dim=700x0&dpr=1&q=100", + "https://www.pobara.com/images/thumbs/0014391_dabur-lal-tail-100-ml.jpeg", + "https://rukminim2.flixcart.com/image/416/416/xif0q/bath-essential-oil/f/k/5/500-lal-tail-500ml-ayurvedic-baby-massage-oil-dabur-original-imagh4cv9z4m9xts.jpeg?q=70&crop=false", + "http://rukmini1.flixcart.com/image/300/300/xif0q/massage-oil/n/w/r/100-lal-tail-ayurvedic-baby-massage-oil-100ml-massage-oil-dabur-original-imagwzqhygntyhqc.jpeg" + ] + }, + { + "image_id": "dabur_real_active_100_orange_juice_1l", + "product_name": "Real active 100% orange juice 1l", + "image_url": "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1000-1000,pr-true,f-auto,q-40,dpr-2/cms/product_variant/b6340135-9484-49e6-942d-2525125cf2e5/Dabur-Real-Activ-Orange-Juice-Fresh-Fruity.jpeg", + "image_urls": [ + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1000-1000,pr-true,f-auto,q-40,dpr-2/cms/product_variant/b6340135-9484-49e6-942d-2525125cf2e5/Dabur-Real-Activ-Orange-Juice-Fresh-Fruity.jpeg", + "http://shreenathfoods.com/cdn/shop/products/DaburRealOrangeJuice180ml_1200x1200.jpg?v=1676460920", + "https://besuresainikcanteen.com/wp-content/uploads/2024/05/265843-2_4-real-indias-no1-juice-orange.jpg", + "https://img3.exportersindia.com/product_images/bc-full/2019/2/1620599/dabur-real-juice-1550467384-4727497.jpeg", + "https://www.dabur.com/digital-annual-report-2022-23/images/products/real-juice.png", + "https://starbazarjapan.com/wp-content/uploads/2021/02/Real-Guava-Juice-1-Ltr-Dabur.jpg", + "https://m.media-amazon.com/images/I/81WApQQVI5L._SL1500_.jpg", + "https://m.media-amazon.com/images/I/81Slqxsjn6L._AC_CR0,0,0,0_SX704_SY660_.jpg", + "https://m.media-amazon.com/images/I/81tr9+Jiy1L._SY250_.jpg", + "https://m.media-amazon.com/images/I/71ec0r+5WKL._AC_CR0,0,0,0_SX352_SY330_.jpg" + ] + }, + { + "image_id": "dabur_gulabari_3c77c84e", + "product_name": "Dabur Gulabari 100g", + "image_url": "https://meds.myupchar.com/2998/Dabur-Gulabari-1.jpg", + "image_urls": [ + "https://meds.myupchar.com/2998/Dabur-Gulabari-1.jpg", + "https://thedesifood.com/media/Dabur-Gulabari-Body-Lotion-400-ml-2.webp", + "https://www.bigbasket.com/media/uploads/p/l/40237655-2_1-dabur-gulabari-moisturising-body-lotion-all-day-rose-glow-petal-soft-skin-100-organic-rose-oil-shea-butter.jpg", + "https://www.bigbasket.com/media/uploads/p/l/40237655-4_1-dabur-gulabari-moisturising-body-lotion-all-day-rose-glow-petal-soft-skin-100-organic-rose-oil-shea-butter.jpg", + "https://www.bigbasket.com/media/uploads/p/l/40237655-3_1-dabur-gulabari-moisturising-body-lotion-all-day-rose-glow-petal-soft-skin-100-organic-rose-oil-shea-butter.jpg", + "https://assets.myntassets.com/h_200,w_200,c_fill,g_auto/h_1440,q_100,w_1080/v1/assets/images/19817188/2025/6/5/7f9657a8-5457-4c73-91cc-00c07fe3a6f01749118347118-Dabur-Gulabari-Set-Of-3-Rose-Glow-Face-Cleanser-For-Balanced-1.jpg" + ] + }, + { + "image_id": "dabur_dabur_clove_toothpaste_150g", + "product_name": "Dabur Clove Toothpaste 150g", + "image_url": "https://www.bombayspices.ca/wp-content/uploads/2019/03/DABUR-CLOVE-TOOTHPASTE.jpg", + "image_urls": [ + "https://www.bombayspices.ca/wp-content/uploads/2019/03/DABUR-CLOVE-TOOTHPASTE.jpg", + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/assets/item/free/original/2TiAViIYy1-dabur_herbal_clove_cavity_protection_toothpaste_pack_of_200g_100g_300g_846914_0_0.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/G19790/dabur-herbl-clove-200g-cavity-protection-toothpaste-no-added-fluoride-parabens-6.4-1674201369.jpg", + "https://shayonauk.com/cdn/shop/products/DaburHerbalCloveToothpaste100MlWM.jpg?v=1681862128", + "https://cdn01.pharmeasy.in/dam/products_otc/G19790/dabur-herbl-clove-200g-cavity-protection-toothpaste-no-added-fluoride-parabens-6.5-1674201214.jpg", + "https://i1.wp.com/spicetown.fi/wp-content/uploads/Dabur-Herbal-Toothpaste-Clove-100ml.jpg?fit=1020,1200&ssl=1", + "https://afro-indian.nl/image/Dabur_Herbal_Clove_Toothpaste_100ml__.jpg", + "https://www.naturesgiftca.com/cdn/shop/files/daburclovetoothpaste.webp?v=1765388690&width=1946", + "https://newgenerationfoods.ca/wp-content/uploads/2020/12/Dabur-clove-scaled.jpg", + "https://api.daburinternational.com/wp-content/uploads/2024/10/Dabur-Herbal-Advanced-Clove-Gel-Main.jpg" + ] + }, + { + "image_id": "dabur_real_mixed_fruit_juice_1l", + "product_name": "Real Mixed Fruit Juice 1L", + "image_url": "https://www.gobuzzaar.com/uploads/2025/07/800x800/dabur-real-masala-mixed-fruit-juice-1l.jpg", + "image_urls": [ + "https://www.gobuzzaar.com/uploads/2025/07/800x800/dabur-real-masala-mixed-fruit-juice-1l.jpg", + "https://avionmart.com/wp-content/uploads/2024/06/Dabur-Real-Mixed-Fruit-Juice-1L.png", + "https://desideliveryvt.com/wp-content/uploads/2025/04/dabur-real-mixed-fruit-juice-1.jpg", + "https://www.zangmart.com/wp-content/uploads/2022/09/Dabur-Real-Fruit-Power-Mixed-Fruit-Juice-1-L-Buy-1-Get-1-Free-900x900.jpg", + "https://sapanpapadwala.co.in/wp-content/uploads/2020/11/Real-Fruit-Power-Mixed-Fruit-Juice-1592295355-10009767-1.jpg", + "https://www.southasiancentral.ca/wp-content/uploads/dabur-real-mixed-fruit-1l-4796008920902.webp", + "https://images.price.tools/images/dabur-real-fruit-power-mixed-fruit-s-87ct38NU.jpg", + "https://indianstaples.com/wp-content/uploads/2025/04/dabur-real-mix-fruit-1l.png", + "https://trstcs.com/wp-content/uploads/2023/09/Real-Juice-5-.jpg", + "https://m.media-amazon.com/images/I/718s5Hnzt-L._SL1500_.jpg" + ] + }, + { + "image_id": "gulabari_milk_honey_56fdc0f6", + "product_name": "Gulabari Milk & Honey 1L", + "image_url": "https://meds.myupchar.com/2998/Dabur-Gulabari-1.jpg", + "image_urls": [ + "https://meds.myupchar.com/2998/Dabur-Gulabari-1.jpg", + "https://meds.myupchar.com/2997/Dabur-Gulabari.jpg", + "https://meds.myupchar.com/7139/dabur-gulabari-cream-in-hindi-37121985-235480.jpg", + "https://img.thecdn.in/309224/dabur-gulabari-cream-1693570389202_SKU-3173_0.jpg?width=600&format=webp", + "https://5.imimg.com/data5/LA/GS/GLADMIN-43693562/dabur-gulabari-moisturizer-1000x1000.png" + ] + }, + { + "image_id": "chyawanprash_4705ca81", + "product_name": "Chyawanprash 20g", + "image_url": "https://example.com/chyawanprash.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/I07432/dabur-chyawanprash-awaleha-health-food-jar-of-500-g-2-1643105467.jpg?dim=700x0&dpr=1&q=100", + "https://pordeshi.com/wp-content/uploads/2023/04/Dabur-Chyawanprash-Awaleha-price.jpg" + ] + }, + { + "image_id": "dabur_dabur_red_toothpaste_200g", + "product_name": "Dabur Red Toothpaste 200g", + "image_url": "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "image_urls": [ + "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-SPER1-000280/dabur-odomos-naturals-mosquito-repellent-spray-100ml_dabur-odomos-naturals-mosquito-repellent-spray-100ml--TM-SPER1-000280_1.png?width=320", + "https://sinin.com.bd/wp-content/uploads/2023/01/Dabur-Odomos-Naturals-Non-Sticky-Mosquito-Repellent-Cream.jpg", + "https://australiaherbalstore.com/uploads/products/Dabur_Odomos_Naturals_Mosquito_Repellant_Lotion1.jpg", + "https://i5.walmartimages.com/seo/Dabur-Odomos-Mosquito-Repellent-Lotion-Natural-60ml-Clinically-Tested-for-Full-Day-Protection_4ef81766-f6d7-4049-9d42-f9655d6a512a.7be63d46fbe85f826f6a78e9c50d1fdb.jpeg?odnHeight=573&odnWidth=573&odnBg=FFFFFF" + ] + }, + { + "image_id": "dabur_sanifresh_100g", + "product_name": "SaniFresh 100g", + "image_url": "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "https://m.media-amazon.com/images/I/71V4i6BG9KL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71ijL19ik-L._SL1158_.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/f7534105-0f00-47c2-a353-2290238f505b.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/71wcxUZ+a2L._SL1342_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/e37c1691-5e27-406b-87bb-ebd69a345212.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/dabur-ayurveda-products-1200x686.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/Dabur-Ayurveda-Products-1.jpg", + "https://www.iconsofindianbusiness.com/files/DABUR_PRODUCTS.jpg" + ] + }, + { + "image_id": "dabur_kabz_over_100g", + "product_name": "KABZ OVER 100g", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/703/0482/front_en.4.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/703/0482/front_en.4.400.jpg", + "http://www.cbeis.zju.edu.cn/_upload/article/images/b8/a9/26c1eeec4e75b529fdb2ab4ea117/447ede22-5dd8-4825-9903-2782181da600_d.jpg" + ] + }, + { + "image_id": "red_dabur_oral_care_toothpaste_1af95921", + "product_name": "Red Dabur Oral Care Toothpaste 4g", + "image_url": "https://example.com/red-dabur.jpg", + "image_urls": [ + "http://cdn.shopify.com/s/files/1/0549/7208/8504/products/Dabur_Red_Toothpaste_200g_200x200_1bd9dcd6-54e9-4f89-9321-68ee1f175a24.jpg?v=1620574614", + "https://assets.truemeds.in/Images/ProductImage/TM-TOTE1-000079/DABUR-RED-TOOTHPASTE-200-GM_dabur-red-toothpaste-200-gm--TM-TOTE1-000079_1.png?tr=cm-pad_resize", + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-416-416,pr-true,f-auto,q-40,dpr-2/cms/product_variant/8915233d-3f3d-426e-a024-250037da7e7d/Dabur-Red-Toothpaste.jpeg", + "https://yenmart.in/wp-content/uploads/2024/08/Dabur-Red-Toothpaste-750g-250gx3-1.jpg", + "https://5.imimg.com/data5/SELLER/Default/2024/1/375241862/VJ/BL/MW/27370585/200g-dabur-red-toothpaste-1000x1000.jpg", + "https://proudlymadeinindia.com/wp-content/uploads/2026/04/Dabur-Red-Toothpaste-768x644.png", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Red-Ayurvedic-Toothpaste-200gm.jpg", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Red-Toothpaste-100g.jpg.webp", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Red-Toothpaste-100g.png.webp", + "https://i5.walmartimages.com/seo/Dabur-Red-Herbal-Toothpaste-Oral-Care-Strong-Teeth-7-05-oz-200-grams_ee466dcc-4151-421a-80a8-6ec415d863d9.17a917d7cfbf349c81f01b7d4544f55e.jpeg" + ] + }, + { + "image_id": "dabur_odomos_a27f3921", + "product_name": "Dabur Odomos 100g", + "image_url": "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "image_urls": [ + "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-mosquito-repellent-gel.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-SPER1-000280/dabur-odomos-naturals-mosquito-repellent-spray-100ml_dabur-odomos-naturals-mosquito-repellent-spray-100ml--TM-SPER1-000280_1.png?width=320", + "https://sinin.com.bd/wp-content/uploads/2023/01/Dabur-Odomos-Naturals-Non-Sticky-Mosquito-Repellent-Cream.jpg", + "https://australiaherbalstore.com/uploads/products/Dabur_Odomos_Naturals_Mosquito_Repellant_Lotion1.jpg", + "https://i5.walmartimages.com/seo/Dabur-Odomos-Mosquito-Repellent-Lotion-Natural-60ml-Clinically-Tested-for-Full-Day-Protection_4ef81766-f6d7-4049-9d42-f9655d6a512a.7be63d46fbe85f826f6a78e9c50d1fdb.jpeg?odnHeight=573&odnWidth=573&odnBg=FFFFFF" + ] + }, + { + "image_id": "dabur_anmol_gold_250g", + "product_name": "Anmol Gold 250g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.1-1674197015.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Jar-1633604311-10092057-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Bottle-1595325245-10073672-1.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.6-1674196895.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.2-1674196894.jpg", + "http://bake-bay.com/wp-content/uploads/2023/05/dabur_anmol_gold_coconut_oil.jpg", + "https://spicetown.fi/wp-content/uploads/Dabur-Anmol-Gold-Pure-Coconut-Oil-500-ml-1.jpg", + "http://rukmini1.flixcart.com/image/300/300/kd1zngw0/hair-oil/p/s/s/500-anmol-gold-pure-coconut-oil-500ml-dabur-original-imafufuwznehzawa.jpeg" + ] + }, + { + "image_id": "dabur_anmol_gold_100g", + "product_name": "Anmol Gold 100g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_2.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_2.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.1-1674197015.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Jar-1633604311-10092057-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Bottle-1595325245-10073672-1.jpg", + "http://bake-bay.com/wp-content/uploads/2023/05/dabur_anmol_gold_coconut_oil.jpg", + "https://spicetown.fi/wp-content/uploads/Dabur-Anmol-Gold-Pure-Coconut-Oil-500-ml-1.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/d4f5f5bb-96c4-4e3b-b595-6106babd3d5a.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/61uPuUCGZsL.jpg", + "https://m.media-amazon.com/images/I/616fTeeXfUL._SL1500_.jpg" + ] + }, + { + "image_id": "meswak_dabur_toothbrush_dd8c1f7a", + "product_name": "Meswak Dabur Toothbrush 40g", + "image_url": "https://example.com/meswak.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.1-1671741851.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.4-1671741853.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.6-1671741857.jpg", + "https://thedesifood.com/media/Dabur-Meswak-Toothpaste-(Free-Toothbrush)-300-g-2.webp", + "https://thedesifood.com/media/Dabur-Meswak-Toothpaste-(Free-Toothbrush)-300-g-4.webp", + "https://thedesifood.com/media/Dabur-Meswak-Toothpaste-(Free-Toothbrush)-300-g-5.webp", + "https://www.zangmart.com/wp-content/uploads/2022/09/Dabur-Meswak-Toothpaste-Free-Toothbrush-100-gm.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Meswak-Complete-Tooth--Gum-Care-Toothpaste-Free-Toothbrush-Worth-Rs-25-1742190595-10156681-1.jpg", + "https://shop4hotel.com/wp-content/uploads/2025/11/DABUR-MESWAK-2-800x800.webp", + "https://5.imimg.com/data5/SELLER/Default/2025/10/555702904/KD/LX/CT/213666265/dabur-meswak-hotel-dental-kit-500x500.webp" + ] + }, + { + "image_id": "dabur_ashwagandha_teblets_60tablets", + "product_name": "Ashwagandha teblets 60tablets", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-TACR1-060142/dabur-ashwagandha-tablet-60_dabur-ashwagandha-tablet-60--TM-TACR1-060142_1.png", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-TACR1-060142/dabur-ashwagandha-tablet-60_dabur-ashwagandha-tablet-60--TM-TACR1-060142_1.png", + "https://www.ayurbeat.com/wp-content/uploads/2023/06/Dabur-Ashwagandha-Tablet.jpg", + "https://rukminim1.flixcart.com/image/416/416/ktketu80/ayurvedic/a/f/2/ashwagandha-tablets-3-dabur-original-imag6vxxpp3gbw3y.jpeg?q=70", + "https://m.media-amazon.com/images/I/71HmLo+HqpL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/513FLIBbbWL._SL1001_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/6826bb2e-48bf-4541-94f9-094524a0457d.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/939a67a6-d3fb-4bd5-8664-650d95f794e7.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/37ae092f-bba5-4d28-bb99-5e87b549b288.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/f403c738-46d1-4b22-a4e0-4f8ac24d4c33.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://cdn.grofers.com/cdn-cgi/image/f=auto,fit=scale-down,q=85,metadata=none,w=480,h=480/app/images/products/full_screen/pro_461181.jpg" + ] + }, + { + "image_id": "dabur_chyawanprash_550g", + "product_name": "Chyawanprash 550g", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/0337/front_en.42.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/6241/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/6989/front_en.9.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/790/0631/front_en.10.400.jpg", + "https://images.openfoodfacts.org/images/products/394/820/704/0337/front_en.7.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/2102/front_fr.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/442/270/0789/front_en.21.400.jpg", + "https://images.openfoodfacts.org/images/products/890/410/947/0196/front_en.7.400.jpg", + "https://images.openfoodfacts.org/images/products/890/124/870/3291/front_en.5.400.jpg" + ] + }, + { + "image_id": "janam_ghutti_baby_oil_8eaee902", + "product_name": "Janam Ghutti Baby Oil 200ml", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/P10137/dabur-baby-oil-500ml-6.4-1671743442.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/P10137/dabur-baby-oil-500ml-6.4-1671743442.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/P10137/dabur-baby-oil-500ml-7-1671743437.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/P10137/dabur-baby-oil-500ml-6.5-1671743446.jpg", + "https://meds.myupchar.com/717/dabur-janam-ghutti-2.jpg", + "https://thedesifood.com/media/Dabur-Baby-Oil-200-ml-1.webp", + "http://woooys.in/wp-content/uploads/2022/01/dabur-baby-oil-1.jpg", + "https://5.imimg.com/data5/SELLER/Default/2023/11/363283076/OY/JI/YM/100674992/500ml-dabur-baby-oil-500x500.jpg", + "https://5.imimg.com/data5/SELLER/Default/2023/11/363292194/OV/OH/MB/100674992/200ml-dabur-baby-oil-1000x1000.jpg", + "https://ayurvedaproducts.co.uk/wp-content/uploads/2022/11/Janma-Ghutti-Dabur.webp", + "https://www.bombayspices.ca/wp-content/uploads/2019/03/DABUR-JANAM-GHUTI-1.jpg" + ] + }, + { + "image_id": "dabur_chyawanprash_575g", + "product_name": "Chyawanprash 575g", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "https://naxofoods.com/wp-content/uploads/2022/11/Dabur-Chyawanprash-500G.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/0337/front_en.42.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/6241/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/6989/front_en.9.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/790/0631/front_en.10.400.jpg", + "https://images.openfoodfacts.org/images/products/394/820/704/0337/front_en.7.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/5035/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/188/800/0682/front_en.3.400.jpg" + ] + }, + { + "image_id": "meswak_toothbrush_natural_brush_0586d5ab", + "product_name": "Meswak Toothbrush Natural Brush 50g", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.4-1671741853.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.4-1671741853.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.6-1671741857.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/U87670/dabur-meswak-toothpaste-with-toothbrush-300-gm-6.1-1671741851.jpg", + "https://thedesifood.com/media/Dabur-Meswak-Toothpaste-(Free-Toothbrush)-300-g-4.webp", + "https://thedesifood.com/media/Dabur-Meswak-Toothpaste-(Free-Toothbrush)-300-g-5.webp", + "https://thedesifood.com/media/Dabur-Meswak-Toothpaste-(Free-Toothbrush)-300-g-1.webp", + "http://rukmini1.flixcart.com/image/300/300/kqzj7gw0/toothpaste/3/o/j/449-meswak-toothpaste-toothbrush-family-pack-4-dabur-original-imag4vsgw6f52bgv.jpeg", + "https://5.imimg.com/data5/SELLER/Default/2025/1/477756243/SR/HY/RL/213666265/hotel-dental-kit-anchor-brush-with-dabur-meswak-10g-tooth-paste-for-hotel-hospital-1000x1000.webp", + "https://5.imimg.com/data5/SELLER/Default/2025/10/555702904/KD/LX/CT/213666265/dabur-meswak-hotel-dental-kit-500x500.webp" + ] + }, + { + "image_id": "dabur_chyawanprash_1kg", + "product_name": "Chyawanprash 1kg", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "https://naxofoods.com/wp-content/uploads/2022/11/Dabur-Chyawanprash-500G.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/0337/front_en.42.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/6241/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/6989/front_en.9.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/790/0631/front_en.10.400.jpg", + "https://images.openfoodfacts.org/images/products/394/820/704/0337/front_en.7.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/5035/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/188/800/0682/front_en.3.400.jpg" + ] + }, + { + "image_id": "dabur_amla_d70299ad", + "product_name": "Dabur Amla 1L", + "image_url": "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2/static/img/product/343963/dabur-amla-hair-oil-550ml-71_1_display_1690799032_290b8d74.jpg", + "image_urls": [ + "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2/static/img/product/343963/dabur-amla-hair-oil-550ml-71_1_display_1690799032_290b8d74.jpg", + "http://nehasupermarket.com/storage/products/dabur-amla-hair-oil-200ml-neha-supermarket-gallery3.jpg", + "https://images-cdn.ubuy.co.in/661b4540b30d370cfd6cf67f-dabur-brahmi-amla-hair-oil-natural.jpg", + "https://tiimg.tistatic.com/fp/1/008/342/dabur-amla-hair-oil-with-3-hair-benefits-937.jpg", + "https://farms2home.sg/wp-content/uploads/Dabur_Amla_Hair_Oil.jpg" + ] + }, + { + "image_id": "dabur_chyawanprash_2kg", + "product_name": "Chyawanprash 2kg", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "https://naxofoods.com/wp-content/uploads/2022/11/Dabur-Chyawanprash-500G.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/0337/front_en.42.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/6241/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/6989/front_en.9.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/790/0631/front_en.10.400.jpg", + "https://images.openfoodfacts.org/images/products/394/820/704/0337/front_en.7.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/5035/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/188/800/0682/front_en.3.400.jpg" + ] + }, + { + "image_id": "dabur_coconut_oil_500ml", + "product_name": "coconut oil 500ml", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000659/dabur-anmol-gold-pure-coconut-oil-500-ml_dabur-anmol-gold-pure-coconut-oil-500-ml--TM-OIIL1-000659_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000659/dabur-anmol-gold-pure-coconut-oil-500-ml_dabur-anmol-gold-pure-coconut-oil-500-ml--TM-OIIL1-000659_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_2.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.1-1674197015.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.2-1674196894.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Jar-1633604311-10092057-1.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Hair-Oil-Free-Dabur-Honey-20-g-1610532626-10081083-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Bottle-1595325245-10073672-1.jpg", + "http://bake-bay.com/wp-content/uploads/2023/05/dabur_anmol_gold_coconut_oil.jpg", + "https://spicetown.fi/wp-content/uploads/Dabur-Anmol-Gold-Pure-Coconut-Oil-500-ml-1.jpg" + ] + }, + { + "image_id": "dabur_coconut_oil_1l", + "product_name": "coconut oil 1L", + "image_url": "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2/static/img/product/340983/dabur-anmol-coconut-oil-1l_2_display_1686042681_83dd557d.jpg", + "image_urls": [ + "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2/static/img/product/340983/dabur-anmol-coconut-oil-1l_2_display_1686042681_83dd557d.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000659/dabur-anmol-gold-pure-coconut-oil-500-ml_dabur-anmol-gold-pure-coconut-oil-500-ml--TM-OIIL1-000659_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_2.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.1-1674197015.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.2-1674196894.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Jar-1633604311-10092057-1.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Hair-Oil-Free-Dabur-Honey-20-g-1610532626-10081083-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Bottle-1595325245-10073672-1.jpg", + "http://bake-bay.com/wp-content/uploads/2023/05/dabur_anmol_gold_coconut_oil.jpg" + ] + }, + { + "image_id": "dabur_dabur_amla_450ml", + "product_name": "Dabur Amla 450ml", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/K92640/dabur-amla-hair-oil-450ml-with-free-dabur-red-paste-100g-6.1-1681995600.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/K92640/dabur-amla-hair-oil-450ml-with-free-dabur-red-paste-100g-6.1-1681995600.jpg", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/f/f1/Amla-Hair-Oil-Dabur-1.JPG/960px-Amla-Hair-Oil-Dabur-1.JPG?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/e/e8/Amla-Hair-Oil-Dabur-2.JPG/960px-Amla-Hair-Oil-Dabur-2.JPG?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://desistore.dk/wp-content/uploads/2023/03/dabur-amla-gold-hair-oil.jpg", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/2/25/Amla%2C_hair_oil.JPG/960px-Amla%2C_hair_oil.JPG?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://ecombe.nahdionline.com/media/catalog/product/d/a/dabur-amla-hair-oil-makes-hair-long-strong-dark-300-ml-1100x1100.jpg", + "https://m.media-amazon.com/images/I/71FlO3G++cL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/81VOFJ4PW6L._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71TB+bYPBrL.jpg", + "https://m.media-amazon.com/images/I/81bdvtOl20L._AC_SL1500_.jpg" + ] + }, + { + "image_id": "dabur_dabur_aloe_vera_100g", + "product_name": "DABUR ALOE VERA 100g", + "image_url": "", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/871/285/700/5900/front_fr.132.400.jpg", + "https://images.openfoodfacts.org/images/products/871/285/700/5801/front_en.104.400.jpg", + "https://images.openfoodfacts.org/images/products/333/570/019/1305/front_fr.18.400.jpg", + "https://images.openfoodfacts.org/images/products/506/040/301/0315/front_en.28.400.jpg", + "https://images.openfoodfacts.org/images/products/871/285/700/4606/front_fr.40.400.jpg", + "https://images.openfoodfacts.org/images/products/880/904/142/0929/front_en.51.400.jpg", + "https://images.openfoodfacts.org/images/products/405/648/964/6877/front_fr.3.400.jpg", + "https://images.openfoodfacts.org/images/products/008/770/313/8322/front_en.42.400.jpg", + "https://images.openfoodfacts.org/images/products/503/513/921/3644/front_fr.56.400.jpg", + "https://images.openfoodfacts.org/images/products/880/904/142/0912/front_fr.18.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_aloe_vera_500g", + "product_name": "DABUR ALOE VERA 500g", + "image_url": "", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/871/285/700/5900/front_fr.132.400.jpg", + "https://images.openfoodfacts.org/images/products/871/285/700/5801/front_en.104.400.jpg", + "https://images.openfoodfacts.org/images/products/333/570/019/1305/front_fr.18.400.jpg", + "https://images.openfoodfacts.org/images/products/506/040/301/0315/front_en.28.400.jpg", + "https://images.openfoodfacts.org/images/products/871/285/700/4606/front_fr.40.400.jpg", + "https://images.openfoodfacts.org/images/products/880/904/142/0929/front_en.51.400.jpg", + "https://images.openfoodfacts.org/images/products/405/648/964/6877/front_fr.3.400.jpg", + "https://images.openfoodfacts.org/images/products/008/770/313/8322/front_en.42.400.jpg", + "https://images.openfoodfacts.org/images/products/503/513/921/3644/front_fr.56.400.jpg", + "https://images.openfoodfacts.org/images/products/880/904/142/0912/front_fr.18.400.jpg" + ] + }, + { + "image_id": "dabur_red_d47926d9", + "product_name": "Dabur Red 100g", + "image_url": "https://www.bigbasket.com/media/uploads/p/l/40287938-6_1-dabur-red-bae-fresh-gel-12-hour-freshness-super-saver-pack.jpg", + "image_urls": [ + "https://www.bigbasket.com/media/uploads/p/l/40287938-6_1-dabur-red-bae-fresh-gel-12-hour-freshness-super-saver-pack.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-TOTE1-000079/DABUR-RED-TOOTHPASTE-200-GM_dabur-red-toothpaste-200-gm--TM-TOTE1-000079_1.png?tr=cm-pad_resize", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Red-Toothpaste-100g.jpg.webp", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Red-Ayurvedic-Toothpaste-200gm.jpg.webp", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Red-Toothpaste-100g.png.webp", + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1500-1500,pr-true,f-auto,q-40,dpr-2/cms/product_variant/57eba30c-f13a-416e-81d1-1975b1b8aada/Dabur-Red-Gel-Toothpaste-For-Effective-Oral-Care-With-12-Hours-Of-Freshness.jpeg", + "https://i5.walmartimages.com/seo/Dabur-Red-Herbal-Toothpaste-Oral-Care-Strong-Teeth-7-05-oz-200-grams_ee466dcc-4151-421a-80a8-6ec415d863d9.17a917d7cfbf349c81f01b7d4544f55e.jpeg" + ] + }, + { + "image_id": "dabur_dabur_aloe_vera_250g", + "product_name": "DABUR ALOE VERA 250g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001194/dabur-aloe-vera-juice-1-litre_dabur-aloe-vera-juice-1-litre--TM-LINT1-001194_3.png?width=320", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001194/dabur-aloe-vera-juice-1-litre_dabur-aloe-vera-juice-1-litre--TM-LINT1-001194_3.png?width=320", + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001194/dabur-aloe-vera-juice-1-litre_dabur-aloe-vera-juice-1-litre--TM-LINT1-001194_1.png", + "https://cdn01.pharmeasy.in/dam/products_otc/U47191/dabur-aloe-vera-juice-1-litre-2-1641969489.jpg", + "https://img1.exportersindia.com/product_images/bc-full/2023/10/12173803/watermark/dabur-aloe-vera-juice-1695723643-7100801.jpeg", + "https://ntmedia.in/wp-content/uploads/2025/02/Dabur-Launches-100-Pure-Aloe-Vera-Gel.jpg", + "https://thedesifood.com/media/Dabur-Aloe-Vera-Juice-1000-ml-1.webp", + "https://thedesifood.com/media/Dabur-Aloe-Vera-Juice-1000-ml-2.webp", + "https://shayonauk.com/cdn/shop/products/DaburHerbalAloeVeraToothpaste100mlWM.jpg?v=1681861872", + "https://rukminim1.flixcart.com/image/416/416/kdj4xow0/ayurvedic/4/k/9/1-aloe-vera-juice-1-litre-dabur-original-imafuey4hezphk7f.jpeg?q=70", + "https://vitaminreviewer.com/wp-content/uploads/2024/01/dabur-herbolene-aloe-vera-gel-petroleum-jelly-enhanced-with-vitamin-e-for-deep-intensive-skin-moisturization-gentle-on-s-9.jpg" + ] + }, + { + "image_id": "dabur_dabur_clove_toothpaste_40g", + "product_name": "Dabur Clove Toothpaste 40g", + "image_url": "https://images.openbeautyfacts.org/images/products/890/120/703/4220/front_en.13.400.jpg", + "image_urls": [ + "https://images.openbeautyfacts.org/images/products/890/120/703/4220/front_en.13.400.jpg", + "https://cdn.shopify.com/s/files/1/0674/9578/8811/files/minimalistic-sunset-in-japan-poster-without-frame.jpg?v=1705090448", + "https://i.pinimg.com/originals/45/56/7c/45567ce96638b9523e513839519ce941.jpg", + "https://i.pinimg.com/originals/5e/f1/ae/5ef1ae7463c5f19dba12f3e3afabd536.jpg", + "https://i.pinimg.com/originals/25/50/8e/25508e5e7890389ce4f558cdfaf8e4aa.jpg", + "https://i.pinimg.com/originals/5f/bc/f7/5fbcf734fb57971956cde4d0e77bbaf2.jpg", + "https://i.pinimg.com/originals/3e/56/91/3e5691143805300a6d10395902aa51e2.jpg", + "https://hypesheriff.com/cdn/shop/products/0_1ccd0d71-366d-469a-8c6a-a3a4fbfb5b93.jpg?v=1761682063&width=1445", + "https://i.pinimg.com/736x/30/27/11/302711e568264bf2a41ba36766e65944.jpg", + "https://cdn.displate.com/artwork/857x1200/2020-12-17/65d92a54d3f70f5d904d0bdcd43770b2_cdc0dd421acdbef596d3df2b37dbe757.jpg" + ] + }, + { + "image_id": "dabur_dabur_clove_toothpaste_80g", + "product_name": "Dabur Clove Toothpaste 80g", + "image_url": "https://www.pakcosmetics.com/images/content/productimgLarge/DaburCavityProtectnCloveToothPaste.jpg", + "image_urls": [ + "https://www.pakcosmetics.com/images/content/productimgLarge/DaburCavityProtectnCloveToothPaste.jpg", + "https://www.bombayspices.ca/wp-content/uploads/2019/03/DABUR-CLOVE-TOOTHPASTE.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/G19790/dabur-herbl-clove-200g-cavity-protection-toothpaste-no-added-fluoride-parabens-6.4-1674201369.jpg", + "https://shayonauk.com/cdn/shop/products/DaburHerbalCloveToothpaste100MlWM.jpg?v=1681862128", + "https://i1.wp.com/spicetown.fi/wp-content/uploads/Dabur-Herbal-Toothpaste-Clove-100ml.jpg?fit=1020,1200&ssl=1", + "https://afro-indian.nl/image/Dabur_Herbal_Clove_Toothpaste_100ml__.jpg", + "https://hallans.co.uk/wp-content/uploads/2013/09/Dabur-Clove-Herbal-Toothpaste-100ml.jpg", + "https://yogimart.de/images/thumbs/0003436_dabur-herbal-clove-toothpaste-100ml.jpeg", + "https://www.naturesgiftca.com/cdn/shop/files/daburclovetoothpaste.webp?v=1765388690&width=1946", + "https://api.daburinternational.com/wp-content/uploads/2024/10/Dabur-Herbal-Advanced-Clove-Gel-Main.jpg" + ] + }, + { + "image_id": "dabur_dabur_cow_ghee_1l", + "product_name": "Dabur Cow Ghee 1l", + "image_url": "https://www.bbassets.com/media/uploads/p/l/40355801_1-dabur-a2-cow-ghee.jpg", + "image_urls": [ + "https://www.bbassets.com/media/uploads/p/l/40355801_1-dabur-a2-cow-ghee.jpg", + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-4272-2848,pr-true,f-auto,q-40,dpr-2/cms/product_variant/f8c499cd-31ba-44c0-89bf-7f9fcafff98a/Dabur-A2-Cow-Ghee-Rich-In-Vitamin-A-Antioxidant-Properties.jpeg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Pure-Cow-Ghee-Refill-1637748203-10092959-1.jpg", + "https://uvkart.com/static/uploads/products/dabur-cow-ghee-daanedaar-1766920083.webp", + "https://www.dealsmagnet.com/images/dabur-100-pure-cow-ghee-1l-o-18NN68Jt.jpg", + "https://pimwp.s3-accelerate.amazonaws.com/2021/01/Dabur-Cow-Ghee-.jpg", + "https://crm.ideallivs.com/wp-content/uploads/2025/10/Dabur-100-Organic-Cow-Ghee-470g-edited_4-e1761075775802.png", + "https://rukminim1.flixcart.com/image/128/128/kplisnk0/ghee/e/z/b/100-pure-cow-ghee-tetrapack-dabur-original-imag3shup7pxvczn.jpeg?q=70", + "https://pimwp.s3-accelerate.amazonaws.com/2021/01/dabur_ghee-696x392.jpg", + "https://dabur1884.com/cdn/shop/files/organic-ghee.jpg?v=1742567683&width=720" + ] + }, + { + "image_id": "red_vatika_161f8e51", + "product_name": "Red Vatika 200ml", + "image_url": "https://fuddins.com/wp-content/uploads/2023/03/Vatika-Coconut-oil-Free-Red-Dabur-Paste.jpg", + "image_urls": [ + "https://fuddins.com/wp-content/uploads/2023/03/Vatika-Coconut-oil-Free-Red-Dabur-Paste.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Vatika-Enriched-Coconut-Hair-Oil-Free-Dabur-Red-Toothpaste-40-g-1658989955-10102779-1.jpg", + "https://desicart.co.uk/cdn/shop/products/vatikaredoniondesicart.jpg?v=1670920921&width=1200" + ] + }, + { + "image_id": "dabur_dabur_glucoplus_400gm", + "product_name": "Dabur glucoplus 400gm", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/704/4168/front_en.3.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/704/4168/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/0001/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/701/9999/front_en.3.400.jpg", + "https://image.winudf.com/v2/image/Y29tLnF1aWNrc2hhcmUuUVMubXlhcHBsaWNhdGlvbl9zY3JlZW5zaG90c18wXzdiODYzNGJm/screen-0.jpg?fakeurl=1&type=.jpg", + "https://is4-ssl.mzstatic.com/image/thumb/Purple112/v4/de/b4/fc/deb4fcb7-4c7d-bfa5-2242-792347e61ace/AppIcon-1x_U007emarketing-0-7-0-0-85-220.png/1200x630wa.png", + "https://androidguias.com/wp-content/uploads/2025/09/Quick-Share-al-iPhone-2-1024x683.jpg", + "https://static1.howtogeekimages.com/wordpress/wp-content/uploads/2024/10/quickshare-from-a-pc-to-a-phone.jpg", + "https://static1.anpoimages.com/wordpress/wp-content/uploads/2024/10/quick-share-hero-google.jpg", + "https://i.ytimg.com/vi/yw-BH7Yk24w/maxresdefault.jpg", + "https://cdn.neowin.com/news/images/uploaded/2023/07/1688547925_screenshot_2023-07-05_at_2.34.13_pm.jpg" + ] + }, + { + "image_id": "dabur_dabur_gulabari_rose_glow_face_cleanser_100ml", + "product_name": "Dabur Gulabari Rose Glow Face Cleanser 100ml", + "image_url": "", + "image_urls": [ + "https://weather-jwa.jp/wp-content/themes/weather-x-wp/public/images/service/forecast/img_case02_photo02-3.png" + ] + }, + { + "image_id": "pudin_hara_208491ba", + "product_name": "Pudin Hara 500g", + "image_url": "https://cdn.netmeds.tech/v2/plain-cake-860195/netmed/wrkr/nmz/products/pictures/item/free/resize-w:400/netmeds/413876/0/8bl7duUZPU-KXQDF7IAe4-dabur_pudin_hara_antacid_syrup_170_ml_0.jpg", + "image_urls": [ + "https://cdn.netmeds.tech/v2/plain-cake-860195/netmed/wrkr/nmz/products/pictures/item/free/resize-w:400/netmeds/413876/0/8bl7duUZPU-KXQDF7IAe4-dabur_pudin_hara_antacid_syrup_170_ml_0.jpg", + "http://indianayurvedicproducts.com/cdn/shop/files/Dabur_Pudin_Hara_Pearls_2.jpg?v=1764910918", + "https://indianayurvedicproducts.com/cdn/shop/files/Dabur_Pudin_Hara_Pearls_1.jpg?v=1764910917&width=1240", + "https://assets.truemeds.in/Images/ProductImage/TM-CACR1-008050/dabur-pudin-hara-pearls-10_dabur-pudin-hara-pearls-10--TM-CACR1-008050_1.png?width=320", + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001218/dabur-pudin-hara-active-liquid-10ml_dabur-pudin-hara-active-liquid-10ml--TM-LINT1-001218_5.png?width=320", + "https://assets.truemeds.in/Images/ProductImage/TM-CACR1-008050/dabur-pudin-hara-pearls-10_dabur-pudin-hara-pearls-10--TM-CACR1-008050_3.png?width=320", + "https://www.themgshop.com/wp-content/uploads/2019/06/Dabur-Pudin-Hara-Pearls-1.jpg", + "https://manmohni.pk/cdn/shop/files/Dabur-Pudin-Hara-Digestive-Care-Tablets-Buy-Online-in-Pakistan-on-Manmohni_1200x.jpg?v=1718542132", + "https://manmohni.pk/cdn/shop/files/Dabur-Pudin-Hara-Digestive-Care-Tablets-Buy-Online-in-Pakistan-on-Manmohni-1--800x800.webp?v=1718542132", + "https://bgstores.in/wp-content/uploads/2020/07/PudinHara2.png" + ] + }, + { + "image_id": "dabur_dabur_honey_650g", + "product_name": "Dabur Honey 650g", + "image_url": "https://m.media-amazon.com/images/I/71O4OnjaHVL.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/71O4OnjaHVL.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/5365/front_en.14.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/5527/front_en.12.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/5372/front_en.21.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/5374/front_fr.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/6553/front_en.9.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/5370/front_en.6.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/5389/front_de.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/7879/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/705/1173/front_en.3.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_honey_50g", + "product_name": "Dabur Honey 50g", + "image_url": "", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/505/931/902/3762/front_fr.32.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/000/4440/front_fr.129.400.jpg", + "https://images.openfoodfacts.org/images/products/761/450/001/0013/front_en.362.400.jpg", + "https://images.openfoodfacts.org/images/products/841/007/660/0837/front_en.36.400.jpg", + "https://images.openfoodfacts.org/images/products/841/007/660/0790/front_en.28.400.jpg", + "https://images.openfoodfacts.org/images/products/501/127/304/0698/front_en.43.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/020/2662/front_fr.26.400.jpg", + "https://images.openfoodfacts.org/images/products/848/000/015/4361/front_es.65.400.jpg", + "https://images.openfoodfacts.org/images/products/505/290/973/2600/front_en.37.400.jpg", + "https://images.openfoodfacts.org/images/products/001/600/026/4694/front_en.319.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_meswak_300g", + "product_name": "Dabur meswak 300g", + "image_url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/a/a5/Lesser_Whitethroat_on_Meswak_DSC_2120_%281%29.jpg/960px-Lesser_Whitethroat_on_Meswak_DSC_2120_%281%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "image_urls": [ + "https://thumb.wikimedia.org/wikipedia/commons/thumb/a/a5/Lesser_Whitethroat_on_Meswak_DSC_2120_%281%29.jpg/960px-Lesser_Whitethroat_on_Meswak_DSC_2120_%281%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/a/af/Lesser_Whitethroat_on_Meswak_DSC_2120_%282%29.jpg/960px-Lesser_Whitethroat_on_Meswak_DSC_2120_%282%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/6/63/Lesser_Whitethroat_on_Meswak_DSC_2120_%284%29.jpg/960px-Lesser_Whitethroat_on_Meswak_DSC_2120_%284%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/41/Meswak.jpg/960px-Meswak.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/7/71/Small_Salmon_Arab_Colotis_amata_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1442_%287%29.jpg/960px-Small_Salmon_Arab_Colotis_amata_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1442_%287%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/b/b0/Small_Salmon_Arab_Colotis_amata_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1442_%288%29.jpg/960px-Small_Salmon_Arab_Colotis_amata_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1442_%288%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/16/Small_Salmon_Arab_Colotis_amata_laying_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1441_%285%29.jpg/960px-Small_Salmon_Arab_Colotis_amata_laying_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1441_%285%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/f/f2/Small_Salmon_Arab_Colotis_amata_laying_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1441_%286%29.jpg/960px-Small_Salmon_Arab_Colotis_amata_laying_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1441_%286%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://thumb.wikimedia.org/wikipedia/commons/thumb/d/d1/Small_Salmon_Arab_Colotis_amata_laying_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1441_%287%29.jpg/960px-Small_Salmon_Arab_Colotis_amata_laying_eggs_on_meswak_by_Dr._Raju_Kasambe_DSCN1441_%287%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "https://images.openbeautyfacts.org/images/products/890/120/703/3414/front_en.5.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_odomos_100g", + "product_name": "Dabur Odomos 100g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-SPER1-000280/dabur-odomos-naturals-mosquito-repellent-spray-100ml_dabur-odomos-naturals-mosquito-repellent-spray-100ml--TM-SPER1-000280_1.png", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-SPER1-000280/dabur-odomos-naturals-mosquito-repellent-spray-100ml_dabur-odomos-naturals-mosquito-repellent-spray-100ml--TM-SPER1-000280_1.png", + "https://www.indianproductsstore.com/uploads/products/dabur-odomos-naturals-nonsticky-mosquito-repellent-cream-50-gm.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/I05453/dabur-odomos-mosquitoes-repellent-spray-bottle-of-100-ml-2-1612781465.jpg", + "https://mophethonline.com/wp-content/uploads/2024/12/Dabur-Odomos-Advanced-Mosquito-Moisturizing-Repellent-Cream-50g-1-scaled.jpg", + "https://pharmily.co.ke/storage/files/2024/06/24/JtlpI_1719220157_odomos-dabur-mosquito-reppelent-creamjpg.jpg", + "https://sinin.com.bd/wp-content/uploads/2023/01/Odomos-Naturals-Mosquito-Repellent-Cream-600x600.jpg", + "https://m.media-amazon.com/images/I/71vT1Gfg4-L.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/7c0002e0-47aa-45a5-a2d2-bfbbcbc5222b.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/71-xa+XaJBL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/716gGn47N6L._SL1500_.jpg" + ] + }, + { + "image_id": "dabur_dabur_real_activ_100_juice_cranberry_1l", + "product_name": "Dabur Real activ 100% juice cranberry 1l", + "image_url": "https://www.bbassets.com/media/uploads/p/l/40327120_2-real-activ-activ-100-juice-cranberry.jpg", + "image_urls": [ + "https://www.bbassets.com/media/uploads/p/l/40327120_2-real-activ-activ-100-juice-cranberry.jpg", + "https://www.bbassets.com/media/uploads/p/l/40327120_1-real-activ-activ-100-juice-cranberry.jpg", + "https://www.bbassets.com/media/uploads/p/l/40327120_3-real-activ-activ-100-juice-cranberry.jpg", + "https://m.media-amazon.com/images/I/71TuykT862L._SY522_.jpg", + "https://m.media-amazon.com/images/I/717GzS1P-uL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/81TWPIcuPzL.jpg", + "https://m.media-amazon.com/images/I/81ATHQc9MoL._SL1500_.jpg", + "https://images.openfoodfacts.org/images/products/890/188/800/8411/front_en.3.400.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/8bf9bd4d-d1f1-4509-825f-2b2b0c9996e0.__CR0,0,970,600_PT0_SX970_V1___.png", + "https://smartdoko.com/storage/products/resized/1766660898_8133.jpg" + ] + }, + { + "image_id": "dabur_dabur_vatika_250g", + "product_name": "Dabur Vatika 250g", + "image_url": "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2,q-40/static/img/product/340978/dabur-vatika-enriched-coconut-hair-oil-300ml_1_display_1747976264_6c4205cd.jpg", + "image_urls": [ + "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2,q-40/static/img/product/340978/dabur-vatika-enriched-coconut-hair-oil-300ml_1_display_1747976264_6c4205cd.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Vatika-Enriched-Coconut-Hair-Oil-1611205544-10081331-1.jpg", + "https://saajkonna.com/wp-content/uploads/2022/03/Dabur-Vatika-Enriched-Coconut-Hair-Oil-150ml-1.jpg", + "https://www.kom-dami.com/wp-content/uploads/2021/05/Dabur-Vatika-Enriched-Coconut-Hair-Oil-150ml.jpg", + "https://images-cdn.ubuy.co.in/647156c9b99d43282b3994c2-dabur-vatika-naturals-enriched-hair-oil.jpg", + "https://www.banglashoppers.com/media/catalog/product/cache/de79bc5569b5032044bc83e7c1343c50/d/a/dabur_vatika_naturals_coconut_enriched_hair_oil_300ml.jpg", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Vatika-Coconut-Enriched-Volume-Thickness-Hair-Oil-200ml.jpg", + "https://m.media-amazon.com/images/I/61WvPJcbVUL.jpg", + "https://m.media-amazon.com/images/I/81esK2ovE2L._SL1500_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/bf0cd7e8-f3c0-49f5-bae4-6b5d28961d19.__CR0,0,970,600_PT0_SX970_V1___.jpg" + ] + }, + { + "image_id": "dabur_dabur_vatika_100g", + "product_name": "Dabur Vatika 100g", + "image_url": "https://www.waangoo.com/cdn/shop/products/0020000_dabur-vatika-coconut-enriched-hair-oil.jpg?v=1694714010", + "image_urls": [ + "https://www.waangoo.com/cdn/shop/products/0020000_dabur-vatika-coconut-enriched-hair-oil.jpg?v=1694714010", + "https://asset.sastasundar.com/incom/images/product/Dabur-Vatika-Enriched-Coconut-Hair-Oil-1611205544-10081331-1.jpg", + "https://saajkonna.com/wp-content/uploads/2022/03/Dabur-Vatika-Enriched-Coconut-Hair-Oil-150ml-1.jpg", + "https://2.bp.blogspot.com/-JDYYNvtAxns/Vgk8O8BOMgI/AAAAAAAAF3w/RrOrQIgUquM/s1600/dabur+vatika+hair+oil+review.jpg", + "https://images-cdn.ubuy.co.in/647156c9b99d43282b3994c2-dabur-vatika-naturals-enriched-hair-oil.jpg", + "https://www.banglashoppers.com/media/catalog/product/cache/de79bc5569b5032044bc83e7c1343c50/d/a/dabur_vatika_naturals_coconut_enriched_hair_oil_300ml.jpg", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Vatika-Coconut-Enriched-Volume-Thickness-Hair-Oil-200ml.jpg", + "https://m.media-amazon.com/images/I/61WvPJcbVUL.jpg", + "https://m.media-amazon.com/images/I/81esK2ovE2L._SL1500_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/bf0cd7e8-f3c0-49f5-bae4-6b5d28961d19.__CR0,0,970,600_PT0_SX970_V1___.jpg" + ] + }, + { + "image_id": "dabur_dabur_vatika_500g", + "product_name": "Dabur Vatika 500g", + "image_url": "https://www.waangoo.com/cdn/shop/products/0020000_dabur-vatika-coconut-enriched-hair-oil.jpg?v=1694714010", + "image_urls": [ + "https://www.waangoo.com/cdn/shop/products/0020000_dabur-vatika-coconut-enriched-hair-oil.jpg?v=1694714010", + "https://asset.sastasundar.com/incom/images/product/Dabur-Vatika-Enriched-Coconut-Hair-Oil-1611205544-10081331-1.jpg", + "https://saajkonna.com/wp-content/uploads/2022/03/Dabur-Vatika-Enriched-Coconut-Hair-Oil-150ml-1.jpg", + "https://2.bp.blogspot.com/-JDYYNvtAxns/Vgk8O8BOMgI/AAAAAAAAF3w/RrOrQIgUquM/s1600/dabur+vatika+hair+oil+review.jpg", + "https://images-cdn.ubuy.co.in/647156c9b99d43282b3994c2-dabur-vatika-naturals-enriched-hair-oil.jpg", + "https://www.banglashoppers.com/media/catalog/product/cache/de79bc5569b5032044bc83e7c1343c50/d/a/dabur_vatika_naturals_coconut_enriched_hair_oil_300ml.jpg", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Vatika-Coconut-Enriched-Volume-Thickness-Hair-Oil-200ml.jpg", + "https://m.media-amazon.com/images/I/61WvPJcbVUL.jpg", + "https://m.media-amazon.com/images/I/81esK2ovE2L._SL1500_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/bf0cd7e8-f3c0-49f5-bae4-6b5d28961d19.__CR0,0,970,600_PT0_SX970_V1___.jpg" + ] + }, + { + "image_id": "chyawanprash_100_natural_a110cae9", + "product_name": "Chyawanprash 100% Natural 70g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-PANG1-000242/dabur-chyawanprash-awaleha-2000-gm_dabur-chyawanprash-awaleha-2000-gm--TM-PANG1-000242_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-PANG1-000242/dabur-chyawanprash-awaleha-2000-gm_dabur-chyawanprash-awaleha-2000-gm--TM-PANG1-000242_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://cdn01.pharmeasy.in/dam/products_otc/E35095/dabur-honey-100-pure-500g-with-free-dabur-chyawanprash-250g-6.4-1678971299.jpg" + ] + }, + { + "image_id": "dabur_dabur_vatika_enrichd_hair_ol_300ml", + "product_name": "Dabur Vatika ENRICHd HAIR OL 300ml", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/703/8440/front_en.4.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/703/8440/front_en.4.400.jpg", + "https://i1.wp.com/ricksfreeautorepairadvice.com/wp-content/uploads/2022/05/Subaru-power-window-wiring-diagram-1024x625.jpg?strip=all" + ] + }, + { + "image_id": "dabur_daburred_paste_for_teeth_gums_600g", + "product_name": "DaburRED PASTE FOR TEETH & GUMS 600g", + "image_url": "https://images.openbeautyfacts.org/images/products/890/120/702/7420/front_en.9.400.jpg", + "image_urls": [ + "https://images.openbeautyfacts.org/images/products/890/120/702/7420/front_en.9.400.jpg", + "https://down-id.img.susercontent.com/file/id-11134201-23030-4hcyquexibovff" + ] + }, + { + "image_id": "dabur_dant_rakshak_350g", + "product_name": "Dant Rakshak 350g", + "image_url": "https://res.retailershakti.com/incom/images/product/Dabur-Dant-Rakshak-Ayurvedic-Paste-1611212264-10081376-4.jpg", + "image_urls": [ + "https://res.retailershakti.com/incom/images/product/Dabur-Dant-Rakshak-Ayurvedic-Paste-1611212264-10081376-4.jpg", + "https://images.price.tools/images/dabur-dant-rakshak-toothpaste-l-ynpOWmNU.jpg", + "https://images.apollo247.in/pub/media/catalog/product/4/0/40198111-5_1-dabur-dant-rakshak-paste.jpg", + "http://www.zevikahealth.com/cdn/shop/files/Dant_Rakshak_1_600x600_9c7a6267-6b9e-4c51-a0f2-f04bc5c4a8a0.png?v=1761477564", + "https://rukminim1.flixcart.com/image/416/416/kehfi4w0/toothpaste/a/2/e/80-dant-rakshak-paste-dabur-original-imafv5bess7jzz5d.jpeg?q=70", + "http://rukmini1.flixcart.com/image/300/300/xif0q/toothpaste/x/0/8/175-dant-rakshak-ayurvedic-paste-toothpaste-pack-of-175g-1-original-imaggz2pw49y7v67.jpeg", + "https://m.media-amazon.com/images/I/61lAAZZnjJL._SL1020_.jpg", + "https://m.media-amazon.com/images/S/aplus-media/vc/073b8b2f-f494-4be7-a2ea-f4bca907a249.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/81WrE6J5SaL._SX679_.jpg", + "https://m.media-amazon.com/images/I/81WrE6J5SaL._SL1500_.jpg" + ] + }, + { + "image_id": "dabur_glucon_d_cbd_500g", + "product_name": "Glucon D cbd 500g", + "image_url": "https://tiimg.tistatic.com/fp/1/008/768/dabur-glucon-d-590.jpg", + "image_urls": [ + "https://tiimg.tistatic.com/fp/1/008/768/dabur-glucon-d-590.jpg", + "https://m.media-amazon.com/images/I/81oIFDGqdoL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71MpaiGl+HL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/6112odSJKjL._SL1200_.jpg", + "https://images.openfoodfacts.org/images/products/890/154/200/1703/front_en.13.400.jpg", + "https://images.openfoodfacts.org/images/products/890/154/200/1703/front_en.13.full.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-000885/glucose-d-powder-500-gm_glucose-d-powder-500-gm--TM-POER1-000885_1.png?width=240", + "https://thespicebazaar.ca/wp-content/uploads/2025/05/ITEM-67-600x720.jpg", + "https://dukaan.b-cdn.net/1000x1000/webp/projecteagle/images/87669985-7314-4149-83c1-17d7c548fdcb.jpg" + ] + }, + { + "image_id": "dabur_glucose_d_125g", + "product_name": "Glucose D 125g", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/705/3160/front_en.4.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/705/3160/front_en.4.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/8304/front_fr.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/705/3467/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/4168/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/701/7025/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/2424/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/600/2004/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/600/1984/front_en.3.400.jpg", + "https://freepngimg.com/thumb/earth_day/56579-8-amusement-park-free-transparent-image-hd.png", + "https://art.ngfiles.com/images/94000/94846_gordyh_park-background.png?f1270566376" + ] + }, + { + "image_id": "dabur_hajmola_100g", + "product_name": "Hajmola 100g", + "image_url": "https://m.media-amazon.com/images/I/61lYigTKtCL.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/61lYigTKtCL.jpg" + ] + }, + { + "image_id": "dabur_hajmola_500g", + "product_name": "Hajmola 500g", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/790/0839/front_en.15.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/790/0839/front_en.15.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/706/2865/front_en.23.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/701/9234/front_fr.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/7545/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/000/008/900/4869/front_en.4.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/651/3005/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/610/4586/front_fr.21.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/4856/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/394/820/701/9234/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/896/110/028/6263/front_fr.4.400.jpg" + ] + }, + { + "image_id": "dabur_honitus_22e181b9", + "product_name": "Dabur Honitus 1L", + "image_url": "https://cdn01.pharmeasy.in/dam/products_otc/P95960/dabur-honitus-mixed-fruit-syrup-for-cold-and-cough-100-ml-3-1632780156.jpg", + "image_urls": [ + "https://cdn01.pharmeasy.in/dam/products_otc/P95960/dabur-honitus-mixed-fruit-syrup-for-cold-and-cough-100-ml-3-1632780156.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/P95960/dabur-honitus-mixed-fruit-syrup-for-cold-and-cough-100-ml-2-1671741727.jpg", + "https://www.zevikahealth.com/cdn/shop/files/Dabur_Honitus_Ayurvedic_Cough_Syrup_100ml_1.png?v=1747916429&1024w", + "https://cdn01.pharmeasy.in/dam/products_otc/P95960/dabur-honitus-mixed-fruit-syrup-for-cold-and-cough-100-ml-2-1632780159.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/U23384/dabur-honitus-herbal-cough-remedy-pack-of-2-2-1672038546.jpg?dim=700x0&dpr=1&q=100" + ] + }, + { + "image_id": "dabur_hand_sanitizer_250ml", + "product_name": "Hand Sanitizer 250ml", + "image_url": "http://microsidd.com/cdn/shop/products/0002193_daburhandsanitizer250ml.jpg?v=1685446675", + "image_urls": [ + "http://microsidd.com/cdn/shop/products/0002193_daburhandsanitizer250ml.jpg?v=1685446675", + "https://img1.exportersindia.com/product_images/bc-full/2021/1/8249962/hand-sanitizer-1610345533-5684695.jpeg", + "https://1.bp.blogspot.com/-NIw5EpGjEuQ/XvgL-rxOMOI/AAAAAAAAFVw/H0ajoB1tLO4Dvw0WgYN5SzyakkK0oWQggCLcBGAsYHQ/s1600/Dabur+Sanitize+Hand+Sanitizer+Alcohol+Based+Sanitizer.jpg", + "https://stylesatlife.com/wp-content/uploads/2020/10/Dabur-Sanitize-Hand-Sanitizer-5.jpg", + "https://bk.shajgoj.com/storage/2020/04/Hand-Sanitizer-30ml-800.jpg", + "https://i.chaldn.com/_mpimage/dabur-sanitize-hand-sanitizer-60-ml?src=https://eggyolk.chaldal.com/api/Picture/Raw?pictureId=86119&q=best&v=1", + "https://m.media-amazon.com/images/I/61RrUB+TNtL._SL1500_.jpg", + "https://images.openbeautyfacts.org/images/products/890/120/703/3872/front_en.3.400.jpg", + "https://m.media-amazon.com/images/S/aplus-media/vc/902ca6fd-0147-4f36-ab68-597a3375fb08.__CR0,0,970,600_PT0_SX970_V1___.png", + "https://www.healthcheckup.co.in/uploads/amazonproduct/1603882770dabur.jpg" + ] + }, + { + "image_id": "dabur_koolerz_mango_135ml", + "product_name": "Koolerz Mango 135ml", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/704/6810/front_en.8.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/704/6810/front_en.8.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/2058/front_en.3.400.jpg", + "https://www.tutkit.com/storage/media/help/3369/3369-suesse-tiere-im-zoo-elefant-und-giraffe-zum-ausmalen-kostenlos-feat.jpg", + "https://i.pinimg.com/originals/71/eb/cd/71ebcd925ad19178bfd9dae1ce89cd59.png", + "https://4.bp.blogspot.com/-WEVKm395amQ/VxRtqJUEsCI/AAAAAAAAALg/OW7aSUoiN6km-wladHwIlSMObSXfgyXMQCLcB/s1600/img151.jpg", + "https://4.bp.blogspot.com/-pI66dHwCCM4/UKrflf2vfPI/AAAAAAAAAvw/5Alb5AdymaM/s1600/Jerapah-Memandangi-Bunga.jpg", + "https://3.bp.blogspot.com/-_loeaBZjY6A/VQMvD57t13I/AAAAAAAAADg/OYeIcBb0xvk/s1600/anak-jerapah-lucu.gif", + "https://4.bp.blogspot.com/-30Gotz_T69A/Vc7aaZ_rBHI/AAAAAAAAAPI/Iyv0_XI8DeM/s1600/boneka-anak-jerapah.gif", + "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2-srrutJ7Y4PUfQOYj80-ZJQfN1tl0NaUYUtpvssizxl9HDZeFhGOn9y1FvxX2xnrdMjE-9VZ6Lkib1H7Q8Ec6OU59csWZtdguWJ0k7wivmAQHjNxBqBDOyl-WU2RBU0LMeLPtOJNeO3z/s1600/10.jpg", + "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMWqcLqg2ERjxzh15osX79Zd6D3O_vS_BwO-HpKskX5z6pEOzFnmXpv-8GFq-KjddQUc2O3WDjAWxT864yJhJlMNmLYspmQNF8FrHHLXVA4oi3lT5q7NwwNazFWuhcE0g179ffXY3PBuFx/s1600/Jerapah-Binatang-Berleher-Panjang.jpg" + ] + }, + { + "image_id": "dabur_kabz_over_250g", + "product_name": "KABZ OVER 250g", + "image_url": "", + "image_urls": [ + "https://heliau.com.au/wp-content/uploads/2023/09/K2_DIESEL_FORKLIFT.jpg" + ] + }, + { + "image_id": "dabur_janam_ghutti_ebd6efe8", + "product_name": "Dabur Janam Ghutti 250g", + "image_url": "https://meds.myupchar.com/710/dabur-janam-ghutti.jpg", + "image_urls": [ + "https://meds.myupchar.com/710/dabur-janam-ghutti.jpg", + "https://ayurvedaproducts.co.uk/wp-content/uploads/2022/11/Janma-Ghutti-Dabur.webp", + "https://thedesifood.com/media/Dabur-Janam-Ghunti-125-ml-1.webp", + "https://img500.exportersindia.com/product_images/bc-500/2024/4/10567865/100ml-shishu-janam-ghutti-syrup-1714392580-7387457.jpeg", + "https://www.beingtheparent.com/wp-content/uploads/2021/11/janam-ghutti-2.jpg", + "https://g.sdlcdn.com/imgs/e/t/2/Mother-Sparsh-Baby-Janam-Ghutti-SDL834238467-2-7bf4e.jpg" + ] + }, + { + "image_id": "dabur_lemoneez_250ml", + "product_name": "lemoneez 250ml", + "image_url": "https://www.bbassets.com/media/uploads/p/l/100005699_5-dabur-hommade-lemoneez-goodness-of-33-lemons.jpg", + "image_urls": [ + "https://www.bbassets.com/media/uploads/p/l/100005699_5-dabur-hommade-lemoneez-goodness-of-33-lemons.jpg", + "https://jagsfresh-bucket.s3.amazonaws.com/media/package/img_two/2019-09-02/dabur_lemoneez_250ml_70rs_2.jpg", + "https://www.baskethunt.com/wp-content/uploads/2021/02/dabur-lemoneez-syrup-250-ml-bottle-1-20200723.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Hommade-Lemoneez-Lemon-Juice-1651136611-10098935-4.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Hommade-Lemoneez-Lemon-Juice-1651136606-10098935-1.jpg", + "https://tiimg.tistatic.com/fp/1/004/657/dabur-hommade-lemoneez-374.jpg", + "https://tiimg.tistatic.com/fp/1/008/184/alcohol-free-sour-and-refreshing-lemon-juice-concentrate-dabur-lemoneez--108.jpg", + "https://rukminim1.flixcart.com/image/832/832/j65cnm80/paste-puree/v/5/z/250-lemoneez-glass-bottle-dabur-hommade-original-imaewzj8szdczhuh.jpeg?q=70", + "https://m.media-amazon.com/images/I/71cdjcxCeAL._SL1500_.jpg", + "https://asset.sastasundar.com/incom/images/product/DABUR-HONEY-1439212711-10020052.jpg" + ] + }, + { + "image_id": "dabur_nature_care_isabgol_375g", + "product_name": "Nature care isabgol 375g", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/700/1826/front_en.3.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/700/1826/front_en.3.400.jpg", + "https://pngate.com/wp-content/uploads/2025/03/x-app-icon-png-social-media-dark-rounded-square-22.png" + ] + }, + { + "image_id": "dabur_roghan_badam_shireen_100g", + "product_name": "Roghan Badam Shireen 100g", + "image_url": "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/byqAXaPbWX-dabur_roghan_badam_shireen_badam_tail_100_ml_free_dabur_honey_100_g_1s_675324_0_0.jpg", + "image_urls": [ + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/byqAXaPbWX-dabur_roghan_badam_shireen_badam_tail_100_ml_free_dabur_honey_100_g_1s_675324_0_0.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/B48341/dabur-roghan-badam-shireen-tail-100ml-with-free-dabur-honey-100g-2-1681722117.jpg?dim=700x0&dpr=1&q=100", + "https://res.retailershakti.com/incom/images/product/Dabur-Roghan-Badam-Shireen-Badam-Tail-1613977200-10082779-2.jpg", + "https://uvkart.com/static/uploads/products/dabur-roghan-badam-shireen-oil-1768745105.webp", + "https://thedesifood.com/media/Dabur-Roghan-Badam-Shireen-100-ml-1.webp", + "https://getkuwa.com/cdn/shop/files/Dabur-Roghan-Badam-Shireen-Badam-Tail-Free-Dabur-Honey-100-g-1613977189-10082776-2.jpg?v=1692723485", + "https://velspices.com.au/cdn/shop/files/dabur_roghan_badam_shireen_100ml.png?v=1699356798", + "http://velspices.com.au/cdn/shop/files/dabur_roghan_badam_shireen_50ml.png?v=1699356797", + "https://fineindia.ca/cdn/shop/files/Dabur-Roghan-Badam-Shireen-_100-ml_grande_9033be07-8338-49bd-a7c7-36ef44f6f8a9.webp?v=1756418268&width=1946", + "https://siddhastore.in/wp-content/uploads/2025/07/258.Roghan-Badam-Shireen-250x250.jpg" + ] + }, + { + "image_id": "dabur_roghan_badam_shireen_100ml", + "product_name": "Roghan Badam Shireen 100ml", + "image_url": "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/assets/item/free/original/lziDDlwxu7-dabur_roghan_badam_shireen_badam_tail_25_ml_free_dabur_honey_20_g_1s_588232_0_1.jpg", + "image_urls": [ + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/assets/item/free/original/lziDDlwxu7-dabur_roghan_badam_shireen_badam_tail_25_ml_free_dabur_honey_20_g_1s_588232_0_1.jpg", + "https://cdn.nmz5.de/v2/plain-cake-860195/netmed/wrkr/products/assets/item/free/resize-w:400/EPkQAGRTaG-dabur_roghan_badam_shireen_badam_tail_25_ml_free_dabur_honey_50_g_1_s_835486_0_0.jpg", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000886/dabur-roghan-badam-shireen-tail-100ml-with-dabur-honey-100gm-free_image_3.png?width=320", + "https://getkuwa.com/cdn/shop/files/Dabur-Roghan-Badam-Shireen-Badam-Tail-Free-Dabur-Honey-100-g-1613977189-10082776-2_large.jpg?v=1692723485", + "https://res.retailershakti.com/incom/images/product/Dabur-Roghan-Badam-Shireen-Badam-Tail-1613977197-10082779-1.jpg", + "https://getkuwa.com/cdn/shop/files/Dabur-Roghan-Badam-Shireen-Badam-Tail-Free-Dabur-Honey-100-g-1613977193-10082776-5_large.jpg?v=1692723484", + "https://cdn01.pharmeasy.in/dam/products_otc/B48341/dabur-roghan-badam-shireen-tail-100ml-with-free-dabur-honey-100g-6.1-1681722318.jpg", + "https://uvkart.com/static/uploads/products/dabur-roghan-badam-shireen-oil-1768745105.webp", + "https://uvkart.com/static/uploads/products/dabur-roghan-badam-shireen-oil-1768745102.webp", + "https://www.zevikahealth.com/cdn/shop/files/Dabur_Roghan_Badam_Shireen_100ml_2_600x600_86f30af1-d704-4d7b-a7e8-60873ba15e34.jpg?v=1761896124&600w" + ] + }, + { + "image_id": "dabur_real_fruits_juss_anar_1l", + "product_name": "Real fruits juss anar 1l", + "image_url": "http://shreenathfoods.com/cdn/shop/products/DaburRealMixedFruitJuice180ml_1200x1200.jpg?v=1676459803", + "image_urls": [ + "http://shreenathfoods.com/cdn/shop/products/DaburRealMixedFruitJuice180ml_1200x1200.jpg?v=1676459803", + "https://img500.exportersindia.com/product_images/bc-500/2024/5/12442047/dabur-real-fruit-juice-1714560214-7406238.jpeg", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Mixed-Fruit-1000-ml-3.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Pomegranate-1000-ml-2.webp", + "https://besuresainikcanteen.com/wp-content/uploads/2024/05/265839_3-real-fruit-juice-grape-400x400.jpg", + "https://besuresainikcanteen.com/wp-content/uploads/2024/05/40190763-3_1-real-fruit-power-juice-mixed-fruit.jpg", + "https://m.media-amazon.com/images/I/718s5Hnzt-L._SL1500_.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/4421/front_en.7.400.jpg", + "https://www.qualityfoodsonline.com/cdn/shop/files/DR-180.jpg?v=1689859204", + "https://i.ytimg.com/vi/YnaPvOlwB_o/maxresdefault.jpg" + ] + }, + { + "image_id": "dabur_sanifresh_250g", + "product_name": "SaniFresh 250g", + "image_url": "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "https://m.media-amazon.com/images/I/71V4i6BG9KL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71ijL19ik-L._SL1158_.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/f7534105-0f00-47c2-a353-2290238f505b.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/71wcxUZ+a2L._SL1342_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/e37c1691-5e27-406b-87bb-ebd69a345212.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/dabur-ayurveda-products-1200x686.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/Dabur-Ayurveda-Products-1.jpg", + "https://www.iconsofindianbusiness.com/files/DABUR_PRODUCTS.jpg" + ] + }, + { + "image_id": "dabur_sat_isabgol_90g", + "product_name": "SAT ISABGOL 90g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-POER1-003408/dabur-sat-isabgol-psyllium-husk-45gm_dabur-sat-isabgol-psyllium-husk-45gm--TM-POER1-003408_1.png?width=240", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-003408/dabur-sat-isabgol-psyllium-husk-45gm_dabur-sat-isabgol-psyllium-husk-45gm--TM-POER1-003408_1.png?width=240", + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002044/DABUR-SAT-ISABGOL-POWDER-200-GM_dabur-sat-isabgol-powder-200-gm--TM-POER1-002044_1.webp?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002931/dabur-sat-isabgol-powder-180-gm_dabur-sat-isabgol-powder-180-gm--TM-POER1-002931_1.png", + "https://cdn01.pharmeasy.in/dam/products_otc/385566/dabur-sat-isabgol-powder-200gm-2.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/385566/dabur-sat-isabgol-powder-200gm-3-1654258044.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/385566/dabur-sat-isabgol-powder-200gm-2-1613117623.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Sat-Isabgol-Husk-Supreme-1758006734-10159734-a.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/Z16203/dabur-sat-isabgol-200gm-pack-of-2-6.01-1692165469.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/Z16203/dabur-sat-isabgol-200gm-pack-of-2-6.02-1692165471.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/09376f0f-4798-4d34-a0c3-fa1cc756e949.__CR0,0,970,600_PT0_SX970_V1___.jpg" + ] + }, + { + "image_id": "dabur_sitopaladi_churna_60g", + "product_name": "SITOPALADI CHURNA 60g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002036/DABUR-SITOPALADI-CHURNA-100-GM_dabur-sitopaladi-churna-100-gm--TM-POER1-002036_1.webp", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002036/DABUR-SITOPALADI-CHURNA-100-GM_dabur-sitopaladi-churna-100-gm--TM-POER1-002036_1.webp", + "https://cdn01.pharmeasy.in/dam/products_otc/I05480/dabur-sitopaladi-churna-100-gm-3.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/I05480/dabur-sitopaladi-churna-cold-cough-paste-bottle-of-100-g-6.5-1669655026.jpg", + "https://d3pi03i22pjpvc.cloudfront.net/images/product/400.400/dabur_sitopaladichurna_nc.webp", + "https://kapeefit.com/wp-content/uploads/2024/10/Sitopaladi-Churna.webp", + "https://ecobayherbals.com/wp-content/uploads/2022/04/Dabur-Sitopaladi-Churna-Relief-to-Cough.-Powder-60g.jpg", + "https://premmedical.in/wp-content/uploads/2023/01/Dabur-Sitopaladi-Churna.png", + "https://images.openfoodfacts.org/images/products/890/120/700/2533/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/122/046/5148/front_en.3.400.jpg", + "https://www.ajeevam.com/uploads/products/upload_853_88159600.1759838818.png" + ] + }, + { + "image_id": "dabur_vatika_enriched_coconut_hair_oil_300ml", + "product_name": "Vatika enriched Coconut Hair Oil 300ml", + "image_url": "https://www.themallbd.com/backoffice/images/products/regular/dabur-vatika-naturals-coconut-enriched-hair-oil-300ml_regular_6385ae14203ee.jpg", + "image_urls": [ + "https://www.themallbd.com/backoffice/images/products/regular/dabur-vatika-naturals-coconut-enriched-hair-oil-300ml_regular_6385ae14203ee.jpg", + "https://saajkonna.com/wp-content/uploads/2022/03/Dabur-Vatika-Enriched-Coconut-Hair-Oil-150ml-3.jpg", + "https://p.kindpng.com/picc/s/331-3318983_vatika-enriched-coconut-hair-oil-hd-png-download.png", + "http://3.bp.blogspot.com/-M4yPReLAIwc/Vg6F5GYTEAI/AAAAAAAAGzg/KzJm1rDYvaQ/w1200-h630-p-k-no-nu/Dabur+Vatika+Enriched+Coconut+Hair+Oil.png", + "https://sinin.com.bd/wp-content/uploads/2024/02/Vatika-Naturals-Enriched-Coconut-Hair-Oil-400ml-1-480x480.jpg", + "https://www.picclickimg.com/WLYAAOSw3gFlr9T5/Vatika-Natural-Coconut-Enriched-Hair-Oil-100.webp", + "https://www.banglashoppers.com/media/catalog/product/cache/4ab9d486d78495fd0ffff667fc0d8653/d/a/dabur_vatika_naturals_enriched_coconut_hair_oil_400ml.jpg", + "https://www.distacart.com/cdn/shop/products/dabur_vatika_enriched_coconut_hair_oil_450ml_0_1_1280x.jpg?v=1635931712", + "https://media.karousell.com/media/photos/products/2021/7/21/vatika_enriched_coconut_hair_o_1626874384_f71c6ec6_progressive.jpg", + "https://www.makeupandbeautyforever.com/wp-content/uploads/2015/05/Dabur-Vatika-Enriched-Coconut-Oil-with-Hibiscus-Review-India-MBF-533x400.jpg" + ] + }, + { + "image_id": "dabur_vatika_enriched_coconut_hair_oil_450ml", + "product_name": "Vatika enriched Coconut Hair Oil 450ml", + "image_url": "https://www.waangoo.com/cdn/shop/products/0020000_dabur-vatika-coconut-enriched-hair-oil.jpg?v=1694714010", + "image_urls": [ + "https://www.waangoo.com/cdn/shop/products/0020000_dabur-vatika-coconut-enriched-hair-oil.jpg?v=1694714010", + "https://saajkonna.com/wp-content/uploads/2022/03/Dabur-Vatika-Enriched-Coconut-Hair-Oil-150ml-1.jpg", + "https://www.urbanbeauty.pk/cdn/shop/files/DaburVatikaCoconutEnrichedHairOil200ml_2048x.jpg?v=1686238045", + "http://2.bp.blogspot.com/-Exs2iLq2H0E/Vgk8NYfNs_I/AAAAAAAAF3g/o2SZDcKYm_4/s1600/dabur+vatika+enriched+coconut+hair+oil+review.jpg", + "https://sufianabd.com/wp-content/uploads/2023/12/dabur-vatika-enriched-coconut-hair-oil-300-ml-275-tk.jpg", + "https://dhamart.pk/storage/dabur-vatika-enriched-coconut-hair-oil-extra-nourishment-125ml.webp", + "https://www.banglashoppers.com/media/catalog/product/cache/de79bc5569b5032044bc83e7c1343c50/d/a/dabur_vatika_naturals_coconut_enriched_hair_oil_300ml.jpg", + "https://www.catchnpack.pk/wp-content/uploads/2023/03/Dabur-Vatika-Coconut-Enriched-Volume-Thickness-Hair-Oil-200ml.jpg", + "https://www.asiandukan.co.uk/pub/media/catalog/product/cache/744ba2e6a57cef4cb8ac0090abd30278/v/a/vatika_naturals_enriched_coconut_hair_oil_1.jpg", + "https://www.ohsogo.com/cdn/shop/products/dabur_vatika_enricheds.jpg?v=1696548653&width=2000" + ] + }, + { + "image_id": "dabur_vedic_tea_500g", + "product_name": "Vedic Tea 500g", + "image_url": "https://gumlet.assettype.com/foodtechbiz/2022-11/cc991cbc-87ff-44af-a6f8-1610d5fdef01/VEDIC_TEA.jpg?w=1200&auto=format,compress&ogImage=true&enlarge=true", + "image_urls": [ + "https://gumlet.assettype.com/foodtechbiz/2022-11/cc991cbc-87ff-44af-a6f8-1610d5fdef01/VEDIC_TEA.jpg?w=1200&auto=format,compress&ogImage=true&enlarge=true", + "https://www.themgshop.com/wp-content/uploads/2023/08/Dabur-Vedic-Tea-Black-Tea-500g-2.jpg", + "https://www.themgshop.com/wp-content/uploads/2023/08/Dabur-Vedic-Green-Tea-Detox-Kahwa-25-Teabags-2.jpg", + "https://www.themgshop.com/wp-content/uploads/2023/08/Dabur-Vedic-Tea-Black-Tea-500g-3.jpg", + "https://www.healthcareyoung.com/wp-content/uploads/2025/2/dabur-vedic-suraksha-tea-25-sachets-1739088390.webp", + "https://rukminim1.flixcart.com/image/832/832/xif0q/tea/c/z/w/100-vedic-1-box-herbal-tea-dabur-leaves-granules-original-imagkb6qxzmwzmhr.jpeg?q=70", + "https://bl-i.thgim.com/public/news/84zo0z/article65917377.ece/alternates/LANDSCAPE_1200/dabur+vedic+tea.jpg", + "https://rukminim1.flixcart.com/image/416/416/xif0q/tea/5/a/k/-original-imaghpyjkfj9zmsg.jpeg?q=70", + "https://rukminim2.flixcart.com/image/416/416/xif0q/tea/o/9/t/-original-imagrngyrd69zxby.jpeg?q=70", + "https://m.media-amazon.com/images/I/81Nt6IUsUTL._SL1500_.jpg" + ] + }, + { + "image_id": "dabur_real_100g", + "product_name": "real 100g", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/c/c5/Dabur_Real_Active.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "image_urls": [ + "https://upload.wikimedia.org/wikipedia/commons/c/c5/Dabur_Real_Active.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Mixed-Fruit-1000-ml-5.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Mixed-Fruit-1000-ml-3.webp", + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1500-1500,pr-true,f-auto,q-40,dpr-2/cms/product_variant/8cb97af7-39f8-4f7b-a5d7-ae061393b137/Dabur-Real-Fruit-Power-Apple-Fruit-Juice-Combo.jpg", + "https://m.media-amazon.com/images/I/718s5Hnzt-L._SL1500_.jpg", + "https://images.openbeautyfacts.org/images/products/800/184/176/8946/front_es.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/934/925/400/6187/front_en.11.400.jpg", + "https://images.openbeautyfacts.org/images/products/922/007/959/7664/front_en.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/922/007/959/7725/front_en.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/750/103/970/1553/front_en.3.400.jpg" + ] + }, + { + "image_id": "dabur_anmol_gold_500g", + "product_name": "Anmol Gold 500g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_2.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_2.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.1-1674197015.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Jar-1633604311-10092057-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Bottle-1595325245-10073672-1.jpg", + "http://bake-bay.com/wp-content/uploads/2023/05/dabur_anmol_gold_coconut_oil.jpg", + "https://spicetown.fi/wp-content/uploads/Dabur-Anmol-Gold-Pure-Coconut-Oil-500-ml-1.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/d4f5f5bb-96c4-4e3b-b595-6106babd3d5a.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/61uPuUCGZsL.jpg", + "https://m.media-amazon.com/images/I/616fTeeXfUL._SL1500_.jpg" + ] + }, + { + "image_id": "dabur_coconut_oil_5l", + "product_name": "coconut oil 5L", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000433/DABUR-ANMOL-GOLD-PURE-COCONUT-OIL-600-ML_dabur-anmol-gold-pure-coconut-oil-600-ml--TM-OIIL1-000433_1.png", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000634/dabur-anmol-gold-pure-coconut-oil-175-ml_dabur-anmol-gold-pure-coconut-oil-175-ml--TM-OIIL1-000634_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://assets.truemeds.in/Images/ProductImage/TM-OIIL1-000659/dabur-anmol-gold-pure-coconut-oil-500-ml_dabur-anmol-gold-pure-coconut-oil-500-ml--TM-OIIL1-000659_1.png?tr=cm-pad_resize,bg-FFFFFF,lo-true,w-315", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.1-1674197015.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/V77599/dabur-anmol-gold-100-pure-coconut-oil-1-l-6.2-1674196894.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Jar-1633604311-10092057-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Anmol-Gold-Pure-Coconut-Oil-Bottle-1595325245-10073672-1.jpg", + "http://bake-bay.com/wp-content/uploads/2023/05/dabur_anmol_gold_coconut_oil.jpg", + "https://spicetown.fi/wp-content/uploads/Dabur-Anmol-Gold-Pure-Coconut-Oil-500-ml-1.jpg", + "http://globalfoodhub.com/cdn/shop/files/Dabur-Anmol-Gold-Pure-Coconut-Oil-600-ml-GlobalFoodHub_com.webp?v=1753179418" + ] + }, + { + "image_id": "dabur_glucose_100g", + "product_name": "Glucose 100g", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/705/3160/front_en.4.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/705/3160/front_en.4.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/702/8304/front_fr.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/705/3467/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/4168/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/701/7025/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/2424/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/600/2004/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/600/1984/front_en.3.400.jpg", + "https://t3.ftcdn.net/jpg/01/03/06/82/360_F_103068298_RZIrjdbEqqzDYFqbQQeX1IE8ZdSPjly0.jpg" + ] + }, + { + "image_id": "dabur_hajmola_250g", + "product_name": "Hajmola 250g", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/790/0839/front_en.15.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/790/0839/front_en.15.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/706/2865/front_en.23.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/701/9234/front_fr.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/700/7545/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/000/008/900/4869/front_en.4.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/651/3005/front_en.3.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/610/4586/front_fr.21.400.jpg", + "https://images.openfoodfacts.org/images/products/502/249/600/3612/front_es.3.400.jpg", + "https://p5z2x9b7.delivery.rocketcdn.me/wp-content/uploads/2021/09/THOR-ATD-DTS-BeforeAfter_Photo.jpg", + "https://cacaca.jp/wp-content/uploads/2017/09/43644.jpg" + ] + }, + { + "image_id": "dabur_real_activ_100_mixed_fruit_juice_1l", + "product_name": "Real activ 100% mixed fruit juice 1l", + "image_url": "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-4200-4200,pr-true,f-auto,q-40,dpr-2/cms/product_variant/b5c66f0c-25d8-43c7-a43b-06657b3bd80e/Dabur-Real-Activ-Mixed-Fruit-Juice-Combo.jpeg", + "image_urls": [ + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-4200-4200,pr-true,f-auto,q-40,dpr-2/cms/product_variant/b5c66f0c-25d8-43c7-a43b-06657b3bd80e/Dabur-Real-Activ-Mixed-Fruit-Juice-Combo.jpeg", + "https://thedesifood.com/media/Dabur-Real-Activ-Mixed-Fruit-1000-ml-4.webp", + "https://thedesifood.com/media/Dabur-Real-Activ-Mixed-Fruit-1000-ml-1.webp", + "https://thedesifood.com/media/Dabur-Real-Activ-Mixed-Fruit-1000-ml-5.webp", + "https://thedesifood.com/media/Dabur-Real-Activ-Mixed-Fruit-1000-ml-2.webp", + "http://shreenathfoods.com/cdn/shop/products/DaburRealMixedFruitJuice180ml_1200x1200.jpg?v=1676459803", + "https://desideliveryvt.com/wp-content/uploads/2025/04/dabur-real-mixed-fruit-juice-1.jpg", + "https://m.media-amazon.com/images/I/81WApQQVI5L._SL1500_.jpg", + "https://m.media-amazon.com/images/S/aplus-media/vc/1453d819-4a99-4ae2-90c5-29d88aa7ef54.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/51lS2Y2p0VL._SX300_SY300_QL70_ML2_.jpg" + ] + }, + { + "image_id": "dabur_real_fruit_pomegranate_tpk_180ml", + "product_name": "Real fruit pomegranate tpk 180ml", + "image_url": "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Pomegranate-1000-ml-1.webp", + "image_urls": [ + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Pomegranate-1000-ml-1.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Pomegranate-1000-ml-4.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Pomegranate-1000-ml-3.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Masala-Pomegranate-1000-ml-2.webp", + "https://nagabazaar.com/cdn/shop/files/real-fruit-power-masala-pomegranate-juice-1-l-product-images-o491419456-p491419456-1-202203170317.webp?v=1747520769&width=1024", + "https://tiimg.tistatic.com/fp/1/007/701/delicious-and-nutritious-dabur-real-pomegranate-fruit-juice-no-added-preservatives-pack-of-1-liter-979.jpg", + "https://starindojapan.com/wp-content/uploads/2020/09/Dabur-Real-Masala-Pomegranate-Juice-1-Ltr.jpg", + "https://www.fruiticanacalgary.com/cdn/shop/products/Pomegranate-Juice---Dabur-1-ltr_1080x.jpg?v=1678971643", + "https://www.southasiancentral.ca/wp-content/uploads/dabur-pomegranate-1l-8901207027536.webp", + "https://m.media-amazon.com/images/I/81VCvVDWoFL._SL1500_.jpg" + ] + }, + { + "image_id": "dabur_real_fruit_power_mango_1l", + "product_name": "Real Fruit Power Mango 1l", + "image_url": "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "https://m.media-amazon.com/images/I/71V4i6BG9KL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71ijL19ik-L._SL1158_.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/f7534105-0f00-47c2-a353-2290238f505b.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/71wcxUZ+a2L._SL1342_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/e37c1691-5e27-406b-87bb-ebd69a345212.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/dabur-ayurveda-products-1200x686.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/Dabur-Ayurveda-Products-1.jpg", + "https://www.iconsofindianbusiness.com/files/DABUR_PRODUCTS.jpg" + ] + }, + { + "image_id": "dabur_dabur_amla_hair_oil_3_hair_benefits_550ml", + "product_name": "Dabur Amla hair oil 3 HAIR BENEFITS 550ml", + "image_url": "https://images.openfoodfacts.org/images/products/890/120/703/1717/front_en.5.400.jpg", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/890/120/703/1717/front_en.5.400.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/8389/front_en.3.400.jpg", + "https://apm.net.au/getmedia/db3ef376-a5ac-4853-bb94-b4853aca6af7/Inclusive-Employment-Australia-logo.jpg", + "https://www.cvgt.com.au/wp-content/uploads/2025/10/image.jpg", + "https://www.civic.org.au/wp-content/uploads/2025/09/JP-IEA-fullred-2048x338.png", + "https://www.civic.org.au/wp-content/uploads/2025/09/JP-IEA-fullred-2000x330.png", + "https://www.civic.org.au/wp-content/uploads/2025/09/JP-IEA-fullred-1536x253.png", + "https://orsgroup.com.au/wp-content/uploads/Inclusive-emplyment-and-ORS.jpg", + "https://www.inclusionaustralia.org.au/wp-content/uploads/2021/11/Inclusion-Australia-Updated-Logo-FA_Horizontal_Full_Colour_Transparent-01-2048x439.png", + "https://www.inclusionaustralia.org.au/wp-content/uploads/2021/11/Inclusion-Australia-Updated-Logo-FA_Horizontal_Full_Colour_Transparent-01-1536x329.png" + ] + }, + { + "image_id": "dabur_dabur_honey_250g", + "product_name": "Dabur Honey 250g", + "image_url": "", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/505/931/902/3762/front_fr.32.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/000/4440/front_fr.129.400.jpg", + "https://images.openfoodfacts.org/images/products/761/450/001/0013/front_en.362.400.jpg", + "https://images.openfoodfacts.org/images/products/841/007/660/0837/front_en.36.400.jpg", + "https://images.openfoodfacts.org/images/products/841/007/660/0790/front_en.28.400.jpg", + "https://images.openfoodfacts.org/images/products/501/127/304/0698/front_en.43.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/020/2662/front_fr.26.400.jpg", + "https://images.openfoodfacts.org/images/products/848/000/015/4361/front_es.65.400.jpg", + "https://images.openfoodfacts.org/images/products/505/290/973/2600/front_en.37.400.jpg", + "https://images.openfoodfacts.org/images/products/001/600/026/4694/front_en.319.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_honey_225g", + "product_name": "Dabur Honey 225g", + "image_url": "https://m.media-amazon.com/images/I/71O4OnjaHVL.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/71O4OnjaHVL.jpg", + "https://images.openbeautyfacts.org/images/products/008/580/507/1387/front_en.3.400.jpg", + "https://images.openbeautyfacts.org/images/products/800/897/005/6234/front_en.5.400.jpg", + "https://images.openbeautyfacts.org/images/products/800/352/001/3026/front_en.5.400.jpg", + "https://images.openbeautyfacts.org/images/products/324/567/858/9694/front_es.15.400.jpg", + "https://images.openbeautyfacts.org/images/products/800/897/005/2380/front_en.5.400.jpg", + "https://images.openbeautyfacts.org/images/products/425/123/228/4324/front_nl.11.400.jpg", + "https://images.openbeautyfacts.org/images/products/800/897/005/2786/front_es.7.400.jpg", + "https://images.openbeautyfacts.org/images/products/800/897/005/6272/front_fr.4.400.jpg", + "https://images.openbeautyfacts.org/images/products/871/478/952/6508/front_en.9.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_triphala_churna_500g", + "product_name": "Dabur Triphala Churna 500g", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002038/dabur-triphala-churna-500gm_dabur-triphala-churna-500gm--TM-POER1-002038_4.png?width=320", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002038/dabur-triphala-churna-500gm_dabur-triphala-churna-500gm--TM-POER1-002038_4.png?width=320", + "https://assets.truemeds.in/Images/ProductImage/TM-POER1-002038/dabur-triphala-churna-500gm_dabur-triphala-churna-500gm--TM-POER1-002038_1.png", + "https://www.quicklly.com/upload_images/product/1673743777-dabur-triphala-churna.jpg", + "https://ayushforlife.com/wp-content/uploads/2024/08/Dabur-Triphala-Churna-Composition.webp", + "https://pilesmedicine.in/wp-content/uploads/2020/10/Dabur-Triphala-Churna-1.jpg", + "https://pilesmedicine.in/wp-content/uploads/2020/10/Dabur-Triphala-Churna-4.jpg", + "https://pilesmedicine.in/wp-content/uploads/2020/10/Dabur-Triphala-Churna-2.jpg", + "https://ayurvedaproducts.co.uk/wp-content/uploads/2022/11/Dabur-Triphala.jpg", + "https://ayurvedaproducts.co.uk/wp-content/uploads/2022/11/Dabur-Triphala-Ingredients02.webp", + "https://www.mahalaxmimedicos.com/assets/upload/products/MM76101493275281.jpg" + ] + }, + { + "image_id": "dabur_odomos_naturals_23g", + "product_name": "Odomos Naturals 23g", + "image_url": "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/V3boqwx6Ws-dabur_odomos_naturals_spray_100_ml_0_1.jpg", + "image_urls": [ + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/V3boqwx6Ws-dabur_odomos_naturals_spray_100_ml_0_1.jpg", + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/K_BUmyZ_JQ-dabur_odomos_naturals_gel_80_gm_0_2.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/I05452/dabur-odomos-naturals-lotion-120ml-1.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/128750/dabur-odomos-naturals-cream-50gm-2-1671741321.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/128740/dabur-odomos-naturals-cream-100gm-2-1641398827.jpg", + "https://rokbucket.rokomari.io/ProductNew20190903/260X372/Dabur_Odomos_Naturals_Mosquito_Repellant-Dabur-c40f3-534231.png", + "https://sinin.com.bd/wp-content/uploads/2023/01/Dabur-Odomos-Naturals-Non-Sticky-Mosquito-Repellent-Cream-600x600.jpg", + "https://static.tudo.lk/uploads/2023/08/thumb/dabur-odomos-naturals-mosquito-repellant-spray-100ml-16933766231661258-300x300.webp", + "https://www.cellsii.com/images/detailed/47/Dabur-Odomos-Naturals-Non-Sticky-Mosquito-Repellent-Cream.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/I05443/dabur-odomos-natural-gel-80-gm-2-1612781717.jpg" + ] + }, + { + "image_id": "dabur_kabz_over_500g", + "product_name": "KABZ OVER 500g", + "image_url": "https://asset.sastasundar.com/incom/images/product/Dabur-Mahanarayan-Tail--Free-Dabur-Kabz-Over-12-g-1602143940-10076335-1.jpg", + "image_urls": [ + "https://asset.sastasundar.com/incom/images/product/Dabur-Mahanarayan-Tail--Free-Dabur-Kabz-Over-12-g-1602143940-10076335-1.jpg", + "https://ayubazar.com/wp-content/uploads/2021/04/dabur-kabz-over-02.jpg", + "https://ayubazar.com/wp-content/uploads/2021/04/dabur-kabz-over-01.jpg", + "https://www.healthwithherbal.com/wp-content/uploads/2021/06/KABZ-OVER-DABUR.jpg", + "https://www.healthwithherbal.com/wp-content/uploads/2021/06/Kabz-Over-Granules-2.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Naturecare-Kabz-Over-Natural-Laxative-Granules-20-Extra-1594016665-10073900-2.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/Y72959/dabur-nature-care-kabz-over-combo-of-4-2-1671744276.jpg?dim=700x0&dpr=1&q=100", + "https://cdn01.pharmeasy.in/dam/products_otc/Y72959/dabur-nature-care-kabz-over-combo-of-4-7-1660312449.jpg", + "https://ayubazar.com/wp-content/uploads/2021/04/kabz2.jpg", + "https://ayubazar.com/wp-content/uploads/2021/04/kabz4-1024x1024.jpg" + ] + }, + { + "image_id": "dabur_glucose_250g", + "product_name": "Glucose 250g", + "image_url": "https://images.price.tools/images/dabur-glucose-d-instant-energy-drink-l-Pdm9aPOU.jpg", + "image_urls": [ + "https://images.price.tools/images/dabur-glucose-d-instant-energy-drink-l-Pdm9aPOU.jpg", + "https://rukminim1.flixcart.com/image/832/832/k6pd7680/energy-sport-drink-mix/8/z/p/500-glucose-c-orange-500g-dabur-original-imafp37qseakgdq3.jpeg?q=70", + "https://rukminim2.flixcart.com/image/612/612/k6mibgw0/energy-sport-drink-mix/9/p/y/500-glucose-c-lemon-500g-dabur-original-imafpfayhhk8nmzc.jpeg?q=70", + "http://rukmini1.flixcart.com/image/300/300/xif0q/energy-sport-drink-mix/v/o/i/1-glucose-d-instant-energy-boosts-immunity-1-dabur-original-imagz7tahhnxdzkz.jpeg", + "http://fmcghouse.com/cdn/shop/files/D.GLUCOSE-DENERGYDRINK100G.jpg?v=1747482375", + "http://fmcghouse.com/cdn/shop/files/D.GLUCOSE-DENERGYDRINK500G.webp?v=1747482497", + "https://rukminim2.flixcart.com/image/612/612/l1mh7rk0/energy-sport-drink-mix/g/o/x/1-glucose-d-1kg-jar-pack-of-1-2-dabur-original-imagd5nyuzadyaf5.jpeg?q=70", + "https://m.media-amazon.com/images/I/71y7yJ+RfdL._AC_SL1500_.jpg", + "https://m.media-amazon.com/images/I/416FQZnFpRL._SL500_.jpg", + "https://m.media-amazon.com/images/I/811UaqU9XZL.jpg" + ] + }, + { + "image_id": "dabur_glucose_500g", + "product_name": "Glucose 500g", + "image_url": "https://images.price.tools/images/dabur-glucose-d-instant-energy-drink-l-Pdm9aPOU.jpg", + "image_urls": [ + "https://images.price.tools/images/dabur-glucose-d-instant-energy-drink-l-Pdm9aPOU.jpg", + "https://rukminim1.flixcart.com/image/832/832/k6pd7680/energy-sport-drink-mix/8/z/p/500-glucose-c-orange-500g-dabur-original-imafp37qseakgdq3.jpeg?q=70", + "https://rukminim2.flixcart.com/image/612/612/k6mibgw0/energy-sport-drink-mix/9/p/y/500-glucose-c-lemon-500g-dabur-original-imafpfayhhk8nmzc.jpeg?q=70", + "http://rukmini1.flixcart.com/image/300/300/xif0q/energy-sport-drink-mix/v/o/i/1-glucose-d-instant-energy-boosts-immunity-1-dabur-original-imagz7tahhnxdzkz.jpeg", + "http://fmcghouse.com/cdn/shop/files/D.GLUCOSE-DENERGYDRINK100G.jpg?v=1747482375", + "http://fmcghouse.com/cdn/shop/files/D.GLUCOSE-DENERGYDRINK500G.webp?v=1747482497", + "https://rukminim2.flixcart.com/image/612/612/l1mh7rk0/energy-sport-drink-mix/g/o/x/1-glucose-d-1kg-jar-pack-of-1-2-dabur-original-imagd5nyuzadyaf5.jpeg?q=70", + "https://m.media-amazon.com/images/I/71y7yJ+RfdL._AC_SL1500_.jpg", + "https://m.media-amazon.com/images/I/416FQZnFpRL._SL500_.jpg", + "https://m.media-amazon.com/images/I/811UaqU9XZL.jpg" + ] + }, + { + "image_id": "dabur_sanifresh_500g", + "product_name": "SaniFresh 500g", + "image_url": "https://besuresainikcanteen.com/wp-content/uploads/2024/05/84-Sanifresh.jpg", + "image_urls": [ + "https://besuresainikcanteen.com/wp-content/uploads/2024/05/84-Sanifresh.jpg", + "https://m.media-amazon.com/images/I/71bbTZDJQ8L._SL1500_.jpg", + "https://images.openproductsfacts.org/images/products/890/120/704/6650/front_en.3.400.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Sani-Fresh-Ultra-Shine-Toilet-Cleaner-Free-Odonil-Air-Freshener-50-g-1587984353-10043156-2.jpg", + "https://res.retailershakti.com/incom/images/product/Dabur-Sani-Fresh-Ultra-Shine-Toilet-Cleaner-Free-Odonil-Air-Freshener-50-g--Save-Rs10--1605611636-10043156-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Sani-Fresh-Ultra-Shine-Toilet-Cleaner-Free-Odonil-Air-Freshener-50-g-1640758778-10093906-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Sani-Fresh-Bathroom-Cleaner-Buy-1-Get-1-Free-1611296178-10081420-1.jpg", + "https://thedesifood.com/media/Dabur-Sani-Fresh-Advanced-Toilet-Cleaner-5000-ml-1.webp", + "https://thedesifood.com/media/Dabur-Sani-Fresh-Advanced-Toilet-Cleaner-5000-ml-2.webp", + "https://thedesifood.com/media/Dabur-Sani-Fresh-Advanced-Toilet-Cleaner-5000-ml-3.webp" + ] + }, + { + "image_id": "dabur_tomato_puree_200g", + "product_name": "Tomato Puree 200g", + "image_url": "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1250-1250,pr-true,f-auto,q-40,dpr-2/cms/product_variant/03c6ef88-9f97-4f81-aa49-ac1131d38a87/Dabur-Hommade-Tomato-Puree.jpeg", + "image_urls": [ + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1250-1250,pr-true,f-auto,q-40,dpr-2/cms/product_variant/03c6ef88-9f97-4f81-aa49-ac1131d38a87/Dabur-Hommade-Tomato-Puree.jpeg", + "https://thedesifood.com/media/Dabur-Hommade-Tomato-Puree-200-g-1.webp", + "https://m.media-amazon.com/images/I/81I3o34-vWL._SL1500_.jpg", + "https://images.openfoodfacts.org/images/products/890/120/701/4642/front_en.7.400.jpg", + "https://m.media-amazon.com/images/S/aplus-media/vc/7a02e654-ac1f-4b99-8560-3b1f564d41d6.__CR0,0,970,600_PT0_SX970_V1___.png", + "https://m.media-amazon.com/images/S/aplus-media/vc/d50e9031-f80e-4416-a034-966e12b76653.__CR0,0,300,300_PT0_SX300_V1___.png", + "https://imagedelivery.net/olI9wp0b6luWFB9nPfnqjQ/res/abillionveg/image/upload/ck3pfxqh5fryozicojqi/1643348685.jpg/public", + "https://image3.mouthshut.com/images/imagesp/925731219s.jpg", + "https://instamart-media-assets.swiggy.com/swiggy/image/upload/fl_lossy,f_auto,q_auto,w_1200,h_630/ketlbrpratmnyvd5d8oy", + "https://i.ytimg.com/vi/PQ98Ce3533Y/oardefault.jpg?sqp=-oaymwEkCJUDENAFSFqQAgHyq4qpAxMIARUAAAAAJQAAyEI9AICiQ3gB&rs=AOn4CLAeWtOb-4UU5-_av2T6NLiqiHmleQ" + ] + }, + { + "image_id": "dabur_real_fruit_power_alphonso_mango_nectar_1l", + "product_name": "Real Fruit Power Alphonso Mango Nectar 1l", + "image_url": "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Alphonso-Mango-Nectar-1000-ml-2.webp", + "image_urls": [ + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Alphonso-Mango-Nectar-1000-ml-2.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Alphonso-Mango-Nectar-1000-ml-4.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Alphonso-Mango-Nectar-1000-ml-6.webp", + "https://res.retailershakti.com/incom/images/product/Real-Fruit-Power-Alphonso-Mango-Nectar-Juice-Rich-in-Vitamin-C-1644466375-10094810-4.jpg", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Alphonso-Mango-Nectar-1000-ml-1.webp", + "https://thedesifood.com/media/Dabur-Real-Fruit-Power-Alphonso-Mango-Nectar-1000-ml-5.webp", + "https://thedesimart.com/cdn/shop/products/dabur-real-alphonso-mango-fruit-juice-nectar-1l-736293.jpg?v=1681863582&width=640", + "https://www.quicklly.com/upload_images/product/1612213362-dabur-real-mango-fruit-juice-nectar.jpg", + "http://twobrothers.co/cdn/shop/files/DaburRealMango1liters_1200x1200.jpg?v=1735151635", + "https://d3pi03i22pjpvc.cloudfront.net/images/product/400.400/dabur_alphonsomangopulp.webp" + ] + }, + { + "image_id": "dabur_real_fruit_power_orange_1l", + "product_name": "Real Fruit Power Orange 1l", + "image_url": "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "image_urls": [ + "https://m.media-amazon.com/images/I/71v3J2-r03L._AC_SX1500_SY1125_.jpg", + "https://m.media-amazon.com/images/I/71V4i6BG9KL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71ijL19ik-L._SL1158_.jpg", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/f7534105-0f00-47c2-a353-2290238f505b.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://m.media-amazon.com/images/I/71wcxUZ+a2L._SL1342_.jpg", + "https://m.media-amazon.com/images/S/aplus-media-library-service-media/e37c1691-5e27-406b-87bb-ebd69a345212.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/dabur-ayurveda-products-1200x686.jpg", + "https://meddrop.in/wp-content/uploads/2023/08/Dabur-Ayurveda-Products-1.jpg", + "https://www.iconsofindianbusiness.com/files/DABUR_PRODUCTS.jpg" + ] + }, + { + "image_id": "dabur_amla_juice_1l", + "product_name": "Amla Juice 1l", + "image_url": "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001023/dabur-amla-juice-1-l_dabur-amla-juice-1-l--TM-LINT1-001023_1.png", + "image_urls": [ + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001023/dabur-amla-juice-1-l_dabur-amla-juice-1-l--TM-LINT1-001023_1.png", + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001023/dabur-amla-juice-1-l_dabur-amla-juice-1-l--TM-LINT1-001023_4.png?width=320", + "https://assets.truemeds.in/Images/ProductImage/TM-LINT1-001023/dabur-amla-juice-1-l_dabur-amla-juice-1-l--TM-LINT1-001023_2.png?width=320", + "https://rukminim2.flixcart.com/image/832/832/kfwvcsw0/ayurvedic/m/n/c/2-amla-juice-dabur-original-imafw9fuadxdbhez.jpeg?q=70&crop=false", + "https://rukminim2.flixcart.com/image/416/416/kihqz680/ayurvedic/z/8/8/amla-ayurvedic-juice-dabur-original-imafy9gp7ah9xjnw.jpeg?q=70&crop=false", + "https://m.media-amazon.com/images/S/aplus-media/sota/001bc614-8022-4433-b740-ba7c6860ded9.__CR0,0,970,600_PT0_SX970_V1___.jpg", + "https://images.openfoodfacts.org/images/products/890/120/703/4145/front_en.3.400.jpg", + "https://ayurvedaindore.com/wp-content/uploads/2024/01/2-2.png", + "https://www.ayurcentralonline.com/wp-content/uploads/webp_images/ac_m_13817.webp", + "https://down-ph.img.susercontent.com/file/ph-11134207-7rasj-m0r7h6ecb1msf8" + ] + }, + { + "image_id": "dabur_dabur_chawanprash_500g", + "product_name": "Dabur chawanprash 500g", + "image_url": "https://sangamitra.de/wp-content/uploads/2022/10/Chawanprash.png", + "image_urls": [ + "https://sangamitra.de/wp-content/uploads/2022/10/Chawanprash.png", + "https://m.media-amazon.com/images/I/71qy5hllAXL.jpg", + "https://images.openfoodfacts.org/images/products/890/120/704/2089/front_en.3.400.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/C87812/dabur-chyawanprash-health-food-jar-of-950-gm-2-1654165471.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/051260/dabur-chyawanprash-250-gm-2-1669634957.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/Q49057/dabur-chyawanprash-3x-immunity-action-2kg-with-250gm-dabur-honey-free-2-1681210398.jpg", + "https://c8.alamy.com/comp/2MXD810/a-closeup-shot-of-dabur-product-chyawanprash-with-the-smoke-in-the-background-2MXD810.jpg", + "https://c8.alamy.com/comp/2MXD802/a-closeup-shot-of-dabur-product-chyawanprash-isolated-on-white-background-2MXD802.jpg", + "https://ayurvedaproducts.co.uk/wp-content/uploads/2020/04/Dabur-chavanprash-534x1024.jpg", + "https://images.openfoodfacts.org/images/products/890/184/800/3098/front_en.3.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_gulabari_120ml", + "product_name": "Dabur Gulabari 120ml", + "image_url": "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2,q-40/static/img/product/387529/dabur-gulabari-250ml_1_display_1738659961_28fe07b4.jpg", + "image_urls": [ + "https://media6.ppl-media.com/tr:h-750,w-750,c-at_max,dpr-2,q-40/static/img/product/387529/dabur-gulabari-250ml_1_display_1738659961_28fe07b4.jpg", + "https://www.desiclik.com/images/P/daburgulabari.jpg", + "http://www.zevikahealth.com/cdn/shop/files/Dabur_Gulabari_Premium_Rose_Water_250ml.png?v=1751351304", + "https://rukminim1.flixcart.com/image/832/832/jpinjbk0/cleanser/p/r/m/250-gulabari-premium-rose-water-skin-toner-250-ml-dabur-original-imafbq7n5k7tumug.jpeg?q=70", + "https://m.media-amazon.com/images/I/61qH6bzKZFL.jpg", + "https://m.media-amazon.com/images/I/71iRQW9b8LL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/61qVhY0XMHL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/61g-TQQZ4qL._SL1500_.jpg", + "https://m.media-amazon.com/images/I/71TNvf82Y9L._SL1500_.jpg", + "https://m.media-amazon.com/images/I/61rs7WKvmwL._SX679_.jpg" + ] + }, + { + "image_id": "dabur_dabur_honey_1kg", + "product_name": "Dabur Honey 1kg", + "image_url": "", + "image_urls": [ + "https://images.openfoodfacts.org/images/products/505/931/902/3762/front_fr.32.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/000/4440/front_fr.129.400.jpg", + "https://images.openfoodfacts.org/images/products/761/450/001/0013/front_en.362.400.jpg", + "https://images.openfoodfacts.org/images/products/841/007/660/0837/front_en.36.400.jpg", + "https://images.openfoodfacts.org/images/products/841/007/660/0790/front_en.28.400.jpg", + "https://images.openfoodfacts.org/images/products/501/127/304/0698/front_en.43.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/020/2662/front_fr.26.400.jpg", + "https://images.openfoodfacts.org/images/products/848/000/015/4361/front_es.65.400.jpg", + "https://images.openfoodfacts.org/images/products/505/290/973/2600/front_en.37.400.jpg", + "https://images.openfoodfacts.org/images/products/001/600/026/4694/front_en.319.400.jpg" + ] + }, + { + "image_id": "dabur_dabur_ratnaprash_900g", + "product_name": "Dabur Ratnaprash 900g", + "image_url": "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/FQ2pLG9z5o-dabur_ratnaprash_sugar_free_450_gm_545532_0_1.jpg", + "image_urls": [ + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/FQ2pLG9z5o-dabur_ratnaprash_sugar_free_450_gm_545532_0_1.jpg", + "https://cdn.pixelbin.io/v2/plain-cake-860195/netmed/wrkr/products/pictures/item/free/original/_aKx0vLQ9o-dabur_ratnaprash_powder_900_gm_0.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Ratnaprash-1602058327-10076703-1.jpg", + "https://i.gadgets360cdn.com/products/ratnaprash-large-91576-159898-1584984334.jpg", + "https://cdn01.pharmeasy.in/dam/products_otc/I05444/dabur-ratnaprash-900-gm-2-1669655395.jpg?dim=700x0&dpr=1&q=100", + "https://ayubazar.com/wp-content/uploads/2021/08/Dabur-ratnaprash-01.jpg", + "https://ayubazar.com/wp-content/uploads/2021/08/Dabur-ratnaprash-02.jpg", + "https://ayubazar.com/wp-content/uploads/2021/08/Dabur-ratnaprash-03.jpg", + "https://dawaghar.in/wp-content/uploads/2024/03/Ratnaprash-900gm-1.jpg", + "https://m.media-amazon.com/images/I/81giToNB6+L._SL1500_.jpg" + ] + }, + { + "image_id": "dabur_hot_sip_ayurvedic_kaadha_15120g", + "product_name": "Hot Sip Ayurvedic Kaadha 15120g", + "image_url": "https://asset.sastasundar.com/incom/images/product/Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha-Granules-1689661318-10151082-1.jpg", + "image_urls": [ + "https://asset.sastasundar.com/incom/images/product/Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha-Granules-1689661318-10151082-1.jpg", + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1200-1200,pr-true,f-auto,q-40,dpr-2/cms/product_variant/a96a2c64-9fe7-43ac-ba23-63accc01eb8e/Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha-Sachet.jpeg", + "https://cdn.zeptonow.com/production/ik-seo/tr:w-470,ar-1000-1000,pr-true,f-auto,q-40,dpr-2/cms/product_variant/be2166d2-0c37-452c-9dda-e1319e287e37/Dabur-OTC-Honitus-Hot-Sip-Ayurvedic-Kaadha.jpeg", + "https://res.retailershakti.com/incom/images/product/Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha-Granules-1613801239-10082786-1.jpg", + "https://asset.sastasundar.com/incom/images/product/Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha-Granules-1690614420-10151226-1.jpg", + "https://storage.googleapis.com/shy-pub/356888/dabur-honitus-hot-sip-ayurvedic-kaadha-4gm-each-30-1709375724901_SKU-3505_0.png", + "https://cpimg.tistatic.com/04657488/b/4/Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha-Pack-of-30-sachets-.jpg", + "https://herbanation.com/wp-content/uploads/2024/08/Benefits-of-Dabur-Honitus-Hot-Sip-Ayurvedic-Kaadha_herbanation3-510x510.jpg", + "https://pilcare.com/wp-content/uploads/elementor/thumbs/Honitus-hot-sip-Ayurvedic-kaadha-rbhr2bfeu39yhhrek47s4aku01gymmd7j829jesp6w.jpg", + "https://rukminim2.flixcart.com/image/832/832/xif0q/ayurvedic/x/i/s/honitus-hot-sip-kadha-30-sachets-chyawanprash-950g-2-dabur-original-imaghgehqdbsczzn.jpeg?q=70&crop=false" + ] + } + ] + } +} \ No newline at end of file diff --git a/data/cache/image_corroboration.db b/data/cache/image_corroboration.db index 3727343..6578000 100644 Binary files a/data/cache/image_corroboration.db and b/data/cache/image_corroboration.db differ diff --git a/data/cache/off_brand_corpus/amul.json b/data/cache/off_brand_corpus/amul.json index e360a30..80d6c27 100644 --- a/data/cache/off_brand_corpus/amul.json +++ b/data/cache/off_brand_corpus/amul.json @@ -1,408 +1,437 @@ { - "brand": "amul", + "schema": 2, + "endpoint": "https://world.openfoodfacts.org/api/v2/search", + "brand": "Amul", + "brand_tag": "amul", "country": "india", - "fetched_at": 1788767461.9774995, - "fetched_at_human": "2026-09-07 13:21:01", + "fetched_at": 1789105277.1010263, + "fetched_at_human": "2026-09-11 11:11:17", "hits": [ { - "code": "8901262202015", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Masala Buttermilk", - "product_name_en": "Amul Masala Buttermilk" - }, - { - "code": "8901262173506", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Ice Cream", - "product_name_en": "Amul Ice Cream" - }, - { - "code": "8901262130363", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Pro Plus", - "product_name_en": "Amul Pro Plus" - }, - { - "code": "8901262150064", - "brands": [ - "Amul" - ], - "quantity": "1L", - "countries_tags": [ - "en:france", - "en:india" - ], - "product_name": "Amul Taaza", - "product_name_en": "Amul Taaza" - }, - { - "code": "8901262090469", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "T-Special Premium Dairy Whitener", - "product_name_en": "T-Special Premium Dairy Whitener" - }, - { - "code": "8901262202046", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Lassi", - "product_name_en": "Amul Lassi" - }, - { - "code": "8901262152945", - "brands": [ - "Amul" - ], - "quantity": "250 ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Protein Lassi - Mango flavour", - "product_name_en": "Protein Lassi - Mango flavour" - }, - { - "code": "8901262201124", - "brands": [ - "Amul" - ], - "quantity": "85 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Masti Dahi", - "product_name_en": "Amul Masti Dahi" - }, - { + "brands": "Amul, Amul taaza", "code": "8901262260121", - "brands": [ - "Amul taaza" - ], - "quantity": "500 ml", "countries_tags": [ "en:india" ], - "product_name": "Toned Milk", - "product_name_en": "Toned Milk" + "product_name": "Amul Taaza Milky Milk", + "product_name_en": "Amul Taaza Milky Milk", + "quantity": "500 ml" }, { - "code": "8901262300612", - "brands": [ - "amul" - ], - "quantity": "67g", + "brands": "AMUL", + "code": "8901262010320", "countries_tags": [ "en:india" ], - "product_name": "Tender Coconut", - "product_name_en": "Tender Coconut" + "product_name": "Amul Butter", + "product_name_en": "Amul Butter", + "quantity": "200gm" }, { - "code": "8901262071703", - "brands": [ - "Amul" - ], - "quantity": "125", + "brands": "Amul", + "code": "8901262070546", "countries_tags": [ "en:india" ], - "product_name": "Twilight Tryst Single Origin Dark Chocolate", - "product_name_en": "Twilight Tryst Single Origin Dark Chocolate" + "product_name": "Chocominis", + "product_name_en": "Chocominis", + "quantity": "250g" }, { - "code": "8901262151269", - "brands": [ - "Amul" - ], - "quantity": "140ml", + "brands": "Amul", + "code": "8901262180115", "countries_tags": [ "en:india" ], - "product_name": "Amul Moti", - "product_name_en": "Amul Moti" + "product_name": "malai paneer fresh", + "product_name_en": "malai paneer fresh", + "quantity": "200g" }, { - "code": "8901262300117", - "brands": [ - "Amul" - ], - "quantity": "540gm", + "brands": "Amul", + "code": "8901262200189", "countries_tags": [ "en:india" ], - "product_name": "Chocolate Crackle Ice cream", - "product_name_en": "Chocolate Crackle Ice cream" + "product_name": "Amul Lassi 200ml", + "product_name_en": "Amul Lassi 200ml", + "quantity": "200mL" }, { - "code": "8901262150217", - "brands": [ - "Amul" - ], - "quantity": "500ml", + "brands": "Amul", + "code": "8901262151863", "countries_tags": [ "en:india" ], - "product_name": "Taaza", - "product_name_en": "Taaza" + "product_name": "Amul Fresh Cream", + "product_name_en": "Amul Fresh Cream", + "quantity": "250 ml" }, { - "code": "8901262071680", - "brands": [ - "Amul" - ], - "quantity": "150g", + "brands": "Amul", + "code": "8901262172448", "countries_tags": [ "en:india" ], - "product_name": "Hazelnut Chocolate", - "product_name_en": "Hazelnut Chocolate" + "product_name": "Amul Chocolate Brownie", + "product_name_en": "Amul Chocolate Brownie", + "quantity": "1L / 540 g" }, { - "code": "8901262200004", - "brands": [ - "Amul" - ], - "quantity": "500ml", + "brands": "Amul", + "code": "8901262151696", "countries_tags": [ "en:india" ], - "product_name": "Butter milk amul", - "product_name_en": "Butter milk amul" + "product_name": "Amul Lassi 250 ml", + "product_name_en": "Amul Lassi 250 ml", + "quantity": "250 ml" }, { - "code": "8901262175074", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262070676", "countries_tags": [ "en:india" ], - "product_name": "Amul nutty ice ceam" + "product_name": "Dark Chocolate", + "product_name_en": "Dark Chocolate", + "quantity": "150 g" }, { - "code": "8901262200271", - "brands": [ - "Amul" - ], - "quantity": "200g", + "brands": "Amul", + "code": "8901262260107", "countries_tags": [ "en:india" ], - "product_name": "Amul dahi", - "product_name_en": "Amul dahi" + "product_name": "Amul Gold Milky Milk", + "product_name_en": "Amul Gold Milky Milk", + "quantity": "" }, { - "code": "8901262080033", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262174114", "countries_tags": [ "en:india" ], - "product_name": "Amulspray Infant Milk Food", - "product_name_en": "Amulspray Infant Milk Food" + "product_name": "King Alphonso Gold Ice Cream", + "product_name_en": "King Alphonso Gold Ice Cream" }, { - "code": "8901262172400", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262071864", "countries_tags": [ "en:india" ], - "product_name": "Two In One. Strawberry And Vanilla", - "product_name_en": "Two In One. Strawberry And Vanilla" + "product_name": "99% Cacao Ultimate Dark Chocolate", + "product_name_en": "99% Cacao Ultimate Dark Chocolate", + "quantity": "125 g" }, { - "code": "8901262030663", - "brands": [ - "amul" - ], - "quantity": "200 ml", + "brands": "Amul", + "code": "8901262173490", "countries_tags": [ "en:india" ], - "product_name": "amul cow ghee", - "product_name_en": "amul cow ghee" + "ecoscore_tags": [ + "b" + ], + "product_name": "Vanilla ice cream", + "product_name_en": "Vanilla ice cream" }, { + "brands": "Amul", "code": "8901262010023", - "brands": [ - "Amul" - ], - "quantity": "250", "countries_tags": [ "en:india" ], "product_name": "Amul pasteurised butter", - "product_name_en": "Amul pasteurised butter" + "product_name_en": "Amul pasteurised butter", + "quantity": "250" }, { - "code": "8901262070058", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262200622", "countries_tags": [ "en:india" ], - "product_name": "Amul Fruit 'N" Nut", - "product_name_en": "Amul Fruit 'N" Nut" + "product_name": "Amul Masti", + "product_name_en": "Amul Masti", + "quantity": "390 g" }, { - "code": "8901262010320", - "brands": [ - "AMUL" + "brands": "Amul", + "code": "8901262202220", + "countries_tags": [ + "en:india", + "en:united-states" ], - "quantity": "200gm", + "product_name": "Dahi Yogurt", + "product_name_en": "Dahi Yogurt" + }, + { + "brands": "Amul", + "code": "8901262202244", "countries_tags": [ "en:india" ], - "product_name": "Amul", - "product_name_en": "Amul" + "product_name": "Tadka Chhach Probiotic Buttermilk", + "product_name_en": "Tadka Chhach Probiotic Buttermilk", + "quantity": "300 ml" }, { - "code": "8901262180146", - "brands": [ - "Amul" - ], - "quantity": "1kg", + "brands": "Amul", + "code": "8901262020244", "countries_tags": [ "en:india" ], - "product_name": "Amul Malai Paneer", - "product_name_en": "Amul Malai Paneer" + "product_name": "Amul Cheese", + "product_name_en": "Amul Cheese", + "quantity": "200g" }, { - "code": "8901262020015", - "brands": [ - "Amul" + "brands": "Amul", + "code": "8901262173483", + "countries_tags": [ + "en:india", + "en:united-states" ], - "quantity": "200 g", + "product_name": "Amul Fruit 'N' Nut Fantasy", + "product_name_en": "Amul Fruit 'N' Nut Fantasy", + "quantity": "540 gm" + }, + { + "brands": "Amul", + "code": "8901262152211", "countries_tags": [ "en:india" ], - "product_name": "Cheese slices", - "product_name_en": "Cheese slices" + "product_name": "Amul Kool Kesar Flavour 180ml", + "product_name_en": "Amul Kool Kesar Flavour 180ml", + "quantity": "180ml" }, { + "brands": "Amul", + "code": "8901262200028", + "countries_tags": [ + "en:india" + ], + "product_name": "Dahi", + "product_name_en": "Dahi", + "quantity": "200 g" + }, + { + "brands": "Amul", + "code": "8901262070454", + "countries_tags": [ + "en:india", + "en:united-states" + ], + "product_name": "Amul Dark Chocolate", + "product_name_en": "Amul Dark Chocolate", + "quantity": "40g" + }, + { + "brands": "Amul", + "code": "8901262175845", + "countries_tags": [ + "en:india" + ], + "product_name": "Icecream" + }, + { + "brands": "Amul", + "code": "8901262071048", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Sugar free chocolate", + "product_name_en": "Amul Sugar free chocolate", + "quantity": "150g" + }, + { + "brands": "Amul", + "code": "8901262173599", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Rajbhog", + "product_name_en": "Amul Rajbhog", + "quantity": "1L/540 g" + }, + { + "brands": "Amul", + "code": "8901262020558", + "countries_tags": [ + "en:india" + ], + "product_name": "Diced Cheese", + "product_name_en": "Diced Cheese" + }, + { + "brands": "Amul", + "code": "8901262040044", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul shrikhand badam pista", + "product_name_en": "Amul shrikhand badam pista", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262173506", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "b" + ], + "product_name": "Amul Ice Cream", + "product_name_en": "Amul Ice Cream", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262151306", + "countries_tags": [ + "en:india", + "en:united-states" + ], + "product_name": "Kool cafe", + "product_name_en": "Kool cafe" + }, + { + "brands": "Amul", + "code": "8901262030243", + "countries_tags": [ + "en:india", + "en:united-states" + ], + "product_name": "Amul gee", + "product_name_en": "Amul gee", + "quantity": "1L" + }, + { + "brands": "Amul", + "code": "8901262070539", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "e" + ], + "product_name": "FRUIT N' NUT Dark Chocolate", + "product_name_en": "FRUIT N' NUT Dark Chocolate", + "quantity": "150g" + }, + { + "brands": "amul", + "code": "8901262152242", + "countries_tags": [ + "en:india", + "en:state-of-palestine" + ], + "product_name": "Amul Kool Badam Flv 180ml", + "product_name_en": "Amul Kool Badam Flv 180ml", + "quantity": "Amul" + }, + { + "brands": "Amul", + "code": "8901262151368", + "countries_tags": [ + "en:india" + ], + "product_name": "Moti", + "product_name_en": "Moti", + "quantity": "" + }, + { + "brands": "Amul", "code": "8901262153348", - "brands": [ - "Amul" - ], "countries_tags": [ "en:india" ], "product_name": "Protein Lassi - Rose Flavour", - "product_name_en": "Protein Lassi - Rose Flavour" + "product_name_en": "Protein Lassi - Rose Flavour", + "quantity": "200mL" }, { - "code": "8901262172363", - "brands": [ - "Amul" - ], - "quantity": "2L", + "brands": "Amul", + "code": "8901262260947", "countries_tags": [ "en:india" ], - "product_name": "Amul Ice Cream", - "product_name_en": "Amul Ice Cream" + "product_name": "Buffalo Milk A2", + "product_name_en": "Buffalo Milk A2", + "quantity": "500ml" }, { - "code": "8901262050173", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262180016", "countries_tags": [ "en:india" ], - "product_name": "Khoa", - "product_name_en": "Khoa" + "ecoscore_tags": [ + "d" + ], + "product_name": "Malai Paneer", + "product_name_en": "Malai Paneer", + "quantity": "200 g" }, { - "code": "9001962200942", - "brands": [ - "Amul" + "brands": "Amul", + "code": "8901262260954", + "countries_tags": [ + "en:india", + "en:united-kingdom" ], + "product_name": "Buffalo Milk", + "product_name_en": "Buffalo Milk", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262152952", "countries_tags": [ "en:india" ], - "product_name": "Kool Badam Flavour", - "product_name_en": "Kool Badam Flavour" + "product_name": "Protein Buttermilk", + "product_name_en": "Protein Buttermilk", + "quantity": "200 ml" }, { - "code": "8901262151429", - "brands": [ - "Amul" - ], - "quantity": "200 ml", + "brands": "Amul", + "code": "8901262071703", "countries_tags": [ "en:india" ], - "product_name": "Amul kool Cofe", - "product_name_en": "Amul kool Cofe" + "product_name": "Twilight Tryst Single Origin Dark Chocolate", + "product_name_en": "Twilight Tryst Single Origin Dark Chocolate", + "quantity": "125" }, { - "code": "8901262171557", - "brands": [ - "Amul" - ], + "brands": "amul", + "code": "8901262300612", "countries_tags": [ "en:india" ], - "product_name": "Chocolate family pack", - "product_name_en": "Chocolate family pack" + "product_name": "Tender Coconut", + "product_name_en": "Tender Coconut", + "quantity": "67g" }, { - "code": "2000000136641", - "brands": [ - "Amul" - ], - "quantity": "100 g", + "brands": "Amul", + "code": "8901262040013", "countries_tags": [ "en:india" ], - "product_name": "Amul Cheese Slices", - "product_name_en": "Amul Cheese Slices" - }, - { - "code": "1948167800646", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Prolife probiotic buttermilk", - "product_name_en": "Prolife probiotic buttermilk" + "product_name": "Shrikhand", + "product_name_en": "Shrikhand", + "quantity": "500g" }, { + "brands": "Amul", "code": "8901262153331", - "brands": [ - "Amul" - ], "countries_tags": [ "en:india" ], @@ -410,849 +439,131 @@ "product_name_en": "Amul Protein Blueberry Shake" }, { - "code": "8901262153423", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262152181", "countries_tags": [ "en:india" ], - "product_name": "High Protein Milk", - "product_name_en": "High Protein Milk" + "product_name": "Dark Chocolate Milk", + "product_name_en": "Dark Chocolate Milk" }, { - "code": "8901262110266", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Whey Protein", - "product_name_en": "Whey Protein" - }, - { - "code": "8901262152303", - "brands": [ - "Amul" - ], - "quantity": "200ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Camel Milk", - "product_name_en": "Camel Milk" - }, - { - "code": "8901262174404", - "brands": [ - "Amul" - ], - "quantity": "125 ml / 75 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Sundae Gudbud", - "product_name_en": "Sundae Gudbud" - }, - { - "code": "8901262178068", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Choco Almond N' Kesar Badam Double Sundae", - "product_name_en": "Choco Almond N' Kesar Badam Double Sundae" - }, - { - "code": "8901262140065", - "brands": [ - "Amul" - ], - "quantity": "500 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Lite Bread Spread", - "product_name_en": "Lite Bread Spread" - }, - { - "code": "8901262071048", - "brands": [ - "Amul" - ], - "quantity": "150g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Sugar free chocolate", - "product_name_en": "Amul Sugar free chocolate" - }, - { - "code": "3948262152334", - "brands": [ - "Amul" - ], - "quantity": "180ml", - "countries_tags": [ - "en:india" - ], - "product_name": "KOOL ROYAL ELAICHI", - "product_name_en": "KOOL ROYAL ELAICHI" - }, - { - "code": "8901262260091", - "brands": [ - "Amul" - ], - "quantity": "1L", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Taaza", - "product_name_en": "Amul Taaza" - }, - { - "code": "8901262150095", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Gold Milk", - "product_name_en": "Amul Gold Milk" - }, - { - "code": "8901262080118", - "brands": [ - "Amul" - ], - "quantity": "200g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amulspray Infant Milk Food", - "product_name_en": "Amulspray Infant Milk Food" - }, - { - "code": "8901262152136", - "brands": [ - "Amul" - ], - "quantity": "200ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Milk Shake Double Chocolate", - "product_name_en": "Milk Shake Double Chocolate" - }, - { - "code": "8901262030601", - "brands": [ - "Amul" - ], - "quantity": "1 kg", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul cow ghee", - "product_name_en": "Amul cow ghee" - }, - { - "code": "8901262070973", - "brands": [ - "Amul" - ], - "quantity": "125 g", + "brands": "Amul", + "code": "8901262010207", "countries_tags": [ "en:india", - "en:united-states" + "en:united-kingdom" ], - "product_name": "Venezuela Ebony Twist", - "product_name_en": "Venezuela Ebony Twist" + "product_name": "Butter School Pack", + "product_name_en": "Butter School Pack", + "quantity": "100 g" }, { - "code": "8901262071017", - "brands": [ - "Amul" - ], - "quantity": "125g", + "brands": "Amul", + "code": "8901262175869", "countries_tags": [ "en:india" ], - "product_name": "Amul Belgian chocolate", - "product_name_en": "Amul Belgian chocolate" + "product_name": "Amul Shalimar", + "product_name_en": "Amul Shalimar", + "quantity": "" }, { - "code": "8901262260374", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262152228", "countries_tags": [ "en:india" ], - "product_name": "Amul Cow Milk", - "product_name_en": "Amul Cow Milk" + "product_name": "Amul kool elaichi 180ml", + "product_name_en": "Amul kool elaichi 180ml", + "quantity": "180 ml" }, { - "code": "8901262173490", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262030700", "countries_tags": [ "en:india" ], - "product_name": "Vanilla ice cream", - "product_name_en": "Vanilla ice cream" + "product_name": "Amul pure ghee 1 ltr", + "product_name_en": "Amul pure ghee 1 ltr", + "quantity": "" }, { - "code": "8901262152952", - "brands": [ - "Amul" - ], - "quantity": "200 ml", + "brands": "Amul", + "code": "8901262200011", "countries_tags": [ "en:india" ], - "product_name": "Protein Buttermilk", - "product_name_en": "Protein Buttermilk" + "product_name": "Masti Dahi", + "product_name_en": "Masti Dahi", + "quantity": "85 g" }, { - "code": "8901262172493", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Chocochips Icecream" - }, - { - "code": "8901262202428", - "brands": [ - "Amul" - ], - "quantity": "1 kg", - "countries_tags": [ - "en:india" - ], - "product_name": "Curd", - "product_name_en": "Curd" - }, - { - "code": "8901262260114", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Gold Milky Milk", - "product_name_en": "Gold Milky Milk" - }, - { - "code": "8901262201995", - "brands": [ - "Amul" - ], - "quantity": "310ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Buttermilk", - "product_name_en": "Amul Buttermilk" - }, - { - "code": "8901262150378", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "slim 'n' trim Skimmed Milk", - "product_name_en": "slim 'n' trim Skimmed Milk" - }, - { - "code": "8901262152341", - "brands": [ - "Amul" - ], - "quantity": "180ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Kool Premium Kesar Flavour", - "product_name_en": "Amul Kool Premium Kesar Flavour" - }, - { - "code": "8901262178198", - "brands": [ - "Amul" - ], - "quantity": "80 ml / 55 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Epic Choco Almond with Beligian Chocolate", - "product_name_en": "Epic Choco Almond with Beligian Chocolate" - }, - { - "code": "8901262040051", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Shrikhand", - "product_name_en": "Shrikhand" - }, - { - "code": "8901262202299", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul High Protein", - "product_name_en": "Amul High Protein" - }, - { - "code": "8901262020671", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Cheese Sauce Green Chutney", - "product_name_en": "Cheese Sauce Green Chutney" - }, - { - "code": "8901262178075", - "brands": [ - "Amul" - ], - "quantity": "1 l", - "countries_tags": [ - "en:india" - ], - "product_name": "Chocolate Magic Sundae", - "product_name_en": "Chocolate Magic Sundae" - }, - { - "code": "8901262153355", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Kool", - "product_name_en": "Amul Kool" - }, - { - "code": "8901262152334", - "brands": [ - "Amul", - "Kool" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Kool Royal Elaichi Flavour", - "product_name_en": "Amul Kool Royal Elaichi Flavour" - }, - { - "code": "8901262080279", - "brands": [ - "Amul" - ], - "quantity": "21g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amulspray", - "product_name_en": "Amulspray" - }, - { - "code": "8901262151863", - "brands": [ - "Amul" - ], - "quantity": "250 ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Fresh Cream", - "product_name_en": "Amul Fresh Cream" - }, - { - "code": "8901262260947", - "brands": [ - "Amul" - ], - "quantity": "500ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Buffalo Milk A2", - "product_name_en": "Buffalo Milk A2" - }, - { - "code": "8901262200189", - "brands": [ - "Amul" - ], - "quantity": "200mL", - "countries_tags": [ - "en:india" - ], - "product_name": "Lassi", - "product_name_en": "Lassi" - }, - { - "code": "8901262174244", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Vanilla Gold", - "product_name_en": "Amul Vanilla Gold" - }, - { - "code": "0941402151368", - "brands": [ - "Amul" - ], - "quantity": "450ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Moti UHT Treated Homogenized Toned Milk", - "product_name_en": "Moti UHT Treated Homogenized Toned Milk" - }, - { - "code": "8901262150088", - "brands": [ - "Amul" - ], - "quantity": "200 ml", + "brands": "Amul", + "code": "8901262260091", "countries_tags": [ "en:india" ], "product_name": "Amul Taaza", - "product_name_en": "Amul Taaza" - }, - { - "code": "8901262176552", - "brands": [ - "Amul" - ], - "quantity": "60ml / 48 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Punjabi Kulfi Mawa Elaichi", - "product_name_en": "Punjabi Kulfi Mawa Elaichi" - }, - { - "code": "8901262176477", - "brands": [ - "Amul" - ], - "quantity": "60ml/50g", - "countries_tags": [ - "en:india" - ], - "product_name": "Mawa Malai Kulfi", - "product_name_en": "Mawa Malai Kulfi" - }, - { - "code": "8901262175845", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Icecream" - }, - { - "code": "8901262200196", - "brands": [ - "Amul" - ], - "quantity": "200mL", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Masti Spiced Buttermilk", - "product_name_en": "Amul Masti Spiced Buttermilk" - }, - { - "code": "8901262151344", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Badam Shakers" - }, - { - "code": "8901262180030", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Paneer", - "product_name_en": "Paneer" - }, - { - "code": "8901262090322", - "brands": [ - "Amul" - ], - "quantity": "500g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amulya dairy whitner", - "product_name_en": "Amulya dairy whitner" - }, - { - "code": "8901262151375", - "brands": [ - "Amul" - ], - "quantity": "1", - "countries_tags": [ - "en:india" - ], - "product_name": "Cool Kesar", - "product_name_en": "Cool Kesar" - }, - { - "code": "8901262020299", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Pure Milk Cheese", - "product_name_en": "Pure Milk Cheese" + "product_name_en": "Amul Taaza", + "quantity": "1L" }, { + "brands": "Amul", "code": "8901262200677", - "brands": [ - "Amul" - ], - "quantity": "1 kg", "countries_tags": [ "en:india" ], + "ecoscore_tags": [ + "unknown" + ], "product_name": "amul masti dahi", - "product_name_en": "amul masti dahi" + "product_name_en": "amul masti dahi", + "quantity": "1 kg" }, { - "code": "8901262071109", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262030878", "countries_tags": [ "en:india" ], - "product_name": "Super Fruit", - "product_name_en": "Super Fruit" + "product_name": "Amul cow ghee 1ltr", + "product_name_en": "Amul cow ghee 1ltr", + "quantity": "1ltr" }, { - "code": "8901262150118", - "brands": [ - "Amul" - ], - "quantity": "1L", + "brands": "Amul", + "code": "8901262140034", "countries_tags": [ "en:india" ], - "product_name": "Fresh Cream", - "product_name_en": "Fresh Cream" + "product_name": "Delicious Fat Spread", + "product_name_en": "Delicious Fat Spread", + "quantity": "" }, { - "code": "8901262222471", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262153607", "countries_tags": [ "en:india" ], - "product_name": "Instant Mashed Potato", - "product_name_en": "Instant Mashed Potato" + "product_name": "Protein Lassi - Plain", + "product_name_en": "Protein Lassi - Plain", + "quantity": "200 ml" }, { - "code": "8901262070539", - "brands": [ - "Amul" - ], - "quantity": "150g", + "brands": "Amul", + "code": "8901262070836", "countries_tags": [ "en:india" ], - "product_name": "FRUIT N' NUT Dark Chocolate", - "product_name_en": "FRUIT N' NUT Dark Chocolate" + "product_name": "Amul Bitter Chocolate", + "product_name_en": "Amul Bitter Chocolate", + "quantity": "150g" }, { - "code": "8901262221382", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Butter Nankhatai", - "product_name_en": "Butter Nankhatai" - }, - { - "code": "0041102177191", - "brands": [ - "Amul" - ], - "quantity": "150g", - "countries_tags": [ - "en:india" - ], - "product_name": "Cassatta", - "product_name_en": "Cassatta" - }, - { - "code": "8901262150989", - "brands": [ - "Amul" - ], - "quantity": "1 litre", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Gold", - "product_name_en": "Amul Gold" - }, - { - "code": "8901262200028", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Dahi", - "product_name_en": "Dahi" - }, - { - "code": "8901262031059", - "brands": [ - "Amul ghee" - ], - "quantity": "500 gm", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul ghee", - "product_name_en": "Amul ghee" - }, - { - "code": "8901262152211", - "brands": [ - "Amul" - ], - "quantity": "2", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Kool Kesar Flv - 180Ml", - "product_name_en": "Amul Kool Kesar Flv - 180Ml" - }, - { - "code": "8901262010511", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Peanut spread" - }, - { - "code": "8901262151368", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "Moti", - "product_name_en": "Moti" - }, - { - "code": "8901262010344", - "brands": [ - "Amul" - ], - "quantity": "100g", - "countries_tags": [ - "en:india" - ], - "product_name": "Garlic & Herbs Buttery Spread", - "product_name_en": "Garlic & Herbs Buttery Spread" - }, - { - "code": "8901262152228", - "brands": [ - "Amul" - ], - "quantity": "180 ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul kool elaichi flavour", - "product_name_en": "Amul kool elaichi flavour" - }, - { - "code": "8901262030243", - "brands": [ - "Amul" - ], - "quantity": "1L", - "countries_tags": [ - "en:india", - "en:united-states" - ], - "product_name": "Pure cow ghee", - "product_name_en": "Pure cow ghee" - }, - { - "code": "8901262180429", - "brands": [ - "Amul" - ], - "countries_tags": [ - "en:india" - ], - "product_name": "High Protein Paneer", - "product_name_en": "High Protein Paneer" - }, - { - "code": "8901262180016", - "brands": [ - "Amul" - ], - "quantity": "200 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Malai Paneer", - "product_name_en": "Malai Paneer" - }, - { - "code": "8901262260152", - "brands": [ - "Amul" - ], - "quantity": "500ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Slim 'n' Trim", - "product_name_en": "Slim 'n' Trim" - }, - { - "code": "8901262173223", - "brands": [ - "Amul" - ], - "quantity": "750 mL", - "countries_tags": [ - "en:india" - ], - "product_name": "Butterscotch Bliss", - "product_name_en": "Butterscotch Bliss" - }, - { - "code": "8901262152242", - "brands": [ - "amul" - ], - "quantity": "Amul", - "countries_tags": [ - "en:india", - "en:state-of-palestine" - ], - "product_name": "Amul Kool Badam Flv 180ml", - "product_name_en": "Amul Kool Badam Flv 180ml" - }, - { - "code": "8901262178440", - "brands": [ - "Amul" - ], - "quantity": "80 ml", - "countries_tags": [ - "en:india" - ], - "product_name": "Cookie Sub", - "product_name_en": "Cookie Sub" - }, - { - "code": "8901262200622", - "brands": [ - "Amul" - ], - "quantity": "400 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Masti", - "product_name_en": "Amul Masti" - }, - { - "code": "8901262180115", - "brands": [ - "Amul" - ], - "quantity": "200g", - "countries_tags": [ - "en:india" - ], - "product_name": "malai paneer fresh", - "product_name_en": "malai paneer fresh" - }, - { - "code": "8901262172448", - "brands": [ - "Amul" - ], - "quantity": "1L / 540 g", - "countries_tags": [ - "en:india" - ], - "product_name": "Amul Chocolate Brownie", - "product_name_en": "Amul Chocolate Brownie" - }, - { - "code": "3948262211062", - "brands": [ - "Amul" - ], - "quantity": "85g", + "brands": "Amul", + "code": "8901262200035", "countries_tags": [ "en:india" ], @@ -1260,56 +571,93 @@ "product_name_en": "Masti Dahi" }, { - "code": "8901262020244", - "brands": [ - "Amul" - ], - "quantity": "200g", + "brands": "Amul", + "code": "8901262177122", "countries_tags": [ "en:india" ], - "product_name": "Amul Cheese", - "product_name_en": "Amul Cheese" + "product_name": "Tricone Butterscotch", + "product_name_en": "Tricone Butterscotch" }, { - "code": "8901262010504", - "brands": [ - "Amul" - ], - "quantity": "300g", + "brands": "Amul", + "code": "8901262020381", "countries_tags": [ "en:india" ], - "product_name": "Peanut Spread Crunchy", - "product_name_en": "Peanut Spread Crunchy" + "product_name": "Amul cheese", + "product_name_en": "Amul cheese", + "quantity": "500g" }, { - "code": "8901262173605", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262151726", "countries_tags": [ "en:india" ], - "product_name": "Icecream American Nuts", - "product_name_en": "Icecream American Nuts" + "product_name": "Amul Lactose Free250ml", + "product_name_en": "Amul Lactose Free250ml", + "quantity": "" }, { - "code": "8901262020404", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262120005", "countries_tags": [ "en:india" ], - "product_name": "Cheese Slice A*", - "product_name_en": "Cheese Slice A*" + "product_name": "mithai mate", + "product_name_en": "mithai mate", + "quantity": "" }, { + "brands": "Amul", + "code": "8901262030151", + "countries_tags": [ + "en:france", + "en:india" + ], + "product_name": "Amul Ghee 1ltr", + "quantity": "1 ltr" + }, + { + "brands": "Amul", + "code": "8901262260114", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Gold Milky Milk", + "product_name_en": "Gold Milky Milk", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262178068", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "b" + ], + "product_name": "Choco Almond N' Kesar Badam Double Sundae", + "product_name_en": "Choco Almond N' Kesar Badam Double Sundae", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262201124", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Masti Dahi", + "product_name_en": "Amul Masti Dahi", + "quantity": "85 g" + }, + { + "brands": "Amul", "code": "8901262020091", - "brands": [ - "Amul" - ], "countries_tags": [ "en:india" ], @@ -1317,44 +665,729 @@ "product_name_en": "Cheese Cube" }, { - "code": "8901262151726", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262151344", "countries_tags": [ "en:india" ], - "product_name": "Lactose Free", - "product_name_en": "Lactose Free" + "product_name": "Badam Shakers" }, { - "code": "8901262090421", - "brands": [ - "Amul" + "brands": "AMUL", + "code": "8901262030366", + "countries_tags": [ + "en:india" ], - "quantity": "10g", + "product_name": "AMUL PURE GHEE", + "product_name_en": "AMUL PURE GHEE", + "quantity": "1L" + }, + { + "brands": "Amul", + "code": "8901262200271", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul dahi", + "product_name_en": "Amul dahi", + "quantity": "200g" + }, + { + "brands": "Amul", + "code": "8901262030519", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Cow Ghee", + "product_name_en": "Amul Cow Ghee", + "quantity": "500ml" + }, + { + "brands": "Amul", + "code": "8901262070324", + "countries_tags": [ + "en:india" + ], + "product_name": "Sugar free dark chocolate", + "product_name_en": "Sugar free dark chocolate", + "quantity": "35g" + }, + { + "brands": "Amul", + "code": "8901262010436", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul White Unsalted Butter", + "product_name_en": "Amul White Unsalted Butter", + "quantity": "100g" + }, + { + "brands": "Amul", + "code": "8901262176224", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Chocobar Probiotic", + "product_name_en": "Amul Chocobar Probiotic", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262173421", + "countries_tags": [ + "en:india" + ], + "product_name": "Moroccon Dry Fruit", + "product_name_en": "Moroccon Dry Fruit" + }, + { + "brands": "Amul", + "code": "8901262153300", + "countries_tags": [ + "en:india" + ], + "product_name": "Try Nutrition", + "product_name_en": "Try Nutrition" + }, + { + "brands": "Amul", + "code": "8901262174138", + "countries_tags": [ + "en:india" + ], + "product_name": "Gold Cookies N Cream", + "product_name_en": "Gold Cookies N Cream" + }, + { + "brands": "Amul", + "code": "8901262150989", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Gold 1ltr", + "product_name_en": "Amul Gold 1ltr", + "quantity": "1 litre" + }, + { + "brands": "Amul", + "code": "8901262130325", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Pro Chocolate 500g pp", + "product_name_en": "Amul Pro Chocolate 500g pp", + "quantity": "5" + }, + { + "brands": "Amul", + "code": "8901262178075", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Chocolate Magic Sundae", + "product_name_en": "Chocolate Magic Sundae", + "quantity": "1 l" + }, + { + "brands": "Amul", + "code": "8901262030564", + "countries_tags": [ + "en:india" + ], + "product_name": "Cow Ghee", + "product_name_en": "Cow Ghee", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262220552", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "d" + ], + "product_name": "Chocolate Cookies", + "product_name_en": "Chocolate Cookies", + "quantity": "50g" + }, + { + "brands": "Amul", + "code": "8901262173223", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "b" + ], + "product_name": "Butterscotch Bliss", + "product_name_en": "Butterscotch Bliss", + "quantity": "750 mL" + }, + { + "brands": "Amul", + "code": "8901262080279", + "countries_tags": [ + "en:india" + ], + "product_name": "Amulspray", + "product_name_en": "Amulspray", + "quantity": "21g" + }, + { + "brands": "Amul", + "code": "8901262020299", + "countries_tags": [ + "en:india" + ], + "product_name": "Pure Milk Cheese", + "product_name_en": "Pure Milk Cheese" + }, + { + "brands": "Amul", + "code": "8901262151375", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Cool Kesar", + "product_name_en": "Cool Kesar", + "quantity": "1" + }, + { + "brands": "Amul", + "code": "8901262176446", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "b" + ], + "product_name": "Pista malai kulfi", + "product_name_en": "Pista malai kulfi", + "quantity": "1,54.6g(60ml)" + }, + { + "brands": "Amul", + "code": "8901262030458", + "countries_tags": [ + "en:india" + ], + "product_name": "Ghee", + "product_name_en": "Ghee", + "quantity": "200ml" + }, + { + "brands": "Amul", + "code": "8901262130301", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Pro Whey Protein Malt Beverage", + "product_name_en": "Amul Pro Whey Protein Malt Beverage", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262120029", + "countries_tags": [ + "en:india" + ], + "product_name": "Mithai Mate", + "product_name_en": "Mithai Mate", + "quantity": "400 gm" + }, + { + "brands": "Amul", + "code": "8901262300513", + "countries_tags": [ + "en:india" + ], + "product_name": "Fruit & Nut Fantasy Gold", + "product_name_en": "Fruit & Nut Fantasy Gold" + }, + { + "brands": "Amul", + "code": "8901262020541", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul diced cheese blend (mozzarella and cheddar cheese blend)", + "product_name_en": "Amul diced cheese blend (mozzarella and cheddar cheese blend)", + "quantity": "1000g" + }, + { + "brands": "Amul", + "code": "8901262070430", + "countries_tags": [ + "en:india", + "en:united-states" + ], + "product_name": "Amul", + "product_name_en": "Amul" + }, + { + "brands": "Amul", + "code": "8901262260152", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "d" + ], + "product_name": "Slim 'n' Trim", + "product_name_en": "Slim 'n' Trim", + "quantity": "500ml" + }, + { + "brands": "Amul", + "code": "8901262150118", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Fresh Cream 1ltr", + "product_name_en": "Amul Fresh Cream 1ltr", + "quantity": "1L" + }, + { + "brands": "Amul", + "code": "8901262201001", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Spiced Buttermilk", + "product_name_en": "Amul Spiced Buttermilk", + "quantity": "200ml" + }, + { + "brands": "amul", + "code": "8901262030663", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "e" + ], + "product_name": "amul cow ghee", + "product_name_en": "amul cow ghee", + "quantity": "200 ml" + }, + { + "brands": "Amul", + "code": "8901262178259", + "countries_tags": [ + "en:india" + ], + "product_name": "Ice Cream Sandwich Vanilla", + "product_name_en": "Ice Cream Sandwich Vanilla", + "quantity": "80ml, 53g" + }, + { + "brands": "Amul", + "code": "8901262260138", + "countries_tags": [ + "en:india" + ], + "product_name": "Taaza Milk", + "product_name_en": "Taaza Milk" + }, + { + "brands": "Amul", + "code": "8901262300117", + "countries_tags": [ + "en:india" + ], + "product_name": "Chocolate Crackle Ice cream", + "product_name_en": "Chocolate Crackle Ice cream", + "quantity": "540gm" + }, + { + "brands": "Amul", + "code": "8901262072670", + "countries_tags": [ + "en:india" + ], + "product_name": "55 % dark chocolate", + "product_name_en": "55 % dark chocolate", + "quantity": "125 g" + }, + { + "brands": "Amul", + "code": "8901262300544", + "countries_tags": [ + "en:india" + ], + "product_name": "Epic Almond Gold With Belgian Chocolate", + "product_name_en": "Epic Almond Gold With Belgian Chocolate" + }, + { + "brands": "AMUL", + "code": "8901262173216", + "countries_tags": [ + "en:india" + ], + "product_name": "AMUL VANILLA ROYALE", + "product_name_en": "AMUL VANILLA ROYALE", + "quantity": "405g" + }, + { + "brands": "Amul", + "code": "8901262040211", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Amrakhand", + "product_name_en": "Amul Amrakhand", + "quantity": "200g" + }, + { + "brands": "Amul", + "code": "8901262174480", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Butterscotch Gold", + "product_name_en": "Amul Butterscotch Gold", + "quantity": "69g" + }, + { + "brands": "Amul", + "code": "8901262202077", + "countries_tags": [ + "en:india" + ], + "product_name": "Mango Yogurt", + "product_name_en": "Mango Yogurt" + }, + { + "brands": "Amul", + "code": "3948262180115", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Malai Paneer" + }, + { + "brands": "Amul", + "code": "8901262010511", + "countries_tags": [ + "en:india" + ], + "product_name": "Peanut Spread Creamy", + "product_name_en": "Peanut Spread Creamy", + "quantity": "300g" + }, + { + "brands": "Amul", + "code": "8901262010504", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Peanut Spread", + "product_name_en": "Amul Peanut Spread", + "quantity": "300g" + }, + { + "brands": "Amul", + "code": "8901262174244", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "b" + ], + "product_name": "Amul Vanilla Gold", + "product_name_en": "Amul Vanilla Gold", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262151429", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul kool Cofe", + "product_name_en": "Amul kool Cofe", + "quantity": "200 ml" + }, + { + "brands": "Amul", + "code": "8901262070058", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Fruit 'N" Nut", + "product_name_en": "Amul Fruit 'N" Nut", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262151658", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Lassi Mango" + }, + { + "brands": "Amul", + "code": "8901262171557", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Chocolate family pack", + "product_name_en": "Chocolate family pack" + }, + { + "brands": "Amul", + "code": "8901262180030", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Paneer", + "product_name_en": "Paneer" + }, + { + "brands": "Amul", + "code": "8901262050173", + "countries_tags": [ + "en:india" + ], + "product_name": "Khoa", + "product_name_en": "Khoa", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262020404", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul cheese", + "product_name_en": "Amul cheese" + }, + { + "brands": "Amul", + "code": "8901262220798", + "countries_tags": [ + "en:india" + ], + "product_name": "Coconut Cookies", + "product_name_en": "Coconut Cookies" + }, + { + "brands": "Amul", + "code": "8901262201438", + "countries_tags": [ + "en:india" + ], + "product_name": "Rabri", + "product_name_en": "Rabri" + }, + { + "brands": "Amul", + "code": "8901262201896", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Stirred Fruit Yogurt Strawberry", + "product_name_en": "Amul Stirred Fruit Yogurt Strawberry", + "quantity": "100g" + }, + { + "brands": "Amul", + "code": "8901262040020", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul shrikhand", + "product_name_en": "Amul shrikhand", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262300155", + "countries_tags": [ + "en:india" + ], + "product_name": "Aamras Kulfi", + "product_name_en": "Aamras Kulfi", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262301435", + "countries_tags": [ + "en:india" + ], + "product_name": "Berry Dazzle", + "product_name_en": "Berry Dazzle", + "quantity": "125ml / 67g" + }, + { + "brands": "Amul", + "code": "8901262152341", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Kool Premium Kesar Flavour", + "product_name_en": "Amul Kool Premium Kesar Flavour", + "quantity": "180ml" + }, + { + "brands": "Amul", + "code": "8901262152235", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Kool Rose" + }, + { + "brands": "Amul", + "code": "8901262170420", + "countries_tags": [ + "en:india" + ], + "product_name": "Chocochips Ice Cream", + "product_name_en": "Chocochips Ice Cream", + "quantity": "270g" + }, + { + "brands": "Amul", + "code": "8901262060028", + "countries_tags": [ + "en:india" + ], + "product_name": "Spicy Garlic Cheese Spread", + "product_name_en": "Spicy Garlic Cheese Spread", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262070010", + "countries_tags": [ + "en:india" + ], + "product_name": "amul milk chocolate", + "product_name_en": "amul milk chocolate", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262150378", + "countries_tags": [ + "en:india" + ], + "product_name": "slim 'n' trim Skimmed Milk", + "product_name_en": "slim 'n' trim Skimmed Milk", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262090421", "countries_tags": [ "en:india" ], "product_name": "Amulya Dairy Whitener", - "product_name_en": "Amulya Dairy Whitener" + "product_name_en": "Amulya Dairy Whitener", + "quantity": "10g" }, { - "code": "8901262178662", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262202046", "countries_tags": [ "en:india" ], - "product_name": "Tricone Dark Chocolate", - "product_name_en": "Tricone Dark Chocolate" + "product_name": "Amul Lassi", + "product_name_en": "Amul Lassi", + "quantity": "" }, { - "code": "8901262110235", - "brands": [ - "Amul" + "brands": "Amul", + "code": "8901262151269", + "countries_tags": [ + "en:india" ], + "product_name": "Amul Moti125ml", + "product_name_en": "Amul Moti125ml", + "quantity": "140ml" + }, + { + "brands": "Amul", + "code": "8901262221382", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "c" + ], + "product_name": "Butter Nankhatai", + "product_name_en": "Butter Nankhatai", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262178365", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "b" + ], + "product_name": "Black Currant Tri Cone", + "product_name_en": "Black Currant Tri Cone", + "quantity": "120ml/77g" + }, + { + "brands": "Amul", + "code": "8901262071109", + "countries_tags": [ + "en:india" + ], + "product_name": "Super Fruit", + "product_name_en": "Super Fruit", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262202015", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Masala Buttermilk", + "product_name_en": "Amul Masala Buttermilk", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262180146", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "d" + ], + "product_name": "Amul Malai Paneer", + "product_name_en": "Amul Malai Paneer", + "quantity": "1kg" + }, + { + "brands": "Amul", + "code": "8901262110235", "countries_tags": [ "en:india" ], @@ -1362,281 +1395,758 @@ "product_name_en": "Whey Protein" }, { - "code": "8901262221245", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262178198", "countries_tags": [ "en:india" ], - "product_name": "Elaichi rusk" + "ecoscore_tags": [ + "b" + ], + "product_name": "Epic Choco Almond with Beligian Chocolate", + "product_name_en": "Epic Choco Almond with Beligian Chocolate", + "quantity": "80 ml / 55 g" }, { - "code": "8901262201001", - "brands": [ - "Amul" - ], - "quantity": "200ml", + "brands": "Amul", + "code": "8901262261425", "countries_tags": [ "en:india" ], - "product_name": "Amul Spiced Buttermilk", - "product_name_en": "Amul Spiced Buttermilk" + "product_name": "Amul A2 Gir Cow Fresh Milk", + "product_name_en": "Amul A2 Gir Cow Fresh Milk", + "quantity": "1 L" }, { + "brands": "Amul", + "code": "8901030932083", + "countries_tags": [ + "en:india" + ], + "product_name": "Gulab Jamm Ice Cream", + "product_name_en": "Gulab Jamm Ice Cream" + }, + { + "brands": "Amul", + "code": "8901262020305", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Amul Cheese", + "product_name_en": "Amul Cheese", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262176552", + "countries_tags": [ + "en:india" + ], + "product_name": "Punjabi Kulfi Mawa Elaichi", + "product_name_en": "Punjabi Kulfi Mawa Elaichi", + "quantity": "60ml / 48 g" + }, + { + "brands": "Amul", + "code": "8901262152402", + "countries_tags": [ + "en:india" + ], + "product_name": "Butterscotch milkshake", + "product_name_en": "Butterscotch milkshake", + "quantity": "180 ml" + }, + { + "brands": "Amul", + "code": "8901262090469", + "countries_tags": [ + "en:india" + ], + "product_name": "T-Special Premium Dairy Whitener", + "product_name_en": "T-Special Premium Dairy Whitener", + "quantity": "22g" + }, + { + "brands": "Amul", + "code": "8901262153423", + "countries_tags": [ + "en:india" + ], + "ecoscore_tags": [ + "unknown" + ], + "product_name": "High Protein Milk", + "product_name_en": "High Protein Milk" + }, + { + "brands": "Amul", + "code": "8901262180429", + "countries_tags": [ + "en:india" + ], + "product_name": "High Protein Paneer", + "product_name_en": "High Protein Paneer", + "quantity": "400g" + }, + { + "brands": "Amul", + "code": "8901262260664", + "countries_tags": [ + "en:india" + ], + "product_name": "Slim n trim milk", + "product_name_en": "Slim n trim milk" + }, + { + "brands": "Amul", + "code": "4901960000016", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Pasteurised Butter", + "product_name_en": "Amul Pasteurised Butter", + "quantity": "100 g" + }, + { + "brands": "Amul", + "code": "8901262071581", + "countries_tags": [ + "en:india" + ], + "product_name": "Chocozoo", + "product_name_en": "Chocozoo", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262110259", + "countries_tags": [ + "en:india" + ], + "product_name": "Whey protein", + "product_name_en": "Whey protein" + }, + { + "brands": "Amul", + "code": "8901262176767", + "countries_tags": [ + "en:india" + ], + "product_name": "Bombay Kulfi Dry Fruits", + "product_name_en": "Bombay Kulfi Dry Fruits", + "quantity": "60 ml" + }, + { + "brands": "Amul", + "code": "8901262301695", + "countries_tags": [ + "en:india" + ], + "product_name": "Ice Malai", + "product_name_en": "Ice Malai", + "quantity": "67g" + }, + { + "brands": "Amul", + "code": "8901262300520", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Frostik Gold", + "product_name_en": "Amul Frostik Gold", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262030908", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Cow Ghee", + "product_name_en": "Amul Cow Ghee", + "quantity": "1L" + }, + { + "brands": "Amul", + "code": "4901068070676", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Dark Chocolate", + "product_name_en": "Amul Dark Chocolate", + "quantity": "150g" + }, + { + "brands": "Amul", + "code": "8901262150811", + "countries_tags": [ + "en:india" + ], + "product_name": "Calci+ milk", + "product_name_en": "Calci+ milk", + "quantity": "1L" + }, + { + "brands": "Amul", + "code": "8901262201902", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Stirred Fruit Yoghurt Pineapple", + "product_name_en": "Amul Stirred Fruit Yoghurt Pineapple", + "quantity": "100g" + }, + { + "brands": "Amul", + "code": "8901262030892", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul buffalo ghee", + "product_name_en": "Amul buffalo ghee", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262153645", + "countries_tags": [ + "en:india" + ], + "product_name": "Half and Half Cream", + "product_name_en": "Half and Half Cream", + "quantity": "250ml" + }, + { + "brands": "Amul", + "code": "8901262070928", + "countries_tags": [ + "en:india" + ], + "product_name": "Choco Cracker", + "product_name_en": "Choco Cracker", + "quantity": "" + }, + { + "brands": "Amul", + "code": "4901488010320", + "countries_tags": [ + "en:india" + ], + "product_name": "Butter", + "product_name_en": "Butter", + "quantity": "200 g" + }, + { + "brands": "Amul", + "code": "8901262030618", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul cow ghee", + "product_name_en": "Amul cow ghee", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262152938", + "countries_tags": [ + "en:india" + ], + "product_name": "Chocolate Spread Hazelnut", + "product_name_en": "Chocolate Spread Hazelnut", + "quantity": "300 g" + }, + { + "brands": "Amul", + "code": "8901262072052", + "countries_tags": [ + "en:india" + ], + "product_name": "Twilight Tryst Milk Chocolate", + "product_name_en": "Twilight Tryst Milk Chocolate", + "quantity": "125 g" + }, + { + "brands": "Amul", + "code": "8901262071017", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Belgian chocolate", + "product_name_en": "Amul Belgian chocolate", + "quantity": "125g" + }, + { + "brands": "Amul", + "code": "8901262152945", + "countries_tags": [ + "en:india" + ], + "product_name": "Protein Lassi - Mango flavour", + "product_name_en": "Protein Lassi - Mango flavour", + "quantity": "250 ml" + }, + { + "brands": "Amul", + "code": "0941402151368", + "countries_tags": [ + "en:india" + ], + "product_name": "Moti UHT Treated Homogenized Toned Milk", + "product_name_en": "Moti UHT Treated Homogenized Toned Milk", + "quantity": "450ml" + }, + { + "brands": "Amul", + "code": "8901262174404", + "countries_tags": [ + "en:india" + ], + "product_name": "Sundae Gudbud", + "product_name_en": "Sundae Gudbud", + "quantity": "125 ml / 75 g" + }, + { + "brands": "Amul", + "code": "8901262150095", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Gold Milk 200ml", + "product_name_en": "Amul Gold Milk 200ml", + "quantity": "" + }, + { + "brands": "Amul", + "code": "3948262211062", + "countries_tags": [ + "en:india" + ], + "product_name": "Masti Dahi", + "product_name_en": "Masti Dahi", + "quantity": "85g" + }, + { + "brands": "Amul", + "code": "8901262040051", + "countries_tags": [ + "en:india" + ], + "product_name": "Shrikhand Elaichi", + "product_name_en": "Shrikhand Elaichi", + "quantity": "" + }, + { + "brands": "Amul", "code": "8901262110068", - "brands": [ - "Amul" - ], - "quantity": "500 g", "countries_tags": [ "en:india" ], "product_name": "Sagar Skimmed Milk Powder", - "product_name_en": "Sagar Skimmed Milk Powder" + "product_name_en": "Sagar Skimmed Milk Powder", + "quantity": "500 g" }, { - "code": "8901262020039", - "brands": [ - "Amul" - ], - "quantity": "200 g", + "brands": "Amul", + "code": "8901262178662", "countries_tags": [ "en:india" ], - "product_name": "Cheese Chiplet", - "product_name_en": "Cheese Chiplet" + "ecoscore_tags": [ + "unknown" + ], + "product_name": "Tricone Dark Chocolate", + "product_name_en": "Tricone Dark Chocolate" }, { - "code": "8901262151658", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262153355", "countries_tags": [ "en:india" ], - "product_name": "Lassi Mango" + "product_name": "Amul Kool", + "product_name_en": "Amul Kool", + "quantity": "180ml" }, { - "code": "8901262152235", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262031059", "countries_tags": [ "en:india" ], - "product_name": "Amul Kool Rose" + "product_name": "Amul ghee", + "product_name_en": "Amul ghee", + "quantity": "500 gm" }, { - "code": "8901262070546", - "brands": [ - "Amul" - ], - "quantity": "250g", + "brands": "amul", + "code": "8902268011304", "countries_tags": [ "en:india" ], - "product_name": "Chocominis", - "product_name_en": "Chocominis" + "product_name": "amul mixed fruit cake veg", + "product_name_en": "amul mixed fruit cake veg" }, { - "code": "8901262201780", - "brands": [ - "Amul" - ], - "quantity": "370 ml", + "brands": "Amul", + "code": "8901262152556", "countries_tags": [ "en:india" ], - "product_name": "Masala butter milk", - "product_name_en": "Masala butter milk" + "product_name": "Amul Saffron Doodh", + "product_name_en": "Amul Saffron Doodh", + "quantity": "" }, { - "code": "8901262152273", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262020169", "countries_tags": [ "en:india" ], - "product_name": "Amul tru" + "product_name": "Gouda Cheese", + "product_name_en": "Gouda Cheese" }, { - "code": "8901262020305", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262150125", "countries_tags": [ "en:india" ], - "product_name": "Amul Cheese", - "product_name_en": "Amul Cheese" + "product_name": "Amul whipping cream", + "product_name_en": "Amul whipping cream", + "quantity": "" }, { - "code": "8901262261197", - "brands": [ - "Amul" - ], - "quantity": "190mL", + "brands": "Amul", + "code": "8901262120081", "countries_tags": [ "en:india" ], - "product_name": "Slim 'n' Trim", - "product_name_en": "Slim 'n' Trim" + "product_name": "Sweetened condensed Milk Gold", + "product_name_en": "Sweetened condensed Milk Gold" }, { - "code": "8901262176446", - "brands": [ - "Amul" - ], - "quantity": "1,54.6g(60ml)", + "brands": "Amul", + "code": "8901262301466", "countries_tags": [ "en:india" ], - "product_name": "Pista malai kulfi", - "product_name_en": "Pista malai kulfi" + "product_name": "Chocolate Brownie Sandwich Gold Icecream", + "product_name_en": "Chocolate Brownie Sandwich Gold Icecream" }, { - "code": "8901262178259", - "brands": [ - "Amul" - ], - "quantity": "80ml, 53g", + "brands": "Amul", + "code": "8901262090445", "countries_tags": [ "en:india" ], - "product_name": "Ice Cream Sandwich Vanilla", - "product_name_en": "Ice Cream Sandwich Vanilla" + "product_name": "Amul T special whiten 1kg pp", + "product_name_en": "Amul T special whiten 1kg pp", + "quantity": "1kg" }, { - "code": "8901262220552", - "brands": [ - "Amul" - ], - "quantity": "50g", + "brands": "Amul", + "code": "8901262176569", "countries_tags": [ "en:india" ], - "product_name": "Chocolate Cookies", - "product_name_en": "Chocolate Cookies" + "product_name": "Ice Lickz Orange Flavour", + "product_name_en": "Ice Lickz Orange Flavour" }, { - "code": "8901262260107", - "brands": [ - "Amul" - ], + "brands": "amul", + "code": "0890126226012", "countries_tags": [ "en:india" ], - "product_name": "Amul Gold Milky Milk", - "product_name_en": "Amul Gold Milky Milk" + "product_name": "milk", + "product_name_en": "milk", + "quantity": "500ml" }, { - "code": "8901262010016", - "brands": [ - "Amul" - ], - "quantity": "100.0 g", - "countries_tags": [ - "en:bahrain", - "en:hong-kong", - "en:india", - "en:kuwait", - "en:malaysia", - "en:new-zealand", - "en:oman", - "en:qatar", - "en:singapore", - "en:sri-lanka", - "en:united-arab-emirates", - "en:united-states" - ], - "product_name": "Amul Pasteurized Butter", - "product_name_en": "Amul Pasteurized Butter" - }, - { - "code": "8901262030564", - "brands": [ - "Amul" - ], + "brands": "Amul", + "code": "8901262020534", "countries_tags": [ "en:india" ], - "product_name": "Cow Ghee", - "product_name_en": "Cow Ghee" + "product_name": "Cheddar Cheese", + "product_name_en": "Cheddar Cheese" }, { - "code": "8901262070454", - "brands": [ - "Amul" - ], - "quantity": "40g", - "countries_tags": [ - "en:india", - "en:united-states" - ], - "product_name": "Amul Dark Chocolate", - "product_name_en": "Amul Dark Chocolate" - }, - { - "code": "8901262130325", - "brands": [ - "Amul" - ], - "quantity": "5", + "brands": "Amul", + "code": "8901262153577", "countries_tags": [ "en:india" ], - "product_name": "Amul Pro Chocolate 500g pp", - "product_name_en": "Amul Pro Chocolate 500g pp" + "product_name": "Amul Taaza Milk", + "product_name_en": "Amul Taaza Milk" }, { - "code": "8901262070676", - "brands": [ - "Amul" - ], - "quantity": "150 g", + "brands": "Amul", + "code": "8901262152631", "countries_tags": [ "en:india" ], - "product_name": "Dark Chocolate", - "product_name_en": "Dark Chocolate" + "product_name": "Lassi Pineapple", + "product_name_en": "Lassi Pineapple", + "quantity": "" }, { - "code": "8901262071864", - "brands": [ - "Amul" - ], - "quantity": "125 g", + "brands": "Amul", + "code": "8901262020435", "countries_tags": [ "en:india" ], - "product_name": "99% Cacao Ultimate Dark Chocolate", - "product_name_en": "99% Cacao Ultimate Dark Chocolate" + "product_name": "Pizza Cheese" }, { - "code": "8901262178365", - "brands": [ - "Amul" - ], - "quantity": "120ml/77g", + "brands": "Amul", + "code": "8901262110242", "countries_tags": [ "en:india" ], - "product_name": "Black Currant Tri Cone", - "product_name_en": "Black Currant Tri Cone" + "product_name": "Whey Protein Lactose Free", + "product_name_en": "Whey Protein Lactose Free", + "quantity": "" }, { - "code": "8901262151696", - "brands": [ - "Amul" - ], - "quantity": "250 ml", + "brands": "Amul", + "code": "8901262060066", "countries_tags": [ "en:india" ], - "product_name": "Amul Lassi 180ml tpk", - "product_name_en": "Amul Lassi 180ml tpk" + "product_name": "Creami Cheese Spread", + "product_name_en": "Creami Cheese Spread" + }, + { + "brands": "Amul", + "code": "8906090641067", + "countries_tags": [ + "en:india" + ], + "product_name": "Protein Water", + "product_name_en": "Protein Water", + "quantity": "" + }, + { + "brands": "Amul", + "code": "3948262153201", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Kool Cafe Classic", + "product_name_en": "Amul Kool Cafe Classic" + }, + { + "brands": "Amul", + "code": "8901262072502", + "countries_tags": [ + "en:india" + ], + "product_name": "Truffles", + "product_name_en": "Truffles" + }, + { + "brands": "Amul", + "code": "8901262202473", + "countries_tags": [ + "en:india" + ], + "product_name": "Toned Milk Dahi", + "product_name_en": "Toned Milk Dahi" + }, + { + "brands": "Amul", + "code": "8901262200646", + "countries_tags": [ + "en:india" + ], + "product_name": "Probiotic Buttermilk", + "product_name_en": "Probiotic Buttermilk" + }, + { + "brands": "Amul", + "code": "8901262174619", + "countries_tags": [ + "en:india" + ], + "product_name": "American Nuts", + "product_name_en": "American Nuts", + "quantity": "125 ml" + }, + { + "brands": "Amul", + "code": "8901262173537", + "countries_tags": [ + "en:india" + ], + "product_name": "Probiotic Frozen Yogurt Wild Berry", + "product_name_en": "Probiotic Frozen Yogurt Wild Berry" + }, + { + "brands": "Amul", + "code": "8901262060110", + "countries_tags": [ + "en:india" + ], + "product_name": "Cheese Spread Yummy Plain", + "product_name_en": "Cheese Spread Yummy Plain" + }, + { + "brands": "amul", + "code": "8901262060127", + "countries_tags": [ + "en:india" + ], + "product_name": "cream cheese", + "product_name_en": "cream cheese" + }, + { + "brands": "Amul", + "code": "8901262175531", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Real Milk Ice Cream Mini Cup", + "product_name_en": "Amul Real Milk Ice Cream Mini Cup" + }, + { + "brands": "Amul", + "code": "0360869060011", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Cheese Spread Yummy Plain", + "product_name_en": "Amul Cheese Spread Yummy Plain", + "quantity": "200gm" + }, + { + "brands": "Amul", + "code": "6945016062403", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul chocominis", + "product_name_en": "Amul chocominis", + "quantity": "250 g" + }, + { + "brands": "Amul", + "code": "8901262110303", + "countries_tags": [ + "en:india" + ], + "product_name": "Whey Shake", + "product_name_en": "Whey Shake" + }, + { + "brands": "Amul", + "code": "8901262176132", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Duetz Raspberry", + "product_name_en": "Amul Duetz Raspberry" + }, + { + "brands": "Amul", + "code": "8353962003964", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul creme rich", + "product_name_en": "Amul creme rich", + "quantity": "125ml/87g" + }, + { + "brands": "Amul", + "code": "8901262071208", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Probiotic Chocolate, 150g", + "product_name_en": "Amul Probiotic Chocolate, 150g", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262175814", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul VANILLA ROYALE", + "product_name_en": "Amul VANILLA ROYALE", + "quantity": "125 mL / 67 g" + }, + { + "brands": "Amul", + "code": "8901262301107", + "countries_tags": [ + "en:india" + ], + "product_name": "Sky Scooper Multi-layered ice cream", + "product_name_en": "Sky Scooper Multi-layered ice cream", + "quantity": "77g" + }, + { + "brands": "Amul", + "code": "9168474762808", + "countries_tags": [ + "en:india" + ], + "product_name": "Ultimate Dark Chocolate", + "product_name_en": "Ultimate Dark Chocolate" + }, + { + "brands": "Amul", + "code": "8901262301411", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Berry Dazzle Ice Cream", + "product_name_en": "Amul Berry Dazzle Ice Cream", + "quantity": "" + }, + { + "brands": "Amul", + "code": "8901262223850", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Roti" + }, + { + "brands": "Amul", + "code": "0035988971028", + "countries_tags": [ + "en:india" + ], + "product_name": "Amul Cool KoKo", + "product_name_en": "Amul Cool KoKo", + "quantity": "200ml" + }, + { + "brands": "Amul", + "code": "8901262220347", + "countries_tags": [ + "en:india" + ], + "product_name": "Butter Cookies", + "product_name_en": "Butter Cookies", + "quantity": "" + }, + { + "brands": "Amul", + "code": "3948262153423", + "countries_tags": [ + "en:india" + ], + "product_name": "High Protein Milk", + "product_name_en": "High Protein Milk", + "quantity": "" } ] } \ No newline at end of file diff --git a/data/cache/off_brand_corpus/naga.json b/data/cache/off_brand_corpus/naga.json new file mode 100644 index 0000000..cc39a52 --- /dev/null +++ b/data/cache/off_brand_corpus/naga.json @@ -0,0 +1,31 @@ +{ + "schema": 2, + "endpoint": "https://world.openfoodfacts.org/api/v2/search", + "brand": "Naga", + "brand_tag": "naga", + "country": "india", + "fetched_at": 1789104574.7944398, + "fetched_at_human": "2026-09-11 10:59:34", + "hits": [ + { + "brands": "Naga", + "code": "8906011830068", + "countries_tags": [ + "en:india" + ], + "product_name": "Sooji", + "product_name_en": "Sooji", + "quantity": "500 g" + }, + { + "brands": "Naga", + "code": "8906011831713", + "countries_tags": [ + "en:india" + ], + "product_name": "Maida", + "product_name_en": "Maida", + "quantity": "500 g" + } + ] +} \ No newline at end of file diff --git a/data/cache/off_brand_corpus/testbrand.json b/data/cache/off_brand_corpus/testbrand.json new file mode 100644 index 0000000..72569a8 --- /dev/null +++ b/data/cache/off_brand_corpus/testbrand.json @@ -0,0 +1,10 @@ +{ + "schema": 2, + "endpoint": "https://world.openfoodfacts.org/api/v2/search", + "brand": "TestBrand", + "brand_tag": "testbrand", + "country": "india", + "fetched_at": 1789110182.5709507, + "fetched_at_human": "2026-09-11 12:33:02", + "hits": [] +} \ No newline at end of file diff --git a/scripts/backfill_barcodes_from_off.py b/scripts/backfill_barcodes_from_off.py index 89c6b6a..6be5bc8 100644 --- a/scripts/backfill_barcodes_from_off.py +++ b/scripts/backfill_barcodes_from_off.py @@ -98,7 +98,7 @@ from app.services.vector_store import _connect logging.basicConfig(level=logging.INFO, format="%(message)s") logger = logging.getLogger("backfill_barcodes_off") -BARCODE_SOURCE = "openfoodfacts_bulk (search.openfoodfacts.org)" +BARCODE_SOURCE = "openfoodfacts_bulk (world.openfoodfacts.org/api/v2)" # The nine keys BarcodeResult.as_product_fields() emits. Named here so the JSON # writer can prove it touches nothing else. diff --git a/scripts/backfill_brand_stores.py b/scripts/backfill_brand_stores.py index 0edc475..b799764 100644 --- a/scripts/backfill_brand_stores.py +++ b/scripts/backfill_brand_stores.py @@ -30,7 +30,10 @@ USAGE python scripts/backfill_brand_stores.py --all # dry run python scripts/backfill_brand_stores.py --all --apply python scripts/backfill_brand_stores.py --brand Gopuram --apply --refresh - python scripts/backfill_brand_stores.py --brand Naga --domain nagafoods.in --apply + python scripts/backfill_brand_stores.py --brand Aachi --domain aachifoods.com --apply + +(Naga has no confirmed storefront to try: nagalimited.com is a parked domain +and nagafoods.in / nagafoods.com do not resolve, checked 2026-09-11.) """ from __future__ import annotations diff --git a/scripts/regate_brand_images.py b/scripts/regate_brand_images.py new file mode 100644 index 0000000..e40bd60 --- /dev/null +++ b/scripts/regate_brand_images.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +""" +Re-run the image gate over a brand's STORED images, and drop what fails it. + +WHY THIS EXISTS +--------------- +`scripts/repair_brand_images.py` fixes rows whose image URLs are DEAD. This +one fixes rows whose image URLs are ALIVE AND WRONG, which the repair script +refuses to touch by design ("never rewrites a row that already has one working +URL"). + +The Dabur run of 2026-09-11 left exactly that: "Dabur Honey 1kg / 250g / 50g" +with an empty `image_url` and four foreign honeys in `image_urls` - which the +product card shows as soon as the primary is blank - and "Dabur Honey 500g" +with a Dabur Odomos mosquito repellent as its primary. The ingestion path no +longer stores rejected candidates (`store_catalog_pipeline.stage_6_images`), +but rows written before that keep them, and a re-ingest does not revisit a +row that already has images. Hence this script. + +WHAT IT DOES, PER ROW +--------------------- +1. Runs `image_corroboration.choose_primary` over every URL the row holds + (primary first). `image_url` becomes the eligible primary, `image_urls` + the eligible list; rejected URLs are dropped. +2. If nothing is eligible, looks for another pack size of the same product + (same `search_key`) whose primary passed, and adopts it - through the gate + for THIS title, exactly as stage 6 does. +3. If still nothing, `image_url` becomes NULL and `image_urls` empty. The card + renders the brand monogram, which is honest; a foreign honey is not. + +Own Products is skipped: its images are curated (`produce_reference`) and the +gate was never meant for a bucket without a brand. + +USAGE +----- + python -m scripts.regate_brand_images --brands Dabur # dry run + python -m scripts.regate_brand_images --brands Dabur --apply + python -m scripts.regate_brand_images --all # dry run, every brand + +`--apply` writes a full backup of each targeted table first, in the same +format `repair_brand_images.py --restore` reads, so the change is reversible: + + python -m scripts.repair_brand_images --restore data/brand_image_repair_backup_X.json --apply + +The Open*Facts barcode cross-check is a network call per distinct barcode +(cached 30 days in data/cache/image_corroboration.db). Everything else is +offline. +""" +from __future__ import annotations + +import argparse +import logging +import sys +from pathlib import Path +from typing import Any, Dict, List, Optional + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from app.infrastructure.settings import DB_HOST, DB_NAME # noqa: E402 +from app.services import image_corroboration as ic # noqa: E402 +from app.services.generic_products import OWN_PRODUCTS_BRAND # noqa: E402 +from app.services.vector_store import ( # noqa: E402 + _connect, + _sanitize_name, + invalidate_brand_overview_cache, +) +from scripts.repair_brand_images import ( # noqa: E402 + _backup, + _brand_tables, + _read_rows, +) + +logging.basicConfig(level=logging.INFO, format="%(message)s") +logger = logging.getLogger("regate_brand_images") + + +def _row_urls(row: Dict[str, Any]) -> List[str]: + """Primary first, then the rest, deduplicated, http only.""" + out: List[str] = [] + for url in [row.get("image_url")] + list(row.get("image_urls") or []): + if url and str(url).startswith("http") and url not in out: + out.append(str(url)) + return out + + +def regate_rows(rows: List[Dict[str, Any]], brand: str) -> List[Dict[str, Any]]: + """The new (image_url, image_urls) for every row, plus why. + + Pure apart from the Open*Facts barcode cross-check inside choose_primary, + so it can be unit-tested with that patched. + """ + verdicts: List[Dict[str, Any]] = [] + for row in rows: + urls = _row_urls(row) + name = row.get("product_name") or "" + if not urls: + verdicts.append({**row, "new_url": None, "new_urls": [], "why": "no images stored"}) + continue + choice = ic.choose_primary(urls, name, brand) + verdicts.append({ + **row, + "new_url": choice.primary, + "new_urls": list(choice.eligible), + "rejected": list(choice.rejected), + "why": choice.reason, + }) + + # Sibling adoption for rows left with nothing, from rows that kept a primary. + eligible_by_key: Dict[tuple, str] = {} + for v in verdicts: + if v["new_url"]: + eligible_by_key.setdefault(ic.search_key(v.get("product_name") or "", brand), v["new_url"]) + for v in verdicts: + if v["new_url"]: + continue + candidate = eligible_by_key.get(ic.search_key(v.get("product_name") or "", brand)) + if not candidate: + continue + adopted = ic.choose_primary([candidate], v.get("product_name") or "", brand) + if adopted.primary: + v["new_url"] = adopted.primary + v["new_urls"] = [adopted.primary] + v["why"] = "adopted from another pack size of this product" + return verdicts + + +def _changed(v: Dict[str, Any]) -> bool: + return (v.get("image_url") or None) != v["new_url"] or list(v.get("image_urls") or []) != v["new_urls"] + + +def regate(cur, conn, brands: Optional[List[str]], apply: bool) -> Dict[str, int]: + totals = {"rows": 0, "changed": 0, "blanked": 0, "adopted": 0} + snapshot: Dict[str, List[Dict[str, Any]]] = {} + plans: Dict[str, List[Dict[str, Any]]] = {} + + for suffix, table in _brand_tables(cur, brands): + if suffix == _sanitize_name(OWN_PRODUCTS_BRAND): + logger.info("%s: skipped (curated images)", table) + continue + rows = _read_rows(cur, table) + if not rows: + continue + snapshot[table] = rows + verdicts = [v for v in regate_rows(rows, suffix) if _changed(v)] + plans[table] = verdicts + totals["rows"] += len(rows) + logger.info("%s: %d row(s), %d to change", table, len(rows), len(verdicts)) + for v in verdicts: + arrow = "->" + logger.info(" %-42s %s %s", (v.get("product_name") or "")[:42], arrow, + (v["new_url"] or "(none)")[:80]) + logger.info(" was %s (+%d more) %s", (v.get("image_url") or "(none)")[:70], + max(0, len(v.get("image_urls") or []) - 1), v["why"]) + for dropped in v.get("rejected") or []: + logger.info(" drop %s", dropped[:90]) + totals["changed"] += 1 + if v["new_url"] is None: + totals["blanked"] += 1 + if v["why"].startswith("adopted"): + totals["adopted"] += 1 + + if apply and any(plans.values()): + logger.info("Backup written: %s", _backup(snapshot)) + for table, verdicts in plans.items(): + for v in verdicts: + cur.execute( + f"UPDATE {table} SET image_url = %s, image_urls = %s, " + f"updated_at = CURRENT_TIMESTAMP WHERE image_id = %s", + (v["new_url"], v["new_urls"], v["image_id"]), + ) + conn.commit() + try: + invalidate_brand_overview_cache() + except Exception: # noqa: BLE001 - the cache is a convenience + pass + return totals + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--brands", help="comma-separated brands to re-gate") + mode.add_argument("--all", action="store_true", help="every brand table") + parser.add_argument("--apply", action="store_true", help="write the changes (default: dry run)") + args = parser.parse_args() + + logger.info("Target database: %s / %s", DB_HOST, DB_NAME) + logger.info("Mode: %s", "APPLY - this writes" if args.apply else "DRY RUN - nothing is written") + + conn = _connect() + if conn is None: + logger.error("No database connection.") + return 1 + try: + with conn.cursor() as cur: + brands = None if args.all else [b for b in args.brands.split(",") if b.strip()] + totals = regate(cur, conn, brands, args.apply) + finally: + conn.close() + + logger.info("") + logger.info("%s: %d row(s) read, %d %s, %d blanked, %d adopted from a sibling size", + "Applied" if args.apply else "Dry run", totals["rows"], totals["changed"], + "changed" if args.apply else "would change", totals["blanked"], totals["adopted"]) + if args.apply: + logger.info("The API caches the brand overview; hit GET /api/brands/overview?refresh=true " + "or wait out BRAND_OVERVIEW_TTL_SECONDS.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/conftest.py b/tests/conftest.py index 6288b5d..459003d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -55,6 +55,11 @@ os.environ.setdefault("USE_PGVECTOR", "true") os.environ.setdefault("DB_PASSWORD", "test-password-not-real") os.environ.setdefault("USE_S3", "false") os.environ.setdefault("USE_GOOGLE_CSE", "false") +# Unconditional: the LLM description stage is on by default for every batch +# and a developer machine often has `ollama serve` running, so without this a +# pipeline test would make real, slow, non-deterministic model calls. Tests +# that exercise the probe itself monkeypatch `ollama_service.USE_OLLAMA`. +os.environ["USE_OLLAMA"] = "false" # Unconditional, NOT setdefault. The suite pins brand-name behaviour all over # the place ("any Nestle chocolates?", the suggest ranking fixtures), and a diff --git a/tests/test_batch_catalog_ingest.py b/tests/test_batch_catalog_ingest.py index d156171..f757729 100644 --- a/tests/test_batch_catalog_ingest.py +++ b/tests/test_batch_catalog_ingest.py @@ -5,8 +5,8 @@ boundary *on the pipeline module object*, build real spreadsheets in memory, and point the batch directory at tmp_path so nothing is written into the repo. Nothing here loads sentence-transformers or torch, and nothing reaches the -network: every batch runs with use_llm=False and fetch_images=False, which are -also the defaults the endpoint ships. +network: every batch runs with fetch_images=False (the endpoint's default) and +the LLM stage, on by default, finds Ollama disabled and falls back at once. """ from __future__ import annotations @@ -486,7 +486,7 @@ def test_ingest_returns_202_and_a_batch_id(client, admin_headers, store, batch_r assert response.status_code == 202 body = response.json() assert body["files_total"] == 2 - assert body["use_llm"] is False, "the LLM stage must be opt-in for a batch" + assert body["use_llm"] is True, "LLM descriptions are on by default; a batch opts OUT" assert body["fetch_images"] is False, "image search must be opt-in for a batch" assert len(body["files"]) == 2 assert body["files"][0]["total_stages"] == pipeline.TOTAL_STAGES diff --git a/tests/test_brand_discovery.py b/tests/test_brand_discovery.py index f524b57..36fb61d 100644 --- a/tests/test_brand_discovery.py +++ b/tests/test_brand_discovery.py @@ -21,6 +21,9 @@ from app.api.routers.user_products import map_spreadsheet_columns, row_to_reques from app.core import store_catalog_pipeline as pipeline from app.services import brand_discovery as bd +# Captured before the autouse `_no_network` fixture replaces it on the module. +_REAL_FROM_OPEN_FACTS = bd._from_open_facts + # --------------------------------------------------------------------------- # Fixtures @@ -220,12 +223,11 @@ def test_a_case_pack_count_is_not_part_of_the_product_name(monkeypatch): # --------------------------------------------------------------------------- # Regression: one GTIN, one pack # --------------------------------------------------------------------------- -def test_a_barcode_is_dropped_when_no_real_pack_size_is_known(monkeypatch): - """A GTIN identifies one pack, and stage 4 invents three when it has none. - - Every column is copied into each exploded variant, so a surviving barcode - would be stamped onto two packs that do not exist - wrong data that looks - authoritative. +def test_a_barcode_survives_when_no_pack_size_is_known(monkeypatch): + """A GTIN identifies one pack, and stage 4 now stores exactly one row for + a product with no size (it used to invent three and stamp the same GTIN + on all of them, which is why the barcode was dropped before). One GTIN on + one unsized row is what a GTIN means; the row is only flagged for review. """ monkeypatch.setattr(bd, "_from_open_facts", lambda brand, refresh=False: [ _off("50 50 Gol Maal", code="8901063017702"), @@ -234,8 +236,8 @@ def test_a_barcode_is_dropped_when_no_real_pack_size_is_known(monkeypatch): product = bd.discover_brand_products("Britannia", use_llm=False).products[0] assert product.size_variants == [] - assert product.barcode is None - assert any("barcode dropped" in note for note in product.notes) + assert product.barcode == "8901063017702" + assert any("pack size unknown" in note for note in product.notes) def test_a_barcode_survives_when_the_pack_size_is_real(monkeypatch): @@ -549,3 +551,113 @@ def test_every_column_the_pipeline_can_fill_is_filled(store, monkeypatch): assert row.get(column), f"{column} was left empty" assert row["barcode"] == "8901063012516" assert row["fssai_license"] == "10012022000103" + + + +# --------------------------------------------------------------------------- +# What the warnings claim - "unreachable" vs "empty" vs "no storefront" +# --------------------------------------------------------------------------- +# Naga (2026-09-11): Open Food Facts holds two real rows for the brand, yet the +# tab said "Neither Open Food Facts nor a brand storefront has anything for +# this brand". The lookup had failed / been mis-indexed, no storefront was +# registered, and one warning text covered all of it. Each claim now has to +# be true on its own. + +def _store_rows(monkeypatch, rows): + monkeypatch.setattr(bd, "_from_brand_store", lambda brand: list(rows)) + + +def test_an_unreachable_off_is_reported_as_unreachable_not_empty(monkeypatch): + def down(brand, refresh=False): + raise bd.OpenFactsUnavailable("HTTP 503 from Open Food Facts") + + monkeypatch.setattr(bd, "_from_open_facts", down) + _store_rows(monkeypatch, []) + monkeypatch.setattr(bd, "_from_llm", lambda brand, deadline, budget: [ + {"title": "Naga Sooji", "category": None, "description": None, + "sizes": ["500g"], "providers": [], "source": "llm"}, + ]) + + result = bd.discover_brand_products("Naga", require_evidence=False) + + reached = [w for w in result.warnings if "could not be reached" in w] + assert len(reached) == 1 and "503" in reached[0] + assert not any("has no products tagged" in w for w in result.warnings) + assert [p.title for p in result.products] == ["Naga Sooji"], "the LLM rows must still come through when OFF is down" + + +def test_a_genuinely_empty_off_says_so_without_blaming_the_network(monkeypatch): + _store_rows(monkeypatch, []) + result = bd.discover_brand_products("Udhaiyam") + assert any("has no products tagged" in w for w in result.warnings) + assert not any("could not be reached" in w for w in result.warnings) + + +def test_unregistered_storefront_is_not_described_as_empty(monkeypatch): + _store_rows(monkeypatch, []) + monkeypatch.setattr(bd, "get_brand_store_domain", lambda brand: None) + + result = bd.discover_brand_products("Naga") + + assert any("No verified storefront is registered" in w for w in result.warnings) + assert not any("Neither Open Food Facts" in w for w in result.warnings) + assert not any("has not been fetched" in w for w in result.warnings) + + +def test_registered_but_unfetched_storefront_names_the_backfill(monkeypatch): + _store_rows(monkeypatch, []) + monkeypatch.setattr(bd, "get_brand_store_domain", lambda brand: "gopuramproducts.com") + + result = bd.discover_brand_products("Gopuram") + + hit = [w for w in result.warnings if "has not been fetched" in w] + assert len(hit) == 1 + assert "gopuramproducts.com" in hit[0] and "backfill_brand_stores.py" in hit[0] + assert not any("No verified storefront" in w for w in result.warnings) + + +def test_the_llm_only_caveat_appears_exactly_once(monkeypatch): + _store_rows(monkeypatch, []) + result = bd.discover_brand_products("Naga") + assert sum("rests on the language model alone" in w for w in result.warnings) == 1 + + +def test_no_storefront_warning_when_the_shop_or_off_has_rows(monkeypatch): + _store_rows(monkeypatch, [{"title": "Naga Maida 1kg", "source": "store", "size": "1kg"}]) + result = bd.discover_brand_products("Naga") + assert not any("storefront" in w.lower() for w in result.warnings) + assert not any("rests on the language model alone" in w for w in result.warnings) + + _store_rows(monkeypatch, []) + monkeypatch.setattr(bd, "_from_open_facts", lambda brand, refresh=False: [ + _off("Sooji", code="8906011830068", quantity="500 g"), + ]) + result = bd.discover_brand_products("Naga") + assert not any("storefront" in w.lower() for w in result.warnings) + assert not any("rests on the language model alone" in w for w in result.warnings) + + +def test_from_open_facts_raises_when_the_fetch_failed_with_nothing(monkeypatch): + from app.services.enrichment.barcode.sources import off_bulk + + monkeypatch.setattr(off_bulk, "fetch_brand_corpus_result", + lambda brand, refresh=False: off_bulk.CorpusFetch(hits=[], error="dns")) + with pytest.raises(bd.OpenFactsUnavailable): + _REAL_FROM_OPEN_FACTS("Naga") + + +def test_from_open_facts_maps_v2_hits_to_candidates(monkeypatch): + from app.services.enrichment.barcode.sources import off_bulk + + monkeypatch.setattr(off_bulk, "fetch_brand_corpus_result", + lambda brand, refresh=False: off_bulk.CorpusFetch(hits=[ + {"code": "8906011830068", "product_name": "Sooji", + "product_name_en": "Sooji", "quantity": "500 g"}, + {"code": "8906011831713", "product_name": "Maida", "quantity": "500 g"}, + ])) + + rows = _REAL_FROM_OPEN_FACTS("Naga") + + assert [(r["title"], r["barcode"], r["source"]) for r in rows] == [ + ("Sooji", "8906011830068", "off"), ("Maida", "8906011831713", "off"), + ] diff --git a/tests/test_image_corroboration.py b/tests/test_image_corroboration.py index cfa68ca..466ce56 100644 --- a/tests/test_image_corroboration.py +++ b/tests/test_image_corroboration.py @@ -174,6 +174,94 @@ def test_a_lookup_failure_never_rejects_an_image(monkeypatch): assert ic.openfacts_product_matches_brand(url, "Anil") is True +class _Resp: + def __init__(self, ok=True, payload=None): + self.ok = ok + self.status_code = 200 if ok else 503 + self._payload = payload or {} + + def json(self): + return self._payload + + +@pytest.fixture +def verdict_cache(tmp_path, monkeypatch): + """A throwaway sqlite cache, so these tests neither read nor poison the + real one in data/cache.""" + monkeypatch.setattr(ic, "_DB_PATH", tmp_path / "verdicts.db") + monkeypatch.setattr(ic, "_initialized", False) # the schema is per file + monkeypatch.setattr(ic.time, "sleep", lambda s: None) # pacing/retry waits + return tmp_path / "verdicts.db" + + +KELLOGGS_HONEY = "https://images.openfoodfacts.org/images/products/505/931/902/3762/front_fr.32.400.jpg" + + +def test_a_throttled_lookup_fails_open_but_is_not_remembered(monkeypatch, verdict_cache): + """The Dabur Honey defect. A hundred lookups in two minutes got the tail + of them a 503; the fail-open True was cached for thirty days, and + Kellogg's "Miel Pops" became a Dabur product for a month.""" + throttled = _Resp(ok=False) + throttled.status_code = 503 + answers = iter([throttled, throttled, _Resp(payload={"status": 1, "product": { + "brands": "KELLOG'S", "product_name": "Miel Pops"}})]) + monkeypatch.setattr(ic.requests, "get", lambda *a, **k: next(answers)) + + assert ic.openfacts_product_matches_brand(KELLOGGS_HONEY, "Dabur") is True, "throttled on both attempts: fails open now" + assert ic.openfacts_product_matches_brand(KELLOGGS_HONEY, "Dabur") is False, "asks again next time" + + +def test_one_throttled_attempt_is_retried_into_a_real_answer(monkeypatch, verdict_cache): + throttled = _Resp(ok=False) + throttled.status_code = 429 + answers = iter([throttled, _Resp(payload={"status": 1, "product": { + "brands": "KELLOG'S", "product_name": "Miel Pops"}})]) + monkeypatch.setattr(ic.requests, "get", lambda *a, **k: next(answers)) + + assert ic.openfacts_product_matches_brand(KELLOGGS_HONEY, "Dabur") is False + + +def test_a_real_verdict_is_cached(monkeypatch, verdict_cache): + calls = [] + + def get(*a, **k): + calls.append(1) + return _Resp(payload={"status": 1, "product": {"brands": "Toblerone", + "product_name": "Milk Chocolate"}}) + + monkeypatch.setattr(ic.requests, "get", get) + url = "https://images.openfoodfacts.org/images/products/761/450/001/0013/front_en.362.400.jpg" + + assert ic.openfacts_product_matches_brand(url, "Dabur") is False + assert ic.openfacts_product_matches_brand(url, "Dabur") is False + assert len(calls) == 1 + + +def test_an_unknown_barcode_is_cached_as_open(monkeypatch, verdict_cache): + calls = [] + + def get(*a, **k): + calls.append(1) + return _Resp(payload={"status": 0, "status_verbose": "product not found"}) + + monkeypatch.setattr(ic.requests, "get", get) + url = "https://images.openfoodfacts.org/images/products/000/000/000/0001/front.jpg" + + assert ic.openfacts_product_matches_brand(url, "Dabur") is True + assert ic.openfacts_product_matches_brand(url, "Dabur") is True + assert len(calls) == 1 + + +def test_pre_fix_cache_entries_are_ignored(monkeypatch, verdict_cache): + """Entries written by the old code (no "v2:" prefix) may be fail-open + Trues; they must not be trusted.""" + ic._cache_set("world.openfoodfacts.org:5059319023762:dabur", True) + monkeypatch.setattr(ic.requests, "get", lambda *a, **k: _Resp(payload={ + "status": 1, "product": {"brands": "KELLOG'S", "product_name": "Miel Pops"}})) + + assert ic.openfacts_product_matches_brand(KELLOGGS_HONEY, "Dabur") is False + + def test_a_non_openfacts_url_is_not_cross_checked(): assert ic.openfacts_product_matches_brand(REAL_RAVA, "Anil") is True @@ -193,3 +281,20 @@ def test_pack_sizes_of_one_product_share_a_search_key(): def test_different_products_do_not_share_a_search_key(): assert (ic.search_key("Anil Wheat Vermicelli 180g", "Anil") != ic.search_key("Anil Samba Rava 500g", "Anil")) + + +# --------------------------------------------------------------------------- +# Plural titles, singular filenames +# --------------------------------------------------------------------------- +def test_a_plural_title_is_named_by_a_singular_filename(): + """The all-brands re-gate would have dropped the correct + `Aachi-Appalam-100-g-1.webp` from "Aachi Appalams 500g" and promoted an + opaque Amazon URL over it.""" + url = "https://thedesifood.com/media/Aachi-Appalam-100-g-1.webp" + assert ic.corroborate(url, "Aachi Appalams 500g", "Aachi").corroborated + assert ic.corroborate("https://x.in/img/aachi-pickle-jar.jpg", "Aachi Pickles 200g", "Aachi").corroborated + + +def test_the_singular_fallback_does_not_widen_short_tokens(): + # "gems" -> "gem" is too short a stem to trust; four letters is the floor. + assert not ic.corroborate("https://x.in/gem-ring.jpg", "Cadbury Gems 20g", "Cadbury").corroborated diff --git a/tests/test_image_precision.py b/tests/test_image_precision.py new file mode 100644 index 0000000..1495567 --- /dev/null +++ b/tests/test_image_precision.py @@ -0,0 +1,297 @@ +"""The Dabur Honey regression: one product, several pack sizes, one photo. + +Brand Discovery for Dabur (2026-09-11) stored, for the same product: + + Dabur Honey 225g Amazon photo of Dabur Honey correct + Dabur Honey 1kg image_url empty; image_urls = 4 honeys from the UK, + Dabur Honey 250g France, Switzerland and Spain (Open Food Facts photos + Dabur Honey 50g whose barcodes belong to other brands) + Dabur Honey 500g a Dabur Odomos mosquito repellent + +Three causes, each pinned below: + +1. Stage 6 searched once PER SIZE. The query was identical - the size is not + in it - but the providers are live and not deterministic, so four asks + came back four ways. One search per product, shared by every size. +2. `find_images_openfacts` threw away the `brands` field of the records it + fetched, so a title-only fallback ("Honey 1kg") handed over every honey on + the site. The brand is checked at the source now. +3. `choose_primary` kept rejected candidates in `image_urls` "for review" - + and the product card falls back to `image_urls[0]` when the primary is + withheld, so the review pile was what the shopper saw. Only eligible + candidates are stored. + +Everything runs offline: the search function and the Open*Facts barcode +cross-check are monkeypatched; storage and embeddings are stubbed. +""" +from __future__ import annotations + +import io + +import pytest + +from app.core import store_catalog_pipeline as pipeline +from app.services import image_corroboration as ic +from app.services import image_search + +openpyxl = pytest.importorskip("openpyxl") + +AMAZON = "https://m.media-amazon.com/images/I/71O4OnjaHVL.jpg" +ODOMOS = ("https://www.indianproductsstore.com/uploads/products/" + "dabur-odomos-naturals-mosquito-repellent-gel.jpg") +FOREIGN_HONEYS = [ + "https://images.openfoodfacts.org/images/products/505/931/902/3762/front_fr.32.400.jpg", + "https://images.openfoodfacts.org/images/products/308/854/000/4440/front_fr.129.400.jpg", +] +DABUR_OFF = "https://images.openfoodfacts.org/images/products/890/120/702/6553/front_en.4.400.jpg" +NAMED = "https://cdn.example.in/dabur-honey-squeezy-bottle.jpg" + + +def _sheet(headers, rows) -> bytes: + wb = openpyxl.Workbook() + ws = wb.active + ws.append(headers) + for row in rows: + ws.append(row) + buf = io.BytesIO() + wb.save(buf) + return buf.getvalue() + + +@pytest.fixture(autouse=True) +def _isolate_sku_counter(tmp_path, monkeypatch): + from app.services import sku_service + monkeypatch.setattr(sku_service, "_data_dir", tmp_path / "sku_sequences") + + +@pytest.fixture(autouse=True) +def _off_barcodes_offline(monkeypatch): + """Open*Facts says: the 890120... barcode is Dabur, the others are not.""" + monkeypatch.setattr( + ic, "openfacts_product_matches_brand", + lambda url, brand, timeout=10: "/890/120/" in url or "openfoodfacts" not in url, + ) + + +@pytest.fixture +def store(monkeypatch): + table: dict = {} + + def fake_upsert(brand, rows, cleanup=False): + for row in rows: + table[row["image_id"]] = dict(row) + return len(rows) + + monkeypatch.setattr(pipeline, "upsert_brand_products", fake_upsert) + monkeypatch.setattr(pipeline, "get_products_by_brand", + lambda b, **kw: [dict(r, brand=b) for r in table.values()]) + monkeypatch.setattr(pipeline, "embed_texts", lambda texts: [[0.0] * 384 for _ in texts]) + return table + + +@pytest.fixture +def search(monkeypatch): + """A scripted `find_all_image_urls`; records every call it receives.""" + calls = [] + + def install(results): + def fake(title, brand=None, country_hint=None, validate=True, max_results=24, produce=False): + calls.append((title, brand)) + return list(results) + monkeypatch.setattr(image_search, "find_all_image_urls", fake) + return calls + + return install + + +def _run(headers, rows): + return pipeline.run_pipeline("store.xlsx", _sheet(headers, rows), + use_llm=False, fetch_images=True) + + +# --------------------------------------------------------------------------- +# 1. One search per product +# --------------------------------------------------------------------------- +def test_every_pack_size_of_a_product_gets_the_same_image_from_one_search(store, search): + calls = search([AMAZON, DABUR_OFF]) + + _run(["Product Name", "Brand", "Pack Size"], [["Dabur Honey", "Dabur", "1kg, 250g, 50g, 225g"]]) + + assert len(store) == 4 + assert {r["image_url"] for r in store.values()} == {AMAZON} + assert len(calls) == 1, "four pack sizes, one search" + assert calls[0][0] == "Dabur Honey", "the size never enters the query" + + +def test_sizes_written_into_the_name_still_share_the_search(store, search): + calls = search([AMAZON]) + + _run(["Product Name", "Brand"], [["Dabur Honey 1kg", "Dabur"], ["Dabur Honey 250g", "Dabur"]]) + + assert len(calls) == 1 + assert calls[0][0] == "Dabur Honey" + assert {r["image_url"] for r in store.values()} == {AMAZON} + + +def test_different_products_of_one_brand_are_searched_separately(store, search): + calls = search([NAMED]) + _run(["Product Name", "Brand"], [["Dabur Honey", "Dabur"], ["Dabur Chyawanprash", "Dabur"]]) + assert sorted(c[0] for c in calls) == ["Dabur Chyawanprash", "Dabur Honey"] + + +# --------------------------------------------------------------------------- +# 2. Open*Facts records are filtered by brand at the source +# --------------------------------------------------------------------------- +def test_openfacts_photos_of_other_brands_honey_are_not_candidates(monkeypatch): + records = [ + {"brands": "Dabur", "brands_tags": ["dabur"], "image_front_url": DABUR_OFF}, + {"brands": "Rowse", "brands_tags": ["rowse"], "image_front_url": FOREIGN_HONEYS[0]}, + {"brands": "Lune de Miel", "image_front_url": FOREIGN_HONEYS[1]}, + {"brands": "", "image_front_url": "https://images.openfoodfacts.org/x/unlabelled.jpg"}, + ] + monkeypatch.setattr(image_search, "_query_openfacts", lambda query, n: records) + monkeypatch.setattr(image_search, "USE_OPEN_FACTS", True) + + urls = image_search.find_images_openfacts("Honey", "Dabur") + + assert DABUR_OFF in urls + assert not any(u in urls for u in FOREIGN_HONEYS) + assert "https://images.openfoodfacts.org/x/unlabelled.jpg" in urls, \ + "a record with no brand at all is not evidence of another brand" + + +def test_the_title_only_fallback_is_also_brand_filtered(monkeypatch): + seen = [] + + def query(q, n): + seen.append(q) + if q.startswith("dabur"): + return [] + return [{"brands": "Rowse", "image_front_url": FOREIGN_HONEYS[0]}] + + monkeypatch.setattr(image_search, "_query_openfacts", query) + monkeypatch.setattr(image_search, "USE_OPEN_FACTS", True) + + assert image_search.find_images_openfacts("Honey 1kg", "dabur") == [] + assert seen == ["dabur Honey 1kg", "Honey 1kg"] + + +def test_without_a_brand_nothing_is_filtered(monkeypatch): + monkeypatch.setattr(image_search, "_query_openfacts", + lambda q, n: [{"brands": "Rowse", "image_front_url": FOREIGN_HONEYS[0]}]) + monkeypatch.setattr(image_search, "USE_OPEN_FACTS", True) + assert image_search.find_images_openfacts("Honey", None) == FOREIGN_HONEYS[:1] + + +# --------------------------------------------------------------------------- +# 3. Rejected candidates are not stored where the UI will show them +# --------------------------------------------------------------------------- +def test_choose_primary_separates_eligible_from_rejected(): + choice = ic.choose_primary([ODOMOS, FOREIGN_HONEYS[0], AMAZON, NAMED], "Dabur Honey 500g", "Dabur") + + assert choice.primary == NAMED # tier 1: names "honey" + assert choice.eligible == [NAMED, AMAZON] # tier 1 then tier 2 + assert choice.rejected == [ODOMOS, FOREIGN_HONEYS[0]] + assert choice.ordered == choice.eligible + choice.rejected + + +def test_a_same_brand_wrong_product_photo_is_never_stored(store, search): + """The 500g row: a Dabur Odomos repellent as the primary image of honey.""" + search([ODOMOS] + FOREIGN_HONEYS) + + result = _run(["Product Name", "Brand"], [["Dabur Honey 500g", "Dabur"]]) + + row = next(iter(store.values())) + assert row["image_url"] is None + assert row["image_urls"] == [], "nothing the gate rejected may reach image_urls" + assert any("rejected" in w for w in result.warnings) + assert any("no primary image" in w for w in result.warnings) + + +def test_foreign_honeys_ride_along_only_when_a_real_primary_exists(store, search): + search([AMAZON, DABUR_OFF] + FOREIGN_HONEYS) + _run(["Product Name", "Brand"], [["Dabur Honey 1kg", "Dabur"]]) + + row = next(iter(store.values())) + assert row["image_url"] == AMAZON + assert row["image_urls"] == [AMAZON, DABUR_OFF] + + +# --------------------------------------------------------------------------- +# 4. A sibling pack size already in the table lends its photo - through the gate +# --------------------------------------------------------------------------- +def test_a_new_pack_size_adopts_the_siblings_image_without_searching(store, search): + calls = search([AMAZON]) + _run(["Product Name", "Brand"], [["Dabur Honey 225g", "Dabur"]]) + assert len(calls) == 1 and store["dabur_dabur_honey_225g"]["image_url"] == AMAZON + + result = _run(["Product Name", "Brand"], [["Dabur Honey 1kg", "Dabur"]]) + + assert len(calls) == 1, "the sibling's photo made the search unnecessary" + assert store["dabur_dabur_honey_1kg"]["image_url"] == AMAZON + assert store["dabur_dabur_honey_1kg"]["image_urls"] == [AMAZON] + assert any("adopted from another pack size" in w for w in result.warnings) + + +def test_a_siblings_wrong_image_is_not_adopted(store, search): + """A stored primary that would not pass the gate for this title stays put.""" + store["dabur_dabur_honey_500g"] = { + "image_id": "dabur_dabur_honey_500g", "product_name": "Dabur Honey 500g", + "image_url": ODOMOS, "image_urls": [ODOMOS], + } + calls = search([NAMED]) + + _run(["Product Name", "Brand"], [["Dabur Honey 1kg", "Dabur"]]) + + assert len(calls) == 1, "an ineligible sibling image forces a fresh search" + assert store["dabur_dabur_honey_1kg"]["image_url"] == NAMED + + +def test_a_different_product_of_the_brand_is_not_a_sibling(store, search): + store["dabur_dabur_chyawanprash_500g"] = { + "image_id": "dabur_dabur_chyawanprash_500g", "product_name": "Dabur Chyawanprash 500g", + "image_url": NAMED, "image_urls": [NAMED], + } + calls = search([AMAZON]) + _run(["Product Name", "Brand"], [["Dabur Honey 1kg", "Dabur"]]) + assert len(calls) == 1 + assert store["dabur_dabur_honey_1kg"]["image_url"] == AMAZON + + +def test_stage_six_without_a_cache_still_works(): + """Direct callers (and the produce tests) pass no cache.""" + row = {"brand": "Dabur", "product_name": "Dabur Honey 1kg", "image_urls": [AMAZON]} + assert pipeline.stage_6_images(row) is row + + +# --------------------------------------------------------------------------- +# 5. The one-off repair for rows written before the fix +# --------------------------------------------------------------------------- +def test_regate_rows_reproduces_the_dabur_repair(): + from scripts.regate_brand_images import regate_rows + + rows = [ + {"image_id": "dabur_dabur_honey_225g", "product_name": "Dabur Honey 225g", + "image_url": AMAZON, "image_urls": [AMAZON, DABUR_OFF] + FOREIGN_HONEYS}, + {"image_id": "dabur_dabur_honey_1kg", "product_name": "Dabur Honey 1kg", + "image_url": None, "image_urls": list(FOREIGN_HONEYS)}, + {"image_id": "dabur_dabur_honey_500g", "product_name": "Dabur Honey 500g", + "image_url": ODOMOS, "image_urls": [ODOMOS]}, + {"image_id": "dabur_dabur_chyawanprash_500g", "product_name": "Dabur Chyawanprash 500g", + "image_url": None, "image_urls": [ODOMOS]}, + ] + + by_id = {v["image_id"]: v for v in regate_rows(rows, "dabur")} + + # 225g keeps its photo and loses the foreign honeys from its gallery. + assert by_id["dabur_dabur_honey_225g"]["new_url"] == AMAZON + assert by_id["dabur_dabur_honey_225g"]["new_urls"] == [AMAZON, DABUR_OFF] + # 1kg had nothing eligible and adopts the 225g photo. + assert by_id["dabur_dabur_honey_1kg"]["new_url"] == AMAZON + assert by_id["dabur_dabur_honey_1kg"]["why"].startswith("adopted") + # 500g drops the mosquito repellent and adopts the sibling photo too. + assert by_id["dabur_dabur_honey_500g"]["new_url"] == AMAZON + assert ODOMOS in by_id["dabur_dabur_honey_500g"]["rejected"] + # A different product with only a wrong image is blanked, not lent a honey. + assert by_id["dabur_dabur_chyawanprash_500g"]["new_url"] is None + assert by_id["dabur_dabur_chyawanprash_500g"]["new_urls"] == [] diff --git a/tests/test_off_bulk_fetch.py b/tests/test_off_bulk_fetch.py new file mode 100644 index 0000000..1a717b0 --- /dev/null +++ b/tests/test_off_bulk_fetch.py @@ -0,0 +1,308 @@ +"""The network half of `off_bulk`: what is asked of Open Food Facts, how the +answer is paged, and - the part that produced a user-visible lie - what gets +written to the corpus cache when the answer never arrives. + +Background, measured 2026-09-11. Brand Discovery told the user that Open Food +Facts had nothing for "Naga". The v2 API returns two real Naga Limited rows +(Sooji 500 g / 8906011830068, Maida 500 g / 8906011831713). The old query hit +the Search-a-licious endpoint with a free-text `brands:Naga`, whose index +still filed 8906011830068 brandless under Kuwait, and any 503 it met on the +way was cached to disk as `hits: []`. Both failure modes are pinned here, with +no network: `requests.get` is replaced for every test. +""" +from __future__ import annotations + +import json + +import pytest +import requests + +from app.services.enrichment.barcode.sources import off_bulk +from app.services.enrichment.barcode.sources.off_bulk import ( + CACHE_SCHEMA, + CorpusFetch, + OffUnavailable, + brand_tag_slug, + fetch_brand_corpus, + fetch_brand_corpus_result, +) + +NAGA_PRODUCTS = [ + {"code": "8906011830068", "product_name": "Sooji", "product_name_en": "Sooji", + "brands": "Naga", "quantity": "500 g", "countries_tags": ["en:india"]}, + {"code": "8906011831713", "product_name": "Maida", "product_name_en": "Maida", + "brands": "Naga", "quantity": "500 g", "countries_tags": ["en:india"]}, +] + + +class _Resp: + def __init__(self, status=200, payload=None, text="", content_type="application/json"): + self.status_code = status + self._payload = payload + self.text = text + self.headers = {"content-type": content_type} + + def json(self): + if self._payload is None: + raise ValueError("not json") + return self._payload + + +def _v2(products, count=None, page=1, page_size=None): + """A v2 search body. `page_count` is deliberately the size of THIS page, + which is what the real API sends and what a naive paginator misreads; + `page_size` is the size the server APPLIED, not the one requested.""" + return {"count": len(products) if count is None else count, "page": page, + "page_count": len(products), + "page_size": off_bulk.PAGE_SIZE if page_size is None else page_size, + "products": products, "skip": 0} + + +@pytest.fixture(autouse=True) +def _no_sleep(monkeypatch): + monkeypatch.setattr(off_bulk.time, "sleep", lambda s: None) + + +@pytest.fixture +def calls(monkeypatch): + """Replace requests.get with a scripted responder. Returns the list of + (url, params) actually sent, so tests can assert on the query.""" + sent = [] + + def install(responder): + def fake_get(url, params=None, headers=None, timeout=None): + sent.append((url, dict(params or {}))) + r = responder(len(sent), dict(params or {})) + if isinstance(r, Exception): + raise r + return r + monkeypatch.setattr(off_bulk.requests, "get", fake_get) + return sent + + return install + + +# --------------------------------------------------------------------------- +# The query +# --------------------------------------------------------------------------- + +@pytest.mark.parametrize("brand, slug", [ + ("Naga", "naga"), + ("Hindustan Unilever", "hindustan-unilever"), + ("P&G", "p-g"), + (" Double Horse ", "double-horse"), + ("Coca-Cola", "coca-cola"), +]) +def test_brand_tag_slug_matches_off_tag_form(brand, slug): + assert brand_tag_slug(brand) == slug + + +def test_query_is_an_exact_brand_tag_filter_on_the_v2_api(calls, tmp_path): + sent = calls(lambda n, p: _Resp(payload=_v2(NAGA_PRODUCTS))) + + result = fetch_brand_corpus_result("Naga", country="india", cache_dir=tmp_path) + + assert result.error is None and not result.from_cache + assert [h["code"] for h in result.hits] == ["8906011830068", "8906011831713"] + url, params = sent[0] + assert url == "https://world.openfoodfacts.org/api/v2/search" + assert params["brands_tags"] == "naga" + assert params["countries_tags"] == "en:india" + assert "q" not in params, "free-text brands: matching is what returned Mr Naga" + + +def test_no_country_filter_when_country_is_blank(calls, tmp_path): + sent = calls(lambda n, p: _Resp(payload=_v2([]))) + fetch_brand_corpus_result("Naga", country="", cache_dir=tmp_path) + assert "countries_tags" not in sent[0][1] + + +# --------------------------------------------------------------------------- +# Pagination +# --------------------------------------------------------------------------- + +def test_pagination_uses_count_not_page_count(calls, tmp_path): + """150 products at PAGE_SIZE 100 is two pages. The first page's + `page_count` is 100 - reading that as "pages" would fetch 100 pages.""" + page1 = [{"code": f"890{i:010d}", "product_name": f"P{i}"} for i in range(100)] + page2 = [{"code": f"891{i:010d}", "product_name": f"Q{i}"} for i in range(50)] + + def responder(n, params): + return _Resp(payload=_v2(page1 if params["page"] == 1 else page2, + count=150, page=params["page"])) + + sent = calls(responder) + result = fetch_brand_corpus_result("Amul", cache_dir=tmp_path) + + assert [p["page"] for _, p in sent] == [1, 2] + assert len(result.hits) == 150 + + +def test_pagination_follows_the_page_size_the_server_applied(calls, tmp_path, monkeypatch): + """The real Amul case: 216 products, 250 requested, 100 served per page. + Dividing by what we ASKED for stops after one page and loses 116 rows.""" + monkeypatch.setattr(off_bulk, "PAGE_SIZE", 250) + pages = { + 1: [{"code": f"890{i:010d}", "product_name": f"P{i}"} for i in range(100)], + 2: [{"code": f"891{i:010d}", "product_name": f"Q{i}"} for i in range(100)], + 3: [{"code": f"892{i:010d}", "product_name": f"R{i}"} for i in range(16)], + } + sent = calls(lambda n, p: _Resp(payload=_v2(pages[p["page"]], count=216, + page=p["page"], page_size=100))) + + result = fetch_brand_corpus_result("Amul", cache_dir=tmp_path) + + assert [p["page"] for _, p in sent] == [1, 2, 3] + assert len(result.hits) == 216 + + +def test_pagination_is_capped_at_max_pages(calls, tmp_path): + body = [{"code": "8900000000000", "product_name": "X"}] * off_bulk.PAGE_SIZE + sent = calls(lambda n, p: _Resp(payload=_v2(body, count=10 ** 6, page=p["page"]))) + fetch_brand_corpus_result("Nestle", cache_dir=tmp_path) + assert len(sent) == off_bulk.MAX_PAGES + + +def test_nameless_products_are_dropped_from_the_corpus(calls, tmp_path): + calls(lambda n, p: _Resp(payload=_v2(NAGA_PRODUCTS + [ + {"code": "8906011839999", "product_name": ""}, + {"code": "8906011839998"}, + ]))) + assert len(fetch_brand_corpus_result("Naga", cache_dir=tmp_path).hits) == 2 + + +# --------------------------------------------------------------------------- +# Failure is not emptiness, and is never cached +# --------------------------------------------------------------------------- + +def test_503_is_retried_then_reported_and_not_cached(calls, tmp_path): + sent = calls(lambda n, p: _Resp(status=503, text="unavailable", content_type="text/html")) + + result = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + + assert len(sent) == 3, "with_retry gives a 5xx three attempts" + assert result.hits == [] + assert result.error and "503" in result.error + assert list(tmp_path.glob("*.json")) == [], "a failed fetch must not become hits: []" + + +def test_503_then_success_recovers(calls, tmp_path): + def responder(n, params): + return _Resp(status=503) if n == 1 else _Resp(payload=_v2(NAGA_PRODUCTS)) + + calls(responder) + result = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + assert result.error is None and len(result.hits) == 2 + + +def test_html_200_error_page_is_a_failure_not_an_empty_brand(calls, tmp_path): + calls(lambda n, p: _Resp(status=200, text="temporarily unavailable", + content_type="text/html")) + result = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + assert result.error and result.hits == [] + assert list(tmp_path.glob("*.json")) == [] + + +def test_transport_error_after_retries_is_reported(calls, tmp_path): + calls(lambda n, p: requests.exceptions.ConnectionError("dns")) + result = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + assert result.error and result.hits == [] + assert list(tmp_path.glob("*.json")) == [] + + +def test_partial_fetch_keeps_what_arrived_but_flags_it(calls, tmp_path): + page1 = [{"code": f"890{i:010d}", "product_name": f"P{i}"} for i in range(100)] + + def responder(n, params): + if params["page"] == 1: + return _Resp(payload=_v2(page1, count=150)) + return _Resp(status=502) + + calls(responder) + result = fetch_brand_corpus_result("Amul", cache_dir=tmp_path) + assert len(result.hits) == 100 and result.error + assert list(tmp_path.glob("*.json")) == [] + + +def test_list_form_still_returns_empty_on_failure(calls, tmp_path): + """Backfill scripts and ingestion call `fetch_brand_corpus` and rely on + [] rather than an exception so one dead brand does not abort a run.""" + calls(lambda n, p: _Resp(status=503)) + assert fetch_brand_corpus("Naga", cache_dir=tmp_path) == [] + + +def test_off_unavailable_is_a_connection_error_for_the_retry_decorator(): + assert issubclass(OffUnavailable, requests.exceptions.ConnectionError) + + +# --------------------------------------------------------------------------- +# The cache +# --------------------------------------------------------------------------- + +def test_successful_fetch_is_cached_with_schema_and_served_from_cache(calls, tmp_path): + sent = calls(lambda n, p: _Resp(payload=_v2(NAGA_PRODUCTS))) + + first = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + second = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + + assert len(sent) == 1 + assert not first.from_cache and second.from_cache + assert second.hits == first.hits + on_disk = json.loads((tmp_path / "naga.json").read_text(encoding="utf-8")) + assert on_disk["schema"] == CACHE_SCHEMA + assert on_disk["brand_tag"] == "naga" + assert on_disk["endpoint"] == off_bulk.SEARCH_URL + + +def test_genuinely_empty_brand_is_cached_and_is_not_an_error(calls, tmp_path): + sent = calls(lambda n, p: _Resp(payload=_v2([]))) + + result = fetch_brand_corpus_result("Udhaiyam", cache_dir=tmp_path) + again = fetch_brand_corpus_result("Udhaiyam", cache_dir=tmp_path) + + assert result == CorpusFetch(hits=[], error=None, from_cache=False) + assert again.from_cache and again.hits == [] + assert len(sent) == 1 + + +def test_empty_pre_schema_cache_is_refetched(calls, tmp_path): + """The exact prod artefact: an old-endpoint file saying Naga has nothing.""" + (tmp_path / "naga.json").write_text(json.dumps({ + "brand": "Naga", "country": "india", "fetched_at": 0, + "fetched_at_human": "2026-09-10 12:00:00", "hits": [], + }), encoding="utf-8") + sent = calls(lambda n, p: _Resp(payload=_v2(NAGA_PRODUCTS))) + + result = fetch_brand_corpus_result("Naga", cache_dir=tmp_path) + + assert len(sent) == 1 and not result.from_cache + assert len(result.hits) == 2 + assert json.loads((tmp_path / "naga.json").read_text(encoding="utf-8"))["schema"] == CACHE_SCHEMA + + +def test_non_empty_pre_schema_cache_is_still_trusted(calls, tmp_path): + (tmp_path / "amul.json").write_text(json.dumps({ + "brand": "Amul", "country": "india", "fetched_at": 0, + "hits": [{"code": "8901262010115", "product_name": "Amul Butter"}], + }), encoding="utf-8") + sent = calls(lambda n, p: _Resp(status=503)) + + result = fetch_brand_corpus_result("Amul", cache_dir=tmp_path) + + assert sent == [] and result.from_cache + assert result.hits[0]["product_name"] == "Amul Butter" + + +def test_refresh_bypasses_the_cache(calls, tmp_path): + (tmp_path / "naga.json").write_text(json.dumps({ + "schema": CACHE_SCHEMA, "brand": "Naga", "hits": [], + }), encoding="utf-8") + sent = calls(lambda n, p: _Resp(payload=_v2(NAGA_PRODUCTS))) + assert len(fetch_brand_corpus_result("Naga", refresh=True, cache_dir=tmp_path).hits) == 2 + assert len(sent) == 1 + + +def test_unreadable_cache_is_refetched(calls, tmp_path): + (tmp_path / "naga.json").write_text("{not json", encoding="utf-8") + calls(lambda n, p: _Resp(payload=_v2(NAGA_PRODUCTS))) + assert len(fetch_brand_corpus_result("Naga", cache_dir=tmp_path).hits) == 2 diff --git a/tests/test_own_products_fields.py b/tests/test_own_products_fields.py index b3ea3db..b2c909f 100644 --- a/tests/test_own_products_fields.py +++ b/tests/test_own_products_fields.py @@ -159,7 +159,11 @@ def test_the_sheets_values_are_kept_and_nothing_else_is_invented(store): # What the sheet said. assert row["product_name"] == "Apple 500g" assert row["size_variants"] == ["500g"] - assert row["final_selling_price"] == 155 + # "Selling Price" is the shop's price and lands in selling_price. The + # final (tax-inclusive) column is filled from it by upsert_brand_products, + # which this fixture stubs, so here it stays what the sheet said: nothing. + assert row["selling_price"] == 155 + assert row["final_selling_price"] is None assert row["price_range"] == "₹143-167" # +/-8% of the sheet's own price # What it did not say, and what we therefore do not claim. diff --git a/tests/test_retail_presence.py b/tests/test_retail_presence.py index 7fa2182..76da026 100644 --- a/tests/test_retail_presence.py +++ b/tests/test_retail_presence.py @@ -3,7 +3,7 @@ WHY THIS EXISTS --------------- Open Food Facts answers "does this product exist" for food and nothing else. -`off_bulk` queries only `search.openfoodfacts.org`, so a toothpaste or a +`off_bulk` queries only Open Food Facts' food database, so a toothpaste or a detergent has no product-discovery source at all and its whole catalogue is language-model output. A live retail lookup is the only signal in the codebase that means "available right now" rather than "was in a database dump", and it diff --git a/tests/test_upload_sheet_fields.py b/tests/test_upload_sheet_fields.py new file mode 100644 index 0000000..2e24c92 --- /dev/null +++ b/tests/test_upload_sheet_fields.py @@ -0,0 +1,312 @@ +"""What an uploaded sheet's own facts become in the brand table. + +Three rules, each of which was violated before this file existed: + +1. THE RETAIL PRICE IS THE SELLING PRICE. Every price header used to land in + `final_selling_price` and `selling_price` was NULL for every uploaded row; + worse, the HSN/GST stage then overwrote whatever price the sheet gave with + the ceiling of the derived band (155 became 167). +2. A PACK SIZE IS RECORDED ONLY WHEN THE SHEET OR THE NAME STATES IT. + "India Gate Basmati Rice 1kg" is one product in one size. "India Gate + Basmati Rice" used to become three products - 1kg, 5kg, 25kg - none of + which the sheet listed. +3. A BLANK DESCRIPTION IS WRITTEN, NOT PADDED. The LLM writes it when Ollama + is up; when it is not, the row gets a short factual line built only from + facts the row holds - never " from ." and never the marketing + essay in catalog_engine.generate_detailed_description. + +Everything runs offline: storage and embeddings are monkeypatched on the +pipeline module, and USE_OLLAMA is false for the whole suite (conftest.py). +""" +from __future__ import annotations + +import io + +import pytest + +from app.core import store_catalog_pipeline as pipeline +from app.services.enrichment.hsn_gst.models import enrich_pricing_fields +from app.services.generic_products import OWN_PRODUCTS_BRAND + +openpyxl = pytest.importorskip("openpyxl") + + +def _sheet(headers, rows) -> bytes: + wb = openpyxl.Workbook() + ws = wb.active + ws.append(headers) + for row in rows: + ws.append(row) + buf = io.BytesIO() + wb.save(buf) + return buf.getvalue() + + +@pytest.fixture(autouse=True) +def _isolate_sku_counter(tmp_path, monkeypatch): + from app.services import sku_service + monkeypatch.setattr(sku_service, "_data_dir", tmp_path / "sku_sequences") + + +@pytest.fixture +def store(monkeypatch): + """A fake brand table: image_id -> stored row, across every brand.""" + table: dict = {} + + def fake_upsert(brand, rows, cleanup=False): + assert cleanup is False + for row in rows: + table[row["image_id"]] = dict(row) + return len(rows) + + monkeypatch.setattr(pipeline, "upsert_brand_products", fake_upsert) + monkeypatch.setattr(pipeline, "get_products_by_brand", lambda b, **kw: list(table.values())) + monkeypatch.setattr(pipeline, "embed_texts", lambda texts: [[0.0] * 384 for _ in texts]) + return table + + +def _run(headers, rows, **kw): + kw.setdefault("use_llm", False) + kw.setdefault("fetch_images", False) + return pipeline.run_pipeline("store.xlsx", _sheet(headers, rows), **kw) + + +# Real-looking names: the validation gate rejects placeholder titles such as +# "Product 3", which would empty the store and hide what a test is about. +_AMUL_NAMES = ["Amul Butter 100g", "Amul Cheese 200g", "Amul Ghee 500ml", "Amul Taaza 1L", + "Amul Kool 200ml", "Amul Lassi 200ml", "Amul Masti Dahi 400g"] + + +def _only(store): + assert len(store) == 1, list(store) + return next(iter(store.values())) + + +# --------------------------------------------------------------------------- +# 1. Price +# --------------------------------------------------------------------------- +def test_retail_price_lands_in_selling_price_and_drives_the_band(store): + _run(["ProductName", "ProductSKU", "ProductBrand", "RetailPrice"], + [["India Gate Basmati Rice 1kg", "IG-BR-1", "India Gate", 120]]) + + row = _only(store) + assert row["selling_price"] == 120 + assert row["price_range"] == "₹110-130" # +/-8% of the sheet's price + assert row["product_sku"] == "IG-BR-1" and row["sku_source"] == "sheet" + + +def test_a_final_price_column_is_kept_separate_and_wins_the_band(store): + _run(["Item Name", "Brand", "Selling Price", "Final Price"], + [["Amul Butter 500g", "Amul", 250, 262]]) + + row = _only(store) + assert row["selling_price"] == 250 + assert row["final_selling_price"] == 262 + assert row["price_range"] == "₹241-283" # band from the final price + + +def test_reingesting_a_sheet_with_a_selling_price_is_a_no_op(store): + headers, rows = ["Item Name", "Brand", "Retail Price"], [["Amul Butter 500g", "Amul", 250]] + first = _run(headers, rows) + assert first.inserted == 1 + + second = _run(headers, rows) + assert (second.inserted, second.backfilled, second.skipped_existing) == (0, 0, 1) + + +def test_a_held_final_price_is_not_clobbered_by_a_different_retail_price(store): + _run(["Item Name", "Brand", "Retail Price"], [["Amul Butter 500g", "Amul", 250]]) + key = next(iter(store)) + store[key]["final_selling_price"] = 262 # what the table already holds + + _run(["Item Name", "Brand", "Retail Price"], [["Amul Butter 500g", "Amul", 199]]) + + assert store[key]["final_selling_price"] == 262 + assert store[key]["selling_price"] == 250, "fill-only-blanks: a held price is kept" + + +class TestHsnStageKeepsHeldPrices: + """The overwrite that turned a sheet's 155 into 167.""" + + def test_a_held_selling_price_is_returned_as_none_so_apply_keeps_it(self): + fields = enrich_pricing_fields({ + "selling_price": 155, "final_selling_price": None, + "price_range": "₹143-167", "gst_percent": 18, + }) + assert fields["selling_price"] is None + assert fields["tax_amount"] == 27.9 # 18% of the HELD 155, not of 167 + assert fields["final_selling_price"] == 182.9 + + def test_a_held_final_price_is_also_kept(self): + fields = enrich_pricing_fields({ + "selling_price": 155, "final_selling_price": 160, + "price_range": "₹143-167", "gst_percent": 18, + }) + assert fields["selling_price"] is None + assert fields["final_selling_price"] is None + + def test_without_a_sheet_price_the_band_ceiling_is_still_the_base(self): + fields = enrich_pricing_fields({"price_range": "₹143-167", "gst_percent": 5}) + assert fields["selling_price"] == 167 + assert fields["final_selling_price"] == 175.35 + + def test_a_held_final_price_alone_seeds_the_selling_price(self): + fields = enrich_pricing_fields({"final_selling_price": 200, "gst_percent": 5, + "price_range": "₹300-400"}) + assert fields["selling_price"] == 200 + assert fields["final_selling_price"] is None + + +# --------------------------------------------------------------------------- +# 2. Size +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("name, size", [ + ("India Gate Basmati Rice 1kg", "1kg"), + ("Fortune Sunflower Oil 1L", "1L"), + ("Amul Taaza Milk 1 Litre", "1 Litre"), + ("Aashirvaad Atta 5 Kg", "5 Kg"), + ("Tata Salt 1.5kg", "1.5kg"), + ("Parle-G Biscuits 800 gm", "800 gm"), + ("Saffola Gold 500ml", "500ml"), +]) +def test_the_size_in_the_name_is_the_size_and_nothing_is_appended(store, name, size): + _run(["Product Name", "Brand"], [[name, name.split()[0]]]) + + row = _only(store) + assert row["size_variants"] == [size] + assert row["product_name"] == name, "the name already carries the size" + + +def test_a_branded_product_with_no_size_is_one_clean_row(store): + result = _run(["Product Name", "Brand"], [["India Gate Basmati Rice", "India Gate"]]) + + row = _only(store) + assert row["size_variants"] == [] + assert row["product_name"] == "India Gate Basmati Rice" + assert "Standard" not in row["product_name"] + assert next(iter(store)) == "india_gate_india_gate_basmati_rice" + assert any("no pack size" in w for w in result.warnings) + + +def test_a_commodity_with_no_size_still_uses_the_standard_sentinel(store): + """Own Products dedupes against a seeded base list built on "Standard".""" + _run(["Product Name"], [["Apple"]]) + + row = _only(store) + assert row["size_variants"] == ["Standard"] + assert next(iter(store)) == pipeline.build_image_id(OWN_PRODUCTS_BRAND, "Apple", "Standard") + + +def test_the_llm_may_not_supply_a_pack_size(store, monkeypatch): + from app.services import ollama_service + + monkeypatch.setattr(ollama_service, "fetch_product_details", + lambda brand, title, category=None, size=None: { + "description": "Refined wheat flour milled for baking.", + "size_variants": ["500g", "1kg", "5kg"], + }) + + _run(["Product Name", "Brand"], [["Naga Maida", "Naga"]], use_llm=True) + + row = _only(store) + assert row["size_variants"] == [] + assert row["description"] == "Refined wheat flour milled for baking." + + +def test_a_sheet_size_column_still_wins_over_the_name(store): + _run(["Product Name", "Brand", "Pack Size"], [["Amul Butter 100g", "Amul", "500g"]]) + assert _only(store)["size_variants"] == ["500g"] + + +# --------------------------------------------------------------------------- +# 3. Description +# --------------------------------------------------------------------------- +def test_the_llm_description_is_used_when_ollama_answers(store, monkeypatch): + from app.services import ollama_service + + seen = {} + + def fake(brand, title, category=None, size=None): + seen.update(brand=brand, title=title, category=category, size=size) + return {"description": "Long-grain aged basmati rice for biryani and pulao."} + + monkeypatch.setattr(ollama_service, "fetch_product_details", fake) + + _run(["Product Name", "Brand"], [["India Gate Basmati Rice 1kg", "India Gate"]], use_llm=True) + + assert _only(store)["description"] == "Long-grain aged basmati rice for biryani and pulao." + assert seen["size"] == "1kg", "the pack size in the name is handed to the prompt" + + +def test_an_overlong_llm_description_is_clamped(store, monkeypatch): + from app.services import ollama_service + + monkeypatch.setattr(ollama_service, "fetch_product_details", + lambda *a, **k: {"description": "x" * 2000}) + _run(["Product Name", "Brand"], [["Amul Butter 500g", "Amul"]], use_llm=True) + assert len(_only(store)["description"]) == pipeline._MAX_LLM_DESCRIPTION_CHARS + + +def test_the_fallback_description_is_factual_and_short(store): + _run(["Product Name", "Brand", "Category"], + [["Fortune Sunflower Oil 1L", "Fortune", "Edible Oils"]]) + + desc = _only(store)["description"] + assert desc == "Fortune Sunflower Oil 1L, Edible Oils, by Fortune." + assert "from Fortune." not in desc and "Introducing" not in desc + + +def test_the_fallback_description_names_a_sheet_size_the_name_lacks(store): + _run(["Product Name", "Brand", "Pack Size"], [["Amul Butter", "Amul", "500g"]]) + assert _only(store)["description"] == "Amul Butter 500g, Dairy, by Amul." + + +def test_a_sheet_description_is_never_overwritten(store, monkeypatch): + from app.services import ollama_service + + monkeypatch.setattr(ollama_service, "fetch_product_details", + lambda *a, **k: pytest.fail("the LLM must not be asked")) + _run(["Product Name", "Brand", "Description"], + [["Amul Butter 500g", "Amul", "Salted table butter."]], use_llm=True) + assert _only(store)["description"] == "Salted table butter." + + +def test_the_llm_is_switched_off_after_three_consecutive_misses(store, monkeypatch): + from app.services import ollama_service + + calls = [] + monkeypatch.setattr(ollama_service, "fetch_product_details", + lambda brand, title, **k: calls.append(title) or None) + + rows = [[name, "Amul"] for name in _AMUL_NAMES[:6]] + result = _run(["Product Name", "Brand"], rows, use_llm=True) + + assert len(calls) == 3 + assert any("consecutive" in w for w in result.warnings) + assert len(store) == 6, "the rows themselves are unaffected" + + +def test_one_answer_resets_the_breaker(store, monkeypatch): + from app.services import ollama_service + + answers = iter([None, None, {"description": "ok"}, None, None, None, None]) + calls = [] + + def fake(brand, title, **k): + calls.append(title) + return next(answers) + + monkeypatch.setattr(ollama_service, "fetch_product_details", fake) + _run(["Product Name", "Brand"], [[name, "Amul"] for name in _AMUL_NAMES], use_llm=True) + + # miss, miss, hit (reset), miss, miss, miss (trip) -> the 7th row is not asked. + assert len(calls) == 6 + + +def test_stage_two_leaves_a_blank_description_blank_on_a_miss(): + """The fallback lives in _to_storage_row; stage 2 must not pre-empt it.""" + row = {"brand": "Amul", "product_name": "Amul Butter 500g", "description": ""} + breaker = pipeline.LlmBreaker() + pipeline.stage_2_row_intake(row, use_llm=True, breaker=breaker) + assert row["description"] == "" + assert breaker.consecutive_misses == 1 diff --git a/tests/test_user_products_upload.py b/tests/test_user_products_upload.py index ebd0d55..35bed0f 100644 --- a/tests/test_user_products_upload.py +++ b/tests/test_user_products_upload.py @@ -187,6 +187,45 @@ def test_template_headers_all_map_to_their_field(): assert mapping.ignored == [] +@pytest.mark.parametrize("header, canonical", [ + # The camel-cased headers a store's export tool produces, which + # _normalize_header collapses to one word - locked as exact aliases so + # they do not depend on the keyword rules' ordering. + ("ProductName", "product_name"), + ("ProductBrand", "brand"), + ("Product Brand", "brand"), + ("ProductSKU", "product_sku"), + ("Product SKU", "product_sku"), + # What the shop charges -> selling_price. + ("RetailPrice", "selling_price"), + ("Retail Price", "selling_price"), + ("Selling Price", "selling_price"), + ("Sale Price", "selling_price"), + ("SP", "selling_price"), + ("Price", "selling_price"), + ("Unit Price (Rs)", "selling_price"), + # The tax-inclusive ceiling -> final_selling_price. "Final Selling Price" + # contains "selling price" and must still win: rule order regression. + ("Final Selling Price", "final_selling_price"), + ("Final Price (₹)", "final_selling_price"), + ("MRP", "final_selling_price"), + ("Maximum MRP", "final_selling_price"), +]) +def test_price_and_identity_headers_map_where_the_sheet_means_them(header, canonical): + """Before the selling_price canonical existed every price header landed in + final_selling_price and the table's selling_price column stayed NULL.""" + mapping = user_products.map_spreadsheet_columns([header]) + assert mapping.columns == {canonical: header} + + +def test_a_sheet_may_carry_both_a_retail_and_a_final_price(): + mapping = user_products.map_spreadsheet_columns( + ["Item Name", "Brand", "Retail Price", "MRP"]) + assert mapping.columns["selling_price"] == "Retail Price" + assert mapping.columns["final_selling_price"] == "MRP" + assert mapping.ignored == [] + + def test_a_product_sku_column_is_not_mistaken_for_the_product_name(): """'Product SKU' contains 'product'. Matched loosely, it used to become a second product_name column, and duplicate columns are what turned a row