--- 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.