SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%

Valuation safety (phase 1): ask-vs-RIV anomaly gate, variant-aware discounts, grade parsing fixes

- scores: assessAsk()/dealScore()/plausibility gates — an ask is compared only with a
  transaction-based RIV of the SAME variant (>=5 sales, confidence >=0.5, match >=0.7);
  asks <10% or >10x RIV are flagged riv_anomaly, never surfaced as deals
- valuation worker: discounts reset each pass, representative variant prefers the default
  (raw/base) variant when it qualifies, changes measured on the representative variant's
  own series, no cross-variant fallback for value_opportunity, numeric(8,6) overflow guard
- taxonomy: parse PSA/DNA n, (8.5), Authentic-only slabs; a grader without a readable grade
  gets its own 'grader|unknown' variant instead of polluting raw
- resolver/writers: refineGrade() re-parses titles for sales, listings, observations, lots
- web: VsRiv primitive (Data/identity anomaly label), opportunity/gainers/losers/listings
  sort + filter directions fixed (negative = below RIV), scanner verdict via assessAsk with FX
- indices: RARE publishes only when its 25-constituent minimum is met
- security: atomic ai_quotas upsert with unique key, scanner choose ownership check,
  anonymous research thread authz
- db: migration 0006 (hot-path indexes, ai_quotas unique); ri regrade repair command

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 13 days ago (Sep 11, 2026) parent 1f956d0

31 changed files +9,556 −101

modified apps/web/package.json +1 −0
@@ -18,6 +18,7 @@
18 18 "@rareindex/search": "workspace:*",
19 19 "@rareindex/shared": "workspace:*",
20 20 "@rareindex/taxonomy": "workspace:*",
21 + "@rareindex/valuation": "workspace:*",
21 22 "@tanstack/react-query": "^5.90.0",
22 23 "drizzle-orm": "^0.45.0",
23 24 "lucide-react": "^1.0.0",
modified apps/web/src/app/api/scanner/[id]/choose/route.ts +4 −1
@@ -1,6 +1,7 @@
1 1 import { NextResponse } from 'next/server';
2 2 import { z } from 'zod';
3 3 import { chooseCandidate } from '@/lib/ai/scanner';
4 +import { clientIpHash, currentUserId, getAnonId } from '@/lib/ai/request';
4 5
5 6 export const runtime = 'nodejs';
6 7
@@ -8,6 +9,8 @@ export async function POST(req: Request, ctx: { params: Promise<{ id: string }>
8 9 const { id } = await ctx.params;
9 10 const body = z.object({ assetId: z.string().nullable() }).safeParse(await req.json().catch(() => ({})));
10 11 if (!body.success || !/^scan_[a-z0-9]+$/.test(id)) return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
11 − await chooseCandidate(id, body.data.assetId);
12 + const [userId, anonId, ipHash] = await Promise.all([currentUserId(), getAnonId(false), clientIpHash()]);
13 + const ok = await chooseCandidate(id, body.data.assetId, { userId, anonId, ipHash });
14 + if (!ok) return NextResponse.json({ error: 'Not found' }, { status: 404 });
12 15 return NextResponse.json({ ok: true });
13 16 }
modified apps/web/src/app/page.tsx +1 −1
@@ -205,7 +205,7 @@ async function Discovery() {
205 205 <RadarRail items={radar} hideWhenEmpty />
206 206 <LotsRail items={lots} hideWhenEmpty />
207 207 <AssetRail title="Newly Added" subtitle="Latest canonical assets — guide prices labelled, RIV when available" href="/explore?sort=newest" items={newest} metric="guide" />
208 − {opportunities.length ? <AssetRail title="Value Opportunities" subtitle="Listings materially below RIV — analytical data, not advice" href="/listings?sort=discount" items={opportunities} metric="opportunity" /> : null}
208 + {opportunities.length ? <AssetRail title="Value Opportunities" subtitle="Asks ≥ 10 % below a transaction-based RIV of the same variant (≥ 5 sales, medium+ confidence). Analytical data, not advice; implausible asks are held back as data anomalies." href="/listings?sort=discount&disc=10" items={opportunities} metric="opportunity" /> : null}
209 209 {watched.length ? <AssetRail title="Most Watched" subtitle="Assets on the most watchlists" href="/explore?sort=trending" items={watched} metric="watchers" /> : null}
210 210 <MarketInsights rows={markets} />
211 211 </>
modified apps/web/src/components/asset/sales-cards.tsx +2 −5
@@ -1,7 +1,7 @@
1 1 import type { SaleRow, ListingRow } from '@/lib/queries/assets';
2 2 import { fmtDate, fmtMoney, fmtRelative, cn } from '@/lib/format';
3 3 import { humanize } from '@/lib/taxonomy';
4 −import { Badge, Delta, EmptyState } from '@/components/ui/primitives';
4 +import { Badge, Delta, EmptyState, VsRiv } from '@/components/ui/primitives';
5 5 import { GradeBadge, SourceLink, Thumb } from '@/components/market/bits';
6 6
7 7 /** Phone-first card lists for sales and listings (tables stay for md+). */
@@ -48,10 +48,7 @@ export function ListingsCards({ items, className, emptyDescription = 'Live listi
48 48 <div className="flex items-baseline justify-between gap-2">
49 49 <span className="num text-[15px] font-semibold text-fg">{fmtMoney(l.priceUsd)}</span>
50 50 {l.discountToRiv !== null ? (
51 − <span className="inline-flex items-center gap-1 text-[11px]">
52 − <span className="text-subtle">vs RIV</span>
53 − <Delta value={-l.discountToRiv} />
54 − </span>
51 + <VsRiv discount={l.discountToRiv} className="text-[11px]" />
55 52 ) : (
56 53 <span className="text-[11px] text-subtle">{fmtRelative(l.lastSeenAt)}</span>
57 54 )}
modified apps/web/src/components/market/asset-list.tsx +3 −3
@@ -2,7 +2,7 @@ import Link from 'next/link';
2 2 import type { AssetCard as AssetCardData } from '@/lib/queries/assets';
3 3 import { fmtMoney, fmtNum, fmtRelative, confidenceLabel, cn } from '@/lib/format';
4 4 import { catName } from '@/lib/taxonomy';
5 −import { Delta, Table, th, td, tdNum, EmptyState } from '@/components/ui/primitives';
5 +import { Delta, Table, th, td, tdNum, EmptyState, VsRiv } from '@/components/ui/primitives';
6 6 import { Thumb } from './bits';
7 7 import { PriceLine, TileImage } from './asset-tile';
8 8
@@ -81,7 +81,7 @@ function cell(a: AssetCardData, c: AssetColumn) {
81 81 case 'trending':
82 82 return a.trendingScore === null ? <span className="text-subtle">—</span> : a.trendingScore.toFixed(1);
83 83 case 'opportunity':
84 − return a.valueOpportunity === null ? <span className="text-subtle">—</span> : <Delta value={-a.valueOpportunity} />;
84 + return <VsRiv discount={a.valueOpportunity} showLabel={false} />;
85 85 case 'watchers':
86 86 return fmtNum(a.watchers);
87 87 case 'updated':
@@ -142,7 +142,7 @@ export function AssetCard({ a, className, metric = 'change30d' }: { a: AssetCard
142 142 metric === 'latestSale' ? (
143 143 a.latestSaleUsd !== null && a.rivUsd !== null ? <span className="num text-[11px] text-muted">last {fmtMoney(a.latestSaleUsd)}</span> : a.number ? <span className="num text-[11px] text-subtle">#{a.number}</span> : null
144 144 ) : metric === 'opportunity' ? (
145 − a.valueOpportunity !== null ? <span className="text-[11px] text-muted">vs RIV <Delta value={-a.valueOpportunity} className="text-[11px]" /></span> : null
145 + a.valueOpportunity !== null ? <VsRiv discount={a.valueOpportunity} className="text-[11px]" /> : null
146 146 ) : metric === 'trending' ? (
147 147 a.trendingScore !== null ? <span className="num text-[11px] text-muted">trend {a.trendingScore.toFixed(1)}</span> : null
148 148 ) : value !== null ? (
modified apps/web/src/components/market/asset-tile.tsx +2 −4
@@ -3,7 +3,7 @@ import type { ReactNode } from 'react';
3 3 import type { AssetCard as AssetCardData } from '@/lib/queries/assets';
4 4 import { cn, confidenceLabel, fmtMoney, fmtNum, fmtRelative } from '@/lib/format';
5 5 import { catName } from '@/lib/taxonomy';
6 −import { Delta } from '@/components/ui/primitives';
6 +import { Delta, VsRiv } from '@/components/ui/primitives';
7 7
8 8 export type TileMetric = 'change7d' | 'change1d' | 'change30d' | 'trending' | 'watchers' | 'riv' | 'opportunity' | 'new' | 'guide' | 'latestSale';
9 9
@@ -61,9 +61,7 @@ function MetricNode({ a, metric }: { a: AssetCardData; metric: TileMetric }) {
61 61 return <span className="num text-[11px] text-muted">{fmtNum(a.watchers)} watching</span>;
62 62 case 'opportunity':
63 63 return (
64 − <span className="text-[11px] text-muted">
65 − vs RIV <Delta value={a.valueOpportunity === null ? null : -a.valueOpportunity} className="text-[12px]" />
66 − </span>
64 + <VsRiv discount={a.valueOpportunity} className="text-[12px]" />
67 65 );
68 66 case 'new':
69 67 return <span className="text-[11px] text-muted">added {fmtRelative(a.updatedAt)}</span>;
modified apps/web/src/components/market/sales-table.tsx +3 −3
@@ -2,7 +2,7 @@ import type { SaleRow, ListingRow } from '@/lib/queries/assets';
2 2 import type { LotRow } from '@/lib/queries/market-lists';
3 3 import { fmtDate, fmtMoney, fmtRelative, fmtPct, cn } from '@/lib/format';
4 4 import { humanize } from '@/lib/taxonomy';
5 −import { Table, th, td, tdNum, EmptyState, Badge, Delta } from '@/components/ui/primitives';
5 +import { Table, th, td, tdNum, EmptyState, Badge, Delta, VsRiv } from '@/components/ui/primitives';
6 6 import { AssetTitleCell, GradeBadge, PriceCell, SourceLink, Thumb } from './bits';
7 7
8 8 /** Card list for sales on small screens (image, title, grade, price, source). */
@@ -119,7 +119,7 @@ export function ListingsCards({ items, className }: { items: ListingRow[]; class
119 119 </span>
120 120 <span className="num flex shrink-0 flex-col items-end leading-tight">
121 121 <span className="text-[14px] font-semibold text-fg">{fmtMoney(l.priceUsd)}</span>
122 − {l.discountToRiv !== null ? <Delta value={-l.discountToRiv} className="text-[11px]" /> : <span className="text-[10px] text-subtle">{humanize(l.listingType)}</span>}
122 + {l.discountToRiv !== null ? <VsRiv discount={l.discountToRiv} className="text-[11px]" showLabel={false} /> : <span className="text-[10px] text-subtle">{humanize(l.listingType)}</span>}
123 123 </span>
124 124 </a>
125 125 </li>
@@ -186,7 +186,7 @@ export function ListingsTable({ items, showAsset = true, className, emptyDescrip
186 186 <span className="text-subtle" title="No valuation to compare against">—</span>
187 187 ) : (
188 188 <span className="inline-flex flex-col items-end leading-tight">
189 − <Delta value={-l.discountToRiv} />
189 + <VsRiv discount={l.discountToRiv} showLabel={false} />
190 190 {l.assetRivUsd !== undefined && l.assetRivUsd !== null ? <span className="text-[10px] text-subtle">RIV {fmtMoney(l.assetRivUsd)}</span> : null}
191 191 </span>
192 192 )}
modified apps/web/src/components/scanner/scanner-client.tsx +1 −1
@@ -260,7 +260,7 @@ function ScanResults({ r, chosen, onChoose }: { r: ScanResponse; chosen: string
260 260 <CardHeader title="Parsed listing" subtitle={r.listing.sourceId} action={<a href={r.listing.sourceUrl} target="_blank" rel="noreferrer noopener" className="text-muted hover:text-fg">Open source ↗</a>} />
261 261 <div className="grid gap-4 p-4 sm:grid-cols-3">
262 262 <Stat label="Asking price" value={r.listing.price !== null ? fmtMoney(r.listing.price, r.listing.currency ?? 'USD') : '—'} sub={r.listing.rawTitle} />
263 − <Stat label="vs RareIndex Valuation" value={r.listing.discountToRiv !== null ? <span className={r.listing.discountToRiv < 0 ? 'text-gain' : 'text-loss'}>{fmtPct(r.listing.discountToRiv, 1)}</span> : '—'} sub={{ below_fair_value: 'Below fair value range', in_range: 'Within fair value range', above_fair_value: 'Above fair value range', unknown: 'No valuation to compare' }[r.listing.verdict]} />
263 + <Stat label="vs RareIndex Valuation" value={r.listing.verdict === 'anomaly' ? <span className="rounded-sm bg-alert-bg px-1.5 py-0.5 text-[11px] font-medium uppercase tracking-wide text-alert">Data/identity anomaly</span> : r.listing.discountToRiv !== null ? <span className={r.listing.discountToRiv <= -0.1 ? 'text-gain' : r.listing.discountToRiv >= 0.1 ? 'text-loss' : 'text-flat'}>{fmtPct(r.listing.discountToRiv, 1)}</span> : '—'} sub={{ below_fair_value: 'Below fair value range', in_range: 'Within fair value range', above_fair_value: 'Above fair value range', anomaly: 'Implausible against the valuation — review the variant, lot, currency and identity', unknown: 'No valuation to compare' }[r.listing.verdict] ?? 'No valuation to compare'} />
264 264 <Stat label="Grade on listing" value={r.listing.grader ? `${r.listing.grader.toUpperCase()} ${r.listing.grade ?? ''}` : 'Raw / unknown'} />
265 265 </div>
266 266 </Card>
modified apps/web/src/components/ui/primitives.tsx +25 −0
@@ -38,6 +38,31 @@ export function Delta({ value, digits = 2, className, arrow = false }: { value:
38 38 );
39 39 }
40 40
41 +/**
42 + * Ask vs RareIndex Valuation (§83–§84, §196). `discount` = (ask − RIV) / RIV as stored: negative = the ask
43 + * is below the valuation. Anything implausible (ask < 10 % or > 10× RIV) is a data/identity anomaly and is
44 + * labelled as such instead of being shown as a percentage — never as a "deal".
45 + */
46 +export function VsRiv({ discount, className, digits = 1, showLabel = true }: { discount: number | null | undefined; className?: string; digits?: number; showLabel?: boolean }) {
47 + if (discount === null || discount === undefined || !Number.isFinite(discount)) return <span className={cn('text-subtle', className)} title="No comparable valuation for this variant">—</span>;
48 + const ratio = 1 + discount;
49 + if (ratio < 0.1 || ratio > 10) {
50 + return (
51 + <span className={cn('inline-flex items-center gap-1 rounded-sm bg-alert-bg px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-alert', className)} title="The asking price is implausible against this valuation (wrong variant, lot, currency or identity). Not a deal until reviewed.">
52 + Data/identity anomaly
53 + </span>
54 + );
55 + }
56 + const tone = discount <= -0.1 ? 'text-gain' : discount >= 0.1 ? 'text-loss' : 'text-flat';
57 + const word = discount <= -0.1 ? 'below RIV' : discount >= 0.1 ? 'above RIV' : 'near RIV';
58 + return (
59 + <span className={cn('num inline-flex items-baseline gap-1 font-medium', tone, className)} title={`Asking price ${fmtPct(Math.abs(discount), digits, false)} ${word}. Analytical data, not advice.`}>
60 + {fmtPct(discount, digits)}
61 + {showLabel ? <span className="text-[0.85em] font-normal text-subtle">vs RIV</span> : null}
62 + </span>
63 + );
64 +}
65 +
41 66 export function Badge({ children, tone = 'neutral', className, dot = false }: { children: ReactNode; tone?: 'neutral' | 'gain' | 'loss' | 'index' | 'rarity' | 'alert' | 'gold'; className?: string; dot?: boolean }) {
42 67 const tones: Record<string, string> = {
43 68 neutral: 'bg-inset text-muted',
modified apps/web/src/lib/ai/request.ts +8 −8
@@ -2,7 +2,7 @@ import 'server-only';
2 2 import { createHmac, randomUUID } from 'node:crypto';
3 3 import { cookies, headers } from 'next/headers';
4 4 import { getCurrentUser } from '@/lib/auth/session';
5 −import { getDb, aiQuotas, sql } from '@rareindex/database';
5 +import { getDb, sql } from '@rareindex/database';
6 6
7 7 const SECRET = () => process.env.SESSION_SECRET ?? 'dev-only';
8 8
@@ -56,13 +56,13 @@ export async function consumeQuota(feature: keyof typeof QUOTAS, opts: { userId:
56 56 const limit = opts.userId ? QUOTAS[feature]!.user : QUOTAS[feature]!.anonymous;
57 57 const key = opts.userId ? `user:${opts.userId}` : `ip:${opts.ipHash}`;
58 58 const date = new Date().toISOString().slice(0, 10);
59 − const db = getDb();
60 − const [row] = await db.execute(sql`select count from ai_quotas where key = ${key} and feature = ${feature} and date = ${date} limit 1`);
61 − const used = Number((row as { count?: number } | undefined)?.count ?? 0);
62 − if (used >= limit) return { ok: false, used, limit };
63 − if (row) await db.execute(sql`update ai_quotas set count = count + 1 where key = ${key} and feature = ${feature} and date = ${date}`);
64 − else await db.insert(aiQuotas).values({ key, feature, date, count: 1 });
65 − return { ok: true, used: used + 1, limit };
59 + // Atomic increment (unique (key, feature, date)): concurrent requests can no longer create
60 + // duplicate counters and slip past the limit.
61 + const [row] = await getDb().execute(sql`insert into ai_quotas (key, feature, date, count) values (${key}, ${feature}, ${date}, 1)
62 + on conflict (key, feature, date) do update set count = ai_quotas.count + 1 returning count`);
63 + const used = Number((row as { count?: number } | undefined)?.count ?? 1);
64 + if (used > limit) return { ok: false, used: used - 1, limit };
65 + return { ok: true, used, limit };
66 66 }
67 67
68 68 export function aiErrorMessage(err: unknown): { status: number; message: string } {
modified apps/web/src/lib/ai/research-tools.ts +2 −1
@@ -203,11 +203,12 @@ export async function executeTool(name: string, rawInput: unknown): Promise<unkn
203 203 const i = input as z.infer<typeof TOOL_SCHEMAS.screen>;
204 204 const col = sql.raw(`s.${WINDOWS[i.window as Window]}`);
205 205 const sortCol = { change: col, value: sql`s.riv_usd`, liquidity: sql`s.liquidity_score`, sales: sql`s.sales_count`, trending: sql`s.trending_score`, opportunity: sql`s.value_opportunity` }[i.sort as string] ?? col;
206 + const sortDir = i.sort === 'opportunity' ? sql`ASC NULLS LAST` : sql`DESC NULLS LAST`; // opportunity: most below RIV first
206 207 const rows = await run(sql`select ${ASSET_COLS} from assets a join asset_stats s on s.asset_id = a.id where s.riv_sample_size >= ${i.min_sales} ${scope(i.category)}
207 208 ${i.min_price_usd != null ? sql`and s.riv_usd >= ${i.min_price_usd}` : sql``} ${i.max_price_usd != null ? sql`and s.riv_usd <= ${i.max_price_usd}` : sql``}
208 209 ${i.min_change != null ? sql`and ${col} >= ${i.min_change}` : sql``} ${i.max_change != null ? sql`and ${col} <= ${i.max_change}` : sql``}
209 210 ${i.max_sales_1y != null ? sql`and s.sales_1y <= ${i.max_sales_1y}` : sql``} ${i.min_liquidity != null ? sql`and s.liquidity_score >= ${i.min_liquidity}` : sql``}
210 − order by ${sortCol} desc nulls last limit ${i.limit}`);
211 + order by ${sortCol} ${sortDir} limit ${i.limit}`);
211 212 return { filters: i, count: rows.length, results: rows };
212 213 }
213 214 case 'record_sales': {
modified apps/web/src/lib/ai/research.ts +3 −1
@@ -48,7 +48,9 @@ export async function loadSession(sessionId: string, owner: { anonId: string | n
48 48 const [s] = await db.select().from(researchSessions).where(eq(researchSessions.id, sessionId)).limit(1);
49 49 if (!s) return null;
50 50 if (s.userId && s.userId !== owner.userId) return null;
51 − if (!s.userId && s.anonId && s.anonId !== owner.anonId) return null;
51 + // anonymous threads are readable only by the browser that created them; a thread without an
52 + // anon id (cookie could not be set) is not readable by anyone afterwards
53 + if (!s.userId && (!owner.anonId || s.anonId !== owner.anonId)) return null;
52 54 const messages = await db.select().from(researchMessages).where(eq(researchMessages.sessionId, sessionId)).orderBy(researchMessages.createdAt);
53 55 return { session: s, messages };
54 56 }
modified apps/web/src/lib/ai/scanner.ts +21 −5
@@ -5,6 +5,8 @@ import { getGrader, parseGradeFromTitle } from '@rareindex/taxonomy';
5 5 import { identifyCollectible, getRouter, type ImageInput, type Identification } from '@rareindex/ai';
6 6 import { lookupViaApi } from './lookup';
7 7 import { findCandidates, assetContext, type Candidate } from './candidates';
8 +import { assessAsk } from '@rareindex/valuation';
9 +import { toUsdAt } from '@/lib/account/fx';
8 10
9 11 export interface ScanResult {
10 12 sessionId: string;
@@ -33,7 +35,7 @@ export interface ListingSummary {
33 35 identifiers: Record<string, string>;
34 36 /** price vs RIV when both known: negative = below fair value */
35 37 discountToRiv: number | null;
36 − verdict: 'below_fair_value' | 'in_range' | 'above_fair_value' | 'unknown';
38 + verdict: 'below_fair_value' | 'in_range' | 'above_fair_value' | 'anomaly' | 'unknown';
37 39 }
38 40
39 41 interface ScanInput {
@@ -104,9 +106,16 @@ export async function runScan(input: ScanInput): Promise<ScanResult> {
104 106 const best = top && top.score >= 0.25 ? { ...top, context: await assetContext(top.assetId) } : null;
105 107 if (!best) notes.push(candidates.length ? 'Low-confidence match: review the candidates below.' : 'No matching asset in the RareIndex catalog yet. The pipeline adds assets as connectors ingest sources.');
106 108
107 − if (listing && best?.rivUsd && listing.price !== null && listing.currency === 'USD') {
108 − listing.discountToRiv = (listing.price - best.rivUsd) / best.rivUsd;
109 − listing.verdict = listing.discountToRiv <= -0.15 ? 'below_fair_value' : listing.discountToRiv >= 0.2 ? 'above_fair_value' : 'in_range';
109 + if (listing && best?.rivUsd && listing.price !== null && listing.currency) {
110 + // Compare in USD at today's ECB rate; gate exactly like the valuation worker (transaction-based RIV,
111 + // ≥ 5 sales, medium+ confidence) and refuse implausible ratios instead of calling them deals.
112 + const fx = listing.currency === 'USD' ? { usd: listing.price } : await toUsdAt(listing.price, listing.currency, null);
113 + const gradedMismatch = Boolean(listing.grader && listing.grader !== 'raw' && best.context.variants.length && !best.context.variants.some((v) => (v.grader as string | null) === listing.grader && (v.grade as string | null) === listing.grade));
114 + const a = assessAsk({ askUsd: fx?.usd ?? null, rivUsd: best.rivUsd, confidence: best.rivConfidence, sampleSize: best.rivSampleSize, sameVariant: gradedMismatch ? false : undefined });
115 + listing.discountToRiv = a.discount;
116 + listing.verdict = a.verdict === 'deal' ? 'below_fair_value' : a.verdict === 'premium' ? 'above_fair_value' : a.verdict === 'fair' ? 'in_range' : a.verdict === 'anomaly' ? 'anomaly' : 'unknown';
117 + if (a.verdict === 'anomaly') notes.push('The asking price is implausible against the matched valuation (below 10 % or above 10× RIV): most likely a different variant, a lot, a currency or an identity mismatch — review before treating it as a deal.');
118 + else if (a.verdict === 'ungated') notes.push(`The ask was not compared with the valuation (${a.reasons.join(', ')}).`);
110 119 }
111 120
112 121 const durationMs = Date.now() - started;
@@ -134,8 +143,15 @@ export async function runScan(input: ScanInput): Promise<ScanResult> {
134 143 return { sessionId: id, mode: input.mode, guess, guessConfidence: guess?.confidence ?? null, candidates, best, listing, model, usdEst, durationMs, notes };
135 144 }
136 145
137 −export async function chooseCandidate(sessionId: string, assetId: string | null): Promise<void> {
146 +/** Record the user's confirmation of a candidate. Only the session's owner (user, anon cookie or, failing both, the same IP hash) may write it. */
147 +export async function chooseCandidate(sessionId: string, assetId: string | null, owner: { userId: string | null; anonId: string | null; ipHash: string }): Promise<boolean> {
148 + const [s] = await getDb().select({ userId: scannerSessions.userId, anonId: scannerSessions.anonId, ipHash: scannerSessions.ipHash, candidates: scannerSessions.candidates }).from(scannerSessions).where(eq(scannerSessions.id, sessionId)).limit(1);
149 + if (!s) return false;
150 + const owns = s.userId ? s.userId === owner.userId : s.anonId ? s.anonId === owner.anonId : s.ipHash === owner.ipHash;
151 + if (!owns) return false;
152 + if (assetId !== null && !s.candidates.some((c) => c.assetId === assetId)) return false; // only a proposed candidate can be chosen
138 153 await getDb().update(scannerSessions).set({ chosenAssetId: assetId, chosenAt: new Date() }).where(eq(scannerSessions.id, sessionId));
154 + return true;
139 155 }
140 156
141 157 /** Resolve a marketplace URL through the connector framework (lookup → normalize), via the API service. */
modified apps/web/src/lib/queries/assets.ts +8 −4
@@ -135,7 +135,7 @@ const SORTS: Record<ExploreSort, SQL> = {
135 135 trending: sql`s.trending_score DESC NULLS LAST`,
136 136 newest: sql`a.created_at DESC`,
137 137 latest_sale: sql`s.latest_sale_at DESC NULLS LAST`,
138 − opportunity: sql`s.value_opportunity DESC NULLS LAST`,
138 + opportunity: sql`s.value_opportunity ASC NULLS LAST`, // most below RIV first
139 139 name: sql`a.name ASC`,
140 140 number: sql`a.set_name ASC NULLS LAST, (substring(a.number from '^[0-9]+'))::int ASC NULLS LAST, a.number ASC NULLS LAST`,
141 141 };
@@ -193,13 +193,17 @@ export async function rankedAssets(kind: RankedKind, opts: { scope?: string[]; l
193 193 const chg = opts.window === '1d' ? sql`s.change_1d` : opts.window === '30d' ? sql`s.change_30d` : sql`s.change_7d`;
194 194 const spec: Record<typeof kind, { where: SQL; order: SQL }> = {
195 195 trending: { where: sql`s.trending_score IS NOT NULL`, order: sql`s.trending_score DESC` },
196 − gainers: { where: sql`${chg} IS NOT NULL AND ${chg} > 0 AND s.riv_sample_size >= 3`, order: sql`${chg} DESC` },
197 − losers: { where: sql`${chg} IS NOT NULL AND ${chg} < 0 AND s.riv_sample_size >= 3`, order: sql`${chg} ASC` },
196 + // Movers need a valuation that can move: ≥ 5 transactions, medium confidence, a plausible move (≤ ±500 %) and
197 + // at least one sale in the last year (a stale series cannot "move").
198 + gainers: { where: sql`${chg} IS NOT NULL AND ${chg} > 0 AND ${chg} <= 5 AND s.riv_sample_size >= 5 AND s.riv_confidence >= 0.5 AND coalesce(s.sales_1y, 0) >= 3`, order: sql`${chg} DESC` },
199 + losers: { where: sql`${chg} IS NOT NULL AND ${chg} < 0 AND ${chg} >= -0.99 AND s.riv_sample_size >= 5 AND s.riv_confidence >= 0.5 AND coalesce(s.sales_1y, 0) >= 3`, order: sql`${chg} ASC` },
198 200 volume: { where: sql`s.volume_30d_usd IS NOT NULL AND s.volume_30d_usd > 0`, order: sql`s.volume_30d_usd DESC` },
199 201 watched: { where: sql`s.watchers > 0`, order: sql`s.watchers DESC, s.views_30d DESC` },
200 202 newest: { where: sql`true`, order: sql`a.created_at DESC` },
201 203 expensive: { where: sql`s.riv_usd IS NOT NULL`, order: sql`s.riv_usd DESC` },
202 − opportunity: { where: sql`s.value_opportunity IS NOT NULL AND s.value_opportunity > 0`, order: sql`s.value_opportunity DESC` },
204 + // value_opportunity = (best gated ask − RIV) / RIV: negative = below the valuation. A deal is ≤ −10 %; the
205 + // worker never stores implausible ratios, the ≥ −0.9 bound is a second safety net (§84, §196).
206 + opportunity: { where: sql`s.value_opportunity IS NOT NULL AND s.value_opportunity <= -0.1 AND s.value_opportunity >= -0.9 AND s.riv_sample_size >= 5 AND s.riv_confidence >= 0.5`, order: sql`s.value_opportunity ASC` },
203 207 liquid: { where: sql`s.liquidity_score IS NOT NULL`, order: sql`s.liquidity_score DESC` },
204 208 // Fallback rails used while valuations are still being computed (§192: honest, data-backed)
205 209 newest_priced: { where: sql`s.riv_usd IS NOT NULL`, order: sql`s.updated_at DESC` },
modified apps/web/src/lib/queries/market-lists.ts +2 −2
@@ -97,14 +97,14 @@ export async function listListings(f: ListingsFilters): Promise<{ items: Listing
97 97 if (f.grader) where.push(sql`l.grader = ${f.grader}`);
98 98 if (f.minUsd != null) where.push(sql`l.price_usd >= ${f.minUsd}`);
99 99 if (f.maxUsd != null) where.push(sql`l.price_usd <= ${f.maxUsd}`);
100 − if (f.minDiscount != null) where.push(sql`l.discount_to_riv >= ${f.minDiscount}`);
100 + if (f.minDiscount != null) where.push(sql`l.discount_to_riv <= ${-Math.abs(f.minDiscount)}`); // stored negative = below RIV
101 101 if (f.listingType) where.push(sql`l.listing_type = ${f.listingType}`);
102 102 if (f.q && f.q.trim().length >= 2) where.push(sql`(a.title ILIKE ${'%' + f.q.trim() + '%'} OR l.raw_title ILIKE ${'%' + f.q.trim() + '%'})`);
103 103 const orders: Record<NonNullable<ListingsFilters['sort']>, SQL> = {
104 104 newest: sql`l.first_seen_at DESC`,
105 105 price_asc: sql`l.price_usd ASC NULLS LAST`,
106 106 price_desc: sql`l.price_usd DESC NULLS LAST`,
107 − discount: sql`l.discount_to_riv DESC NULLS LAST`,
107 + discount: sql`l.discount_to_riv ASC NULLS LAST`, // most below RIV first
108 108 ending: sql`l.ends_at ASC NULLS LAST`,
109 109 };
110 110 const r = await rows<Record<string, unknown>>(sql`
added packages/database/migrations/0006_skinny_kulan_gath.sql +13 −0
@@ -0,0 +1,13 @@
1 +DROP INDEX "ai_quotas_uq";--> statement-breakpoint
2 +-- collapse duplicate daily counters created by the former non-atomic read-then-write before the key becomes unique
3 +DELETE FROM "ai_quotas" a USING "ai_quotas" b WHERE a.key = b.key AND a.feature = b.feature AND a.date = b.date AND a.ctid < b.ctid;--> statement-breakpoint
4 +CREATE INDEX "asset_stats_change_7d_idx" ON "asset_stats" USING btree ("change_7d");--> statement-breakpoint
5 +CREATE INDEX "asset_stats_change_30d_idx" ON "asset_stats" USING btree ("change_30d");--> statement-breakpoint
6 +CREATE INDEX "asset_stats_change_1y_idx" ON "asset_stats" USING btree ("change_1y");--> statement-breakpoint
7 +CREATE INDEX "asset_stats_opportunity_idx" ON "asset_stats" USING btree ("value_opportunity") WHERE value_opportunity is not null;--> statement-breakpoint
8 +CREATE INDEX "asset_stats_sales_30d_idx" ON "asset_stats" USING btree ("sales_30d");--> statement-breakpoint
9 +CREATE INDEX "variant_stats_asset_idx" ON "variant_stats" USING btree ("asset_id");--> statement-breakpoint
10 +CREATE INDEX "listings_variant_avail_idx" ON "listings" USING btree ("variant_id","availability");--> statement-breakpoint
11 +CREATE INDEX "listings_discount_idx" ON "listings" USING btree ("discount_to_riv") WHERE discount_to_riv is not null and availability = 'available';--> statement-breakpoint
12 +CREATE INDEX "sales_valid_date_idx" ON "sales" USING btree ("sale_date") WHERE status = 'valid';--> statement-breakpoint
13 +CREATE UNIQUE INDEX "ai_quotas_uq" ON "ai_quotas" USING btree ("key","feature","date");
\ No newline at end of file
added packages/database/migrations/meta/0006_snapshot.json +9035 −0
@@ -0,0 +1,9035 @@
1 +{
2 + "id": "179d4792-c6f0-4fb6-b655-fb8cf497ddda",
3 + "prevId": "c11ee010-c4bb-44df-aeea-a93ad2c1d1bb",
4 + "version": "7",
5 + "dialect": "postgresql",
6 + "tables": {
7 + "public.brands": {
8 + "name": "brands",
9 + "schema": "",
10 + "columns": {
11 + "slug": {
12 + "name": "slug",
13 + "type": "text",
14 + "primaryKey": true,
15 + "notNull": true
16 + },
17 + "name": {
18 + "name": "name",
19 + "type": "text",
20 + "primaryKey": false,
21 + "notNull": true
22 + },
23 + "category_slugs": {
24 + "name": "category_slugs",
25 + "type": "text[]",
26 + "primaryKey": false,
27 + "notNull": true,
28 + "default": "'{}'::text[]"
29 + },
30 + "country": {
31 + "name": "country",
32 + "type": "text",
33 + "primaryKey": false,
34 + "notNull": false
35 + },
36 + "founded_year": {
37 + "name": "founded_year",
38 + "type": "integer",
39 + "primaryKey": false,
40 + "notNull": false
41 + },
42 + "metadata": {
43 + "name": "metadata",
44 + "type": "jsonb",
45 + "primaryKey": false,
46 + "notNull": true,
47 + "default": "'{}'::jsonb"
48 + },
49 + "created_at": {
50 + "name": "created_at",
51 + "type": "timestamp with time zone",
52 + "primaryKey": false,
53 + "notNull": true,
54 + "default": "now()"
55 + }
56 + },
57 + "indexes": {
58 + "brands_name_idx": {
59 + "name": "brands_name_idx",
60 + "columns": [
61 + {
62 + "expression": "name",
63 + "isExpression": false,
64 + "asc": true,
65 + "nulls": "last"
66 + }
67 + ],
68 + "isUnique": false,
69 + "concurrently": false,
70 + "method": "btree",
71 + "with": {}
72 + }
73 + },
74 + "foreignKeys": {},
75 + "compositePrimaryKeys": {},
76 + "uniqueConstraints": {},
77 + "policies": {},
78 + "checkConstraints": {},
79 + "isRLSEnabled": false
80 + },
81 + "public.categories": {
82 + "name": "categories",
83 + "schema": "",
84 + "columns": {
85 + "slug": {
86 + "name": "slug",
87 + "type": "text",
88 + "primaryKey": true,
89 + "notNull": true
90 + },
91 + "parent_slug": {
92 + "name": "parent_slug",
93 + "type": "text",
94 + "primaryKey": false,
95 + "notNull": false
96 + },
97 + "family_slug": {
98 + "name": "family_slug",
99 + "type": "text",
100 + "primaryKey": false,
101 + "notNull": true
102 + },
103 + "name": {
104 + "name": "name",
105 + "type": "text",
106 + "primaryKey": false,
107 + "notNull": true
108 + },
109 + "short_name": {
110 + "name": "short_name",
111 + "type": "text",
112 + "primaryKey": false,
113 + "notNull": false
114 + },
115 + "description": {
116 + "name": "description",
117 + "type": "text",
118 + "primaryKey": false,
119 + "notNull": false
120 + },
121 + "level": {
122 + "name": "level",
123 + "type": "integer",
124 + "primaryKey": false,
125 + "notNull": true,
126 + "default": 0
127 + },
128 + "phase": {
129 + "name": "phase",
130 + "type": "integer",
131 + "primaryKey": false,
132 + "notNull": true,
133 + "default": 3
134 + },
135 + "active": {
136 + "name": "active",
137 + "type": "boolean",
138 + "primaryKey": false,
139 + "notNull": true,
140 + "default": true
141 + },
142 + "condition_scale": {
143 + "name": "condition_scale",
144 + "type": "text",
145 + "primaryKey": false,
146 + "notNull": false
147 + },
148 + "graders": {
149 + "name": "graders",
150 + "type": "text[]",
151 + "primaryKey": false,
152 + "notNull": true,
153 + "default": "'{}'::text[]"
154 + },
155 + "index_ticker": {
156 + "name": "index_ticker",
157 + "type": "text",
158 + "primaryKey": false,
159 + "notNull": false
160 + },
161 + "attribute_schema": {
162 + "name": "attribute_schema",
163 + "type": "jsonb",
164 + "primaryKey": false,
165 + "notNull": true,
166 + "default": "'{}'::jsonb"
167 + },
168 + "sort_order": {
169 + "name": "sort_order",
170 + "type": "integer",
171 + "primaryKey": false,
172 + "notNull": true,
173 + "default": 0
174 + },
175 + "compliance_flags": {
176 + "name": "compliance_flags",
177 + "type": "text[]",
178 + "primaryKey": false,
179 + "notNull": true,
180 + "default": "'{}'::text[]"
181 + },
182 + "icon": {
183 + "name": "icon",
184 + "type": "text",
185 + "primaryKey": false,
186 + "notNull": false
187 + },
188 + "created_at": {
189 + "name": "created_at",
190 + "type": "timestamp with time zone",
191 + "primaryKey": false,
192 + "notNull": true,
193 + "default": "now()"
194 + },
195 + "updated_at": {
196 + "name": "updated_at",
197 + "type": "timestamp with time zone",
198 + "primaryKey": false,
199 + "notNull": true,
200 + "default": "now()"
201 + }
202 + },
203 + "indexes": {
204 + "categories_parent_idx": {
205 + "name": "categories_parent_idx",
206 + "columns": [
207 + {
208 + "expression": "parent_slug",
209 + "isExpression": false,
210 + "asc": true,
211 + "nulls": "last"
212 + }
213 + ],
214 + "isUnique": false,
215 + "concurrently": false,
216 + "method": "btree",
217 + "with": {}
218 + },
219 + "categories_family_idx": {
220 + "name": "categories_family_idx",
221 + "columns": [
222 + {
223 + "expression": "family_slug",
224 + "isExpression": false,
225 + "asc": true,
226 + "nulls": "last"
227 + }
228 + ],
229 + "isUnique": false,
230 + "concurrently": false,
231 + "method": "btree",
232 + "with": {}
233 + }
234 + },
235 + "foreignKeys": {},
236 + "compositePrimaryKeys": {},
237 + "uniqueConstraints": {},
238 + "policies": {},
239 + "checkConstraints": {},
240 + "isRLSEnabled": false
241 + },
242 + "public.franchises": {
243 + "name": "franchises",
244 + "schema": "",
245 + "columns": {
246 + "slug": {
247 + "name": "slug",
248 + "type": "text",
249 + "primaryKey": true,
250 + "notNull": true
251 + },
252 + "name": {
253 + "name": "name",
254 + "type": "text",
255 + "primaryKey": false,
256 + "notNull": true
257 + },
258 + "owner": {
259 + "name": "owner",
260 + "type": "text",
261 + "primaryKey": false,
262 + "notNull": false
263 + },
264 + "category_slugs": {
265 + "name": "category_slugs",
266 + "type": "text[]",
267 + "primaryKey": false,
268 + "notNull": true,
269 + "default": "'{}'::text[]"
270 + },
271 + "metadata": {
272 + "name": "metadata",
273 + "type": "jsonb",
274 + "primaryKey": false,
275 + "notNull": true,
276 + "default": "'{}'::jsonb"
277 + },
278 + "created_at": {
279 + "name": "created_at",
280 + "type": "timestamp with time zone",
281 + "primaryKey": false,
282 + "notNull": true,
283 + "default": "now()"
284 + }
285 + },
286 + "indexes": {},
287 + "foreignKeys": {},
288 + "compositePrimaryKeys": {},
289 + "uniqueConstraints": {},
290 + "policies": {},
291 + "checkConstraints": {},
292 + "isRLSEnabled": false
293 + },
294 + "public.graders": {
295 + "name": "graders",
296 + "schema": "",
297 + "columns": {
298 + "slug": {
299 + "name": "slug",
300 + "type": "text",
301 + "primaryKey": true,
302 + "notNull": true
303 + },
304 + "name": {
305 + "name": "name",
306 + "type": "text",
307 + "primaryKey": false,
308 + "notNull": true
309 + },
310 + "category_slugs": {
311 + "name": "category_slugs",
312 + "type": "text[]",
313 + "primaryKey": false,
314 + "notNull": true,
315 + "default": "'{}'::text[]"
316 + },
317 + "scale": {
318 + "name": "scale",
319 + "type": "jsonb",
320 + "primaryKey": false,
321 + "notNull": true,
322 + "default": "'{}'::jsonb"
323 + },
324 + "population_url": {
325 + "name": "population_url",
326 + "type": "text",
327 + "primaryKey": false,
328 + "notNull": false
329 + },
330 + "verify_url": {
331 + "name": "verify_url",
332 + "type": "text",
333 + "primaryKey": false,
334 + "notNull": false
335 + },
336 + "active": {
337 + "name": "active",
338 + "type": "boolean",
339 + "primaryKey": false,
340 + "notNull": true,
341 + "default": true
342 + }
343 + },
344 + "indexes": {},
345 + "foreignKeys": {},
346 + "compositePrimaryKeys": {},
347 + "uniqueConstraints": {},
348 + "policies": {},
349 + "checkConstraints": {},
350 + "isRLSEnabled": false
351 + },
352 + "public.sets": {
353 + "name": "sets",
354 + "schema": "",
355 + "columns": {
356 + "slug": {
357 + "name": "slug",
358 + "type": "text",
359 + "primaryKey": true,
360 + "notNull": true
361 + },
362 + "name": {
363 + "name": "name",
364 + "type": "text",
365 + "primaryKey": false,
366 + "notNull": true
367 + },
368 + "code": {
369 + "name": "code",
370 + "type": "text",
371 + "primaryKey": false,
372 + "notNull": false
373 + },
374 + "category_slug": {
375 + "name": "category_slug",
376 + "type": "text",
377 + "primaryKey": false,
378 + "notNull": true
379 + },
380 + "franchise_slug": {
381 + "name": "franchise_slug",
382 + "type": "text",
383 + "primaryKey": false,
384 + "notNull": false
385 + },
386 + "brand_slug": {
387 + "name": "brand_slug",
388 + "type": "text",
389 + "primaryKey": false,
390 + "notNull": false
391 + },
392 + "release_year": {
393 + "name": "release_year",
394 + "type": "integer",
395 + "primaryKey": false,
396 + "notNull": false
397 + },
398 + "release_date": {
399 + "name": "release_date",
400 + "type": "text",
401 + "primaryKey": false,
402 + "notNull": false
403 + },
404 + "language": {
405 + "name": "language",
406 + "type": "text",
407 + "primaryKey": false,
408 + "notNull": false
409 + },
410 + "total_items": {
411 + "name": "total_items",
412 + "type": "integer",
413 + "primaryKey": false,
414 + "notNull": false
415 + },
416 + "identifiers": {
417 + "name": "identifiers",
418 + "type": "jsonb",
419 + "primaryKey": false,
420 + "notNull": true,
421 + "default": "'{}'::jsonb"
422 + },
423 + "metadata": {
424 + "name": "metadata",
425 + "type": "jsonb",
426 + "primaryKey": false,
427 + "notNull": true,
428 + "default": "'{}'::jsonb"
429 + },
430 + "created_at": {
431 + "name": "created_at",
432 + "type": "timestamp with time zone",
433 + "primaryKey": false,
434 + "notNull": true,
435 + "default": "now()"
436 + }
437 + },
438 + "indexes": {
439 + "sets_category_idx": {
440 + "name": "sets_category_idx",
441 + "columns": [
442 + {
443 + "expression": "category_slug",
444 + "isExpression": false,
445 + "asc": true,
446 + "nulls": "last"
447 + }
448 + ],
449 + "isUnique": false,
450 + "concurrently": false,
451 + "method": "btree",
452 + "with": {}
453 + },
454 + "sets_category_code_uq": {
455 + "name": "sets_category_code_uq",
456 + "columns": [
457 + {
458 + "expression": "category_slug",
459 + "isExpression": false,
460 + "asc": true,
461 + "nulls": "last"
462 + },
463 + {
464 + "expression": "code",
465 + "isExpression": false,
466 + "asc": true,
467 + "nulls": "last"
468 + }
469 + ],
470 + "isUnique": true,
471 + "concurrently": false,
472 + "method": "btree",
473 + "with": {}
474 + }
475 + },
476 + "foreignKeys": {},
477 + "compositePrimaryKeys": {},
478 + "uniqueConstraints": {},
479 + "policies": {},
480 + "checkConstraints": {},
481 + "isRLSEnabled": false
482 + },
483 + "public.taxonomy_proposals": {
484 + "name": "taxonomy_proposals",
485 + "schema": "",
486 + "columns": {
487 + "id": {
488 + "name": "id",
489 + "type": "text",
490 + "primaryKey": true,
491 + "notNull": true
492 + },
493 + "proposed_slug": {
494 + "name": "proposed_slug",
495 + "type": "text",
496 + "primaryKey": false,
497 + "notNull": true
498 + },
499 + "name": {
500 + "name": "name",
501 + "type": "text",
502 + "primaryKey": false,
503 + "notNull": true
504 + },
505 + "parent_slug": {
506 + "name": "parent_slug",
507 + "type": "text",
508 + "primaryKey": false,
509 + "notNull": false
510 + },
511 + "evidence": {
512 + "name": "evidence",
513 + "type": "jsonb",
514 + "primaryKey": false,
515 + "notNull": true,
516 + "default": "'{}'::jsonb"
517 + },
518 + "volume_estimate": {
519 + "name": "volume_estimate",
520 + "type": "integer",
521 + "primaryKey": false,
522 + "notNull": false
523 + },
524 + "status": {
525 + "name": "status",
526 + "type": "text",
527 + "primaryKey": false,
528 + "notNull": true,
529 + "default": "'pending'"
530 + },
531 + "decided_by": {
532 + "name": "decided_by",
533 + "type": "text",
534 + "primaryKey": false,
535 + "notNull": false
536 + },
537 + "decided_at": {
538 + "name": "decided_at",
539 + "type": "text",
540 + "primaryKey": false,
541 + "notNull": false
542 + },
543 + "created_at": {
544 + "name": "created_at",
545 + "type": "timestamp with time zone",
546 + "primaryKey": false,
547 + "notNull": true,
548 + "default": "now()"
549 + }
550 + },
551 + "indexes": {},
552 + "foreignKeys": {},
553 + "compositePrimaryKeys": {},
554 + "uniqueConstraints": {},
555 + "policies": {},
556 + "checkConstraints": {},
557 + "isRLSEnabled": false
558 + },
559 + "public.certificate_sightings": {
560 + "name": "certificate_sightings",
561 + "schema": "",
562 + "columns": {
563 + "id": {
564 + "name": "id",
565 + "type": "text",
566 + "primaryKey": true,
567 + "notNull": true
568 + },
569 + "certificate_id": {
570 + "name": "certificate_id",
571 + "type": "text",
572 + "primaryKey": false,
573 + "notNull": true
574 + },
575 + "kind": {
576 + "name": "kind",
577 + "type": "text",
578 + "primaryKey": false,
579 + "notNull": true
580 + },
581 + "target_id": {
582 + "name": "target_id",
583 + "type": "text",
584 + "primaryKey": false,
585 + "notNull": false
586 + },
587 + "source_id": {
588 + "name": "source_id",
589 + "type": "text",
590 + "primaryKey": false,
591 + "notNull": true
592 + },
593 + "connector_id": {
594 + "name": "connector_id",
595 + "type": "text",
596 + "primaryKey": false,
597 + "notNull": false
598 + },
599 + "source_url": {
600 + "name": "source_url",
601 + "type": "text",
602 + "primaryKey": false,
603 + "notNull": false
604 + },
605 + "price_usd": {
606 + "name": "price_usd",
607 + "type": "numeric(18, 4)",
608 + "primaryKey": false,
609 + "notNull": false
610 + },
611 + "currency": {
612 + "name": "currency",
613 + "type": "text",
614 + "primaryKey": false,
615 + "notNull": false
616 + },
617 + "price": {
618 + "name": "price",
619 + "type": "numeric(18, 4)",
620 + "primaryKey": false,
621 + "notNull": false
622 + },
623 + "observed_at": {
624 + "name": "observed_at",
625 + "type": "timestamp with time zone",
626 + "primaryKey": false,
627 + "notNull": true
628 + },
629 + "created_at": {
630 + "name": "created_at",
631 + "type": "timestamp with time zone",
632 + "primaryKey": false,
633 + "notNull": true,
634 + "default": "now()"
635 + }
636 + },
637 + "indexes": {
638 + "certificate_sightings_cert_idx": {
639 + "name": "certificate_sightings_cert_idx",
640 + "columns": [
641 + {
642 + "expression": "certificate_id",
643 + "isExpression": false,
644 + "asc": true,
645 + "nulls": "last"
646 + },
647 + {
648 + "expression": "observed_at",
649 + "isExpression": false,
650 + "asc": true,
651 + "nulls": "last"
652 + }
653 + ],
654 + "isUnique": false,
655 + "concurrently": false,
656 + "method": "btree",
657 + "with": {}
658 + },
659 + "certificate_sightings_uq": {
660 + "name": "certificate_sightings_uq",
661 + "columns": [
662 + {
663 + "expression": "certificate_id",
664 + "isExpression": false,
665 + "asc": true,
666 + "nulls": "last"
667 + },
668 + {
669 + "expression": "kind",
670 + "isExpression": false,
671 + "asc": true,
672 + "nulls": "last"
673 + },
674 + {
675 + "expression": "target_id",
676 + "isExpression": false,
677 + "asc": true,
678 + "nulls": "last"
679 + }
680 + ],
681 + "isUnique": true,
682 + "concurrently": false,
683 + "method": "btree",
684 + "with": {}
685 + }
686 + },
687 + "foreignKeys": {},
688 + "compositePrimaryKeys": {},
689 + "uniqueConstraints": {},
690 + "policies": {},
691 + "checkConstraints": {},
692 + "isRLSEnabled": false
693 + },
694 + "public.certificates": {
695 + "name": "certificates",
696 + "schema": "",
697 + "columns": {
698 + "id": {
699 + "name": "id",
700 + "type": "text",
701 + "primaryKey": true,
702 + "notNull": true
703 + },
704 + "grader": {
705 + "name": "grader",
706 + "type": "text",
707 + "primaryKey": false,
708 + "notNull": true
709 + },
710 + "cert_number": {
711 + "name": "cert_number",
712 + "type": "text",
713 + "primaryKey": false,
714 + "notNull": true
715 + },
716 + "asset_id": {
717 + "name": "asset_id",
718 + "type": "text",
719 + "primaryKey": false,
720 + "notNull": false
721 + },
722 + "variant_id": {
723 + "name": "variant_id",
724 + "type": "text",
725 + "primaryKey": false,
726 + "notNull": false
727 + },
728 + "grade": {
729 + "name": "grade",
730 + "type": "text",
731 + "primaryKey": false,
732 + "notNull": false
733 + },
734 + "qualifier": {
735 + "name": "qualifier",
736 + "type": "text",
737 + "primaryKey": false,
738 + "notNull": false
739 + },
740 + "verify_url": {
741 + "name": "verify_url",
742 + "type": "text",
743 + "primaryKey": false,
744 + "notNull": false
745 + },
746 + "first_seen_at": {
747 + "name": "first_seen_at",
748 + "type": "timestamp with time zone",
749 + "primaryKey": false,
750 + "notNull": true
751 + },
752 + "last_seen_at": {
753 + "name": "last_seen_at",
754 + "type": "timestamp with time zone",
755 + "primaryKey": false,
756 + "notNull": true
757 + },
758 + "sightings": {
759 + "name": "sightings",
760 + "type": "integer",
761 + "primaryKey": false,
762 + "notNull": true,
763 + "default": 0
764 + },
765 + "source_ids": {
766 + "name": "source_ids",
767 + "type": "text[]",
768 + "primaryKey": false,
769 + "notNull": true,
770 + "default": "'{}'::text[]"
771 + },
772 + "last_source_url": {
773 + "name": "last_source_url",
774 + "type": "text",
775 + "primaryKey": false,
776 + "notNull": false
777 + },
778 + "last_price_usd": {
779 + "name": "last_price_usd",
780 + "type": "numeric(18, 4)",
781 + "primaryKey": false,
782 + "notNull": false
783 + },
784 + "verified_at": {
785 + "name": "verified_at",
786 + "type": "timestamp with time zone",
787 + "primaryKey": false,
788 + "notNull": false
789 + },
790 + "verification": {
791 + "name": "verification",
792 + "type": "jsonb",
793 + "primaryKey": false,
794 + "notNull": true,
795 + "default": "'{}'::jsonb"
796 + },
797 + "created_at": {
798 + "name": "created_at",
799 + "type": "timestamp with time zone",
800 + "primaryKey": false,
801 + "notNull": true,
802 + "default": "now()"
803 + }
804 + },
805 + "indexes": {
806 + "certificates_grader_cert_uq": {
807 + "name": "certificates_grader_cert_uq",
808 + "columns": [
809 + {
810 + "expression": "grader",
811 + "isExpression": false,
812 + "asc": true,
813 + "nulls": "last"
814 + },
815 + {
816 + "expression": "cert_number",
817 + "isExpression": false,
818 + "asc": true,
819 + "nulls": "last"
820 + }
821 + ],
822 + "isUnique": true,
823 + "concurrently": false,
824 + "method": "btree",
825 + "with": {}
826 + },
827 + "certificates_asset_idx": {
828 + "name": "certificates_asset_idx",
829 + "columns": [
830 + {
831 + "expression": "asset_id",
832 + "isExpression": false,
833 + "asc": true,
834 + "nulls": "last"
835 + }
836 + ],
837 + "isUnique": false,
838 + "concurrently": false,
839 + "method": "btree",
840 + "with": {}
841 + },
842 + "certificates_last_seen_idx": {
843 + "name": "certificates_last_seen_idx",
844 + "columns": [
845 + {
846 + "expression": "last_seen_at",
847 + "isExpression": false,
848 + "asc": true,
849 + "nulls": "last"
850 + }
851 + ],
852 + "isUnique": false,
853 + "concurrently": false,
854 + "method": "btree",
855 + "with": {}
856 + }
857 + },
858 + "foreignKeys": {},
859 + "compositePrimaryKeys": {},
860 + "uniqueConstraints": {},
861 + "policies": {},
862 + "checkConstraints": {},
863 + "isRLSEnabled": false
864 + },
865 + "public.connector_backfills": {
866 + "name": "connector_backfills",
867 + "schema": "",
868 + "columns": {
869 + "id": {
870 + "name": "id",
871 + "type": "text",
872 + "primaryKey": true,
873 + "notNull": true
874 + },
875 + "connector_id": {
876 + "name": "connector_id",
877 + "type": "text",
878 + "primaryKey": false,
879 + "notNull": true
880 + },
881 + "status": {
882 + "name": "status",
883 + "type": "text",
884 + "primaryKey": false,
885 + "notNull": true,
886 + "default": "'running'"
887 + },
888 + "started_at": {
889 + "name": "started_at",
890 + "type": "timestamp with time zone",
891 + "primaryKey": false,
892 + "notNull": true
893 + },
894 + "updated_at": {
895 + "name": "updated_at",
896 + "type": "timestamp with time zone",
897 + "primaryKey": false,
898 + "notNull": true,
899 + "default": "now()"
900 + },
901 + "finished_at": {
902 + "name": "finished_at",
903 + "type": "timestamp with time zone",
904 + "primaryKey": false,
905 + "notNull": false
906 + },
907 + "backfill_start_date": {
908 + "name": "backfill_start_date",
909 + "type": "date",
910 + "primaryKey": false,
911 + "notNull": false
912 + },
913 + "backfill_end_date": {
914 + "name": "backfill_end_date",
915 + "type": "date",
916 + "primaryKey": false,
917 + "notNull": false
918 + },
919 + "reached_date": {
920 + "name": "reached_date",
921 + "type": "date",
922 + "primaryKey": false,
923 + "notNull": false
924 + },
925 + "pages_processed": {
926 + "name": "pages_processed",
927 + "type": "integer",
928 + "primaryKey": false,
929 + "notNull": true,
930 + "default": 0
931 + },
932 + "total_pages": {
933 + "name": "total_pages",
934 + "type": "integer",
935 + "primaryKey": false,
936 + "notNull": false
937 + },
938 + "items_processed": {
939 + "name": "items_processed",
940 + "type": "integer",
941 + "primaryKey": false,
942 + "notNull": true,
943 + "default": 0
944 + },
945 + "last_cursor": {
946 + "name": "last_cursor",
947 + "type": "jsonb",
948 + "primaryKey": false,
949 + "notNull": true,
950 + "default": "'{}'::jsonb"
951 + },
952 + "last_successful_page": {
953 + "name": "last_successful_page",
954 + "type": "integer",
955 + "primaryKey": false,
956 + "notNull": false
957 + },
958 + "runs": {
959 + "name": "runs",
960 + "type": "integer",
961 + "primaryKey": false,
962 + "notNull": true,
963 + "default": 0
964 + },
965 + "errors": {
966 + "name": "errors",
967 + "type": "integer",
968 + "primaryKey": false,
969 + "notNull": true,
970 + "default": 0
971 + },
972 + "retry_count": {
973 + "name": "retry_count",
974 + "type": "integer",
975 + "primaryKey": false,
976 + "notNull": true,
977 + "default": 0
978 + },
979 + "last_error": {
980 + "name": "last_error",
981 + "type": "text",
982 + "primaryKey": false,
983 + "notNull": false
984 + },
985 + "percent": {
986 + "name": "percent",
987 + "type": "real",
988 + "primaryKey": false,
989 + "notNull": false
990 + }
991 + },
992 + "indexes": {
993 + "connector_backfills_connector_idx": {
994 + "name": "connector_backfills_connector_idx",
995 + "columns": [
996 + {
997 + "expression": "connector_id",
998 + "isExpression": false,
999 + "asc": true,
1000 + "nulls": "last"
1001 + },
1002 + {
1003 + "expression": "status",
1004 + "isExpression": false,
1005 + "asc": true,
1006 + "nulls": "last"
1007 + }
1008 + ],
1009 + "isUnique": false,
1010 + "concurrently": false,
1011 + "method": "btree",
1012 + "with": {}
1013 + }
1014 + },
1015 + "foreignKeys": {},
1016 + "compositePrimaryKeys": {},
1017 + "uniqueConstraints": {},
1018 + "policies": {},
1019 + "checkConstraints": {},
1020 + "isRLSEnabled": false
1021 + },
1022 + "public.connector_field_stats": {
1023 + "name": "connector_field_stats",
1024 + "schema": "",
1025 + "columns": {
1026 + "connector_id": {
1027 + "name": "connector_id",
1028 + "type": "text",
1029 + "primaryKey": false,
1030 + "notNull": true
1031 + },
1032 + "day": {
1033 + "name": "day",
1034 + "type": "date",
1035 + "primaryKey": false,
1036 + "notNull": true
1037 + },
1038 + "field": {
1039 + "name": "field",
1040 + "type": "text",
1041 + "primaryKey": false,
1042 + "notNull": true
1043 + },
1044 + "total": {
1045 + "name": "total",
1046 + "type": "integer",
1047 + "primaryKey": false,
1048 + "notNull": true,
1049 + "default": 0
1050 + },
1051 + "nulls": {
1052 + "name": "nulls",
1053 + "type": "integer",
1054 + "primaryKey": false,
1055 + "notNull": true,
1056 + "default": 0
1057 + }
1058 + },
1059 + "indexes": {},
1060 + "foreignKeys": {},
1061 + "compositePrimaryKeys": {
1062 + "connector_field_stats_connector_id_day_field_pk": {
1063 + "name": "connector_field_stats_connector_id_day_field_pk",
1064 + "columns": [
1065 + "connector_id",
1066 + "day",
1067 + "field"
1068 + ]
1069 + }
1070 + },
1071 + "uniqueConstraints": {},
1072 + "policies": {},
1073 + "checkConstraints": {},
1074 + "isRLSEnabled": false
1075 + },
1076 + "public.connector_health": {
1077 + "name": "connector_health",
1078 + "schema": "",
1079 + "columns": {
1080 + "connector_id": {
1081 + "name": "connector_id",
1082 + "type": "text",
1083 + "primaryKey": true,
1084 + "notNull": true
1085 + },
1086 + "computed_at": {
1087 + "name": "computed_at",
1088 + "type": "timestamp with time zone",
1089 + "primaryKey": false,
1090 + "notNull": true
1091 + },
1092 + "status": {
1093 + "name": "status",
1094 + "type": "text",
1095 + "primaryKey": false,
1096 + "notNull": true
1097 + },
1098 + "health": {
1099 + "name": "health",
1100 + "type": "jsonb",
1101 + "primaryKey": false,
1102 + "notNull": true,
1103 + "default": "'{}'::jsonb"
1104 + }
1105 + },
1106 + "indexes": {},
1107 + "foreignKeys": {},
1108 + "compositePrimaryKeys": {},
1109 + "uniqueConstraints": {},
1110 + "policies": {},
1111 + "checkConstraints": {},
1112 + "isRLSEnabled": false
1113 + },
1114 + "public.connector_runs": {
1115 + "name": "connector_runs",
1116 + "schema": "",
1117 + "columns": {
1118 + "id": {
1119 + "name": "id",
1120 + "type": "text",
1121 + "primaryKey": true,
1122 + "notNull": true
1123 + },
1124 + "connector_id": {
1125 + "name": "connector_id",
1126 + "type": "text",
1127 + "primaryKey": false,
1128 + "notNull": true
1129 + },
1130 + "trigger": {
1131 + "name": "trigger",
1132 + "type": "text",
1133 + "primaryKey": false,
1134 + "notNull": true,
1135 + "default": "'schedule'"
1136 + },
1137 + "started_at": {
1138 + "name": "started_at",
1139 + "type": "timestamp with time zone",
1140 + "primaryKey": false,
1141 + "notNull": true
1142 + },
1143 + "finished_at": {
1144 + "name": "finished_at",
1145 + "type": "timestamp with time zone",
1146 + "primaryKey": false,
1147 + "notNull": false
1148 + },
1149 + "status": {
1150 + "name": "status",
1151 + "type": "text",
1152 + "primaryKey": false,
1153 + "notNull": true,
1154 + "default": "'running'"
1155 + },
1156 + "pages_attempted": {
1157 + "name": "pages_attempted",
1158 + "type": "integer",
1159 + "primaryKey": false,
1160 + "notNull": true,
1161 + "default": 0
1162 + },
1163 + "pages_success": {
1164 + "name": "pages_success",
1165 + "type": "integer",
1166 + "primaryKey": false,
1167 + "notNull": true,
1168 + "default": 0
1169 + },
1170 + "records_raw": {
1171 + "name": "records_raw",
1172 + "type": "integer",
1173 + "primaryKey": false,
1174 + "notNull": true,
1175 + "default": 0
1176 + },
1177 + "records_normalized": {
1178 + "name": "records_normalized",
1179 + "type": "integer",
1180 + "primaryKey": false,
1181 + "notNull": true,
1182 + "default": 0
1183 + },
1184 + "records_duplicate": {
1185 + "name": "records_duplicate",
1186 + "type": "integer",
1187 + "primaryKey": false,
1188 + "notNull": true,
1189 + "default": 0
1190 + },
1191 + "records_rejected": {
1192 + "name": "records_rejected",
1193 + "type": "integer",
1194 + "primaryKey": false,
1195 + "notNull": true,
1196 + "default": 0
1197 + },
1198 + "engine_stats": {
1199 + "name": "engine_stats",
1200 + "type": "jsonb",
1201 + "primaryKey": false,
1202 + "notNull": true,
1203 + "default": "'{}'::jsonb"
1204 + },
1205 + "anomalies": {
1206 + "name": "anomalies",
1207 + "type": "jsonb",
1208 + "primaryKey": false,
1209 + "notNull": true,
1210 + "default": "'[]'::jsonb"
1211 + },
1212 + "error": {
1213 + "name": "error",
1214 + "type": "text",
1215 + "primaryKey": false,
1216 + "notNull": false
1217 + },
1218 + "cost_credits": {
1219 + "name": "cost_credits",
1220 + "type": "real",
1221 + "primaryKey": false,
1222 + "notNull": true,
1223 + "default": 0
1224 + },
1225 + "cost_usd_est": {
1226 + "name": "cost_usd_est",
1227 + "type": "real",
1228 + "primaryKey": false,
1229 + "notNull": true,
1230 + "default": 0
1231 + },
1232 + "cursor": {
1233 + "name": "cursor",
1234 + "type": "jsonb",
1235 + "primaryKey": false,
1236 + "notNull": true,
1237 + "default": "'{}'::jsonb"
1238 + }
1239 + },
1240 + "indexes": {
1241 + "connector_runs_connector_started_idx": {
1242 + "name": "connector_runs_connector_started_idx",
1243 + "columns": [
1244 + {
1245 + "expression": "connector_id",
1246 + "isExpression": false,
1247 + "asc": true,
1248 + "nulls": "last"
1249 + },
1250 + {
1251 + "expression": "started_at",
1252 + "isExpression": false,
1253 + "asc": true,
1254 + "nulls": "last"
1255 + }
1256 + ],
1257 + "isUnique": false,
1258 + "concurrently": false,
1259 + "method": "btree",
1260 + "with": {}
1261 + }
1262 + },
1263 + "foreignKeys": {},
1264 + "compositePrimaryKeys": {},
1265 + "uniqueConstraints": {},
1266 + "policies": {},
1267 + "checkConstraints": {},
1268 + "isRLSEnabled": false
1269 + },
1270 + "public.connectors": {
1271 + "name": "connectors",
1272 + "schema": "",
1273 + "columns": {
1274 + "id": {
1275 + "name": "id",
1276 + "type": "text",
1277 + "primaryKey": true,
1278 + "notNull": true
1279 + },
1280 + "source_id": {
1281 + "name": "source_id",
1282 + "type": "text",
1283 + "primaryKey": false,
1284 + "notNull": true
1285 + },
1286 + "display_name": {
1287 + "name": "display_name",
1288 + "type": "text",
1289 + "primaryKey": false,
1290 + "notNull": true
1291 + },
1292 + "engine_priority": {
1293 + "name": "engine_priority",
1294 + "type": "text[]",
1295 + "primaryKey": false,
1296 + "notNull": true,
1297 + "default": "'{}'::text[]"
1298 + },
1299 + "categories": {
1300 + "name": "categories",
1301 + "type": "text[]",
1302 + "primaryKey": false,
1303 + "notNull": true,
1304 + "default": "'{}'::text[]"
1305 + },
1306 + "regions": {
1307 + "name": "regions",
1308 + "type": "text[]",
1309 + "primaryKey": false,
1310 + "notNull": true,
1311 + "default": "'{}'::text[]"
1312 + },
1313 + "languages": {
1314 + "name": "languages",
1315 + "type": "text[]",
1316 + "primaryKey": false,
1317 + "notNull": true,
1318 + "default": "'{}'::text[]"
1319 + },
1320 + "currency": {
1321 + "name": "currency",
1322 + "type": "text[]",
1323 + "primaryKey": false,
1324 + "notNull": true,
1325 + "default": "'{}'::text[]"
1326 + },
1327 + "supports_listings": {
1328 + "name": "supports_listings",
1329 + "type": "boolean",
1330 + "primaryKey": false,
1331 + "notNull": true,
1332 + "default": false
1333 + },
1334 + "supports_sold": {
1335 + "name": "supports_sold",
1336 + "type": "boolean",
1337 + "primaryKey": false,
1338 + "notNull": true,
1339 + "default": false
1340 + },
1341 + "supports_auctions": {
1342 + "name": "supports_auctions",
1343 + "type": "boolean",
1344 + "primaryKey": false,
1345 + "notNull": true,
1346 + "default": false
1347 + },
1348 + "supports_images": {
1349 + "name": "supports_images",
1350 + "type": "boolean",
1351 + "primaryKey": false,
1352 + "notNull": true,
1353 + "default": true
1354 + },
1355 + "supports_catalog": {
1356 + "name": "supports_catalog",
1357 + "type": "boolean",
1358 + "primaryKey": false,
1359 + "notNull": true,
1360 + "default": false
1361 + },
1362 + "supports_population": {
1363 + "name": "supports_population",
1364 + "type": "boolean",
1365 + "primaryKey": false,
1366 + "notNull": true,
1367 + "default": false
1368 + },
1369 + "refresh_frequency_minutes": {
1370 + "name": "refresh_frequency_minutes",
1371 + "type": "integer",
1372 + "primaryKey": false,
1373 + "notNull": true,
1374 + "default": 1440
1375 + },
1376 + "priority": {
1377 + "name": "priority",
1378 + "type": "text",
1379 + "primaryKey": false,
1380 + "notNull": true,
1381 + "default": "'medium'"
1382 + },
1383 + "status": {
1384 + "name": "status",
1385 + "type": "text",
1386 + "primaryKey": false,
1387 + "notNull": true,
1388 + "default": "'active'"
1389 + },
1390 + "schema_version": {
1391 + "name": "schema_version",
1392 + "type": "text",
1393 + "primaryKey": false,
1394 + "notNull": true,
1395 + "default": "'1.0'"
1396 + },
1397 + "connector_version": {
1398 + "name": "connector_version",
1399 + "type": "text",
1400 + "primaryKey": false,
1401 + "notNull": true,
1402 + "default": "'1.0.0'"
1403 + },
1404 + "config": {
1405 + "name": "config",
1406 + "type": "jsonb",
1407 + "primaryKey": false,
1408 + "notNull": true,
1409 + "default": "'{}'::jsonb"
1410 + },
1411 + "meta": {
1412 + "name": "meta",
1413 + "type": "jsonb",
1414 + "primaryKey": false,
1415 + "notNull": true,
1416 + "default": "'{}'::jsonb"
1417 + },
1418 + "last_run_at": {
1419 + "name": "last_run_at",
1420 + "type": "timestamp with time zone",
1421 + "primaryKey": false,
1422 + "notNull": false
1423 + },
1424 + "last_success_at": {
1425 + "name": "last_success_at",
1426 + "type": "timestamp with time zone",
1427 + "primaryKey": false,
1428 + "notNull": false
1429 + },
1430 + "next_run_at": {
1431 + "name": "next_run_at",
1432 + "type": "timestamp with time zone",
1433 + "primaryKey": false,
1434 + "notNull": false
1435 + },
1436 + "created_at": {
1437 + "name": "created_at",
1438 + "type": "timestamp with time zone",
1439 + "primaryKey": false,
1440 + "notNull": true,
1441 + "default": "now()"
1442 + },
1443 + "updated_at": {
1444 + "name": "updated_at",
1445 + "type": "timestamp with time zone",
1446 + "primaryKey": false,
1447 + "notNull": true,
1448 + "default": "now()"
1449 + }
1450 + },
1451 + "indexes": {},
1452 + "foreignKeys": {},
1453 + "compositePrimaryKeys": {},
1454 + "uniqueConstraints": {},
1455 + "policies": {},
1456 + "checkConstraints": {},
1457 + "isRLSEnabled": false
1458 + },
1459 + "public.costs": {
1460 + "name": "costs",
1461 + "schema": "",
1462 + "columns": {
1463 + "id": {
1464 + "name": "id",
1465 + "type": "text",
1466 + "primaryKey": true,
1467 + "notNull": true
1468 + },
1469 + "occurred_at": {
1470 + "name": "occurred_at",
1471 + "type": "timestamp with time zone",
1472 + "primaryKey": false,
1473 + "notNull": true
1474 + },
1475 + "kind": {
1476 + "name": "kind",
1477 + "type": "text",
1478 + "primaryKey": false,
1479 + "notNull": true
1480 + },
1481 + "provider": {
1482 + "name": "provider",
1483 + "type": "text",
1484 + "primaryKey": false,
1485 + "notNull": false
1486 + },
1487 + "connector_id": {
1488 + "name": "connector_id",
1489 + "type": "text",
1490 + "primaryKey": false,
1491 + "notNull": false
1492 + },
1493 + "category_slug": {
1494 + "name": "category_slug",
1495 + "type": "text",
1496 + "primaryKey": false,
1497 + "notNull": false
1498 + },
1499 + "endpoint": {
1500 + "name": "endpoint",
1501 + "type": "text",
1502 + "primaryKey": false,
1503 + "notNull": false
1504 + },
1505 + "user_id": {
1506 + "name": "user_id",
1507 + "type": "text",
1508 + "primaryKey": false,
1509 + "notNull": false
1510 + },
1511 + "units": {
1512 + "name": "units",
1513 + "type": "real",
1514 + "primaryKey": false,
1515 + "notNull": true,
1516 + "default": 1
1517 + },
1518 + "credits": {
1519 + "name": "credits",
1520 + "type": "real",
1521 + "primaryKey": false,
1522 + "notNull": true,
1523 + "default": 0
1524 + },
1525 + "usd_est": {
1526 + "name": "usd_est",
1527 + "type": "real",
1528 + "primaryKey": false,
1529 + "notNull": true,
1530 + "default": 0
1531 + },
1532 + "metadata": {
1533 + "name": "metadata",
1534 + "type": "jsonb",
1535 + "primaryKey": false,
1536 + "notNull": true,
1537 + "default": "'{}'::jsonb"
1538 + }
1539 + },
1540 + "indexes": {
1541 + "costs_occurred_idx": {
1542 + "name": "costs_occurred_idx",
1543 + "columns": [
1544 + {
1545 + "expression": "occurred_at",
1546 + "isExpression": false,
1547 + "asc": true,
1548 + "nulls": "last"
1549 + }
1550 + ],
1551 + "isUnique": false,
1552 + "concurrently": false,
1553 + "method": "btree",
1554 + "with": {}
1555 + },
1556 + "costs_connector_idx": {
1557 + "name": "costs_connector_idx",
1558 + "columns": [
1559 + {
1560 + "expression": "connector_id",
1561 + "isExpression": false,
1562 + "asc": true,
1563 + "nulls": "last"
1564 + }
1565 + ],
1566 + "isUnique": false,
1567 + "concurrently": false,
1568 + "method": "btree",
1569 + "with": {}
1570 + }
1571 + },
1572 + "foreignKeys": {},
1573 + "compositePrimaryKeys": {},
1574 + "uniqueConstraints": {},
1575 + "policies": {},
1576 + "checkConstraints": {},
1577 + "isRLSEnabled": false
1578 + },
1579 + "public.crawl_state": {
1580 + "name": "crawl_state",
1581 + "schema": "",
1582 + "columns": {
1583 + "url_hash": {
1584 + "name": "url_hash",
1585 + "type": "text",
1586 + "primaryKey": true,
1587 + "notNull": true
1588 + },
1589 + "connector_id": {
1590 + "name": "connector_id",
1591 + "type": "text",
1592 + "primaryKey": false,
1593 + "notNull": true
1594 + },
1595 + "url": {
1596 + "name": "url",
1597 + "type": "text",
1598 + "primaryKey": false,
1599 + "notNull": true
1600 + },
1601 + "etag": {
1602 + "name": "etag",
1603 + "type": "text",
1604 + "primaryKey": false,
1605 + "notNull": false
1606 + },
1607 + "last_modified": {
1608 + "name": "last_modified",
1609 + "type": "text",
1610 + "primaryKey": false,
1611 + "notNull": false
1612 + },
1613 + "content_hash": {
1614 + "name": "content_hash",
1615 + "type": "text",
1616 + "primaryKey": false,
1617 + "notNull": false
1618 + },
1619 + "last_fetched_at": {
1620 + "name": "last_fetched_at",
1621 + "type": "timestamp with time zone",
1622 + "primaryKey": false,
1623 + "notNull": false
1624 + },
1625 + "last_changed_at": {
1626 + "name": "last_changed_at",
1627 + "type": "timestamp with time zone",
1628 + "primaryKey": false,
1629 + "notNull": false
1630 + },
1631 + "fetch_count": {
1632 + "name": "fetch_count",
1633 + "type": "integer",
1634 + "primaryKey": false,
1635 + "notNull": true,
1636 + "default": 0
1637 + },
1638 + "change_count": {
1639 + "name": "change_count",
1640 + "type": "integer",
1641 + "primaryKey": false,
1642 + "notNull": true,
1643 + "default": 0
1644 + },
1645 + "change_interval_hours": {
1646 + "name": "change_interval_hours",
1647 + "type": "real",
1648 + "primaryKey": false,
1649 + "notNull": false
1650 + },
1651 + "next_fetch_at": {
1652 + "name": "next_fetch_at",
1653 + "type": "timestamp with time zone",
1654 + "primaryKey": false,
1655 + "notNull": false
1656 + },
1657 + "last_status": {
1658 + "name": "last_status",
1659 + "type": "integer",
1660 + "primaryKey": false,
1661 + "notNull": false
1662 + },
1663 + "failures": {
1664 + "name": "failures",
1665 + "type": "integer",
1666 + "primaryKey": false,
1667 + "notNull": true,
1668 + "default": 0
1669 + }
1670 + },
1671 + "indexes": {
1672 + "crawl_state_next_idx": {
1673 + "name": "crawl_state_next_idx",
1674 + "columns": [
1675 + {
1676 + "expression": "connector_id",
1677 + "isExpression": false,
1678 + "asc": true,
1679 + "nulls": "last"
1680 + },
1681 + {
1682 + "expression": "next_fetch_at",
1683 + "isExpression": false,
1684 + "asc": true,
1685 + "nulls": "last"
1686 + }
1687 + ],
1688 + "isUnique": false,
1689 + "concurrently": false,
1690 + "method": "btree",
1691 + "with": {}
1692 + }
1693 + },
1694 + "foreignKeys": {},
1695 + "compositePrimaryKeys": {},
1696 + "uniqueConstraints": {},
1697 + "policies": {},
1698 + "checkConstraints": {},
1699 + "isRLSEnabled": false
1700 + },
1701 + "public.sources": {
1702 + "name": "sources",
1703 + "schema": "",
1704 + "columns": {
1705 + "id": {
1706 + "name": "id",
1707 + "type": "text",
1708 + "primaryKey": true,
1709 + "notNull": true
1710 + },
1711 + "name": {
1712 + "name": "name",
1713 + "type": "text",
1714 + "primaryKey": false,
1715 + "notNull": true
1716 + },
1717 + "source_type": {
1718 + "name": "source_type",
1719 + "type": "text",
1720 + "primaryKey": false,
1721 + "notNull": true
1722 + },
1723 + "url": {
1724 + "name": "url",
1725 + "type": "text",
1726 + "primaryKey": false,
1727 + "notNull": false
1728 + },
1729 + "countries": {
1730 + "name": "countries",
1731 + "type": "text[]",
1732 + "primaryKey": false,
1733 + "notNull": true,
1734 + "default": "'{}'::text[]"
1735 + },
1736 + "languages": {
1737 + "name": "languages",
1738 + "type": "text[]",
1739 + "primaryKey": false,
1740 + "notNull": true,
1741 + "default": "'{}'::text[]"
1742 + },
1743 + "currencies": {
1744 + "name": "currencies",
1745 + "type": "text[]",
1746 + "primaryKey": false,
1747 + "notNull": true,
1748 + "default": "'{}'::text[]"
1749 + },
1750 + "trust_score": {
1751 + "name": "trust_score",
1752 + "type": "numeric(8, 6)",
1753 + "primaryKey": false,
1754 + "notNull": true,
1755 + "default": 0.5
1756 + },
1757 + "trust_factors": {
1758 + "name": "trust_factors",
1759 + "type": "jsonb",
1760 + "primaryKey": false,
1761 + "notNull": true,
1762 + "default": "'{}'::jsonb"
1763 + },
1764 + "attribution_required": {
1765 + "name": "attribution_required",
1766 + "type": "boolean",
1767 + "primaryKey": false,
1768 + "notNull": true,
1769 + "default": true
1770 + },
1771 + "terms_url": {
1772 + "name": "terms_url",
1773 + "type": "text",
1774 + "primaryKey": false,
1775 + "notNull": false
1776 + },
1777 + "active": {
1778 + "name": "active",
1779 + "type": "boolean",
1780 + "primaryKey": false,
1781 + "notNull": true,
1782 + "default": true
1783 + },
1784 + "created_at": {
1785 + "name": "created_at",
1786 + "type": "timestamp with time zone",
1787 + "primaryKey": false,
1788 + "notNull": true,
1789 + "default": "now()"
1790 + },
1791 + "updated_at": {
1792 + "name": "updated_at",
1793 + "type": "timestamp with time zone",
1794 + "primaryKey": false,
1795 + "notNull": true,
1796 + "default": "now()"
1797 + }
1798 + },
1799 + "indexes": {},
1800 + "foreignKeys": {},
1801 + "compositePrimaryKeys": {},
1802 + "uniqueConstraints": {},
1803 + "policies": {},
1804 + "checkConstraints": {},
1805 + "isRLSEnabled": false
1806 + },
1807 + "public.audit_log": {
1808 + "name": "audit_log",
1809 + "schema": "",
1810 + "columns": {
1811 + "id": {
1812 + "name": "id",
1813 + "type": "text",
1814 + "primaryKey": true,
1815 + "notNull": true
1816 + },
1817 + "entity_type": {
1818 + "name": "entity_type",
1819 + "type": "text",
1820 + "primaryKey": false,
1821 + "notNull": true
1822 + },
1823 + "entity_id": {
1824 + "name": "entity_id",
1825 + "type": "text",
1826 + "primaryKey": false,
1827 + "notNull": true
1828 + },
1829 + "action": {
1830 + "name": "action",
1831 + "type": "text",
1832 + "primaryKey": false,
1833 + "notNull": true
1834 + },
1835 + "reason": {
1836 + "name": "reason",
1837 + "type": "text",
1838 + "primaryKey": false,
1839 + "notNull": true
1840 + },
1841 + "actor": {
1842 + "name": "actor",
1843 + "type": "text",
1844 + "primaryKey": false,
1845 + "notNull": true,
1846 + "default": "'system'"
1847 + },
1848 + "details": {
1849 + "name": "details",
1850 + "type": "jsonb",
1851 + "primaryKey": false,
1852 + "notNull": true,
1853 + "default": "'{}'::jsonb"
1854 + },
1855 + "created_at": {
1856 + "name": "created_at",
1857 + "type": "timestamp with time zone",
1858 + "primaryKey": false,
1859 + "notNull": true,
1860 + "default": "now()"
1861 + }
1862 + },
1863 + "indexes": {
1864 + "audit_entity_idx": {
1865 + "name": "audit_entity_idx",
1866 + "columns": [
1867 + {
1868 + "expression": "entity_type",
1869 + "isExpression": false,
1870 + "asc": true,
1871 + "nulls": "last"
1872 + },
1873 + {
1874 + "expression": "entity_id",
1875 + "isExpression": false,
1876 + "asc": true,
1877 + "nulls": "last"
1878 + }
1879 + ],
1880 + "isUnique": false,
1881 + "concurrently": false,
1882 + "method": "btree",
1883 + "with": {}
1884 + }
1885 + },
1886 + "foreignKeys": {},
1887 + "compositePrimaryKeys": {},
1888 + "uniqueConstraints": {},
1889 + "policies": {},
1890 + "checkConstraints": {},
1891 + "isRLSEnabled": false
1892 + },
1893 + "public.events": {
1894 + "name": "events",
1895 + "schema": "",
1896 + "columns": {
1897 + "id": {
1898 + "name": "id",
1899 + "type": "text",
1900 + "primaryKey": true,
1901 + "notNull": true
1902 + },
1903 + "type": {
1904 + "name": "type",
1905 + "type": "text",
1906 + "primaryKey": false,
1907 + "notNull": true
1908 + },
1909 + "entity_type": {
1910 + "name": "entity_type",
1911 + "type": "text",
1912 + "primaryKey": false,
1913 + "notNull": false
1914 + },
1915 + "entity_id": {
1916 + "name": "entity_id",
1917 + "type": "text",
1918 + "primaryKey": false,
1919 + "notNull": false
1920 + },
1921 + "payload": {
1922 + "name": "payload",
1923 + "type": "jsonb",
1924 + "primaryKey": false,
1925 + "notNull": true,
1926 + "default": "'{}'::jsonb"
1927 + },
1928 + "created_at": {
1929 + "name": "created_at",
1930 + "type": "timestamp with time zone",
1931 + "primaryKey": false,
1932 + "notNull": true,
1933 + "default": "now()"
1934 + }
1935 + },
1936 + "indexes": {
1937 + "events_type_created_idx": {
1938 + "name": "events_type_created_idx",
1939 + "columns": [
1940 + {
1941 + "expression": "type",
1942 + "isExpression": false,
1943 + "asc": true,
1944 + "nulls": "last"
1945 + },
1946 + {
1947 + "expression": "created_at",
1948 + "isExpression": false,
1949 + "asc": true,
1950 + "nulls": "last"
1951 + }
1952 + ],
1953 + "isUnique": false,
1954 + "concurrently": false,
1955 + "method": "btree",
1956 + "with": {}
1957 + },
1958 + "events_entity_idx": {
1959 + "name": "events_entity_idx",
1960 + "columns": [
1961 + {
1962 + "expression": "entity_type",
1963 + "isExpression": false,
1964 + "asc": true,
1965 + "nulls": "last"
1966 + },
1967 + {
1968 + "expression": "entity_id",
1969 + "isExpression": false,
1970 + "asc": true,
1971 + "nulls": "last"
1972 + }
1973 + ],
1974 + "isUnique": false,
1975 + "concurrently": false,
1976 + "method": "btree",
1977 + "with": {}
1978 + }
1979 + },
1980 + "foreignKeys": {},
1981 + "compositePrimaryKeys": {},
1982 + "uniqueConstraints": {},
1983 + "policies": {},
1984 + "checkConstraints": {},
1985 + "isRLSEnabled": false
1986 + },
1987 + "public.normalized_records": {
1988 + "name": "normalized_records",
1989 + "schema": "",
1990 + "columns": {
1991 + "id": {
1992 + "name": "id",
1993 + "type": "text",
1994 + "primaryKey": true,
1995 + "notNull": true
1996 + },
1997 + "raw_record_id": {
1998 + "name": "raw_record_id",
1999 + "type": "text",
2000 + "primaryKey": false,
2001 + "notNull": true
2002 + },
2003 + "connector_id": {
2004 + "name": "connector_id",
2005 + "type": "text",
2006 + "primaryKey": false,
2007 + "notNull": true
2008 + },
2009 + "source_id": {
2010 + "name": "source_id",
2011 + "type": "text",
2012 + "primaryKey": false,
2013 + "notNull": true
2014 + },
2015 + "kind": {
2016 + "name": "kind",
2017 + "type": "text",
2018 + "primaryKey": false,
2019 + "notNull": true
2020 + },
2021 + "payload": {
2022 + "name": "payload",
2023 + "type": "jsonb",
2024 + "primaryKey": false,
2025 + "notNull": true
2026 + },
2027 + "seq": {
2028 + "name": "seq",
2029 + "type": "integer",
2030 + "primaryKey": false,
2031 + "notNull": true,
2032 + "default": 0
2033 + },
2034 + "asset_id": {
2035 + "name": "asset_id",
2036 + "type": "text",
2037 + "primaryKey": false,
2038 + "notNull": false
2039 + },
2040 + "variant_id": {
2041 + "name": "variant_id",
2042 + "type": "text",
2043 + "primaryKey": false,
2044 + "notNull": false
2045 + },
2046 + "match_method": {
2047 + "name": "match_method",
2048 + "type": "text",
2049 + "primaryKey": false,
2050 + "notNull": false
2051 + },
2052 + "match_confidence": {
2053 + "name": "match_confidence",
2054 + "type": "numeric(8, 6)",
2055 + "primaryKey": false,
2056 + "notNull": false
2057 + },
2058 + "status": {
2059 + "name": "status",
2060 + "type": "text",
2061 + "primaryKey": false,
2062 + "notNull": true,
2063 + "default": "'pending'"
2064 + },
2065 + "reject_reason": {
2066 + "name": "reject_reason",
2067 + "type": "text",
2068 + "primaryKey": false,
2069 + "notNull": false
2070 + },
2071 + "target_id": {
2072 + "name": "target_id",
2073 + "type": "text",
2074 + "primaryKey": false,
2075 + "notNull": false
2076 + },
2077 + "created_at": {
2078 + "name": "created_at",
2079 + "type": "timestamp with time zone",
2080 + "primaryKey": false,
2081 + "notNull": true,
2082 + "default": "now()"
2083 + },
2084 + "processed_at": {
2085 + "name": "processed_at",
2086 + "type": "timestamp with time zone",
2087 + "primaryKey": false,
2088 + "notNull": false
2089 + }
2090 + },
2091 + "indexes": {
2092 + "normalized_records_status_idx": {
2093 + "name": "normalized_records_status_idx",
2094 + "columns": [
2095 + {
2096 + "expression": "status",
2097 + "isExpression": false,
2098 + "asc": true,
2099 + "nulls": "last"
2100 + },
2101 + {
2102 + "expression": "created_at",
2103 + "isExpression": false,
2104 + "asc": true,
2105 + "nulls": "last"
2106 + }
2107 + ],
2108 + "isUnique": false,
2109 + "concurrently": false,
2110 + "method": "btree",
2111 + "with": {}
2112 + },
2113 + "normalized_records_status_kind_idx": {
2114 + "name": "normalized_records_status_kind_idx",
2115 + "columns": [
2116 + {
2117 + "expression": "status",
2118 + "isExpression": false,
2119 + "asc": true,
2120 + "nulls": "last"
2121 + },
2122 + {
2123 + "expression": "kind",
2124 + "isExpression": false,
2125 + "asc": true,
2126 + "nulls": "last"
2127 + },
2128 + {
2129 + "expression": "created_at",
2130 + "isExpression": false,
2131 + "asc": true,
2132 + "nulls": "last"
2133 + }
2134 + ],
2135 + "isUnique": false,
2136 + "concurrently": false,
2137 + "method": "btree",
2138 + "with": {}
2139 + },
2140 + "normalized_records_asset_idx": {
2141 + "name": "normalized_records_asset_idx",
2142 + "columns": [
2143 + {
2144 + "expression": "asset_id",
2145 + "isExpression": false,
2146 + "asc": true,
2147 + "nulls": "last"
2148 + }
2149 + ],
2150 + "isUnique": false,
2151 + "concurrently": false,
2152 + "method": "btree",
2153 + "with": {}
2154 + },
2155 + "normalized_records_raw_uq": {
2156 + "name": "normalized_records_raw_uq",
2157 + "columns": [
2158 + {
2159 + "expression": "raw_record_id",
2160 + "isExpression": false,
2161 + "asc": true,
2162 + "nulls": "last"
2163 + },
2164 + {
2165 + "expression": "kind",
2166 + "isExpression": false,
2167 + "asc": true,
2168 + "nulls": "last"
2169 + },
2170 + {
2171 + "expression": "seq",
2172 + "isExpression": false,
2173 + "asc": true,
2174 + "nulls": "last"
2175 + }
2176 + ],
2177 + "isUnique": true,
2178 + "concurrently": false,
2179 + "method": "btree",
2180 + "with": {}
2181 + }
2182 + },
2183 + "foreignKeys": {},
2184 + "compositePrimaryKeys": {},
2185 + "uniqueConstraints": {},
2186 + "policies": {},
2187 + "checkConstraints": {},
2188 + "isRLSEnabled": false
2189 + },
2190 + "public.raw_records": {
2191 + "name": "raw_records",
2192 + "schema": "",
2193 + "columns": {
2194 + "id": {
2195 + "name": "id",
2196 + "type": "text",
2197 + "primaryKey": true,
2198 + "notNull": true
2199 + },
2200 + "connector_id": {
2201 + "name": "connector_id",
2202 + "type": "text",
2203 + "primaryKey": false,
2204 + "notNull": true
2205 + },
2206 + "source_id": {
2207 + "name": "source_id",
2208 + "type": "text",
2209 + "primaryKey": false,
2210 + "notNull": true
2211 + },
2212 + "run_id": {
2213 + "name": "run_id",
2214 + "type": "text",
2215 + "primaryKey": false,
2216 + "notNull": false
2217 + },
2218 + "engine": {
2219 + "name": "engine",
2220 + "type": "text",
2221 + "primaryKey": false,
2222 + "notNull": true
2223 + },
2224 + "url": {
2225 + "name": "url",
2226 + "type": "text",
2227 + "primaryKey": false,
2228 + "notNull": true
2229 + },
2230 + "external_id": {
2231 + "name": "external_id",
2232 + "type": "text",
2233 + "primaryKey": false,
2234 + "notNull": false
2235 + },
2236 + "kind": {
2237 + "name": "kind",
2238 + "type": "text",
2239 + "primaryKey": false,
2240 + "notNull": true
2241 + },
2242 + "fetched_at": {
2243 + "name": "fetched_at",
2244 + "type": "timestamp with time zone",
2245 + "primaryKey": false,
2246 + "notNull": true
2247 + },
2248 + "content_hash": {
2249 + "name": "content_hash",
2250 + "type": "text",
2251 + "primaryKey": false,
2252 + "notNull": true
2253 + },
2254 + "http_status": {
2255 + "name": "http_status",
2256 + "type": "integer",
2257 + "primaryKey": false,
2258 + "notNull": false
2259 + },
2260 + "payload": {
2261 + "name": "payload",
2262 + "type": "jsonb",
2263 + "primaryKey": false,
2264 + "notNull": true
2265 + },
2266 + "snapshot_ref": {
2267 + "name": "snapshot_ref",
2268 + "type": "text",
2269 + "primaryKey": false,
2270 + "notNull": false
2271 + },
2272 + "parser_version": {
2273 + "name": "parser_version",
2274 + "type": "text",
2275 + "primaryKey": false,
2276 + "notNull": true
2277 + },
2278 + "connector_version": {
2279 + "name": "connector_version",
2280 + "type": "text",
2281 + "primaryKey": false,
2282 + "notNull": true
2283 + },
2284 + "processed_at": {
2285 + "name": "processed_at",
2286 + "type": "timestamp with time zone",
2287 + "primaryKey": false,
2288 + "notNull": false
2289 + },
2290 + "process_error": {
2291 + "name": "process_error",
2292 + "type": "text",
2293 + "primaryKey": false,
2294 + "notNull": false
2295 + },
2296 + "created_at": {
2297 + "name": "created_at",
2298 + "type": "timestamp with time zone",
2299 + "primaryKey": false,
2300 + "notNull": true,
2301 + "default": "now()"
2302 + }
2303 + },
2304 + "indexes": {
2305 + "raw_records_connector_hash_uq": {
2306 + "name": "raw_records_connector_hash_uq",
2307 + "columns": [
2308 + {
2309 + "expression": "connector_id",
2310 + "isExpression": false,
2311 + "asc": true,
2312 + "nulls": "last"
2313 + },
2314 + {
2315 + "expression": "content_hash",
2316 + "isExpression": false,
2317 + "asc": true,
2318 + "nulls": "last"
2319 + }
2320 + ],
2321 + "isUnique": true,
2322 + "concurrently": false,
2323 + "method": "btree",
2324 + "with": {}
2325 + },
2326 + "raw_records_connector_fetched_idx": {
2327 + "name": "raw_records_connector_fetched_idx",
2328 + "columns": [
2329 + {
2330 + "expression": "connector_id",
2331 + "isExpression": false,
2332 + "asc": true,
2333 + "nulls": "last"
2334 + },
2335 + {
2336 + "expression": "fetched_at",
2337 + "isExpression": false,
2338 + "asc": true,
2339 + "nulls": "last"
2340 + }
2341 + ],
2342 + "isUnique": false,
2343 + "concurrently": false,
2344 + "method": "btree",
2345 + "with": {}
2346 + },
2347 + "raw_records_unprocessed_idx": {
2348 + "name": "raw_records_unprocessed_idx",
2349 + "columns": [
2350 + {
2351 + "expression": "processed_at",
2352 + "isExpression": false,
2353 + "asc": true,
2354 + "nulls": "last"
2355 + }
2356 + ],
2357 + "isUnique": false,
2358 + "concurrently": false,
2359 + "method": "btree",
2360 + "with": {}
2361 + },
2362 + "raw_records_external_idx": {
2363 + "name": "raw_records_external_idx",
2364 + "columns": [
2365 + {
2366 + "expression": "connector_id",
2367 + "isExpression": false,
2368 + "asc": true,
2369 + "nulls": "last"
2370 + },
2371 + {
2372 + "expression": "external_id",
2373 + "isExpression": false,
2374 + "asc": true,
2375 + "nulls": "last"
2376 + }
2377 + ],
2378 + "isUnique": false,
2379 + "concurrently": false,
2380 + "method": "btree",
2381 + "with": {}
2382 + }
2383 + },
2384 + "foreignKeys": {},
2385 + "compositePrimaryKeys": {},
2386 + "uniqueConstraints": {},
2387 + "policies": {},
2388 + "checkConstraints": {},
2389 + "isRLSEnabled": false
2390 + },
2391 + "public.asset_embeddings": {
2392 + "name": "asset_embeddings",
2393 + "schema": "",
2394 + "columns": {
2395 + "asset_id": {
2396 + "name": "asset_id",
2397 + "type": "text",
2398 + "primaryKey": true,
2399 + "notNull": true
2400 + },
2401 + "model": {
2402 + "name": "model",
2403 + "type": "text",
2404 + "primaryKey": false,
2405 + "notNull": true
2406 + },
2407 + "embedding": {
2408 + "name": "embedding",
2409 + "type": "vector(1536)",
2410 + "primaryKey": false,
2411 + "notNull": true
2412 + },
2413 + "updated_at": {
2414 + "name": "updated_at",
2415 + "type": "timestamp with time zone",
2416 + "primaryKey": false,
2417 + "notNull": true,
2418 + "default": "now()"
2419 + }
2420 + },
2421 + "indexes": {},
2422 + "foreignKeys": {},
2423 + "compositePrimaryKeys": {},
2424 + "uniqueConstraints": {},
2425 + "policies": {},
2426 + "checkConstraints": {},
2427 + "isRLSEnabled": false
2428 + },
2429 + "public.asset_stats": {
2430 + "name": "asset_stats",
2431 + "schema": "",
2432 + "columns": {
2433 + "asset_id": {
2434 + "name": "asset_id",
2435 + "type": "text",
2436 + "primaryKey": true,
2437 + "notNull": true
2438 + },
2439 + "riv_usd": {
2440 + "name": "riv_usd",
2441 + "type": "numeric(18, 4)",
2442 + "primaryKey": false,
2443 + "notNull": false
2444 + },
2445 + "riv_low_usd": {
2446 + "name": "riv_low_usd",
2447 + "type": "numeric(18, 4)",
2448 + "primaryKey": false,
2449 + "notNull": false
2450 + },
2451 + "riv_high_usd": {
2452 + "name": "riv_high_usd",
2453 + "type": "numeric(18, 4)",
2454 + "primaryKey": false,
2455 + "notNull": false
2456 + },
2457 + "riv_confidence": {
2458 + "name": "riv_confidence",
2459 + "type": "numeric(8, 6)",
2460 + "primaryKey": false,
2461 + "notNull": false
2462 + },
2463 + "riv_sample_size": {
2464 + "name": "riv_sample_size",
2465 + "type": "integer",
2466 + "primaryKey": false,
2467 + "notNull": true,
2468 + "default": 0
2469 + },
2470 + "riv_variant_id": {
2471 + "name": "riv_variant_id",
2472 + "type": "text",
2473 + "primaryKey": false,
2474 + "notNull": false
2475 + },
2476 + "latest_sale_usd": {
2477 + "name": "latest_sale_usd",
2478 + "type": "numeric(18, 4)",
2479 + "primaryKey": false,
2480 + "notNull": false
2481 + },
2482 + "latest_sale_at": {
2483 + "name": "latest_sale_at",
2484 + "type": "timestamp with time zone",
2485 + "primaryKey": false,
2486 + "notNull": false
2487 + },
2488 + "change_1d": {
2489 + "name": "change_1d",
2490 + "type": "numeric(8, 6)",
2491 + "primaryKey": false,
2492 + "notNull": false
2493 + },
2494 + "change_7d": {
2495 + "name": "change_7d",
2496 + "type": "numeric(8, 6)",
2497 + "primaryKey": false,
2498 + "notNull": false
2499 + },
2500 + "change_30d": {
2501 + "name": "change_30d",
2502 + "type": "numeric(8, 6)",
2503 + "primaryKey": false,
2504 + "notNull": false
2505 + },
2506 + "change_90d": {
2507 + "name": "change_90d",
2508 + "type": "numeric(8, 6)",
2509 + "primaryKey": false,
2510 + "notNull": false
2511 + },
2512 + "change_1y": {
2513 + "name": "change_1y",
2514 + "type": "numeric(8, 6)",
2515 + "primaryKey": false,
2516 + "notNull": false
2517 + },
2518 + "ath_usd": {
2519 + "name": "ath_usd",
2520 + "type": "numeric(18, 4)",
2521 + "primaryKey": false,
2522 + "notNull": false
2523 + },
2524 + "ath_at": {
2525 + "name": "ath_at",
2526 + "type": "timestamp with time zone",
2527 + "primaryKey": false,
2528 + "notNull": false
2529 + },
2530 + "atl_usd": {
2531 + "name": "atl_usd",
2532 + "type": "numeric(18, 4)",
2533 + "primaryKey": false,
2534 + "notNull": false
2535 + },
2536 + "atl_at": {
2537 + "name": "atl_at",
2538 + "type": "timestamp with time zone",
2539 + "primaryKey": false,
2540 + "notNull": false
2541 + },
2542 + "sales_count": {
2543 + "name": "sales_count",
2544 + "type": "integer",
2545 + "primaryKey": false,
2546 + "notNull": true,
2547 + "default": 0
2548 + },
2549 + "sales_30d": {
2550 + "name": "sales_30d",
2551 + "type": "integer",
2552 + "primaryKey": false,
2553 + "notNull": true,
2554 + "default": 0
2555 + },
2556 + "sales_1y": {
2557 + "name": "sales_1y",
2558 + "type": "integer",
2559 + "primaryKey": false,
2560 + "notNull": true,
2561 + "default": 0
2562 + },
2563 + "volume_30d_usd": {
2564 + "name": "volume_30d_usd",
2565 + "type": "numeric(18, 4)",
2566 + "primaryKey": false,
2567 + "notNull": false
2568 + },
2569 + "active_listings": {
2570 + "name": "active_listings",
2571 + "type": "integer",
2572 + "primaryKey": false,
2573 + "notNull": true,
2574 + "default": 0
2575 + },
2576 + "min_ask_usd": {
2577 + "name": "min_ask_usd",
2578 + "type": "numeric(18, 4)",
2579 + "primaryKey": false,
2580 + "notNull": false
2581 + },
2582 + "observations_count": {
2583 + "name": "observations_count",
2584 + "type": "integer",
2585 + "primaryKey": false,
2586 + "notNull": true,
2587 + "default": 0
2588 + },
2589 + "sources_count": {
2590 + "name": "sources_count",
2591 + "type": "integer",
2592 + "primaryKey": false,
2593 + "notNull": true,
2594 + "default": 0
2595 + },
2596 + "liquidity_score": {
2597 + "name": "liquidity_score",
2598 + "type": "real",
2599 + "primaryKey": false,
2600 + "notNull": false
2601 + },
2602 + "rarity_score": {
2603 + "name": "rarity_score",
2604 + "type": "real",
2605 + "primaryKey": false,
2606 + "notNull": false
2607 + },
2608 + "momentum_7d": {
2609 + "name": "momentum_7d",
2610 + "type": "real",
2611 + "primaryKey": false,
2612 + "notNull": false
2613 + },
2614 + "momentum_30d": {
2615 + "name": "momentum_30d",
2616 + "type": "real",
2617 + "primaryKey": false,
2618 + "notNull": false
2619 + },
2620 + "momentum_90d": {
2621 + "name": "momentum_90d",
2622 + "type": "real",
2623 + "primaryKey": false,
2624 + "notNull": false
2625 + },
2626 + "momentum_1y": {
2627 + "name": "momentum_1y",
2628 + "type": "real",
2629 + "primaryKey": false,
2630 + "notNull": false
2631 + },
2632 + "trending_score": {
2633 + "name": "trending_score",
2634 + "type": "real",
2635 + "primaryKey": false,
2636 + "notNull": false
2637 + },
2638 + "value_opportunity": {
2639 + "name": "value_opportunity",
2640 + "type": "real",
2641 + "primaryKey": false,
2642 + "notNull": false
2643 + },
2644 + "data_quality": {
2645 + "name": "data_quality",
2646 + "type": "real",
2647 + "primaryKey": false,
2648 + "notNull": false
2649 + },
2650 + "watchers": {
2651 + "name": "watchers",
2652 + "type": "integer",
2653 + "primaryKey": false,
2654 + "notNull": true,
2655 + "default": 0
2656 + },
2657 + "views_30d": {
2658 + "name": "views_30d",
2659 + "type": "integer",
2660 + "primaryKey": false,
2661 + "notNull": true,
2662 + "default": 0
2663 + },
2664 + "updated_at": {
2665 + "name": "updated_at",
2666 + "type": "timestamp with time zone",
2667 + "primaryKey": false,
2668 + "notNull": true,
2669 + "default": "now()"
2670 + }
2671 + },
2672 + "indexes": {
2673 + "asset_stats_riv_idx": {
2674 + "name": "asset_stats_riv_idx",
2675 + "columns": [
2676 + {
2677 + "expression": "riv_usd",
2678 + "isExpression": false,
2679 + "asc": true,
2680 + "nulls": "last"
2681 + }
2682 + ],
2683 + "isUnique": false,
2684 + "concurrently": false,
2685 + "method": "btree",
2686 + "with": {}
2687 + },
2688 + "asset_stats_trending_idx": {
2689 + "name": "asset_stats_trending_idx",
2690 + "columns": [
2691 + {
2692 + "expression": "trending_score",
2693 + "isExpression": false,
2694 + "asc": true,
2695 + "nulls": "last"
2696 + }
2697 + ],
2698 + "isUnique": false,
2699 + "concurrently": false,
2700 + "method": "btree",
2701 + "with": {}
2702 + },
2703 + "asset_stats_liquidity_idx": {
2704 + "name": "asset_stats_liquidity_idx",
2705 + "columns": [
2706 + {
2707 + "expression": "liquidity_score",
2708 + "isExpression": false,
2709 + "asc": true,
2710 + "nulls": "last"
2711 + }
2712 + ],
2713 + "isUnique": false,
2714 + "concurrently": false,
2715 + "method": "btree",
2716 + "with": {}
2717 + },
2718 + "asset_stats_change_7d_idx": {
2719 + "name": "asset_stats_change_7d_idx",
2720 + "columns": [
2721 + {
2722 + "expression": "change_7d",
2723 + "isExpression": false,
2724 + "asc": true,
2725 + "nulls": "last"
2726 + }
2727 + ],
2728 + "isUnique": false,
2729 + "concurrently": false,
2730 + "method": "btree",
2731 + "with": {}
2732 + },
2733 + "asset_stats_change_30d_idx": {
2734 + "name": "asset_stats_change_30d_idx",
2735 + "columns": [
2736 + {
2737 + "expression": "change_30d",
2738 + "isExpression": false,
2739 + "asc": true,
2740 + "nulls": "last"
2741 + }
2742 + ],
2743 + "isUnique": false,
2744 + "concurrently": false,
2745 + "method": "btree",
2746 + "with": {}
2747 + },
2748 + "asset_stats_change_1y_idx": {
2749 + "name": "asset_stats_change_1y_idx",
2750 + "columns": [
2751 + {
2752 + "expression": "change_1y",
2753 + "isExpression": false,
2754 + "asc": true,
2755 + "nulls": "last"
2756 + }
2757 + ],
2758 + "isUnique": false,
2759 + "concurrently": false,
2760 + "method": "btree",
2761 + "with": {}
2762 + },
2763 + "asset_stats_opportunity_idx": {
2764 + "name": "asset_stats_opportunity_idx",
2765 + "columns": [
2766 + {
2767 + "expression": "value_opportunity",
2768 + "isExpression": false,
2769 + "asc": true,
2770 + "nulls": "last"
2771 + }
2772 + ],
2773 + "isUnique": false,
2774 + "where": "value_opportunity is not null",
2775 + "concurrently": false,
2776 + "method": "btree",
2777 + "with": {}
2778 + },
2779 + "asset_stats_sales_30d_idx": {
2780 + "name": "asset_stats_sales_30d_idx",
2781 + "columns": [
2782 + {
2783 + "expression": "sales_30d",
2784 + "isExpression": false,
2785 + "asc": true,
2786 + "nulls": "last"
2787 + }
2788 + ],
2789 + "isUnique": false,
2790 + "concurrently": false,
2791 + "method": "btree",
2792 + "with": {}
2793 + }
2794 + },
2795 + "foreignKeys": {},
2796 + "compositePrimaryKeys": {},
2797 + "uniqueConstraints": {},
2798 + "policies": {},
2799 + "checkConstraints": {},
2800 + "isRLSEnabled": false
2801 + },
2802 + "public.asset_variants": {
2803 + "name": "asset_variants",
2804 + "schema": "",
2805 + "columns": {
2806 + "id": {
2807 + "name": "id",
2808 + "type": "text",
2809 + "primaryKey": true,
2810 + "notNull": true
2811 + },
2812 + "asset_id": {
2813 + "name": "asset_id",
2814 + "type": "text",
2815 + "primaryKey": false,
2816 + "notNull": true
2817 + },
2818 + "variant_key": {
2819 + "name": "variant_key",
2820 + "type": "text",
2821 + "primaryKey": false,
2822 + "notNull": true
2823 + },
2824 + "grader": {
2825 + "name": "grader",
2826 + "type": "text",
2827 + "primaryKey": false,
2828 + "notNull": false
2829 + },
2830 + "grade": {
2831 + "name": "grade",
2832 + "type": "text",
2833 + "primaryKey": false,
2834 + "notNull": false
2835 + },
2836 + "qualifier": {
2837 + "name": "qualifier",
2838 + "type": "text",
2839 + "primaryKey": false,
2840 + "notNull": false
2841 + },
2842 + "condition": {
2843 + "name": "condition",
2844 + "type": "text",
2845 + "primaryKey": false,
2846 + "notNull": false
2847 + },
2848 + "completeness": {
2849 + "name": "completeness",
2850 + "type": "text",
2851 + "primaryKey": false,
2852 + "notNull": false
2853 + },
2854 + "size_label": {
2855 + "name": "size_label",
2856 + "type": "text",
2857 + "primaryKey": false,
2858 + "notNull": false
2859 + },
2860 + "label": {
2861 + "name": "label",
2862 + "type": "text",
2863 + "primaryKey": false,
2864 + "notNull": true
2865 + },
2866 + "is_default": {
2867 + "name": "is_default",
2868 + "type": "boolean",
2869 + "primaryKey": false,
2870 + "notNull": true,
2871 + "default": false
2872 + },
2873 + "created_at": {
2874 + "name": "created_at",
2875 + "type": "timestamp with time zone",
2876 + "primaryKey": false,
2877 + "notNull": true,
2878 + "default": "now()"
2879 + }
2880 + },
2881 + "indexes": {
2882 + "asset_variants_uq": {
2883 + "name": "asset_variants_uq",
2884 + "columns": [
2885 + {
2886 + "expression": "asset_id",
2887 + "isExpression": false,
2888 + "asc": true,
2889 + "nulls": "last"
2890 + },
2891 + {
2892 + "expression": "variant_key",
2893 + "isExpression": false,
2894 + "asc": true,
2895 + "nulls": "last"
2896 + }
2897 + ],
2898 + "isUnique": true,
2899 + "concurrently": false,
2900 + "method": "btree",
2901 + "with": {}
2902 + },
2903 + "asset_variants_asset_idx": {
2904 + "name": "asset_variants_asset_idx",
2905 + "columns": [
2906 + {
2907 + "expression": "asset_id",
2908 + "isExpression": false,
2909 + "asc": true,
2910 + "nulls": "last"
2911 + }
2912 + ],
2913 + "isUnique": false,
2914 + "concurrently": false,
2915 + "method": "btree",
2916 + "with": {}
2917 + }
2918 + },
2919 + "foreignKeys": {},
2920 + "compositePrimaryKeys": {},
2921 + "uniqueConstraints": {},
2922 + "policies": {},
2923 + "checkConstraints": {},
2924 + "isRLSEnabled": false
2925 + },
2926 + "public.assets": {
2927 + "name": "assets",
2928 + "schema": "",
2929 + "columns": {
2930 + "id": {
2931 + "name": "id",
2932 + "type": "text",
2933 + "primaryKey": true,
2934 + "notNull": true
2935 + },
2936 + "slug": {
2937 + "name": "slug",
2938 + "type": "text",
2939 + "primaryKey": false,
2940 + "notNull": true
2941 + },
2942 + "canonical_key": {
2943 + "name": "canonical_key",
2944 + "type": "text",
2945 + "primaryKey": false,
2946 + "notNull": true
2947 + },
2948 + "category_slug": {
2949 + "name": "category_slug",
2950 + "type": "text",
2951 + "primaryKey": false,
2952 + "notNull": true
2953 + },
2954 + "subcategory_slug": {
2955 + "name": "subcategory_slug",
2956 + "type": "text",
2957 + "primaryKey": false,
2958 + "notNull": false
2959 + },
2960 + "family_slug": {
2961 + "name": "family_slug",
2962 + "type": "text",
2963 + "primaryKey": false,
2964 + "notNull": true
2965 + },
2966 + "franchise": {
2967 + "name": "franchise",
2968 + "type": "text",
2969 + "primaryKey": false,
2970 + "notNull": false
2971 + },
2972 + "brand": {
2973 + "name": "brand",
2974 + "type": "text",
2975 + "primaryKey": false,
2976 + "notNull": false
2977 + },
2978 + "series": {
2979 + "name": "series",
2980 + "type": "text",
2981 + "primaryKey": false,
2982 + "notNull": false
2983 + },
2984 + "set_slug": {
2985 + "name": "set_slug",
2986 + "type": "text",
2987 + "primaryKey": false,
2988 + "notNull": false
2989 + },
2990 + "set_name": {
2991 + "name": "set_name",
2992 + "type": "text",
2993 + "primaryKey": false,
2994 + "notNull": false
2995 + },
2996 + "set_code": {
2997 + "name": "set_code",
2998 + "type": "text",
2999 + "primaryKey": false,
3000 + "notNull": false
3001 + },
3002 + "name": {
3003 + "name": "name",
3004 + "type": "text",
3005 + "primaryKey": false,
3006 + "notNull": true
3007 + },
3008 + "title": {
3009 + "name": "title",
3010 + "type": "text",
3011 + "primaryKey": false,
3012 + "notNull": true
3013 + },
3014 + "model": {
3015 + "name": "model",
3016 + "type": "text",
3017 + "primaryKey": false,
3018 + "notNull": false
3019 + },
3020 + "reference": {
3021 + "name": "reference",
3022 + "type": "text",
3023 + "primaryKey": false,
3024 + "notNull": false
3025 + },
3026 + "number": {
3027 + "name": "number",
3028 + "type": "text",
3029 + "primaryKey": false,
3030 + "notNull": false
3031 + },
3032 + "year": {
3033 + "name": "year",
3034 + "type": "integer",
3035 + "primaryKey": false,
3036 + "notNull": false
3037 + },
3038 + "edition": {
3039 + "name": "edition",
3040 + "type": "text",
3041 + "primaryKey": false,
3042 + "notNull": false
3043 + },
3044 + "variant": {
3045 + "name": "variant",
3046 + "type": "text",
3047 + "primaryKey": false,
3048 + "notNull": false
3049 + },
3050 + "language": {
3051 + "name": "language",
3052 + "type": "text",
3053 + "primaryKey": false,
3054 + "notNull": false
3055 + },
3056 + "region": {
3057 + "name": "region",
3058 + "type": "text",
3059 + "primaryKey": false,
3060 + "notNull": false
3061 + },
3062 + "country": {
3063 + "name": "country",
3064 + "type": "text",
3065 + "primaryKey": false,
3066 + "notNull": false
3067 + },
3068 + "material": {
3069 + "name": "material",
3070 + "type": "text",
3071 + "primaryKey": false,
3072 + "notNull": false
3073 + },
3074 + "size": {
3075 + "name": "size",
3076 + "type": "text",
3077 + "primaryKey": false,
3078 + "notNull": false
3079 + },
3080 + "color": {
3081 + "name": "color",
3082 + "type": "text",
3083 + "primaryKey": false,
3084 + "notNull": false
3085 + },
3086 + "rarity": {
3087 + "name": "rarity",
3088 + "type": "text",
3089 + "primaryKey": false,
3090 + "notNull": false
3091 + },
3092 + "production_quantity": {
3093 + "name": "production_quantity",
3094 + "type": "integer",
3095 + "primaryKey": false,
3096 + "notNull": false
3097 + },
3098 + "original_msrp": {
3099 + "name": "original_msrp",
3100 + "type": "numeric(18, 4)",
3101 + "primaryKey": false,
3102 + "notNull": false
3103 + },
3104 + "original_msrp_currency": {
3105 + "name": "original_msrp_currency",
3106 + "type": "text",
3107 + "primaryKey": false,
3108 + "notNull": false
3109 + },
3110 + "release_date": {
3111 + "name": "release_date",
3112 + "type": "text",
3113 + "primaryKey": false,
3114 + "notNull": false
3115 + },
3116 + "description": {
3117 + "name": "description",
3118 + "type": "text",
3119 + "primaryKey": false,
3120 + "notNull": false
3121 + },
3122 + "hero_image_url": {
3123 + "name": "hero_image_url",
3124 + "type": "text",
3125 + "primaryKey": false,
3126 + "notNull": false
3127 + },
3128 + "identifiers": {
3129 + "name": "identifiers",
3130 + "type": "jsonb",
3131 + "primaryKey": false,
3132 + "notNull": true,
3133 + "default": "'{}'::jsonb"
3134 + },
3135 + "metadata": {
3136 + "name": "metadata",
3137 + "type": "jsonb",
3138 + "primaryKey": false,
3139 + "notNull": true,
3140 + "default": "'{}'::jsonb"
3141 + },
3142 + "merged_from": {
3143 + "name": "merged_from",
3144 + "type": "text[]",
3145 + "primaryKey": false,
3146 + "notNull": true,
3147 + "default": "'{}'::text[]"
3148 + },
3149 + "data_quality": {
3150 + "name": "data_quality",
3151 + "type": "real",
3152 + "primaryKey": false,
3153 + "notNull": true,
3154 + "default": 0
3155 + },
3156 + "verified": {
3157 + "name": "verified",
3158 + "type": "boolean",
3159 + "primaryKey": false,
3160 + "notNull": true,
3161 + "default": false
3162 + },
3163 + "search": {
3164 + "name": "search",
3165 + "type": "tsvector",
3166 + "primaryKey": false,
3167 + "notNull": false,
3168 + "generated": {
3169 + "as": "setweight(to_tsvector('simple', coalesce(name, '')), 'A') || setweight(to_tsvector('simple', coalesce(set_name, '') || ' ' || coalesce(number, '') || ' ' || coalesce(variant, '') || ' ' || coalesce(edition, '')), 'B') || setweight(to_tsvector('simple', coalesce(brand, '') || ' ' || coalesce(franchise, '') || ' ' || coalesce(reference, '') || ' ' || coalesce(model, '') || ' ' || coalesce(year::text, '') || ' ' || coalesce(category_slug, '')), 'C')",
3170 + "type": "stored"
3171 + }
3172 + },
3173 + "created_at": {
3174 + "name": "created_at",
3175 + "type": "timestamp with time zone",
3176 + "primaryKey": false,
3177 + "notNull": true,
3178 + "default": "now()"
3179 + },
3180 + "updated_at": {
3181 + "name": "updated_at",
3182 + "type": "timestamp with time zone",
3183 + "primaryKey": false,
3184 + "notNull": true,
3185 + "default": "now()"
3186 + }
3187 + },
3188 + "indexes": {
3189 + "assets_canonical_key_uq": {
3190 + "name": "assets_canonical_key_uq",
3191 + "columns": [
3192 + {
3193 + "expression": "canonical_key",
3194 + "isExpression": false,
3195 + "asc": true,
3196 + "nulls": "last"
3197 + }
3198 + ],
3199 + "isUnique": true,
3200 + "concurrently": false,
3201 + "method": "btree",
3202 + "with": {}
3203 + },
3204 + "assets_slug_uq": {
3205 + "name": "assets_slug_uq",
3206 + "columns": [
3207 + {
3208 + "expression": "slug",
3209 + "isExpression": false,
3210 + "asc": true,
3211 + "nulls": "last"
3212 + }
3213 + ],
3214 + "isUnique": true,
3215 + "concurrently": false,
3216 + "method": "btree",
3217 + "with": {}
3218 + },
3219 + "assets_category_idx": {
3220 + "name": "assets_category_idx",
3221 + "columns": [
3222 + {
3223 + "expression": "category_slug",
3224 + "isExpression": false,
3225 + "asc": true,
3226 + "nulls": "last"
3227 + }
3228 + ],
3229 + "isUnique": false,
3230 + "concurrently": false,
3231 + "method": "btree",
3232 + "with": {}
3233 + },
3234 + "assets_family_idx": {
3235 + "name": "assets_family_idx",
3236 + "columns": [
3237 + {
3238 + "expression": "family_slug",
3239 + "isExpression": false,
3240 + "asc": true,
3241 + "nulls": "last"
3242 + }
3243 + ],
3244 + "isUnique": false,
3245 + "concurrently": false,
3246 + "method": "btree",
3247 + "with": {}
3248 + },
3249 + "assets_set_idx": {
3250 + "name": "assets_set_idx",
3251 + "columns": [
3252 + {
3253 + "expression": "set_slug",
3254 + "isExpression": false,
3255 + "asc": true,
3256 + "nulls": "last"
3257 + }
3258 + ],
3259 + "isUnique": false,
3260 + "concurrently": false,
3261 + "method": "btree",
3262 + "with": {}
3263 + },
3264 + "assets_search_gin": {
3265 + "name": "assets_search_gin",
3266 + "columns": [
3267 + {
3268 + "expression": "search",
3269 + "isExpression": false,
3270 + "asc": true,
3271 + "nulls": "last"
3272 + }
3273 + ],
3274 + "isUnique": false,
3275 + "concurrently": false,
3276 + "method": "gin",
3277 + "with": {}
3278 + },
3279 + "assets_title_trgm": {
3280 + "name": "assets_title_trgm",
3281 + "columns": [
3282 + {
3283 + "expression": "\"title\" gin_trgm_ops",
3284 + "asc": true,
3285 + "isExpression": true,
3286 + "nulls": "last"
3287 + }
3288 + ],
3289 + "isUnique": false,
3290 + "concurrently": false,
3291 + "method": "gin",
3292 + "with": {}
3293 + },
3294 + "assets_identifiers_gin": {
3295 + "name": "assets_identifiers_gin",
3296 + "columns": [
3297 + {
3298 + "expression": "identifiers",
3299 + "isExpression": false,
3300 + "asc": true,
3301 + "nulls": "last"
3302 + }
3303 + ],
3304 + "isUnique": false,
3305 + "concurrently": false,
3306 + "method": "gin",
3307 + "with": {}
3308 + }
3309 + },
3310 + "foreignKeys": {},
3311 + "compositePrimaryKeys": {},
3312 + "uniqueConstraints": {},
3313 + "policies": {},
3314 + "checkConstraints": {},
3315 + "isRLSEnabled": false
3316 + },
3317 + "public.grade_premiums": {
3318 + "name": "grade_premiums",
3319 + "schema": "",
3320 + "columns": {
3321 + "id": {
3322 + "name": "id",
3323 + "type": "text",
3324 + "primaryKey": true,
3325 + "notNull": true
3326 + },
3327 + "category_slug": {
3328 + "name": "category_slug",
3329 + "type": "text",
3330 + "primaryKey": false,
3331 + "notNull": true
3332 + },
3333 + "grader": {
3334 + "name": "grader",
3335 + "type": "text",
3336 + "primaryKey": false,
3337 + "notNull": true
3338 + },
3339 + "grade": {
3340 + "name": "grade",
3341 + "type": "text",
3342 + "primaryKey": false,
3343 + "notNull": true
3344 + },
3345 + "market_multiplier": {
3346 + "name": "market_multiplier",
3347 + "type": "real",
3348 + "primaryKey": false,
3349 + "notNull": true
3350 + },
3351 + "sample_size": {
3352 + "name": "sample_size",
3353 + "type": "integer",
3354 + "primaryKey": false,
3355 + "notNull": true
3356 + },
3357 + "computed_at": {
3358 + "name": "computed_at",
3359 + "type": "timestamp with time zone",
3360 + "primaryKey": false,
3361 + "notNull": true
3362 + }
3363 + },
3364 + "indexes": {
3365 + "grade_premiums_uq": {
3366 + "name": "grade_premiums_uq",
3367 + "columns": [
3368 + {
3369 + "expression": "category_slug",
3370 + "isExpression": false,
3371 + "asc": true,
3372 + "nulls": "last"
3373 + },
3374 + {
3375 + "expression": "grader",
3376 + "isExpression": false,
3377 + "asc": true,
3378 + "nulls": "last"
3379 + },
3380 + {
3381 + "expression": "grade",
3382 + "isExpression": false,
3383 + "asc": true,
3384 + "nulls": "last"
3385 + }
3386 + ],
3387 + "isUnique": true,
3388 + "concurrently": false,
3389 + "method": "btree",
3390 + "with": {}
3391 + }
3392 + },
3393 + "foreignKeys": {},
3394 + "compositePrimaryKeys": {},
3395 + "uniqueConstraints": {},
3396 + "policies": {},
3397 + "checkConstraints": {},
3398 + "isRLSEnabled": false
3399 + },
3400 + "public.images": {
3401 + "name": "images",
3402 + "schema": "",
3403 + "columns": {
3404 + "id": {
3405 + "name": "id",
3406 + "type": "text",
3407 + "primaryKey": true,
3408 + "notNull": true
3409 + },
3410 + "asset_id": {
3411 + "name": "asset_id",
3412 + "type": "text",
3413 + "primaryKey": false,
3414 + "notNull": false
3415 + },
3416 + "listing_id": {
3417 + "name": "listing_id",
3418 + "type": "text",
3419 + "primaryKey": false,
3420 + "notNull": false
3421 + },
3422 + "sale_id": {
3423 + "name": "sale_id",
3424 + "type": "text",
3425 + "primaryKey": false,
3426 + "notNull": false
3427 + },
3428 + "source_id": {
3429 + "name": "source_id",
3430 + "type": "text",
3431 + "primaryKey": false,
3432 + "notNull": false
3433 + },
3434 + "url": {
3435 + "name": "url",
3436 + "type": "text",
3437 + "primaryKey": false,
3438 + "notNull": true
3439 + },
3440 + "role": {
3441 + "name": "role",
3442 + "type": "text",
3443 + "primaryKey": false,
3444 + "notNull": true,
3445 + "default": "'gallery'"
3446 + },
3447 + "width": {
3448 + "name": "width",
3449 + "type": "integer",
3450 + "primaryKey": false,
3451 + "notNull": false
3452 + },
3453 + "height": {
3454 + "name": "height",
3455 + "type": "integer",
3456 + "primaryKey": false,
3457 + "notNull": false
3458 + },
3459 + "phash": {
3460 + "name": "phash",
3461 + "type": "text",
3462 + "primaryKey": false,
3463 + "notNull": false
3464 + },
3465 + "embedding": {
3466 + "name": "embedding",
3467 + "type": "vector(512)",
3468 + "primaryKey": false,
3469 + "notNull": false
3470 + },
3471 + "attribution": {
3472 + "name": "attribution",
3473 + "type": "text",
3474 + "primaryKey": false,
3475 + "notNull": false
3476 + },
3477 + "status": {
3478 + "name": "status",
3479 + "type": "text",
3480 + "primaryKey": false,
3481 + "notNull": true,
3482 + "default": "'unchecked'"
3483 + },
3484 + "checked_at": {
3485 + "name": "checked_at",
3486 + "type": "timestamp with time zone",
3487 + "primaryKey": false,
3488 + "notNull": false
3489 + },
3490 + "bytes": {
3491 + "name": "bytes",
3492 + "type": "integer",
3493 + "primaryKey": false,
3494 + "notNull": false
3495 + },
3496 + "content_type": {
3497 + "name": "content_type",
3498 + "type": "text",
3499 + "primaryKey": false,
3500 + "notNull": false
3501 + },
3502 + "cache_key": {
3503 + "name": "cache_key",
3504 + "type": "text",
3505 + "primaryKey": false,
3506 + "notNull": false
3507 + },
3508 + "error": {
3509 + "name": "error",
3510 + "type": "text",
3511 + "primaryKey": false,
3512 + "notNull": false
3513 + },
3514 + "created_at": {
3515 + "name": "created_at",
3516 + "type": "timestamp with time zone",
3517 + "primaryKey": false,
3518 + "notNull": true,
3519 + "default": "now()"
3520 + }
3521 + },
3522 + "indexes": {
3523 + "images_asset_idx": {
3524 + "name": "images_asset_idx",
3525 + "columns": [
3526 + {
3527 + "expression": "asset_id",
3528 + "isExpression": false,
3529 + "asc": true,
3530 + "nulls": "last"
3531 + }
3532 + ],
3533 + "isUnique": false,
3534 + "concurrently": false,
3535 + "method": "btree",
3536 + "with": {}
3537 + },
3538 + "images_phash_idx": {
3539 + "name": "images_phash_idx",
3540 + "columns": [
3541 + {
3542 + "expression": "phash",
3543 + "isExpression": false,
3544 + "asc": true,
3545 + "nulls": "last"
3546 + }
3547 + ],
3548 + "isUnique": false,
3549 + "concurrently": false,
3550 + "method": "btree",
3551 + "with": {}
3552 + },
3553 + "images_url_uq": {
3554 + "name": "images_url_uq",
3555 + "columns": [
3556 + {
3557 + "expression": "url",
3558 + "isExpression": false,
3559 + "asc": true,
3560 + "nulls": "last"
3561 + }
3562 + ],
3563 + "isUnique": true,
3564 + "concurrently": false,
3565 + "method": "btree",
3566 + "with": {}
3567 + },
3568 + "images_cache_key_idx": {
3569 + "name": "images_cache_key_idx",
3570 + "columns": [
3571 + {
3572 + "expression": "cache_key",
3573 + "isExpression": false,
3574 + "asc": true,
3575 + "nulls": "last"
3576 + }
3577 + ],
3578 + "isUnique": false,
3579 + "concurrently": false,
3580 + "method": "btree",
3581 + "with": {}
3582 + },
3583 + "images_status_idx": {
3584 + "name": "images_status_idx",
3585 + "columns": [
3586 + {
3587 + "expression": "status",
3588 + "isExpression": false,
3589 + "asc": true,
3590 + "nulls": "last"
3591 + },
3592 + {
3593 + "expression": "checked_at",
3594 + "isExpression": false,
3595 + "asc": true,
3596 + "nulls": "last"
3597 + }
3598 + ],
3599 + "isUnique": false,
3600 + "concurrently": false,
3601 + "method": "btree",
3602 + "with": {}
3603 + }
3604 + },
3605 + "foreignKeys": {},
3606 + "compositePrimaryKeys": {},
3607 + "uniqueConstraints": {},
3608 + "policies": {},
3609 + "checkConstraints": {},
3610 + "isRLSEnabled": false
3611 + },
3612 + "public.population_reports": {
3613 + "name": "population_reports",
3614 + "schema": "",
3615 + "columns": {
3616 + "id": {
3617 + "name": "id",
3618 + "type": "text",
3619 + "primaryKey": true,
3620 + "notNull": true
3621 + },
3622 + "asset_id": {
3623 + "name": "asset_id",
3624 + "type": "text",
3625 + "primaryKey": false,
3626 + "notNull": true
3627 + },
3628 + "grader": {
3629 + "name": "grader",
3630 + "type": "text",
3631 + "primaryKey": false,
3632 + "notNull": true
3633 + },
3634 + "source_id": {
3635 + "name": "source_id",
3636 + "type": "text",
3637 + "primaryKey": false,
3638 + "notNull": true
3639 + },
3640 + "source_url": {
3641 + "name": "source_url",
3642 + "type": "text",
3643 + "primaryKey": false,
3644 + "notNull": false
3645 + },
3646 + "report_date": {
3647 + "name": "report_date",
3648 + "type": "text",
3649 + "primaryKey": false,
3650 + "notNull": true
3651 + },
3652 + "total": {
3653 + "name": "total",
3654 + "type": "integer",
3655 + "primaryKey": false,
3656 + "notNull": true
3657 + },
3658 + "by_grade": {
3659 + "name": "by_grade",
3660 + "type": "jsonb",
3661 + "primaryKey": false,
3662 + "notNull": true
3663 + },
3664 + "created_at": {
3665 + "name": "created_at",
3666 + "type": "timestamp with time zone",
3667 + "primaryKey": false,
3668 + "notNull": true,
3669 + "default": "now()"
3670 + }
3671 + },
3672 + "indexes": {
3673 + "population_reports_uq": {
3674 + "name": "population_reports_uq",
3675 + "columns": [
3676 + {
3677 + "expression": "asset_id",
3678 + "isExpression": false,
3679 + "asc": true,
3680 + "nulls": "last"
3681 + },
3682 + {
3683 + "expression": "grader",
3684 + "isExpression": false,
3685 + "asc": true,
3686 + "nulls": "last"
3687 + },
3688 + {
3689 + "expression": "report_date",
3690 + "isExpression": false,
3691 + "asc": true,
3692 + "nulls": "last"
3693 + }
3694 + ],
3695 + "isUnique": true,
3696 + "concurrently": false,
3697 + "method": "btree",
3698 + "with": {}
3699 + }
3700 + },
3701 + "foreignKeys": {},
3702 + "compositePrimaryKeys": {},
3703 + "uniqueConstraints": {},
3704 + "policies": {},
3705 + "checkConstraints": {},
3706 + "isRLSEnabled": false
3707 + },
3708 + "public.variant_stats": {
3709 + "name": "variant_stats",
3710 + "schema": "",
3711 + "columns": {
3712 + "variant_id": {
3713 + "name": "variant_id",
3714 + "type": "text",
3715 + "primaryKey": true,
3716 + "notNull": true
3717 + },
3718 + "asset_id": {
3719 + "name": "asset_id",
3720 + "type": "text",
3721 + "primaryKey": false,
3722 + "notNull": true
3723 + },
3724 + "riv_usd": {
3725 + "name": "riv_usd",
3726 + "type": "numeric(18, 4)",
3727 + "primaryKey": false,
3728 + "notNull": false
3729 + },
3730 + "riv_low_usd": {
3731 + "name": "riv_low_usd",
3732 + "type": "numeric(18, 4)",
3733 + "primaryKey": false,
3734 + "notNull": false
3735 + },
3736 + "riv_high_usd": {
3737 + "name": "riv_high_usd",
3738 + "type": "numeric(18, 4)",
3739 + "primaryKey": false,
3740 + "notNull": false
3741 + },
3742 + "riv_confidence": {
3743 + "name": "riv_confidence",
3744 + "type": "numeric(8, 6)",
3745 + "primaryKey": false,
3746 + "notNull": false
3747 + },
3748 + "riv_sample_size": {
3749 + "name": "riv_sample_size",
3750 + "type": "integer",
3751 + "primaryKey": false,
3752 + "notNull": true,
3753 + "default": 0
3754 + },
3755 + "latest_sale_usd": {
3756 + "name": "latest_sale_usd",
3757 + "type": "numeric(18, 4)",
3758 + "primaryKey": false,
3759 + "notNull": false
3760 + },
3761 + "latest_sale_at": {
3762 + "name": "latest_sale_at",
3763 + "type": "timestamp with time zone",
3764 + "primaryKey": false,
3765 + "notNull": false
3766 + },
3767 + "change_30d": {
3768 + "name": "change_30d",
3769 + "type": "numeric(8, 6)",
3770 + "primaryKey": false,
3771 + "notNull": false
3772 + },
3773 + "change_1y": {
3774 + "name": "change_1y",
3775 + "type": "numeric(8, 6)",
3776 + "primaryKey": false,
3777 + "notNull": false
3778 + },
3779 + "sales_count": {
3780 + "name": "sales_count",
3781 + "type": "integer",
3782 + "primaryKey": false,
3783 + "notNull": true,
3784 + "default": 0
3785 + },
3786 + "sales_30d": {
3787 + "name": "sales_30d",
3788 + "type": "integer",
3789 + "primaryKey": false,
3790 + "notNull": true,
3791 + "default": 0
3792 + },
3793 + "active_listings": {
3794 + "name": "active_listings",
3795 + "type": "integer",
3796 + "primaryKey": false,
3797 + "notNull": true,
3798 + "default": 0
3799 + },
3800 + "min_ask_usd": {
3801 + "name": "min_ask_usd",
3802 + "type": "numeric(18, 4)",
3803 + "primaryKey": false,
3804 + "notNull": false
3805 + },
3806 + "liquidity_score": {
3807 + "name": "liquidity_score",
3808 + "type": "real",
3809 + "primaryKey": false,
3810 + "notNull": false
3811 + },
3812 + "updated_at": {
3813 + "name": "updated_at",
3814 + "type": "timestamp with time zone",
3815 + "primaryKey": false,
3816 + "notNull": true,
3817 + "default": "now()"
3818 + }
3819 + },
3820 + "indexes": {
3821 + "variant_stats_asset_idx": {
3822 + "name": "variant_stats_asset_idx",
3823 + "columns": [
3824 + {
3825 + "expression": "asset_id",
3826 + "isExpression": false,
3827 + "asc": true,
3828 + "nulls": "last"
3829 + }
3830 + ],
3831 + "isUnique": false,
3832 + "concurrently": false,
3833 + "method": "btree",
3834 + "with": {}
3835 + }
3836 + },
3837 + "foreignKeys": {},
3838 + "compositePrimaryKeys": {},
3839 + "uniqueConstraints": {},
3840 + "policies": {},
3841 + "checkConstraints": {},
3842 + "isRLSEnabled": false
3843 + },
3844 + "public.auction_lots": {
3845 + "name": "auction_lots",
3846 + "schema": "",
3847 + "columns": {
3848 + "id": {
3849 + "name": "id",
3850 + "type": "text",
3851 + "primaryKey": true,
3852 + "notNull": true
3853 + },
3854 + "auction_id": {
3855 + "name": "auction_id",
3856 + "type": "text",
3857 + "primaryKey": false,
3858 + "notNull": true
3859 + },
3860 + "asset_id": {
3861 + "name": "asset_id",
3862 + "type": "text",
3863 + "primaryKey": false,
3864 + "notNull": false
3865 + },
3866 + "variant_id": {
3867 + "name": "variant_id",
3868 + "type": "text",
3869 + "primaryKey": false,
3870 + "notNull": false
3871 + },
3872 + "source_id": {
3873 + "name": "source_id",
3874 + "type": "text",
3875 + "primaryKey": false,
3876 + "notNull": true
3877 + },
3878 + "lot_number": {
3879 + "name": "lot_number",
3880 + "type": "text",
3881 + "primaryKey": false,
3882 + "notNull": false
3883 + },
3884 + "title": {
3885 + "name": "title",
3886 + "type": "text",
3887 + "primaryKey": false,
3888 + "notNull": true
3889 + },
3890 + "url": {
3891 + "name": "url",
3892 + "type": "text",
3893 + "primaryKey": false,
3894 + "notNull": true
3895 + },
3896 + "estimate_low": {
3897 + "name": "estimate_low",
3898 + "type": "numeric(18, 4)",
3899 + "primaryKey": false,
3900 + "notNull": false
3901 + },
3902 + "estimate_high": {
3903 + "name": "estimate_high",
3904 + "type": "numeric(18, 4)",
3905 + "primaryKey": false,
3906 + "notNull": false
3907 + },
3908 + "current_bid": {
3909 + "name": "current_bid",
3910 + "type": "numeric(18, 4)",
3911 + "primaryKey": false,
3912 + "notNull": false
3913 + },
3914 + "hammer_price": {
3915 + "name": "hammer_price",
3916 + "type": "numeric(18, 4)",
3917 + "primaryKey": false,
3918 + "notNull": false
3919 + },
3920 + "currency": {
3921 + "name": "currency",
3922 + "type": "text",
3923 + "primaryKey": false,
3924 + "notNull": false
3925 + },
3926 + "bid_count": {
3927 + "name": "bid_count",
3928 + "type": "integer",
3929 + "primaryKey": false,
3930 + "notNull": false
3931 + },
3932 + "starts_at": {
3933 + "name": "starts_at",
3934 + "type": "timestamp with time zone",
3935 + "primaryKey": false,
3936 + "notNull": false
3937 + },
3938 + "ends_at": {
3939 + "name": "ends_at",
3940 + "type": "timestamp with time zone",
3941 + "primaryKey": false,
3942 + "notNull": false
3943 + },
3944 + "status": {
3945 + "name": "status",
3946 + "type": "text",
3947 + "primaryKey": false,
3948 + "notNull": true,
3949 + "default": "'upcoming'"
3950 + },
3951 + "image_urls": {
3952 + "name": "image_urls",
3953 + "type": "jsonb",
3954 + "primaryKey": false,
3955 + "notNull": true,
3956 + "default": "'[]'::jsonb"
3957 + },
3958 + "grader": {
3959 + "name": "grader",
3960 + "type": "text",
3961 + "primaryKey": false,
3962 + "notNull": false
3963 + },
3964 + "grade": {
3965 + "name": "grade",
3966 + "type": "text",
3967 + "primaryKey": false,
3968 + "notNull": false
3969 + },
3970 + "created_at": {
3971 + "name": "created_at",
3972 + "type": "timestamp with time zone",
3973 + "primaryKey": false,
3974 + "notNull": true,
3975 + "default": "now()"
3976 + },
3977 + "updated_at": {
3978 + "name": "updated_at",
3979 + "type": "timestamp with time zone",
3980 + "primaryKey": false,
3981 + "notNull": true,
3982 + "default": "now()"
3983 + }
3984 + },
3985 + "indexes": {
3986 + "auction_lots_url_uq": {
3987 + "name": "auction_lots_url_uq",
3988 + "columns": [
3989 + {
3990 + "expression": "url",
3991 + "isExpression": false,
3992 + "asc": true,
3993 + "nulls": "last"
3994 + }
3995 + ],
3996 + "isUnique": true,
3997 + "concurrently": false,
3998 + "method": "btree",
3999 + "with": {}
4000 + },
4001 + "auction_lots_auction_idx": {
4002 + "name": "auction_lots_auction_idx",
4003 + "columns": [
4004 + {
4005 + "expression": "auction_id",
4006 + "isExpression": false,
4007 + "asc": true,
4008 + "nulls": "last"
4009 + }
4010 + ],
4011 + "isUnique": false,
4012 + "concurrently": false,
4013 + "method": "btree",
4014 + "with": {}
4015 + },
4016 + "auction_lots_asset_idx": {
4017 + "name": "auction_lots_asset_idx",
4018 + "columns": [
4019 + {
4020 + "expression": "asset_id",
4021 + "isExpression": false,
4022 + "asc": true,
4023 + "nulls": "last"
4024 + }
4025 + ],
4026 + "isUnique": false,
4027 + "concurrently": false,
4028 + "method": "btree",
4029 + "with": {}
4030 + },
4031 + "auction_lots_ends_idx": {
4032 + "name": "auction_lots_ends_idx",
4033 + "columns": [
4034 + {
4035 + "expression": "ends_at",
4036 + "isExpression": false,
4037 + "asc": true,
4038 + "nulls": "last"
4039 + }
4040 + ],
4041 + "isUnique": false,
4042 + "concurrently": false,
4043 + "method": "btree",
4044 + "with": {}
4045 + }
4046 + },
4047 + "foreignKeys": {},
4048 + "compositePrimaryKeys": {},
4049 + "uniqueConstraints": {},
4050 + "policies": {},
4051 + "checkConstraints": {},
4052 + "isRLSEnabled": false
4053 + },
4054 + "public.auctions": {
4055 + "name": "auctions",
4056 + "schema": "",
4057 + "columns": {
4058 + "id": {
4059 + "name": "id",
4060 + "type": "text",
4061 + "primaryKey": true,
4062 + "notNull": true
4063 + },
4064 + "source_id": {
4065 + "name": "source_id",
4066 + "type": "text",
4067 + "primaryKey": false,
4068 + "notNull": true
4069 + },
4070 + "auction_house": {
4071 + "name": "auction_house",
4072 + "type": "text",
4073 + "primaryKey": false,
4074 + "notNull": true
4075 + },
4076 + "name": {
4077 + "name": "name",
4078 + "type": "text",
4079 + "primaryKey": false,
4080 + "notNull": true
4081 + },
4082 + "url": {
4083 + "name": "url",
4084 + "type": "text",
4085 + "primaryKey": false,
4086 + "notNull": true
4087 + },
4088 + "starts_at": {
4089 + "name": "starts_at",
4090 + "type": "timestamp with time zone",
4091 + "primaryKey": false,
4092 + "notNull": false
4093 + },
4094 + "ends_at": {
4095 + "name": "ends_at",
4096 + "type": "timestamp with time zone",
4097 + "primaryKey": false,
4098 + "notNull": false
4099 + },
4100 + "location": {
4101 + "name": "location",
4102 + "type": "text",
4103 + "primaryKey": false,
4104 + "notNull": false
4105 + },
4106 + "category_slugs": {
4107 + "name": "category_slugs",
4108 + "type": "text[]",
4109 + "primaryKey": false,
4110 + "notNull": true,
4111 + "default": "'{}'::text[]"
4112 + },
4113 + "lot_count": {
4114 + "name": "lot_count",
4115 + "type": "integer",
4116 + "primaryKey": false,
4117 + "notNull": false
4118 + },
4119 + "status": {
4120 + "name": "status",
4121 + "type": "text",
4122 + "primaryKey": false,
4123 + "notNull": true,
4124 + "default": "'upcoming'"
4125 + },
4126 + "currency": {
4127 + "name": "currency",
4128 + "type": "text",
4129 + "primaryKey": false,
4130 + "notNull": false
4131 + },
4132 + "created_at": {
4133 + "name": "created_at",
4134 + "type": "timestamp with time zone",
4135 + "primaryKey": false,
4136 + "notNull": true,
4137 + "default": "now()"
4138 + },
4139 + "updated_at": {
4140 + "name": "updated_at",
4141 + "type": "timestamp with time zone",
4142 + "primaryKey": false,
4143 + "notNull": true,
4144 + "default": "now()"
4145 + }
4146 + },
4147 + "indexes": {
4148 + "auctions_url_uq": {
4149 + "name": "auctions_url_uq",
4150 + "columns": [
4151 + {
4152 + "expression": "url",
4153 + "isExpression": false,
4154 + "asc": true,
4155 + "nulls": "last"
4156 + }
4157 + ],
4158 + "isUnique": true,
4159 + "concurrently": false,
4160 + "method": "btree",
4161 + "with": {}
4162 + },
4163 + "auctions_ends_idx": {
4164 + "name": "auctions_ends_idx",
4165 + "columns": [
4166 + {
4167 + "expression": "ends_at",
4168 + "isExpression": false,
4169 + "asc": true,
4170 + "nulls": "last"
4171 + }
4172 + ],
4173 + "isUnique": false,
4174 + "concurrently": false,
4175 + "method": "btree",
4176 + "with": {}
4177 + }
4178 + },
4179 + "foreignKeys": {},
4180 + "compositePrimaryKeys": {},
4181 + "uniqueConstraints": {},
4182 + "policies": {},
4183 + "checkConstraints": {},
4184 + "isRLSEnabled": false
4185 + },
4186 + "public.cross_listing_groups": {
4187 + "name": "cross_listing_groups",
4188 + "schema": "",
4189 + "columns": {
4190 + "id": {
4191 + "name": "id",
4192 + "type": "text",
4193 + "primaryKey": true,
4194 + "notNull": true
4195 + },
4196 + "asset_id": {
4197 + "name": "asset_id",
4198 + "type": "text",
4199 + "primaryKey": false,
4200 + "notNull": false
4201 + },
4202 + "signals": {
4203 + "name": "signals",
4204 + "type": "jsonb",
4205 + "primaryKey": false,
4206 + "notNull": true,
4207 + "default": "'{}'::jsonb"
4208 + },
4209 + "created_at": {
4210 + "name": "created_at",
4211 + "type": "timestamp with time zone",
4212 + "primaryKey": false,
4213 + "notNull": true,
4214 + "default": "now()"
4215 + }
4216 + },
4217 + "indexes": {},
4218 + "foreignKeys": {},
4219 + "compositePrimaryKeys": {},
4220 + "uniqueConstraints": {},
4221 + "policies": {},
4222 + "checkConstraints": {},
4223 + "isRLSEnabled": false
4224 + },
4225 + "public.fx_rates": {
4226 + "name": "fx_rates",
4227 + "schema": "",
4228 + "columns": {
4229 + "date": {
4230 + "name": "date",
4231 + "type": "date",
4232 + "primaryKey": false,
4233 + "notNull": true
4234 + },
4235 + "base": {
4236 + "name": "base",
4237 + "type": "text",
4238 + "primaryKey": false,
4239 + "notNull": true
4240 + },
4241 + "quote": {
4242 + "name": "quote",
4243 + "type": "text",
4244 + "primaryKey": false,
4245 + "notNull": true
4246 + },
4247 + "rate": {
4248 + "name": "rate",
4249 + "type": "real",
4250 + "primaryKey": false,
4251 + "notNull": true
4252 + },
4253 + "source": {
4254 + "name": "source",
4255 + "type": "text",
4256 + "primaryKey": false,
4257 + "notNull": true,
4258 + "default": "'ecb'"
4259 + }
4260 + },
4261 + "indexes": {
4262 + "fx_rates_uq": {
4263 + "name": "fx_rates_uq",
4264 + "columns": [
4265 + {
4266 + "expression": "date",
4267 + "isExpression": false,
4268 + "asc": true,
4269 + "nulls": "last"
4270 + },
4271 + {
4272 + "expression": "base",
4273 + "isExpression": false,
4274 + "asc": true,
4275 + "nulls": "last"
4276 + },
4277 + {
4278 + "expression": "quote",
4279 + "isExpression": false,
4280 + "asc": true,
4281 + "nulls": "last"
4282 + }
4283 + ],
4284 + "isUnique": true,
4285 + "concurrently": false,
4286 + "method": "btree",
4287 + "with": {}
4288 + }
4289 + },
4290 + "foreignKeys": {},
4291 + "compositePrimaryKeys": {},
4292 + "uniqueConstraints": {},
4293 + "policies": {},
4294 + "checkConstraints": {},
4295 + "isRLSEnabled": false
4296 + },
4297 + "public.listing_events": {
4298 + "name": "listing_events",
4299 + "schema": "",
4300 + "columns": {
4301 + "id": {
4302 + "name": "id",
4303 + "type": "text",
4304 + "primaryKey": true,
4305 + "notNull": true
4306 + },
4307 + "listing_id": {
4308 + "name": "listing_id",
4309 + "type": "text",
4310 + "primaryKey": false,
4311 + "notNull": true
4312 + },
4313 + "event_type": {
4314 + "name": "event_type",
4315 + "type": "text",
4316 + "primaryKey": false,
4317 + "notNull": true
4318 + },
4319 + "old_price": {
4320 + "name": "old_price",
4321 + "type": "numeric(18, 4)",
4322 + "primaryKey": false,
4323 + "notNull": false
4324 + },
4325 + "new_price": {
4326 + "name": "new_price",
4327 + "type": "numeric(18, 4)",
4328 + "primaryKey": false,
4329 + "notNull": false
4330 + },
4331 + "currency": {
4332 + "name": "currency",
4333 + "type": "text",
4334 + "primaryKey": false,
4335 + "notNull": false
4336 + },
4337 + "occurred_at": {
4338 + "name": "occurred_at",
4339 + "type": "timestamp with time zone",
4340 + "primaryKey": false,
4341 + "notNull": true
4342 + }
4343 + },
4344 + "indexes": {
4345 + "listing_events_listing_idx": {
4346 + "name": "listing_events_listing_idx",
4347 + "columns": [
4348 + {
4349 + "expression": "listing_id",
4350 + "isExpression": false,
4351 + "asc": true,
4352 + "nulls": "last"
4353 + },
4354 + {
4355 + "expression": "occurred_at",
4356 + "isExpression": false,
4357 + "asc": true,
4358 + "nulls": "last"
4359 + }
4360 + ],
4361 + "isUnique": false,
4362 + "concurrently": false,
4363 + "method": "btree",
4364 + "with": {}
4365 + }
4366 + },
4367 + "foreignKeys": {},
4368 + "compositePrimaryKeys": {},
4369 + "uniqueConstraints": {},
4370 + "policies": {},
4371 + "checkConstraints": {},
4372 + "isRLSEnabled": false
4373 + },
4374 + "public.listings": {
4375 + "name": "listings",
4376 + "schema": "",
4377 + "columns": {
4378 + "id": {
4379 + "name": "id",
4380 + "type": "text",
4381 + "primaryKey": true,
4382 + "notNull": true
4383 + },
4384 + "asset_id": {
4385 + "name": "asset_id",
4386 + "type": "text",
4387 + "primaryKey": false,
4388 + "notNull": true
4389 + },
4390 + "variant_id": {
4391 + "name": "variant_id",
4392 + "type": "text",
4393 + "primaryKey": false,
4394 + "notNull": false
4395 + },
4396 + "source_id": {
4397 + "name": "source_id",
4398 + "type": "text",
4399 + "primaryKey": false,
4400 + "notNull": true
4401 + },
4402 + "connector_id": {
4403 + "name": "connector_id",
4404 + "type": "text",
4405 + "primaryKey": false,
4406 + "notNull": true
4407 + },
4408 + "raw_record_id": {
4409 + "name": "raw_record_id",
4410 + "type": "text",
4411 + "primaryKey": false,
4412 + "notNull": false
4413 + },
4414 + "source_url": {
4415 + "name": "source_url",
4416 + "type": "text",
4417 + "primaryKey": false,
4418 + "notNull": true
4419 + },
4420 + "external_id": {
4421 + "name": "external_id",
4422 + "type": "text",
4423 + "primaryKey": false,
4424 + "notNull": true
4425 + },
4426 + "listing_type": {
4427 + "name": "listing_type",
4428 + "type": "text",
4429 + "primaryKey": false,
4430 + "notNull": true,
4431 + "default": "'unknown'"
4432 + },
4433 + "price": {
4434 + "name": "price",
4435 + "type": "numeric(18, 4)",
4436 + "primaryKey": false,
4437 + "notNull": false
4438 + },
4439 + "currency": {
4440 + "name": "currency",
4441 + "type": "text",
4442 + "primaryKey": false,
4443 + "notNull": false
4444 + },
4445 + "price_usd": {
4446 + "name": "price_usd",
4447 + "type": "numeric(18, 4)",
4448 + "primaryKey": false,
4449 + "notNull": false
4450 + },
4451 + "seller": {
4452 + "name": "seller",
4453 + "type": "text",
4454 + "primaryKey": false,
4455 + "notNull": false
4456 + },
4457 + "seller_reputation": {
4458 + "name": "seller_reputation",
4459 + "type": "text",
4460 + "primaryKey": false,
4461 + "notNull": false
4462 + },
4463 + "location": {
4464 + "name": "location",
4465 + "type": "text",
4466 + "primaryKey": false,
4467 + "notNull": false
4468 + },
4469 + "shipping_cost": {
4470 + "name": "shipping_cost",
4471 + "type": "numeric(18, 4)",
4472 + "primaryKey": false,
4473 + "notNull": false
4474 + },
4475 + "quantity": {
4476 + "name": "quantity",
4477 + "type": "integer",
4478 + "primaryKey": false,
4479 + "notNull": false
4480 + },
4481 + "condition": {
4482 + "name": "condition",
4483 + "type": "text",
4484 + "primaryKey": false,
4485 + "notNull": false
4486 + },
4487 + "grader": {
4488 + "name": "grader",
4489 + "type": "text",
4490 + "primaryKey": false,
4491 + "notNull": false
4492 + },
4493 + "grade": {
4494 + "name": "grade",
4495 + "type": "text",
4496 + "primaryKey": false,
4497 + "notNull": false
4498 + },
4499 + "certification_number": {
4500 + "name": "certification_number",
4501 + "type": "text",
4502 + "primaryKey": false,
4503 + "notNull": false
4504 + },
4505 + "image_urls": {
4506 + "name": "image_urls",
4507 + "type": "jsonb",
4508 + "primaryKey": false,
4509 + "notNull": true,
4510 + "default": "'[]'::jsonb"
4511 + },
4512 + "raw_title": {
4513 + "name": "raw_title",
4514 + "type": "text",
4515 + "primaryKey": false,
4516 + "notNull": true
4517 + },
4518 + "description": {
4519 + "name": "description",
4520 + "type": "text",
4521 + "primaryKey": false,
4522 + "notNull": false
4523 + },
4524 + "listed_at": {
4525 + "name": "listed_at",
4526 + "type": "timestamp with time zone",
4527 + "primaryKey": false,
4528 + "notNull": false
4529 + },
4530 + "ends_at": {
4531 + "name": "ends_at",
4532 + "type": "timestamp with time zone",
4533 + "primaryKey": false,
4534 + "notNull": false
4535 + },
4536 + "availability": {
4537 + "name": "availability",
4538 + "type": "text",
4539 + "primaryKey": false,
4540 + "notNull": true,
4541 + "default": "'available'"
4542 + },
4543 + "bid_count": {
4544 + "name": "bid_count",
4545 + "type": "integer",
4546 + "primaryKey": false,
4547 + "notNull": false
4548 + },
4549 + "first_seen_at": {
4550 + "name": "first_seen_at",
4551 + "type": "timestamp with time zone",
4552 + "primaryKey": false,
4553 + "notNull": true
4554 + },
4555 + "last_seen_at": {
4556 + "name": "last_seen_at",
4557 + "type": "timestamp with time zone",
4558 + "primaryKey": false,
4559 + "notNull": true
4560 + },
4561 + "price_changed_at": {
4562 + "name": "price_changed_at",
4563 + "type": "timestamp with time zone",
4564 + "primaryKey": false,
4565 + "notNull": false
4566 + },
4567 + "cross_listing_group_id": {
4568 + "name": "cross_listing_group_id",
4569 + "type": "text",
4570 + "primaryKey": false,
4571 + "notNull": false
4572 + },
4573 + "confidence": {
4574 + "name": "confidence",
4575 + "type": "numeric(8, 6)",
4576 + "primaryKey": false,
4577 + "notNull": true,
4578 + "default": 0.8
4579 + },
4580 + "data_quality": {
4581 + "name": "data_quality",
4582 + "type": "real",
4583 + "primaryKey": false,
4584 + "notNull": true,
4585 + "default": 0
4586 + },
4587 + "flags": {
4588 + "name": "flags",
4589 + "type": "text[]",
4590 + "primaryKey": false,
4591 + "notNull": true,
4592 + "default": "'{}'::text[]"
4593 + },
4594 + "discount_to_riv": {
4595 + "name": "discount_to_riv",
4596 + "type": "numeric(8, 6)",
4597 + "primaryKey": false,
4598 + "notNull": false
4599 + },
4600 + "created_at": {
4601 + "name": "created_at",
4602 + "type": "timestamp with time zone",
4603 + "primaryKey": false,
4604 + "notNull": true,
4605 + "default": "now()"
4606 + },
4607 + "updated_at": {
4608 + "name": "updated_at",
4609 + "type": "timestamp with time zone",
4610 + "primaryKey": false,
4611 + "notNull": true,
4612 + "default": "now()"
4613 + }
4614 + },
4615 + "indexes": {
4616 + "listings_source_external_uq": {
4617 + "name": "listings_source_external_uq",
4618 + "columns": [
4619 + {
4620 + "expression": "source_id",
4621 + "isExpression": false,
4622 + "asc": true,
4623 + "nulls": "last"
4624 + },
4625 + {
4626 + "expression": "external_id",
4627 + "isExpression": false,
4628 + "asc": true,
4629 + "nulls": "last"
4630 + }
4631 + ],
4632 + "isUnique": true,
4633 + "concurrently": false,
4634 + "method": "btree",
4635 + "with": {}
4636 + },
4637 + "listings_asset_avail_idx": {
4638 + "name": "listings_asset_avail_idx",
4639 + "columns": [
4640 + {
4641 + "expression": "asset_id",
4642 + "isExpression": false,
4643 + "asc": true,
4644 + "nulls": "last"
4645 + },
4646 + {
4647 + "expression": "availability",
4648 + "isExpression": false,
4649 + "asc": true,
4650 + "nulls": "last"
4651 + }
4652 + ],
4653 + "isUnique": false,
4654 + "concurrently": false,
4655 + "method": "btree",
4656 + "with": {}
4657 + },
4658 + "listings_avail_price_idx": {
4659 + "name": "listings_avail_price_idx",
4660 + "columns": [
4661 + {
4662 + "expression": "availability",
4663 + "isExpression": false,
4664 + "asc": true,
4665 + "nulls": "last"
4666 + },
4667 + {
4668 + "expression": "price_usd",
4669 + "isExpression": false,
4670 + "asc": true,
4671 + "nulls": "last"
4672 + }
4673 + ],
4674 + "isUnique": false,
4675 + "concurrently": false,
4676 + "method": "btree",
4677 + "with": {}
4678 + },
4679 + "listings_ends_idx": {
4680 + "name": "listings_ends_idx",
4681 + "columns": [
4682 + {
4683 + "expression": "ends_at",
4684 + "isExpression": false,
4685 + "asc": true,
4686 + "nulls": "last"
4687 + }
4688 + ],
4689 + "isUnique": false,
4690 + "concurrently": false,
4691 + "method": "btree",
4692 + "with": {}
4693 + },
4694 + "listings_last_seen_idx": {
4695 + "name": "listings_last_seen_idx",
4696 + "columns": [
4697 + {
4698 + "expression": "last_seen_at",
4699 + "isExpression": false,
4700 + "asc": true,
4701 + "nulls": "last"
4702 + }
4703 + ],
4704 + "isUnique": false,
4705 + "concurrently": false,
4706 + "method": "btree",
4707 + "with": {}
4708 + },
4709 + "listings_variant_avail_idx": {
4710 + "name": "listings_variant_avail_idx",
4711 + "columns": [
4712 + {
4713 + "expression": "variant_id",
4714 + "isExpression": false,
4715 + "asc": true,
4716 + "nulls": "last"
4717 + },
4718 + {
4719 + "expression": "availability",
4720 + "isExpression": false,
4721 + "asc": true,
4722 + "nulls": "last"
4723 + }
4724 + ],
4725 + "isUnique": false,
4726 + "concurrently": false,
4727 + "method": "btree",
4728 + "with": {}
4729 + },
4730 + "listings_discount_idx": {
4731 + "name": "listings_discount_idx",
4732 + "columns": [
4733 + {
4734 + "expression": "discount_to_riv",
4735 + "isExpression": false,
4736 + "asc": true,
4737 + "nulls": "last"
4738 + }
4739 + ],
4740 + "isUnique": false,
4741 + "where": "discount_to_riv is not null and availability = 'available'",
4742 + "concurrently": false,
4743 + "method": "btree",
4744 + "with": {}
4745 + }
4746 + },
4747 + "foreignKeys": {},
4748 + "compositePrimaryKeys": {},
4749 + "uniqueConstraints": {},
4750 + "policies": {},
4751 + "checkConstraints": {},
4752 + "isRLSEnabled": false
4753 + },
4754 + "public.news": {
4755 + "name": "news",
4756 + "schema": "",
4757 + "columns": {
4758 + "id": {
4759 + "name": "id",
4760 + "type": "text",
4761 + "primaryKey": true,
4762 + "notNull": true
4763 + },
4764 + "source_id": {
4765 + "name": "source_id",
4766 + "type": "text",
4767 + "primaryKey": false,
4768 + "notNull": true
4769 + },
4770 + "url": {
4771 + "name": "url",
4772 + "type": "text",
4773 + "primaryKey": false,
4774 + "notNull": true
4775 + },
4776 + "title": {
4777 + "name": "title",
4778 + "type": "text",
4779 + "primaryKey": false,
4780 + "notNull": true
4781 + },
4782 + "summary": {
4783 + "name": "summary",
4784 + "type": "text",
4785 + "primaryKey": false,
4786 + "notNull": false
4787 + },
4788 + "ai_summary": {
4789 + "name": "ai_summary",
4790 + "type": "text",
4791 + "primaryKey": false,
4792 + "notNull": false
4793 + },
4794 + "published_at": {
4795 + "name": "published_at",
4796 + "type": "timestamp with time zone",
4797 + "primaryKey": false,
4798 + "notNull": false
4799 + },
4800 + "category_slugs": {
4801 + "name": "category_slugs",
4802 + "type": "text[]",
4803 + "primaryKey": false,
4804 + "notNull": true,
4805 + "default": "'{}'::text[]"
4806 + },
4807 + "news_type": {
4808 + "name": "news_type",
4809 + "type": "text",
4810 + "primaryKey": false,
4811 + "notNull": false
4812 + },
4813 + "image_url": {
4814 + "name": "image_url",
4815 + "type": "text",
4816 + "primaryKey": false,
4817 + "notNull": false
4818 + },
4819 + "fetched_at": {
4820 + "name": "fetched_at",
4821 + "type": "timestamp with time zone",
4822 + "primaryKey": false,
4823 + "notNull": true
4824 + }
4825 + },
4826 + "indexes": {
4827 + "news_url_uq": {
4828 + "name": "news_url_uq",
4829 + "columns": [
4830 + {
4831 + "expression": "url",
4832 + "isExpression": false,
4833 + "asc": true,
4834 + "nulls": "last"
4835 + }
4836 + ],
4837 + "isUnique": true,
4838 + "concurrently": false,
4839 + "method": "btree",
4840 + "with": {}
4841 + },
4842 + "news_published_idx": {
4843 + "name": "news_published_idx",
4844 + "columns": [
4845 + {
4846 + "expression": "published_at",
4847 + "isExpression": false,
4848 + "asc": true,
4849 + "nulls": "last"
4850 + }
4851 + ],
4852 + "isUnique": false,
4853 + "concurrently": false,
4854 + "method": "btree",
4855 + "with": {}
4856 + }
4857 + },
4858 + "foreignKeys": {},
4859 + "compositePrimaryKeys": {},
4860 + "uniqueConstraints": {},
4861 + "policies": {},
4862 + "checkConstraints": {},
4863 + "isRLSEnabled": false
4864 + },
4865 + "public.price_observations": {
4866 + "name": "price_observations",
4867 + "schema": "",
4868 + "columns": {
4869 + "id": {
4870 + "name": "id",
4871 + "type": "text",
4872 + "primaryKey": true,
4873 + "notNull": true
4874 + },
4875 + "asset_id": {
4876 + "name": "asset_id",
4877 + "type": "text",
4878 + "primaryKey": false,
4879 + "notNull": true
4880 + },
4881 + "variant_id": {
4882 + "name": "variant_id",
4883 + "type": "text",
4884 + "primaryKey": false,
4885 + "notNull": false
4886 + },
4887 + "source_id": {
4888 + "name": "source_id",
4889 + "type": "text",
4890 + "primaryKey": false,
4891 + "notNull": true
4892 + },
4893 + "connector_id": {
4894 + "name": "connector_id",
4895 + "type": "text",
4896 + "primaryKey": false,
4897 + "notNull": true
4898 + },
4899 + "raw_record_id": {
4900 + "name": "raw_record_id",
4901 + "type": "text",
4902 + "primaryKey": false,
4903 + "notNull": false
4904 + },
4905 + "source_url": {
4906 + "name": "source_url",
4907 + "type": "text",
4908 + "primaryKey": false,
4909 + "notNull": true
4910 + },
4911 + "price_kind": {
4912 + "name": "price_kind",
4913 + "type": "text",
4914 + "primaryKey": false,
4915 + "notNull": true
4916 + },
4917 + "price": {
4918 + "name": "price",
4919 + "type": "numeric(18, 4)",
4920 + "primaryKey": false,
4921 + "notNull": true
4922 + },
4923 + "currency": {
4924 + "name": "currency",
4925 + "type": "text",
4926 + "primaryKey": false,
4927 + "notNull": true
4928 + },
4929 + "price_usd": {
4930 + "name": "price_usd",
4931 + "type": "numeric(18, 4)",
4932 + "primaryKey": false,
4933 + "notNull": true
4934 + },
4935 + "observation_date": {
4936 + "name": "observation_date",
4937 + "type": "date",
4938 + "primaryKey": false,
4939 + "notNull": true
4940 + },
4941 + "sample_size": {
4942 + "name": "sample_size",
4943 + "type": "integer",
4944 + "primaryKey": false,
4945 + "notNull": false
4946 + },
4947 + "dedupe_key": {
4948 + "name": "dedupe_key",
4949 + "type": "text",
4950 + "primaryKey": false,
4951 + "notNull": true
4952 + },
4953 + "created_at": {
4954 + "name": "created_at",
4955 + "type": "timestamp with time zone",
4956 + "primaryKey": false,
4957 + "notNull": true,
4958 + "default": "now()"
4959 + }
4960 + },
4961 + "indexes": {
4962 + "price_observations_dedupe_uq": {
4963 + "name": "price_observations_dedupe_uq",
4964 + "columns": [
4965 + {
4966 + "expression": "dedupe_key",
4967 + "isExpression": false,
4968 + "asc": true,
4969 + "nulls": "last"
4970 + }
4971 + ],
4972 + "isUnique": true,
4973 + "concurrently": false,
4974 + "method": "btree",
4975 + "with": {}
4976 + },
4977 + "price_observations_asset_date_idx": {
4978 + "name": "price_observations_asset_date_idx",
4979 + "columns": [
4980 + {
4981 + "expression": "asset_id",
4982 + "isExpression": false,
4983 + "asc": true,
4984 + "nulls": "last"
4985 + },
4986 + {
4987 + "expression": "observation_date",
4988 + "isExpression": false,
4989 + "asc": true,
4990 + "nulls": "last"
4991 + }
4992 + ],
4993 + "isUnique": false,
4994 + "concurrently": false,
4995 + "method": "btree",
4996 + "with": {}
4997 + }
4998 + },
4999 + "foreignKeys": {},
5000 + "compositePrimaryKeys": {},
5001 + "uniqueConstraints": {},
5002 + "policies": {},
5003 + "checkConstraints": {},
5004 + "isRLSEnabled": false
5005 + },
5006 + "public.sales": {
5007 + "name": "sales",
5008 + "schema": "",
5009 + "columns": {
5010 + "id": {
5011 + "name": "id",
5012 + "type": "text",
5013 + "primaryKey": true,
5014 + "notNull": true
5015 + },
5016 + "asset_id": {
5017 + "name": "asset_id",
5018 + "type": "text",
5019 + "primaryKey": false,
5020 + "notNull": true
5021 + },
5022 + "variant_id": {
5023 + "name": "variant_id",
5024 + "type": "text",
5025 + "primaryKey": false,
5026 + "notNull": false
5027 + },
5028 + "source_id": {
5029 + "name": "source_id",
5030 + "type": "text",
5031 + "primaryKey": false,
5032 + "notNull": true
5033 + },
5034 + "connector_id": {
5035 + "name": "connector_id",
5036 + "type": "text",
5037 + "primaryKey": false,
5038 + "notNull": true
5039 + },
5040 + "raw_record_id": {
5041 + "name": "raw_record_id",
5042 + "type": "text",
5043 + "primaryKey": false,
5044 + "notNull": false
5045 + },
5046 + "normalized_record_id": {
5047 + "name": "normalized_record_id",
5048 + "type": "text",
5049 + "primaryKey": false,
5050 + "notNull": false
5051 + },
5052 + "source_url": {
5053 + "name": "source_url",
5054 + "type": "text",
5055 + "primaryKey": false,
5056 + "notNull": true
5057 + },
5058 + "external_id": {
5059 + "name": "external_id",
5060 + "type": "text",
5061 + "primaryKey": false,
5062 + "notNull": false
5063 + },
5064 + "sale_type": {
5065 + "name": "sale_type",
5066 + "type": "text",
5067 + "primaryKey": false,
5068 + "notNull": true,
5069 + "default": "'unknown'"
5070 + },
5071 + "sale_date": {
5072 + "name": "sale_date",
5073 + "type": "timestamp with time zone",
5074 + "primaryKey": false,
5075 + "notNull": true
5076 + },
5077 + "price": {
5078 + "name": "price",
5079 + "type": "numeric(18, 4)",
5080 + "primaryKey": false,
5081 + "notNull": true
5082 + },
5083 + "currency": {
5084 + "name": "currency",
5085 + "type": "text",
5086 + "primaryKey": false,
5087 + "notNull": true
5088 + },
5089 + "price_usd": {
5090 + "name": "price_usd",
5091 + "type": "numeric(18, 4)",
5092 + "primaryKey": false,
5093 + "notNull": true
5094 + },
5095 + "fx_rate": {
5096 + "name": "fx_rate",
5097 + "type": "real",
5098 + "primaryKey": false,
5099 + "notNull": false
5100 + },
5101 + "fx_date": {
5102 + "name": "fx_date",
5103 + "type": "date",
5104 + "primaryKey": false,
5105 + "notNull": false
5106 + },
5107 + "buyer_premium_included": {
5108 + "name": "buyer_premium_included",
5109 + "type": "boolean",
5110 + "primaryKey": false,
5111 + "notNull": false
5112 + },
5113 + "quantity": {
5114 + "name": "quantity",
5115 + "type": "integer",
5116 + "primaryKey": false,
5117 + "notNull": true,
5118 + "default": 1
5119 + },
5120 + "is_bundle": {
5121 + "name": "is_bundle",
5122 + "type": "boolean",
5123 + "primaryKey": false,
5124 + "notNull": true,
5125 + "default": false
5126 + },
5127 + "condition": {
5128 + "name": "condition",
5129 + "type": "text",
5130 + "primaryKey": false,
5131 + "notNull": false
5132 + },
5133 + "grader": {
5134 + "name": "grader",
5135 + "type": "text",
5136 + "primaryKey": false,
5137 + "notNull": false
5138 + },
5139 + "grade": {
5140 + "name": "grade",
5141 + "type": "text",
5142 + "primaryKey": false,
5143 + "notNull": false
5144 + },
5145 + "certification_number": {
5146 + "name": "certification_number",
5147 + "type": "text",
5148 + "primaryKey": false,
5149 + "notNull": false
5150 + },
5151 + "location": {
5152 + "name": "location",
5153 + "type": "text",
5154 + "primaryKey": false,
5155 + "notNull": false
5156 + },
5157 + "auction_house": {
5158 + "name": "auction_house",
5159 + "type": "text",
5160 + "primaryKey": false,
5161 + "notNull": false
5162 + },
5163 + "lot_number": {
5164 + "name": "lot_number",
5165 + "type": "text",
5166 + "primaryKey": false,
5167 + "notNull": false
5168 + },
5169 + "image_urls": {
5170 + "name": "image_urls",
5171 + "type": "jsonb",
5172 + "primaryKey": false,
5173 + "notNull": true,
5174 + "default": "'[]'::jsonb"
5175 + },
5176 + "raw_title": {
5177 + "name": "raw_title",
5178 + "type": "text",
5179 + "primaryKey": false,
5180 + "notNull": true
5181 + },
5182 + "confidence": {
5183 + "name": "confidence",
5184 + "type": "numeric(8, 6)",
5185 + "primaryKey": false,
5186 + "notNull": true,
5187 + "default": 0.8
5188 + },
5189 + "data_quality": {
5190 + "name": "data_quality",
5191 + "type": "real",
5192 + "primaryKey": false,
5193 + "notNull": true,
5194 + "default": 0
5195 + },
5196 + "status": {
5197 + "name": "status",
5198 + "type": "text",
5199 + "primaryKey": false,
5200 + "notNull": true,
5201 + "default": "'valid'"
5202 + },
5203 + "flags": {
5204 + "name": "flags",
5205 + "type": "text[]",
5206 + "primaryKey": false,
5207 + "notNull": true,
5208 + "default": "'{}'::text[]"
5209 + },
5210 + "dedupe_key": {
5211 + "name": "dedupe_key",
5212 + "type": "text",
5213 + "primaryKey": false,
5214 + "notNull": true
5215 + },
5216 + "created_at": {
5217 + "name": "created_at",
5218 + "type": "timestamp with time zone",
5219 + "primaryKey": false,
5220 + "notNull": true,
5221 + "default": "now()"
5222 + }
5223 + },
5224 + "indexes": {
5225 + "sales_dedupe_uq": {
5226 + "name": "sales_dedupe_uq",
5227 + "columns": [
5228 + {
5229 + "expression": "dedupe_key",
5230 + "isExpression": false,
5231 + "asc": true,
5232 + "nulls": "last"
5233 + }
5234 + ],
5235 + "isUnique": true,
5236 + "concurrently": false,
5237 + "method": "btree",
5238 + "with": {}
5239 + },
5240 + "sales_asset_date_idx": {
5241 + "name": "sales_asset_date_idx",
5242 + "columns": [
5243 + {
5244 + "expression": "asset_id",
5245 + "isExpression": false,
5246 + "asc": true,
5247 + "nulls": "last"
5248 + },
5249 + {
5250 + "expression": "sale_date",
5251 + "isExpression": false,
5252 + "asc": true,
5253 + "nulls": "last"
5254 + }
5255 + ],
5256 + "isUnique": false,
5257 + "concurrently": false,
5258 + "method": "btree",
5259 + "with": {}
5260 + },
5261 + "sales_variant_date_idx": {
5262 + "name": "sales_variant_date_idx",
5263 + "columns": [
5264 + {
5265 + "expression": "variant_id",
5266 + "isExpression": false,
5267 + "asc": true,
5268 + "nulls": "last"
5269 + },
5270 + {
5271 + "expression": "sale_date",
5272 + "isExpression": false,
5273 + "asc": true,
5274 + "nulls": "last"
5275 + }
5276 + ],
5277 + "isUnique": false,
5278 + "concurrently": false,
5279 + "method": "btree",
5280 + "with": {}
5281 + },
5282 + "sales_source_idx": {
5283 + "name": "sales_source_idx",
5284 + "columns": [
5285 + {
5286 + "expression": "source_id",
5287 + "isExpression": false,
5288 + "asc": true,
5289 + "nulls": "last"
5290 + },
5291 + {
5292 + "expression": "sale_date",
5293 + "isExpression": false,
5294 + "asc": true,
5295 + "nulls": "last"
5296 + }
5297 + ],
5298 + "isUnique": false,
5299 + "concurrently": false,
5300 + "method": "btree",
5301 + "with": {}
5302 + },
5303 + "sales_date_idx": {
5304 + "name": "sales_date_idx",
5305 + "columns": [
5306 + {
5307 + "expression": "sale_date",
5308 + "isExpression": false,
5309 + "asc": true,
5310 + "nulls": "last"
5311 + }
5312 + ],
5313 + "isUnique": false,
5314 + "concurrently": false,
5315 + "method": "btree",
5316 + "with": {}
5317 + },
5318 + "sales_price_idx": {
5319 + "name": "sales_price_idx",
5320 + "columns": [
5321 + {
5322 + "expression": "price_usd",
5323 + "isExpression": false,
5324 + "asc": true,
5325 + "nulls": "last"
5326 + }
5327 + ],
5328 + "isUnique": false,
5329 + "concurrently": false,
5330 + "method": "btree",
5331 + "with": {}
5332 + },
5333 + "sales_valid_date_idx": {
5334 + "name": "sales_valid_date_idx",
5335 + "columns": [
5336 + {
5337 + "expression": "sale_date",
5338 + "isExpression": false,
5339 + "asc": true,
5340 + "nulls": "last"
5341 + }
5342 + ],
5343 + "isUnique": false,
5344 + "where": "status = 'valid'",
5345 + "concurrently": false,
5346 + "method": "btree",
5347 + "with": {}
5348 + }
5349 + },
5350 + "foreignKeys": {},
5351 + "compositePrimaryKeys": {},
5352 + "uniqueConstraints": {},
5353 + "policies": {},
5354 + "checkConstraints": {},
5355 + "isRLSEnabled": false
5356 + },
5357 + "public.benchmarks": {
5358 + "name": "benchmarks",
5359 + "schema": "",
5360 + "columns": {
5361 + "ticker": {
5362 + "name": "ticker",
5363 + "type": "text",
5364 + "primaryKey": false,
5365 + "notNull": true
5366 + },
5367 + "date": {
5368 + "name": "date",
5369 + "type": "date",
5370 + "primaryKey": false,
5371 + "notNull": true
5372 + },
5373 + "value": {
5374 + "name": "value",
5375 + "type": "real",
5376 + "primaryKey": false,
5377 + "notNull": true
5378 + },
5379 + "source": {
5380 + "name": "source",
5381 + "type": "text",
5382 + "primaryKey": false,
5383 + "notNull": true
5384 + }
5385 + },
5386 + "indexes": {},
5387 + "foreignKeys": {},
5388 + "compositePrimaryKeys": {
5389 + "benchmarks_ticker_date_pk": {
5390 + "name": "benchmarks_ticker_date_pk",
5391 + "columns": [
5392 + "ticker",
5393 + "date"
5394 + ]
5395 + }
5396 + },
5397 + "uniqueConstraints": {},
5398 + "policies": {},
5399 + "checkConstraints": {},
5400 + "isRLSEnabled": false
5401 + },
5402 + "public.category_snapshots": {
5403 + "name": "category_snapshots",
5404 + "schema": "",
5405 + "columns": {
5406 + "category_slug": {
5407 + "name": "category_slug",
5408 + "type": "text",
5409 + "primaryKey": false,
5410 + "notNull": true
5411 + },
5412 + "date": {
5413 + "name": "date",
5414 + "type": "date",
5415 + "primaryKey": false,
5416 + "notNull": true
5417 + },
5418 + "index_value": {
5419 + "name": "index_value",
5420 + "type": "real",
5421 + "primaryKey": false,
5422 + "notNull": false
5423 + },
5424 + "tracked_assets": {
5425 + "name": "tracked_assets",
5426 + "type": "integer",
5427 + "primaryKey": false,
5428 + "notNull": true,
5429 + "default": 0
5430 + },
5431 + "assets_with_valuation": {
5432 + "name": "assets_with_valuation",
5433 + "type": "integer",
5434 + "primaryKey": false,
5435 + "notNull": true,
5436 + "default": 0
5437 + },
5438 + "sales": {
5439 + "name": "sales",
5440 + "type": "integer",
5441 + "primaryKey": false,
5442 + "notNull": true,
5443 + "default": 0
5444 + },
5445 + "volume_usd": {
5446 + "name": "volume_usd",
5447 + "type": "numeric(18, 4)",
5448 + "primaryKey": false,
5449 + "notNull": false
5450 + },
5451 + "median_sale_usd": {
5452 + "name": "median_sale_usd",
5453 + "type": "numeric(18, 4)",
5454 + "primaryKey": false,
5455 + "notNull": false
5456 + },
5457 + "active_listings": {
5458 + "name": "active_listings",
5459 + "type": "integer",
5460 + "primaryKey": false,
5461 + "notNull": true,
5462 + "default": 0
5463 + },
5464 + "market_cap_est_usd": {
5465 + "name": "market_cap_est_usd",
5466 + "type": "numeric(18, 4)",
5467 + "primaryKey": false,
5468 + "notNull": false
5469 + },
5470 + "liquidity_score": {
5471 + "name": "liquidity_score",
5472 + "type": "real",
5473 + "primaryKey": false,
5474 + "notNull": false
5475 + },
5476 + "change_1d": {
5477 + "name": "change_1d",
5478 + "type": "numeric(8, 6)",
5479 + "primaryKey": false,
5480 + "notNull": false
5481 + },
5482 + "change_7d": {
5483 + "name": "change_7d",
5484 + "type": "numeric(8, 6)",
5485 + "primaryKey": false,
5486 + "notNull": false
5487 + },
5488 + "change_30d": {
5489 + "name": "change_30d",
5490 + "type": "numeric(8, 6)",
5491 + "primaryKey": false,
5492 + "notNull": false
5493 + },
5494 + "change_1y": {
5495 + "name": "change_1y",
5496 + "type": "numeric(8, 6)",
5497 + "primaryKey": false,
5498 + "notNull": false
5499 + }
5500 + },
5501 + "indexes": {},
5502 + "foreignKeys": {},
5503 + "compositePrimaryKeys": {
5504 + "category_snapshots_category_slug_date_pk": {
5505 + "name": "category_snapshots_category_slug_date_pk",
5506 + "columns": [
5507 + "category_slug",
5508 + "date"
5509 + ]
5510 + }
5511 + },
5512 + "uniqueConstraints": {},
5513 + "policies": {},
5514 + "checkConstraints": {},
5515 + "isRLSEnabled": false
5516 + },
5517 + "public.correlations": {
5518 + "name": "correlations",
5519 + "schema": "",
5520 + "columns": {
5521 + "a": {
5522 + "name": "a",
5523 + "type": "text",
5524 + "primaryKey": false,
5525 + "notNull": true
5526 + },
5527 + "b": {
5528 + "name": "b",
5529 + "type": "text",
5530 + "primaryKey": false,
5531 + "notNull": true
5532 + },
5533 + "window_days": {
5534 + "name": "window_days",
5535 + "type": "integer",
5536 + "primaryKey": false,
5537 + "notNull": true
5538 + },
5539 + "coefficient": {
5540 + "name": "coefficient",
5541 + "type": "real",
5542 + "primaryKey": false,
5543 + "notNull": true
5544 + },
5545 + "observations": {
5546 + "name": "observations",
5547 + "type": "integer",
5548 + "primaryKey": false,
5549 + "notNull": true
5550 + },
5551 + "computed_at": {
5552 + "name": "computed_at",
5553 + "type": "timestamp with time zone",
5554 + "primaryKey": false,
5555 + "notNull": true
5556 + }
5557 + },
5558 + "indexes": {},
5559 + "foreignKeys": {},
5560 + "compositePrimaryKeys": {
5561 + "correlations_a_b_window_days_pk": {
5562 + "name": "correlations_a_b_window_days_pk",
5563 + "columns": [
5564 + "a",
5565 + "b",
5566 + "window_days"
5567 + ]
5568 + }
5569 + },
5570 + "uniqueConstraints": {},
5571 + "policies": {},
5572 + "checkConstraints": {},
5573 + "isRLSEnabled": false
5574 + },
5575 + "public.index_constituents": {
5576 + "name": "index_constituents",
5577 + "schema": "",
5578 + "columns": {
5579 + "index_id": {
5580 + "name": "index_id",
5581 + "type": "text",
5582 + "primaryKey": false,
5583 + "notNull": true
5584 + },
5585 + "asset_id": {
5586 + "name": "asset_id",
5587 + "type": "text",
5588 + "primaryKey": false,
5589 + "notNull": true
5590 + },
5591 + "variant_id": {
5592 + "name": "variant_id",
5593 + "type": "text",
5594 + "primaryKey": false,
5595 + "notNull": true,
5596 + "default": "''"
5597 + },
5598 + "weight": {
5599 + "name": "weight",
5600 + "type": "real",
5601 + "primaryKey": false,
5602 + "notNull": true,
5603 + "default": 1
5604 + },
5605 + "added_at": {
5606 + "name": "added_at",
5607 + "type": "date",
5608 + "primaryKey": false,
5609 + "notNull": true
5610 + },
5611 + "removed_at": {
5612 + "name": "removed_at",
5613 + "type": "date",
5614 + "primaryKey": false,
5615 + "notNull": false
5616 + },
5617 + "reason": {
5618 + "name": "reason",
5619 + "type": "text",
5620 + "primaryKey": false,
5621 + "notNull": false
5622 + }
5623 + },
5624 + "indexes": {
5625 + "index_constituents_asset_idx": {
5626 + "name": "index_constituents_asset_idx",
5627 + "columns": [
5628 + {
5629 + "expression": "asset_id",
5630 + "isExpression": false,
5631 + "asc": true,
5632 + "nulls": "last"
5633 + }
5634 + ],
5635 + "isUnique": false,
5636 + "concurrently": false,
5637 + "method": "btree",
5638 + "with": {}
5639 + }
5640 + },
5641 + "foreignKeys": {},
5642 + "compositePrimaryKeys": {
5643 + "index_constituents_index_id_asset_id_variant_id_added_at_pk": {
5644 + "name": "index_constituents_index_id_asset_id_variant_id_added_at_pk",
5645 + "columns": [
5646 + "index_id",
5647 + "asset_id",
5648 + "variant_id",
5649 + "added_at"
5650 + ]
5651 + }
5652 + },
5653 + "uniqueConstraints": {},
5654 + "policies": {},
5655 + "checkConstraints": {},
5656 + "isRLSEnabled": false
5657 + },
5658 + "public.index_values": {
5659 + "name": "index_values",
5660 + "schema": "",
5661 + "columns": {
5662 + "index_id": {
5663 + "name": "index_id",
5664 + "type": "text",
5665 + "primaryKey": false,
5666 + "notNull": true
5667 + },
5668 + "date": {
5669 + "name": "date",
5670 + "type": "date",
5671 + "primaryKey": false,
5672 + "notNull": true
5673 + },
5674 + "value": {
5675 + "name": "value",
5676 + "type": "real",
5677 + "primaryKey": false,
5678 + "notNull": true
5679 + },
5680 + "constituents_count": {
5681 + "name": "constituents_count",
5682 + "type": "integer",
5683 + "primaryKey": false,
5684 + "notNull": true,
5685 + "default": 0
5686 + },
5687 + "transactions": {
5688 + "name": "transactions",
5689 + "type": "integer",
5690 + "primaryKey": false,
5691 + "notNull": true,
5692 + "default": 0
5693 + },
5694 + "volume_usd": {
5695 + "name": "volume_usd",
5696 + "type": "numeric(18, 4)",
5697 + "primaryKey": false,
5698 + "notNull": false
5699 + },
5700 + "median_sale_usd": {
5701 + "name": "median_sale_usd",
5702 + "type": "numeric(18, 4)",
5703 + "primaryKey": false,
5704 + "notNull": false
5705 + },
5706 + "avg_sale_usd": {
5707 + "name": "avg_sale_usd",
5708 + "type": "numeric(18, 4)",
5709 + "primaryKey": false,
5710 + "notNull": false
5711 + },
5712 + "market_cap_est_usd": {
5713 + "name": "market_cap_est_usd",
5714 + "type": "numeric(18, 4)",
5715 + "primaryKey": false,
5716 + "notNull": false
5717 + },
5718 + "market_cap_confidence": {
5719 + "name": "market_cap_confidence",
5720 + "type": "text",
5721 + "primaryKey": false,
5722 + "notNull": false
5723 + },
5724 + "liquidity_score": {
5725 + "name": "liquidity_score",
5726 + "type": "real",
5727 + "primaryKey": false,
5728 + "notNull": false
5729 + },
5730 + "momentum": {
5731 + "name": "momentum",
5732 + "type": "real",
5733 + "primaryKey": false,
5734 + "notNull": false
5735 + },
5736 + "breadth": {
5737 + "name": "breadth",
5738 + "type": "integer",
5739 + "primaryKey": false,
5740 + "notNull": false
5741 + },
5742 + "tracked_assets": {
5743 + "name": "tracked_assets",
5744 + "type": "integer",
5745 + "primaryKey": false,
5746 + "notNull": false
5747 + },
5748 + "coverage": {
5749 + "name": "coverage",
5750 + "type": "real",
5751 + "primaryKey": false,
5752 + "notNull": false
5753 + }
5754 + },
5755 + "indexes": {},
5756 + "foreignKeys": {},
5757 + "compositePrimaryKeys": {
5758 + "index_values_index_id_date_pk": {
5759 + "name": "index_values_index_id_date_pk",
5760 + "columns": [
5761 + "index_id",
5762 + "date"
5763 + ]
5764 + }
5765 + },
5766 + "uniqueConstraints": {},
5767 + "policies": {},
5768 + "checkConstraints": {},
5769 + "isRLSEnabled": false
5770 + },
5771 + "public.indices": {
5772 + "name": "indices",
5773 + "schema": "",
5774 + "columns": {
5775 + "id": {
5776 + "name": "id",
5777 + "type": "text",
5778 + "primaryKey": true,
5779 + "notNull": true
5780 + },
5781 + "ticker": {
5782 + "name": "ticker",
5783 + "type": "text",
5784 + "primaryKey": false,
5785 + "notNull": true
5786 + },
5787 + "name": {
5788 + "name": "name",
5789 + "type": "text",
5790 + "primaryKey": false,
5791 + "notNull": true
5792 + },
5793 + "description": {
5794 + "name": "description",
5795 + "type": "text",
5796 + "primaryKey": false,
5797 + "notNull": false
5798 + },
5799 + "category_slugs": {
5800 + "name": "category_slugs",
5801 + "type": "text[]",
5802 + "primaryKey": false,
5803 + "notNull": true,
5804 + "default": "'{}'::text[]"
5805 + },
5806 + "family_slugs": {
5807 + "name": "family_slugs",
5808 + "type": "text[]",
5809 + "primaryKey": false,
5810 + "notNull": true,
5811 + "default": "'{}'::text[]"
5812 + },
5813 + "parent_ticker": {
5814 + "name": "parent_ticker",
5815 + "type": "text",
5816 + "primaryKey": false,
5817 + "notNull": false
5818 + },
5819 + "methodology": {
5820 + "name": "methodology",
5821 + "type": "text",
5822 + "primaryKey": false,
5823 + "notNull": true,
5824 + "default": "'chain_linked_equal_weight_v1'"
5825 + },
5826 + "weighting": {
5827 + "name": "weighting",
5828 + "type": "text",
5829 + "primaryKey": false,
5830 + "notNull": true,
5831 + "default": "'equal'"
5832 + },
5833 + "base_date": {
5834 + "name": "base_date",
5835 + "type": "date",
5836 + "primaryKey": false,
5837 + "notNull": true
5838 + },
5839 + "base_value": {
5840 + "name": "base_value",
5841 + "type": "real",
5842 + "primaryKey": false,
5843 + "notNull": true,
5844 + "default": 1000
5845 + },
5846 + "min_constituents": {
5847 + "name": "min_constituents",
5848 + "type": "integer",
5849 + "primaryKey": false,
5850 + "notNull": true,
5851 + "default": 10
5852 + },
5853 + "active": {
5854 + "name": "active",
5855 + "type": "boolean",
5856 + "primaryKey": false,
5857 + "notNull": true,
5858 + "default": true
5859 + },
5860 + "is_flagship": {
5861 + "name": "is_flagship",
5862 + "type": "boolean",
5863 + "primaryKey": false,
5864 + "notNull": true,
5865 + "default": false
5866 + },
5867 + "color": {
5868 + "name": "color",
5869 + "type": "text",
5870 + "primaryKey": false,
5871 + "notNull": false
5872 + },
5873 + "created_at": {
5874 + "name": "created_at",
5875 + "type": "timestamp with time zone",
5876 + "primaryKey": false,
5877 + "notNull": true,
5878 + "default": "now()"
5879 + }
5880 + },
5881 + "indexes": {},
5882 + "foreignKeys": {},
5883 + "compositePrimaryKeys": {},
5884 + "uniqueConstraints": {
5885 + "indices_ticker_unique": {
5886 + "name": "indices_ticker_unique",
5887 + "nullsNotDistinct": false,
5888 + "columns": [
5889 + "ticker"
5890 + ]
5891 + }
5892 + },
5893 + "policies": {},
5894 + "checkConstraints": {},
5895 + "isRLSEnabled": false
5896 + },
5897 + "public.price_snapshots": {
5898 + "name": "price_snapshots",
5899 + "schema": "",
5900 + "columns": {
5901 + "asset_id": {
5902 + "name": "asset_id",
5903 + "type": "text",
5904 + "primaryKey": false,
5905 + "notNull": true
5906 + },
5907 + "variant_id": {
5908 + "name": "variant_id",
5909 + "type": "text",
5910 + "primaryKey": false,
5911 + "notNull": true,
5912 + "default": "''"
5913 + },
5914 + "date": {
5915 + "name": "date",
5916 + "type": "date",
5917 + "primaryKey": false,
5918 + "notNull": true
5919 + },
5920 + "riv_usd": {
5921 + "name": "riv_usd",
5922 + "type": "numeric(18, 4)",
5923 + "primaryKey": false,
5924 + "notNull": false
5925 + },
5926 + "latest_sale_usd": {
5927 + "name": "latest_sale_usd",
5928 + "type": "numeric(18, 4)",
5929 + "primaryKey": false,
5930 + "notNull": false
5931 + },
5932 + "median_usd": {
5933 + "name": "median_usd",
5934 + "type": "numeric(18, 4)",
5935 + "primaryKey": false,
5936 + "notNull": false
5937 + },
5938 + "sales_count": {
5939 + "name": "sales_count",
5940 + "type": "integer",
5941 + "primaryKey": false,
5942 + "notNull": true,
5943 + "default": 0
5944 + },
5945 + "volume_usd": {
5946 + "name": "volume_usd",
5947 + "type": "numeric(18, 4)",
5948 + "primaryKey": false,
5949 + "notNull": false
5950 + },
5951 + "listings_count": {
5952 + "name": "listings_count",
5953 + "type": "integer",
5954 + "primaryKey": false,
5955 + "notNull": true,
5956 + "default": 0
5957 + },
5958 + "min_ask_usd": {
5959 + "name": "min_ask_usd",
5960 + "type": "numeric(18, 4)",
5961 + "primaryKey": false,
5962 + "notNull": false
5963 + },
5964 + "observation_usd": {
5965 + "name": "observation_usd",
5966 + "type": "numeric(18, 4)",
5967 + "primaryKey": false,
5968 + "notNull": false
5969 + }
5970 + },
5971 + "indexes": {
5972 + "price_snapshots_date_idx": {
5973 + "name": "price_snapshots_date_idx",
5974 + "columns": [
5975 + {
5976 + "expression": "date",
5977 + "isExpression": false,
5978 + "asc": true,
5979 + "nulls": "last"
5980 + }
5981 + ],
5982 + "isUnique": false,
5983 + "concurrently": false,
5984 + "method": "btree",
5985 + "with": {}
5986 + }
5987 + },
5988 + "foreignKeys": {},
5989 + "compositePrimaryKeys": {
5990 + "price_snapshots_asset_id_variant_id_date_pk": {
5991 + "name": "price_snapshots_asset_id_variant_id_date_pk",
5992 + "columns": [
5993 + "asset_id",
5994 + "variant_id",
5995 + "date"
5996 + ]
5997 + }
5998 + },
5999 + "uniqueConstraints": {},
6000 + "policies": {},
6001 + "checkConstraints": {},
6002 + "isRLSEnabled": false
6003 + },
6004 + "public.radar_findings": {
6005 + "name": "radar_findings",
6006 + "schema": "",
6007 + "columns": {
6008 + "id": {
6009 + "name": "id",
6010 + "type": "text",
6011 + "primaryKey": true,
6012 + "notNull": true
6013 + },
6014 + "asset_id": {
6015 + "name": "asset_id",
6016 + "type": "text",
6017 + "primaryKey": false,
6018 + "notNull": true
6019 + },
6020 + "kind": {
6021 + "name": "kind",
6022 + "type": "text",
6023 + "primaryKey": false,
6024 + "notNull": true
6025 + },
6026 + "score": {
6027 + "name": "score",
6028 + "type": "real",
6029 + "primaryKey": false,
6030 + "notNull": true
6031 + },
6032 + "evidence": {
6033 + "name": "evidence",
6034 + "type": "jsonb",
6035 + "primaryKey": false,
6036 + "notNull": true,
6037 + "default": "'{}'::jsonb"
6038 + },
6039 + "entity_type": {
6040 + "name": "entity_type",
6041 + "type": "text",
6042 + "primaryKey": false,
6043 + "notNull": false
6044 + },
6045 + "entity_id": {
6046 + "name": "entity_id",
6047 + "type": "text",
6048 + "primaryKey": false,
6049 + "notNull": false
6050 + },
6051 + "detected_at": {
6052 + "name": "detected_at",
6053 + "type": "timestamp with time zone",
6054 + "primaryKey": false,
6055 + "notNull": true
6056 + },
6057 + "expires_at": {
6058 + "name": "expires_at",
6059 + "type": "timestamp with time zone",
6060 + "primaryKey": false,
6061 + "notNull": false
6062 + }
6063 + },
6064 + "indexes": {
6065 + "radar_kind_idx": {
6066 + "name": "radar_kind_idx",
6067 + "columns": [
6068 + {
6069 + "expression": "kind",
6070 + "isExpression": false,
6071 + "asc": true,
6072 + "nulls": "last"
6073 + },
6074 + {
6075 + "expression": "detected_at",
6076 + "isExpression": false,
6077 + "asc": true,
6078 + "nulls": "last"
6079 + }
6080 + ],
6081 + "isUnique": false,
6082 + "concurrently": false,
6083 + "method": "btree",
6084 + "with": {}
6085 + },
6086 + "radar_entity_uq": {
6087 + "name": "radar_entity_uq",
6088 + "columns": [
6089 + {
6090 + "expression": "kind",
6091 + "isExpression": false,
6092 + "asc": true,
6093 + "nulls": "last"
6094 + },
6095 + {
6096 + "expression": "entity_type",
6097 + "isExpression": false,
6098 + "asc": true,
6099 + "nulls": "last"
6100 + },
6101 + {
6102 + "expression": "entity_id",
6103 + "isExpression": false,
6104 + "asc": true,
6105 + "nulls": "last"
6106 + }
6107 + ],
6108 + "isUnique": true,
6109 + "concurrently": false,
6110 + "method": "btree",
6111 + "with": {}
6112 + }
6113 + },
6114 + "foreignKeys": {},
6115 + "compositePrimaryKeys": {},
6116 + "uniqueConstraints": {},
6117 + "policies": {},
6118 + "checkConstraints": {},
6119 + "isRLSEnabled": false
6120 + },
6121 + "public.valuations": {
6122 + "name": "valuations",
6123 + "schema": "",
6124 + "columns": {
6125 + "id": {
6126 + "name": "id",
6127 + "type": "text",
6128 + "primaryKey": true,
6129 + "notNull": true
6130 + },
6131 + "asset_id": {
6132 + "name": "asset_id",
6133 + "type": "text",
6134 + "primaryKey": false,
6135 + "notNull": true
6136 + },
6137 + "variant_id": {
6138 + "name": "variant_id",
6139 + "type": "text",
6140 + "primaryKey": false,
6141 + "notNull": false
6142 + },
6143 + "computed_at": {
6144 + "name": "computed_at",
6145 + "type": "timestamp with time zone",
6146 + "primaryKey": false,
6147 + "notNull": true
6148 + },
6149 + "riv_usd": {
6150 + "name": "riv_usd",
6151 + "type": "numeric(18, 4)",
6152 + "primaryKey": false,
6153 + "notNull": false
6154 + },
6155 + "low_usd": {
6156 + "name": "low_usd",
6157 + "type": "numeric(18, 4)",
6158 + "primaryKey": false,
6159 + "notNull": false
6160 + },
6161 + "high_usd": {
6162 + "name": "high_usd",
6163 + "type": "numeric(18, 4)",
6164 + "primaryKey": false,
6165 + "notNull": false
6166 + },
6167 + "confidence": {
6168 + "name": "confidence",
6169 + "type": "numeric(8, 6)",
6170 + "primaryKey": false,
6171 + "notNull": true,
6172 + "default": 0
6173 + },
6174 + "confidence_label": {
6175 + "name": "confidence_label",
6176 + "type": "text",
6177 + "primaryKey": false,
6178 + "notNull": true,
6179 + "default": "'insufficient'"
6180 + },
6181 + "sample_size": {
6182 + "name": "sample_size",
6183 + "type": "integer",
6184 + "primaryKey": false,
6185 + "notNull": true,
6186 + "default": 0
6187 + },
6188 + "window_days": {
6189 + "name": "window_days",
6190 + "type": "integer",
6191 + "primaryKey": false,
6192 + "notNull": true,
6193 + "default": 365
6194 + },
6195 + "methods": {
6196 + "name": "methods",
6197 + "type": "jsonb",
6198 + "primaryKey": false,
6199 + "notNull": true,
6200 + "default": "'{}'::jsonb"
6201 + },
6202 + "sales_used": {
6203 + "name": "sales_used",
6204 + "type": "text[]",
6205 + "primaryKey": false,
6206 + "notNull": true,
6207 + "default": "'{}'::text[]"
6208 + },
6209 + "observations_used": {
6210 + "name": "observations_used",
6211 + "type": "integer",
6212 + "primaryKey": false,
6213 + "notNull": true,
6214 + "default": 0
6215 + },
6216 + "method": {
6217 + "name": "method",
6218 + "type": "text",
6219 + "primaryKey": false,
6220 + "notNull": true,
6221 + "default": "'ensemble_v1'"
6222 + },
6223 + "notes": {
6224 + "name": "notes",
6225 + "type": "text[]",
6226 + "primaryKey": false,
6227 + "notNull": true,
6228 + "default": "'{}'::text[]"
6229 + }
6230 + },
6231 + "indexes": {
6232 + "valuations_asset_idx": {
6233 + "name": "valuations_asset_idx",
6234 + "columns": [
6235 + {
6236 + "expression": "asset_id",
6237 + "isExpression": false,
6238 + "asc": true,
6239 + "nulls": "last"
6240 + },
6241 + {
6242 + "expression": "computed_at",
6243 + "isExpression": false,
6244 + "asc": true,
6245 + "nulls": "last"
6246 + }
6247 + ],
6248 + "isUnique": false,
6249 + "concurrently": false,
6250 + "method": "btree",
6251 + "with": {}
6252 + },
6253 + "valuations_variant_idx": {
6254 + "name": "valuations_variant_idx",
6255 + "columns": [
6256 + {
6257 + "expression": "variant_id",
6258 + "isExpression": false,
6259 + "asc": true,
6260 + "nulls": "last"
6261 + },
6262 + {
6263 + "expression": "computed_at",
6264 + "isExpression": false,
6265 + "asc": true,
6266 + "nulls": "last"
6267 + }
6268 + ],
6269 + "isUnique": false,
6270 + "concurrently": false,
6271 + "method": "btree",
6272 + "with": {}
6273 + }
6274 + },
6275 + "foreignKeys": {},
6276 + "compositePrimaryKeys": {},
6277 + "uniqueConstraints": {},
6278 + "policies": {},
6279 + "checkConstraints": {},
6280 + "isRLSEnabled": false
6281 + },
6282 + "public.alert_events": {
6283 + "name": "alert_events",
6284 + "schema": "",
6285 + "columns": {
6286 + "id": {
6287 + "name": "id",
6288 + "type": "text",
6289 + "primaryKey": true,
6290 + "notNull": true
6291 + },
6292 + "alert_id": {
6293 + "name": "alert_id",
6294 + "type": "text",
6295 + "primaryKey": false,
6296 + "notNull": true
6297 + },
6298 + "user_id": {
6299 + "name": "user_id",
6300 + "type": "text",
6301 + "primaryKey": false,
6302 + "notNull": true
6303 + },
6304 + "message": {
6305 + "name": "message",
6306 + "type": "text",
6307 + "primaryKey": false,
6308 + "notNull": true
6309 + },
6310 + "payload": {
6311 + "name": "payload",
6312 + "type": "jsonb",
6313 + "primaryKey": false,
6314 + "notNull": true,
6315 + "default": "'{}'::jsonb"
6316 + },
6317 + "read_at": {
6318 + "name": "read_at",
6319 + "type": "timestamp with time zone",
6320 + "primaryKey": false,
6321 + "notNull": false
6322 + },
6323 + "created_at": {
6324 + "name": "created_at",
6325 + "type": "timestamp with time zone",
6326 + "primaryKey": false,
6327 + "notNull": true,
6328 + "default": "now()"
6329 + }
6330 + },
6331 + "indexes": {
6332 + "alert_events_user_idx": {
6333 + "name": "alert_events_user_idx",
6334 + "columns": [
6335 + {
6336 + "expression": "user_id",
6337 + "isExpression": false,
6338 + "asc": true,
6339 + "nulls": "last"
6340 + },
6341 + {
6342 + "expression": "created_at",
6343 + "isExpression": false,
6344 + "asc": true,
6345 + "nulls": "last"
6346 + }
6347 + ],
6348 + "isUnique": false,
6349 + "concurrently": false,
6350 + "method": "btree",
6351 + "with": {}
6352 + }
6353 + },
6354 + "foreignKeys": {},
6355 + "compositePrimaryKeys": {},
6356 + "uniqueConstraints": {},
6357 + "policies": {},
6358 + "checkConstraints": {},
6359 + "isRLSEnabled": false
6360 + },
6361 + "public.alerts": {
6362 + "name": "alerts",
6363 + "schema": "",
6364 + "columns": {
6365 + "id": {
6366 + "name": "id",
6367 + "type": "text",
6368 + "primaryKey": true,
6369 + "notNull": true
6370 + },
6371 + "user_id": {
6372 + "name": "user_id",
6373 + "type": "text",
6374 + "primaryKey": false,
6375 + "notNull": true
6376 + },
6377 + "alert_type": {
6378 + "name": "alert_type",
6379 + "type": "text",
6380 + "primaryKey": false,
6381 + "notNull": true
6382 + },
6383 + "target_type": {
6384 + "name": "target_type",
6385 + "type": "text",
6386 + "primaryKey": false,
6387 + "notNull": true
6388 + },
6389 + "target_id": {
6390 + "name": "target_id",
6391 + "type": "text",
6392 + "primaryKey": false,
6393 + "notNull": true
6394 + },
6395 + "threshold": {
6396 + "name": "threshold",
6397 + "type": "numeric(18, 4)",
6398 + "primaryKey": false,
6399 + "notNull": false
6400 + },
6401 + "currency": {
6402 + "name": "currency",
6403 + "type": "text",
6404 + "primaryKey": false,
6405 + "notNull": false
6406 + },
6407 + "params": {
6408 + "name": "params",
6409 + "type": "jsonb",
6410 + "primaryKey": false,
6411 + "notNull": true,
6412 + "default": "'{}'::jsonb"
6413 + },
6414 + "channel": {
6415 + "name": "channel",
6416 + "type": "text",
6417 + "primaryKey": false,
6418 + "notNull": true,
6419 + "default": "'inapp'"
6420 + },
6421 + "active": {
6422 + "name": "active",
6423 + "type": "boolean",
6424 + "primaryKey": false,
6425 + "notNull": true,
6426 + "default": true
6427 + },
6428 + "name": {
6429 + "name": "name",
6430 + "type": "text",
6431 + "primaryKey": false,
6432 + "notNull": false
6433 + },
6434 + "cooldown_minutes": {
6435 + "name": "cooldown_minutes",
6436 + "type": "integer",
6437 + "primaryKey": false,
6438 + "notNull": true,
6439 + "default": 1440
6440 + },
6441 + "last_triggered_at": {
6442 + "name": "last_triggered_at",
6443 + "type": "timestamp with time zone",
6444 + "primaryKey": false,
6445 + "notNull": false
6446 + },
6447 + "trigger_count": {
6448 + "name": "trigger_count",
6449 + "type": "integer",
6450 + "primaryKey": false,
6451 + "notNull": true,
6452 + "default": 0
6453 + },
6454 + "created_at": {
6455 + "name": "created_at",
6456 + "type": "timestamp with time zone",
6457 + "primaryKey": false,
6458 + "notNull": true,
6459 + "default": "now()"
6460 + }
6461 + },
6462 + "indexes": {
6463 + "alerts_user_idx": {
6464 + "name": "alerts_user_idx",
6465 + "columns": [
6466 + {
6467 + "expression": "user_id",
6468 + "isExpression": false,
6469 + "asc": true,
6470 + "nulls": "last"
6471 + }
6472 + ],
6473 + "isUnique": false,
6474 + "concurrently": false,
6475 + "method": "btree",
6476 + "with": {}
6477 + },
6478 + "alerts_target_idx": {
6479 + "name": "alerts_target_idx",
6480 + "columns": [
6481 + {
6482 + "expression": "target_type",
6483 + "isExpression": false,
6484 + "asc": true,
6485 + "nulls": "last"
6486 + },
6487 + {
6488 + "expression": "target_id",
6489 + "isExpression": false,
6490 + "asc": true,
6491 + "nulls": "last"
6492 + },
6493 + {
6494 + "expression": "active",
6495 + "isExpression": false,
6496 + "asc": true,
6497 + "nulls": "last"
6498 + }
6499 + ],
6500 + "isUnique": false,
6501 + "concurrently": false,
6502 + "method": "btree",
6503 + "with": {}
6504 + }
6505 + },
6506 + "foreignKeys": {},
6507 + "compositePrimaryKeys": {},
6508 + "uniqueConstraints": {},
6509 + "policies": {},
6510 + "checkConstraints": {},
6511 + "isRLSEnabled": false
6512 + },
6513 + "public.api_keys": {
6514 + "name": "api_keys",
6515 + "schema": "",
6516 + "columns": {
6517 + "id": {
6518 + "name": "id",
6519 + "type": "text",
6520 + "primaryKey": true,
6521 + "notNull": true
6522 + },
6523 + "user_id": {
6524 + "name": "user_id",
6525 + "type": "text",
6526 + "primaryKey": false,
6527 + "notNull": false
6528 + },
6529 + "name": {
6530 + "name": "name",
6531 + "type": "text",
6532 + "primaryKey": false,
6533 + "notNull": true
6534 + },
6535 + "prefix": {
6536 + "name": "prefix",
6537 + "type": "text",
6538 + "primaryKey": false,
6539 + "notNull": true
6540 + },
6541 + "key_hash": {
6542 + "name": "key_hash",
6543 + "type": "text",
6544 + "primaryKey": false,
6545 + "notNull": true
6546 + },
6547 + "tier": {
6548 + "name": "tier",
6549 + "type": "text",
6550 + "primaryKey": false,
6551 + "notNull": true,
6552 + "default": "'free'"
6553 + },
6554 + "rate_limit_per_minute": {
6555 + "name": "rate_limit_per_minute",
6556 + "type": "integer",
6557 + "primaryKey": false,
6558 + "notNull": true,
6559 + "default": 60
6560 + },
6561 + "daily_quota": {
6562 + "name": "daily_quota",
6563 + "type": "integer",
6564 + "primaryKey": false,
6565 + "notNull": true,
6566 + "default": 1000
6567 + },
6568 + "created_at": {
6569 + "name": "created_at",
6570 + "type": "timestamp with time zone",
6571 + "primaryKey": false,
6572 + "notNull": true,
6573 + "default": "now()"
6574 + },
6575 + "last_used_at": {
6576 + "name": "last_used_at",
6577 + "type": "timestamp with time zone",
6578 + "primaryKey": false,
6579 + "notNull": false
6580 + },
6581 + "revoked_at": {
6582 + "name": "revoked_at",
6583 + "type": "timestamp with time zone",
6584 + "primaryKey": false,
6585 + "notNull": false
6586 + }
6587 + },
6588 + "indexes": {
6589 + "api_keys_user_idx": {
6590 + "name": "api_keys_user_idx",
6591 + "columns": [
6592 + {
6593 + "expression": "user_id",
6594 + "isExpression": false,
6595 + "asc": true,
6596 + "nulls": "last"
6597 + }
6598 + ],
6599 + "isUnique": false,
6600 + "concurrently": false,
6601 + "method": "btree",
6602 + "with": {}
6603 + }
6604 + },
6605 + "foreignKeys": {},
6606 + "compositePrimaryKeys": {},
6607 + "uniqueConstraints": {
6608 + "api_keys_key_hash_unique": {
6609 + "name": "api_keys_key_hash_unique",
6610 + "nullsNotDistinct": false,
6611 + "columns": [
6612 + "key_hash"
6613 + ]
6614 + }
6615 + },
6616 + "policies": {},
6617 + "checkConstraints": {},
6618 + "isRLSEnabled": false
6619 + },
6620 + "public.api_usage": {
6621 + "name": "api_usage",
6622 + "schema": "",
6623 + "columns": {
6624 + "key_id": {
6625 + "name": "key_id",
6626 + "type": "text",
6627 + "primaryKey": false,
6628 + "notNull": true
6629 + },
6630 + "date": {
6631 + "name": "date",
6632 + "type": "date",
6633 + "primaryKey": false,
6634 + "notNull": true
6635 + },
6636 + "endpoint": {
6637 + "name": "endpoint",
6638 + "type": "text",
6639 + "primaryKey": false,
6640 + "notNull": true
6641 + },
6642 + "count": {
6643 + "name": "count",
6644 + "type": "integer",
6645 + "primaryKey": false,
6646 + "notNull": true,
6647 + "default": 0
6648 + },
6649 + "latency_ms_avg": {
6650 + "name": "latency_ms_avg",
6651 + "type": "real",
6652 + "primaryKey": false,
6653 + "notNull": false
6654 + }
6655 + },
6656 + "indexes": {},
6657 + "foreignKeys": {},
6658 + "compositePrimaryKeys": {
6659 + "api_usage_key_id_date_endpoint_pk": {
6660 + "name": "api_usage_key_id_date_endpoint_pk",
6661 + "columns": [
6662 + "key_id",
6663 + "date",
6664 + "endpoint"
6665 + ]
6666 + }
6667 + },
6668 + "uniqueConstraints": {},
6669 + "policies": {},
6670 + "checkConstraints": {},
6671 + "isRLSEnabled": false
6672 + },
6673 + "public.asset_views": {
6674 + "name": "asset_views",
6675 + "schema": "",
6676 + "columns": {
6677 + "asset_id": {
6678 + "name": "asset_id",
6679 + "type": "text",
6680 + "primaryKey": false,
6681 + "notNull": true
6682 + },
6683 + "date": {
6684 + "name": "date",
6685 + "type": "date",
6686 + "primaryKey": false,
6687 + "notNull": true
6688 + },
6689 + "views": {
6690 + "name": "views",
6691 + "type": "integer",
6692 + "primaryKey": false,
6693 + "notNull": true,
6694 + "default": 0
6695 + }
6696 + },
6697 + "indexes": {},
6698 + "foreignKeys": {},
6699 + "compositePrimaryKeys": {
6700 + "asset_views_asset_id_date_pk": {
6701 + "name": "asset_views_asset_id_date_pk",
6702 + "columns": [
6703 + "asset_id",
6704 + "date"
6705 + ]
6706 + }
6707 + },
6708 + "uniqueConstraints": {},
6709 + "policies": {},
6710 + "checkConstraints": {},
6711 + "isRLSEnabled": false
6712 + },
6713 + "public.collection_items": {
6714 + "name": "collection_items",
6715 + "schema": "",
6716 + "columns": {
6717 + "id": {
6718 + "name": "id",
6719 + "type": "text",
6720 + "primaryKey": true,
6721 + "notNull": true
6722 + },
6723 + "collection_id": {
6724 + "name": "collection_id",
6725 + "type": "text",
6726 + "primaryKey": false,
6727 + "notNull": true
6728 + },
6729 + "asset_id": {
6730 + "name": "asset_id",
6731 + "type": "text",
6732 + "primaryKey": false,
6733 + "notNull": true
6734 + },
6735 + "variant_id": {
6736 + "name": "variant_id",
6737 + "type": "text",
6738 + "primaryKey": false,
6739 + "notNull": false
6740 + },
6741 + "quantity": {
6742 + "name": "quantity",
6743 + "type": "integer",
6744 + "primaryKey": false,
6745 + "notNull": true,
6746 + "default": 1
6747 + },
6748 + "acquired_at": {
6749 + "name": "acquired_at",
6750 + "type": "date",
6751 + "primaryKey": false,
6752 + "notNull": false
6753 + },
6754 + "purchase_price": {
6755 + "name": "purchase_price",
6756 + "type": "numeric(18, 4)",
6757 + "primaryKey": false,
6758 + "notNull": false
6759 + },
6760 + "purchase_currency": {
6761 + "name": "purchase_currency",
6762 + "type": "text",
6763 + "primaryKey": false,
6764 + "notNull": false
6765 + },
6766 + "purchase_price_usd": {
6767 + "name": "purchase_price_usd",
6768 + "type": "numeric(18, 4)",
6769 + "primaryKey": false,
6770 + "notNull": false
6771 + },
6772 + "source": {
6773 + "name": "source",
6774 + "type": "text",
6775 + "primaryKey": false,
6776 + "notNull": false
6777 + },
6778 + "grader": {
6779 + "name": "grader",
6780 + "type": "text",
6781 + "primaryKey": false,
6782 + "notNull": false
6783 + },
6784 + "grade": {
6785 + "name": "grade",
6786 + "type": "text",
6787 + "primaryKey": false,
6788 + "notNull": false
6789 + },
6790 + "certification_number": {
6791 + "name": "certification_number",
6792 + "type": "text",
6793 + "primaryKey": false,
6794 + "notNull": false
6795 + },
6796 + "serial": {
6797 + "name": "serial",
6798 + "type": "text",
6799 + "primaryKey": false,
6800 + "notNull": false
6801 + },
6802 + "photos": {
6803 + "name": "photos",
6804 + "type": "jsonb",
6805 + "primaryKey": false,
6806 + "notNull": true,
6807 + "default": "'[]'::jsonb"
6808 + },
6809 + "notes": {
6810 + "name": "notes",
6811 + "type": "text",
6812 + "primaryKey": false,
6813 + "notNull": false
6814 + },
6815 + "tags": {
6816 + "name": "tags",
6817 + "type": "jsonb",
6818 + "primaryKey": false,
6819 + "notNull": true,
6820 + "default": "'[]'::jsonb"
6821 + },
6822 + "condition": {
6823 + "name": "condition",
6824 + "type": "text",
6825 + "primaryKey": false,
6826 + "notNull": false
6827 + },
6828 + "manual_value_usd": {
6829 + "name": "manual_value_usd",
6830 + "type": "numeric(18, 4)",
6831 + "primaryKey": false,
6832 + "notNull": false
6833 + },
6834 + "sold_at": {
6835 + "name": "sold_at",
6836 + "type": "date",
6837 + "primaryKey": false,
6838 + "notNull": false
6839 + },
6840 + "sold_price_usd": {
6841 + "name": "sold_price_usd",
6842 + "type": "numeric(18, 4)",
6843 + "primaryKey": false,
6844 + "notNull": false
6845 + },
6846 + "created_at": {
6847 + "name": "created_at",
6848 + "type": "timestamp with time zone",
6849 + "primaryKey": false,
6850 + "notNull": true,
6851 + "default": "now()"
6852 + },
6853 + "updated_at": {
6854 + "name": "updated_at",
6855 + "type": "timestamp with time zone",
6856 + "primaryKey": false,
6857 + "notNull": true,
6858 + "default": "now()"
6859 + }
6860 + },
6861 + "indexes": {
6862 + "collection_items_collection_idx": {
6863 + "name": "collection_items_collection_idx",
6864 + "columns": [
6865 + {
6866 + "expression": "collection_id",
6867 + "isExpression": false,
6868 + "asc": true,
6869 + "nulls": "last"
6870 + }
6871 + ],
6872 + "isUnique": false,
6873 + "concurrently": false,
6874 + "method": "btree",
6875 + "with": {}
6876 + },
6877 + "collection_items_asset_idx": {
6878 + "name": "collection_items_asset_idx",
6879 + "columns": [
6880 + {
6881 + "expression": "asset_id",
6882 + "isExpression": false,
6883 + "asc": true,
6884 + "nulls": "last"
6885 + }
6886 + ],
6887 + "isUnique": false,
6888 + "concurrently": false,
6889 + "method": "btree",
6890 + "with": {}
6891 + }
6892 + },
6893 + "foreignKeys": {},
6894 + "compositePrimaryKeys": {},
6895 + "uniqueConstraints": {},
6896 + "policies": {},
6897 + "checkConstraints": {},
6898 + "isRLSEnabled": false
6899 + },
6900 + "public.collection_snapshots": {
6901 + "name": "collection_snapshots",
6902 + "schema": "",
6903 + "columns": {
6904 + "collection_id": {
6905 + "name": "collection_id",
6906 + "type": "text",
6907 + "primaryKey": false,
6908 + "notNull": true
6909 + },
6910 + "date": {
6911 + "name": "date",
6912 + "type": "date",
6913 + "primaryKey": false,
6914 + "notNull": true
6915 + },
6916 + "value_usd": {
6917 + "name": "value_usd",
6918 + "type": "numeric(18, 4)",
6919 + "primaryKey": false,
6920 + "notNull": true
6921 + },
6922 + "cost_basis_usd": {
6923 + "name": "cost_basis_usd",
6924 + "type": "numeric(18, 4)",
6925 + "primaryKey": false,
6926 + "notNull": true
6927 + },
6928 + "items": {
6929 + "name": "items",
6930 + "type": "integer",
6931 + "primaryKey": false,
6932 + "notNull": true
6933 + }
6934 + },
6935 + "indexes": {},
6936 + "foreignKeys": {},
6937 + "compositePrimaryKeys": {
6938 + "collection_snapshots_collection_id_date_pk": {
6939 + "name": "collection_snapshots_collection_id_date_pk",
6940 + "columns": [
6941 + "collection_id",
6942 + "date"
6943 + ]
6944 + }
6945 + },
6946 + "uniqueConstraints": {},
6947 + "policies": {},
6948 + "checkConstraints": {},
6949 + "isRLSEnabled": false
6950 + },
6951 + "public.collections": {
6952 + "name": "collections",
6953 + "schema": "",
6954 + "columns": {
6955 + "id": {
6956 + "name": "id",
6957 + "type": "text",
6958 + "primaryKey": true,
6959 + "notNull": true
6960 + },
6961 + "user_id": {
6962 + "name": "user_id",
6963 + "type": "text",
6964 + "primaryKey": false,
6965 + "notNull": true
6966 + },
6967 + "name": {
6968 + "name": "name",
6969 + "type": "text",
6970 + "primaryKey": false,
6971 + "notNull": true
6972 + },
6973 + "description": {
6974 + "name": "description",
6975 + "type": "text",
6976 + "primaryKey": false,
6977 + "notNull": false
6978 + },
6979 + "is_public": {
6980 + "name": "is_public",
6981 + "type": "boolean",
6982 + "primaryKey": false,
6983 + "notNull": true,
6984 + "default": false
6985 + },
6986 + "public_slug": {
6987 + "name": "public_slug",
6988 + "type": "text",
6989 + "primaryKey": false,
6990 + "notNull": false
6991 + },
6992 + "kind": {
6993 + "name": "kind",
6994 + "type": "text",
6995 + "primaryKey": false,
6996 + "notNull": true,
6997 + "default": "'collection'"
6998 + },
6999 + "budget_usd": {
7000 + "name": "budget_usd",
7001 + "type": "numeric(18, 4)",
7002 + "primaryKey": false,
7003 + "notNull": false
7004 + },
7005 + "color": {
7006 + "name": "color",
7007 + "type": "text",
7008 + "primaryKey": false,
7009 + "notNull": false
7010 + },
7011 + "created_at": {
7012 + "name": "created_at",
7013 + "type": "timestamp with time zone",
7014 + "primaryKey": false,
7015 + "notNull": true,
7016 + "default": "now()"
7017 + },
7018 + "updated_at": {
7019 + "name": "updated_at",
7020 + "type": "timestamp with time zone",
7021 + "primaryKey": false,
7022 + "notNull": true,
7023 + "default": "now()"
7024 + }
7025 + },
7026 + "indexes": {
7027 + "collections_user_idx": {
7028 + "name": "collections_user_idx",
7029 + "columns": [
7030 + {
7031 + "expression": "user_id",
7032 + "isExpression": false,
7033 + "asc": true,
7034 + "nulls": "last"
7035 + }
7036 + ],
7037 + "isUnique": false,
7038 + "concurrently": false,
7039 + "method": "btree",
7040 + "with": {}
7041 + },
7042 + "collections_public_slug_uq": {
7043 + "name": "collections_public_slug_uq",
7044 + "columns": [
7045 + {
7046 + "expression": "public_slug",
7047 + "isExpression": false,
7048 + "asc": true,
7049 + "nulls": "last"
7050 + }
7051 + ],
7052 + "isUnique": true,
7053 + "concurrently": false,
7054 + "method": "btree",
7055 + "with": {}
7056 + }
7057 + },
7058 + "foreignKeys": {},
7059 + "compositePrimaryKeys": {},
7060 + "uniqueConstraints": {},
7061 + "policies": {},
7062 + "checkConstraints": {},
7063 + "isRLSEnabled": false
7064 + },
7065 + "public.search_log": {
7066 + "name": "search_log",
7067 + "schema": "",
7068 + "columns": {
7069 + "id": {
7070 + "name": "id",
7071 + "type": "text",
7072 + "primaryKey": true,
7073 + "notNull": true
7074 + },
7075 + "query": {
7076 + "name": "query",
7077 + "type": "text",
7078 + "primaryKey": false,
7079 + "notNull": true
7080 + },
7081 + "normalized": {
7082 + "name": "normalized",
7083 + "type": "text",
7084 + "primaryKey": false,
7085 + "notNull": true
7086 + },
7087 + "results": {
7088 + "name": "results",
7089 + "type": "integer",
7090 + "primaryKey": false,
7091 + "notNull": true
7092 + },
7093 + "user_id": {
7094 + "name": "user_id",
7095 + "type": "text",
7096 + "primaryKey": false,
7097 + "notNull": false
7098 + },
7099 + "created_at": {
7100 + "name": "created_at",
7101 + "type": "timestamp with time zone",
7102 + "primaryKey": false,
7103 + "notNull": true,
7104 + "default": "now()"
7105 + }
7106 + },
7107 + "indexes": {
7108 + "search_log_created_idx": {
7109 + "name": "search_log_created_idx",
7110 + "columns": [
7111 + {
7112 + "expression": "created_at",
7113 + "isExpression": false,
7114 + "asc": true,
7115 + "nulls": "last"
7116 + }
7117 + ],
7118 + "isUnique": false,
7119 + "concurrently": false,
7120 + "method": "btree",
7121 + "with": {}
7122 + },
7123 + "search_log_normalized_idx": {
7124 + "name": "search_log_normalized_idx",
7125 + "columns": [
7126 + {
7127 + "expression": "normalized",
7128 + "isExpression": false,
7129 + "asc": true,
7130 + "nulls": "last"
7131 + }
7132 + ],
7133 + "isUnique": false,
7134 + "concurrently": false,
7135 + "method": "btree",
7136 + "with": {}
7137 + }
7138 + },
7139 + "foreignKeys": {},
7140 + "compositePrimaryKeys": {},
7141 + "uniqueConstraints": {},
7142 + "policies": {},
7143 + "checkConstraints": {},
7144 + "isRLSEnabled": false
7145 + },
7146 + "public.sessions": {
7147 + "name": "sessions",
7148 + "schema": "",
7149 + "columns": {
7150 + "id": {
7151 + "name": "id",
7152 + "type": "text",
7153 + "primaryKey": true,
7154 + "notNull": true
7155 + },
7156 + "user_id": {
7157 + "name": "user_id",
7158 + "type": "text",
7159 + "primaryKey": false,
7160 + "notNull": true
7161 + },
7162 + "expires_at": {
7163 + "name": "expires_at",
7164 + "type": "timestamp with time zone",
7165 + "primaryKey": false,
7166 + "notNull": true
7167 + },
7168 + "user_agent": {
7169 + "name": "user_agent",
7170 + "type": "text",
7171 + "primaryKey": false,
7172 + "notNull": false
7173 + },
7174 + "ip": {
7175 + "name": "ip",
7176 + "type": "text",
7177 + "primaryKey": false,
7178 + "notNull": false
7179 + },
7180 + "last_seen_at": {
7181 + "name": "last_seen_at",
7182 + "type": "timestamp with time zone",
7183 + "primaryKey": false,
7184 + "notNull": false
7185 + },
7186 + "revoked_at": {
7187 + "name": "revoked_at",
7188 + "type": "timestamp with time zone",
7189 + "primaryKey": false,
7190 + "notNull": false
7191 + },
7192 + "created_at": {
7193 + "name": "created_at",
7194 + "type": "timestamp with time zone",
7195 + "primaryKey": false,
7196 + "notNull": true,
7197 + "default": "now()"
7198 + }
7199 + },
7200 + "indexes": {
7201 + "sessions_user_idx": {
7202 + "name": "sessions_user_idx",
7203 + "columns": [
7204 + {
7205 + "expression": "user_id",
7206 + "isExpression": false,
7207 + "asc": true,
7208 + "nulls": "last"
7209 + }
7210 + ],
7211 + "isUnique": false,
7212 + "concurrently": false,
7213 + "method": "btree",
7214 + "with": {}
7215 + }
7216 + },
7217 + "foreignKeys": {},
7218 + "compositePrimaryKeys": {},
7219 + "uniqueConstraints": {},
7220 + "policies": {},
7221 + "checkConstraints": {},
7222 + "isRLSEnabled": false
7223 + },
7224 + "public.users": {
7225 + "name": "users",
7226 + "schema": "",
7227 + "columns": {
7228 + "id": {
7229 + "name": "id",
7230 + "type": "text",
7231 + "primaryKey": true,
7232 + "notNull": true
7233 + },
7234 + "email": {
7235 + "name": "email",
7236 + "type": "text",
7237 + "primaryKey": false,
7238 + "notNull": true
7239 + },
7240 + "email_verified_at": {
7241 + "name": "email_verified_at",
7242 + "type": "timestamp with time zone",
7243 + "primaryKey": false,
7244 + "notNull": false
7245 + },
7246 + "password_hash": {
7247 + "name": "password_hash",
7248 + "type": "text",
7249 + "primaryKey": false,
7250 + "notNull": false
7251 + },
7252 + "name": {
7253 + "name": "name",
7254 + "type": "text",
7255 + "primaryKey": false,
7256 + "notNull": false
7257 + },
7258 + "role": {
7259 + "name": "role",
7260 + "type": "text",
7261 + "primaryKey": false,
7262 + "notNull": true,
7263 + "default": "'user'"
7264 + },
7265 + "display_currency": {
7266 + "name": "display_currency",
7267 + "type": "text",
7268 + "primaryKey": false,
7269 + "notNull": true,
7270 + "default": "'USD'"
7271 + },
7272 + "providers": {
7273 + "name": "providers",
7274 + "type": "jsonb",
7275 + "primaryKey": false,
7276 + "notNull": true,
7277 + "default": "'[]'::jsonb"
7278 + },
7279 + "preferences": {
7280 + "name": "preferences",
7281 + "type": "jsonb",
7282 + "primaryKey": false,
7283 + "notNull": true,
7284 + "default": "'{}'::jsonb"
7285 + },
7286 + "handle": {
7287 + "name": "handle",
7288 + "type": "text",
7289 + "primaryKey": false,
7290 + "notNull": false
7291 + },
7292 + "avatar_url": {
7293 + "name": "avatar_url",
7294 + "type": "text",
7295 + "primaryKey": false,
7296 + "notNull": false
7297 + },
7298 + "bio": {
7299 + "name": "bio",
7300 + "type": "text",
7301 + "primaryKey": false,
7302 + "notNull": false
7303 + },
7304 + "mfa_enabled": {
7305 + "name": "mfa_enabled",
7306 + "type": "boolean",
7307 + "primaryKey": false,
7308 + "notNull": true,
7309 + "default": false
7310 + },
7311 + "totp_secret_enc": {
7312 + "name": "totp_secret_enc",
7313 + "type": "text",
7314 + "primaryKey": false,
7315 + "notNull": false
7316 + },
7317 + "always_ask_code": {
7318 + "name": "always_ask_code",
7319 + "type": "boolean",
7320 + "primaryKey": false,
7321 + "notNull": true,
7322 + "default": true
7323 + },
7324 + "password_changed_at": {
7325 + "name": "password_changed_at",
7326 + "type": "timestamp with time zone",
7327 + "primaryKey": false,
7328 + "notNull": false
7329 + },
7330 + "pending_email": {
7331 + "name": "pending_email",
7332 + "type": "text",
7333 + "primaryKey": false,
7334 + "notNull": false
7335 + },
7336 + "deleted_at": {
7337 + "name": "deleted_at",
7338 + "type": "timestamp with time zone",
7339 + "primaryKey": false,
7340 + "notNull": false
7341 + },
7342 + "purge_after": {
7343 + "name": "purge_after",
7344 + "type": "timestamp with time zone",
7345 + "primaryKey": false,
7346 + "notNull": false
7347 + },
7348 + "created_at": {
7349 + "name": "created_at",
7350 + "type": "timestamp with time zone",
7351 + "primaryKey": false,
7352 + "notNull": true,
7353 + "default": "now()"
7354 + },
7355 + "last_login_at": {
7356 + "name": "last_login_at",
7357 + "type": "timestamp with time zone",
7358 + "primaryKey": false,
7359 + "notNull": false
7360 + }
7361 + },
7362 + "indexes": {},
7363 + "foreignKeys": {},
7364 + "compositePrimaryKeys": {},
7365 + "uniqueConstraints": {
7366 + "users_email_unique": {
7367 + "name": "users_email_unique",
7368 + "nullsNotDistinct": false,
7369 + "columns": [
7370 + "email"
7371 + ]
7372 + },
7373 + "users_handle_unique": {
7374 + "name": "users_handle_unique",
7375 + "nullsNotDistinct": false,
7376 + "columns": [
7377 + "handle"
7378 + ]
7379 + }
7380 + },
7381 + "policies": {},
7382 + "checkConstraints": {},
7383 + "isRLSEnabled": false
7384 + },
7385 + "public.watchlist_items": {
7386 + "name": "watchlist_items",
7387 + "schema": "",
7388 + "columns": {
7389 + "id": {
7390 + "name": "id",
7391 + "type": "text",
7392 + "primaryKey": true,
7393 + "notNull": true
7394 + },
7395 + "watchlist_id": {
7396 + "name": "watchlist_id",
7397 + "type": "text",
7398 + "primaryKey": false,
7399 + "notNull": true
7400 + },
7401 + "target_type": {
7402 + "name": "target_type",
7403 + "type": "text",
7404 + "primaryKey": false,
7405 + "notNull": true
7406 + },
7407 + "target_id": {
7408 + "name": "target_id",
7409 + "type": "text",
7410 + "primaryKey": false,
7411 + "notNull": true
7412 + },
7413 + "label": {
7414 + "name": "label",
7415 + "type": "text",
7416 + "primaryKey": false,
7417 + "notNull": false
7418 + },
7419 + "note": {
7420 + "name": "note",
7421 + "type": "text",
7422 + "primaryKey": false,
7423 + "notNull": false
7424 + },
7425 + "target_price_usd": {
7426 + "name": "target_price_usd",
7427 + "type": "numeric(18, 4)",
7428 + "primaryKey": false,
7429 + "notNull": false
7430 + },
7431 + "baseline_usd": {
7432 + "name": "baseline_usd",
7433 + "type": "numeric(18, 4)",
7434 + "primaryKey": false,
7435 + "notNull": false
7436 + },
7437 + "created_at": {
7438 + "name": "created_at",
7439 + "type": "timestamp with time zone",
7440 + "primaryKey": false,
7441 + "notNull": true,
7442 + "default": "now()"
7443 + }
7444 + },
7445 + "indexes": {
7446 + "watchlist_items_uq": {
7447 + "name": "watchlist_items_uq",
7448 + "columns": [
7449 + {
7450 + "expression": "watchlist_id",
7451 + "isExpression": false,
7452 + "asc": true,
7453 + "nulls": "last"
7454 + },
7455 + {
7456 + "expression": "target_type",
7457 + "isExpression": false,
7458 + "asc": true,
7459 + "nulls": "last"
7460 + },
7461 + {
7462 + "expression": "target_id",
7463 + "isExpression": false,
7464 + "asc": true,
7465 + "nulls": "last"
7466 + }
7467 + ],
7468 + "isUnique": true,
7469 + "concurrently": false,
7470 + "method": "btree",
7471 + "with": {}
7472 + }
7473 + },
7474 + "foreignKeys": {},
7475 + "compositePrimaryKeys": {},
7476 + "uniqueConstraints": {},
7477 + "policies": {},
7478 + "checkConstraints": {},
7479 + "isRLSEnabled": false
7480 + },
7481 + "public.watchlists": {
7482 + "name": "watchlists",
7483 + "schema": "",
7484 + "columns": {
7485 + "id": {
7486 + "name": "id",
7487 + "type": "text",
7488 + "primaryKey": true,
7489 + "notNull": true
7490 + },
7491 + "user_id": {
7492 + "name": "user_id",
7493 + "type": "text",
7494 + "primaryKey": false,
7495 + "notNull": true
7496 + },
7497 + "name": {
7498 + "name": "name",
7499 + "type": "text",
7500 + "primaryKey": false,
7501 + "notNull": true,
7502 + "default": "'Watchlist'"
7503 + },
7504 + "created_at": {
7505 + "name": "created_at",
7506 + "type": "timestamp with time zone",
7507 + "primaryKey": false,
7508 + "notNull": true,
7509 + "default": "now()"
7510 + }
7511 + },
7512 + "indexes": {
7513 + "watchlists_user_idx": {
7514 + "name": "watchlists_user_idx",
7515 + "columns": [
7516 + {
7517 + "expression": "user_id",
7518 + "isExpression": false,
7519 + "asc": true,
7520 + "nulls": "last"
7521 + }
7522 + ],
7523 + "isUnique": false,
7524 + "concurrently": false,
7525 + "method": "btree",
7526 + "with": {}
7527 + }
7528 + },
7529 + "foreignKeys": {},
7530 + "compositePrimaryKeys": {},
7531 + "uniqueConstraints": {},
7532 + "policies": {},
7533 + "checkConstraints": {},
7534 + "isRLSEnabled": false
7535 + },
7536 + "public.ai_quotas": {
7537 + "name": "ai_quotas",
7538 + "schema": "",
7539 + "columns": {
7540 + "key": {
7541 + "name": "key",
7542 + "type": "text",
7543 + "primaryKey": false,
7544 + "notNull": true
7545 + },
7546 + "feature": {
7547 + "name": "feature",
7548 + "type": "text",
7549 + "primaryKey": false,
7550 + "notNull": true
7551 + },
7552 + "date": {
7553 + "name": "date",
7554 + "type": "text",
7555 + "primaryKey": false,
7556 + "notNull": true
7557 + },
7558 + "count": {
7559 + "name": "count",
7560 + "type": "integer",
7561 + "primaryKey": false,
7562 + "notNull": true,
7563 + "default": 0
7564 + }
7565 + },
7566 + "indexes": {
7567 + "ai_quotas_uq": {
7568 + "name": "ai_quotas_uq",
7569 + "columns": [
7570 + {
7571 + "expression": "key",
7572 + "isExpression": false,
7573 + "asc": true,
7574 + "nulls": "last"
7575 + },
7576 + {
7577 + "expression": "feature",
7578 + "isExpression": false,
7579 + "asc": true,
7580 + "nulls": "last"
7581 + },
7582 + {
7583 + "expression": "date",
7584 + "isExpression": false,
7585 + "asc": true,
7586 + "nulls": "last"
7587 + }
7588 + ],
7589 + "isUnique": true,
7590 + "concurrently": false,
7591 + "method": "btree",
7592 + "with": {}
7593 + }
7594 + },
7595 + "foreignKeys": {},
7596 + "compositePrimaryKeys": {},
7597 + "uniqueConstraints": {},
7598 + "policies": {},
7599 + "checkConstraints": {},
7600 + "isRLSEnabled": false
7601 + },
7602 + "public.research_messages": {
7603 + "name": "research_messages",
7604 + "schema": "",
7605 + "columns": {
7606 + "id": {
7607 + "name": "id",
7608 + "type": "text",
7609 + "primaryKey": true,
7610 + "notNull": true
7611 + },
7612 + "session_id": {
7613 + "name": "session_id",
7614 + "type": "text",
7615 + "primaryKey": false,
7616 + "notNull": true
7617 + },
7618 + "role": {
7619 + "name": "role",
7620 + "type": "text",
7621 + "primaryKey": false,
7622 + "notNull": true
7623 + },
7624 + "content": {
7625 + "name": "content",
7626 + "type": "text",
7627 + "primaryKey": false,
7628 + "notNull": true
7629 + },
7630 + "tool_calls": {
7631 + "name": "tool_calls",
7632 + "type": "jsonb",
7633 + "primaryKey": false,
7634 + "notNull": true,
7635 + "default": "'[]'::jsonb"
7636 + },
7637 + "usage": {
7638 + "name": "usage",
7639 + "type": "jsonb",
7640 + "primaryKey": false,
7641 + "notNull": true,
7642 + "default": "'{}'::jsonb"
7643 + },
7644 + "usd_est": {
7645 + "name": "usd_est",
7646 + "type": "real",
7647 + "primaryKey": false,
7648 + "notNull": true,
7649 + "default": 0
7650 + },
7651 + "model": {
7652 + "name": "model",
7653 + "type": "text",
7654 + "primaryKey": false,
7655 + "notNull": false
7656 + },
7657 + "created_at": {
7658 + "name": "created_at",
7659 + "type": "timestamp with time zone",
7660 + "primaryKey": false,
7661 + "notNull": true,
7662 + "default": "now()"
7663 + }
7664 + },
7665 + "indexes": {
7666 + "research_messages_session_idx": {
7667 + "name": "research_messages_session_idx",
7668 + "columns": [
7669 + {
7670 + "expression": "session_id",
7671 + "isExpression": false,
7672 + "asc": true,
7673 + "nulls": "last"
7674 + },
7675 + {
7676 + "expression": "created_at",
7677 + "isExpression": false,
7678 + "asc": true,
7679 + "nulls": "last"
7680 + }
7681 + ],
7682 + "isUnique": false,
7683 + "concurrently": false,
7684 + "method": "btree",
7685 + "with": {}
7686 + }
7687 + },
7688 + "foreignKeys": {},
7689 + "compositePrimaryKeys": {},
7690 + "uniqueConstraints": {},
7691 + "policies": {},
7692 + "checkConstraints": {},
7693 + "isRLSEnabled": false
7694 + },
7695 + "public.research_sessions": {
7696 + "name": "research_sessions",
7697 + "schema": "",
7698 + "columns": {
7699 + "id": {
7700 + "name": "id",
7701 + "type": "text",
7702 + "primaryKey": true,
7703 + "notNull": true
7704 + },
7705 + "user_id": {
7706 + "name": "user_id",
7707 + "type": "text",
7708 + "primaryKey": false,
7709 + "notNull": false
7710 + },
7711 + "anon_id": {
7712 + "name": "anon_id",
7713 + "type": "text",
7714 + "primaryKey": false,
7715 + "notNull": false
7716 + },
7717 + "title": {
7718 + "name": "title",
7719 + "type": "text",
7720 + "primaryKey": false,
7721 + "notNull": false
7722 + },
7723 + "model": {
7724 + "name": "model",
7725 + "type": "text",
7726 + "primaryKey": false,
7727 + "notNull": false
7728 + },
7729 + "message_count": {
7730 + "name": "message_count",
7731 + "type": "integer",
7732 + "primaryKey": false,
7733 + "notNull": true,
7734 + "default": 0
7735 + },
7736 + "usd_est": {
7737 + "name": "usd_est",
7738 + "type": "real",
7739 + "primaryKey": false,
7740 + "notNull": true,
7741 + "default": 0
7742 + },
7743 + "archived": {
7744 + "name": "archived",
7745 + "type": "boolean",
7746 + "primaryKey": false,
7747 + "notNull": true,
7748 + "default": false
7749 + },
7750 + "created_at": {
7751 + "name": "created_at",
7752 + "type": "timestamp with time zone",
7753 + "primaryKey": false,
7754 + "notNull": true,
7755 + "default": "now()"
7756 + },
7757 + "updated_at": {
7758 + "name": "updated_at",
7759 + "type": "timestamp with time zone",
7760 + "primaryKey": false,
7761 + "notNull": true,
7762 + "default": "now()"
7763 + }
7764 + },
7765 + "indexes": {
7766 + "research_sessions_anon_idx": {
7767 + "name": "research_sessions_anon_idx",
7768 + "columns": [
7769 + {
7770 + "expression": "anon_id",
7771 + "isExpression": false,
7772 + "asc": true,
7773 + "nulls": "last"
7774 + },
7775 + {
7776 + "expression": "updated_at",
7777 + "isExpression": false,
7778 + "asc": true,
7779 + "nulls": "last"
7780 + }
7781 + ],
7782 + "isUnique": false,
7783 + "concurrently": false,
7784 + "method": "btree",
7785 + "with": {}
7786 + },
7787 + "research_sessions_user_idx": {
7788 + "name": "research_sessions_user_idx",
7789 + "columns": [
7790 + {
7791 + "expression": "user_id",
7792 + "isExpression": false,
7793 + "asc": true,
7794 + "nulls": "last"
7795 + },
7796 + {
7797 + "expression": "updated_at",
7798 + "isExpression": false,
7799 + "asc": true,
7800 + "nulls": "last"
7801 + }
7802 + ],
7803 + "isUnique": false,
7804 + "concurrently": false,
7805 + "method": "btree",
7806 + "with": {}
7807 + }
7808 + },
7809 + "foreignKeys": {},
7810 + "compositePrimaryKeys": {},
7811 + "uniqueConstraints": {},
7812 + "policies": {},
7813 + "checkConstraints": {},
7814 + "isRLSEnabled": false
7815 + },
7816 + "public.scanner_sessions": {
7817 + "name": "scanner_sessions",
7818 + "schema": "",
7819 + "columns": {
7820 + "id": {
7821 + "name": "id",
7822 + "type": "text",
7823 + "primaryKey": true,
7824 + "notNull": true
7825 + },
7826 + "user_id": {
7827 + "name": "user_id",
7828 + "type": "text",
7829 + "primaryKey": false,
7830 + "notNull": false
7831 + },
7832 + "anon_id": {
7833 + "name": "anon_id",
7834 + "type": "text",
7835 + "primaryKey": false,
7836 + "notNull": false
7837 + },
7838 + "ip_hash": {
7839 + "name": "ip_hash",
7840 + "type": "text",
7841 + "primaryKey": false,
7842 + "notNull": false
7843 + },
7844 + "mode": {
7845 + "name": "mode",
7846 + "type": "text",
7847 + "primaryKey": false,
7848 + "notNull": true
7849 + },
7850 + "input_url": {
7851 + "name": "input_url",
7852 + "type": "text",
7853 + "primaryKey": false,
7854 + "notNull": false
7855 + },
7856 + "input_text": {
7857 + "name": "input_text",
7858 + "type": "text",
7859 + "primaryKey": false,
7860 + "notNull": false
7861 + },
7862 + "image_count": {
7863 + "name": "image_count",
7864 + "type": "integer",
7865 + "primaryKey": false,
7866 + "notNull": true,
7867 + "default": 0
7868 + },
7869 + "thumbnails": {
7870 + "name": "thumbnails",
7871 + "type": "jsonb",
7872 + "primaryKey": false,
7873 + "notNull": true,
7874 + "default": "'[]'::jsonb"
7875 + },
7876 + "guess": {
7877 + "name": "guess",
7878 + "type": "jsonb",
7879 + "primaryKey": false,
7880 + "notNull": true,
7881 + "default": "'{}'::jsonb"
7882 + },
7883 + "guess_confidence": {
7884 + "name": "guess_confidence",
7885 + "type": "real",
7886 + "primaryKey": false,
7887 + "notNull": false
7888 + },
7889 + "candidates": {
7890 + "name": "candidates",
7891 + "type": "jsonb",
7892 + "primaryKey": false,
7893 + "notNull": true,
7894 + "default": "'[]'::jsonb"
7895 + },
7896 + "chosen_asset_id": {
7897 + "name": "chosen_asset_id",
7898 + "type": "text",
7899 + "primaryKey": false,
7900 + "notNull": false
7901 + },
7902 + "listing": {
7903 + "name": "listing",
7904 + "type": "jsonb",
7905 + "primaryKey": false,
7906 + "notNull": true,
7907 + "default": "'{}'::jsonb"
7908 + },
7909 + "model": {
7910 + "name": "model",
7911 + "type": "text",
7912 + "primaryKey": false,
7913 + "notNull": false
7914 + },
7915 + "usd_est": {
7916 + "name": "usd_est",
7917 + "type": "real",
7918 + "primaryKey": false,
7919 + "notNull": true,
7920 + "default": 0
7921 + },
7922 + "duration_ms": {
7923 + "name": "duration_ms",
7924 + "type": "integer",
7925 + "primaryKey": false,
7926 + "notNull": false
7927 + },
7928 + "error": {
7929 + "name": "error",
7930 + "type": "text",
7931 + "primaryKey": false,
7932 + "notNull": false
7933 + },
7934 + "created_at": {
7935 + "name": "created_at",
7936 + "type": "timestamp with time zone",
7937 + "primaryKey": false,
7938 + "notNull": true,
7939 + "default": "now()"
7940 + },
7941 + "chosen_at": {
7942 + "name": "chosen_at",
7943 + "type": "timestamp with time zone",
7944 + "primaryKey": false,
7945 + "notNull": false
7946 + }
7947 + },
7948 + "indexes": {
7949 + "scanner_sessions_created_idx": {
7950 + "name": "scanner_sessions_created_idx",
7951 + "columns": [
7952 + {
7953 + "expression": "created_at",
7954 + "isExpression": false,
7955 + "asc": true,
7956 + "nulls": "last"
7957 + }
7958 + ],
7959 + "isUnique": false,
7960 + "concurrently": false,
7961 + "method": "btree",
7962 + "with": {}
7963 + },
7964 + "scanner_sessions_ip_idx": {
7965 + "name": "scanner_sessions_ip_idx",
7966 + "columns": [
7967 + {
7968 + "expression": "ip_hash",
7969 + "isExpression": false,
7970 + "asc": true,
7971 + "nulls": "last"
7972 + },
7973 + {
7974 + "expression": "created_at",
7975 + "isExpression": false,
7976 + "asc": true,
7977 + "nulls": "last"
7978 + }
7979 + ],
7980 + "isUnique": false,
7981 + "concurrently": false,
7982 + "method": "btree",
7983 + "with": {}
7984 + },
7985 + "scanner_sessions_user_idx": {
7986 + "name": "scanner_sessions_user_idx",
7987 + "columns": [
7988 + {
7989 + "expression": "user_id",
7990 + "isExpression": false,
7991 + "asc": true,
7992 + "nulls": "last"
7993 + }
7994 + ],
7995 + "isUnique": false,
7996 + "concurrently": false,
7997 + "method": "btree",
7998 + "with": {}
7999 + }
8000 + },
8001 + "foreignKeys": {},
8002 + "compositePrimaryKeys": {},
8003 + "uniqueConstraints": {},
8004 + "policies": {},
8005 + "checkConstraints": {},
8006 + "isRLSEnabled": false
8007 + },
8008 + "public.auth_codes": {
8009 + "name": "auth_codes",
8010 + "schema": "",
8011 + "columns": {
8012 + "id": {
8013 + "name": "id",
8014 + "type": "text",
8015 + "primaryKey": true,
8016 + "notNull": true
8017 + },
8018 + "user_id": {
8019 + "name": "user_id",
8020 + "type": "text",
8021 + "primaryKey": false,
8022 + "notNull": false
8023 + },
8024 + "email": {
8025 + "name": "email",
8026 + "type": "text",
8027 + "primaryKey": false,
8028 + "notNull": true
8029 + },
8030 + "purpose": {
8031 + "name": "purpose",
8032 + "type": "text",
8033 + "primaryKey": false,
8034 + "notNull": true
8035 + },
8036 + "code_hash": {
8037 + "name": "code_hash",
8038 + "type": "text",
8039 + "primaryKey": false,
8040 + "notNull": true
8041 + },
8042 + "expires_at": {
8043 + "name": "expires_at",
8044 + "type": "timestamp with time zone",
8045 + "primaryKey": false,
8046 + "notNull": true
8047 + },
8048 + "attempts": {
8049 + "name": "attempts",
8050 + "type": "integer",
8051 + "primaryKey": false,
8052 + "notNull": true,
8053 + "default": 0
8054 + },
8055 + "consumed_at": {
8056 + "name": "consumed_at",
8057 + "type": "timestamp with time zone",
8058 + "primaryKey": false,
8059 + "notNull": false
8060 + },
8061 + "payload": {
8062 + "name": "payload",
8063 + "type": "jsonb",
8064 + "primaryKey": false,
8065 + "notNull": true,
8066 + "default": "'{}'::jsonb"
8067 + },
8068 + "created_at": {
8069 + "name": "created_at",
8070 + "type": "timestamp with time zone",
8071 + "primaryKey": false,
8072 + "notNull": true,
8073 + "default": "now()"
8074 + }
8075 + },
8076 + "indexes": {
8077 + "auth_codes_lookup_idx": {
8078 + "name": "auth_codes_lookup_idx",
8079 + "columns": [
8080 + {
8081 + "expression": "email",
8082 + "isExpression": false,
8083 + "asc": true,
8084 + "nulls": "last"
8085 + },
8086 + {
8087 + "expression": "purpose",
8088 + "isExpression": false,
8089 + "asc": true,
8090 + "nulls": "last"
8091 + },
8092 + {
8093 + "expression": "created_at",
8094 + "isExpression": false,
8095 + "asc": true,
8096 + "nulls": "last"
8097 + }
8098 + ],
8099 + "isUnique": false,
8100 + "concurrently": false,
8101 + "method": "btree",
8102 + "with": {}
8103 + }
8104 + },
8105 + "foreignKeys": {},
8106 + "compositePrimaryKeys": {},
8107 + "uniqueConstraints": {},
8108 + "policies": {},
8109 + "checkConstraints": {},
8110 + "isRLSEnabled": false
8111 + },
8112 + "public.login_events": {
8113 + "name": "login_events",
8114 + "schema": "",
8115 + "columns": {
8116 + "id": {
8117 + "name": "id",
8118 + "type": "text",
8119 + "primaryKey": true,
8120 + "notNull": true
8121 + },
8122 + "user_id": {
8123 + "name": "user_id",
8124 + "type": "text",
8125 + "primaryKey": false,
8126 + "notNull": false
8127 + },
8128 + "email": {
8129 + "name": "email",
8130 + "type": "text",
8131 + "primaryKey": false,
8132 + "notNull": false
8133 + },
8134 + "ip": {
8135 + "name": "ip",
8136 + "type": "text",
8137 + "primaryKey": false,
8138 + "notNull": false
8139 + },
8140 + "user_agent": {
8141 + "name": "user_agent",
8142 + "type": "text",
8143 + "primaryKey": false,
8144 + "notNull": false
8145 + },
8146 + "outcome": {
8147 + "name": "outcome",
8148 + "type": "text",
8149 + "primaryKey": false,
8150 + "notNull": true
8151 + },
8152 + "method": {
8153 + "name": "method",
8154 + "type": "text",
8155 + "primaryKey": false,
8156 + "notNull": false
8157 + },
8158 + "created_at": {
8159 + "name": "created_at",
8160 + "type": "timestamp with time zone",
8161 + "primaryKey": false,
8162 + "notNull": true,
8163 + "default": "now()"
8164 + }
8165 + },
8166 + "indexes": {
8167 + "login_events_user_idx": {
8168 + "name": "login_events_user_idx",
8169 + "columns": [
8170 + {
8171 + "expression": "user_id",
8172 + "isExpression": false,
8173 + "asc": true,
8174 + "nulls": "last"
8175 + },
8176 + {
8177 + "expression": "created_at",
8178 + "isExpression": false,
8179 + "asc": true,
8180 + "nulls": "last"
8181 + }
8182 + ],
8183 + "isUnique": false,
8184 + "concurrently": false,
8185 + "method": "btree",
8186 + "with": {}
8187 + }
8188 + },
8189 + "foreignKeys": {},
8190 + "compositePrimaryKeys": {},
8191 + "uniqueConstraints": {},
8192 + "policies": {},
8193 + "checkConstraints": {},
8194 + "isRLSEnabled": false
8195 + },
8196 + "public.notifications": {
8197 + "name": "notifications",
8198 + "schema": "",
8199 + "columns": {
8200 + "id": {
8201 + "name": "id",
8202 + "type": "text",
8203 + "primaryKey": true,
8204 + "notNull": true
8205 + },
8206 + "user_id": {
8207 + "name": "user_id",
8208 + "type": "text",
8209 + "primaryKey": false,
8210 + "notNull": true
8211 + },
8212 + "kind": {
8213 + "name": "kind",
8214 + "type": "text",
8215 + "primaryKey": false,
8216 + "notNull": true
8217 + },
8218 + "title": {
8219 + "name": "title",
8220 + "type": "text",
8221 + "primaryKey": false,
8222 + "notNull": true
8223 + },
8224 + "body": {
8225 + "name": "body",
8226 + "type": "text",
8227 + "primaryKey": false,
8228 + "notNull": false
8229 + },
8230 + "href": {
8231 + "name": "href",
8232 + "type": "text",
8233 + "primaryKey": false,
8234 + "notNull": false
8235 + },
8236 + "payload": {
8237 + "name": "payload",
8238 + "type": "jsonb",
8239 + "primaryKey": false,
8240 + "notNull": true,
8241 + "default": "'{}'::jsonb"
8242 + },
8243 + "read_at": {
8244 + "name": "read_at",
8245 + "type": "timestamp with time zone",
8246 + "primaryKey": false,
8247 + "notNull": false
8248 + },
8249 + "emailed_at": {
8250 + "name": "emailed_at",
8251 + "type": "timestamp with time zone",
8252 + "primaryKey": false,
8253 + "notNull": false
8254 + },
8255 + "created_at": {
8256 + "name": "created_at",
8257 + "type": "timestamp with time zone",
8258 + "primaryKey": false,
8259 + "notNull": true,
8260 + "default": "now()"
8261 + }
8262 + },
8263 + "indexes": {
8264 + "notifications_user_idx": {
8265 + "name": "notifications_user_idx",
8266 + "columns": [
8267 + {
8268 + "expression": "user_id",
8269 + "isExpression": false,
8270 + "asc": true,
8271 + "nulls": "last"
8272 + },
8273 + {
8274 + "expression": "created_at",
8275 + "isExpression": false,
8276 + "asc": true,
8277 + "nulls": "last"
8278 + }
8279 + ],
8280 + "isUnique": false,
8281 + "concurrently": false,
8282 + "method": "btree",
8283 + "with": {}
8284 + },
8285 + "notifications_unread_idx": {
8286 + "name": "notifications_unread_idx",
8287 + "columns": [
8288 + {
8289 + "expression": "user_id",
8290 + "isExpression": false,
8291 + "asc": true,
8292 + "nulls": "last"
8293 + },
8294 + {
8295 + "expression": "read_at",
8296 + "isExpression": false,
8297 + "asc": true,
8298 + "nulls": "last"
8299 + }
8300 + ],
8301 + "isUnique": false,
8302 + "concurrently": false,
8303 + "method": "btree",
8304 + "with": {}
8305 + }
8306 + },
8307 + "foreignKeys": {},
8308 + "compositePrimaryKeys": {},
8309 + "uniqueConstraints": {},
8310 + "policies": {},
8311 + "checkConstraints": {},
8312 + "isRLSEnabled": false
8313 + },
8314 + "public.price_targets": {
8315 + "name": "price_targets",
8316 + "schema": "",
8317 + "columns": {
8318 + "id": {
8319 + "name": "id",
8320 + "type": "text",
8321 + "primaryKey": true,
8322 + "notNull": true
8323 + },
8324 + "user_id": {
8325 + "name": "user_id",
8326 + "type": "text",
8327 + "primaryKey": false,
8328 + "notNull": true
8329 + },
8330 + "asset_id": {
8331 + "name": "asset_id",
8332 + "type": "text",
8333 + "primaryKey": false,
8334 + "notNull": true
8335 + },
8336 + "variant_id": {
8337 + "name": "variant_id",
8338 + "type": "text",
8339 + "primaryKey": false,
8340 + "notNull": false
8341 + },
8342 + "direction": {
8343 + "name": "direction",
8344 + "type": "text",
8345 + "primaryKey": false,
8346 + "notNull": true,
8347 + "default": "'above'"
8348 + },
8349 + "target_usd": {
8350 + "name": "target_usd",
8351 + "type": "numeric(18, 4)",
8352 + "primaryKey": false,
8353 + "notNull": true
8354 + },
8355 + "baseline_usd": {
8356 + "name": "baseline_usd",
8357 + "type": "numeric(18, 4)",
8358 + "primaryKey": false,
8359 + "notNull": false
8360 + },
8361 + "note": {
8362 + "name": "note",
8363 + "type": "text",
8364 + "primaryKey": false,
8365 + "notNull": false
8366 + },
8367 + "hit_at": {
8368 + "name": "hit_at",
8369 + "type": "timestamp with time zone",
8370 + "primaryKey": false,
8371 + "notNull": false
8372 + },
8373 + "notified_at": {
8374 + "name": "notified_at",
8375 + "type": "timestamp with time zone",
8376 + "primaryKey": false,
8377 + "notNull": false
8378 + },
8379 + "created_at": {
8380 + "name": "created_at",
8381 + "type": "timestamp with time zone",
8382 + "primaryKey": false,
8383 + "notNull": true,
8384 + "default": "now()"
8385 + }
8386 + },
8387 + "indexes": {
8388 + "price_targets_user_idx": {
8389 + "name": "price_targets_user_idx",
8390 + "columns": [
8391 + {
8392 + "expression": "user_id",
8393 + "isExpression": false,
8394 + "asc": true,
8395 + "nulls": "last"
8396 + }
8397 + ],
8398 + "isUnique": false,
8399 + "concurrently": false,
8400 + "method": "btree",
8401 + "with": {}
8402 + },
8403 + "price_targets_asset_idx": {
8404 + "name": "price_targets_asset_idx",
8405 + "columns": [
8406 + {
8407 + "expression": "asset_id",
8408 + "isExpression": false,
8409 + "asc": true,
8410 + "nulls": "last"
8411 + },
8412 + {
8413 + "expression": "hit_at",
8414 + "isExpression": false,
8415 + "asc": true,
8416 + "nulls": "last"
8417 + }
8418 + ],
8419 + "isUnique": false,
8420 + "concurrently": false,
8421 + "method": "btree",
8422 + "with": {}
8423 + }
8424 + },
8425 + "foreignKeys": {},
8426 + "compositePrimaryKeys": {},
8427 + "uniqueConstraints": {},
8428 + "policies": {},
8429 + "checkConstraints": {},
8430 + "isRLSEnabled": false
8431 + },
8432 + "public.rate_limits": {
8433 + "name": "rate_limits",
8434 + "schema": "",
8435 + "columns": {
8436 + "key": {
8437 + "name": "key",
8438 + "type": "text",
8439 + "primaryKey": true,
8440 + "notNull": true
8441 + },
8442 + "count": {
8443 + "name": "count",
8444 + "type": "integer",
8445 + "primaryKey": false,
8446 + "notNull": true,
8447 + "default": 0
8448 + },
8449 + "reset_at": {
8450 + "name": "reset_at",
8451 + "type": "timestamp with time zone",
8452 + "primaryKey": false,
8453 + "notNull": true
8454 + }
8455 + },
8456 + "indexes": {},
8457 + "foreignKeys": {},
8458 + "compositePrimaryKeys": {},
8459 + "uniqueConstraints": {},
8460 + "policies": {},
8461 + "checkConstraints": {},
8462 + "isRLSEnabled": false
8463 + },
8464 + "public.recovery_codes": {
8465 + "name": "recovery_codes",
8466 + "schema": "",
8467 + "columns": {
8468 + "id": {
8469 + "name": "id",
8470 + "type": "text",
8471 + "primaryKey": true,
8472 + "notNull": true
8473 + },
8474 + "user_id": {
8475 + "name": "user_id",
8476 + "type": "text",
8477 + "primaryKey": false,
8478 + "notNull": true
8479 + },
8480 + "code_hash": {
8481 + "name": "code_hash",
8482 + "type": "text",
8483 + "primaryKey": false,
8484 + "notNull": true
8485 + },
8486 + "used_at": {
8487 + "name": "used_at",
8488 + "type": "timestamp with time zone",
8489 + "primaryKey": false,
8490 + "notNull": false
8491 + },
8492 + "created_at": {
8493 + "name": "created_at",
8494 + "type": "timestamp with time zone",
8495 + "primaryKey": false,
8496 + "notNull": true,
8497 + "default": "now()"
8498 + }
8499 + },
8500 + "indexes": {
8501 + "recovery_codes_user_idx": {
8502 + "name": "recovery_codes_user_idx",
8503 + "columns": [
8504 + {
8505 + "expression": "user_id",
8506 + "isExpression": false,
8507 + "asc": true,
8508 + "nulls": "last"
8509 + }
8510 + ],
8511 + "isUnique": false,
8512 + "concurrently": false,
8513 + "method": "btree",
8514 + "with": {}
8515 + }
8516 + },
8517 + "foreignKeys": {},
8518 + "compositePrimaryKeys": {},
8519 + "uniqueConstraints": {},
8520 + "policies": {},
8521 + "checkConstraints": {},
8522 + "isRLSEnabled": false
8523 + },
8524 + "public.saved_searches": {
8525 + "name": "saved_searches",
8526 + "schema": "",
8527 + "columns": {
8528 + "id": {
8529 + "name": "id",
8530 + "type": "text",
8531 + "primaryKey": true,
8532 + "notNull": true
8533 + },
8534 + "user_id": {
8535 + "name": "user_id",
8536 + "type": "text",
8537 + "primaryKey": false,
8538 + "notNull": true
8539 + },
8540 + "name": {
8541 + "name": "name",
8542 + "type": "text",
8543 + "primaryKey": false,
8544 + "notNull": true
8545 + },
8546 + "url": {
8547 + "name": "url",
8548 + "type": "text",
8549 + "primaryKey": false,
8550 + "notNull": true
8551 + },
8552 + "params": {
8553 + "name": "params",
8554 + "type": "jsonb",
8555 + "primaryKey": false,
8556 + "notNull": true,
8557 + "default": "'{}'::jsonb"
8558 + },
8559 + "notify": {
8560 + "name": "notify",
8561 + "type": "boolean",
8562 + "primaryKey": false,
8563 + "notNull": true,
8564 + "default": false
8565 + },
8566 + "last_run_at": {
8567 + "name": "last_run_at",
8568 + "type": "timestamp with time zone",
8569 + "primaryKey": false,
8570 + "notNull": false
8571 + },
8572 + "last_count": {
8573 + "name": "last_count",
8574 + "type": "integer",
8575 + "primaryKey": false,
8576 + "notNull": false
8577 + },
8578 + "created_at": {
8579 + "name": "created_at",
8580 + "type": "timestamp with time zone",
8581 + "primaryKey": false,
8582 + "notNull": true,
8583 + "default": "now()"
8584 + }
8585 + },
8586 + "indexes": {
8587 + "saved_searches_user_idx": {
8588 + "name": "saved_searches_user_idx",
8589 + "columns": [
8590 + {
8591 + "expression": "user_id",
8592 + "isExpression": false,
8593 + "asc": true,
8594 + "nulls": "last"
8595 + }
8596 + ],
8597 + "isUnique": false,
8598 + "concurrently": false,
8599 + "method": "btree",
8600 + "with": {}
8601 + }
8602 + },
8603 + "foreignKeys": {},
8604 + "compositePrimaryKeys": {},
8605 + "uniqueConstraints": {},
8606 + "policies": {},
8607 + "checkConstraints": {},
8608 + "isRLSEnabled": false
8609 + },
8610 + "public.trusted_devices": {
8611 + "name": "trusted_devices",
8612 + "schema": "",
8613 + "columns": {
8614 + "id": {
8615 + "name": "id",
8616 + "type": "text",
8617 + "primaryKey": true,
8618 + "notNull": true
8619 + },
8620 + "user_id": {
8621 + "name": "user_id",
8622 + "type": "text",
8623 + "primaryKey": false,
8624 + "notNull": true
8625 + },
8626 + "token_hash": {
8627 + "name": "token_hash",
8628 + "type": "text",
8629 + "primaryKey": false,
8630 + "notNull": true
8631 + },
8632 + "label": {
8633 + "name": "label",
8634 + "type": "text",
8635 + "primaryKey": false,
8636 + "notNull": false
8637 + },
8638 + "user_agent": {
8639 + "name": "user_agent",
8640 + "type": "text",
8641 + "primaryKey": false,
8642 + "notNull": false
8643 + },
8644 + "ip": {
8645 + "name": "ip",
8646 + "type": "text",
8647 + "primaryKey": false,
8648 + "notNull": false
8649 + },
8650 + "created_at": {
8651 + "name": "created_at",
8652 + "type": "timestamp with time zone",
8653 + "primaryKey": false,
8654 + "notNull": true,
8655 + "default": "now()"
8656 + },
8657 + "last_used_at": {
8658 + "name": "last_used_at",
8659 + "type": "timestamp with time zone",
8660 + "primaryKey": false,
8661 + "notNull": false
8662 + },
8663 + "expires_at": {
8664 + "name": "expires_at",
8665 + "type": "timestamp with time zone",
8666 + "primaryKey": false,
8667 + "notNull": true
8668 + },
8669 + "revoked_at": {
8670 + "name": "revoked_at",
8671 + "type": "timestamp with time zone",
8672 + "primaryKey": false,
8673 + "notNull": false
8674 + }
8675 + },
8676 + "indexes": {
8677 + "trusted_devices_user_idx": {
8678 + "name": "trusted_devices_user_idx",
8679 + "columns": [
8680 + {
8681 + "expression": "user_id",
8682 + "isExpression": false,
8683 + "asc": true,
8684 + "nulls": "last"
8685 + }
8686 + ],
8687 + "isUnique": false,
8688 + "concurrently": false,
8689 + "method": "btree",
8690 + "with": {}
8691 + },
8692 + "trusted_devices_token_uq": {
8693 + "name": "trusted_devices_token_uq",
8694 + "columns": [
8695 + {
8696 + "expression": "token_hash",
8697 + "isExpression": false,
8698 + "asc": true,
8699 + "nulls": "last"
8700 + }
8701 + ],
8702 + "isUnique": true,
8703 + "concurrently": false,
8704 + "method": "btree",
8705 + "with": {}
8706 + }
8707 + },
8708 + "foreignKeys": {},
8709 + "compositePrimaryKeys": {},
8710 + "uniqueConstraints": {},
8711 + "policies": {},
8712 + "checkConstraints": {},
8713 + "isRLSEnabled": false
8714 + },
8715 + "public.uploads": {
8716 + "name": "uploads",
8717 + "schema": "",
8718 + "columns": {
8719 + "id": {
8720 + "name": "id",
8721 + "type": "text",
8722 + "primaryKey": true,
8723 + "notNull": true
8724 + },
8725 + "user_id": {
8726 + "name": "user_id",
8727 + "type": "text",
8728 + "primaryKey": false,
8729 + "notNull": true
8730 + },
8731 + "kind": {
8732 + "name": "kind",
8733 + "type": "text",
8734 + "primaryKey": false,
8735 + "notNull": true
8736 + },
8737 + "path": {
8738 + "name": "path",
8739 + "type": "text",
8740 + "primaryKey": false,
8741 + "notNull": true
8742 + },
8743 + "mime": {
8744 + "name": "mime",
8745 + "type": "text",
8746 + "primaryKey": false,
8747 + "notNull": true
8748 + },
8749 + "bytes": {
8750 + "name": "bytes",
8751 + "type": "integer",
8752 + "primaryKey": false,
8753 + "notNull": true
8754 + },
8755 + "width": {
8756 + "name": "width",
8757 + "type": "integer",
8758 + "primaryKey": false,
8759 + "notNull": false
8760 + },
8761 + "height": {
8762 + "name": "height",
8763 + "type": "integer",
8764 + "primaryKey": false,
8765 + "notNull": false
8766 + },
8767 + "created_at": {
8768 + "name": "created_at",
8769 + "type": "timestamp with time zone",
8770 + "primaryKey": false,
8771 + "notNull": true,
8772 + "default": "now()"
8773 + }
8774 + },
8775 + "indexes": {
8776 + "uploads_user_idx": {
8777 + "name": "uploads_user_idx",
8778 + "columns": [
8779 + {
8780 + "expression": "user_id",
8781 + "isExpression": false,
8782 + "asc": true,
8783 + "nulls": "last"
8784 + }
8785 + ],
8786 + "isUnique": false,
8787 + "concurrently": false,
8788 + "method": "btree",
8789 + "with": {}
8790 + }
8791 + },
8792 + "foreignKeys": {},
8793 + "compositePrimaryKeys": {},
8794 + "uniqueConstraints": {},
8795 + "policies": {},
8796 + "checkConstraints": {},
8797 + "isRLSEnabled": false
8798 + },
8799 + "public.user_badges": {
8800 + "name": "user_badges",
8801 + "schema": "",
8802 + "columns": {
8803 + "user_id": {
8804 + "name": "user_id",
8805 + "type": "text",
8806 + "primaryKey": false,
8807 + "notNull": true
8808 + },
8809 + "badge": {
8810 + "name": "badge",
8811 + "type": "text",
8812 + "primaryKey": false,
8813 + "notNull": true
8814 + },
8815 + "evidence": {
8816 + "name": "evidence",
8817 + "type": "jsonb",
8818 + "primaryKey": false,
8819 + "notNull": true,
8820 + "default": "'{}'::jsonb"
8821 + },
8822 + "created_at": {
8823 + "name": "created_at",
8824 + "type": "timestamp with time zone",
8825 + "primaryKey": false,
8826 + "notNull": true,
8827 + "default": "now()"
8828 + }
8829 + },
8830 + "indexes": {
8831 + "user_badges_uq": {
8832 + "name": "user_badges_uq",
8833 + "columns": [
8834 + {
8835 + "expression": "user_id",
8836 + "isExpression": false,
8837 + "asc": true,
8838 + "nulls": "last"
8839 + },
8840 + {
8841 + "expression": "badge",
8842 + "isExpression": false,
8843 + "asc": true,
8844 + "nulls": "last"
8845 + }
8846 + ],
8847 + "isUnique": true,
8848 + "concurrently": false,
8849 + "method": "btree",
8850 + "with": {}
8851 + }
8852 + },
8853 + "foreignKeys": {},
8854 + "compositePrimaryKeys": {},
8855 + "uniqueConstraints": {},
8856 + "policies": {},
8857 + "checkConstraints": {},
8858 + "isRLSEnabled": false
8859 + }
8860 + },
8861 + "enums": {},
8862 + "schemas": {},
8863 + "sequences": {},
8864 + "roles": {},
8865 + "policies": {},
8866 + "views": {
8867 + "public.listings_multi_currency": {
8868 + "columns": {
8869 + "listing_id": {
8870 + "name": "listing_id",
8871 + "type": "text",
8872 + "primaryKey": false,
8873 + "notNull": false
8874 + },
8875 + "asset_id": {
8876 + "name": "asset_id",
8877 + "type": "text",
8878 + "primaryKey": false,
8879 + "notNull": false
8880 + },
8881 + "last_seen_at": {
8882 + "name": "last_seen_at",
8883 + "type": "timestamp with time zone",
8884 + "primaryKey": false,
8885 + "notNull": false
8886 + },
8887 + "price": {
8888 + "name": "price",
8889 + "type": "numeric(18, 4)",
8890 + "primaryKey": false,
8891 + "notNull": false
8892 + },
8893 + "currency": {
8894 + "name": "currency",
8895 + "type": "text",
8896 + "primaryKey": false,
8897 + "notNull": false
8898 + },
8899 + "price_usd": {
8900 + "name": "price_usd",
8901 + "type": "numeric(18, 4)",
8902 + "primaryKey": false,
8903 + "notNull": false
8904 + },
8905 + "price_cad": {
8906 + "name": "price_cad",
8907 + "type": "numeric(18, 4)",
8908 + "primaryKey": false,
8909 + "notNull": false
8910 + },
8911 + "price_eur": {
8912 + "name": "price_eur",
8913 + "type": "numeric(18, 4)",
8914 + "primaryKey": false,
8915 + "notNull": false
8916 + },
8917 + "price_gbp": {
8918 + "name": "price_gbp",
8919 + "type": "numeric(18, 4)",
8920 + "primaryKey": false,
8921 + "notNull": false
8922 + },
8923 + "price_jpy": {
8924 + "name": "price_jpy",
8925 + "type": "numeric(18, 4)",
8926 + "primaryKey": false,
8927 + "notNull": false
8928 + }
8929 + },
8930 + "definition": "\n select l.id as listing_id, l.asset_id, l.last_seen_at, l.price, l.currency, l.price_usd,\n l.price_usd * cad.rate as price_cad, l.price_usd * eur.rate as price_eur, l.price_usd * gbp.rate as price_gbp, l.price_usd * jpy.rate as price_jpy\n from listings l\n left join lateral (select f.rate from fx_rates f where f.base = 'USD' and f.quote = 'CAD' and f.date <= l.last_seen_at::date order by f.date desc limit 1) cad on true\n left join lateral (select f.rate from fx_rates f where f.base = 'USD' and f.quote = 'EUR' and f.date <= l.last_seen_at::date order by f.date desc limit 1) eur on true\n left join lateral (select f.rate from fx_rates f where f.base = 'USD' and f.quote = 'GBP' and f.date <= l.last_seen_at::date order by f.date desc limit 1) gbp on true\n left join lateral (select f.rate from fx_rates f where f.base = 'USD' and f.quote = 'JPY' and f.date <= l.last_seen_at::date order by f.date desc limit 1) jpy on true\n",
8931 + "name": "listings_multi_currency",
8932 + "schema": "public",
8933 + "isExisting": false,
8934 + "materialized": false
8935 + },
8936 + "public.sales_multi_currency": {
8937 + "columns": {
8938 + "sale_id": {
8939 + "name": "sale_id",
8940 + "type": "text",
8941 + "primaryKey": false,
8942 + "notNull": false
8943 + },
8944 + "asset_id": {
8945 + "name": "asset_id",
8946 + "type": "text",
8947 + "primaryKey": false,
8948 + "notNull": false
8949 + },
8950 + "sale_date": {
8951 + "name": "sale_date",
8952 + "type": "timestamp with time zone",
8953 + "primaryKey": false,
8954 + "notNull": false
8955 + },
8956 + "price": {
8957 + "name": "price",
8958 + "type": "numeric(18, 4)",
8959 + "primaryKey": false,
8960 + "notNull": false
8961 + },
8962 + "currency": {
8963 + "name": "currency",
8964 + "type": "text",
8965 + "primaryKey": false,
8966 + "notNull": false
8967 + },
8968 + "price_usd": {
8969 + "name": "price_usd",
8970 + "type": "numeric(18, 4)",
8971 + "primaryKey": false,
8972 + "notNull": false
8973 + },
8974 + "price_cad": {
8975 + "name": "price_cad",
8976 + "type": "numeric(18, 4)",
8977 + "primaryKey": false,
8978 + "notNull": false
8979 + },
8980 + "price_eur": {
8981 + "name": "price_eur",
8982 + "type": "numeric(18, 4)",
8983 + "primaryKey": false,
8984 + "notNull": false
8985 + },
8986 + "price_gbp": {
8987 + "name": "price_gbp",
8988 + "type": "numeric(18, 4)",
8989 + "primaryKey": false,
8990 + "notNull": false
8991 + },
8992 + "price_jpy": {
8993 + "name": "price_jpy",
8994 + "type": "numeric(18, 4)",
8995 + "primaryKey": false,
8996 + "notNull": false
8997 + },
8998 + "fx_date_cad": {
8999 + "name": "fx_date_cad",
9000 + "type": "date",
9001 + "primaryKey": false,
9002 + "notNull": false
9003 + },
9004 + "fx_date_eur": {
9005 + "name": "fx_date_eur",
9006 + "type": "date",
9007 + "primaryKey": false,
9008 + "notNull": false
9009 + },
9010 + "fx_date_gbp": {
9011 + "name": "fx_date_gbp",
9012 + "type": "date",
9013 + "primaryKey": false,
9014 + "notNull": false
9015 + },
9016 + "fx_date_jpy": {
9017 + "name": "fx_date_jpy",
9018 + "type": "date",
9019 + "primaryKey": false,
9020 + "notNull": false
9021 + }
9022 + },
9023 + "definition": "\n select s.id as sale_id, s.asset_id, s.sale_date, s.price, s.currency, s.price_usd,\n s.price_usd * cad.rate as price_cad, s.price_usd * eur.rate as price_eur, s.price_usd * gbp.rate as price_gbp, s.price_usd * jpy.rate as price_jpy,\n cad.date as fx_date_cad, eur.date as fx_date_eur, gbp.date as fx_date_gbp, jpy.date as fx_date_jpy\n from sales s\n left join lateral (select f.rate, f.date from fx_rates f where f.base = 'USD' and f.quote = 'CAD' and f.date <= s.sale_date::date order by f.date desc limit 1) cad on true\n left join lateral (select f.rate, f.date from fx_rates f where f.base = 'USD' and f.quote = 'EUR' and f.date <= s.sale_date::date order by f.date desc limit 1) eur on true\n left join lateral (select f.rate, f.date from fx_rates f where f.base = 'USD' and f.quote = 'GBP' and f.date <= s.sale_date::date order by f.date desc limit 1) gbp on true\n left join lateral (select f.rate, f.date from fx_rates f where f.base = 'USD' and f.quote = 'JPY' and f.date <= s.sale_date::date order by f.date desc limit 1) jpy on true\n",
9024 + "name": "sales_multi_currency",
9025 + "schema": "public",
9026 + "isExisting": false,
9027 + "materialized": false
9028 + }
9029 + },
9030 + "_meta": {
9031 + "columns": {},
9032 + "schemas": {},
9033 + "tables": {}
9034 + }
9035 +}
\ No newline at end of file
modified packages/database/migrations/meta/_journal.json +7 −0
@@ -43,6 +43,13 @@
43 43 "when": 1788855117528,
44 44 "tag": "0005_watery_weapon_omega",
45 45 "breakpoints": true
46 + },
47 + {
48 + "idx": 6,
49 + "version": "7",
50 + "when": 1789101341333,
51 + "tag": "0006_skinny_kulan_gath",
52 + "breakpoints": true
46 53 }
47 54 ]
48 55 }
\ No newline at end of file
modified packages/database/src/schema/ai.ts +2 −2
@@ -1,4 +1,4 @@
1 −import { pgTable, text, integer, real, index, jsonb, boolean } from 'drizzle-orm/pg-core';
1 +import { pgTable, text, integer, real, index, uniqueIndex, jsonb, boolean } from 'drizzle-orm/pg-core';
2 2 import { createdAt, ts, jsonObject } from './_common.js';
3 3
4 4 /**
@@ -78,5 +78,5 @@ export const aiQuotas = pgTable(
78 78 date: text('date').notNull(),
79 79 count: integer('count').notNull().default(0),
80 80 },
81 − (t) => [index('ai_quotas_uq').on(t.key, t.feature, t.date)],
81 + (t) => [uniqueIndex('ai_quotas_uq').on(t.key, t.feature, t.date)],
82 82 );
modified packages/database/src/schema/assets.ts +12 −2
@@ -133,7 +133,17 @@ export const assetStats = pgTable(
133 133 views30d: integer('views_30d').notNull().default(0),
134 134 updatedAt: updatedAt(),
135 135 },
136 − (t) => [index('asset_stats_riv_idx').on(t.rivUsd), index('asset_stats_trending_idx').on(t.trendingScore), index('asset_stats_liquidity_idx').on(t.liquidityScore)],
136 + (t) => [
137 + index('asset_stats_riv_idx').on(t.rivUsd),
138 + index('asset_stats_trending_idx').on(t.trendingScore),
139 + index('asset_stats_liquidity_idx').on(t.liquidityScore),
140 + // movers, screener and opportunity rails order by these
141 + index('asset_stats_change_7d_idx').on(t.change7d),
142 + index('asset_stats_change_30d_idx').on(t.change30d),
143 + index('asset_stats_change_1y_idx').on(t.change1y),
144 + index('asset_stats_opportunity_idx').on(t.valueOpportunity).where(sql`value_opportunity is not null`),
145 + index('asset_stats_sales_30d_idx').on(t.sales30d),
146 + ],
137 147 );
138 148
139 149 export const variantStats = pgTable('variant_stats', {
@@ -154,7 +164,7 @@ export const variantStats = pgTable('variant_stats', {
154 164 minAskUsd: money('min_ask_usd'),
155 165 liquidityScore: real('liquidity_score'),
156 166 updatedAt: updatedAt(),
157 −});
167 +}, (t) => [index('variant_stats_asset_idx').on(t.assetId)]);
158 168
159 169 export const images = pgTable(
160 170 'images',
modified packages/database/src/schema/market.ts +5 −0
@@ -49,6 +49,8 @@ export const sales = pgTable(
49 49 index('sales_source_idx').on(t.sourceId, t.saleDate),
50 50 index('sales_date_idx').on(t.saleDate),
51 51 index('sales_price_idx').on(t.priceUsd),
52 + // every market query filters status = 'valid' with a date/price order
53 + index('sales_valid_date_idx').on(t.saleDate).where(sql`status = 'valid'`),
52 54 ],
53 55 );
54 56
@@ -102,6 +104,9 @@ export const listings = pgTable(
102 104 index('listings_avail_price_idx').on(t.availability, t.priceUsd),
103 105 index('listings_ends_idx').on(t.endsAt),
104 106 index('listings_last_seen_idx').on(t.lastSeenAt),
107 + // valuation worker updates asks per variant; deal rails / Deal Radar order by discount
108 + index('listings_variant_avail_idx').on(t.variantId, t.availability),
109 + index('listings_discount_idx').on(t.discountToRiv).where(sql`discount_to_riv is not null and availability = 'available'`),
105 110 ],
106 111 );
107 112
modified packages/taxonomy/src/index.ts +18 −6
@@ -153,23 +153,31 @@ export function getGrader(slugOrAlias: string | null | undefined): GraderDef | u
153 153 return graderByAlias.get(slugOrAlias.trim().toLowerCase());
154 154 }
155 155
156 −const GRADE_RE = /\b(PSA|BGS|BECKETT|CGC|SGC|TAG|ACE|CBCS|PGX|WATA|VGA|PCGS|NGC|ANACS|ICCS|PMG)\b[\s:-]*(?:GEM\s*MT|GEM\s*MINT|MINT|NM-?MT|PRISTINE|BLACK\s*LABEL)?[\s:-]*(\d{1,3}(?:\.\d)?)?/gi;
156 +// Grader token, optional "/DNA" (PSA/DNA autograph slabs), optional descriptive word, then either a
157 +// numeric grade ("10", "9.5", "(8.5)") or an authentication-only marker ("Authentic", "AUTH", "A").
158 +const GRADE_RE = /\b(PSA|BGS|BECKETT|CGC|SGC|TAG|ACE|CBCS|PGX|WATA|VGA|PCGS|NGC|ANACS|ICCS|PMG)\b(?:\s*\/?\s*DNA\b)?[\s:\-\[(]*(?:GEM\s*MT|GEM\s*MINT|MINT|NM-?MT|PRISTINE|BLACK\s*LABEL)?[\s:\-\[(]*(?:(\d{1,3}(?:\.\d)?)(?![\d.])|\b(AUTHENTIC|AUTH|CERTIFIED\s+AUTHENTIC)\b)?/gi;
157 159 /** Grader tokens that are also ordinary words ("Tag Team", "Ace Attorney") — only trusted when followed by a grade. */
158 160 const AMBIGUOUS_GRADERS = new Set(['tag', 'ace']);
161 +/** Authentication-only slabs (no numeric grade). Kept apart from raw and from graded variants. */
162 +export const AUTHENTIC_GRADE = 'authentic';
159 163
160 −/** Extract grader + grade from a free-text title. Returns nulls when absent. */
164 +/**
165 + * Extract grader + grade from a free-text title. Returns nulls when absent.
166 + * A grader token WITHOUT a readable grade yields `{ grader, grade: null }` — callers must treat that as
167 + * "graded, grade unknown" (never as raw): mixing such records into the raw variant polluted RIVs.
168 + */
161 169 export function parseGradeFromTitle(title: string): { grader: string | null; grade: string | null; qualifier: string | null } {
162 170 const matches = [...title.matchAll(GRADE_RE)];
163 171 // Prefer the first match that carries a numeric grade; ambiguous tokens without a grade are ignored.
164 − const m = matches.find((x) => x[2]) ?? matches.find((x) => !AMBIGUOUS_GRADERS.has(x[1]!.toLowerCase()));
172 + const m = matches.find((x) => x[2]) ?? matches.find((x) => x[3]) ?? matches.find((x) => !AMBIGUOUS_GRADERS.has(x[1]!.toLowerCase()));
165 173 if (!m) {
166 174 if (/\b(raw|ungraded)\b/i.test(title)) return { grader: 'raw', grade: null, qualifier: null };
167 175 return { grader: null, grade: null, qualifier: null };
168 176 }
169 177 const grader = getGrader(m[1]!)?.slug ?? m[1]!.toLowerCase();
170 − let grade = m[2] ?? null;
178 + let grade = m[2] ?? (m[3] ? AUTHENTIC_GRADE : null);
171 179 const g = getGrader(grader);
172 − if (grade && g && g.scale.values.length && !g.scale.values.includes(grade)) {
180 + if (grade && grade !== AUTHENTIC_GRADE && g && g.scale.values.length && !g.scale.values.includes(grade)) {
173 181 // e.g. "PSA 10" where 10 is fine; "PSA 2021" is a year → reject
174 182 if (Number(grade) > Number(g.scale.top)) grade = null;
175 183 }
@@ -187,11 +195,15 @@ export function variantKey(input: { grader?: string | null; grade?: string | nul
187 195 const labelParts: string[] = [];
188 196 if (input.grader && input.grader !== 'raw' && input.grade) {
189 197 parts.push(input.grader, input.grade);
190 − labelParts.push(`${getGrader(input.grader)?.name ?? input.grader.toUpperCase()} ${input.grade}`);
198 + labelParts.push(`${getGrader(input.grader)?.name ?? input.grader.toUpperCase()} ${input.grade === AUTHENTIC_GRADE ? 'Authentic' : input.grade}`);
191 199 if (input.qualifier) {
192 200 parts.push(input.qualifier.toLowerCase().replace(/\s+/g, '_'));
193 201 labelParts.push(input.qualifier);
194 202 }
203 + } else if (input.grader && input.grader !== 'raw') {
204 + // Slabbed by a known grader but the grade could not be read: its own variant, never "raw".
205 + parts.push(input.grader, 'unknown');
206 + labelParts.push(`${getGrader(input.grader)?.name ?? input.grader.toUpperCase()} · grade unknown`);
195 207 } else {
196 208 parts.push('raw');
197 209 if (input.completeness) {
modified packages/taxonomy/src/taxonomy.test.ts +12 −0
@@ -25,6 +25,18 @@ describe('taxonomy', () => {
25 25 expect(parseGradeFromTitle('Super Mario 64 WATA 9.8 A++')).toMatchObject({ grader: 'wata', grade: '9.8' });
26 26 expect(parseGradeFromTitle('Rolex Daytona 116500LN')).toMatchObject({ grader: null, grade: null });
27 27 expect(parseGradeFromTitle('1952 Topps Mantle raw')).toMatchObject({ grader: 'raw' });
28 + // PSA/DNA autograph slabs and authentication-only slabs (COMC, mycomicshop titles)
29 + expect(parseGradeFromTitle('1986-87 Fleer - [Base] #128 Al Wood [PSA/DNA 3 VG]')).toMatchObject({ grader: 'psa', grade: '3' });
30 + expect(parseGradeFromTitle('1986-87 Fleer - [Base] #33 Vern Fleming [PSA/DNA 8 NM-MT]')).toMatchObject({ grader: 'psa', grade: '8' });
31 + expect(parseGradeFromTitle('1989 Upper Deck - Blank Back #535 Danny Cox [SGC Authentic]')).toMatchObject({ grader: 'sgc', grade: 'authentic' });
32 + expect(parseGradeFromTitle('Brad Davis [PSA/DNA Certified Authentic Card & Auto]')).toMatchObject({ grader: 'psa', grade: 'authentic' });
33 + expect(parseGradeFromTitle('Charizard PSA 2021 release')).toMatchObject({ grader: 'psa', grade: null }); // a year is not a grade
34 + expect(parseGradeFromTitle('Batman #1 CGC (8.5) white pages')).toMatchObject({ grader: 'cgc', grade: '8.5' });
35 + });
36 + it('keeps slabs with an unreadable grade out of the raw variant', () => {
37 + expect(variantKey({ grader: 'psa', grade: null })).toEqual({ key: 'psa|unknown', label: 'PSA · grade unknown' });
38 + expect(variantKey({ grader: 'sgc', grade: 'authentic' })).toEqual({ key: 'sgc|authentic', label: 'SGC Authentic' });
39 + expect(variantKey({ grader: 'raw', grade: null }).key).toBe('raw');
28 40 });
29 41 it('builds variant keys', () => {
30 42 expect(variantKey({ grader: 'psa', grade: '10' })).toEqual({ key: 'psa|10', label: 'PSA 10' });
modified packages/valuation/src/scores.ts +106 −5
@@ -1,4 +1,4 @@
1 −import { clamp, pctChange, round } from '@rareindex/shared';
1 +import { clamp, round } from '@rareindex/shared';
2 2
3 3 /** Liquidity Score 0–100 (§121). Null when there is no evidence at all. */
4 4 export function liquidityScore(input: { salesPerMonth: number; activeListings: number; sources: number; medianDaysBetweenSales: number | null; askSoldSpread: number | null }): number | null {
@@ -36,10 +36,111 @@ export function momentumScore(input: { priceChange: number | null; volumeNow: nu
36 36 return round(100 * (0.7 * p + 0.3 * v), 1);
37 37 }
38 38
39 −/** Value opportunity (§123): discount of an ask vs RIV; null when RIV confidence is insufficient. */
40 −export function valueOpportunity(askUsd: number | null, riv: number | null, confidence: number | null): number | null {
41 − if (askUsd === null || riv === null || confidence === null || confidence < 0.5 || riv <= 0) return null;
42 − return round(pctChange(riv, askUsd) ?? 0, 4); // negative = below fair value
39 +// ---------- Ask vs RIV: sign convention, gates and the anomaly guard (§83–§84, §174, §196) ----------
40 +//
41 +// Everywhere in RareIndex `discount_to_riv` / `value_opportunity` = (ask − RIV) / RIV.
42 +// negative → the ask is BELOW the valuation (a discount) e.g. −0.167 = 16.7 % below RIV
43 +// positive → the ask is ABOVE the valuation (a premium)
44 +// A number is only produced when the comparison is legitimate: same variant, transaction-based RIV
45 +// with enough evidence, plausible ratio. Anything else is `null` + a verdict, never a "deal".
46 +
47 +/** An ask below 10 % or above 10× the valuation is almost always a mismatch (variant, lot, currency, typo). */
48 +export const ASK_ANOMALY_LOW_RATIO = 0.1;
49 +export const ASK_ANOMALY_HIGH_RATIO = 10;
50 +/** Minimum valuation quality for an ask to be compared at all. */
51 +export const ASK_MIN_CONFIDENCE = 0.5;
52 +export const ASK_MIN_SAMPLE = 5;
53 +/** Minimum identification confidence of the listing→asset match. */
54 +export const ASK_MIN_MATCH_CONFIDENCE = 0.7;
55 +/** Threshold below/above which an ask is called a deal / a premium. */
56 +export const DEAL_THRESHOLD = -0.1;
57 +export const PREMIUM_THRESHOLD = 0.1;
58 +
59 +export type AskVerdict = 'deal' | 'fair' | 'premium' | 'anomaly' | 'ungated';
60 +
61 +export interface AskAssessment {
62 + /** (ask − RIV) / RIV, 4 decimals; null when ungated or anomalous */
63 + discount: number | null;
64 + /** raw ratio ask / RIV, kept for diagnostics even when anomalous */
65 + ratio: number | null;
66 + verdict: AskVerdict;
67 + reasons: string[];
68 +}
69 +
70 +export interface AskAssessmentInput {
71 + askUsd: number | null | undefined;
72 + rivUsd: number | null | undefined;
73 + confidence: number | null | undefined;
74 + sampleSize?: number | null;
75 + /** valuation basis; only transaction-based valuations may qualify an ask */
76 + basis?: 'transactions' | 'comps' | 'guide' | 'none' | null;
77 + /** identification confidence of the listing (0–1) */
78 + matchConfidence?: number | null;
79 + /** true when the listing's variant is the one the RIV was computed for */
80 + sameVariant?: boolean;
81 + /** buyer premium / shipping / fees to add to the ask before comparing (USD) */
82 + feesUsd?: number | null;
83 +}
84 +
85 +/** Classify an ask against a valuation. Never returns a discount that failed a gate. */
86 +export function assessAsk(i: AskAssessmentInput): AskAssessment {
87 + const reasons: string[] = [];
88 + const ask = i.askUsd ?? null;
89 + const riv = i.rivUsd ?? null;
90 + if (ask === null || !(ask > 0)) reasons.push('no_ask');
91 + if (riv === null || !(riv > 0)) reasons.push('no_riv');
92 + if (reasons.length) return { discount: null, ratio: null, verdict: 'ungated', reasons };
93 + const allIn = ask! + Math.max(0, i.feesUsd ?? 0);
94 + const ratio = allIn / riv!;
95 + if (i.sameVariant === false) reasons.push('variant_mismatch');
96 + if (i.basis && i.basis !== 'transactions') reasons.push(`riv_basis_${i.basis}`);
97 + if ((i.confidence ?? 0) < ASK_MIN_CONFIDENCE) reasons.push('riv_confidence');
98 + if (i.sampleSize !== undefined && i.sampleSize !== null && i.sampleSize < ASK_MIN_SAMPLE) reasons.push('riv_sample');
99 + if (i.matchConfidence !== undefined && i.matchConfidence !== null && i.matchConfidence < ASK_MIN_MATCH_CONFIDENCE) reasons.push('match_confidence');
100 + if (reasons.length) return { discount: null, ratio: round(ratio, 4), verdict: 'ungated', reasons };
101 + if (ratio < ASK_ANOMALY_LOW_RATIO || ratio > ASK_ANOMALY_HIGH_RATIO) {
102 + return { discount: null, ratio: round(ratio, 4), verdict: 'anomaly', reasons: [ratio < 1 ? 'ask_implausibly_low' : 'ask_implausibly_high'] };
103 + }
104 + const discount = round(ratio - 1, 4);
105 + return { discount, ratio: round(ratio, 4), verdict: discount <= DEAL_THRESHOLD ? 'deal' : discount >= PREMIUM_THRESHOLD ? 'premium' : 'fair', reasons };
106 +}
107 +
108 +/**
109 + * Value opportunity (§123): (ask − RIV) / RIV for a gated, plausible comparison; null otherwise.
110 + * negative = below fair value. Thin wrapper over assessAsk kept for existing callers.
111 + */
112 +export function valueOpportunity(askUsd: number | null, riv: number | null, confidence: number | null, sampleSize?: number | null): number | null {
113 + const a = assessAsk({ askUsd, rivUsd: riv, confidence, sampleSize: sampleSize ?? undefined });
114 + return a.verdict === 'anomaly' || a.verdict === 'ungated' ? null : a.discount;
115 +}
116 +
117 +/**
118 + * Deal Score 0–100 (§83): how actionable a below-RIV ask is once valuation quality, match quality
119 + * and liquidity are considered. 0 for anything that is not a gated discount. Components are
120 + * multiplicative so one weak leg (e.g. an illiquid asset) cannot be compensated by a huge discount.
121 + */
122 +export function dealScore(input: { discount: number | null; confidence: number | null; sampleSize: number | null; liquidity: number | null; matchConfidence?: number | null }): number {
123 + if (input.discount === null || input.discount >= 0) return 0;
124 + const depth = clamp(-input.discount / 0.5, 0, 1) ** 0.7; // 50 % below → 1, concave so 10 % is already meaningful
125 + const conf = 0.4 + 0.6 * clamp(input.confidence ?? 0, 0, 1);
126 + const sample = 0.5 + 0.5 * clamp(Math.log10(1 + (input.sampleSize ?? 0)) / Math.log10(41), 0, 1); // 40 sales → 1
127 + const liq = 0.5 + 0.5 * clamp((input.liquidity ?? 30) / 100, 0, 1);
128 + const match = clamp(input.matchConfidence ?? 0.8, 0, 1);
129 + return round(100 * depth * conf * sample * liq * match, 1);
130 +}
131 +
132 +/**
133 + * Plausibility gate for displayed percentage moves (§143, §196): anything beyond ±500 % on a
134 + * valuation change, or beyond the anomaly ratios on an ask, is a data/identity anomaly, not a signal.
135 + */
136 +export const MAX_PLAUSIBLE_CHANGE = 5;
137 +export function isPlausibleChange(value: number | null | undefined): boolean {
138 + return value !== null && value !== undefined && Number.isFinite(value) && Math.abs(value) <= MAX_PLAUSIBLE_CHANGE;
139 +}
140 +export function isPlausibleDiscount(value: number | null | undefined): boolean {
141 + if (value === null || value === undefined || !Number.isFinite(value)) return false;
142 + const ratio = 1 + value;
143 + return ratio >= ASK_ANOMALY_LOW_RATIO && ratio <= ASK_ANOMALY_HIGH_RATIO;
43 144 }
44 145
45 146 /** Trending (§152): geometric blend of normalised momenta (0..1 each) → 0–100. */
modified packages/valuation/src/valuation.test.ts +38 −1
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2 2 import { computeValuation } from './valuation.js';
3 3 import { detectOutliers } from './outliers.js';
4 4 import { adjustmentFactor, computeGradePremiums } from './premiums.js';
5 −import { liquidityScore, rarityScore, momentumScore, valueOpportunity, trendingScore } from './scores.js';
5 +import { assessAsk, dealScore, isPlausibleChange, liquidityScore, rarityScore, momentumScore, valueOpportunity, trendingScore } from './scores.js';
6 6
7 7 const now = new Date('2026-09-01T00:00:00Z');
8 8 const d = (daysAgo: number) => new Date(now.getTime() - daysAgo * 86_400_000);
@@ -89,6 +89,43 @@ describe('scores', () => {
89 89 expect(momentumScore({ priceChange: 0.25, volumeNow: 10, volumePrev: 5 })!).toBeGreaterThan(0);
90 90 expect(valueOpportunity(2950, 3850, 0.91)).toBeCloseTo(-0.2338, 3);
91 91 expect(valueOpportunity(2950, 3850, 0.3)).toBeNull();
92 + expect(valueOpportunity(2950, 3850, 0.91, 2)).toBeNull(); // thin sample never qualifies an ask
92 93 expect(trendingScore({ priceMomentum: 50, volumeMomentum: 30, searchMomentum: null, listingMomentum: 10, newsMomentum: null })!).toBeGreaterThan(50);
93 94 });
94 95 });
96 +
97 +describe('ask vs RIV gate (§83–§84, §196)', () => {
98 + it('signs a discount negative and classifies deal / fair / premium', () => {
99 + expect(assessAsk({ askUsd: 2650, rivUsd: 3180, confidence: 0.8, sampleSize: 42, basis: 'transactions' })).toMatchObject({ verdict: 'deal', discount: -0.1667 });
100 + expect(assessAsk({ askUsd: 3200, rivUsd: 3180, confidence: 0.8, sampleSize: 42 }).verdict).toBe('fair');
101 + expect(assessAsk({ askUsd: 4000, rivUsd: 3180, confidence: 0.8, sampleSize: 42 }).verdict).toBe('premium');
102 + });
103 + it('never turns the 1986 Fleer case into a deal: a $472 slab against a $5.48 raw RIV is an anomaly', () => {
104 + const a = assessAsk({ askUsd: 472.2, rivUsd: 5.48, confidence: 0.78, sampleSize: 18, basis: 'transactions' });
105 + expect(a.verdict).toBe('anomaly');
106 + expect(a.discount).toBeNull();
107 + expect(a.ratio).toBeCloseTo(86.17, 1);
108 + expect(assessAsk({ askUsd: 0.4, rivUsd: 5.48, confidence: 0.78, sampleSize: 18 }).verdict).toBe('anomaly'); // implausibly low
109 + });
110 + it('refuses comps/guide valuations, thin samples, low confidence and variant mismatches', () => {
111 + expect(assessAsk({ askUsd: 100, rivUsd: 120, confidence: 0.9, sampleSize: 20, basis: 'comps' })).toMatchObject({ verdict: 'ungated', reasons: ['riv_basis_comps'] });
112 + expect(assessAsk({ askUsd: 100, rivUsd: 120, confidence: 0.9, sampleSize: 3 }).reasons).toContain('riv_sample');
113 + expect(assessAsk({ askUsd: 100, rivUsd: 120, confidence: 0.45, sampleSize: 30 }).reasons).toContain('riv_confidence');
114 + expect(assessAsk({ askUsd: 100, rivUsd: 120, confidence: 0.9, sampleSize: 30, sameVariant: false }).reasons).toContain('variant_mismatch');
115 + expect(assessAsk({ askUsd: 100, rivUsd: 120, confidence: 0.9, sampleSize: 30, matchConfidence: 0.5 }).reasons).toContain('match_confidence');
116 + });
117 + it('adds fees before comparing', () => {
118 + expect(assessAsk({ askUsd: 900, rivUsd: 1000, confidence: 0.9, sampleSize: 30, feesUsd: 150 }).verdict).toBe('fair');
119 + expect(assessAsk({ askUsd: 900, rivUsd: 1000, confidence: 0.9, sampleSize: 30 }).verdict).toBe('deal');
120 + });
121 + it('deal score is 0 without a gated discount and rewards confidence, depth and liquidity', () => {
122 + expect(dealScore({ discount: null, confidence: 0.9, sampleSize: 30, liquidity: 70 })).toBe(0);
123 + expect(dealScore({ discount: 0.2, confidence: 0.9, sampleSize: 30, liquidity: 70 })).toBe(0);
124 + const strong = dealScore({ discount: -0.3, confidence: 0.9, sampleSize: 40, liquidity: 80 });
125 + const weak = dealScore({ discount: -0.3, confidence: 0.55, sampleSize: 6, liquidity: 10 });
126 + expect(strong).toBeGreaterThan(weak);
127 + expect(strong).toBeLessThanOrEqual(100);
128 + expect(isPlausibleChange(85.17)).toBe(false);
129 + expect(isPlausibleChange(0.42)).toBe(true);
130 + });
131 +});
modified pnpm-lock.yaml +3 −0
@@ -93,6 +93,9 @@ importers:
93 93 '@rareindex/taxonomy':
94 94 specifier: workspace:*
95 95 version: link:../../packages/taxonomy
96 + '@rareindex/valuation':
97 + specifier: workspace:*
98 + version: link:../../packages/valuation
96 99 '@tanstack/react-query':
97 100 specifier: ^5.90.0
98 101 version: 5.102.8(react@19.2.8)
modified workers/cli.ts +20 −7
@@ -2,10 +2,10 @@
2 2 /**
3 3 * RareIndex operator CLI.
4 4 * ri connectors | ri crawl <id> [--mode probe|incremental|backfill] [--limit N]
5 − * ri backfill <id> [--reset] [--pause] [--status] [--start YYYY-MM-DD] [--end YYYY-MM-DD]
5 + * ri backfill <id…> [--start-only] [--reset] [--pause] [--status] [--start YYYY-MM-DD] [--end YYYY-MM-DD]
6 6 * ri certs [--limit N] [--grader psa] | ri certs --backfill (verify cert numbers via cert_lookup connectors / register certs from existing sales+listings)
7 7 * ri normalize [--connector id] [--limit N] | ri resolve [--limit N]
8 − * ri value [--asset id | --all] [--history] | ri premiums [--category slug]
8 + * ri value [--asset id | --all] [--history] | ri premiums [--category slug] | ri regrade [--kind sale|listing|both] [--dry-run]
9 9 * ri index | ri snapshots | ri radar | ri health [--probe] | ri fx [--backfill] | ri benchmarks
10 10 * ri expire | ri stats | ri run-all [--limit N] | ri worker
11 11 */
@@ -21,6 +21,7 @@ import { flushCosts } from './lib/costs.ts';
21 21 import { activeBackfill, pauseBackfill, runCrawl, startBackfill } from './crawler/run.ts';
22 22 import { normalizeBatch } from './normalizer/index.ts';
23 23 import { pendingCount, resolveBatch } from './entity-resolution/index.ts';
24 +import { regradeRecords } from './entity-resolution/regrade.ts';
24 25 import { assetsNeedingValuation, computePremiums, valueAsset, valueMany } from './valuation/run.ts';
25 26 import { runCategorySnapshots, runIndices, runRadar } from './indices/run.ts';
26 27 import { syncFx } from './fx.ts';
@@ -96,10 +97,16 @@ async function main() {
96 97 print({ paused: await pauseBackfill(id) });
97 98 break;
98 99 }
99 − const campaign = await startBackfill(id, { reset: Boolean(flags.reset), startDate: str('start') ?? null, endDate: str('end') ?? null });
100 − print({ campaign: campaign.id, resumedFrom: campaign.lastCursor });
101 − print(await runCrawl(id, { mode: 'backfill', limit: num('limit'), trigger: 'manual' }));
102 − print(await activeBackfill(id));
100 + const ids = positional.length > 1 ? positional : [id];
101 + for (const cid of ids) {
102 + const campaign = await startBackfill(cid, { reset: Boolean(flags.reset), startDate: str('start') ?? null, endDate: str('end') ?? null });
103 + print({ connector: cid, campaign: campaign.id, status: campaign.status, resumedFrom: campaign.lastCursor });
104 + // --start-only: register the campaign and let the worker scheduler run the slices (recommended for many sources)
105 + if (!flags['start-only']) {
106 + print(await runCrawl(cid, { mode: 'backfill', limit: num('limit'), trigger: 'manual' }));
107 + print(await activeBackfill(cid));
108 + }
109 + }
103 110 break;
104 111 }
105 112 case 'certs':
@@ -117,6 +124,12 @@ async function main() {
117 124 case 'premiums':
118 125 print({ written: await computePremiums(str('category')) });
119 126 break;
127 + case 'regrade': {
128 + // ri regrade [--kind sale|listing|both] [--connector id] [--limit N] [--dry-run]
129 + const kind = (str('kind') ?? 'both') as 'sale' | 'listing' | 'both';
130 + print(await regradeRecords({ kind, connectorId: str('connector'), limit: num('limit', 50_000), dryRun: Boolean(flags['dry-run']) }));
131 + break;
132 + }
120 133 case 'value': {
121 134 const asset = str('asset');
122 135 if (asset) print(await valueAsset(asset, { rebuildHistory: Boolean(flags.history) }));
@@ -190,7 +203,7 @@ async function main() {
190 203 return; // keep running
191 204 }
192 205 default:
193 − print(`RareIndex CLI\n ri connectors | crawl <id> [--mode probe|incremental|backfill] [--limit N] | normalize [--connector id] | resolve\n ri value [--asset id|--all] [--history] | premiums | index | snapshots | radar | health [--probe] | fx [--backfill] | benchmarks | expire\n ri stats | run-all [--limit N] [--history] | worker`);
206 + print(`RareIndex CLI\n ri connectors | crawl <id> [--mode probe|incremental|backfill] [--limit N] | normalize [--connector id] | resolve\n ri value [--asset id|--all] [--history] | premiums | regrade [--dry-run] | index | snapshots | radar | health [--probe] | fx [--backfill] | benchmarks | expire\n ri stats | run-all [--limit N] [--history] | worker`);
194 207 }
195 208 await flushCosts();
196 209 await closeDb();
added workers/entity-resolution/regrade.ts +107 −0
@@ -0,0 +1,107 @@
1 +/**
2 + * One-off / periodic repair (§84, §176): sales and listings whose grader was read but whose grade was
3 + * not (or that carry a grader token in the title the connector missed) were attached to the RAW
4 + * variant of their asset, polluting raw valuations and making graded asks look 80× "above RIV".
5 + *
6 + * `regradeRecords` re-parses the raw title with the current parser, moves the row to the proper
7 + * variant (creating it if needed; "grader · grade unknown" when the grade still cannot be read) and
8 + * marks the touched assets for revaluation by bumping `updated_at`/`created_at` watermarks the
9 + * valuation scheduler already watches. Nothing is deleted; every move is audited.
10 + */
11 +import { and, eq, inArray, isNotNull, isNull, or, sql } from 'drizzle-orm';
12 +import { assetVariants, listings, sales } from '@rareindex/database';
13 +import { parseGradeFromTitle } from '@rareindex/taxonomy';
14 +import { logger } from '@rareindex/shared';
15 +import { db } from '../lib/db.ts';
16 +import { auditMany } from '../lib/audit.ts';
17 +import { ensureVariant } from './resolver.ts';
18 +
19 +const log = logger.child({ component: 'regrade' });
20 +
21 +export interface RegradeResult {
22 + scanned: number;
23 + moved: number;
24 + unknownGrade: number;
25 + unchanged: number;
26 + assets: number;
27 +}
28 +
29 +interface Row {
30 + id: string;
31 + assetId: string;
32 + variantId: string | null;
33 + grader: string | null;
34 + grade: string | null;
35 + rawTitle: string;
36 + variantKey: string | null;
37 + vGrader: string | null;
38 + vGrade: string | null;
39 + condition: string | null;
40 + completeness: string | null;
41 + sizeLabel: string | null;
42 +}
43 +
44 +async function candidates(kind: 'sale' | 'listing', limit: number, connectorId?: string): Promise<Row[]> {
45 + const t = kind === 'sale' ? sales : listings;
46 + const where = and(
47 + connectorId ? eq(t.connectorId, connectorId) : undefined,
48 + kind === 'listing' ? eq(listings.availability, 'available') : undefined,
49 + or(
50 + // grader known, grade unreadable → currently in the raw variant
51 + and(isNotNull(t.grader), isNull(t.grade)),
52 + // no grader recorded but the title carries a grader token (heuristic prefilter; parser decides)
53 + and(isNull(t.grader), sql`${t.rawTitle} ~* '\\m(PSA|BGS|BECKETT|CGC|SGC|CBCS|PGX|WATA|VGA|PCGS|NGC|ANACS|ICCS|PMG)\\M'`),
54 + ),
55 + );
56 + const rows = await db()
57 + .select({ id: t.id, assetId: t.assetId, variantId: t.variantId, grader: t.grader, grade: t.grade, rawTitle: t.rawTitle, variantKey: assetVariants.variantKey, vGrader: assetVariants.grader, vGrade: assetVariants.grade, condition: assetVariants.condition, completeness: assetVariants.completeness, sizeLabel: assetVariants.sizeLabel })
58 + .from(t)
59 + .leftJoin(assetVariants, eq(assetVariants.id, t.variantId))
60 + .where(where)
61 + .limit(limit);
62 + return rows as Row[];
63 +}
64 +
65 +export async function regradeRecords(opts: { kind?: 'sale' | 'listing' | 'both'; limit?: number; connectorId?: string; dryRun?: boolean } = {}): Promise<RegradeResult> {
66 + const kinds: Array<'sale' | 'listing'> = opts.kind === 'sale' ? ['sale'] : opts.kind === 'listing' ? ['listing'] : ['sale', 'listing'];
67 + const limit = opts.limit ?? 50_000;
68 + const res: RegradeResult = { scanned: 0, moved: 0, unknownGrade: 0, unchanged: 0, assets: 0 };
69 + const touched = new Set<string>();
70 + for (const kind of kinds) {
71 + const rows = await candidates(kind, limit, opts.connectorId);
72 + res.scanned += rows.length;
73 + const audits: Array<{ entityType: string; entityId: string; action: string; reason: string; details: Record<string, unknown> }> = [];
74 + for (const r of rows) {
75 + const parsed = parseGradeFromTitle(r.rawTitle);
76 + const grader = r.grader && r.grader !== 'raw' ? r.grader : parsed.grader && parsed.grader !== 'raw' ? parsed.grader : null;
77 + if (!grader) {
78 + res.unchanged++;
79 + continue;
80 + }
81 + const grade = r.grade ?? (parsed.grader === grader ? parsed.grade : null);
82 + const target = await ensureVariant(r.assetId, { grader, grade, qualifier: parsed.qualifier, certificationNumber: null }, { condition: r.condition, completeness: r.completeness }, r.sizeLabel);
83 + if (target.id === r.variantId && r.grader === grader && r.grade === grade) {
84 + res.unchanged++;
85 + continue;
86 + }
87 + if (!grade) res.unknownGrade++;
88 + res.moved++;
89 + touched.add(r.assetId);
90 + if (opts.dryRun) continue;
91 + const t = kind === 'sale' ? sales : listings;
92 + await db().update(t).set({ variantId: target.id, grader, grade, ...(kind === 'listing' ? { discountToRiv: null } : {}) }).where(eq(t.id, r.id));
93 + audits.push({ entityType: kind, entityId: r.id, action: 'regraded', reason: 'grade_reparsed_from_title', details: { from: r.variantKey, to: target.key, grader, grade, title: r.rawTitle } });
94 + }
95 + if (audits.length) await auditMany(audits);
96 + log.info({ kind, scanned: rows.length, moved: res.moved, dryRun: Boolean(opts.dryRun) }, 'regrade pass');
97 + }
98 + res.assets = touched.size;
99 + if (!opts.dryRun && touched.size) {
100 + // Make the valuation scheduler pick these assets up (it watches max(listings.updated_at) / sales.created_at).
101 + const ids = [...touched];
102 + for (let i = 0; i < ids.length; i += 1000) await db().update(listings).set({ updatedAt: new Date() }).where(and(inArray(listings.assetId, ids.slice(i, i + 1000)), eq(listings.availability, 'available')));
103 + // assets without an available listing: touch their stats watermark backwards so they re-qualify
104 + for (let i = 0; i < ids.length; i += 1000) await db().execute(sql`update asset_stats set updated_at = '1970-01-01' where asset_id in ${ids.slice(i, i + 1000)}`);
105 + }
106 + return res;
107 +}
modified workers/entity-resolution/writers.ts +37 −17
@@ -1,6 +1,7 @@
1 1 import { and, eq, sql } from 'drizzle-orm';
2 2 import { auctionLots, auctions, images, listingEvents, listings, news, populationReports, priceObservations, sales, sources } from '@rareindex/database';
3 −import { normalizeCondition } from '@rareindex/taxonomy';
3 +import { normalizeCondition, parseGradeFromTitle } from '@rareindex/taxonomy';
4 +import type { Grade } from '@rareindex/shared';
4 5 import { newId, sha256, toDateOnly, type NormalizedAuctionLot, type NormalizedCatalogItem, type NormalizedListing, type NormalizedNewsItem, type NormalizedPopulationReport, type NormalizedPriceObservation, type NormalizedRecord, type NormalizedSale } from '@rareindex/shared';
5 6 import { db } from '../lib/db.ts';
6 7 import { toUsd } from '../lib/fx.ts';
@@ -9,10 +10,11 @@ import { enrichAttributesFromTitle, ensureVariant, resolveAsset, type Resolution
9 10 import { recordCertificate } from './certificates.ts';
10 11
11 12 /** Record a slab certification sighting when a record carries one (SPEC §22); never throws into the writer. */
12 −async function trackCert(rec: { grade: { grader: string | null; grade: string | null; qualifier: string | null; certificationNumber: string | null }; sourceId: string; connectorId: string; sourceUrl: string }, kind: 'sale' | 'listing' | 'auction_lot', targetId: string | null, assetId: string | null, variantId: string | null, priceUsd: number | null, price: number | null, currency: string | null, observedAt: Date): Promise<void> {
13 − if (!rec.grade.certificationNumber || !rec.grade.grader) return;
13 +async function trackCert(rec: { grade: { grader: string | null; grade: string | null; qualifier: string | null; certificationNumber: string | null }; sourceId: string; connectorId: string; sourceUrl: string; rawTitle?: string | null }, kind: 'sale' | 'listing' | 'auction_lot', targetId: string | null, assetId: string | null, variantId: string | null, priceUsd: number | null, price: number | null, currency: string | null, observedAt: Date): Promise<void> {
14 + const g = refineGrade(rec.grade, rec.rawTitle);
15 + if (!g.certificationNumber || !g.grader) return;
14 16 try {
15 − await recordCertificate({ grader: rec.grade.grader, certNumber: rec.grade.certificationNumber, assetId, variantId, grade: rec.grade.grade, qualifier: rec.grade.qualifier, kind, targetId, sourceId: rec.sourceId, connectorId: rec.connectorId, sourceUrl: rec.sourceUrl, priceUsd, price, currency, observedAt });
17 + await recordCertificate({ grader: g.grader, certNumber: g.certificationNumber, assetId, variantId, grade: g.grade, qualifier: g.qualifier, kind, targetId, sourceId: rec.sourceId, connectorId: rec.connectorId, sourceUrl: rec.sourceUrl, priceUsd, price, currency, observedAt });
16 18 } catch {
17 19 /* cert tracking is best-effort; the canonical write already succeeded */
18 20 }
@@ -82,6 +84,20 @@ export async function applyRecord(rec: NormalizedRecord): Promise<ApplyResult> {
82 84 }
83 85 }
84 86
87 +/**
88 + * Grade refinement (§18, §84): when a connector did not read the slab (or read the grader but not the
89 + * grade), parse the raw title. A grader with no readable grade stays "grader · grade unknown" — it must
90 + * never fall into the raw/ungraded variant, which is what compared PSA slabs against raw valuations.
91 + * An explicit `raw` from the connector is trusted over the title.
92 + */
93 +export function refineGrade(grade: Grade, rawTitle: string | null | undefined): Grade {
94 + if (!rawTitle || grade.grader === 'raw' || (grade.grader && grade.grade)) return grade;
95 + const parsed = parseGradeFromTitle(rawTitle);
96 + if (!parsed.grader || parsed.grader === 'raw') return grade;
97 + if (grade.grader && parsed.grader !== grade.grader) return grade; // never contradict the connector's grader
98 + return { ...grade, grader: grade.grader ?? parsed.grader, grade: grade.grade ?? parsed.grade, qualifier: grade.qualifier ?? parsed.qualifier };
99 +}
100 +
85 101 async function applyCatalog(rec: NormalizedCatalogItem): Promise<ApplyResult> {
86 102 const r = await resolveAsset(rec.attributes, { enrich: true, imageUrls: rec.imageUrls, description: rec.description });
87 103 if (!r) return { assetId: null, variantId: null, targetId: null, method: null, confidence: null, status: 'unmatched' };
@@ -90,10 +106,11 @@ async function applyCatalog(rec: NormalizedCatalogItem): Promise<ApplyResult> {
90 106 }
91 107
92 108 async function applySale(rec: NormalizedSale): Promise<ApplyResult> {
109 + const grade = refineGrade(rec.grade, rec.rawTitle);
93 110 const r = await resolveAsset(enrichAttributesFromTitle(rec.attributes, rec.rawTitle), { imageUrls: rec.imageUrls });
94 111 if (!r) return { assetId: null, variantId: null, targetId: null, method: null, confidence: null, status: 'unmatched' };
95 112 const condition = normCond(rec.attributes.categorySlug, rec.condition.conditionRaw, rec.condition.condition);
96 − const variant = await ensureVariant(r.assetId, rec.grade, { condition, completeness: rec.condition.completeness }, rec.attributes.size);
113 + const variant = await ensureVariant(r.assetId, grade, { condition, completeness: rec.condition.completeness }, rec.attributes.size);
97 114 const fx = await toUsd(rec.price, rec.currency, rec.saleDate);
98 115 if (!fx) throw new FxMissingError(rec.currency, rec.saleDate);
99 116 const dedupeKey = sha256(['sale', rec.sourceId, rec.externalId ?? rec.sourceUrl, toDateOnly(rec.saleDate), rec.price.toFixed(2)].join('|'));
@@ -126,16 +143,16 @@ async function applySale(rec: NormalizedSale): Promise<ApplyResult> {
126 143 quantity: rec.quantity,
127 144 isBundle: rec.isBundle,
128 145 condition,
129 − grader: rec.grade.grader,
130 − grade: rec.grade.grade,
131 − certificationNumber: rec.grade.certificationNumber,
146 + grader: grade.grader,
147 + grade: grade.grade,
148 + certificationNumber: grade.certificationNumber,
132 149 location: rec.location,
133 150 auctionHouse: rec.auctionHouse,
134 151 lotNumber: rec.lotNumber,
135 152 imageUrls: rec.imageUrls,
136 153 rawTitle: rec.rawTitle,
137 154 confidence,
138 − dataQuality: dataQuality({ date: rec.saleDate, price: rec.price, currency: rec.currency, images: rec.imageUrls, grade: rec.grade.grade, condition, externalId: rec.externalId, type: rec.saleType === 'unknown' ? null : rec.saleType }, trust, confidence),
155 + dataQuality: dataQuality({ date: rec.saleDate, price: rec.price, currency: rec.currency, images: rec.imageUrls, grade: grade.grade, condition, externalId: rec.externalId, type: rec.saleType === 'unknown' ? null : rec.saleType }, trust, confidence),
139 156 status,
140 157 flags,
141 158 dedupeKey,
@@ -150,10 +167,11 @@ async function applySale(rec: NormalizedSale): Promise<ApplyResult> {
150 167 }
151 168
152 169 async function applyListing(rec: NormalizedListing): Promise<ApplyResult> {
170 + const grade = refineGrade(rec.grade, rec.rawTitle);
153 171 const r = await resolveAsset(enrichAttributesFromTitle(rec.attributes, rec.rawTitle), { imageUrls: rec.imageUrls });
154 172 if (!r) return { assetId: null, variantId: null, targetId: null, method: null, confidence: null, status: 'unmatched' };
155 173 const condition = normCond(rec.attributes.categorySlug, rec.condition.conditionRaw, rec.condition.condition);
156 − const variant = await ensureVariant(r.assetId, rec.grade, { condition, completeness: rec.condition.completeness }, rec.attributes.size);
174 + const variant = await ensureVariant(r.assetId, grade, { condition, completeness: rec.condition.completeness }, rec.attributes.size);
157 175 let priceUsd: number | null = null;
158 176 if (rec.price !== null && rec.currency) {
159 177 const fx = await toUsd(rec.price, rec.currency, rec.observedAt);
@@ -165,7 +183,7 @@ async function applyListing(rec: NormalizedListing): Promise<ApplyResult> {
165 183 const confidence = Math.min(rec.confidence, r.confidence);
166 184 const now = rec.observedAt;
167 185 const [existing] = await db().select({ id: listings.id, price: listings.price, availability: listings.availability }).from(listings).where(and(eq(listings.sourceId, rec.sourceId), eq(listings.externalId, externalId))).limit(1);
168 − const quality = dataQuality({ price: rec.price, currency: rec.currency, images: rec.imageUrls, seller: rec.seller, condition, grade: rec.grade.grade, listedAt: rec.listedAt, description: rec.description }, trust, confidence);
186 + const quality = dataQuality({ price: rec.price, currency: rec.currency, images: rec.imageUrls, seller: rec.seller, condition, grade: grade.grade, listedAt: rec.listedAt, description: rec.description }, trust, confidence);
169 187 if (!existing) {
170 188 const id = newId('listing');
171 189 await db().insert(listings).values({
@@ -186,9 +204,9 @@ async function applyListing(rec: NormalizedListing): Promise<ApplyResult> {
186 204 shippingCost: rec.shippingCost,
187 205 quantity: rec.quantity,
188 206 condition,
189 − grader: rec.grade.grader,
190 − grade: rec.grade.grade,
191 − certificationNumber: rec.grade.certificationNumber,
207 + grader: grade.grader,
208 + grade: grade.grade,
209 + certificationNumber: grade.certificationNumber,
192 210 imageUrls: rec.imageUrls,
193 211 rawTitle: rec.rawTitle,
194 212 description: rec.description,
@@ -232,10 +250,11 @@ async function applyListing(rec: NormalizedListing): Promise<ApplyResult> {
232 250 }
233 251
234 252 async function applyObservation(rec: NormalizedPriceObservation): Promise<ApplyResult> {
253 + const grade = refineGrade(rec.grade, rec.rawTitle);
235 254 const r = await resolveAsset(rec.attributes, { imageUrls: rec.imageUrls });
236 255 if (!r) return { assetId: null, variantId: null, targetId: null, method: null, confidence: null, status: 'unmatched' };
237 256 const condition = normCond(rec.attributes.categorySlug, rec.condition.conditionRaw, rec.condition.condition);
238 − const variant = await ensureVariant(r.assetId, rec.grade, { condition, completeness: rec.condition.completeness }, rec.attributes.size);
257 + const variant = await ensureVariant(r.assetId, grade, { condition, completeness: rec.condition.completeness }, rec.attributes.size);
239 258 const fx = await toUsd(rec.price, rec.currency, rec.observationDate);
240 259 if (!fx) throw new FxMissingError(rec.currency, rec.observationDate);
241 260 const dedupeKey = sha256(['obs', rec.sourceId, rec.externalId ?? rec.sourceUrl, rec.priceKind, variant.key, toDateOnly(rec.observationDate)].join('|'));
@@ -250,6 +269,7 @@ async function applyObservation(rec: NormalizedPriceObservation): Promise<ApplyR
250 269 }
251 270
252 271 async function applyAuctionLot(rec: NormalizedAuctionLot): Promise<ApplyResult> {
272 + const grade = refineGrade(rec.grade, rec.rawTitle);
253 273 const r = await resolveAsset(enrichAttributesFromTitle(rec.attributes, rec.rawTitle), { imageUrls: rec.imageUrls, allowCreate: rec.confidence >= 0.7 });
254 274 const auctionUrl = (rec.attributes.metadata?.auctionUrl as string | undefined) ?? `${new URL(rec.sourceUrl).origin}#${rec.auctionHouse}:${rec.auctionName ?? 'auction'}`;
255 275 const auctionId = sha256(auctionUrl).slice(0, 24);
@@ -258,11 +278,11 @@ async function applyAuctionLot(rec: NormalizedAuctionLot): Promise<ApplyResult>
258 278 .values({ id: `auc_${auctionId}`, sourceId: rec.sourceId, auctionHouse: rec.auctionHouse, name: rec.auctionName ?? rec.auctionHouse, url: auctionUrl, startsAt: rec.startsAt, endsAt: rec.endsAt, location: rec.location, categorySlugs: [rec.attributes.categorySlug], status: rec.status === 'unknown' ? 'upcoming' : rec.status, currency: rec.currency })
259 279 .onConflictDoUpdate({ target: auctions.url, set: { endsAt: sql`coalesce(excluded.ends_at, ${auctions.endsAt})`, status: sql`excluded.status`, updatedAt: new Date() } });
260 280 let variantId: string | null = null;
261 − if (r) variantId = (await ensureVariant(r.assetId, rec.grade, { condition: normCond(rec.attributes.categorySlug, rec.condition.conditionRaw, rec.condition.condition), completeness: rec.condition.completeness }, rec.attributes.size)).id;
281 + if (r) variantId = (await ensureVariant(r.assetId, grade, { condition: normCond(rec.attributes.categorySlug, rec.condition.conditionRaw, rec.condition.condition), completeness: rec.condition.completeness }, rec.attributes.size)).id;
262 282 const id = `lot_${sha256(rec.sourceUrl).slice(0, 24)}`;
263 283 await db()
264 284 .insert(auctionLots)
265 − .values({ id, auctionId: `auc_${auctionId}`, assetId: r?.assetId ?? null, variantId, sourceId: rec.sourceId, lotNumber: rec.lotNumber, title: rec.rawTitle, url: rec.sourceUrl, estimateLow: rec.estimateLow, estimateHigh: rec.estimateHigh, currentBid: rec.currentBid, currency: rec.currency, startsAt: rec.startsAt, endsAt: rec.endsAt, status: rec.status === 'unknown' ? 'upcoming' : rec.status, imageUrls: rec.imageUrls, grader: rec.grade.grader, grade: rec.grade.grade })
285 + .values({ id, auctionId: `auc_${auctionId}`, assetId: r?.assetId ?? null, variantId, sourceId: rec.sourceId, lotNumber: rec.lotNumber, title: rec.rawTitle, url: rec.sourceUrl, estimateLow: rec.estimateLow, estimateHigh: rec.estimateHigh, currentBid: rec.currentBid, currency: rec.currency, startsAt: rec.startsAt, endsAt: rec.endsAt, status: rec.status === 'unknown' ? 'upcoming' : rec.status, imageUrls: rec.imageUrls, grader: grade.grader, grade: grade.grade })
266 286 .onConflictDoUpdate({ target: auctionLots.url, set: { currentBid: sql`excluded.current_bid`, status: sql`excluded.status`, endsAt: sql`coalesce(excluded.ends_at, ${auctionLots.endsAt})`, assetId: sql`coalesce(${auctionLots.assetId}, excluded.asset_id)`, updatedAt: new Date() } });
267 287 await trackCert(rec, 'auction_lot', id, r?.assetId ?? null, variantId, null, rec.currentBid, rec.currency, rec.observedAt);
268 288 return { assetId: r?.assetId ?? null, variantId, targetId: id, method: r?.method ?? null, confidence: r?.confidence ?? null, status: r ? 'applied' : 'unmatched' };
modified workers/indices/run.ts +7 −4
@@ -91,7 +91,7 @@ export async function runIndices(opts: { now?: Date } = {}): Promise<{ published
91 91 log.info({ constituents: cons.length }, 'eligible constituents');
92 92 const published: string[] = [];
93 93 const skipped: string[] = [];
94 − const subSeries: Record<string, Array<{ date: string; value: number; transactions: number }>> = {};
94 + const subSeries: Record<string, Array<{ date: string; value: number; transactions: number; constituents: number }>> = {};
95 95 const today = toDateOnly(now);
96 96
97 97 for (const def of defs.filter((d) => !d.isFlagship)) {
@@ -120,7 +120,7 @@ export async function runIndices(opts: { now?: Date } = {}): Promise<{ published
120 120 .values(rows.slice(i, i + 500))
121 121 .onConflictDoUpdate({ target: [indexValues.indexId, indexValues.date], set: { value: sql`excluded.value`, constituentsCount: sql`excluded.constituents_count`, transactions: sql`excluded.transactions`, volumeUsd: sql`excluded.volume_usd`, medianSaleUsd: sql`excluded.median_sale_usd`, avgSaleUsd: sql`excluded.avg_sale_usd`, marketCapEstUsd: sql`excluded.market_cap_est_usd`, marketCapConfidence: sql`excluded.market_cap_confidence`, liquidityScore: sql`excluded.liquidity_score`, momentum: sql`excluded.momentum`, breadth: sql`excluded.breadth`, trackedAssets: sql`excluded.tracked_assets`, coverage: sql`excluded.coverage` } });
122 122 }
123 − subSeries[def.ticker] = rows.map((r) => ({ date: r.date, value: r.value, transactions: r.transactions }));
123 + subSeries[def.ticker] = rows.map((r) => ({ date: r.date, value: r.value, transactions: r.transactions, constituents: r.constituentsCount }));
124 124 published.push(def.ticker);
125 125 await emit('index_updated', { type: 'index', id: def.id }, { ticker: def.ticker, value: points.at(-1)!.value, date: points.at(-1)!.date, constituents: points.at(-1)!.constituents });
126 126 }
@@ -129,7 +129,10 @@ export async function runIndices(opts: { now?: Date } = {}): Promise<{ published
129 129 const flagship = defs.find((d) => d.isFlagship);
130 130 if (flagship) {
131 131 const tickers = Object.keys(subSeries);
132 − if (tickers.length >= 1) {
132 + // The flagship publishes only when its own minimum (25 distinct priced constituents across the
133 + // published subindices, as stated on /rareindex) is met — not as soon as one subindex exists.
134 + const flagshipConstituents = tickers.reduce((a, t) => a + (subSeries[t]!.at(-1)?.constituents ?? 0), 0);
135 + if (tickers.length >= 1 && flagshipConstituents >= flagship.minConstituents) {
133 136 const dates = [...new Set(tickers.flatMap((t) => subSeries[t]!.map((p) => p.date)))].sort();
134 137 const last = new Map<string, number>();
135 138 let level = flagship.baseValue;
@@ -166,7 +169,7 @@ export async function runIndices(opts: { now?: Date } = {}): Promise<{ published
166 169 await db().insert(indexValues).values(rows.slice(i, i + 500)).onConflictDoUpdate({ target: [indexValues.indexId, indexValues.date], set: { value: sql`excluded.value`, constituentsCount: sql`excluded.constituents_count`, transactions: sql`excluded.transactions`, breadth: sql`excluded.breadth`, coverage: sql`excluded.coverage` } });
167 170 }
168 171 published.push(flagship.ticker);
169 − } else skipped.push('RARE (no subindex published)');
172 + } else skipped.push(tickers.length ? `RARE (${flagshipConstituents}/${flagship.minConstituents} constituents across ${tickers.length} subindices)` : 'RARE (no subindex published)');
170 173 }
171 174
172 175 // correlations between subindices
modified workers/valuation/run.ts +48 −18
@@ -1,6 +1,6 @@
1 1 import { and, desc, eq, gte, inArray, isNull, or, sql } from 'drizzle-orm';
2 2 import { assetStats, assetVariants, assets, gradePremiums, listings, populationReports, priceObservations, priceSnapshots, sales, sources, valuations, variantStats } from '@rareindex/database';
3 −import { adjustmentFactor, assetDataQuality, computeGradePremiums, computeValuation, detectOutliers, liquidityScore, momentumScore, rarityScore, trendingScore, valueOpportunity, type GradePremium, type SaleInput } from '@rareindex/valuation';
3 +import { ASK_ANOMALY_HIGH_RATIO, ASK_ANOMALY_LOW_RATIO, ASK_MIN_CONFIDENCE, ASK_MIN_MATCH_CONFIDENCE, ASK_MIN_SAMPLE, adjustmentFactor, assetDataQuality, computeGradePremiums, computeValuation, detectOutliers, liquidityScore, momentumScore, rarityScore, trendingScore, type GradePremium, type SaleInput } from '@rareindex/valuation';
4 4 import { logger, median, newId, pctChange, toDateOnly } from '@rareindex/shared';
5 5 import { db } from '../lib/db.ts';
6 6 import { auditMany } from '../lib/audit.ts';
@@ -8,6 +8,7 @@ import { emit } from '../lib/events.ts';
8 8
9 9 const log = logger.child({ component: 'valuation' });
10 10 const DAY = 86_400_000;
11 +const safeRatioV = (v: number | null): number | null => (v === null || !Number.isFinite(v) || Math.abs(v) >= 99 ? null : v);
11 12 const asDate = (v: unknown): Date | null => (v instanceof Date ? v : typeof v === 'string' ? new Date(v) : null);
12 13
13 14 const premiumCache = new Map<string, { at: number; rows: GradePremium[] }>();
@@ -85,8 +86,11 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
85 86 const toInput = (s: SaleRow): SaleInput => ({ id: s.id, priceUsd: Number(s.priceUsd), date: s.saleDate, trust: trust.get(s.sourceId) ?? 0.6, confidence: Number(s.confidence), grader: s.grader, grade: s.grade, quantity: s.quantity, isBundle: s.isBundle, status: s.status as SaleInput['status'] });
86 87
87 88 // 3. per-variant valuations
88 − const results: Array<{ variantId: string; out: ReturnType<typeof computeValuation>; salesCount: number; sales30d: number }> = [];
89 + const results: Array<{ variantId: string; isDefault: boolean; out: ReturnType<typeof computeValuation>; salesCount: number; sales30d: number; activeL: { count: number; minAsk: number | null } }> = [];
89 90 const today = toDateOnly(now);
91 + // Ask-vs-RIV is recomputed from scratch on every pass: a stale discount computed against a
92 + // previous (possibly wrong-variant) valuation must never survive (§84, §196).
93 + await db().update(listings).set({ discountToRiv: null }).where(and(eq(listings.assetId, assetId), eq(listings.availability, 'available'), sql`${listings.discountToRiv} is not null`));
90 94 for (const v of variants) {
91 95 const own = (byVariant.get(v.id) ?? []).map(toInput);
92 96 const comps: NonNullable<Parameters<typeof computeValuation>[0]['comps']> = [];
@@ -107,7 +111,8 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
107 111 });
108 112 const salesCount = own.filter((s) => (s.status ?? 'valid') === 'valid').length;
109 113 const sales30d = own.filter((s) => (s.status ?? 'valid') === 'valid' && now.getTime() - s.date.getTime() <= 30 * DAY).length;
110 − results.push({ variantId: v.id, out, salesCount, sales30d });
114 + const activeL = await activeListingStats(assetId, v.id);
115 + results.push({ variantId: v.id, isDefault: v.isDefault, out, salesCount, sales30d, activeL });
111 116 if (out.riv === null && salesCount === 0 && (obsByVariant.get(v.id) ?? []).length === 0) continue;
112 117 await db().insert(valuations).values({
113 118 id: newId('valuation'),
@@ -129,33 +134,52 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
129 134 });
130 135 const prev30 = await snapshotValue(assetId, v.id, new Date(now.getTime() - 30 * DAY));
131 136 const prev1y = await snapshotValue(assetId, v.id, new Date(now.getTime() - 365 * DAY));
132 − const activeL = await activeListingStats(assetId, v.id);
133 137 const latest = own[0];
134 138 await db()
135 139 .insert(variantStats)
136 − .values({ variantId: v.id, assetId, rivUsd: out.riv, rivLowUsd: out.low, rivHighUsd: out.high, rivConfidence: out.confidence, rivSampleSize: out.sampleSize, latestSaleUsd: latest?.priceUsd ?? null, latestSaleAt: latest?.date ?? null, change30d: pctChange(prev30, out.riv), change1y: pctChange(prev1y, out.riv), salesCount, sales30d, activeListings: activeL.count, minAskUsd: activeL.minAsk, liquidityScore: liquidityScore({ salesPerMonth: (own.length / 36) || 0, activeListings: activeL.count, sources: new Set((byVariant.get(v.id) ?? []).map((s) => s.sourceId)).size, medianDaysBetweenSales: medianGapDays(own.map((s) => s.date)), askSoldSpread: activeL.minAsk && out.riv ? (activeL.minAsk - out.riv) / out.riv : null }), updatedAt: now })
137 − .onConflictDoUpdate({ target: variantStats.variantId, set: { rivUsd: out.riv, rivLowUsd: out.low, rivHighUsd: out.high, rivConfidence: out.confidence, rivSampleSize: out.sampleSize, latestSaleUsd: latest?.priceUsd ?? null, latestSaleAt: latest?.date ?? null, change30d: pctChange(prev30, out.riv), change1y: pctChange(prev1y, out.riv), salesCount, sales30d, activeListings: activeL.count, minAskUsd: activeL.minAsk, updatedAt: now } });
140 + .values({ variantId: v.id, assetId, rivUsd: out.riv, rivLowUsd: out.low, rivHighUsd: out.high, rivConfidence: out.confidence, rivSampleSize: out.sampleSize, latestSaleUsd: latest?.priceUsd ?? null, latestSaleAt: latest?.date ?? null, change30d: safeRatioV(pctChange(prev30, out.riv)), change1y: safeRatioV(pctChange(prev1y, out.riv)), salesCount, sales30d, activeListings: activeL.count, minAskUsd: activeL.minAsk, liquidityScore: liquidityScore({ salesPerMonth: (own.length / 36) || 0, activeListings: activeL.count, sources: new Set((byVariant.get(v.id) ?? []).map((s) => s.sourceId)).size, medianDaysBetweenSales: medianGapDays(own.map((s) => s.date)), askSoldSpread: activeL.minAsk && out.riv ? (activeL.minAsk - out.riv) / out.riv : null }), updatedAt: now })
141 + .onConflictDoUpdate({ target: variantStats.variantId, set: { rivUsd: out.riv, rivLowUsd: out.low, rivHighUsd: out.high, rivConfidence: out.confidence, rivSampleSize: out.sampleSize, latestSaleUsd: latest?.priceUsd ?? null, latestSaleAt: latest?.date ?? null, change30d: safeRatioV(pctChange(prev30, out.riv)), change1y: safeRatioV(pctChange(prev1y, out.riv)), salesCount, sales30d, activeListings: activeL.count, minAskUsd: activeL.minAsk, updatedAt: now } });
138 142 await db()
139 143 .insert(priceSnapshots)
140 144 .values({ assetId, variantId: v.id, date: today, rivUsd: out.riv, latestSaleUsd: latest?.priceUsd ?? null, medianUsd: out.distribution.median, salesCount: own.filter((s) => toDateOnly(s.date) === today).length, volumeUsd: own.filter((s) => toDateOnly(s.date) === today).reduce((a, s) => a + s.priceUsd, 0) || null, listingsCount: activeL.count, minAskUsd: activeL.minAsk, observationUsd: out.methods.guide })
141 145 .onConflictDoUpdate({ target: [priceSnapshots.assetId, priceSnapshots.variantId, priceSnapshots.date], set: { rivUsd: out.riv, latestSaleUsd: latest?.priceUsd ?? null, medianUsd: out.distribution.median, listingsCount: activeL.count, minAskUsd: activeL.minAsk, observationUsd: out.methods.guide } });
142 − // value opportunity on active listings of this variant
143 − if (out.riv !== null && out.confidence >= 0.5) {
146 + // Ask vs RIV on the active listings of THIS variant only (§84 data-quality gate):
147 + // – the valuation must rest on ≥ ASK_MIN_SAMPLE transactions with confidence ≥ ASK_MIN_CONFIDENCE
148 + // (comps-only / guide-only estimates never qualify an ask);
149 + // – the listing must be confidently matched and carry a readable grade when it is slabbed;
150 + // – an ask outside [0.1×, 10×] RIV is flagged `riv_anomaly` (identity/data problem), not priced.
151 + if (out.riv !== null && out.basis === 'transactions' && out.confidence >= ASK_MIN_CONFIDENCE && out.sampleSize >= ASK_MIN_SAMPLE) {
152 + const lo = out.riv * ASK_ANOMALY_LOW_RATIO;
153 + const hi = out.riv * ASK_ANOMALY_HIGH_RATIO;
154 + const scope = and(eq(listings.variantId, v.id), eq(listings.availability, 'available'), sql`${listings.priceUsd} > 0`);
155 + await db()
156 + .update(listings)
157 + .set({ discountToRiv: sql`round((${listings.priceUsd} - ${out.riv}) / ${out.riv}, 4)`, flags: sql`array_remove(${listings.flags}, 'riv_anomaly')` })
158 + .where(and(scope, sql`${listings.priceUsd} between ${lo} and ${hi}`, sql`${listings.confidence} >= ${ASK_MIN_MATCH_CONFIDENCE}`, sql`not (${listings.grader} is not null and ${listings.grade} is null)`));
144 159 await db()
145 160 .update(listings)
146 − .set({ discountToRiv: sql`case when ${listings.priceUsd} is null then null else round((${listings.priceUsd} - ${out.riv}) / ${out.riv}, 4) end` })
147 − .where(and(eq(listings.variantId, v.id), eq(listings.availability, 'available')));
161 + .set({ discountToRiv: null, flags: sql`case when 'riv_anomaly' = any(${listings.flags}) then ${listings.flags} else array_append(${listings.flags}, 'riv_anomaly') end` })
162 + .where(and(scope, sql`(${listings.priceUsd} < ${lo} or ${listings.priceUsd} > ${hi})`));
148 163 }
149 164 }
150 165
151 − // 4. asset-level: representative variant = highest confidence, then most sales
166 + // 4. asset-level representative variant (the headline RIV). Preference order:
167 + // transaction-based valuation with confidence ≥ 0.5 and ≥ 5 sales → the DEFAULT (raw/base) variant
168 + // when it qualifies, else the qualifying variant with the most sales; fallback: any priced variant
169 + // by confidence. Rationale: a graded sub-variant with a tighter distribution used to win over the
170 + // base market and made the headline value (and every ask comparison) wrong by an order of magnitude.
152 171 const priced = results.filter((r) => r.out.riv !== null).sort((a, b) => b.out.confidence - a.out.confidence || b.salesCount - a.salesCount);
153 − const rep = priced[0] ?? null;
172 + const qualified = priced.filter((r) => r.out.basis === 'transactions' && r.out.confidence >= ASK_MIN_CONFIDENCE && r.out.sampleSize >= ASK_MIN_SAMPLE);
173 + const rep = qualified.find((r) => r.isDefault) ?? qualified.sort((a, b) => b.salesCount - a.salesCount || b.out.confidence - a.out.confidence)[0] ?? priced[0] ?? null;
154 174 const validSales = saleRows.filter((s) => s.status === 'valid');
155 175 const latest = validSales[0];
156 − const prevAsset = { d1: await snapshotValue(assetId, '', new Date(now.getTime() - 1 * DAY)), d7: await snapshotValue(assetId, '', new Date(now.getTime() - 7 * DAY)), d30: await snapshotValue(assetId, '', new Date(now.getTime() - 30 * DAY)), d90: await snapshotValue(assetId, '', new Date(now.getTime() - 90 * DAY)), y1: await snapshotValue(assetId, '', new Date(now.getTime() - 365 * DAY)) };
176 + // Changes are measured on the representative variant's own series so that a change of
177 + // representative variant never shows up as a price move.
178 + const repSeries = rep?.variantId ?? '';
179 + const prevAsset = { d1: await snapshotValue(assetId, repSeries, new Date(now.getTime() - 1 * DAY)), d7: await snapshotValue(assetId, repSeries, new Date(now.getTime() - 7 * DAY)), d30: await snapshotValue(assetId, repSeries, new Date(now.getTime() - 30 * DAY)), d90: await snapshotValue(assetId, repSeries, new Date(now.getTime() - 90 * DAY)), y1: await snapshotValue(assetId, repSeries, new Date(now.getTime() - 365 * DAY)) };
157 180 const riv = rep?.out.riv ?? null;
158 181 const activeAll = await activeListingStats(assetId, null);
182 + const repActive = rep?.activeL ?? null;
159 183 const salesCount = allSalesCount.n;
160 184 const sales30d = validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 30 * DAY).length;
161 185 const sales1y = validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 365 * DAY).length;
@@ -163,13 +187,18 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
163 187 const pop = await latestPopulation(assetId);
164 188 const [obsCount] = await db().select({ n: sql<number>`count(*)::int` }).from(priceObservations).where(eq(priceObservations.assetId, assetId));
165 189 const sourcesCount = new Set([...saleRows.map((s) => s.sourceId), ...obsRows.map((o) => o.sourceId)]).size;
166 − const liq = liquidityScore({ salesPerMonth: sales1y / 12, activeListings: activeAll.count, sources: sourcesCount, medianDaysBetweenSales: medianGapDays(validSales.map((s) => s.saleDate)), askSoldSpread: activeAll.minAsk && riv ? (activeAll.minAsk - riv) / riv : null });
190 + const liq = liquidityScore({ salesPerMonth: sales1y / 12, activeListings: activeAll.count, sources: sourcesCount, medianDaysBetweenSales: medianGapDays(validSales.map((s) => s.saleDate)), askSoldSpread: repActive?.minAsk && riv ? (repActive.minAsk - riv) / riv : null });
167 191 const rar = rarityScore({ population: pop?.total ?? null, productionQuantity: asset.productionQuantity, listingsPerYear: activeAll.count > 0 || sales1y > 0 ? activeAll.count * 4 : null, salesPerYear: sales1y > 0 || salesCount > 0 ? sales1y : null, populationGrowthPct: pop?.growth ?? null });
168 − const ch = { d1: pctChange(prevAsset.d1, riv), d7: pctChange(prevAsset.d7, riv), d30: pctChange(prevAsset.d30, riv), d90: pctChange(prevAsset.d90, riv), y1: pctChange(prevAsset.y1, riv) };
192 + // ratio columns are numeric(8,6): anything ≥ ±99 (9,900 %) would overflow and abort the asset; such a
193 + // move is never a market signal but a data/identity artefact → stored as null (§143, §196).
194 + const safeRatio = (v: number | null): number | null => (v === null || !Number.isFinite(v) || Math.abs(v) >= 99 ? null : v);
195 + const ch = { d1: safeRatio(pctChange(prevAsset.d1, riv)), d7: safeRatio(pctChange(prevAsset.d7, riv)), d30: safeRatio(pctChange(prevAsset.d30, riv)), d90: safeRatio(pctChange(prevAsset.d90, riv)), y1: safeRatio(pctChange(prevAsset.y1, riv)) };
169 196 const mom = { m7: momentumScore({ priceChange: ch.d7, volumeNow: validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 7 * DAY).length, volumePrev: validSales.filter((s) => { const a = now.getTime() - s.saleDate.getTime(); return a > 7 * DAY && a <= 14 * DAY; }).length }), m30: momentumScore({ priceChange: ch.d30, volumeNow: sales30d, volumePrev: salesPrev30 }), m90: momentumScore({ priceChange: ch.d90, volumeNow: validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 90 * DAY).length, volumePrev: validSales.filter((s) => { const a = now.getTime() - s.saleDate.getTime(); return a > 90 * DAY && a <= 180 * DAY; }).length }), y1: momentumScore({ priceChange: ch.y1, volumeNow: sales1y, volumePrev: validSales.length - sales1y }) };
170 197 const listingMomentum = activeAll.count > 0 ? Math.min(100, activeAll.count * 5) : null;
171 198 const trending = trendingScore({ priceMomentum: mom.m30, volumeMomentum: momentumScore({ priceChange: null, volumeNow: sales30d, volumePrev: salesPrev30 }), searchMomentum: null, listingMomentum, newsMomentum: null });
172 − const [bestListing] = await db().select({ d: sql<number>`min(${listings.discountToRiv})` }).from(listings).where(and(eq(listings.assetId, assetId), eq(listings.availability, 'available')));
199 + // Best gated ask across the asset's variants (negative = below its own variant's RIV). No fallback:
200 + // comparing the cheapest ask of ANY variant against the headline RIV was the −8,500 % bug.
201 + const [bestListing] = await db().select({ d: sql<number | null>`min(${listings.discountToRiv})` }).from(listings).where(and(eq(listings.assetId, assetId), eq(listings.availability, 'available'), sql`${listings.discountToRiv} is not null`));
173 202 const fields = { brand: asset.brand, set: asset.setName, number: asset.number, year: asset.year, variant: asset.variant, image: asset.heroImageUrl, description: asset.description, identifiers: Object.keys(asset.identifiers).length ? 1 : null };
174 203 const dq = assetDataQuality({ fieldsPresent: Object.values(fields).filter((x) => x !== null && x !== undefined).length, fieldsTotal: Object.keys(fields).length, sourceTrustAvg: sourcesCount ? median([...new Set(saleRows.map((s) => s.sourceId))].map((s) => trust.get(s) ?? 0.5)) : null, identificationConfidence: rep ? Number(median(validSales.map((s) => Number(s.confidence))) ?? 0.8) : null, hasImage: Boolean(asset.heroImageUrl), salesCount });
175 204 const statsRow = {
@@ -196,7 +225,8 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
196 225 sales1y,
197 226 volume30dUsd: sales30d ? validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 30 * DAY).reduce((a, s) => a + Number(s.priceUsd), 0) : null,
198 227 activeListings: activeAll.count,
199 − minAskUsd: activeAll.minAsk,
228 + // lowest ask of the representative variant (comparable to the headline RIV); all variants otherwise
229 + minAskUsd: repActive?.minAsk ?? activeAll.minAsk,
200 230 observationsCount: obsCount?.n ?? 0,
201 231 sourcesCount,
202 232 liquidityScore: liq,
@@ -206,7 +236,7 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
206 236 momentum90d: mom.m90,
207 237 momentum1y: mom.y1,
208 238 trendingScore: trending,
209 − valueOpportunity: bestListing?.d !== null && bestListing?.d !== undefined ? Number(bestListing.d) : (riv && activeAll.minAsk ? valueOpportunity(activeAll.minAsk, riv, rep?.out.confidence ?? null) : null),
239 + valueOpportunity: bestListing?.d !== null && bestListing?.d !== undefined ? Number(bestListing.d) : null,
210 240 dataQuality: dq,
211 241 updatedAt: now,
212 242 };
213 243