| 151 |
151 |
if (out.riv !== null && out.basis === 'transactions' && out.confidence >= ASK_MIN_CONFIDENCE && out.sampleSize >= ASK_MIN_SAMPLE) { |
| 152 |
152 |
const lo = out.riv * ASK_ANOMALY_LOW_RATIO; |
| 153 |
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`); |
|
154 |
+ // Auction lots are excluded: a current/opening bid (often ¥1 on Yahoo! Auctions) is not an asking price. |
|
155 |
+ // Bid-vs-RIV belongs to auction intelligence with fees (§33–§35), not to the deal rails. |
|
156 |
+ const scope = and(eq(listings.variantId, v.id), eq(listings.availability, 'available'), sql`${listings.priceUsd} > 0`, sql`${listings.listingType} <> 'auction'`); |
| 155 |
157 |
await db() |
| 156 |
158 |
.update(listings) |
| 157 |
159 |
.set({ discountToRiv: sql`round((${listings.priceUsd} - ${out.riv}) / ${out.riv}, 4)`, flags: sql`array_remove(${listings.flags}, 'riv_anomaly')` }) |
| 268 |
270 |
|
| 269 |
271 |
async function activeListingStats(assetId: string, variantId: string | null): Promise<{ count: number; minAsk: number | null }> { |
| 270 |
272 |
const [row] = await db() |
| 271 |
|
− .select({ n: sql<number>`count(*)::int`, min: sql<number | null>`min(${listings.priceUsd})` }) |
|
273 |
+ // count = every live listing; min ask = fixed-price / best-offer / ask only (an auction bid is not an ask) |
|
274 |
+ .select({ n: sql<number>`count(*)::int`, min: sql<number | null>`min(${listings.priceUsd}) filter (where ${listings.listingType} <> 'auction' and ${listings.priceUsd} > 0)` }) |
| 272 |
275 |
.from(listings) |
| 273 |
276 |
.where(and(eq(listings.assetId, assetId), eq(listings.availability, 'available'), variantId ? eq(listings.variantId, variantId) : undefined)); |
| 274 |
277 |
return { count: row?.n ?? 0, minAsk: row?.min === null || row?.min === undefined ? null : Number(row.min) }; |
| 275 |
278 |
|