← back to Designer Wallcoverings
onboarding/sangetsu-lilycolor/DOSSIER.md
142 lines
# Vendor Onboarding Dossier — Sangetsu (Goodrich) & Lilycolor
**Status:** STAGED / recon complete — **nothing online, nothing pushed to Shopify.**
**Authorized by:** Steve, 2026-06-29 ("Onboard but do not show online until approved. both companies and dig into catalogs too").
**Gate:** Onboarding Activation Gate + publish gate fully in force. Publish only on Steve sign-off.
---
## 0. Disambiguation (the trap we avoided)
The originating customer note said *"sangetsu and goodrich catalogs."* These are **not two vendors.**
**Goodrich = Sangetsu's international/export brand** ("Sangetsu Goodrich", sangetsugoodrich.com,
sangetsu-goodrich.co.th/.vn, goodrichglobal.com). Onboarding "Goodrich" as a separate vendor
would have created a duplicate/garbage vendor that collides on every SKU.
→ **Two vendors to onboard, not three:**
1. **Sangetsu** (incl. the Goodrich export catalog)
2. **Lilycolor** (リリカラ)
Both confirmed ABSENT from the 108-vendor registry (`3-scraper-system-analysis.json`) and from
`~/Projects` — clean adds, no vendor-level collision.
---
## 1. Lilycolor (リリカラ) — HIGH confidence, feed-first
| Field | Finding |
|---|---|
| Founded | 1907, Tokyo. Wallpaper + curtains + flooring. |
| Trade catalogs | WILL (LW####), LIGHT (LL6###), V-wall (LV2###/LV3###), BASE, Digital Deco. |
| **Machine-readable feed** | ✅ **`https://shop.lilycolor.co.jp/products.json`** → 200 `application/json`, full Shopify schema. |
| Feed size | ≥250 products/page (paginated `?limit=250&page=N`). Consumer paste-on (のり付き) shop = a SUBSET of the full trade line. |
| SKU prefixes seen in feed | **LB, LV, LW** (variant.sku). Trade line adds LL6### etc. |
| Sample-variant convention | Already present in feed: handle `…___sample`, title `…|サンプル`. Maps cleanly to our sample-variant rule. |
| Language | **Japanese** titles + body_html → **translation pass required** for customer-facing copy. |
| Specs in feed | width (有効巾, e.g. 92.5cm), composition (塩化ビニル = vinyl), fire grade (F☆☆☆☆, 準不燃=semi-noncombustible) embedded in body_html table. |
| English resellers (cross-ref) | softhome.sg/wallcoverings/lilycolor (WILL/LIGHT/V-wall digital catalogs). |
**Scraper strategy:** feed-first on `products.json` (paginate to exhaustion) for the consumer line,
**plus** the trade `allpatterns` web-catalog pages (WILL/LIGHT/V-wall) for the full line. Translate
JA→EN at enrichment. This is the *easy* one — the previous "scattered, low-confidence" read was wrong
once the Shopify endpoint was found.
**Proposed DW series prefix:** `DWLC` (proposal only — real assignment lives in `vendor_registry`, gated).
---
## 2. Sangetsu (incl. Goodrich) — MEDIUM confidence, DOM-rendered
| Field | Finding |
|---|---|
| US/export face | `sangetsugoodrich.com/en/...` (home 200). `/wp-json/` → **403 (anti-bot)** → no open WP REST. |
| Regional product pages | `sangetsu-goodrich.co.th`, `.vn`, `goodrichglobal.com/singapore` — per-SKU pages at `/products/wallpaper-wallcovering/<category>/<pattern>/`. |
| `sitemap.xml` | 404 (no open sitemap at root). Need to discover via collection/category crawl or a rendered crawl. |
| Catalog PDFs | `sangetsu.co.jp/english/pdfdownload/...` (TEXTURE & MATERIAL 2025-2029 XSELECT, etc.) — usable for spec backfill. |
| Grasscloth / natural line (relevant to the customer ask) | Woven Grass (GSD1038-1041), Savannah (Y476xx, "matte and metallic"), Sass-a-Grass (Y474xx), Sanshin (AZ527xx, "metallic shimmer"), Virtuoso (AZ522xx, "soft shimmer / metallic glamour"), Harvest (HV21-xx), Ambience (LX199x, sisal). |
| SKU schemes | Mixed: GSD####, Y#####, AZ#####, LX####, HV##-## (per collection). |
| Specs | width 52"/54" Type II, 20oz, Osnaburg backing (commercial); residential lines differ. |
**Scraper strategy:** **feed-first will NOT work** (wp-json blocked, no sitemap). Use a rendered
crawl (Playwright / dev-browser) seeded from the category index pages, OR Browserbase if anti-bot
bites. Spec backfill from the English PDFs. This is the harder vendor — onboard **Lilycolor first**,
Sangetsu second.
**Proposed DW series prefix:** `DWSG` (Sangetsu Goodrich) — proposal only, gated.
**Distributor / purchase channel (2026-07-06):** Sangetsu is the **mill**, but its SKUs
are only orderable through the regional **distributor, Goodrich** (`sangetsu-goodrich.co.th`
/ `.vn` / `goodrichglobal.com`) — the site we scraped. So buy-from channel ≠ brand. Goodrich
appears to be the **sole** distributor in our reach → likely an **exclusive** relationship
worth pursuing (better terms; SKU not price-shoppable elsewhere) — *hypothesis, not confirmed.*
- **Data:** every staging row now carries `distributor` + `distributor_direct`
(`japan-enrich/annotate_distributor.py`); the viewer shows a distributor chip + facet + sort.
- **dw_unified target:** reuse the existing `vendor_catalog.us_distributor` col (→ `shopify_products.supplier_name`),
NOT a new column — distributor is 1:1 with the vendor. SQL staged at `japan-enrich/db/distributor-field.sql`; canonical write gated.
- **Exclusivity** → capture at the **vendor level** on onboarding (Shopify `Exclusive` metafield + `vendor_registry`), not per-SKU.
---
## 3. Standing gates that apply before ANY publish
1. **Dedup** by series prefix + mfr_sku against **both** `dw_sku_registry` (~84k) AND `shopify_products` (~160k). (Requires a live psql session — not run from here.)
2. **Full-page scrape** — all images + width + repeat + composition + weight + fire rating + all metafields, not just listing fields.
3. **Settlement gate** — run on any tropical/foliage/botanical pattern before publish (both lines have naturals).
4. **Activation Gate** — every SKU needs all 5: sample variant, product variant, complete pricing (no $0/null), description, ≥2 tags. **No confirmed vendor COST → SKU stays HELD/draft, never force-priced.** Neither vendor's net cost is known yet → this is the real blocker to activation.
5. **Pricing** — non-Kravet formula cost/0.65/0.85 once cost is confirmed; sample = standard sample price.
6. **Cadence-only creation** — only the metered 18/hr `cadence-import.js` may create products. No burst jobs.
## 4. What is DONE (this session, all reversible/offline)
- Disambiguated Goodrich→Sangetsu. Confirmed both absent from registry.
- Confirmed Lilycolor Shopify feed (live pull proof: `lily-feed-recon.cjs`).
- Characterized Sangetsu platform (DOM-rendered, anti-bot) + grasscloth line.
- Added 2 registry entries (status `onboarding`, `integrated:false`) — see registry diff.
- Disco/lookalike shortlist for the customer: `disco-candidates-for-andrew.md`.
## 5. What is GATED (needs Steve sign-off — see pending-approval memo)
- Building + integrating the two scrapers into `lib/scrapers/`.
- Assigning real `vendor_registry` rows + DW prefixes.
- Any cost confirmation / pricing.
- Any Shopify create / publish (stays "Staged for New" → draft → active only on approval).
## 6. Session progress (2026-06-29, post-approval — Steve said "1 then 3")
**Lilycolor (built):** `lilycolor-feed-scraper.cjs` staged **3,107 products** offline →
`staging/lilycolor-staging.jsonl` (gitignored). Normalized: mfr_sku, prefix (LV 1003 / LL 882 /
LW 628 / LYT 167 / LH 114 / XR 90 / LB 69 / …), width, composition, fire grade (F☆☆☆☆ / 準不燃),
images, product_type. Nothing online.
- **Nuance found:** Lilycolor splits SAMPLE and MAIN into SEPARATE Shopify products (handle
`…___sample`, product_type 壁紙サンプル) — sample↔main is a **product-level join**, not a variant.
Handle this at enrichment when building DW's sample+product variant pair.
- **Next (gated):** JA→EN translate pass, dedup by prefix vs dw_sku_registry+shopify_products, cost.
**Sangetsu (discovery solved):** `sangetsu-sitemap-scraper.cjs discover` enumerates the catalog from
`sangetsu-goodrich.co.th/wp-sitemap-posts-product-1.xml` → **879 pattern pages** (filtered to
wallpaper-wallcovering). US site blocks sitemap/wp-json; the **regional .co.th sitemap is the seed**
(shared global catalog). Each page = a pattern with N colorway SKUs (explodes to thousands of SKUs).
- **Next (gated):** implement `parsePage()` (H1 pattern + `.product_desc` overview + `<select>` colorway
SKUs); rendered fallback (dev-browser/Browserbase) only if SKUs are JS-injected.
**Customer:** Andrew Glick reply DRAFTED via George (`info@` account, draftId r-7044…) with top-3
Sanshin/Virtuoso/Savannah — awaiting Steve review/send.
## 7. Sangetsu per-page parse — PROVEN (plain fetch, NO Browserbase)
`sangetsu-sitemap-scraper.cjs parse <url>` works on a plain `curl --compressed`-style fetch
(server-rendered WooCommerce; Node `https` needs manual gzip/br decompress — handled). Proven:
- **Sanshin** → pattern, decoded overview, spec (Width 52" / Type II / 20 oz / grasscloth+metallic),
**24 colorway SKUs** AZ52790–AZ52813, each with full-res image `uploads/YYYY/MM/<SKU>.jpg`.
- Batch `stage 15` → 15 patterns, **238 colorway SKUs** (Acappella 24, Uncorked 13, Sanshin 24…).
- **No paid Browserbase needed** — the anti-bot "block" was just a missing decompress.
**Two known refinements before staging the full 623 (gated):**
1. **Swatch-style pages return 0 SKUs.** ~1/3 of pages use a swatch widget (not `<select>`), and some
carry **numeric SKUs** (e.g. 384500, G0167, TF3385) the `<option>` regex skips. Fix = parse the
`data-product_variations` JSON blob (authoritative for BOTH dropdown + swatch products) and/or the
`uploads/…/<SKU>.jpg` image filenames (catches numeric SKUs). Present on every page.
2. **`backing` field** captures `OsnaburgWidth` (no separator in source spec block) — split on the
next capitalized label.
Both are local parser tweaks (offline). Then: dedup (prefix AZ/Y/GSD/LX/numeric vs dw_sku_registry +
shopify_products), settlement-gate naturals, source net COST → only then stage to Shopify DRAFT.