← back to Dw Yolo Loop
Kravet-family cost drive: MAP resolver + parallel per-brand currency-check
f145eb5d28cc3b1da28aaa7c98094e3941a11a8f · 2026-06-15 08:48:24 -0700 · Steve Abrams
- Resolved MAP for 2,439 active Kravet-family from kravet_master_price (whls x1.5, verified)
- 4,618 need cost sourcing (Kravet 2043, Clarke&Clarke 1157, Brunschwig 917, ...)
- currency-check.js: per-brand exact-sku Algolia verifier (reuses kravet-disco-algolia-verify
logic); CURRENT / DISCO_COLOR / DISCO_PATTERN / AMW_SKIP; read-only, per-brand CSV
- run-parallel.sh: fans all 14 brands concurrently (needs AKEY)
- blocked: AKEY (Algolia key) empty in env
Files touched
M .gitignoreA gmc-425-count.jsA gmc-425-result.jsonA gmc-gate-probe.jsA scripts/kravet-cost/currency-check.jsA scripts/kravet-cost/run-parallel.sh
Diff
commit f145eb5d28cc3b1da28aaa7c98094e3941a11a8f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 15 08:48:24 2026 -0700
Kravet-family cost drive: MAP resolver + parallel per-brand currency-check
- Resolved MAP for 2,439 active Kravet-family from kravet_master_price (whls x1.5, verified)
- 4,618 need cost sourcing (Kravet 2043, Clarke&Clarke 1157, Brunschwig 917, ...)
- currency-check.js: per-brand exact-sku Algolia verifier (reuses kravet-disco-algolia-verify
logic); CURRENT / DISCO_COLOR / DISCO_PATTERN / AMW_SKIP; read-only, per-brand CSV
- run-parallel.sh: fans all 14 brands concurrently (needs AKEY)
- blocked: AKEY (Algolia key) empty in env
---
.gitignore | 1 +
gmc-425-count.js | 135 ++++
gmc-425-result.json | 1227 +++++++++++++++++++++++++++++++++
gmc-gate-probe.js | 14 +
scripts/kravet-cost/currency-check.js | 71 ++
scripts/kravet-cost/run-parallel.sh | 32 +
6 files changed, 1480 insertions(+)
diff --git a/.gitignore b/.gitignore
index 4e2ab7e..c5c86d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ data/google-feed/run-full.*
data/google-feed/*.csv
data/google-feed/*.md
data/google-feed/report.json
+data/kravet-cost/
diff --git a/gmc-425-count.js b/gmc-425-count.js
new file mode 100644
index 0000000..f66140f
--- /dev/null
+++ b/gmc-425-count.js
@@ -0,0 +1,135 @@
+#!/usr/bin/env node
+// READ-ONLY GMC $4.25 feed-price blast-radius count. No writes anywhere except local result json.
+const fs = require('fs');
+
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const VERSION = '2024-10';
+const ENV = fs.readFileSync('/Users/stevestudio2/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1];
+if (!TOKEN) { console.error('no token'); process.exit(1); }
+
+const URL = `https://${SHOP}/admin/api/${VERSION}/graphql.json`;
+const SAMPLE_PRICE = 4.25;
+
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function gql(query, variables) {
+ for (let attempt = 0; attempt < 7; attempt++) {
+ let res;
+ try {
+ res = await fetch(URL, {
+ method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }),
+ });
+ } catch (e) { await sleep(2000 * (attempt + 1)); continue; }
+ if (res.status === 429) { await sleep(2000 * (attempt + 1)); continue; }
+ const json = await res.json();
+ if (json.errors) {
+ const throttled = JSON.stringify(json.errors).includes('THROTTLED');
+ if (throttled) { await sleep(2000 * (attempt + 1)); continue; }
+ throw new Error(JSON.stringify(json.errors));
+ }
+ const avail = json.extensions?.cost?.throttleStatus?.currentlyAvailable ?? 4000;
+ if (avail < 400) await sleep(1500);
+ return json.data;
+ }
+ throw new Error('exhausted retries');
+}
+
+const QUERY = `
+query($cursor: String) {
+ products(first: 100, after: $cursor, query: "status:active") {
+ pageInfo { hasNextPage endCursor }
+ nodes {
+ title
+ vendor
+ hasOnlyDefaultVariant
+ variants(first: 60) {
+ nodes { title price }
+ }
+ }
+ }
+}`;
+
+(async () => {
+ let cursor = null;
+ let total = 0, pages = 0;
+
+ let bucketA = 0; // sample-only
+ let bucketB = 0; // multi-variant, min/feed is 4.25 but a real roll exists
+ let bucketC = 0; // real sellable feed price
+ let bucketD = 0; // no price / $0 only
+
+ let feedMin425 = 0;
+ let feedFirst425 = 0;
+
+ const vendorCount = {}; // a+b by MIN model
+ const vendorCountFirst = {}; // first-variant == 4.25
+
+ const is425 = (x) => Math.abs(x - SAMPLE_PRICE) < 0.005;
+ const startedAt = Date.now();
+
+ while (true) {
+ const data = await gql(QUERY, { cursor });
+ const conn = data.products;
+ pages++;
+ for (const p of conn.nodes) {
+ total++;
+ const variants = p.variants.nodes || [];
+ const prices = variants.map(v => parseFloat(v.price)).filter(x => !isNaN(x));
+ if (prices.length === 0) { bucketD++; continue; }
+
+ const firstPrice = prices[0];
+ const minPrice = Math.min(...prices);
+ const maxPrice = Math.max(...prices);
+ const hasRealRoll = prices.some(x => x > SAMPLE_PRICE + 0.005);
+ const minIs425 = is425(minPrice);
+ const firstIs425 = is425(firstPrice);
+
+ if (minIs425) feedMin425++;
+ if (firstIs425) feedFirst425++;
+
+ if (prices.every(x => x === 0)) {
+ bucketD++;
+ } else if (is425(maxPrice) && !hasRealRoll) {
+ bucketA++;
+ vendorCount[p.vendor] = (vendorCount[p.vendor] || 0) + 1;
+ } else if (minIs425 && hasRealRoll) {
+ bucketB++;
+ vendorCount[p.vendor] = (vendorCount[p.vendor] || 0) + 1;
+ } else if (minPrice === 0 && hasRealRoll) {
+ bucketD++;
+ } else {
+ bucketC++;
+ }
+
+ if (firstIs425) vendorCountFirst[p.vendor] = (vendorCountFirst[p.vendor] || 0) + 1;
+ }
+ if (!conn.pageInfo.hasNextPage) break;
+ cursor = conn.pageInfo.endCursor;
+ if (pages % 10 === 0) {
+ process.stderr.write(` ...${total} products, ${pages} pages, ${((Date.now()-startedAt)/1000).toFixed(0)}s\n`);
+ }
+ }
+
+ const result = {
+ generated_at: new Date().toISOString(),
+ total_active: total,
+ pages,
+ elapsed_s: ((Date.now() - startedAt) / 1000).toFixed(1),
+ buckets_min_model: {
+ a_sample_only: bucketA,
+ b_has_real_roll_but_feeds_425: bucketB,
+ c_real_feed_price_ok: bucketC,
+ d_no_price_or_zero: bucketD,
+ },
+ feed_425_min_model: feedMin425,
+ feed_425_first_model: feedFirst425,
+ would_feed_425_a_plus_b: bucketA + bucketB,
+ vendor_breakdown_a_plus_b: Object.entries(vendorCount).sort((x,y)=>y[1]-x[1]),
+ vendor_breakdown_first_model: Object.entries(vendorCountFirst).sort((x,y)=>y[1]-x[1]),
+ };
+ console.log(JSON.stringify(result, null, 2));
+ fs.writeFileSync('/Users/stevestudio2/Projects/designerwallcoverings/gmc-425-result.json', JSON.stringify(result, null, 2));
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });
diff --git a/gmc-425-result.json b/gmc-425-result.json
new file mode 100644
index 0000000..5a6bb1f
--- /dev/null
+++ b/gmc-425-result.json
@@ -0,0 +1,1227 @@
+{
+ "generated_at": "2026-06-15T15:03:48.894Z",
+ "total_active": 74656,
+ "pages": 747,
+ "elapsed_s": "242.1",
+ "buckets_min_model": {
+ "a_sample_only": 35835,
+ "b_has_real_roll_but_feeds_425": 26077,
+ "c_real_feed_price_ok": 12632,
+ "d_no_price_or_zero": 112
+ },
+ "feed_425_min_model": 61879,
+ "feed_425_first_model": 59119,
+ "would_feed_425_a_plus_b": 61912,
+ "vendor_breakdown_a_plus_b": [
+ [
+ "Phillipe Romano",
+ 9183
+ ],
+ [
+ "Rebel Walls",
+ 3618
+ ],
+ [
+ "Hollywood Wallcoverings",
+ 3156
+ ],
+ [
+ "Malibu Wallpaper",
+ 2832
+ ],
+ [
+ "Phillip Jeffries",
+ 2473
+ ],
+ [
+ "Kravet",
+ 2464
+ ],
+ [
+ "Koroseal",
+ 2353
+ ],
+ [
+ "Schumacher",
+ 2118
+ ],
+ [
+ "China Seas",
+ 1788
+ ],
+ [
+ "Los Angeles Fabrics",
+ 1634
+ ],
+ [
+ "Brunschwig & Fils",
+ 1600
+ ],
+ [
+ "Thibaut",
+ 1595
+ ],
+ [
+ "Jeffrey Stevens",
+ 1501
+ ],
+ [
+ "Designer Wallcoverings",
+ 1481
+ ],
+ [
+ "Coordonné",
+ 1319
+ ],
+ [
+ "Mind the Gap",
+ 1133
+ ],
+ [
+ "Osborne & Little",
+ 1010
+ ],
+ [
+ "Scalamandre Wallpaper",
+ 962
+ ],
+ [
+ "DW Bespoke Studio",
+ 957
+ ],
+ [
+ "Lee Jofa",
+ 957
+ ],
+ [
+ "Wolf Gordon",
+ 935
+ ],
+ [
+ "Anna French",
+ 924
+ ],
+ [
+ "Cole & Son",
+ 885
+ ],
+ [
+ "Designers Guild",
+ 850
+ ],
+ [
+ "Versa Designed Surfaces",
+ 842
+ ],
+ [
+ "Sandberg",
+ 764
+ ],
+ [
+ "Surface Stick",
+ 740
+ ],
+ [
+ "Maya Romanoff",
+ 624
+ ],
+ [
+ "Harlequin",
+ 583
+ ],
+ [
+ "Arte International",
+ 558
+ ],
+ [
+ "Nina Campbell",
+ 551
+ ],
+ [
+ "LA Walls",
+ 529
+ ],
+ [
+ "William Morris",
+ 526
+ ],
+ [
+ "Newmor Wallcoverings",
+ 515
+ ],
+ [
+ "Versace",
+ 513
+ ],
+ [
+ "Romo",
+ 503
+ ],
+ [
+ "British Walls",
+ 449
+ ],
+ [
+ "Graham & Brown",
+ 439
+ ],
+ [
+ "AS Creation",
+ 431
+ ],
+ [
+ "Glitter Walls",
+ 402
+ ],
+ [
+ "Clarke And Clarke",
+ 399
+ ],
+ [
+ "Architectural Fabrics",
+ 328
+ ],
+ [
+ "Gaston Y Daniela",
+ 273
+ ],
+ [
+ "GP & J Baker",
+ 263
+ ],
+ [
+ "Fentucci",
+ 254
+ ],
+ [
+ "Elitis",
+ 244
+ ],
+ [
+ "Innovations USA",
+ 203
+ ],
+ [
+ "Christian Lacroix Europe",
+ 170
+ ],
+ [
+ "Donghia",
+ 159
+ ],
+ [
+ "Brand McKenzie",
+ 158
+ ],
+ [
+ "Novasuede",
+ 157
+ ],
+ [
+ "Roberto Cavalli Wallpaper",
+ 149
+ ],
+ [
+ "Ralph Lauren",
+ 141
+ ],
+ [
+ "Mulberry",
+ 128
+ ],
+ [
+ "Marburg",
+ 119
+ ],
+ [
+ "Laura Ashley Wallpaper",
+ 110
+ ],
+ [
+ "Primo Leathers",
+ 107
+ ],
+ [
+ "Ultrasuede",
+ 107
+ ],
+ [
+ "Mid Century Modern",
+ 106
+ ],
+ [
+ "Hospitality Wallcoverings",
+ 96
+ ],
+ [
+ "Traditional Whimsy",
+ 89
+ ],
+ [
+ "Nobilis",
+ 77
+ ],
+ [
+ "LEED Walls",
+ 73
+ ],
+ [
+ "Daisy Bennett",
+ 69
+ ],
+ [
+ "Threads",
+ 63
+ ],
+ [
+ "Missoni Wallpaper",
+ 58
+ ],
+ [
+ "Glambeads by DW",
+ 58
+ ],
+ [
+ "Luxury Murals",
+ 56
+ ],
+ [
+ "MC Escher Wallpaper",
+ 50
+ ],
+ [
+ "Madagascar Walls",
+ 49
+ ],
+ [
+ "Graduate Collection",
+ 49
+ ],
+ [
+ "Lincrusta",
+ 47
+ ],
+ [
+ "Retro Walls",
+ 46
+ ],
+ [
+ "Hollywood Acoustical",
+ 46
+ ],
+ [
+ "Sancar",
+ 42
+ ],
+ [
+ "Hand Crafted",
+ 40
+ ],
+ [
+ "Apartment Wallpaper",
+ 39
+ ],
+ [
+ "Glass Beaded",
+ 37
+ ],
+ [
+ "Tres Tintas",
+ 36
+ ],
+ [
+ "Marimekko Exclusive",
+ 35
+ ],
+ [
+ "DW Home",
+ 35
+ ],
+ [
+ "Pixels",
+ 35
+ ],
+ [
+ "Graduate Collection UK",
+ 34
+ ],
+ [
+ "Boråstapeter",
+ 31
+ ],
+ [
+ "Phyllis Morris",
+ 28
+ ],
+ [
+ "Dolce & Gabbana",
+ 27
+ ],
+ [
+ "Natural Paint",
+ 26
+ ],
+ [
+ "Franquemont London",
+ 26
+ ],
+ [
+ "Zeuxis Parrhasius Europe",
+ 21
+ ],
+ [
+ "Steve Abrams Studios",
+ 16
+ ],
+ [
+ "Emiliana Parati",
+ 15
+ ],
+ [
+ "Wallpaper NYC",
+ 12
+ ],
+ [
+ "Showroom Line",
+ 11
+ ],
+ [
+ "Witch & Watchman",
+ 10
+ ],
+ [
+ "Hygge & West",
+ 10
+ ],
+ [
+ "SUGARBOO",
+ 10
+ ],
+ [
+ "NLXL",
+ 9
+ ],
+ [
+ "Backdrop",
+ 9
+ ],
+ [
+ "Designtex",
+ 9
+ ],
+ [
+ "Contrado",
+ 6
+ ],
+ [
+ "Zeuxis Parrhasius at DW",
+ 5
+ ],
+ [
+ "Andrew Martin",
+ 5
+ ],
+ [
+ "Vahallan",
+ 4
+ ],
+ [
+ "Zoffany",
+ 4
+ ],
+ [
+ "Architectural Wallcoverings",
+ 4
+ ],
+ [
+ "Baker Lifestyle",
+ 4
+ ],
+ [
+ "Pierre Frey",
+ 3
+ ],
+ [
+ "Holland and Sherry",
+ 3
+ ],
+ [
+ "Peg Norriss",
+ 3
+ ],
+ [
+ "Sacha Walckhoff",
+ 3
+ ],
+ [
+ "Fabricut",
+ 3
+ ],
+ [
+ "WallQuest",
+ 3
+ ],
+ [
+ "Zinc Textile",
+ 3
+ ],
+ [
+ "Villa Nova",
+ 3
+ ],
+ [
+ "Black Edition",
+ 3
+ ],
+ [
+ "Caroline Cecil Textiles",
+ 3
+ ],
+ [
+ "Clarke and Clarke",
+ 3
+ ],
+ [
+ "Erika Wakerly",
+ 2
+ ],
+ [
+ "Clarence House",
+ 2
+ ],
+ [
+ "Winfield Thybony",
+ 2
+ ],
+ [
+ "Patty Madden",
+ 2
+ ],
+ [
+ "Atomic 50 Ceilings",
+ 2
+ ],
+ [
+ "Kirkby Design",
+ 2
+ ],
+ [
+ "Kravet Couture",
+ 2
+ ],
+ [
+ "Kravet Design",
+ 2
+ ],
+ [
+ "PR Faux Leather",
+ 1
+ ],
+ [
+ "Malibu Walls",
+ 1
+ ],
+ [
+ "Printy6",
+ 1
+ ],
+ [
+ "French Market",
+ 1
+ ],
+ [
+ "OLIVIA AND POPP",
+ 1
+ ],
+ [
+ "Edge Wallcovering",
+ 1
+ ],
+ [
+ "Morris and Company",
+ 1
+ ],
+ [
+ "Steven Abrams Photography",
+ 1
+ ],
+ [
+ "DW Exclusive Wallpaper",
+ 1
+ ],
+ [
+ "Catchii Netherlands Europe",
+ 1
+ ],
+ [
+ "Celerie Kemble",
+ 1
+ ],
+ [
+ "Happy Menocal",
+ 1
+ ],
+ [
+ "Sara Bergqvist",
+ 1
+ ],
+ [
+ "Rebel Studio",
+ 1
+ ],
+ [
+ "Milton & King",
+ 1
+ ],
+ [
+ "Kelly Wearstler",
+ 1
+ ],
+ [
+ "Linherr Hollingsworth",
+ 1
+ ],
+ [
+ "Sarah Bartholomew",
+ 1
+ ],
+ [
+ "Farrow & Ball",
+ 1
+ ],
+ [
+ "Rebecca Moses",
+ 1
+ ],
+ [
+ "Ananbo",
+ 1
+ ],
+ [
+ "Studio Ditte",
+ 1
+ ],
+ [
+ "A.S. Création",
+ 1
+ ],
+ [
+ "Dedar",
+ 1
+ ],
+ [
+ "Mark Alexander",
+ 1
+ ],
+ [
+ "Dupenny",
+ 1
+ ],
+ [
+ "DWHD",
+ 1
+ ],
+ [
+ "G P & J Baker",
+ 1
+ ],
+ [
+ "Lee Jofa Modern",
+ 1
+ ],
+ [
+ "Scion",
+ 1
+ ]
+ ],
+ "vendor_breakdown_first_model": [
+ [
+ "Phillipe Romano",
+ 9130
+ ],
+ [
+ "Hollywood Wallcoverings",
+ 3156
+ ],
+ [
+ "Malibu Wallpaper",
+ 2832
+ ],
+ [
+ "Rebel Walls",
+ 2499
+ ],
+ [
+ "Phillip Jeffries",
+ 2472
+ ],
+ [
+ "Kravet",
+ 2464
+ ],
+ [
+ "Koroseal",
+ 2353
+ ],
+ [
+ "China Seas",
+ 1788
+ ],
+ [
+ "Los Angeles Fabrics",
+ 1634
+ ],
+ [
+ "Brunschwig & Fils",
+ 1597
+ ],
+ [
+ "Thibaut",
+ 1566
+ ],
+ [
+ "Jeffrey Stevens",
+ 1482
+ ],
+ [
+ "Designer Wallcoverings",
+ 1451
+ ],
+ [
+ "Schumacher",
+ 1393
+ ],
+ [
+ "Coordonné",
+ 1319
+ ],
+ [
+ "Mind the Gap",
+ 1133
+ ],
+ [
+ "Osborne & Little",
+ 1010
+ ],
+ [
+ "Scalamandre Wallpaper",
+ 962
+ ],
+ [
+ "Lee Jofa",
+ 956
+ ],
+ [
+ "Wolf Gordon",
+ 935
+ ],
+ [
+ "Anna French",
+ 924
+ ],
+ [
+ "Cole & Son",
+ 853
+ ],
+ [
+ "Designers Guild",
+ 850
+ ],
+ [
+ "Versa Designed Surfaces",
+ 842
+ ],
+ [
+ "DW Bespoke Studio",
+ 768
+ ],
+ [
+ "Sandberg",
+ 764
+ ],
+ [
+ "Surface Stick",
+ 740
+ ],
+ [
+ "Maya Romanoff",
+ 624
+ ],
+ [
+ "Harlequin",
+ 583
+ ],
+ [
+ "Arte International",
+ 558
+ ],
+ [
+ "Nina Campbell",
+ 551
+ ],
+ [
+ "LA Walls",
+ 529
+ ],
+ [
+ "William Morris",
+ 526
+ ],
+ [
+ "Newmor Wallcoverings",
+ 515
+ ],
+ [
+ "Versace",
+ 513
+ ],
+ [
+ "British Walls",
+ 449
+ ],
+ [
+ "Graham & Brown",
+ 439
+ ],
+ [
+ "AS Creation",
+ 431
+ ],
+ [
+ "Glitter Walls",
+ 402
+ ],
+ [
+ "Clarke And Clarke",
+ 399
+ ],
+ [
+ "Architectural Fabrics",
+ 328
+ ],
+ [
+ "Gaston Y Daniela",
+ 272
+ ],
+ [
+ "GP & J Baker",
+ 263
+ ],
+ [
+ "Fentucci",
+ 253
+ ],
+ [
+ "Elitis",
+ 244
+ ],
+ [
+ "Innovations USA",
+ 203
+ ],
+ [
+ "Christian Lacroix Europe",
+ 170
+ ],
+ [
+ "Donghia",
+ 159
+ ],
+ [
+ "Brand McKenzie",
+ 158
+ ],
+ [
+ "Novasuede",
+ 157
+ ],
+ [
+ "Roberto Cavalli Wallpaper",
+ 149
+ ],
+ [
+ "Ralph Lauren",
+ 141
+ ],
+ [
+ "Mulberry",
+ 127
+ ],
+ [
+ "Marburg",
+ 119
+ ],
+ [
+ "Laura Ashley Wallpaper",
+ 110
+ ],
+ [
+ "Primo Leathers",
+ 107
+ ],
+ [
+ "Ultrasuede",
+ 107
+ ],
+ [
+ "Mid Century Modern",
+ 106
+ ],
+ [
+ "Hospitality Wallcoverings",
+ 96
+ ],
+ [
+ "Traditional Whimsy",
+ 89
+ ],
+ [
+ "Nobilis",
+ 77
+ ],
+ [
+ "LEED Walls",
+ 73
+ ],
+ [
+ "Daisy Bennett",
+ 69
+ ],
+ [
+ "Threads",
+ 62
+ ],
+ [
+ "Missoni Wallpaper",
+ 58
+ ],
+ [
+ "Glambeads by DW",
+ 58
+ ],
+ [
+ "Luxury Murals",
+ 56
+ ],
+ [
+ "MC Escher Wallpaper",
+ 50
+ ],
+ [
+ "Madagascar Walls",
+ 49
+ ],
+ [
+ "Graduate Collection",
+ 49
+ ],
+ [
+ "Lincrusta",
+ 47
+ ],
+ [
+ "Hollywood Acoustical",
+ 46
+ ],
+ [
+ "Retro Walls",
+ 43
+ ],
+ [
+ "Sancar",
+ 42
+ ],
+ [
+ "Hand Crafted",
+ 40
+ ],
+ [
+ "Apartment Wallpaper",
+ 39
+ ],
+ [
+ "Glass Beaded",
+ 37
+ ],
+ [
+ "Tres Tintas",
+ 36
+ ],
+ [
+ "Marimekko Exclusive",
+ 35
+ ],
+ [
+ "Graduate Collection UK",
+ 34
+ ],
+ [
+ "Boråstapeter",
+ 31
+ ],
+ [
+ "Phyllis Morris",
+ 28
+ ],
+ [
+ "Dolce & Gabbana",
+ 27
+ ],
+ [
+ "Natural Paint",
+ 26
+ ],
+ [
+ "Franquemont London",
+ 26
+ ],
+ [
+ "Zeuxis Parrhasius Europe",
+ 21
+ ],
+ [
+ "Emiliana Parati",
+ 15
+ ],
+ [
+ "Romo",
+ 13
+ ],
+ [
+ "Steve Abrams Studios",
+ 13
+ ],
+ [
+ "Wallpaper NYC",
+ 12
+ ],
+ [
+ "Showroom Line",
+ 11
+ ],
+ [
+ "Witch & Watchman",
+ 10
+ ],
+ [
+ "Hygge & West",
+ 10
+ ],
+ [
+ "SUGARBOO",
+ 10
+ ],
+ [
+ "NLXL",
+ 9
+ ],
+ [
+ "Backdrop",
+ 9
+ ],
+ [
+ "Pixels",
+ 6
+ ],
+ [
+ "Zeuxis Parrhasius at DW",
+ 5
+ ],
+ [
+ "Andrew Martin",
+ 5
+ ],
+ [
+ "Vahallan",
+ 4
+ ],
+ [
+ "Zoffany",
+ 4
+ ],
+ [
+ "Architectural Wallcoverings",
+ 4
+ ],
+ [
+ "DW Home",
+ 3
+ ],
+ [
+ "Pierre Frey",
+ 3
+ ],
+ [
+ "Holland and Sherry",
+ 3
+ ],
+ [
+ "Peg Norriss",
+ 3
+ ],
+ [
+ "Sacha Walckhoff",
+ 3
+ ],
+ [
+ "Fabricut",
+ 3
+ ],
+ [
+ "WallQuest",
+ 3
+ ],
+ [
+ "Zinc Textile",
+ 3
+ ],
+ [
+ "Villa Nova",
+ 3
+ ],
+ [
+ "Black Edition",
+ 3
+ ],
+ [
+ "Erika Wakerly",
+ 2
+ ],
+ [
+ "Clarence House",
+ 2
+ ],
+ [
+ "Patty Madden",
+ 2
+ ],
+ [
+ "Atomic 50 Ceilings",
+ 2
+ ],
+ [
+ "Kirkby Design",
+ 2
+ ],
+ [
+ "PR Faux Leather",
+ 1
+ ],
+ [
+ "Malibu Walls",
+ 1
+ ],
+ [
+ "French Market",
+ 1
+ ],
+ [
+ "OLIVIA AND POPP",
+ 1
+ ],
+ [
+ "Edge Wallcovering",
+ 1
+ ],
+ [
+ "Steven Abrams Photography",
+ 1
+ ],
+ [
+ "Contrado",
+ 1
+ ],
+ [
+ "DW Exclusive Wallpaper",
+ 1
+ ],
+ [
+ "Winfield Thybony",
+ 1
+ ],
+ [
+ "Catchii Netherlands Europe",
+ 1
+ ],
+ [
+ "Celerie Kemble",
+ 1
+ ],
+ [
+ "Happy Menocal",
+ 1
+ ],
+ [
+ "Sara Bergqvist",
+ 1
+ ],
+ [
+ "Rebel Studio",
+ 1
+ ],
+ [
+ "Milton & King",
+ 1
+ ],
+ [
+ "Kelly Wearstler",
+ 1
+ ],
+ [
+ "Linherr Hollingsworth",
+ 1
+ ],
+ [
+ "Sarah Bartholomew",
+ 1
+ ],
+ [
+ "Farrow & Ball",
+ 1
+ ],
+ [
+ "Rebecca Moses",
+ 1
+ ],
+ [
+ "Ananbo",
+ 1
+ ],
+ [
+ "Studio Ditte",
+ 1
+ ],
+ [
+ "A.S. Création",
+ 1
+ ],
+ [
+ "Dedar",
+ 1
+ ],
+ [
+ "Mark Alexander",
+ 1
+ ],
+ [
+ "Dupenny",
+ 1
+ ],
+ [
+ "DWHD",
+ 1
+ ],
+ [
+ "Baker Lifestyle",
+ 1
+ ],
+ [
+ "Kravet Couture",
+ 1
+ ],
+ [
+ "Kravet Design",
+ 1
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/gmc-gate-probe.js b/gmc-gate-probe.js
new file mode 100644
index 0000000..b6de185
--- /dev/null
+++ b/gmc-gate-probe.js
@@ -0,0 +1,14 @@
+const fs=require('fs');
+const SHOP='designer-laboratory-sandbox.myshopify.com',VERSION='2024-10';
+const ENV=fs.readFileSync('/Users/stevestudio2/Projects/secrets-manager/.env','utf8');
+const TOKEN=(ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1];
+const URL=`https://${SHOP}/admin/api/${VERSION}/graphql.json`;
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+async function gql(q,v){for(let a=0;a<7;a++){let res;try{res=await fetch(URL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});}catch(e){await sleep(2000*(a+1));continue;}if(res.status===429){await sleep(2000*(a+1));continue;}const j=await res.json();if(j.errors){if(JSON.stringify(j.errors).includes('THROTTLED')){await sleep(2000*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}const av=j.extensions?.cost?.throttleStatus?.currentlyAvailable??4000;if(av<400)await sleep(1500);return j.data;}throw new Error('retries');}
+const Q=`query($c:String){products(first:120,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor}nodes{featuredImage{url} widthMeta:metafield(namespace:"custom",key:"width"){value} widthMeta2:metafield(namespace:"specs",key:"width"){value}}}}`;
+(async()=>{let cursor=null,total=0,pages=0,noImg=0,hasImg=0,noWidth=0,hasWidth=0,imgAndWidth=0;
+while(true){const d=await gql(Q,{cursor});const c=d.products;pages++;
+for(const p of c.nodes){total++;const img=!!p.featuredImage?.url;const w=!!(p.widthMeta?.value||p.widthMeta2?.value);if(img)hasImg++;else noImg++;if(w)hasWidth++;else noWidth++;if(img&&w)imgAndWidth++;}
+if(!c.pageInfo.hasNextPage)break;cursor=c.pageInfo.endCursor;if(pages%15===0)process.stderr.write(` ...${total} ${pages}p\n`);}
+const out={total_active:total,pages,has_image:hasImg,no_image:noImg,has_width_metafield:hasWidth,no_width_metafield:noWidth,image_AND_width:imgAndWidth};
+console.log(JSON.stringify(out,null,2));fs.writeFileSync('/Users/stevestudio2/Projects/designerwallcoverings/gmc-gate-probe.json',JSON.stringify(out,null,2));})().catch(e=>{console.error('FATAL',e.message);process.exit(1);});
diff --git a/scripts/kravet-cost/currency-check.js b/scripts/kravet-cost/currency-check.js
new file mode 100644
index 0000000..a7de589
--- /dev/null
+++ b/scripts/kravet-cost/currency-check.js
@@ -0,0 +1,71 @@
+#!/usr/bin/env node
+/**
+ * currency-check.js — per-brand "is this mfr_sku still CURRENT at Kravet?" verifier.
+ * Reuses the proven exact-sku Algolia logic from kravet-disco-algolia-verify.js.
+ * READ-ONLY (Algolia index queries + a DB read). Writes a per-brand result CSV only —
+ * never archives/updates Shopify or the DB (archiving disco SKUs is a separate gated step).
+ *
+ * USAGE: AKEY=<algolia-key> node currency-check.js "<vendor brand>"
+ * Designed to run one brand per process so the whole family fans out in parallel
+ * (see run-parallel.sh). Output: data/kravet-cost/currency-<brand-slug>.csv
+ *
+ * A SKU is:
+ * CURRENT — exact sku present in the live Kravet US index
+ * DISCO_COLOR — pattern still indexed, this colorway gone
+ * DISCO_PATTERN — whole pattern absent (line itself is indexed elsewhere)
+ * AMW_SKIP — Andrew Martin (AMW*/AM1*) — not in this index, needs its own source
+ */
+const https = require('https');
+const fs = require('fs');
+const { execSync } = require('child_process');
+
+const APP = 'M9TBUM1WAE', IDX = 'kravet_production_kravet_us_products', KEY = process.env.AKEY;
+const BRAND = process.argv[2];
+if (!BRAND) { console.error('usage: AKEY=… node currency-check.js "<brand>"'); process.exit(2); }
+if (!KEY) { console.error('FATAL: AKEY (Algolia key) not set in env — cannot currency-check'); process.exit(3); }
+
+const slug = BRAND.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
+const OUT = `data/kravet-cost/currency-${slug}.csv`;
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+// pull this brand's ACTIVE mfr_skus from the mirror (read-only)
+const sql = `select distinct on (shopify_id) mfr_sku from shopify_products
+ where status='ACTIVE' and vendor='${BRAND.replace(/'/g, "''")}' and mfr_sku is not null and mfr_sku<>'';`;
+const rows = execSync(`psql -d dw_unified -tA -c "${sql}"`, { encoding: 'utf8' })
+ .trim().split('\n').map(s => s.trim()).filter(Boolean);
+
+function query(q) {
+ return new Promise(res => {
+ const body = JSON.stringify({ query: q, restrictSearchableAttributes: ['sku'], hitsPerPage: 200, attributesToRetrieve: ['sku'] });
+ const req = https.request(`https://${APP}-dsn.algolia.net/1/indexes/${IDX}/query`,
+ { method: 'POST', headers: { 'X-Algolia-Application-Id': APP, 'X-Algolia-API-Key': KEY, 'Content-Type': 'application/json' } },
+ r => { let b = ''; r.on('data', d => b += d); r.on('end', () => { try { res(JSON.parse(b)); } catch (e) { res({ hits: [], _err: 1 }); } }); });
+ req.on('error', () => res({ hits: [], _err: 1 }));
+ req.setTimeout(20000, () => { req.destroy(); res({ hits: [], _err: 1 }); });
+ req.write(body); req.end();
+ });
+}
+
+(async () => {
+ const patCache = {};
+ const out = [];
+ let current = 0, dColor = 0, dPat = 0, amw = 0, err = 0, i = 0;
+ for (const sku of rows) {
+ i++;
+ const pat = sku.split('.')[0];
+ if (/^AMW|^AM1/i.test(pat)) { out.push([sku, 'AMW_SKIP']); amw++; continue; }
+ if (!(pat in patCache)) {
+ const r = await query(pat);
+ if (r._err) { out.push([sku, 'ERROR']); err++; continue; }
+ patCache[pat] = new Set((r.hits || []).map(h => (h.sku || '').toUpperCase()).filter(s => s.startsWith(pat.toUpperCase())));
+ await sleep(120);
+ }
+ const set = patCache[pat];
+ if (set.has(sku.toUpperCase())) { out.push([sku, 'CURRENT']); current++; }
+ else if (set.size > 0) { out.push([sku, 'DISCO_COLOR']); dColor++; }
+ else { out.push([sku, 'DISCO_PATTERN']); dPat++; }
+ if (i % 100 === 0) process.stderr.write(` [${BRAND}] ${i}/${rows.length} cur:${current} discoC:${dColor} discoP:${dPat}\n`);
+ }
+ fs.writeFileSync(OUT, 'mfr_sku,status\n' + out.map(r => r.join(',')).join('\n') + '\n');
+ console.log(`[${BRAND}] total:${rows.length} CURRENT:${current} DISCO_COLOR:${dColor} DISCO_PATTERN:${dPat} AMW:${amw} ERR:${err} -> ${OUT}`);
+})();
diff --git a/scripts/kravet-cost/run-parallel.sh b/scripts/kravet-cost/run-parallel.sh
new file mode 100644
index 0000000..6439228
--- /dev/null
+++ b/scripts/kravet-cost/run-parallel.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+# run-parallel.sh — fan the Kravet-family currency-check out, one process per brand,
+# all brands concurrent. Needs AKEY (Algolia key) in the environment.
+# AKEY=<key> bash scripts/kravet-cost/run-parallel.sh
+# Read-only: each worker only queries Algolia + reads the mirror, writes a per-brand CSV.
+set -uo pipefail
+cd "$(dirname "$0")/../.."
+[ -z "${AKEY:-}" ] && { echo "FATAL: export AKEY=<algolia-key> first"; exit 1; }
+mkdir -p data/kravet-cost
+
+BRANDS=(
+ "Kravet" "Brunschwig & Fils" "Clarke And Clarke" "Cole & Son" "Lee Jofa"
+ "GP & J Baker" "Mulberry" "Threads" "Andrew Martin" "Baker Lifestyle"
+ "Kravet Design" "Kravet Couture" "Lee Jofa Modern" "G P & J Baker"
+)
+echo "fanning ${#BRANDS[@]} brands in parallel…"
+pids=()
+for b in "${BRANDS[@]}"; do
+ AKEY="$AKEY" node scripts/kravet-cost/currency-check.js "$b" >>"data/kravet-cost/run.log" 2>&1 &
+ pids+=($!)
+done
+# wait for all, report
+fail=0
+for p in "${pids[@]}"; do wait "$p" || fail=$((fail+1)); done
+echo "done — $fail worker(s) errored. per-brand CSVs in data/kravet-cost/, log: data/kravet-cost/run.log"
+echo "=== summary ==="
+for f in data/kravet-cost/currency-*.csv; do
+ [ -f "$f" ] || continue
+ tot=$(($(wc -l < "$f")-1))
+ cur=$(grep -c ',CURRENT$' "$f" 2>/dev/null || echo 0)
+ echo " $(basename "$f"): $tot skus, $cur current"
+done
\ No newline at end of file
← 90c7e9a Google feed: prep-actions (punch-list + unpublish-list) + ga
·
back to Dw Yolo Loop
·
Kravet cost campaign: 6,373/8,317 (77%) MAP-resolved from fr 2dae150 →