unpopular brand generation

This commit is contained in:
sriram
2026-09-10 17:51:05 +05:30
parent d5a23f6456
commit e1a5962f82
11 changed files with 7629 additions and 3 deletions

274
tests/test_brand_store.py Normal file
View File

@@ -0,0 +1,274 @@
"""A brand's own shop as a catalogue source.
WHY THIS SOURCE EXISTS
----------------------
`off_bulk.fetch_brand_corpus` is the only thing in the codebase that can
ENUMERATE a brand's products; everything else verifies one product at a time
and needs a name as input. For regional South Indian brands Open Food Facts is
empty - measured 2026-09-10: Udhaiyam 0 hits, Gopuram 0, Tenali Double Horse 0,
and both of Naga's 2 hits are a British pickle - so their catalogues were
entirely `qwen2.5:1.5b` invention.
Their storefronts publish the real thing as structured JSON. Measured live:
Aachi 161 products (159 with a VALID EAN-13), Double Horse 99, Gopuram 122,
Anil 57 - against 83 for the same brands in Open Food Facts.
WHAT THESE TESTS GUARD
----------------------
This source is different in kind from every other one here: it ASSERTS a
hundred products at once instead of verifying one. So the identity guard is the
thing that matters most - pointing it at the wrong site imports a hundred of
somebody else's real products under this brand's name, and they look flawless
because they are flawless, just not theirs.
The fixtures are trimmed copies of real responses.
"""
from __future__ import annotations
import json
import pytest
from app.services import brand_store as bs
from app.services.brand_registry import get_brand_store_domain, resolve_parent_brand
# A real aachifoods.com /products.json record, trimmed.
SHOPIFY_PRODUCT = {
"title": "Soan Papdi Ghee",
"product_type": "Snacks",
"vendor": "Aachifoods",
"images": [{"src": "https://cdn.shopify.com/s/files/1/x/ghee-soan-papdi.webp"}],
"variants": [
{"title": "200g", "price": "76.00", "sku": "8904209319340", "grams": 215},
],
}
# A real gopuramproducts.com Store API record, trimmed. Note the price.
WOO_PRODUCT = {
"name": "GOPURAM PREMIUM SAMBRAIN POWDER",
"sku": "",
"categories": [{"name": "Pooja Products"}],
"images": [{"src": "https://gopuramproducts.com/wp-content/uploads/x.jpg"}],
"prices": {"price": "5500", "currency_minor_unit": 2},
}
# ---------------------------------------------------------------------------
# Identity - the guard that matters most
# ---------------------------------------------------------------------------
def test_a_city_council_is_not_a_food_brand():
"""THE CASE THIS GUARD EXISTS FOR.
`retail_presence.resolve_brand_domain("Naga")` really returns
`cityofnagacebu.gov.ph` - the website of Naga City in the Philippines -
because `_pick_brand_domain` accepted a domain whose stem merely CONTAINS a
brand token. Here that would not cost one bad row; it would file a
municipal website's contents as a Tamil Nadu brand's product list.
"""
assert bs._store_identity_ok("Naga", "cityofnagacebu.gov.ph", []) is False
def test_a_brand_domain_is_the_brand_plus_a_qualifier():
"""Prefix, not substring - that is the whole distinction.
"gopuramproducts".startswith("gopuram") but "cityofnagacebu" does not
start with "naga", though it contains it."""
for brand, domain in [("Gopuram", "gopuramproducts.com"),
("Udhaiyam", "udhaiyamdhall.com"),
("Double Horse", "doublehorse.in"),
("Naga", "nagafoods.in")]:
assert bs._store_identity_ok(brand, domain, []) is True, domain
def test_the_vendor_field_can_vouch_for_a_domain_that_does_not():
"""Shopify states the seller. A brand trading on a domain that does not
contain its name is still identifiable."""
assert bs._store_identity_ok("Aachi", "someshop.com", ["Aachifoods"]) is True
assert bs._store_identity_ok("Aachi", "someshop.com", ["Britannia"]) is False
def test_a_rejected_catalogue_is_rejected_whole(monkeypatch):
"""Half a foreign catalogue is not better than all of it."""
monkeypatch.setattr(bs, "fetch_shopify",
lambda d: [dict(SHOPIFY_PRODUCT, vendor="Someone Else")])
monkeypatch.setattr(bs, "read_cached", lambda b: None)
monkeypatch.setattr(bs, "_write_cache", lambda p, d: None)
assert bs.fetch_store_catalogue("Naga", "cityofnagacebu.gov.ph", live=True) is None
def test_a_curated_domain_skips_the_heuristic(monkeypatch):
"""A person already checked it. The heuristic cannot recognise every shape
a real brand domain takes - "theanilgroup.com" is Anil's and neither starts
with nor contains "anil" as a whole token - so applying it to a hand-verified
mapping would reject good catalogues for looking unusual."""
assert bs._store_identity_ok("Anil", "shop.theanilgroup.com", []) is False
monkeypatch.setattr(bs, "fetch_shopify", lambda d: [SHOPIFY_PRODUCT])
monkeypatch.setattr(bs, "read_cached", lambda b: None)
monkeypatch.setattr(bs, "_write_cache", lambda p, d: None)
rows = bs.fetch_store_catalogue("Anil", "shop.theanilgroup.com",
live=True, trusted_domain=True)
assert rows and len(rows) == 1
# ---------------------------------------------------------------------------
# Field mapping
# ---------------------------------------------------------------------------
def test_a_shopify_variant_carries_the_pack_size_and_a_real_barcode():
rows = bs._shopify_candidates(SHOPIFY_PRODUCT)
assert len(rows) == 1
row = rows[0]
assert row["title"] == "Soan Papdi Ghee"
assert row["category"] == "Snacks"
assert row["size"] == "200g"
assert row["price"] == 76.00
assert row["image_url"].endswith("ghee-soan-papdi.webp")
# A genuine EAN-13 on the Indian 890 GS1 prefix.
assert row["barcode"] == "8904209319340"
def test_a_shop_internal_sku_is_not_treated_as_a_barcode():
"""Shops put anything in `sku`. `validate_barcode` decides, so an internal
numbering scheme never reaches the barcode column - Gopuram leaves it blank
and others use their own codes."""
for junk in ("", "GOP-001", "12345"):
product = json.loads(json.dumps(SHOPIFY_PRODUCT))
product["variants"][0]["sku"] = junk
assert bs._shopify_candidates(product)[0]["barcode"] is None, junk
def test_default_title_is_not_a_pack_size():
"""Shopify's placeholder for a product with no variant axis."""
product = json.loads(json.dumps(SHOPIFY_PRODUCT))
product["variants"][0]["title"] = "Default Title"
product["variants"][0].pop("sku")
assert bs._shopify_candidates(product)[0]["size"] is None
def test_woocommerce_prices_are_minor_units():
"""gopuramproducts.com returns price "5500" with currency_minor_unit 2,
which is Rs55.00 and not Rs5,500. Read straight, every price on the site is
a hundred times too large - and `validate_price_range` would then reject
every row, so the bug would present as "this brand is all wrong" rather
than as a units error."""
row = bs._woo_candidates(WOO_PRODUCT)[0]
assert row["price"] == 55.00
assert row["category"] == "Pooja Products"
assert row["title"] == "GOPURAM PREMIUM SAMBRAIN POWDER"
def test_a_utf8_bom_does_not_break_parsing():
"""WooCommerce really serves one; `resp.json()` raises on it."""
class Resp:
status_code = 200
headers = {"content-type": "application/json; charset=UTF-8"}
content = b"\xef\xbb\xbf" + json.dumps([WOO_PRODUCT]).encode()
url = "https://example.com"
parsed = bs._json_or_none(Resp())
assert isinstance(parsed, list) and parsed[0]["name"].startswith("GOPURAM")
# ---------------------------------------------------------------------------
# The sitemap tier states only what the site states
# ---------------------------------------------------------------------------
def test_the_sitemap_tier_never_invents_a_size_or_price():
"""udhaiyamdhall.com is a brochure site: an <h1> and a photograph, no
price, no pack size, no variants. Those stay None. A catalogue source that
guesses them is the failure this whole area exists to fix."""
import inspect
source = inspect.getsource(bs.fetch_sitemap)
assert '"size": None' in source
assert '"price": None' in source
assert '"barcode": None' in source
# ---------------------------------------------------------------------------
# The registry
# ---------------------------------------------------------------------------
def test_double_horse_and_tenali_double_horse_stay_apart():
"""TWO COMPANIES, ONE NAME. Double Horse is Manjilas of Kerala; Tenali
Double Horse is an Andhra rice brand.
`_contains_word` matches whole words in BOTH directions, and "double horse"
is a whole word sequence inside "tenali double horse". Registering only one
of them moved the collision instead of fixing it - measured: with only the
longer key present, resolve_parent_brand("Double Horse") returned
"tenali double horse". Each direct entry protects the OTHER brand.
"""
assert resolve_parent_brand("Tenali Double Horse") == "tenali double horse"
assert resolve_parent_brand("Double Horse") == "double horse"
assert resolve_parent_brand("Tenali Double Horse") != resolve_parent_brand("Double Horse")
def test_spelling_variants_reach_the_same_brand():
assert resolve_parent_brand("Udhayam") == "udhaiyam"
assert resolve_parent_brand("DoubleHorse") == "double horse"
def test_a_brand_with_no_verified_store_has_none():
"""Absent is the safe answer. A guessed domain is not - Naga's search
result is a Philippine city council."""
assert get_brand_store_domain("Naga") is None
assert get_brand_store_domain("Tenali Double Horse") is None
assert get_brand_store_domain("Gopuram") == "gopuramproducts.com"
def test_existing_brands_are_unaffected():
for brand, parent in [("Cadbury Dairy Milk", "cadbury"),
("Haldiram", "haldirams"),
("Sunfeast Yippee", "sunfeast")]:
assert resolve_parent_brand(brand) == parent
# ---------------------------------------------------------------------------
# Discovery integration
# ---------------------------------------------------------------------------
def test_a_store_row_outranks_open_food_facts():
"""A manufacturer's own catalogue is first-party; Open Food Facts is
crowd-sourced and, for these brands, absent or wrong."""
from app.services import brand_discovery as bd
assert bd._score(["store"], None) > bd._score(["off"], "openfacts")
assert bd._score(["store", "off"], "openfacts") == 1.0
def test_a_store_row_survives_the_evidence_gate():
"""WITHOUT THIS THE WHOLE SOURCE IS INVISIBLE. `require_evidence` drops any
row with no `off` source and no evidence, and a store row has neither - it
would score 0.25 and never reach the preview."""
from app.services import brand_discovery as bd
import inspect
gate = inspect.getsource(bd.discover_brand_products)
assert '"store" not in product.sources' in gate
def test_discovery_reads_the_store_cache_and_never_fetches(monkeypatch):
"""Same split as the retail check: the backfill script fetches, discovery
reads. A preview must not block on somebody's storefront."""
from app.services import brand_discovery as bd
def explode(*_a, **_kw):
raise AssertionError("discovery must not fetch a storefront")
monkeypatch.setattr(bs, "fetch_shopify", explode)
monkeypatch.setattr(bs, "fetch_woocommerce", explode)
monkeypatch.setattr(bs, "fetch_sitemap", explode)
monkeypatch.setattr(bs, "read_cached", lambda b: [dict(SHOPIFY_PRODUCT, source="store")])
rows = bd._from_brand_store("Aachi")
assert rows and rows[0]["source"] == "store"