data/work-orders/PHASE-1-COST-SOURCING.md
# Phase-1 Cost-Sourcing Plan — GO-set (read-only) **Generated:** 2026-07-27 · **Read-only** from the local `dw_unified` mirror (`psql host=/tmp dbname=dw_unified`) + `data/work-orders/*.csv` **No Shopify writes · no variant creation · no canonical dw_unified write · no metered loads · no scraper runs.** **Depends on:** `PHASE-0-GO-NO-GO.md` (merchandising GO/NO-GO ruling). This document only covers the **5 merchandising-GO lines** and answers one question per line: *what price basis is needed, does the source exist today, and which staging table would it land in.* > **The hard reality restated.** Every GO line is **cost-blocked at the product level** — `has_cost = 0` in the mirror rollup, and every backlog row carries `NEEDS_COST`. A sellable roll variant needs a *confirmed price basis*; `NEEDS_COST` clearing is the Phase-1 exit condition, and Phase-2 cannot write a variant until it does. ## What "price basis" means here Two valid bases, per line class: 1. **Vendor list price + DW markup** — retail = `net_cost / 0.65 / 0.85` (DW standard). Requires a real **wholesale/net cost** (or a list price + known trade discount to derive cost). 2. **Kravet MAP** — for any Kravet-umbrella SKU: retail = **WHLS × 1.5**, never the cost formula. ~101 Kravet SKUs sit inside the backlog (Cole & Son, Brunschwig & Fils, Lee Jofa, Baker Lifestyle, GP&J Baker, Clarke & Clarke, Threads, Andrew Martin, Ralph Lauren) — small, but MAP-gated. Nicolette Mayer stays archived. A **display "retail" figure already on a product** (a `min_variant_price` > $4.25) is **NOT** a confirmed cost basis — it lets us *check margin* but not *set a compliant price with known cost*. This distinction matters for Osborne (below). ## Ground-truth cost-source inventory (verified read-only against the mirror this run) | Staging table | Rows | Price/cost columns | Populated? | Verdict | |---|--:|---|---|---| | `greenland_catalog` | 213 | `price_per_yard`, `min_increment_yards` | **213 / 213** | **EXISTS** — $59.50/yd (6-yд increments) | | `momentum_colorways` | 10,985 | `list_price`, `cost`, `hw_price`, `retail_price` | `list_price` **10,985**, `hw_price` **10,985**, `retail_price` 8,083, **`cost` 0 (NULL)** | **EXISTS via `hw_price`** — pre-computed HW sell price populated; wholesale `cost` column is EMPTY (see corrected Line 3) | | `justin_david_pricing_2026` | 1,833 | `list_price_yd`, `our_cost_yd`, `retail_yd`, `tariff_yd` | **1,528** list+cost | **EXISTS** (partial) — JD Jan-2026 list, 15% trade | | `osborne_catalog` | 14,107 | `price_retail`, `price_trade`, `our_price` | `price_retail` **836**, `price_trade` **1,010**, `our_price` **0** | **PARTIAL** — scraped list/trade exist; confirmed DW cost (`our_price`) NULL | | `coordonne_catalog` | 6,460 | `price_retail`, `price_trade`, `tariff_amount_per_yard` | `price_retail` **4,205**, `price_trade` **0** | **PARTIAL** — retail only, **no trade/cost basis** | | `designers_guild_catalog` | 1,294 | `price_retail`, `price_trade` | `price_retail` **23**, `price_trade` **0** | **MISSING** — effectively no cost basis | | `command54_catalog` | 2,120 | `price_retail`, `price_trade`, `tariff_amount_per_yard` | **0 / 0** | **MISSING** — must request list | | `rigo_catalog` | 725 | (none) | — | **MISSING** — must request list | | `kravet_master_price` | 4,685 | `whls_cost`, `map_price`, `retail_price` | **4,685 / 4,685** | **EXISTS** — MAP feed (× 1.5 rule) | --- ## Line 1 — Phillipe Romano (private-label; 3,468 backlog SKUs; `has_cost` populated on only 3) **Class:** private-label umbrella over four sub-labels. Cost basis is **per-sub-label**, not one list. Backlog `dw_sku` prefixes route to the sub-label (this run, read-only): `GRS-` (289) → Greenland cork; `PRT-/PRX-/PRG-/PRP-` and `RWY-` families; `TXT-`; `CORK-` (30); plus the DW-native `DWWC-/DWRT-` families. | Sub-label | Price basis needed | Source | Exists today? | Lands in | |---|---|---|---|---| | **Greenland** (cork; joins as `DWGL-`) | list-per-yard + DW markup | Greenland open JSON API — **$59.50/yд, 6-yд increments** | **YES (priced) but NOT in backlog** — ⚠️ **VERIFIED CORRECTION:** the 213 rows that join `greenland_catalog` (via `sp.mfr_sku=gc.mfr_sku`, all `DWGL-` prefix) are **already buyable** — `0` of them are in the sample-only backlog. The earlier "289 `GRS-` + 29 `CORK-`" figure was a CSV **prefix count**, and those SKUs join greenland **0 rows**. Greenland is therefore **NOT a backlog canary candidate**. | n/a — already has variants | | **Justin David** (per-yard fabric) | JD list_yd + 15% trade → cost → markup | JD Jan-2026 list | **PARTIAL** — `justin_david_pricing_2026.list_price_yd`/`our_cost_yd` populated 1,528 | `justin_david_pricing_2026` | | **Command54** | list + DW markup | Command54 price list | **NO** — `command54_catalog` price columns exist but **0 populated**; must be **requested from the vendor** | `command54_catalog.price_retail/price_trade` (empty schema ready) | | **RIGO** | list + DW markup | RIGO cost list | **NO** — `rigo_catalog` has **no price columns at all**; must be **requested + a price column added** | `rigo_catalog` (needs `price_*` cols) | **Leak-guard (hard):** Command54 / RIGO / Greenland / Justin David upstream names must NEVER surface customer-facing. Titles/collections stay "Phillipe Romano" per the private-label rename. **Phase-1 status:** partially sourceable (Greenland + Justin David real today); Command54 + RIGO are **request-the-vendor** blockers. A PR canary is premature until at least one sub-label is fully joined. ## Line 2 — Coordonné (designer/retail; 1,300 backlog SKUs; `has_cost` on 28) **Price basis needed:** vendor list price + DW markup (retail = cost/0.65/0.85). Coordonné is customer-facing designer — no MAP. **Source / exists today:** **PARTIAL.** `coordonne_catalog.price_retail` is populated **4,205 rows**, but `price_trade` is **0** — so we have a *retail* number and **no wholesale/net cost** to derive a compliant, margin-verified price. A **Coordonné trade/wholesale price list** must be **requested from the vendor** (or the trade discount confirmed to back out cost from retail). **Lands in:** `coordonne_catalog.price_trade` (column exists, empty) → join to backlog `dw_sku`. **Phase-1 status:** cost-blocked; needs a trade list or a confirmed discount %. ## Line 3 — Hollywood Wallcoverings (private-label = Momentum/Versa; 843 backlog SKUs; `has_cost` on 0) **Price basis needed:** list price + DW markup. **Source / exists today:** **EXISTS via `hw_price`, NOT `cost`.** ⚠️ **VERIFIED CORRECTION (2026-07-27, direct query):** the wholesale `momentum_colorways.cost` column is **0 / 10,985 — entirely NULL.** What IS populated: `list_price` **10,985/10,985**, `hw_price` **10,985/10,985** (the pre-computed Hollywood private-label sell price), `retail_price` 8,083/10,985. So Hollywood is still a strong position — arguably the strongest, since `hw_price` is already the customer-facing sell figure — but it prices off **`hw_price`**, not a wholesale `cost`. A canary that joined on `cost` would hit all-NULLs and fail. **Join path (VERIFIED):** `sp.mfr_sku = momentum_colorways.alt_sku` — **813 of 843** Hollywood backlog rows join, all with `hw_price` populated. Hollywood backlog `dw_sku` is NULL (842/843), so **do NOT join on `dw_sku`**; and do **not** join on `cost` (empty). Land the price via `momentum_colorways.hw_price`. **Leak-guard (hard):** "Momentum" and "Versa / Versa Designed Surfaces" must NEVER appear customer-facing — line stays "Hollywood Wallcoverings" with California-city collection names. **Phase-1 status:** **the single genuinely cost-ready backlog canary** — 813/843 backlog rows have a joinable `hw_price`; blocker is only the join + a `has_cost`/price refresh, not a vendor request. (Greenland was mistakenly listed as cost-ready — corrected: its priced rows are already buyable, 0 in backlog. Osborne is joinable but cost-blocked. So Hollywood is the one cost-ready-now candidate — the leak-guard is the managed risk.) ## Line 4 — Designers Guild (designer/retail; 840 backlog SKUs; `has_cost` on 2) **Price basis needed:** vendor list price + DW markup. **Source / exists today:** **MISSING.** `designers_guild_catalog.price_retail` is populated on only **23 / 1,294** rows and `price_trade` is **0**. There is effectively no cost basis in the mirror. A **Designers Guild wholesale/net price list** must be **requested from the vendor**. (Note: DG's UK wallpaper lines are distributed via Osborne & Little in the US — a DG price list may arrive through the same O&L channel.) **Lands in:** `designers_guild_catalog.price_retail`/`price_trade` (schema ready, near-empty). **Phase-1 status:** cost-blocked; hard vendor-request dependency. ## Line 5 — Osborne & Little (designer/retail; 820 backlog SKUs; `has_cost` on 0) — **first canary line** **Price basis needed:** vendor list price + DW markup. **Source / exists today:** **PARTIAL — and the best-positioned of the five.** `osborne_catalog` already carries scraped `price_retail` (**836** rows) and `price_trade` (**1,010** rows). What is **NOT** present: `our_price` (the confirmed DW cost) is **0 / NULL**, and every backlog row is `has_cost=false` / `NEEDS_COST`. **Honest correction to the Phase-0 phrasing.** Phase-0 noted "some SKUs already carry real roll prices ($76.02 / $195.48) → variant-structure fix, not from-scratch." Verified this run: **90** backlog rows have `min_variant_price > $4.25` (values $76.02, $195.48, $322.17 … up to $962.90) — but **all 90 still carry `has_cost=false` and `NEEDS_COST`.** That price is a **display / min-variant retail figure, not a confirmed wholesale cost.** So even Osborne's "priced" subset is cost-blocked in the strict sense: we can *sanity-check* a derived retail against the displayed figure, but we still need a wholesale/net cost (or O&L trade discount) to set a margin-correct price and clear `NEEDS_COST`. **Why it is still the canary line:** Osborne has (a) 100% sample/image/description completeness on the backlog, (b) a scraped `price_retail`/`price_trade` list already in `osborne_catalog` to derive from and cross-check, (c) no private-label leak-guard, and (d) a subset with an existing displayed roll price to validate against. It needs the **least new external data** — either O&L's confirmed trade discount to back cost out of the scraped list, or a wholesale line-sheet — to become the first buyable line. **Lands in:** `osborne_catalog.our_price` (column exists, NULL) → join to backlog `dw_sku` on `mfr_sku`. **Phase-1 status:** **sourceable-soonest.** One input away (confirmed O&L trade discount OR a wholesale sheet); no vendor-name leak risk. --- ## Kravet-family MAP sub-track (~101 backlog SKUs, cross-line) Any Kravet-umbrella SKU in the backlog prices at **MAP = WHLS × 1.5** (never cost/0.65/0.85). **Source / exists today:** **EXISTS.** `kravet_master_price` has `map_price` populated **4,685 / 4,685** (and `whls_cost` to derive MAP where map is absent). Loader path per memory: `shopify/scripts/cadence/kravet-master-loader.py`; live-adjustment watcher lands into `kravet_authoritative_pricing`. **Lands in:** `kravet_master_price.map_price` (authoritative) with fallback to `kravet_catalog.dw_sell_price`, then derived `whls_cost × 1.5`. **Phase-1 status:** **cost source present** — these ~101 are MAP-computable today for any SKU where the mfr_sku matches `kravet_master_price`. Nicolette Mayer stays archived (never re-activate). ## Phase-1 exit criteria (per line, before any Phase-2 write) A GO line is Phase-1-complete when, for the SKUs to be activated: 1. a **confirmed price basis** is joined to the backlog `dw_sku` (list-price+discount→cost, or Kravet MAP), landed in the named staging column above; 2. the mirror rollup `has_cost` flips **true** (i.e. `NEEDS_COST` clears) for those SKUs; 3. derived retail sanity-checks against any existing displayed price (Osborne's 90) without an inversion (retail < displayed = investigate). ## Sourcing status summary | Line | Cost source today | Blocker | Soonest path | |---|---|---|---| | **Osborne & Little** | scraped list/trade in `osborne_catalog` | `our_price` NULL; need O&L trade % or wholesale sheet | **Nearest** — confirm discount, derive cost, load `our_price` | | **Hollywood (Momentum)** | `hw_price` in `momentum_colorways` (**`cost` NULL**; join `sp.mfr_sku=mc.alt_sku`) — **813/843 backlog rows join** | join + `has_cost` refresh (no vendor request) | **Cost-ready now — the ONE viable canary** (leak-guard = managed risk) | | **Greenland (PR sub-label)** | `greenland_catalog` 213 DWGL @ $59.50/yd but **0 in backlog (already buyable)** | n/a | **NOT a backlog candidate** — corrected from an earlier prefix-count error | | **Phillipe Romano** | Greenland + Justin David real; Command54 + RIGO empty | request Command54/RIGO lists | Partial (per sub-label) | | **Coordonné** | `price_retail` only (no trade/cost) | request trade list / confirm discount | Vendor request | | **Designers Guild** | ~none (`price_retail` 23/1,294) | request DG wholesale list (likely via O&L) | Vendor request | | **Kravet ~101 (cross-line)** | `kravet_master_price.map_price` 4,685/4,685 | none — MAP computable now | Ready | ## Honest gated boundary Cost sourcing itself is Steve-gated: pulling a fresh vendor list, running a scraper to repopulate a staging table, or a metered enrichment/load all require Steve's go and (where applicable) a vendor request. This document is a **read-only plan** — it loaded no cost, created no variant, wrote nothing to Shopify or canonical dw_unified. Nothing here activates a product.