← back to Elitis Price 2026
WS3b: Bucket B accent/fuzzy matcher recovers 30 draft colorways (metal-suffix + accent drift), 47 patterns routed to scrape
8620c28f81d13b846a599a93cb2dd7a2235eb35e · 2026-07-11 08:20:36 -0700 · Steve Abrams
Files touched
A exec/__pycache__/ws3_drafts.cpython-314.pycM exec/ledger/ws3_drafts.csvA exec/ws3b_bucketB.py
Diff
commit 8620c28f81d13b846a599a93cb2dd7a2235eb35e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Jul 11 08:20:36 2026 -0700
WS3b: Bucket B accent/fuzzy matcher recovers 30 draft colorways (metal-suffix + accent drift), 47 patterns routed to scrape
---
exec/__pycache__/ws3_drafts.cpython-314.pyc | Bin 0 -> 14445 bytes
exec/ledger/ws3_drafts.csv | 42 ++++++++++++
exec/ws3b_bucketB.py | 102 ++++++++++++++++++++++++++++
3 files changed, 144 insertions(+)
diff --git a/exec/__pycache__/ws3_drafts.cpython-314.pyc b/exec/__pycache__/ws3_drafts.cpython-314.pyc
new file mode 100644
index 0000000..cfbf1c1
Binary files /dev/null and b/exec/__pycache__/ws3_drafts.cpython-314.pyc differ
diff --git a/exec/ledger/ws3_drafts.csv b/exec/ledger/ws3_drafts.csv
index 4f39f9a..13c86d6 100644
--- a/exec/ledger/ws3_drafts.csv
+++ b/exec/ledger/ws3_drafts.csv
@@ -330,3 +330,45 @@ ts,list_sku,pattern,colorway,unit,retail,product_id,dw_sku,status
2026-07-10T21:56:49,VP_1035,Twill,Écume d'ivoire,Roll,619.0,7880951922739,DWEL-VP-1035-02,OK
2026-07-10T21:56:51,VP_1035,Twill,Ombre d'oasis,Roll,619.0,7880952021043,DWEL-VP-1035-15,OK
2026-07-10T21:56:54,VP_1035,Twill,Brise égéenne,Roll,619.0,7880952184883,DWEL-VP-1035-44,OK
+2026-07-11T08:19:10,RM_631,Éclat,Le sens du glamour,Roll,876.02,7881307750451,DWEL-RM-889-92,OK
+2026-07-11T08:19:13,RM_880,Éclat,Le sens du glamour,Yard,445.25,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:13,RM_881,Éclat,Le sens du glamour,Yard,445.25,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:13,RM_882,Éclat,Le sens du glamour,Yard,445.25,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:13,RM_883,Éclat,Le sens du glamour,Yard,445.25,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:14,RM_889,Éclat,Le sens du glamour,Yard,581.0,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:14,RM_986,Métal brush,Le fait du Prince,Yard,237.1,7881307783219,DWEL-RM-986-01,OK
+2026-07-11T08:19:16,RM_986,Métal brush,La poudrière des âmes,Yard,237.1,7881307815987,DWEL-RM-986-20,OK
+2026-07-11T08:19:19,RM_986,Métal brush,Un charisme en or,Yard,237.1,7881307848755,DWEL-RM-986-72,OK
+2026-07-11T08:19:21,RM_1018,Pépite,Essence féminine,Yard,309.5,7881307881523,DWEL-RM-1018-01,OK
+2026-07-11T08:19:23,RM_1018,Pépite,Assumer ses rondeurs,Yard,309.5,7881307914291,DWEL-RM-1018-02,OK
+2026-07-11T08:19:26,RM_1018,Pépite,Trio gagnant,Yard,309.5,7881307947059,DWEL-RM-1018-04,OK
+2026-07-11T08:19:28,RM_1018,Pépite,L'air du temps,Yard,309.5,7881307979827,DWEL-RM-1018-60,OK
+2026-07-11T08:19:30,RM_1109,Enlacé,Un glamour très chic,Yard,674.45,7881308012595,DWEL-RM-1109-01,OK
+2026-07-11T08:19:33,RM_1110,Icône,charcoal,Yard,514.41,7881308045363,DWEL-RM-1110-80,OK
+2026-07-11T08:19:35,RM_1110,Icône,Sable tressé,Yard,514.41,7881308078131,DWEL-RM-1110-02,OK
+2026-07-11T08:19:37,RM_1112,Épi,Sillage d'ivoire,Yard,697.31,7881308110899,DWEL-RM-1112-02,OK
+2026-07-11T08:19:40,RM_1112,Épi,Toison d'argile,Yard,697.31,7881308143667,DWEL-RM-1112-01,OK
+2026-07-11T08:19:42,VP_653,Panthère,Royale parure,Roll,445.25,7881308176435,DWEL-VP-653-04,OK
+2026-07-11T08:19:44,VP_653,Panthère,Son plus beau trophé,Roll,445.25,7881308209203,DWEL-VP-653-01,OK
+2026-07-11T08:19:47,VP_653,Panthère,Un trophée mérité,Roll,445.25,7881308241971,DWEL-VP-653-07,OK
+2026-07-11T08:19:49,VP_653,Panthère,À pas de velours,Roll,445.25,7881308274739,DWEL-VP-653-11,OK
+2026-07-11T08:19:51,VP_1022_01,Éclat,Le sens du glamour,Roll,4278.73,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:51,VP_1022_01_Lx,Éclat,Le sens du glamour,Roll,709.5,,DWEL-RM-889-92,SKIP:already-on-shopify
+2026-07-11T08:19:52,RM_1053,Twisted lin,beige,Yard,678.73,7881308307507,DWEL-RM-1053-01,OK
+2026-07-11T08:19:54,RM_1091,Colosse fruitier,burnt sienna,Panel,1837.1,7881308340275,DWEL-RM-1090-31,OK
+2026-07-11T08:19:56,VP_935,Kosa silk métal,Privilège de Maharaja,Roll,1286.88,7881308405811,DWEL-VP-935-94,OK
+2026-07-11T08:19:59,VP_935,Kosa silk métal,Rivaliser de magnificience,Roll,1286.88,7881308438579,DWEL-VP-935-92,OK
+2026-07-11T08:20:01,VP_935,Kosa silk métal,La clé des songes,Roll,1286.88,7881308471347,DWEL-VP-935-96,OK
+2026-07-11T08:20:04,VP_935,Kosa silk métal,Royal apparat,Roll,1286.88,7881308504115,DWEL-VP-935-93,OK
+2026-07-11T08:20:06,VP_935,Kosa silk métal,Digne d'une princesse,Roll,1286.88,7881308536883,DWEL-VP-935-95,OK
+2026-07-11T08:20:08,VP_935,Kosa silk métal,Privilège de Maharaja,Roll,1286.88,,DWEL-VP-935-94,SKIP:already-on-shopify
+2026-07-11T08:20:08,VP_935,Kosa silk métal,Rivaliser de magnificience,Roll,1286.88,,DWEL-VP-935-92,SKIP:already-on-shopify
+2026-07-11T08:20:09,VP_935,Kosa silk métal,La clé des songes,Roll,1286.88,,DWEL-VP-935-96,SKIP:already-on-shopify
+2026-07-11T08:20:09,VP_935,Kosa silk métal,Royal apparat,Roll,1286.88,,DWEL-VP-935-93,SKIP:already-on-shopify
+2026-07-11T08:20:09,VP_935,Kosa silk métal,Digne d'une princesse,Roll,1286.88,,DWEL-VP-935-95,SKIP:already-on-shopify
+2026-07-11T08:20:09,VP_948,Movida métal,Glisser sur sa peau,Roll,892.31,7881308569651,DWEL-VP-948-93,OK
+2026-07-11T08:20:12,VP_948,Movida métal,Richesse intérieure,Roll,892.31,7881308602419,DWEL-VP-948-91,OK
+2026-07-11T08:20:14,VP_950,Seta métal,Son coeur contre mon coeur,Roll,1286.88,7881308635187,DWEL-VP-950-93,OK
+2026-07-11T08:20:17,VP_950,Seta métal,Rayonner avec éclat,Roll,1286.88,7881308667955,DWEL-VP-950-90,OK
+2026-07-11T08:20:19,VP_950,Seta métal,Ondes sensuelles,Roll,1286.88,7881308700723,DWEL-VP-950-91,OK
+2026-07-11T08:20:21,VP_951,Big Croco métal,golden brown,Roll,892.31,7881308733491,DWEL-VP-951-92,OK
diff --git a/exec/ws3b_bucketB.py b/exec/ws3b_bucketB.py
new file mode 100644
index 0000000..8a518ac
--- /dev/null
+++ b/exec/ws3b_bucketB.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+"""WS3b - Bucket B recovery: build the staging colorways whose pattern did NOT
+exact-match the catalog only because of ACCENT / CASE / SPACING drift
+(e.g. 'Eclat'->'Éclat', 'Panthere'->'Panthère', 'Colosse frutier'->'fruitier').
+
+Strategy (cheaper than re-scraping, per FOLLOWUP_scrape_403.md):
+ 1. Normalize both sides: casefold + strip accents (NFKD) + collapse whitespace.
+ 2. Exact-normalized match -> HIGH confidence -> build.
+ 3. difflib close match >=0.90 -> FUZZY (single-char typos) -> build, logged FUZZY.
+ 4. else -> leave for the /elitis scrape (Bucket A / D).
+
+Reuses ws3_drafts.create_one (same DRAFT + Needs-Image + variant recipe). $0.
+Steve-approved via AskUserQuestion 2026-07-11 (option C)."""
+import sys, os, csv, json, unicodedata, difflib
+
+sys.path.insert(0, os.path.dirname(__file__))
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "lib"))
+import ws3_drafts as W
+
+ROOT = os.path.join(os.path.dirname(__file__), "..")
+CAT = os.path.join(ROOT, "elitis_catalog.json")
+SJ = os.path.join(ROOT, "OUT_577_staging_join.csv")
+
+
+def norm(s):
+ s = unicodedata.normalize("NFKD", s or "")
+ s = "".join(c for c in s if not unicodedata.combining(c))
+ return " ".join(s.casefold().split())
+
+
+def build_bucketB():
+ cat = json.load(open(CAT))
+ sj = list(csv.DictReader(open(SJ)))
+ exact_pats = set(e["pattern_name"] for e in cat)
+ cat_by_norm = {}
+ for e in cat:
+ cat_by_norm.setdefault(norm(e["pattern_name"]), []).append(e)
+ norm_keys = list(cat_by_norm.keys())
+
+ high, fuzzy, miss = [], [], []
+ for r in sj:
+ pat = r["pattern"]
+ if pat in exact_pats:
+ continue # already handled by WS3 exact pass
+ nk = norm(pat)
+ ents = cat_by_norm.get(nk)
+ conf = "HIGH"
+ if not ents:
+ close = difflib.get_close_matches(nk, norm_keys, n=1, cutoff=0.90)
+ if close:
+ ents = cat_by_norm[close[0]]
+ conf = f"FUZZY:{difflib.SequenceMatcher(None, nk, close[0]).ratio():.2f}:{close[0]}"
+ else:
+ miss.append(pat)
+ continue
+ for e in ents:
+ if e.get("on_shopify"):
+ continue
+ tgt = {
+ "list_sku": r["list_sku"], "pattern": e["pattern_name"], "unit": r["unit"],
+ "retail": r["retail"], "mfr_sku": e["mfr_sku"], "color": e["color_name"],
+ "width": e["width"], "product_type": e["product_type"], "_conf": conf,
+ "_list_pat": pat,
+ }
+ (high if conf == "HIGH" else fuzzy).append(tgt)
+ return high, fuzzy, sorted(set(miss))
+
+
+if __name__ == "__main__":
+ high, fuzzy, miss = build_bucketB()
+ if "--report" in sys.argv or "--build" not in sys.argv:
+ print(f"Bucket B recovery (accent/case/spacing + fuzzy):")
+ print(f" HIGH (normalized exact) colorways: {len(high)}")
+ print(f" FUZZY (>=0.90 close) colorways: {len(fuzzy)}")
+ print(f" still-missing distinct patterns : {len(miss)} -> route to scrape")
+ print("\n -- FUZZY joins (eyeball these) --")
+ for t in fuzzy:
+ print(f" {t['_list_pat']!r} -> {t['pattern']!r} [{t['_conf']}] {t['mfr_sku']}")
+ print("\n -- still missing (Bucket A / D scrape) --")
+ for p in miss:
+ print(f" {p!r}")
+ sys.exit(0)
+
+ # --build
+ seen = W.seen_dw()
+ n_ok = n_skip = n_fail = 0
+ allt = high + fuzzy
+ for i, t in enumerate(allt):
+ if W.dw_sku(t["mfr_sku"]) in seen:
+ n_skip += 1
+ continue
+ st = W.create_one(t)
+ if st == "OK":
+ n_ok += 1
+ elif st.startswith("FAIL"):
+ n_fail += 1
+ else:
+ n_skip += 1
+ if (i + 1) % 20 == 0:
+ print(f" ...{i+1}/{len(allt)} ok={n_ok} skip={n_skip} fail={n_fail}")
+ print(f"WS3b DONE: ok={n_ok} skip={n_skip} fail={n_fail} "
+ f"(high={len(high)} fuzzy={len(fuzzy)} still-missing={len(miss)})")
← b42aea3 WS4 cleanup: retitle VP-963->Osorno, archive Raffia/Frivole
·
back to Elitis Price 2026
·
auto-save: 2026-07-11T08:42:28 (3 files) — exec/__pycache__/ ff985e8 →