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%
3.7 KB · 63 lines typescript
Raw Blame History
1import { describe, expect, it } from 'vitest';2import { assessLot } from './auction.js';3import { feeBasisLabel } from './fees.js';45const riv = { rivUsd: 10_000, confidence: 0.8, sampleSize: 30, basis: 'transactions' as const };67describe('assessLot (§33–§35)', () => {8  it('uses the low estimate when there is no bid, on an all-in basis', () => {9    const a = assessLot({ house: "Sotheby's", currentBidUsd: null, bidCount: 0, estimateLowUsd: 6_000, estimateHighUsd: 9_000, riv });10    expect(a.basedOn).toBe('estimate');11    expect(a.allInBidUsd).toBeNull();12    expect(a.allInEstimateLowUsd).toBe(7_620); // 6,000 × 1.2713    expect(a.allInEstimateHighUsd).toBe(11_430);14    expect(a.estimateVsRiv).toBeCloseTo(-0.238, 3);15    expect(a.verdict).toBe('deal');16    expect(a.feeBasis).toBe('added_published');17    expect(a.buyerPremiumRate).toBe(0.27);18  });19  it('uses the all-in current bid when bids exist', () => {20    const a = assessLot({ house: 'Goldin', currentBidUsd: 7_000, bidCount: 3, estimateLowUsd: 5_000, estimateHighUsd: 8_000, riv });21    expect(a.basedOn).toBe('bid');22    expect(a.allInBidUsd).toBe(8_400); // 20 % premium23    expect(a.bidVsRiv).toBeCloseTo(-0.16, 3);24    expect(a.verdict).toBe('deal');25  });26  it('an opening bid with no bidders is ignored (it is not a price)', () => {27    const a = assessLot({ house: 'Goldin', currentBidUsd: 1, bidCount: 0, estimateLowUsd: null, estimateHighUsd: null, riv });28    expect(a.basedOn).toBe('none');29    expect(a.verdict).toBe('ungated');30    expect(a.bidVsRiv).toBeNull();31  });32  it('unknown house → default premium, labelled as such', () => {33    const a = assessLot({ house: 'Enchères du Coin', currentBidUsd: 8_000, bidCount: 2, estimateLowUsd: null, estimateHighUsd: null, riv });34    expect(a.feeBasis).toBe('added_default');35    expect(a.allInBidUsd).toBe(9_760); // 22 %36    expect(feeBasisLabel(a.feeBasis)).toContain('default');37  });38  it('variant mismatch → ungated, no discount written', () => {39    const a = assessLot({ house: 'Goldin', currentBidUsd: 5_000, bidCount: 4, estimateLowUsd: null, estimateHighUsd: null, riv, sameVariant: false });40    expect(a.verdict).toBe('ungated');41    expect(a.reasons).toContain('variant_mismatch');42    expect(a.bidVsRiv).toBeNull();43    expect(a.allInBidUsd).toBe(6_000); // the all-in cost is still informative44  });45  it('review and anomaly verdicts never produce a usable discount', () => {46    expect(assessLot({ house: 'Goldin', currentBidUsd: 2_000, bidCount: 5, estimateLowUsd: null, estimateHighUsd: null, riv }).verdict).toBe('review'); // 2,400 all-in vs 10,00047    const anomaly = assessLot({ house: 'Goldin', currentBidUsd: 200, bidCount: 5, estimateLowUsd: null, estimateHighUsd: null, riv });48    expect(anomaly.verdict).toBe('anomaly');49    expect(anomaly.bidVsRiv).toBeNull();50  });51  it('comps-only or thin valuations never qualify a lot', () => {52    expect(assessLot({ house: 'Goldin', currentBidUsd: 7_000, bidCount: 3, estimateLowUsd: null, estimateHighUsd: null, riv: { ...riv, basis: 'comps' } }).verdict).toBe('ungated');53    expect(assessLot({ house: 'Goldin', currentBidUsd: 7_000, bidCount: 3, estimateLowUsd: null, estimateHighUsd: null, riv: { ...riv, sampleSize: 3 } }).verdict).toBe('ungated');54    expect(assessLot({ house: 'Goldin', currentBidUsd: 7_000, bidCount: 3, estimateLowUsd: null, estimateHighUsd: null, riv: null }).reasons).toContain('no_riv');55  });56  it('fee basis labels are human-readable', () => {57    expect(feeBasisLabel('included')).toBe('premium included');58    expect(feeBasisLabel('none')).toBe('no buyer premium');59    expect(feeBasisLabel('unknown')).toBe('fees unknown');60    expect(feeBasisLabel('added_approximate')).toContain('estimated');61  });62});63