← back to Reid Witlin Onboarding
TK-11256: fix Reid Witlin/Architectural Fabrics onboarder dropping description on the floor
97382b28fe63b1f04fa413e71e79b287c20ab8b9 · 2026-09-04 11:42:30 -0700 · Steve Abrams
Root cause of dw-five-field-canary cadence-regression FAIL (1005 recent products,
2026-09-03 batch): build_batch2.py never SELECTed rwltd_catalog.description/
ai_description and never emitted a description field in the CSV; create2.py never
sent descriptionHtml to Shopify at all. Also: activate_batch.py had no
description-present gate before flipping DRAFT->ACTIVE, unlike every other DW
onboarder's go-live.mjs (which holds as draft on empty descriptionHtml).
Fixes (code-only, no data/Shopify writes fired):
- build_batch2.py: SELECT description, ai_description in both pool queries; rec_common()
now emits description (description || ai_description, may still be empty if the
scraper never captured either -- separate data gap, not this bug).
- create2.py: sends descriptionHtml when row['description'] is non-empty.
- activate_batch.py: holds any product with empty descriptionHtml as DRAFT (writes
held-no-description.json) instead of activating it -- protects future runs against
repeating this regression.
Note: the single-Sample-variant/quote-only shape is NOT a bug -- Steve explicitly
approved activating this batch in that shape (git a5b4a18), matching the precedent of
190 prior same-shape ACTIVE Reid Witlin products. Not touched.
Already-live 1,005 products still missing descriptions is a Shopify write -- gated,
see pending-approval memo.
Files touched
M activate_batch.pyM build_batch2.pyM create2.py
Diff
commit 97382b28fe63b1f04fa413e71e79b287c20ab8b9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 4 11:42:30 2026 -0700
TK-11256: fix Reid Witlin/Architectural Fabrics onboarder dropping description on the floor
Root cause of dw-five-field-canary cadence-regression FAIL (1005 recent products,
2026-09-03 batch): build_batch2.py never SELECTed rwltd_catalog.description/
ai_description and never emitted a description field in the CSV; create2.py never
sent descriptionHtml to Shopify at all. Also: activate_batch.py had no
description-present gate before flipping DRAFT->ACTIVE, unlike every other DW
onboarder's go-live.mjs (which holds as draft on empty descriptionHtml).
Fixes (code-only, no data/Shopify writes fired):
- build_batch2.py: SELECT description, ai_description in both pool queries; rec_common()
now emits description (description || ai_description, may still be empty if the
scraper never captured either -- separate data gap, not this bug).
- create2.py: sends descriptionHtml when row['description'] is non-empty.
- activate_batch.py: holds any product with empty descriptionHtml as DRAFT (writes
held-no-description.json) instead of activating it -- protects future runs against
repeating this regression.
Note: the single-Sample-variant/quote-only shape is NOT a bug -- Steve explicitly
approved activating this batch in that shape (git a5b4a18), matching the precedent of
190 prior same-shape ACTIVE Reid Witlin products. Not touched.
Already-live 1,005 products still missing descriptions is a Shopify write -- gated,
see pending-approval memo.
---
activate_batch.py | 17 ++++++++++++++++-
build_batch2.py | 11 +++++++++--
create2.py | 8 ++++++++
3 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/activate_batch.py b/activate_batch.py
index 01001db..c94633b 100644
--- a/activate_batch.py
+++ b/activate_batch.py
@@ -48,7 +48,7 @@ LIST_Q = """
query($cursor: String) {
products(first: 250, after: $cursor, query: "vendor:'Architectural Fabrics' status:draft") {
pageInfo { hasNextPage endCursor }
- nodes { id handle title }
+ nodes { id handle title descriptionHtml }
}
}"""
@@ -73,6 +73,21 @@ def fetch_all():
def main():
products = fetch_all()
+ # GATE (TK-11256, 2026-09-04): every other DW onboarder's go-live.mjs holds a product
+ # as DRAFT when descriptionHtml is empty (see maharam/osborne/knoll-onboard go-live.mjs
+ # "even if create-drafts shipped an empty descriptionHtml, this HOLDS the product as
+ # draft"). This script had no such check -- the 2026-09-03 1,005-item activation shipped
+ # every product with no description and tripped dw-five-field-canary. This does NOT
+ # touch anything already live; it only protects future runs of this script (e.g. the
+ # 275 rows still pending onboarding).
+ held_no_desc = [p for p in products
+ if not (p.get("descriptionHtml") or "").replace("<p>", "").replace("</p>", "").strip()]
+ if held_no_desc:
+ print(f"HOLDING {len(held_no_desc)}/{len(products)} as DRAFT (no descriptionHtml) -- "
+ f"see held-no-description.json")
+ json.dump([{"id": p["id"], "handle": p["handle"]} for p in held_no_desc],
+ open("held-no-description.json", "w"), indent=2)
+ products = [p for p in products if p not in held_no_desc]
if LIMIT:
products = products[:LIMIT]
print(f"{'DRY-RUN' if DRY_RUN else 'LIVE'}: {len(products)} products "
diff --git a/build_batch2.py b/build_batch2.py
index 4345caf..49cef7c 100644
--- a/build_batch2.py
+++ b/build_batch2.py
@@ -115,6 +115,13 @@ def rec_common(mfr, dw_sku, pat, town, colorway_disp, product_type_, img, conf,
"repeat": row.get("spec_repeat") or "",
"style": ", ".join(row["ai_styles"]) if isinstance(row.get("ai_styles"), list) else "",
"tags": " | ".join(build_tags(row, colorway_disp.lower().replace(' ', '-'), town)),
+ # FIX (TK-11256, 2026-09-04): rwltd_catalog.description/ai_description were being
+ # queried nowhere and dropped on the floor here, so create2.py never had a body_html
+ # to send -- every product this pipeline created shipped with NO description (the
+ # dw-five-field-canary cadence-regression FAIL on the 1005-item 2026-09-03 batch).
+ # Prefer human-authored description, fall back to ai_description. May still be empty
+ # for rows where the scraper never captured either -- that's a data gap, not this bug.
+ "description": (row.get("description") or row.get("ai_description") or "").strip(),
"pool": "A-reconfirmed-old" if row.get("_pool") == "A" else "B-new-gap",
}
@@ -126,7 +133,7 @@ def main():
SELECT mfr_sku, dw_sku, pattern_name, color_name, virginia_town, spec_type,
spec_width, spec_actual_width, spec_content, spec_repeat, spec_flamecode,
spec_origin, spec_cleaning, spec_finish, spec_abrasion,
- gallery_images, ai_tags, ai_styles
+ gallery_images, ai_tags, ai_styles, description, ai_description
FROM rwltd_catalog
WHERE dw_sku IS NOT NULL AND dw_sku <> ''
AND (shopify_product_id IS NULL OR shopify_product_id='')
@@ -155,7 +162,7 @@ def main():
SELECT mfr_sku, pattern_name, color_name, virginia_town, spec_type,
spec_width, spec_actual_width, spec_content, spec_repeat, spec_flamecode,
spec_origin, spec_cleaning, spec_finish, spec_abrasion,
- gallery_images, ai_tags, ai_styles, image_url
+ gallery_images, ai_tags, ai_styles, image_url, description, ai_description
FROM rwltd_catalog
WHERE (dw_sku IS NULL OR dw_sku='')
AND (shopify_product_id IS NULL OR shopify_product_id='')
diff --git a/create2.py b/create2.py
index 1c66c6c..ea9ba77 100644
--- a/create2.py
+++ b/create2.py
@@ -89,6 +89,12 @@ def build_input(row):
"productType": row["product_type"],
"status": "DRAFT",
"tags": [t.strip() for t in row["tags"].split("|") if t.strip()],
+ # FIX (TK-11256, 2026-09-04): body_html was never sent -- this is the #2 cause
+ # (with the sample-only-variant shape being by-design/quote-only) of the
+ # dw-five-field-canary FAIL on the 2026-09-03 1,005-item batch. build_batch2.py
+ # now carries description/ai_description through the CSV as row["description"];
+ # send it as body_html when present. Still empty for rows where the scraper never
+ # captured a description at all (rwltd_catalog gap, not this script's fault).
"productOptions": [{"name": "Title", "values": [{"name": "Sample"}]}],
"variants": [{
"optionValues": [{"optionName": "Title", "name": "Sample"}],
@@ -97,6 +103,8 @@ def build_input(row):
"inventoryItem": {"tracked": False},
}],
}
+ if row.get("description") and row["description"].strip():
+ inp["descriptionHtml"] = row["description"].strip()
if metafields:
inp["metafields"] = metafields
if row.get("image_url"):
← 4d7b594 auto-data-snapshot: 2026-09-03T17:39:42 (1 data files) — rwl
·
back to Reid Witlin Onboarding
·
backfill-rwltd-mfr: namespace-scoped idempotency + HTTP stat bb0a80a →