← back to Lifestyle Asset Intel
yolo tick #8: MSRP series + premium-to-retail ratio
19cb967b765864b23e3b888b3d52b657b1e0546f · 2026-05-10 00:11:21 -0700 · Steve Abrams
BLUEPRINT.md flagged MSRP backfill as the central v0 problem for
Hermès — they sell Birkin/Kelly/Constance "exclusively in stores",
so there's no e-commerce feed to harvest. Migration 0005 lays in
msrp_observations with a CHECK-constrained source enum
(boutique_receipt / specialist_tracker / auction_house_commentary /
user_upload / client_pdf_invoice) and a (asset, source, date) unique
key so the same observation can't be double-counted across sources.
Seed: only the Birkin 30 Togo 2022–2026 series from BLUEPRINT.md's
own table, attributed to 'auction_house_commentary' (Sotheby's
report). The other 14 canonical assets stay null until real
observations land — Steve's standing rule against synthesized data
applies here too, so no fabricated MSRPs.
valueAsset() now joins the latest msrp_observation per asset and
computes premium_to_msrp = q50 / msrp_usd to two decimals. Returns
null when no MSRP exists. Live values for seeded B30 Togo:
q50=22300, msrp_usd=14900 (2026 reading), premium=1.50×
The number is naturally lower than Sotheby's 2024-quoted 2.4× because
2026 MSRP has caught up while 2025 secondary held — exactly the
drift signal METHODOLOGY § 1 calls out.
Asset detail page renders a "Primary-market reference" KPI block
(MSRP $ + observed year + source) with a teal-tinted "Q50 ÷ MSRP"
cell when the ratio is computable, and a contextual blurb pointing
to /methodology when MSRP is missing. CSS adds .msrp-block + the
accent-color premium cell.
37/37 tests green; smoke test now asserts the MSRP fields too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
A db/migrations/0005_msrp.sqlM db/schema.sqlM db/seed.sqlM lib/valuation.jsM public/css/app.cssM tests/smoke.test.jsM views/asset.ejs
Diff
commit 19cb967b765864b23e3b888b3d52b657b1e0546f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 10 00:11:21 2026 -0700
yolo tick #8: MSRP series + premium-to-retail ratio
BLUEPRINT.md flagged MSRP backfill as the central v0 problem for
Hermès — they sell Birkin/Kelly/Constance "exclusively in stores",
so there's no e-commerce feed to harvest. Migration 0005 lays in
msrp_observations with a CHECK-constrained source enum
(boutique_receipt / specialist_tracker / auction_house_commentary /
user_upload / client_pdf_invoice) and a (asset, source, date) unique
key so the same observation can't be double-counted across sources.
Seed: only the Birkin 30 Togo 2022–2026 series from BLUEPRINT.md's
own table, attributed to 'auction_house_commentary' (Sotheby's
report). The other 14 canonical assets stay null until real
observations land — Steve's standing rule against synthesized data
applies here too, so no fabricated MSRPs.
valueAsset() now joins the latest msrp_observation per asset and
computes premium_to_msrp = q50 / msrp_usd to two decimals. Returns
null when no MSRP exists. Live values for seeded B30 Togo:
q50=22300, msrp_usd=14900 (2026 reading), premium=1.50×
The number is naturally lower than Sotheby's 2024-quoted 2.4× because
2026 MSRP has caught up while 2025 secondary held — exactly the
drift signal METHODOLOGY § 1 calls out.
Asset detail page renders a "Primary-market reference" KPI block
(MSRP $ + observed year + source) with a teal-tinted "Q50 ÷ MSRP"
cell when the ratio is computable, and a contextual blurb pointing
to /methodology when MSRP is missing. CSS adds .msrp-block + the
accent-color premium cell.
37/37 tests green; smoke test now asserts the MSRP fields too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
db/migrations/0005_msrp.sql | 35 +++++++++++++++++++++++++++++++++++
db/schema.sql | 1 +
db/seed.sql | 35 +++++++++++++++++++++++++++++++++++
lib/valuation.js | 27 +++++++++++++++++++++++++++
public/css/app.css | 4 ++++
tests/smoke.test.js | 6 ++++++
views/asset.ejs | 33 +++++++++++++++++++++++++++++++++
7 files changed, 141 insertions(+)
diff --git a/db/migrations/0005_msrp.sql b/db/migrations/0005_msrp.sql
new file mode 100644
index 0000000..7a03a0f
--- /dev/null
+++ b/db/migrations/0005_msrp.sql
@@ -0,0 +1,35 @@
+-- 0005_msrp.sql — primary-market MSRP history for canonical assets.
+--
+-- BLUEPRINT.md flags MSRP as a critical v0 problem: Hermès sells the
+-- Birkin/Kelly/Constance "exclusively in stores" (per their own site),
+-- so there's no normal e-commerce feed to harvest. MSRPs therefore
+-- come from boutique receipts, specialist price trackers, client
+-- uploads, and high-trust market commentary (auction houses).
+--
+-- One observation per (asset, source, observed_at). The valuation
+-- engine reads the most recent observation per asset to compute the
+-- premium-to-retail ratio (Sotheby's-quoted "2.4x retail" headline).
+
+CREATE TABLE IF NOT EXISTS msrp_observations (
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
+ canonical_asset_id uuid NOT NULL REFERENCES canonical_assets(id) ON DELETE CASCADE,
+ msrp_usd numeric(12,2) NOT NULL,
+ currency text NOT NULL DEFAULT 'USD',
+ observed_at date NOT NULL,
+ source text NOT NULL CHECK (source IN (
+ 'boutique_receipt',
+ 'specialist_tracker',
+ 'auction_house_commentary',
+ 'user_upload',
+ 'client_pdf_invoice'
+ )),
+ source_note text,
+ created_at timestamptz NOT NULL DEFAULT now(),
+ UNIQUE (canonical_asset_id, source, observed_at)
+);
+
+CREATE INDEX IF NOT EXISTS msrp_observations_asset_observed_idx
+ ON msrp_observations(canonical_asset_id, observed_at DESC);
+
+INSERT INTO schema_migrations(filename) VALUES ('0005_msrp.sql')
+ ON CONFLICT (filename) DO NOTHING;
diff --git a/db/schema.sql b/db/schema.sql
index 94af7ca..864759e 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -3,3 +3,4 @@
\i db/migrations/0002_transactions_unique.sql
\i db/migrations/0003_image_intake.sql
\i db/migrations/0004_valuation_calls.sql
+\i db/migrations/0005_msrp.sql
diff --git a/db/seed.sql b/db/seed.sql
index 3411ab0..ef7f522 100644
--- a/db/seed.sql
+++ b/db/seed.sql
@@ -751,3 +751,38 @@ ON CONFLICT (index_id, as_of) DO NOTHING;
INSERT INTO index_points (index_id, as_of, value, change_pct)
SELECT i.id, '2025-12-31'::date, 1.168, 0.0228 FROM indices i WHERE i.slug = 'recent-stamp-premium'
ON CONFLICT (index_id, as_of) DO NOTHING;
+
+-- ---------------------------------------------------------------------------
+-- v0 yolo tick #8: MSRP history (only what BLUEPRINT.md authoritatively
+-- gives us — Birkin 30 Togo 2022-2026). Other assets stay null until
+-- boutique receipts or specialist trackers feed in. No synthesized data.
+-- ---------------------------------------------------------------------------
+INSERT INTO msrp_observations (canonical_asset_id, msrp_usd, currency, observed_at, source, source_note)
+SELECT ca.id, 11300.00, 'USD', '2022-01-01'::date, 'auction_house_commentary',
+ 'BLUEPRINT.md table — Sotheby''s reference series, US Birkin 30 Togo'
+ FROM canonical_assets ca WHERE ca.slug = 'birkin-30-togo-gold-ghw-us'
+ON CONFLICT (canonical_asset_id, source, observed_at) DO NOTHING;
+
+INSERT INTO msrp_observations (canonical_asset_id, msrp_usd, currency, observed_at, source, source_note)
+SELECT ca.id, 11600.00, 'USD', '2023-01-01'::date, 'auction_house_commentary',
+ 'BLUEPRINT.md table — Sotheby''s reference series, US Birkin 30 Togo'
+ FROM canonical_assets ca WHERE ca.slug = 'birkin-30-togo-gold-ghw-us'
+ON CONFLICT (canonical_asset_id, source, observed_at) DO NOTHING;
+
+INSERT INTO msrp_observations (canonical_asset_id, msrp_usd, currency, observed_at, source, source_note)
+SELECT ca.id, 12500.00, 'USD', '2024-01-01'::date, 'auction_house_commentary',
+ 'BLUEPRINT.md table — Sotheby''s reference series, US Birkin 30 Togo'
+ FROM canonical_assets ca WHERE ca.slug = 'birkin-30-togo-gold-ghw-us'
+ON CONFLICT (canonical_asset_id, source, observed_at) DO NOTHING;
+
+INSERT INTO msrp_observations (canonical_asset_id, msrp_usd, currency, observed_at, source, source_note)
+SELECT ca.id, 13900.00, 'USD', '2025-01-01'::date, 'auction_house_commentary',
+ 'BLUEPRINT.md table — Sotheby''s reference series, US Birkin 30 Togo'
+ FROM canonical_assets ca WHERE ca.slug = 'birkin-30-togo-gold-ghw-us'
+ON CONFLICT (canonical_asset_id, source, observed_at) DO NOTHING;
+
+INSERT INTO msrp_observations (canonical_asset_id, msrp_usd, currency, observed_at, source, source_note)
+SELECT ca.id, 14900.00, 'USD', '2026-01-01'::date, 'auction_house_commentary',
+ 'BLUEPRINT.md table — Sotheby''s reference series, US Birkin 30 Togo'
+ FROM canonical_assets ca WHERE ca.slug = 'birkin-30-togo-gold-ghw-us'
+ON CONFLICT (canonical_asset_id, source, observed_at) DO NOTHING;
diff --git a/lib/valuation.js b/lib/valuation.js
index 48d2848..1908a63 100644
--- a/lib/valuation.js
+++ b/lib/valuation.js
@@ -46,6 +46,18 @@ async function valueAsset(slug) {
[asset.id]
);
+ // Latest MSRP observation — Hermès doesn't sell online so MSRP is
+ // backfilled from boutique receipts / specialist trackers / auction
+ // commentary. Returns null if we have no authoritative data point.
+ const msrp = await one(
+ `SELECT msrp_usd, currency, observed_at, source
+ FROM msrp_observations
+ WHERE canonical_asset_id = $1
+ ORDER BY observed_at DESC
+ LIMIT 1`,
+ [asset.id]
+ );
+
// Build a tier→weight lookup from the joined comps; falls back to constants.
const sourceWeights = {};
for (const c of comps) {
@@ -104,6 +116,14 @@ async function valueAsset(slug) {
});
const expected_dts = computeExpectedDts(liquidity_score);
+ // Premium-to-retail ratio — Sotheby's-style "2.4x retail" headline.
+ // Q50 / latest MSRP, rounded to 2 dp. Null if we have no MSRP.
+ const msrp_usd = msrp ? numberOrNull(msrp.msrp_usd) : null;
+ const q50 = snapshot ? numberOrNull(snapshot.q50) : null;
+ const premium_to_msrp = (msrp_usd != null && msrp_usd > 0 && q50 != null)
+ ? +(q50 / msrp_usd).toFixed(2)
+ : null;
+
return {
asset: {
slug: asset.slug,
@@ -131,6 +151,13 @@ async function valueAsset(slug) {
comp_count: snapshot.comp_count,
methodology_version: snapshot.methodology_version
} : null,
+ msrp: msrp ? {
+ msrp_usd,
+ currency: msrp.currency,
+ observed_at: msrp.observed_at,
+ source: msrp.source
+ } : null,
+ premium_to_msrp,
comps: comps.map((c) => ({
source: c.source_slug,
source_tier: c.source_tier,
diff --git a/public/css/app.css b/public/css/app.css
index f8ba845..6d9c39b 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -173,6 +173,10 @@ tfoot tr.totals th, tfoot tr.totals td {
font-weight: 600; color: var(--fg);
}
+/* MSRP / premium-to-retail block (v0.2) */
+.msrp-block .premium-cell { background: color-mix(in srgb, var(--accent) 14%, var(--card)); border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
+.msrp-block .premium-val { color: var(--accent); font-size: 1.05em; }
+
/* methodology long-form (v0.2) */
.methodology-page { max-width: 880px; }
.methodology h1 { font-size: 1.7rem; margin: 0.4rem 0 1rem; }
diff --git a/tests/smoke.test.js b/tests/smoke.test.js
index 178e9fd..4361213 100644
--- a/tests/smoke.test.js
+++ b/tests/smoke.test.js
@@ -86,6 +86,12 @@ test('GET /api/valuation/birkin-30-togo-gold-ghw-us returns blueprint contract s
assert.ok(r.json.confidence_breakdown);
assert.ok('source_quality_weight' in r.json.confidence_breakdown);
assert.ok('authenticity_risk_penalty' in r.json.confidence_breakdown);
+ // MSRP block — the seeded Birkin 30 Togo gets BLUEPRINT.md MSRP series.
+ assert.ok(r.json.msrp, 'msrp present for seeded B30 Togo');
+ assert.ok(r.json.msrp.msrp_usd > 0);
+ assert.equal(r.json.msrp.source, 'auction_house_commentary');
+ assert.ok(typeof r.json.premium_to_msrp === 'number');
+ assert.ok(r.json.premium_to_msrp > 1.0, 'premium > 1x retail');
});
test('GET /api/valuation/<unknown-slug> returns 404 with shape', async () => {
diff --git a/views/asset.ejs b/views/asset.ejs
index eb3873e..08d3f48 100644
--- a/views/asset.ejs
+++ b/views/asset.ejs
@@ -25,6 +25,39 @@
</ul>
</section>
+ <% if (data.msrp || data.premium_to_msrp != null) { %>
+ <section class="msrp-block">
+ <h2>Primary-market reference</h2>
+ <ul class="kpis">
+ <% if (data.msrp) { %>
+ <li>
+ <span class="lbl">MSRP (<%= fmtDate(data.msrp.observed_at).slice(0,4) %>)</span>
+ <span class="val">$<%= money(data.msrp.msrp_usd) %></span>
+ </li>
+ <li>
+ <span class="lbl">MSRP source</span>
+ <span class="val mono"><%= (data.msrp.source || '').replace(/_/g,' ') %></span>
+ </li>
+ <% } else { %>
+ <li><span class="lbl">MSRP</span><span class="val muted">— no observation —</span></li>
+ <% } %>
+ <% if (data.premium_to_msrp != null) { %>
+ <li class="premium-cell">
+ <span class="lbl">Q50 ÷ MSRP</span>
+ <span class="val premium-val"><%= data.premium_to_msrp.toFixed(2) %>×</span>
+ </li>
+ <% } %>
+ </ul>
+ <% if (!data.msrp) { %>
+ <p class="muted">
+ Hermès sells the Birkin/Kelly/Constance exclusively in stores —
+ MSRP backfill comes from boutique receipts, specialist trackers, or
+ client uploads. See <a href="/methodology">METHODOLOGY § 1</a>.
+ </p>
+ <% } %>
+ </section>
+ <% } %>
+
<% if (data.confidence_breakdown && Object.keys(data.confidence_breakdown).length) { %>
<section class="breakdown">
<h2>Confidence breakdown</h2>
← 44cae3a yolo tick #7: immutable valuation_calls audit log + middlewa
·
back to Lifestyle Asset Intel
·
yolo tick #9: OpenAPI 3.1 spec at /api/openapi.json 8639f38 →