← back to Lifestyle Asset Intel
views/asset.ejs
180 lines
<%- include('partials/head') %>
<main class="container">
<p class="crumbs"><a href="/">← Console</a></p>
<h1>
<%= data.asset.brand %>
<a href="/family/<%= (data.asset.family || '').toLowerCase() %>"><%= data.asset.family %></a>
<%= data.asset.size || '' %>
<% if (data.asset.attrs && data.asset.attrs.exotic) { %><span class="exotic-badge" title="Exotic skin or special edition">EXOTIC</span><% } %>
<% if (data.asset.attrs && data.asset.attrs.special_edition) { %><span class="edition-badge" title="Special edition: <%= data.asset.attrs.special_edition %>"><%= String(data.asset.attrs.special_edition).toUpperCase() %></span><% } %>
</h1>
<p class="muted">
<%= data.asset.material || '—' %> · <%= data.asset.color || '—' %> · <%= data.asset.hardware || '—' %>
· <%= data.asset.construction || '—' %> · <%= data.asset.region %>
</p>
<% if (data.latest_snapshot) { %>
<section class="snapshot">
<h2>Current valuation snapshot · <%= fmtDate(data.latest_snapshot.as_of) %></h2>
<div class="value-band big">
<div><span class="lbl">Q10</span><span class="val">$<%= money(data.latest_snapshot.q10) %></span></div>
<div class="mid"><span class="lbl">Q50</span><span class="val">$<%= money(data.latest_snapshot.q50) %></span></div>
<div><span class="lbl">Q90</span><span class="val">$<%= money(data.latest_snapshot.q90) %></span></div>
</div>
<ul class="kpis">
<li><span class="lbl">Liquidity</span><span class="val"><%= pct(data.latest_snapshot.liquidity_score) %></span></li>
<li><span class="lbl">Days-to-sell</span><span class="val"><%= data.latest_snapshot.expected_dts ?? '—' %></span></li>
<li><span class="lbl">Confidence</span><span class="val"><%= pct(data.latest_snapshot.confidence) %></span></li>
<li><span class="lbl">Auth risk</span><span class="val"><%= pct(data.latest_snapshot.auth_risk) %></span></li>
<li><span class="lbl">Comps</span><span class="val"><%= data.latest_snapshot.comp_count %></span></li>
<li><span class="lbl">Method</span><span class="val mono"><%= data.latest_snapshot.methodology_version %></span></li>
</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>
<table>
<thead><tr><th>Component</th><th>Weight</th></tr></thead>
<tbody>
<% Object.entries(data.confidence_breakdown).forEach(([k,v]) => { %>
<tr><td><%= k %></td><td class="num"><%= numFmt(v) %></td></tr>
<% }) %>
</tbody>
</table>
</section>
<% } %>
<% } else { %>
<p class="muted">No snapshot yet for this asset.</p>
<% } %>
<%
var _trendPoints = (data.comps || [])
.filter(function (c) { return c.transacted_at && c.gross != null; })
.map(function (c) { return { as_of: c.transacted_at, value: c.gross }; })
.sort(function (a, b) { return new Date(a.as_of) - new Date(b.as_of); });
%>
<section class="trend">
<h2>Price trend (comps)</h2>
<%- include('partials/sparkline', { points: _trendPoints, width: 480, height: 80 }) %>
</section>
<section class="comps">
<h2>Comparable transactions (<%= data.comps.length %>)</h2>
<% if (data.comps.length) { %>
<table>
<thead><tr><th>Date</th><th>Source</th><th>Tier</th><th>Cond.</th><th>Gross</th><th>Net</th></tr></thead>
<tbody>
<% data.comps.forEach(c => { %>
<tr>
<td><%= fmtDate(c.transacted_at) %></td>
<td>
<%= c.source %><% if (c.source_url) { %>
<a class="cite" href="<%= c.source_url %>" target="_blank" rel="noopener noreferrer nofollow" title="Open source page (new tab)">↗ cite</a>
<% } %>
</td>
<td>T<%= c.source_tier %></td>
<td>
<%= grade(c.condition_grade) %>
<%- facetChips(c.condition_facets) %>
</td>
<td class="num">$<%= money(c.gross) %></td>
<td class="num">$<%= money(c.net) %></td>
</tr>
<% }) %>
</tbody>
</table>
<% } else { %>
<p class="muted">No comps yet.</p>
<% } %>
</section>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "<%= data.asset.brand %> <%= data.asset.family %> <%= data.asset.size || '' %>",
"brand": { "@type": "Brand", "name": "<%= data.asset.brand %>" },
"category": "<%= data.asset.family %>",
"color": "<%= data.asset.color || '' %>",
"material": "<%= data.asset.material || '' %>",
"offers": {
"@type": "AggregateOffer",
"priceCurrency": "USD",
"lowPrice": "<%= data.latest_snapshot ? data.latest_snapshot.q10 : '' %>",
"highPrice": "<%= data.latest_snapshot ? data.latest_snapshot.q90 : '' %>",
"offerCount": "<%= data.latest_snapshot ? data.latest_snapshot.comp_count : 0 %>"
}
}
</script>
</main>
<%
function money(v){ if(v==null||v==='') return '—'; var n=parseFloat(v); return isFinite(n)?n.toLocaleString('en-US',{maximumFractionDigits:0}):'—'; }
function pct(v){ if(v==null||v==='') return '—'; var n=parseFloat(v); return isFinite(n)?Math.round(n*100)+'%':'—'; }
function numFmt(v){ if(v==null) return '—'; var n=parseFloat(v); return isFinite(n)?n.toFixed(2):String(v); }
function fmtDate(v){ if(!v) return '—'; try { return new Date(v).toISOString().slice(0,10); } catch(e){ return String(v); } }
function grade(g){ const labels=['As Is','Fair','Good','Very Good','Excellent','Pristine','Giftable']; return (g==null) ? '—' : (labels[g] || g); }
function htmlEscape(s){ return String(s).replace(/[&<>"']/g, function(ch){ return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[ch]; }); }
function facetLabel(k){ return k.replace(/_/g,' '); }
function facetValue(v){
if (v === true) return '✓';
if (v === false) return '✗';
if (v == null) return '—';
return String(v);
}
function facetChips(f){
if (!f || typeof f !== 'object') return '';
var keys = Object.keys(f);
if (!keys.length) return '';
var html = '<div class="facets-row">';
for (var i=0;i<keys.length;i++){
var k = keys[i], v = f[k];
var cls = 'facet-chip';
if (v === true) cls += ' facet-true';
else if (v === false) cls += ' facet-false';
html += '<span class="' + cls + '" title="' + htmlEscape(k) + ': ' + htmlEscape(facetValue(v)) + '">';
html += '<span class="facet-k">' + htmlEscape(facetLabel(k)) + '</span>';
if (typeof v !== 'boolean') html += '<span class="facet-v">' + htmlEscape(facetValue(v)) + '</span>';
else html += '<span class="facet-v">' + htmlEscape(facetValue(v)) + '</span>';
html += '</span>';
}
html += '</div>';
return html;
}
%>
<%- include('partials/foot') %>