import type { Metadata } from 'next'; import { unstable_cache } from 'next/cache'; import Link from 'next/link'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { PageHeader } from '@/components/ui/page-header'; import { Card, CardHeader, Table, th, td, tdNum, Badge, EmptyState, Stat } from '@/components/ui/primitives'; import { getSiteStats } from '@/lib/queries/site'; import { getConnectorCoverage, getFxCoverage } from '@/lib/queries/market-lists'; import { getMarketRows } from '@/lib/queries/markets'; import { fmtNum, fmtRelative, cn } from '@/lib/format'; import { catName, humanize } from '@/lib/taxonomy'; export const metadata: Metadata = { title: 'Data & methodology', description: 'Coverage by category, source and connector; RareIndex methodology; exports and API access.' }; // Coverage aggregates over millions of rows take ~70 s: prerender at build (staticPageGenerationTimeout raised), then hourly ISR. // Rendered on demand and cached for an hour at runtime: the coverage aggregates run over millions of // rows and exceeded the 240 s prerender budget at build time, which broke deployments. export const dynamic = 'force-dynamic'; const loadDataPage = unstable_cache(async () => Promise.all([getSiteStats(), getConnectorCoverage(), getFxCoverage(), getMarketRows()]), ['data-page-v1'], { revalidate: 3600 }); async function loadMethodology(): Promise { const candidates = [path.resolve(process.cwd(), '../../docs/METHODOLOGY.md'), path.resolve(process.cwd(), 'docs/METHODOLOGY.md'), path.resolve(process.cwd(), '../docs/METHODOLOGY.md')]; for (const p of candidates) { try { return await readFile(p, 'utf8'); } catch { /* try next */ } } return ''; } /** Minimal markdown → JSX for headings, paragraphs and bullet lists (methodology doc is controlled content). */ function Markdown({ text }: { text: string }) { const blocks = text.split(/\n{2,}/); return (
{blocks.map((b, i) => { const t = b.trim(); if (!t) return null; if (t.startsWith('# ')) return

{t.slice(2)}

; if (t.startsWith('## ')) return

{t.slice(3)}

; if (t.startsWith('### ')) return

{t.slice(4)}

; if (t.startsWith('- ')) return ; return

; })}

); } function inline(s: string): string { return s.replace(/&/g, '&').replace(/$1').replace(/\*(.+?)\*/g, '$1').replace(/`(.+?)`/g, '$1'); } export default async function DataPage() { const [[stats, connectors, fx, rows], md] = await Promise.all([loadDataPage(), loadMethodology()]); const active = rows.filter((r) => r.counts.assets > 0); return (
{[ ['Assets', fmtNum(stats.assets)], ['Sales', fmtNum(stats.sales)], ['Observations', fmtNum(stats.observations)], ['Active listings', fmtNum(stats.activeListings)], ['Sources', fmtNum(stats.sources)], ['Connectors', fmtNum(stats.connectors)], ['Categories with data', `${active.length}/${rows.length}`], ['FX rates', fx.rows ? `${fmtNum(fx.rows)} · ${fx.first}→${fx.last}` : '—'], ].map(([k, v]) => (
))}
{connectors.length ? ( {connectors.map((c) => ( ))}
Connector Source Type Contributes Categories Trust Raw Sales Listings Observations Health Last success
{c.displayName} {c.sourceUrl ? ( {c.sourceName} ↗ ) : ( c.sourceName )} {humanize(c.sourceType)} {[c.supportsSold && 'sales', c.supportsListings && 'listings', c.supportsCatalog && 'catalog', c.supportsAuctions && 'auctions', c.supportsPopulation && 'population'].filter(Boolean).join(' · ') || '—'} {c.categories.map(catName).join(', ')} {Math.round(c.trustScore * 100)}% {fmtNum(c.rawRecords)} {fmtNum(c.sales)} {fmtNum(c.listings)} {fmtNum(c.observations)} {c.healthStatus ?? c.status} {c.lastSuccessAt ? fmtRelative(c.lastSuccessAt) : 'never'}
) : ( )}
{[...rows].sort((a, b) => b.counts.assets - a.counts.assets).map((r) => ( ))}
Category Assets Priced Sales Listings Observations Last sale
{r.node.name} P{r.node.phase} {fmtNum(r.counts.assets)} {fmtNum(r.counts.priced)} {fmtNum(r.counts.sales)} {fmtNum(r.counts.listings)} {fmtNum(r.counts.observations)} {r.counts.lastSaleAt ? fmtRelative(r.counts.lastSaleAt) : '—'}
{md ? : }

Exports & API

Professional and research users can export comparable sales, price histories and index series as CSV or JSON through the public API. Each export carries source attribution and the timestamp of the underlying observations.

  • GET /v1/assets/:id/sales?format=csv
  • GET /v1/assets/:id/history?format=csv
  • GET /v1/indices/:ticker/history?format=json
API documentation →

Corrections

Spotted a misidentified item, a duplicate or a wrong price? Every record links to its source; write to data@rareindex.io with the asset URL and we will review it against the audit log.

); }