← 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 ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[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') %>