← back to Dw Unbuyable Recovery Pilot
data/cost-sourcing-feasibility.md
106 lines
# Cost-Sourcing Feasibility Matrix — Unbuyable ACTIVE Recovery (TK-10978)
Follow-on to TK-10875 (Wolf Gordon 224 + Scalamandre 144 recovered — both had joinable staging cost, SHIPPED). This matrix ranks the **top ~20 cost-BLOCKED vendors** (their `<vendor>_catalog` staging tables have empty cost columns) so Steve's cost-sourcing greenlight is concrete, vendor-by-vendor.
**All findings from read-only local `dw_unified` (/tmp socket) reads + scraper SKILL.md reads. $0. No scrapes, no writes, no gated actions.**
## The reframe that changes the ROI ranking
"Unbuyable" ≠ "needs a cost." A product is sellable in **two** ways:
1. **Priceable** — we have a real published price or a cost to compute one (`cost/0.65/0.85`). Needs cost SOURCED. This is the classic cost-blocked case.
2. **Quote-only** — the line legitimately publishes **no price** (bespoke / contract / trade-quote). These recover via the **Pierre-Frey pattern** (already proven live): sellable variant is the `$4.25` sample + `quotes` tag + `Needs-Price`, product price NULL. **No cost needs sourcing at all** — this is a variant-build job, not a cost-sourcing job.
The registry flag `do_not_price=t` marks the policy quote-only lines. This splits the backlog cleanly and is the single most important input to sequencing.
## Matrix (sorted by unbuyable count)
| Vendor | Unbuyable | Class | Staging cost col populated? | Scraper fetches cost? | Blocker | Effort |
|---|---:|---|---|---|---|---|
| **Phillipe Romano** | 8274 | TRADE-PORTAL (private label; source = York/Brewster) | `phillipe_romano_catalog` price_trade **0/3503**, price_retail 474/3503 | Partial — retail only, no trade cost | Steve cost list OR gated York/Brewster trade-portal scrape (yorkwall.com login). Source vendor is York/Brewster per registry | HIGH |
| **Koroseal** | 2448 | TRADE-PORTAL / quasi-QUOTE (contract vinyl, `buy_from=Direct`) | `koroseal_catalog` **no price/cost columns at all** | No — scraper captures specs/images only | Koroseal contract line is loginforprice; needs Steve cost list OR gated login scrape. No price scaffolding exists | HIGH |
| **Phillip Jeffries** | 2338 | QUOTE-ONLY (`do_not_price=t`, `buy_from=Direct`) | `pj_catalog` **no price/cost columns** | No | None for cost — recover via **quotes-tag path** (no cost needed). PJ is trade-quote by policy | LOW (quotes path) |
| **China Seas** | 1776 | QUOTE-ONLY (`do_not_price=t`; Quadrille house) | `china_seas_catalog` **no price cols**; `quadrille_house_catalog` cost_price 0/1942 | No | **quotes-tag path** — no cost needed | LOW (quotes path) |
| **Coordonné** | 1299 | FEED-FIRST priceable (`display_prices=t`, discount 20%) | `coordonne_catalog` price_retail **3676/6460**, price_trade 0 | Partial — retail captured, no net cost | Have list retail for ~57%; either apply 20% trade discount to net + `/0.65/0.85`, OR re-scrape to fill remaining retail. Cheapest priceable win | MED |
| **Pierre Frey** | 659 | QUOTE-ONLY (confirmed in skill; `buy_from=Schumacher`) | `pierre_frey_catalog` our_price 0/1161 | No — skill states "QUOTE-ONLY, no per-SKU price even in portal" | **quotes-tag path already proven for PF** — no cost needed. Pure variant-build | LOW (quotes path) |
| **Maya Romanoff** | 620 | UNKNOWN → likely TRADE/QUOTE (`buy_from=Quintus`) | `maya_catalog` **no price/cost cols** | No — scraper is sitemap→products.json, no price | Needs investigation: is MR priced or quote? Dedicated scraper-manager exists but doesn't fetch cost. Likely Steve cost list | MED |
| **Rebel Walls** | 494 | FEED-FIRST priceable (public price storefront, no auth) | `rebelwalls_catalog` price_retail **4382/4383**, price_per_sqft_* 4133/4383; price_trade 449 | **YES** — public per-m² price fully captured | Nearly all retail present already. Compute DW sell from per-m² retail (per-sqft cols populated). Best pure-feed win | LOW |
| **Arte** | 489 | FEED-FIRST priceable (customer-facing, discount 0) | `arte_catalog` cost 0/1991, price 0/1991 | Colorway scraper captures color/SKU, **not price** | Re-scrape needs a price source; Arte colorway scraper is DOM-based for colorways only. Needs cost list or price-capturing scrape | MED |
| **Quadrille** | 443 | QUOTE-ONLY (`do_not_price=t`; Quadrille house) | `quadrille_house_catalog` cost_price 0/1942 | No | **quotes-tag path** — no cost needed | LOW (quotes path) |
| **Alan Campbell** | 434 | QUOTE-ONLY (`do_not_price=t`; Quadrille house) | `quadrille_house_catalog` cost_price 0/1942 | No | **quotes-tag path** — no cost needed | LOW (quotes path) |
| **Vahallan** | 390 | UNKNOWN (registry 'Vahallan' → shared `vendor_catalog`, no cost; dedicated `vahallan_catalog` exists, no price cols) | `vahallan_catalog` **no price/cost cols** | No — no SKILL.md for Vahallan | Needs investigation: table mapping ambiguous (2 registry rows). Likely quote/handmade. Confirm table + pricing model first | MED (invest.) |
| **CMO Paris** | 344 | FEED-FIRST priceable (`display_prices=t`, no `do_not_price`) | `cmo_paris_catalog` cost 0/345, price 0/345 | Shared-engine scraper, no dedicated cost logic | Re-scrape with price capture OR Steve cost list. Small count | MED |
| **Malibu Wallpaper** | 339 | TRADE-PORTAL (private label = WallQuest; cost EXISTS but SKU-remap needed) | `malibu_catalog` cost 0/3718; **`wallquest_catalog` net_cost 2085/2515 (source has cost!)** | **YES** (WallQuest browserbase login captures net_cost=cost) | Cost exists in the WallQuest source table but Malibu unbuyable SKUs join only **1/339** by mfr_sku/dw_sku — needs a private-label SKU-reconcile, not a fresh scrape | MED (reconcile) |
| **Architectural Fabrics** | 328 | TRADE-PORTAL (Fabricut-family; `buy_from=Duralee`) | `fabricut_catalog` price_trade **0/1333**, price_retail/vendor_msrp 1263/1333 | **YES** (scraper schema has price_trade col; Fabricut login) — but trade col currently empty | Have MSRP; net cost from Fabricut trade portal (login exists). Or derive from MSRP × discount. Fabricut portal is a known CF-challenge target | MED |
| **AS Creation** | 327 | FEED-FIRST priceable (retail scrape returned currency only) | `as_creation_catalog` price_retail **0/6166** (price_currency=EUR filled) | Partial — scrape got currency but no number | Re-scrape to capture the numeric price (structure is there, extraction missed the value). Cheap fix | MED |
| **Suncloth** | 254 | QUOTE-ONLY (`do_not_price=t`; Quadrille house) | `quadrille_house_catalog` cost_price 0/1942 | No | **quotes-tag path** — no cost needed | LOW (quotes path) |
| **Home Couture** | 229 | QUOTE-ONLY (`do_not_price=t`; Quadrille house) | `quadrille_house_catalog` cost_price 0/1942 | No | **quotes-tag path** — no cost needed | LOW (quotes path) |
| **Thibaut** | 203 | FEED-FIRST priceable (cost already exists — mostly recovered) | `thibaut_catalog` net_cost **3670/6057**, your_cost 3625/6057 | **YES** — Thibaut scraper captures net_cost | These 203 are the join-gap tail; cost exists for most Thibaut. Likely a TK-10875-style join recovery, NOT cost-blocked | LOW |
## Class rollup
| Class | Vendors | ~Unbuyable | What it needs |
|---|---|---:|---|
| **QUOTE-ONLY (recover via quotes-tag, NO cost needed)** | Phillip Jeffries, China Seas, Pierre Frey, Quadrille, Alan Campbell, Suncloth, Home Couture | **~5,689** | Variant-build only (Pierre-Frey pattern). Zero cost sourcing. |
| **FEED-FIRST-$0 (cost/price re-scrapeable, no login)** | Rebel Walls, Coordonné, AS Creation, CMO Paris, Arte | ~2,953 | (Re)scrape the public price the extractor missed / derive DW sell |
| **TRADE-PORTAL (gated login scrape or SKU-reconcile)** | Phillipe Romano, Koroseal, Malibu, Architectural Fabrics | ~11,389 | Gated trade-portal scrape (SPEND if Browserbase/2captcha) OR Steve cost list |
| **JOIN-GAP tail (cost already exists — TK-10875-style)** | Thibaut | ~203 | Join recovery, not cost sourcing |
| **UNKNOWN (needs investigation first)** | Maya Romanoff, Vahallan | ~1,010 | Confirm table + pricing model before classing |
## Recommended sequencing
### Track 0 — the "free" prize (do FIRST, no cost sourcing at all): QUOTE-ONLY quotes-tag recovery
**~5,689 unbuyable across 7 vendors** (PJ, China Seas, Pierre Frey, Quadrille, Alan Campbell, Suncloth, Home Couture) recover with **zero cost input** — they are legitimately quote-only (`do_not_price=t` / confirmed in PF skill). Build the `$4.25`-sample + `quotes` tag + `Needs-Price` variant (the **already-live Pierre Frey pattern**). This is the highest-ROI move: biggest recovery, no cost list, no gated scrape, no spend. **This is a variant-build job, not the cost-sourcing job Steve is greenlighting — so it likely doesn't even need his cost greenlight; it needs a quotes-path build ticket.**
### Track A — gated scrapes / internal reconciles (agent can execute once armed)
Ranked by ROI (recovery per unit effort):
1. **Rebel Walls (494)** — public price storefront, `price_retail` 4382/4383 + per-sqft cols already populated. Pure compute → sell price. Cheapest priceable win, no login, no spend.
2. **Malibu (339)** — cost EXISTS in `wallquest_catalog` (net_cost 2085/2515); needs a **private-label SKU-reconcile** (Malibu↔WallQuest join is 1/339 today). Internal DB work, no new scrape.
3. **Coordonné (1299)** — retail 3676/6460 + 20% trade discount known; derive net + DW sell for the 57% we have, re-scrape the rest. Feed-first, no login.
4. **AS Creation (327)** — extractor got currency but not the number; a targeted re-scrape fixes the numeric-price extraction. Feed-first.
5. **CMO Paris (344)** / **Arte (489)** — re-scrape with price capture (shared-engine / colorway scraper doesn't currently fetch price). Feed-first, low count.
6. **Architectural Fabrics / Fabricut (328)** — MSRP present; net cost via Fabricut trade portal (login exists) **or** derive from MSRP × discount. **GATED + possible SPEND** — Fabricut is a known Cloudflare-challenge target (may need Browserbase/2captcha → cost-tracker).
### Track B — needs Steve to supply a trade cost list (no scrapeable price exists)
1. **Phillipe Romano (8274)** — biggest single prize but hardest. Private label; source = York/Brewster trade portal (yorkwall.com login). Either Steve supplies the PR/York cost list OR a gated York/Brewster trade-portal scrape. No retail exists for 87% of rows. **Split by collection to make tractable.**
2. **Koroseal (2448)** — contract vinyl, loginforprice, no price scaffolding in staging at all. Steve cost list OR gated Direct login scrape.
3. **Maya Romanoff (620)** — investigate first (priced vs quote?); scraper doesn't fetch cost. Likely Steve cost list.
### Track C — investigate before classing
- **Vahallan (390)** — ambiguous table mapping (2 registry rows, `vendor_catalog` vs `vahallan_catalog`, neither has cost). Confirm the real feed + whether it's quote/handmade before assigning a track.
- **Thibaut tail (203)** — almost certainly a TK-10875-style join recovery (cost already staged); verify and fold into the Wolf-Gordon/Scalamandre recovery pattern, not the cost-sourcing effort.
## The one-line recommendation for Steve
**Greenlight Track 0 (quotes-tag build, ~5,689 recovered, $0, no cost list) immediately — it's the largest recovery and needs no cost at all. Then Track A feed-first/reconcile (Rebel Walls → Malibu → Coordonné → AS Creation, ~2,459 more, mostly $0 local/no-login). Hold Track B (Phillipe Romano 8274 + Koroseal 2448) for a cost-list decision — those two alone are ~44% of the backlog but are the ones that genuinely can't be scraped.**
## Uncertainty flags (not fabricated — marked UNKNOWN in the matrix)
- **Maya Romanoff** and **Vahallan** classed UNKNOWN — no cost columns and no cost-fetching scraper; could be quote-only OR need a cost list. Do not assume priceable.
- **Malibu 1/339 join** — the low join rate is real (measured); root cause (SKU-form remap vs genuinely-absent WallQuest rows) not yet confirmed. Reconcile effort could be small (remap) or larger (missing source rows).
- **Coordonné / AS Creation** — retail present but partial/malformed; assumed re-scrape fixes it, not verified end-to-end.
- Registry `buy_from` (Pierre Frey→Schumacher, Maya→Quintus, Fabricut→Duralee) implies the real cost owner may be the distributor, not the named brand — affects where a cost list would come from.
---
## ⚠️ VERIFICATION CORRECTION (codex-10875, VERIFY-BEFORE-ACT) — Track 0 is a PHANTOM
Independently checked the Track-0 "~5,689 free prize" against live `vendor_registry` + the
`shopify_products.tags` column. It does NOT hold:
1. **Pierre Frey is `do_not_price=FALSE`** in the registry (not quote-only per the authoritative
flag) — remove its 659 from the quote-only track. Verified `do_not_price=t` set = 6 vendors
(Alan Campbell, China Seas, Home Couture, Phillip Jeffries, Quadrille, Suncloth).
2. **5,271 of the 5,474 unbuyable across those 6 vendors ALREADY carry a quote-style tag**
(`quotes`/`Needs-Price`/`contact-for-price`) — i.e. they are ALREADY correctly-configured
quote-only products. `has_product_variant=false` is CORRECT-BY-DESIGN for a quote-only line
(you request a quote; there is no sellable roll). They are NOT a revenue hole.
3. **Only ~203 truly lack any quote tag** — that is the entire real Track-0 gap, and even those
need per-product verification (they may have other treatment or be genuinely display-broken)
before any (reversible, tag-add) fix.
**Corrected conclusion:** Track 0 is ~203 (unverified), not 5,689. The "unbuyable" flag is a
FALSE signal for quote-only lines. Do NOT mass-act on the do_not_price vendors — they are
working as intended. Genuine remaining recovery = the FEED-FIRST priceable vendors (Rebel Walls
494, Coordonné partial, Malibu SKU-reconcile, Thibaut 203 join-gap) + Track B cost-lists
(Phillipe Romano 8274, Koroseal 2448). Lesson: a "flag=true" backlog count must be checked
against the intended-state (tags/policy) before it's called recoverable.