deckhearth/.convoys/pricing-online-local/brief-2-pricing-foundation-lib.md

57 lines
2.8 KiB
Markdown
Raw Normal View History

---
convoy: pricing-online-local
brief_number: 2
depends_on: [1]
recommended_model: composer-2.5
model_tier: standard
files:
- lib/pricing/card-price.js
- lib/pricing/price-history.js
- lib/pricing/collection-valuation.js
- lib/pricing/outbound-links.js
- test/lib/pricing/card-price.test.js
- test/lib/pricing/price-history.test.js
- test/lib/pricing/collection-valuation.test.js
cross_brief_commitments:
- brief: 3
description: |
Briefs 3–5 import `recordPriceSnapshots` from `lib/pricing/price-history.js`
and set `prices_updated_at` after successful price writes. They must call
the shared helpers rather than duplicating snapshot SQL.
---
# Brief 2: Pricing foundation lib
## Goal (1 sentence)
Create shared pricing helpers for foil-aware unit prices, daily history snapshots, collection valuation SQL, and outbound buy links.
## Files in scope (do not edit anything else)
- `lib/pricing/card-price.js`
- `lib/pricing/price-history.js`
- `lib/pricing/collection-valuation.js`
- `lib/pricing/outbound-links.js`
- `test/lib/pricing/card-price.test.js`
- `test/lib/pricing/price-history.test.js`
- `test/lib/pricing/collection-valuation.test.js`
## Conventions to follow
- ESM (`import`/`export`); DB via `import { sql } from '../sql.js'` (relative from `lib/pricing/`).
- `card-price.js`: export `resolveUnitPrice({ currentPrice, marketPrice, isFoil })` — nonfoil prefers `current_price`, foil prefers `market_price`; export `PRICE_STALE_HOURS = 24`.
- `collection-valuation.js`: export `effectiveUnitPriceExpr({ cardAlias = 'c', userCardAlias = 'uc' })` returning SQL `CASE WHEN uc.is_foil THEN c.market_price ELSE COALESCE(c.current_price, c.market_price) END`; export `listUnitPriceExpr({ cardAlias = 'c' })` → `COALESCE(c.current_price, c.market_price)`.
- `price-history.js`: export `recordPriceSnapshots(rows)` where each row is `{ cardId, source, priceType, currency, amount }`; upsert on daily UNIQUE key.
- `outbound-links.js`: extract TCGPlayer + eBay URL logic currently inline in `CardDetailView.js` (game-aware).
- Mirror test style from `test/lib/collection-vocabulary.test.js`.
## Acceptance criteria
- [ ] `resolveUnitPrice` returns foil vs nonfoil correctly for MTG-shaped data
- [ ] `recordPriceSnapshots` is idempotent for same card/day/source/type
- [ ] `effectiveUnitPriceExpr` produces valid SQL fragment (unit-tested via known inputs or integration mock)
- [ ] `buildTcgplayerUrl(card)` and `buildEbayUrl(card)` exported
- [ ] All three test files pass (`npm run test:run`)
- [ ] No scope expansion
## Rationale (≤3 sentences)
Centralizes pricing semantics so bulk sync, valuation fix, and APIs cannot drift. The foil bug (`market_price` = Scryfall `usd_foil`) is fixed once at the helper layer. Snapshot writer gives Briefs 3–5 a single hook.