[object Object]

← back to Designer Wallcoverings

contrarian fixes: (1) read color_details BEFORE legacy color_dots_json (no shadowing); (2) anchor marketed color to dominant swatch on near-solid products (Gold foil no longer 'Walnut'), canonical lexicon names

b21d7edb50aed94ffaced2fd85bb9d004391203e · 2026-07-07 10:15:25 -0700 · Steve

Files touched

Diff

commit b21d7edb50aed94ffaced2fd85bb9d004391203e
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 7 10:15:25 2026 -0700

    contrarian fixes: (1) read color_details BEFORE legacy color_dots_json (no shadowing); (2) anchor marketed color to dominant swatch on near-solid products (Gold foil no longer 'Walnut'), canonical lexicon names
---
 shopify/scripts/enrich-color-details-local.py      | 59 +++++++++++++++++++++-
 .../theme-LIVE-591/snippets/color-palette.liquid   |  8 +--
 2 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/shopify/scripts/enrich-color-details-local.py b/shopify/scripts/enrich-color-details-local.py
index 3569ca3b..62b8e8d8 100644
--- a/shopify/scripts/enrich-color-details-local.py
+++ b/shopify/scripts/enrich-color-details-local.py
@@ -44,11 +44,12 @@ def gql(query, variables=None):
 
 
 def get_products(ids, query_str, limit):
+    cn = 'cn: metafield(namespace:"custom", key:"color_name"){ value }'
     if ids:
         gids = ','.join(f'"gid://shopify/Product/{i}"' for i in ids)
-        q = f'{{ nodes(ids:[{gids}]){{ ... on Product {{ id title featuredImage{{url}} }} }} }}'
+        q = f'{{ nodes(ids:[{gids}]){{ ... on Product {{ id title featuredImage{{url}} {cn} }} }} }}'
         return [n for n in gql(q)['data']['nodes'] if n and n.get('featuredImage')]
-    q = 'query($q:String!,$n:Int!){ products(first:$n, query:$q){ edges{ node{ id title featuredImage{url} } } } }'
+    q = f'query($q:String!,$n:Int!){{ products(first:$n, query:$q){{ edges{{ node{{ id title featuredImage{{url}} {cn} }} }} }} }}'
     return [e['node'] for e in gql(q, {'q': query_str, 'n': limit})['data']['products']['edges'] if e['node'].get('featuredImage')]
 
 
@@ -56,6 +57,7 @@ def iter_published(skip_enriched=True):
     """Cursor-paginate ALL published products, yielding those needing enrichment."""
     q = ('query($c:String){ products(first:100, after:$c, query:"status:active published_status:published"){ '
          'pageInfo{hasNextPage endCursor} edges{ node{ id title featuredImage{url} '
+         'cn: metafield(namespace:"custom", key:"color_name"){ value } '
          'cd: metafield(namespace:"custom", key:"color_details"){ value } } } } }')
     cursor = None
     while True:
@@ -202,11 +204,64 @@ def write_metafield(gid, colors):
         raise RuntimeError(errs)
 
 
+_MATERIAL_WORDS = ('Wallcovering', 'Wallcoverings', 'Wallpaper', 'Fabric', 'Fabrics',
+                   'Mural', 'Murals', 'Commercial', 'Grasscloth', 'Vinyl')
+
+
+def marketed_color(p):
+    """The product's SOLD color name (custom.color_name, else parsed from the title)."""
+    name = ((p.get('cn') or {}).get('value') or '').strip() if p.get('cn') else ''
+    if not name:
+        t = (p.get('title') or '').split('|')[0].strip()
+        if ' - ' in t:
+            name = t.split(' - ')[-1].strip()
+    for w in _MATERIAL_WORDS:
+        name = name.replace(w, '').strip()
+    name = ' '.join(name.split())
+    return name if 1 < len(name) <= 24 else None
+
+
+def resolve_color(name):
+    """Marketed color name -> (canonical lexicon name, RGB). Exact, then color-word substring."""
+    if not name:
+        return None
+    low = name.lower()
+    for k, v in NAMED_COLORS.items():
+        if k.lower() == low:
+            return (k, v)
+    for k, v in sorted(NAMED_COLORS.items(), key=lambda kv: -len(kv[0])):
+        if k.lower() in low:
+            return (k, v)
+    return None
+
+
+def anchor_marketed(colors, p):
+    """Tag the product's SOLD color onto the right swatch, using the canonical lexicon name:
+    - near-solid product (dominant >= 58%): the product IS its color -> rename the DOMINANT.
+    - multi-color pattern: rename the swatch NEAREST the marketed color (tags the accent, not the ground).
+    Keeps every real hex."""
+    resolved = resolve_color(marketed_color(p))
+    if not resolved or not colors:
+        return colors
+    canon, kr = resolved
+    if colors[0].get('pct', 0) >= 58:
+        idx = 0
+    else:
+        def dist(hexstr):
+            h = hexstr.lstrip('#'); r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
+            return (r - kr[0]) ** 2 + (g - kr[1]) ** 2 + (b - kr[2]) ** 2
+        idx = min(range(len(colors)), key=lambda i: dist(colors[i]['hex']))
+    if all(c['name'] != canon for j, c in enumerate(colors) if j != idx):
+        colors[idx]['name'] = canon
+    return colors
+
+
 def enrich_one(p, dry):
     img_bytes = urllib.request.urlopen(p['featuredImage']['url'], timeout=40).read()
     hexpct = dominant_colors(img_bytes)
     names = name_hexes([h for h, _ in hexpct])   # deterministic table (no model call)
     colors = [{'name': names[i], 'hex': hexpct[i][0], 'pct': hexpct[i][1]} for i in range(len(hexpct))]
+    colors = anchor_marketed(colors, p)          # tag the marketed color onto its nearest swatch
     if not dry:
         write_metafield(p['id'], colors)
     return ', '.join(f"{c['name']} {c['pct']}%" for c in colors)
diff --git a/shopify/theme-LIVE-591/snippets/color-palette.liquid b/shopify/theme-LIVE-591/snippets/color-palette.liquid
index fd5ae41e..055af45c 100644
--- a/shopify/theme-LIVE-591/snippets/color-palette.liquid
+++ b/shopify/theme-LIVE-591/snippets/color-palette.liquid
@@ -12,12 +12,14 @@
   `pct`/`percentage` field when present, else are computed live from the image.
 {% endcomment %}
 
+{% comment %} Read the authoritative enriched field FIRST (color_details); legacy
+   color_dots_json is only a fallback so it can never shadow fresh enrichment. {% endcomment %}
 {% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
 {% assign color_details_val = product.metafields.custom.color_details.value %}
-{% if color_dots_json_val != blank %}
-  {% assign color_details_raw = color_dots_json_val | json %}
-{% elsif color_details_val != blank %}
+{% if color_details_val != blank %}
   {% assign color_details_raw = color_details_val | json %}
+{% elsif color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
 {% else %}
   {% assign color_details_raw = blank %}
 {% endif %}

← 75188a05 enrich: replace LLM naming with deterministic ~115-color int  ·  back to Designer Wallcoverings  ·  cadence: Alan Campbell productType fallback Wallcovering->Fa f1fea763 →