← back to Dw Kravet Hires
data/tk12097/P3-hollywood-momentum-rescrape-SPEC.md
128 lines
# P3 — Hollywood Wallcoverings 434 Momentum Re-Scrape Lane (SPEC)
**Ticket:** TK-12097 P3 (child of TK-12097 / TK-12090)
**Author:** vp-dw-commerce
**Date:** 2026-09-24
**Status:** SPEC — do not build. Read-only analysis complete; execution is a separate gated lane.
**Cost:** $0 (feed/Meilisearch scrape; local re-join)
> **PRIVATE-LABEL GUARD:** "Hollywood Wallcoverings" is the customer-facing brand. The real
> vendor **Momentum / Momentum Textiles & Walls** and its sub-brand **Versa / Versa Designed
> Surfaces** must NEVER appear in any customer-facing field, filename, alt text, or metafield.
> Every step below stages internally; nothing in this lane changes that rule.
---
## 1. Problem
Batch C of TK-12097 recovered true-native hi-res images for **307** low-res Hollywood
Wallcoverings products by joining the Shopify manufacturer-code tail (e.g.
`bristol_asl-135003` → `ASL-135003`) to `momentum_colorways.alt_sku` / `pattern_number` /
`lcode` and pulling the Cloudinary true-native asset.
A further **434** low-res Hollywood products got `join_type = none` — **no match at all** in the
`momentum_colorways` staging table. The `dw_sku` join is dead for this line (Shopify carries the
real original codes `XJU-/XMH-/XWA-/XUW-/XHW-…` while momentum staging carries fabricated
`DWHD-*` codes), so Batch C's unique manufacturer-code join was the only path — and for these 434
it returned nothing.
- Population source: `~/Projects/dw-kravet-hires/data/tk12097/batchC-no-hires-source.json` (434 rows)
- Manifest bucket: `rescrape-lane-manifest.json` → `buckets.no_vendor_mapping` (count 434,
note "Hollywood — needs momentum live re-scrape / code-map fix"), and
`by_vendor_bucket["Hollywood Wallcoverings | no_vendor_mapping"] = 434`.
- Resolution split of the 434: **264 ≤400px**, **170 401–500px** (all below the ≥800px hi-res bar).
- None of the 434 are flagged discontinued (Hollywood appears only under `no_vendor_mapping`, not
under `placeholder_or_discontinued`).
## 2. Why they don't match — the (a)/(b)/(c) split (measured)
Method: loaded all 434, normalized each Shopify `mfr_sku` (trim + case-fold), and joined against
`momentum_colorways` on **every** candidate column — `momentum_sku`, `alt_sku`, `pattern_number`,
`lcode` — plus an exact and fuzzy match on `pattern_name`. (`psql -h /tmp dw_unified`, read-only.)
| Bucket | Meaning | Count |
|---|---|---|
| **(a) genuinely absent from momentum staging** | 0 code-column matches AND 0 pattern-name matches | **434 / 434 (100%)** |
| **(b) present but ambiguous** (>1 candidate image) | — | **0** |
| **(c) present but staging image itself low-res** | — | **0** |
**The split is unambiguous: 434 / 0 / 0.** Every one of the 434 is entirely absent from the
current `momentum_colorways` snapshot — the failure is missing staging rows, not ambiguity and not
a low-res staging asset.
**Root cause.** `momentum_colorways` holds **12,279 rows / 1,007 distinct patterns** — a bounded,
point-in-time snapshot of Momentum's Meilisearch `redesign-colors` index (which itself is capped at
`maxTotalHits = 20000`, and the refresh keeps only `category_name IN ('Wallcovering','Acoustic')`,
dropping upholstery). The 434 patterns/colorways were simply never captured into that snapshot —
added since the last refresh, beyond the 20k pagination cap, and/or category-filtered out. A fresh
scrape of the live index is the fix.
**Code-format sub-finding (drives the recovery estimate + the code-map half of the job).**
Of the 434 Shopify `mfr_sku` values:
- **~405** are Momentum-Meilisearch-shaped (`H2O_R2-HO-17-1`, `Surf_L2-SF-07`, `Q56-197`,
`TRI-LA-03`, `VP42306`, `dy24-06`, `2VFO-17`) — these should resolve on a refreshed join.
- **~29** are foreign / dirty and will NOT auto-resolve to Momentum: grasscloth `dw_sku`s carrying
non-Momentum codes (`GRS-1410 → PO151`, `GRS-6007 → c3507.jpg`, `GRS-7432 → md4033 / PDSH296`,
`GRS-3624 → NR125`), cork (`CORK-804 → SBTW-804`, `CORK-99000 → LCK101`), and multi-code / "old
SKU" strings (`WBS-39664 → WW945/ WW860 old SKU WW764`). These need per-item code-map
investigation — some may be mis-bucketed non-Momentum products, not a re-scrape miss.
## 3. Proposed approach (execution lane — separate, gated)
0. **Preflight — confirm truncation is the cause (cheap, $0).** Spot-check a sample of the ~405
Momentum-format codes directly against the LIVE `redesign-colors` index before committing the
full refresh. If they are present live, truncation/staleness is confirmed and the refresh will
recover them; if a sample is *also* absent live, the gap is upstream (never scraped into
Momentum's own index) and those items route to the code-map/adjudication step instead — don't
assume the refresh will find them.
1. **Refresh `momentum_colorways` from the live source ($0).** Run the existing
`momentum-feed/refresh.mjs` (momentum-scraper-manager skill) against the live Meilisearch
`redesign-colors` index — re-grab the rotating search key from
`momentumco.com/build/assets/app-*.js` first (the `<meta>` key is a decoy), page the full index,
targeted **upsert** (INSERT-new, non-clobbering) of Wallcovering + Acoustic colorways. No browser,
no captcha, no spend.
2. **Re-run the Batch-C unique-code join** over the refreshed table for the 434 — the same
normalized `mfr_sku` → `alt_sku`/`pattern_number`/`lcode`/`momentum_sku` match Batch C used.
3. **Fix the Hollywood↔momentum code mapping** for the residual that still misses: normalize the
dirty codes (strip `.jpg` suffixes, split multi-code "old SKU" strings, pick the current code),
and investigate the ~29 GRS/CORK/foreign-code items — confirm whether they are Momentum at all or
mis-bucketed into Hollywood; route non-Momentum ones out of this lane.
4. **Pull the Cloudinary true-native asset** for every newly-joined colorway (same true-native path
as Batch C), measure ≥800px, and stage the swap.
## 4. Expected recovery
- **Optimistic ceiling ~405** (the Momentum-format codes), less any patterns Momentum has genuinely
retired from its live line and any that resolve only to a shared pattern-level code.
- **~29 residual** (grasscloth/cork/dirty-code) will likely NOT recover from the re-scrape alone and
fall to the code-map/adjudication step; treat as a smaller follow-on, not a blocker.
- Realistic first-pass target: **the bulk of the 405 Momentum-format items** resolve to a ≥800px
true-native asset; residual carries to a code-map pass.
## 5. Colorway-safety requirement (non-negotiable)
This is a per-colorway image swap on a live catalog — a wrong-colorway image is worse than a
low-res one. Reuse Batch C's guardrails:
- **Unique-code join only** — match on the colorway-level code (`alt_sku`/`lcode`/`momentum_sku`),
never a pattern-level `pattern_number` that maps to many colorways. A code that resolves to >1
colorway is AMBIGUOUS and is **skipped**, not guessed.
- **Filename / asset verify** — confirm the resolved Cloudinary asset's own code matches the target
colorway's code before staging the swap (the same verify Batch C applied).
- Any item that cannot be uniquely + verifiably matched stays low-res and is logged, not swapped.
## 6. Apply path (reuse + gate)
- The eventual image swap **reuses `~/Projects/dw-kravet-hires/scripts/apply-hires.mjs`** — the
same reversible, restore-map-ledgered applier used by Batches A–D, with `scoped-ledger-rollback`
semantics so this lane's swaps can be reverted independently of sibling runs.
- The apply is a **customer-facing Shopify write → GATED.** Dry-run + staged swap map first; the
live `--apply` drafts to `~/.claude/yolo-queue/pending-approval/` for Steve. The re-scrape,
re-join, and code-map steps are $0 read/stage work and run without a gate; only the image write is
gated.
## 7. Out of scope
- No Shopify writes, no scrape run, no code commit in THIS spec (P3 = SPEC only).
- The other TK-12097 buckets (`reresolve_slug` 150, `vendor_capped_lowres` 129,
`recoverable_now_auto_url` 44, `placeholder_or_discontinued` 6) are separate lanes.