← back to Dw Yolo Loop
collection merge+301 map (c42): 28 empties->twin, 15 no-twin (some whole-vendor-empty)
4aeca4e332bfa613dbfa6ee61812dbcbc39387ec · 2026-06-16 15:31:59 -0700 · Steve Abrams
Read-only. Turns c41's 43 empties into a merge proposal. 28 have a populated twin
(notably 18 phillipe-romano-* sub-collections -> phillipe-romano-1; clean ones
china-seas-fabrics->china-seas, ralph-lauren-fabrics->ralph-lauren, gold-wallpaper
->gold-wallcoverings). ~6 twins are generic/cross-category (paul-montgomery-murals
->murals, kravet-contract-1->kravet, york->brewster-york) = VERIFY before 301.
15 no-twin: 3 leaks/gone (wallquest, nicolette-mayer x2, stacy-garcia) + a deeper
signal — Colefax&Fowler, Carlisle, 1838, Paul-Montgomery have ALL collections
empty (products dark/unpublished or mis-collected). All merges GATED + per-pair verify.
Files touched
A scripts/orphan-collections/build-merge-map.mjs
Diff
commit 4aeca4e332bfa613dbfa6ee61812dbcbc39387ec
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 16 15:31:59 2026 -0700
collection merge+301 map (c42): 28 empties->twin, 15 no-twin (some whole-vendor-empty)
Read-only. Turns c41's 43 empties into a merge proposal. 28 have a populated twin
(notably 18 phillipe-romano-* sub-collections -> phillipe-romano-1; clean ones
china-seas-fabrics->china-seas, ralph-lauren-fabrics->ralph-lauren, gold-wallpaper
->gold-wallcoverings). ~6 twins are generic/cross-category (paul-montgomery-murals
->murals, kravet-contract-1->kravet, york->brewster-york) = VERIFY before 301.
15 no-twin: 3 leaks/gone (wallquest, nicolette-mayer x2, stacy-garcia) + a deeper
signal — Colefax&Fowler, Carlisle, 1838, Paul-Montgomery have ALL collections
empty (products dark/unpublished or mis-collected). All merges GATED + per-pair verify.
---
scripts/orphan-collections/build-merge-map.mjs | 42 ++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/scripts/orphan-collections/build-merge-map.mjs b/scripts/orphan-collections/build-merge-map.mjs
new file mode 100644
index 0000000..5edccd8
--- /dev/null
+++ b/scripts/orphan-collections/build-merge-map.mjs
@@ -0,0 +1,42 @@
+// build-merge-map (c42) — READ-ONLY. Turns c41's 43 empty live collections into a
+// ready merge+301 map: for each empty collection, find its populated TWIN (same
+// vendor/normalized stem) so Steve can 301 the dead handle to the live one and
+// recover its SEO/link equity. Handle list from the feed (handles are reliable;
+// only products_count is stale). $0.
+import fs from 'node:fs';
+const BASE='https://www.designerwallcoverings.com';
+const c41=JSON.parse(fs.readFileSync(`${process.env.HOME}/.claude/yolo-queue/orphan-collections-LIVE-2026-06-16.json`,'utf8'));
+const OUT=`${process.env.HOME}/.claude/yolo-queue/collection-merge-map-2026-06-16.json`;
+const MD =`${process.env.HOME}/.claude/yolo-queue/collection-merge-map-2026-06-16.md`;
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+// pull all handles
+const handles=[];
+for(let p=1;p<=20;p++){ const r=await fetch(`${BASE}/collections.json?limit=250&page=${p}`,{signal:AbortSignal.timeout(15000)}); const j=await r.json(); if(!(j.collections||[]).length)break; handles.push(...j.collections.map(c=>c.handle)); await sleep(150); }
+const emptySet=new Set(c41.empty_list.map(c=>c.handle));
+const nearSet=new Set(c41.near_list.map(c=>c.handle));
+const populated=handles.filter(h=>!emptySet.has(h)&&!nearSet.has(h));
+// normalize a handle to a comparable stem
+const STOP=['wallcoverings','wallcovering','wallpaper','fabrics','fabric','designs','design','collection','co','and-company','company','all-products','all','products'];
+function stem(h){ let s=h.toLowerCase(); for(const w of STOP) s=s.replace(new RegExp(`(^|-)${w}(-|$)`,'g'),'-'); s=s.replace(/-\d+$/,'').replace(/[^a-z0-9]/g,''); return s; }
+const popByStem=new Map(); for(const h of populated){ const k=stem(h); if(!popByStem.has(k)) popByStem.set(k,[]); popByStem.get(k).push(h); }
+const map=[];
+for(const c of c41.empty_list){
+ const k=stem(c.handle);
+ let twins = popByStem.get(k) || [];
+ if(!twins.length){ // fallback: populated handle whose stem contains/!is contained by this stem
+ twins = populated.filter(h=>{ const ph=stem(h); return ph && k && (ph.includes(k)||k.includes(ph)) && Math.min(ph.length,k.length)>=4; }).slice(0,3);
+ }
+ map.push({ empty:c.handle, feed_count_stale:c.feed_count, stem:k, twin: twins[0]||null, twin_candidates:twins.slice(0,3) });
+}
+const withTwin=map.filter(m=>m.twin), noTwin=map.filter(m=>!m.twin);
+fs.writeFileSync(OUT, JSON.stringify({generated_at:new Date().toISOString(), total_empty:map.length, with_twin:withTwin.length, no_twin:noTwin.length, map},null,2));
+let md=`# Collection merge+301 map (c42) — ${new Date().toISOString().slice(0,16)}\n\n`;
+md+=`**READ-ONLY, \$0.** For c41's ${map.length} empty live collections: proposed merge target (populated twin) so each dead handle can be 301'd. ${withTwin.length} have a twin, ${noTwin.length} don't (hide/repopulate instead). VERIFY each pair before merging — all edits GATED.\n\n`;
+md+=`## ${withTwin.length} EMPTY → TWIN (301 candidates)\n| empty (dead) | → merge into (populated twin) | other candidates |\n|---|---|---|\n`;
+for(const m of withTwin) md+=`| ${m.empty} | **${m.twin}** | ${m.twin_candidates.slice(1).join(', ')||'—'} |\n`;
+md+=`\n## ${noTwin.length} EMPTY, no twin found (hide or repopulate)\n`;
+for(const m of noTwin) md+=`- ${m.empty} (feed said ${m.feed_count_stale})\n`;
+fs.writeFileSync(MD,md);
+console.log(`[merge-map] empties=${map.length} with_twin=${withTwin.length} no_twin=${noTwin.length}`);
+console.log('sample pairs:'); for(const m of withTwin.slice(0,10)) console.log(` ${m.empty} -> ${m.twin}`);
+console.log(`Report: ${MD}`);
← ba1a136 Kravet add-roll: harden width parser to extract leading numb
·
back to Dw Yolo Loop
·
harden add_roll_variants: price floor (>=$10, block $4.25 sa 8ff645a →