diff --git a/CHANGELOG.md b/CHANGELOG.md index 7579594..1d52b68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ - Fade-up entrance animations, sticky frosted header, sticky search bar - Alert form: toggle between Price Drop and Arbitrage Spread types - Developers nav link in dashboard header +- AI grading: corner crop preprocessing via sharp (8 magnified crops from front+back for corners subgrade) +- AI grading: all listing images passed to centering/edges/surface (corners uses front+back + crops only) +- eBay image resolution upgrade: s-l500 (500px) to s-l1600 (full resolution) ### Changed - Dashboard UI synced with casecomp.xyz frontend: Inter Tight + JetBrains Mono fonts, pill-style tabs/hints, ghost view button @@ -40,7 +43,9 @@ - Card identity: cleaned up long names (strips pack names, condition text from titles) - track-prices: now also tracks cards from active alerts, not just 3 hardcoded defaults - Demo condition filter: checks detectedCondition in addition to raw condition field -- Tests: 208 total (92 unit + 76 API + 40 smoke), up from 183 +- Tests: 214 total (98 unit + 76 API + 40 smoke), up from 183 +- AI grading prompts: full PSA rubric (5-10), perspective correction, per-corner/edge detail, holo-specific surface guidance +- Demo grades re-evaluated with improved prompts (more conservative scores, honest confidence) - Removed dead code: Redis import from api.js, updateCardField from card-identity.js ## 1.0.0-beta.1 (2026-05-10) diff --git a/README.md b/README.md index c54c741..eac32d8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ lib/ tcgplayer.js TCGPlayer price seeding grading/ grading.js AI pre-grading (per-subgrade, Claude/OpenAI) + preprocessing.js Corner crop extraction via sharp psa.js PSA pop reports, cert lookup, grading signal psaTiers.js PSA submission tier data data/ @@ -85,7 +86,7 @@ public/admin/ Admin dashboard (keys, stats, errors) extension/ Chrome extension: queue auto-join, drop intel terraform/ GCP infra: Cloud Run ×2, Firestore, LB + CDN, Secret Manager test/ - unit-test.js 92 unit tests + unit-test.js 98 unit tests api-test.js 76 API integration tests smoke-test.js 40 Playwright smoke tests (dashboard UI) ``` @@ -233,7 +234,7 @@ Load unpacked from `extension/` in `chrome://extensions`. ## Tests -208 tests: 92 unit (filters, grading, query builder, card identity, condition detection, demo data, resolveCardIdToQuery, findDemoByNumber) + 76 API (health, drops, webhooks, search, sold, PSA, grade, auth, admin keys, arbitrage, price-history, condition, alerts, share pages, demo validation) + 40 Playwright smoke (dashboard UI, detail panel, tabs, PSA stats, arbitrage, mobile viewport). +214 tests: 98 unit (filters, grading, query builder, card identity, condition detection, demo data, resolveCardIdToQuery, findDemoByNumber, image preprocessing, image resolution) + 76 API (health, drops, webhooks, search, sold, PSA, grade, auth, admin keys, arbitrage, price-history, condition, alerts, share pages, demo validation) + 40 Playwright smoke (dashboard UI, detail panel, tabs, PSA stats, arbitrage, mobile viewport). ## Contributing diff --git a/api.js b/api.js index 4ce0d7d..dfde7f6 100644 --- a/api.js +++ b/api.js @@ -184,8 +184,7 @@ async function storeGradeLog(record) { async function gradeItems(items, config, cardName, source) { return Promise.all(items.map(async (row) => { try { - const backImg = (row.additionalImages || [])[0]; - const extraImages = backImg ? [backImg] : []; + const extraImages = row.additionalImages || []; const g = await gradeImage(row.imageUrl, config, extraImages); if (g && !g.error) { await storeGradeLog({ diff --git a/index.js b/index.js index 39f6bc4..d1c40b9 100644 --- a/index.js +++ b/index.js @@ -270,8 +270,7 @@ async function gradeItems(items, config, counters) { const results = await Promise.all( items.map(async (row) => { try { - const backImg = (row.additionalImages || [])[0]; - const extraImages = backImg ? [backImg] : []; + const extraImages = row.additionalImages || []; const g = await gradeImage(row.imageUrl, config, extraImages); return { row, g, err: null }; } catch (e) { diff --git a/lib/data/demo.js b/lib/data/demo.js index 327b7d2..4e5948d 100644 --- a/lib/data/demo.js +++ b/lib/data/demo.js @@ -14,7 +14,7 @@ const DEMO_CARDS = { condition: "A — Mint", imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/e7caf458-db53-44cc-b9c6-c6dec1ace529/980404.jpeg", additionalImages: [{ imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/434c07ea-98b5-480d-baef-ff4dcae38e7e/980404.jpeg" }], - grade: { overall: 8.5, centering: 9.0, corners: 9.5, edges: 9.0, surface: 8.5, confidence: 0.78, mode: "llm-detailed", notes: "Grade limiter: surface — Slight texture visible under light, possible holo wear.", limitations: "", subgradeDetails: { centering: { score: 9.0, confidence: 0.80, detail: "Borders appear even, approximately 52/48 left-right and 53/47 top-bottom. Within PSA 10 threshold." }, corners: { score: 9.5, confidence: 0.75, detail: "All four corners appear sharp with no visible whitening or rounding." }, edges: { score: 9.0, confidence: 0.72, detail: "Edges appear clean with no chipping. Minor texture variation on top edge." }, surface: { score: 8.5, confidence: 0.82, detail: "Slight surface texture visible under light. Holo pattern may mask minor wear." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8, confidence: 0.55, mode: "llm-detailed", notes: "Grade limiter: centering — Front centering shows a noticeable shift with the left border appearing wider than the right, while top and bottom are fairly even; mild camera tilt slightly limits precision.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.6, detail: "Front centering shows a noticeable shift with the left border appearing wider than the right, while top and bottom are fairly even; mild camera tilt slightly limits precision." }, corners: { score: 8, confidence: 0.6, detail: "Minor whitening visible on the top-left and bottom-right corners against the rainbow holo border, but all corners retain sharp points." }, edges: { score: 8, confidence: 0.6, detail: "Minor whitening visible along the top and right edges of the front, with the dark holographic border making slight wear more apparent; bottom and left edges appear clean." }, surface: { score: 8, confidence: 0.4, detail: "Heavy holo glare and rainbow foil pattern obscure surface detail, but no obvious scratches or print lines visible; light wear possible on reflective areas limits confident higher grade." } } }, }, { itemId: "01KR0F73BG7H5THN8HZCG7VWXJ", @@ -24,7 +24,7 @@ const DEMO_CARDS = { condition: "A — Mint", imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/3ef68064-4269-44d7-b7fd-795cd293cb9c/4939452.jpeg", additionalImages: [{ imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/3492d757-4c5f-4a5c-838d-951afe1d860d/4939452.jpeg" }], - grade: { overall: 9.0, centering: 9.5, corners: 10.0, edges: 9.5, surface: 9.0, confidence: 0.85, mode: "llm-detailed", notes: "Grade limiter: surface — Minor haze on holo, within PSA 10 tolerance.", limitations: "", subgradeDetails: { centering: { score: 9.5, confidence: 0.88, detail: "Excellent centering front and back. Approximately 51/49 both axes." }, corners: { score: 10.0, confidence: 0.82, detail: "All corners are pristine — no whitening, rounding, or softness visible." }, edges: { score: 9.5, confidence: 0.85, detail: "Back image confirms clean edges. No whitening or chipping." }, surface: { score: 9.0, confidence: 0.80, detail: "Minor haze on holo surface, but within PSA 10 tolerance. No scratches." } } }, + grade: { overall: 8, centering: 9, corners: 8, edges: 8.5, surface: 9, confidence: 0.53, mode: "llm-detailed", notes: "Grade limiter: corners — Slight whitening appears visible on the bottom-left and bottom-right corners of this dark-bordered card, while the top corners look cleaner; image resolution limits close inspection.", limitations: "", subgradeDetails: { centering: { score: 9, confidence: 0.6, detail: "Card shows slight perspective tilt, but borders appear close to even with a very slight shift toward the top compared to the bottom; left/right look balanced." }, corners: { score: 8, confidence: 0.4, detail: "Slight whitening appears visible on the bottom-left and bottom-right corners of this dark-bordered card, while the top corners look cleaner; image resolution limits close inspection." }, edges: { score: 8.5, confidence: 0.6, detail: "Edges appear clean overall with possible very minor whitening on the bottom edge of the back, while front edges look sharp under the case." }, surface: { score: 9, confidence: 0.5, detail: "Holo SAR surface appears clean with no obvious scratches or print lines, but heavy reflective glare across the textured foil limits confident detection of fine surface defects." } } }, }, { itemId: "01KR0XJ2VGEPX95JF0N7FEQC8T", @@ -37,7 +37,7 @@ const DEMO_CARDS = { { imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/9fe2f9fb-7c3c-458b-9362-d015078a20a7/8486557.jpeg" }, { imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/0fc1780a-c7a8-4903-a639-6de2c8f23f90/8486557.jpeg" }, ], - grade: { overall: 7.5, centering: 7.5, corners: 9.0, edges: 9.0, surface: 9.0, confidence: 0.72, mode: "llm-detailed", notes: "Grade limiter: centering — Left border noticeably wider than right, approximately 62/38.", limitations: "", subgradeDetails: { centering: { score: 7.5, confidence: 0.78, detail: "Left border noticeably wider than right, approximately 62/38. Top-bottom closer to even at 55/45." }, corners: { score: 9.0, confidence: 0.68, detail: "Corners appear clean with no visible whitening. Photo angle limits assessment." }, edges: { score: 9.0, confidence: 0.65, detail: "Edges look clean from available angles. No chipping visible." }, surface: { score: 9.0, confidence: 0.75, detail: "Surface is clean with no scratches or print defects visible." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8.5, confidence: 0.45, mode: "llm-detailed", notes: "Grade limiter: centering — Card appears slightly tilted in photo, but front shows a noticeably wider left border than right, with top and bottom borders looking roughly even; angle limits precision.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.5, detail: "Card appears slightly tilted in photo, but front shows a noticeably wider left border than right, with top and bottom borders looking roughly even; angle limits precision." }, corners: { score: 8, confidence: 0.4, detail: "Minor whitening appears visible on the bottom-left and bottom-right corners of this dark-bordered card, though image resolution limits detailed assessment of all four corners." }, edges: { score: 8, confidence: 0.5, detail: "Minor whitening visible along the top and right edges of the front, with the other edges appearing clean; image resolution limits detailed inspection." }, surface: { score: 8.5, confidence: 0.4, detail: "Holographic SAR card with significant glare and rainbow foil reflections that obscure surface details; no obvious scratches, print lines, or contamination visible, but holo pattern and photo quality limit confident assessment." } } }, }, { itemId: "01KR13CDR08H86QTVEHDRENNX2", @@ -50,7 +50,7 @@ const DEMO_CARDS = { { imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/d48ab64e-75aa-4881-9eac-9a0fc890422e/6079962.png" }, { imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/8c5b521b-0a87-41c1-829b-db09e405f592/6079962.png" }, ], - grade: { overall: 9.0, centering: 9.5, corners: 9.5, edges: 10.0, surface: 9.0, confidence: 0.82, mode: "llm-detailed", notes: "Grade limiter: surface — Typical SAR texture, no scratches. Strong PSA 10 candidate.", limitations: "", subgradeDetails: { centering: { score: 9.5, confidence: 0.85, detail: "Near-perfect centering front and back. Approximately 52/48 both axes." }, corners: { score: 9.5, confidence: 0.80, detail: "All corners sharp. No whitening or softness detected." }, edges: { score: 10.0, confidence: 0.82, detail: "Edges are immaculate. No whitening, chipping, or roughness." }, surface: { score: 9.0, confidence: 0.78, detail: "Typical SAR texture — no scratches visible. PNG compression limits micro-assessment." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8, confidence: 0.48, mode: "llm-detailed", notes: "Grade limiter: centering — Left border appears slightly wider than right and top border looks marginally thicker than bottom, indicating a mild shift toward the bottom-right, though camera tilt limits precision.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.6, detail: "Left border appears slightly wider than right and top border looks marginally thicker than bottom, indicating a mild shift toward the bottom-right, though camera tilt limits precision." }, corners: { score: 8, confidence: 0.4, detail: "Top-left corner shows slight softness/minor whitening visible in the close-up; other corners not clearly assessable from the provided images." }, edges: { score: 8, confidence: 0.5, detail: "Top edge shows minor whitening along the holographic border, while left, right, and bottom edges appear clean with no visible chipping or nicks." }, surface: { score: 8, confidence: 0.4, detail: "Holo SAR surface shows heavy sparkle/glare that obscures fine scratch detection, but no obvious print lines, dents, or major scratches are visible; confidence limited by reflective foil pattern." } } }, }, { itemId: "01KR0QKN3G7JRW22M8K7AYQ9J0", @@ -63,7 +63,7 @@ const DEMO_CARDS = { { imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/748acb56-83b7-4bb8-b81d-b0ffcebfe7e6/8254743.jpeg" }, { imageUrl: "https://cdn.snkrdunk.com/apparel_used_listings/db3f8cf0-4e14-4807-a1c2-12d05e02d42a/8254743.jpeg" }, ], - grade: { overall: 8.5, centering: 8.5, corners: 9.5, edges: 9.0, surface: 9.0, confidence: 0.75, mode: "llm-detailed", notes: "Grade limiter: centering — Slight offset on back, approximately 58/42.", limitations: "", subgradeDetails: { centering: { score: 8.5, confidence: 0.78, detail: "Front well-centered ~52/48. Back shows slight offset ~58/42 left-right." }, corners: { score: 9.5, confidence: 0.72, detail: "Corners are crisp with no visible wear across all angles." }, edges: { score: 9.0, confidence: 0.70, detail: "Edges clean. Lighting varies between shots but no chipping visible." }, surface: { score: 9.0, confidence: 0.78, detail: "Surface clean with no scratches or defects. Holo pattern intact." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8.5, confidence: 0.53, mode: "llm-detailed", notes: "Grade limiter: centering — Card appears slightly tilted in photo, but left border looks noticeably wider than right while top and bottom appear closer to even, suggesting a horizontal shift to the right.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.6, detail: "Card appears slightly tilted in photo, but left border looks noticeably wider than right while top and bottom appear closer to even, suggesting a horizontal shift to the right." }, corners: { score: 8, confidence: 0.5, detail: "Top-left corner visible in close-up shows slight whitening along the dark border edge; other three corners cannot be clearly assessed from the provided images." }, edges: { score: 8, confidence: 0.6, detail: "Minor whitening visible along the top-left corner edge area against the dark border, with other edges appearing clean from the available view." }, surface: { score: 8.5, confidence: 0.4, detail: "Holo foil surface shows significant glare and reflective patterns that limit confident assessment; no obvious scratches or print lines visible in the visible areas, but the heavy texture/glare on the SAR finish could mask minor defects." } } }, }, // magi { @@ -117,82 +117,82 @@ const DEMO_CARDS = { itemId: "v1|178048869261|0", itemWebUrl: "https://www.ebay.com/itm/178048869261", title: "Pokemon Indonesia 2025 Umbreon Ex sv8a 217/187 SAR Special Art Rare", price: 379.90, priceCurrency: "USD", shippingLabel: "$19.00", totalCost: 398.90, condition: "Ungraded", detectedCondition: "LP", - imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l500.jpg", - additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/WUIAAeSwmlFp3n58/s-l500.jpg" }], - grade: { overall: 7.5, centering: 7.5, corners: 9.0, edges: 9.0, surface: 9.0, confidence: 0.70, mode: "llm-detailed", notes: "Grade limiter: centering — Back centering off, bottom border wider than top ~63/37.", limitations: "", subgradeDetails: { centering: { score: 7.5, confidence: 0.75, detail: "Front ~54/46 acceptable. Back noticeably off — bottom wider than top ~63/37." }, corners: { score: 9.0, confidence: 0.65, detail: "Corners appear clean. No close-up available — reduced confidence." }, edges: { score: 9.0, confidence: 0.62, detail: "Edges look clean from full shots. No close-up detail available." }, surface: { score: 9.0, confidence: 0.78, detail: "Front surface clean, no scratches or print defects visible." } } }, + imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l1600.jpg", + additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/WUIAAeSwmlFp3n58/s-l1600.jpg" }], + grade: { overall: 6, centering: 6, corners: 7, edges: 8, surface: 8, confidence: 0.40, mode: "llm-detailed", notes: "Grade limiter: centering — Front appears to have a noticeably heavier left border than right and a thicker bottom border than top, suggesting a shift up and to the right, though the card is tilted in the photo which limits precise assessment.", limitations: "", subgradeDetails: { centering: { score: 6, confidence: 0.4, detail: "Front appears to have a noticeably heavier left border than right and a thicker bottom border than top, suggesting a shift up and to the right, though the card is tilted in the photo which limits precise assessment." }, corners: { score: 7, confidence: 0.4, detail: "Slight whitening and softness appears visible on the top-right and bottom-right corners, though image resolution limits close inspection of all four corners." }, edges: { score: 8, confidence: 0.4, detail: "Edges appear mostly clean from this distance, with possible minor whitening on the top and right edges of the front; back edges not clearly visible for detailed assessment." }, surface: { score: 8, confidence: 0.4, detail: "Holographic surface with significant rainbow glare across the card limits scratch detection; no obvious print lines or major defects visible, but reflective foil could mask minor holo wear." } } }, }, { itemId: "v1|397899646795|0", itemWebUrl: "https://www.ebay.com/itm/397899646795", title: "Umbreon ex SAR 217/187 SV8a TERASTAL FEST EX Japanese Pokemon TCG", price: 400, priceCurrency: "USD", shippingLabel: "Free", totalCost: 400, condition: "Ungraded", detectedCondition: "NM", - imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l500.jpg", + imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l1600.jpg", additionalImages: [ - { imageUrl: "https://i.ebayimg.com/images/g/PcwAAeSw5zhp9JS~/s-l500.jpg" }, - { imageUrl: "https://i.ebayimg.com/images/g/TbAAAeSwto1p9JS~/s-l500.jpg" }, - { imageUrl: "https://i.ebayimg.com/images/g/~mIAAeSwcM9p9JTA/s-l500.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/PcwAAeSw5zhp9JS~/s-l1600.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/TbAAAeSwto1p9JS~/s-l1600.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/~mIAAeSwcM9p9JTA/s-l1600.jpg" }, ], - grade: { overall: 9.5, centering: 9.5, corners: 9.5, edges: 9.5, surface: 9.5, confidence: 0.88, mode: "llm-detailed", notes: "Grade limiter: centering — Near-perfect at ~52/48. Strong PSA 10 candidate.", limitations: "", subgradeDetails: { centering: { score: 9.5, confidence: 0.90, detail: "Excellent centering front and back. Approximately 52/48 both axes." }, corners: { score: 9.5, confidence: 0.88, detail: "Multiple angles confirm sharp corners. No whitening visible." }, edges: { score: 9.5, confidence: 0.85, detail: "Clean edges confirmed from multiple photos. No chipping." }, surface: { score: 9.5, confidence: 0.88, detail: "Surface pristine. No scratches, print lines, or holo wear." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8.5, confidence: 0.46, mode: "llm-detailed", notes: "Grade limiter: centering — Left border appears noticeably wider than right, top and bottom look relatively even; slight camera tilt limits precision but shift is visible.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.6, detail: "Left border appears noticeably wider than right, top and bottom look relatively even; slight camera tilt limits precision but shift is visible." }, corners: { score: 8, confidence: 0.4, detail: "Image resolution limits close inspection, but the bottom-left and bottom-right corners appear to show slight whitening along the dark border, while the top corners look relatively clean." }, edges: { score: 8, confidence: 0.5, detail: "Minor whitening visible along the top and right edges of this dark-bordered card, with the other edges appearing clean." }, surface: { score: 8.5, confidence: 0.35, detail: "Holo glare and rainbow foil pattern across the entire card surface obscure fine detail; no obvious scratches, dents, or print lines visible but reflective surface significantly limits confident assessment." } } }, }, { itemId: "v1|177832326093|0", itemWebUrl: "https://www.ebay.com/itm/177832326093", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card Japanese NM", price: 400, priceCurrency: "USD", shippingLabel: "Free", totalCost: 400, condition: "Ungraded", detectedCondition: "NM", - imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l500.jpg", - additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/7NIAAeSw8s9pgfeE/s-l500.jpg" }], - grade: { overall: 8.5, centering: 9.0, corners: 9.0, edges: 9.5, surface: 8.5, confidence: 0.74, mode: "llm-detailed", notes: "Grade limiter: surface — Faint holo texture disruption, likely print variation.", limitations: "", subgradeDetails: { centering: { score: 9.0, confidence: 0.76, detail: "Centering solid ~53/47 front. Back appears centered from single photo." }, corners: { score: 9.0, confidence: 0.70, detail: "Corners look clean. Single back photo limits full assessment." }, edges: { score: 9.5, confidence: 0.72, detail: "Edges clean with no whitening visible from available angles." }, surface: { score: 8.5, confidence: 0.78, detail: "Faint holo texture disruption under light. Likely print variation but could cost half a point." } } }, + imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l1600.jpg", + additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/7NIAAeSw8s9pgfeE/s-l1600.jpg" }], + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8, confidence: 0.43, mode: "llm-detailed", notes: "Grade limiter: centering — Card is photographed at a slight angle making precise assessment difficult, but the left border appears slightly wider than the right while top and bottom borders look fairly balanced.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.5, detail: "Card is photographed at a slight angle making precise assessment difficult, but the left border appears slightly wider than the right while top and bottom borders look fairly balanced." }, corners: { score: 8, confidence: 0.4, detail: "Minor whitening appears visible on the top-left and bottom-right corners of the dark-bordered card, though image resolution limits detailed assessment." }, edges: { score: 8, confidence: 0.4, detail: "Minor whitening appears visible along the top and right edges of the front, though image resolution limits close inspection; bottom and left edges look clean." }, surface: { score: 8, confidence: 0.4, detail: "Holo rainbow foil surface shows heavy glare and reflections that obscure detailed assessment; no obvious scratches or print lines visible but foil texture and lighting significantly limit confident evaluation." } } }, }, { itemId: "v1|397643034526|0", itemWebUrl: "https://www.ebay.com/itm/397643034526", title: "With tracking Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card", price: 415.31, priceCurrency: "USD", shippingLabel: "Free", totalCost: 415.31, condition: "Ungraded", detectedCondition: "NM", - imageUrl: "https://i.ebayimg.com/images/g/8fIAAeSwny1pnSPT/s-l500.jpg", + imageUrl: "https://i.ebayimg.com/images/g/8fIAAeSwny1pnSPT/s-l1600.jpg", additionalImages: [ - { imageUrl: "https://i.ebayimg.com/images/g/2iYAAeSwV0hpnSPT/s-l500.jpg" }, - { imageUrl: "https://i.ebayimg.com/images/g/6vkAAeSw1q9pnSPT/s-l500.jpg" }, - { imageUrl: "https://i.ebayimg.com/images/g/sF8AAeSwn4NpnSPT/s-l500.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/2iYAAeSwV0hpnSPT/s-l1600.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/6vkAAeSw1q9pnSPT/s-l1600.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/sF8AAeSwn4NpnSPT/s-l1600.jpg" }, ], - grade: { overall: 8.5, centering: 8.5, corners: 9.5, edges: 9.0, surface: 9.5, confidence: 0.82, mode: "llm-detailed", notes: "Grade limiter: centering — Front left border slightly wider ~57/43.", limitations: "", subgradeDetails: { centering: { score: 8.5, confidence: 0.80, detail: "Front left border slightly wider ~57/43. Back centering hard to judge from angle." }, corners: { score: 9.5, confidence: 0.85, detail: "Corners pristine across all angles. No whitening or softness." }, edges: { score: 9.0, confidence: 0.80, detail: "Edges clean from multiple angles. No chipping detected." }, surface: { score: 9.5, confidence: 0.82, detail: "Surface immaculate. No scratches, dents, or print defects." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 8, confidence: 0.35, mode: "llm-detailed", notes: "Grade limiter: centering — Left border appears slightly wider than right and top border slightly heavier than bottom, suggesting a mild shift toward the lower-right, though the steep camera angle limits reliability.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.4, detail: "Left border appears slightly wider than right and top border slightly heavier than bottom, suggesting a mild shift toward the lower-right, though the steep camera angle limits reliability." }, corners: { score: 8, confidence: 0.3, detail: "Only top-left and top-right corners are partially visible in the close-up; they appear sharp with no obvious whitening, but bottom corners cannot be assessed from these images." }, edges: { score: 8, confidence: 0.4, detail: "Top and right edges show very minor whitening visible against the dark holographic border, while left and bottom edges appear clean from the visible angles." }, surface: { score: 8, confidence: 0.3, detail: "Heavy glare and reflection across the holo surface obscure most of the printable area; no obvious scratches or print lines visible but holo masking significantly limits confident assessment." } } }, }, { itemId: "v1|397467499018|0", itemWebUrl: "https://www.ebay.com/itm/397467499018", title: "With tracking Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card", price: 425.65, priceCurrency: "USD", shippingLabel: "Free", totalCost: 425.65, condition: "Ungraded", detectedCondition: "LP", - imageUrl: "https://i.ebayimg.com/images/g/MK8AAeSwqEVpXH5z/s-l500.jpg", + imageUrl: "https://i.ebayimg.com/images/g/MK8AAeSwqEVpXH5z/s-l1600.jpg", additionalImages: [ - { imageUrl: "https://i.ebayimg.com/images/g/xrYAAeSwMW1pXH5z/s-l500.jpg" }, - { imageUrl: "https://i.ebayimg.com/images/g/MAoAAeSw4cVpXH5z/s-l500.jpg" }, - { imageUrl: "https://i.ebayimg.com/images/g/JFYAAeSw~d1pXH5z/s-l500.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/xrYAAeSwMW1pXH5z/s-l1600.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/MAoAAeSw4cVpXH5z/s-l1600.jpg" }, + { imageUrl: "https://i.ebayimg.com/images/g/JFYAAeSw~d1pXH5z/s-l1600.jpg" }, ], - grade: { overall: 7.5, centering: 8.0, corners: 8.0, edges: 8.5, surface: 7.5, confidence: 0.68, mode: "llm-detailed", notes: "Grade limiter: surface — Visible scratch near bottom-right of artwork.", limitations: "", subgradeDetails: { centering: { score: 8.0, confidence: 0.70, detail: "Centering acceptable ~56/44 front. Back appears slightly off." }, corners: { score: 8.0, confidence: 0.65, detail: "Corner whitening on back top-left. Other corners appear clean." }, edges: { score: 8.5, confidence: 0.68, detail: "Edges mostly clean with minor wear visible on right edge." }, surface: { score: 7.5, confidence: 0.72, detail: "Visible scratch near bottom-right of artwork. Photo compression may exaggerate but defect is clear." } } }, + grade: { overall: 8, centering: 8, corners: 8, edges: 8, surface: 9, confidence: 0.43, mode: "llm-detailed", notes: "Grade limiter: centering — Card appears slightly tilted in both photos, but the left border looks noticeably wider than the right while top/bottom appear fairly even, suggesting a mild horizontal shift; angle limits precise assessment.", limitations: "", subgradeDetails: { centering: { score: 8, confidence: 0.5, detail: "Card appears slightly tilted in both photos, but the left border looks noticeably wider than the right while top/bottom appear fairly even, suggesting a mild horizontal shift; angle limits precise assessment." }, corners: { score: 8, confidence: 0.4, detail: "Image resolution limits detailed corner inspection, but the holographic border appears intact with possible minor whitening on the bottom-left and bottom-right corners." }, edges: { score: 8, confidence: 0.4, detail: "Minor whitening appears visible along the top and right edges, though image resolution limits detailed assessment." }, surface: { score: 9, confidence: 0.4, detail: "Holo surface appears clean with no obvious scratches or print lines visible, but heavy rainbow holo reflection and photo glare significantly limit confident surface assessment." } } }, }, { itemId: "magi-umb-001", itemWebUrl: "https://magi.camp/item/umb-sar-217187-001", title: "ブラッキーex SAR 217/187 テラスタルフェスティバル ex sv8a 1枚", price: 348.73, priceCurrency: "USD", priceJPY: 54800, shippingLabel: "—", totalCost: 348.73, detectedCondition: "NM", - imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l500.jpg", + imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l1600.jpg", }, { itemId: "magi-umb-002", itemWebUrl: "https://magi.camp/item/umb-sar-217187-002", title: "〔状態A-〕ブラッキーex【SAR】{217/187} テラスタルフェスティバル 1枚", price: 362.42, priceCurrency: "USD", priceJPY: 56950, shippingLabel: "—", totalCost: 362.42, detectedCondition: "NM", - imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l500.jpg", + imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l1600.jpg", }, { itemId: "yahoo-umb-001", itemWebUrl: "https://page.auctions.yahoo.co.jp/jp/auction/umb217187001", title: "ブラッキーex SAR 217/187 sv8a テラスタルフェスティバルex ポケモンカード 美品", price: 370.06, priceCurrency: "USD", priceJPY: 58150, shippingLabel: "—", totalCost: 370.06, detectedCondition: "NM", - imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l500.jpg", + imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l1600.jpg", }, ], }, sold: [ - { itemId: "v1|397899646795|sold1", itemWebUrl: "https://www.ebay.com/itm/397899646795", title: "Umbreon ex SAR 217/187 SV8a Terastal Festival Japanese Pokemon TCG", price: 385.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-05-08", imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l500.jpg" }, - { itemId: "v1|177832326093|sold1", itemWebUrl: "https://www.ebay.com/itm/177832326093", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a Pokemon Card Japanese NM", price: 392.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-05-04", imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l500.jpg" }, - { itemId: "v1|178048869261|sold1", itemWebUrl: "https://www.ebay.com/itm/178048869261", title: "Pokemon Umbreon Ex sv8a 217/187 SAR Special Art Rare Japanese", price: 370.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-28", imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l500.jpg" }, - { itemId: "v1|397643034526|sold1", itemWebUrl: "https://www.ebay.com/itm/397643034526", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card", price: 405.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-22", imageUrl: "https://i.ebayimg.com/images/g/8fIAAeSwny1pnSPT/s-l500.jpg" }, - { itemId: "v1|397467499018|sold1", itemWebUrl: "https://www.ebay.com/itm/397467499018", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a Pokemon Card", price: 358.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-15", imageUrl: "https://i.ebayimg.com/images/g/MK8AAeSwqEVpXH5z/s-l500.jpg" }, - { itemId: "magi-umb-sold1", itemWebUrl: "https://magi.camp/item/umb-sold-001", title: "ブラッキーex SAR 217/187 sv8a 1枚", price: 342.36, priceCurrency: "USD", priceJPY: 53800, soldDate: "2026-04-10", imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l500.jpg" }, - { itemId: "v1|397899646795|sold2", itemWebUrl: "https://www.ebay.com/itm/397899646795", title: "Umbreon ex SAR 217/187 SV8a Terastal Festival Pokemon", price: 375.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-03", imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l500.jpg" }, + { itemId: "v1|397899646795|sold1", itemWebUrl: "https://www.ebay.com/itm/397899646795", title: "Umbreon ex SAR 217/187 SV8a Terastal Festival Japanese Pokemon TCG", price: 385.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-05-08", imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l1600.jpg" }, + { itemId: "v1|177832326093|sold1", itemWebUrl: "https://www.ebay.com/itm/177832326093", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a Pokemon Card Japanese NM", price: 392.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-05-04", imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l1600.jpg" }, + { itemId: "v1|178048869261|sold1", itemWebUrl: "https://www.ebay.com/itm/178048869261", title: "Pokemon Umbreon Ex sv8a 217/187 SAR Special Art Rare Japanese", price: 370.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-28", imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l1600.jpg" }, + { itemId: "v1|397643034526|sold1", itemWebUrl: "https://www.ebay.com/itm/397643034526", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card", price: 405.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-22", imageUrl: "https://i.ebayimg.com/images/g/8fIAAeSwny1pnSPT/s-l1600.jpg" }, + { itemId: "v1|397467499018|sold1", itemWebUrl: "https://www.ebay.com/itm/397467499018", title: "Umbreon ex SAR 217/187 Terastal Festival sv8a Pokemon Card", price: 358.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-15", imageUrl: "https://i.ebayimg.com/images/g/MK8AAeSwqEVpXH5z/s-l1600.jpg" }, + { itemId: "magi-umb-sold1", itemWebUrl: "https://magi.camp/item/umb-sold-001", title: "ブラッキーex SAR 217/187 sv8a 1枚", price: 342.36, priceCurrency: "USD", priceJPY: 53800, soldDate: "2026-04-10", imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l1600.jpg" }, + { itemId: "v1|397899646795|sold2", itemWebUrl: "https://www.ebay.com/itm/397899646795", title: "Umbreon ex SAR 217/187 SV8a Terastal Festival Pokemon", price: 375.00, priceCurrency: "USD", condition: "Ungraded", soldDate: "2026-04-03", imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l1600.jpg" }, ], soldSource: "scrape", psaSignal: { certNumber: null, totalPop: 3420, pop10: 1890, pop9: 1105, difficulty: "easy", gem10Pct: 55.3, grade10Chance: "high", avgDaysToGrade: 30, tier: "Regular", estCost: "$50", tierReason: "PSA 10 comps at $750+, above the $499 Value cap. Submitting at Value risks an upcharge to Regular anyway." }, @@ -214,14 +214,14 @@ const DEMO_CARDS = { title: "【PSA10】ピカチュウex SAR 234/193 1枚", price: 741, priceCurrency: "USD", priceJPY: 114855, shippingLabel: "—", totalCost: 741, condition: "PSA 10", listingGradeLabel: "PSA 10", - imageUrl: "https://i.ebayimg.com/images/g/zkYAAeSwNqNpiCOx/s-l500.jpg", additionalImages: [], grade: null, + imageUrl: "https://i.ebayimg.com/images/g/zkYAAeSwNqNpiCOx/s-l1600.jpg", additionalImages: [], grade: null, }, { itemId: "584264140", itemWebUrl: "https://magi.camp/items/584264140", title: "【PSA10】ピカチュウex SAR 234/193 1枚", price: 761.29, priceCurrency: "USD", priceJPY: 118000, shippingLabel: "—", totalCost: 761.29, condition: "PSA 10", listingGradeLabel: "PSA 10", - imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l500.jpg", additionalImages: [], grade: null, + imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l1600.jpg", additionalImages: [], grade: null, }, // yahoo { @@ -229,7 +229,7 @@ const DEMO_CARDS = { title: "ピカチュウex SAR [MEGAドリームex] M2a 234/193 (PSA10) ポケモンカード ポケカ", price: 761.29, priceCurrency: "USD", priceJPY: 118000, shippingLabel: "—", totalCost: 761.29, condition: "PSA 10", listingGradeLabel: "PSA 10", - imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l500.jpg", additionalImages: [], grade: null, + imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l1600.jpg", additionalImages: [], grade: null, }, // ebay { @@ -237,8 +237,8 @@ const DEMO_CARDS = { title: "PSA 10 Pikachu ex SAR 234/193 MEGA Dream ex M2a Pokemon Card Japanese 2025 Japan", price: 848, priceCurrency: "USD", shippingLabel: "Free", totalCost: 848, condition: "Graded", listingGradeLabel: "PSA 10", - imageUrl: "https://i.ebayimg.com/images/g/zkYAAeSwNqNpiCOx/s-l500.jpg", - additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/U1gAAeSwdvppiCOy/s-l500.jpg" }], + imageUrl: "https://i.ebayimg.com/images/g/zkYAAeSwNqNpiCOx/s-l1600.jpg", + additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/U1gAAeSwdvppiCOy/s-l1600.jpg" }], grade: null, }, { @@ -246,8 +246,8 @@ const DEMO_CARDS = { title: "PSA 10 Pikachu ex 234/193 SAR Japanese Pokemon", price: 810, priceCurrency: "USD", shippingLabel: "$140.00", totalCost: 950, condition: "Graded", listingGradeLabel: "PSA 10", - imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l500.jpg", - additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/vcwAAeSwQI1p4Xfa/s-l500.jpg" }], + imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l1600.jpg", + additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/vcwAAeSwQI1p4Xfa/s-l1600.jpg" }], grade: null, }, { @@ -255,18 +255,18 @@ const DEMO_CARDS = { title: "PSA 10 Pikachu ex SAR 234/193 MEGA Dream ex M2a Japanese Pokemon Card 2025", price: 808, priceCurrency: "USD", shippingLabel: "$150.00", totalCost: 958, condition: "Graded", listingGradeLabel: "PSA 10", - imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l500.jpg", - additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/70kAAeSwZPJp~IVp/s-l500.jpg" }], + imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l1600.jpg", + additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/70kAAeSwZPJp~IVp/s-l1600.jpg" }], grade: null, }, ], }, sold: [ - { itemId: "1978552265", itemWebUrl: "https://magi.camp/items/1978552265", title: "【PSA10】ピカチュウex SAR 234/193 1枚", price: 785.61, priceCurrency: "USD", priceJPY: 121770, listingGradeLabel: "PSA 10", soldDate: "2026-05-10", imageUrl: "https://i.ebayimg.com/images/g/zkYAAeSwNqNpiCOx/s-l500.jpg" }, - { itemId: "j1228450383", itemWebUrl: "https://auctions.yahoo.co.jp/jp/auction/j1228450383", title: "☆ PSA10 ポケモンカード ピカチュウex 234/193 SAR ☆", price: 741.94, priceCurrency: "USD", priceJPY: 115000, listingGradeLabel: "PSA 10", soldDate: "2026-05-05", imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l500.jpg" }, - { itemId: "w1228860729", itemWebUrl: "https://auctions.yahoo.co.jp/jp/auction/w1228860729", title: "ポケモンカード ピカチュウex SAR PSA10 GEM MT 234/193 M2a JP", price: 741.94, priceCurrency: "USD", priceJPY: 115000, listingGradeLabel: "PSA 10", soldDate: "2026-04-28", imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l500.jpg" }, - { itemId: "p1227663240", itemWebUrl: "https://auctions.yahoo.co.jp/jp/auction/p1227663240", title: "ポケモンカード ピカチュウex 234/193 SAR PSA10 PSA鑑定品", price: 819.41, priceCurrency: "USD", priceJPY: 127009, listingGradeLabel: "PSA 10", soldDate: "2026-04-20", imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l500.jpg" }, - { itemId: "695466231-sold", itemWebUrl: "https://magi.camp/items/695466231", title: "【PSA10】ピカチュウex SAR 234/193 1枚", price: 741, priceCurrency: "USD", priceJPY: 114855, listingGradeLabel: "PSA 10", soldDate: "2026-04-12", imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l500.jpg" }, + { itemId: "1978552265", itemWebUrl: "https://magi.camp/items/1978552265", title: "【PSA10】ピカチュウex SAR 234/193 1枚", price: 785.61, priceCurrency: "USD", priceJPY: 121770, listingGradeLabel: "PSA 10", soldDate: "2026-05-10", imageUrl: "https://i.ebayimg.com/images/g/zkYAAeSwNqNpiCOx/s-l1600.jpg" }, + { itemId: "j1228450383", itemWebUrl: "https://auctions.yahoo.co.jp/jp/auction/j1228450383", title: "☆ PSA10 ポケモンカード ピカチュウex 234/193 SAR ☆", price: 741.94, priceCurrency: "USD", priceJPY: 115000, listingGradeLabel: "PSA 10", soldDate: "2026-05-05", imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l1600.jpg" }, + { itemId: "w1228860729", itemWebUrl: "https://auctions.yahoo.co.jp/jp/auction/w1228860729", title: "ポケモンカード ピカチュウex SAR PSA10 GEM MT 234/193 M2a JP", price: 741.94, priceCurrency: "USD", priceJPY: 115000, listingGradeLabel: "PSA 10", soldDate: "2026-04-28", imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l1600.jpg" }, + { itemId: "p1227663240", itemWebUrl: "https://auctions.yahoo.co.jp/jp/auction/p1227663240", title: "ポケモンカード ピカチュウex 234/193 SAR PSA10 PSA鑑定品", price: 819.41, priceCurrency: "USD", priceJPY: 127009, listingGradeLabel: "PSA 10", soldDate: "2026-04-20", imageUrl: "https://i.ebayimg.com/images/g/g~8AAeSw7kpp~IVo/s-l1600.jpg" }, + { itemId: "695466231-sold", itemWebUrl: "https://magi.camp/items/695466231", title: "【PSA10】ピカチュウex SAR 234/193 1枚", price: 741, priceCurrency: "USD", priceJPY: 114855, listingGradeLabel: "PSA 10", soldDate: "2026-04-12", imageUrl: "https://i.ebayimg.com/images/g/ZTwAAeSwzrlp~Y3k/s-l1600.jpg" }, ], soldSource: "multi", psaSignal: { certNumber: null, totalPop: 8920, pop10: 5870, pop9: 2215, difficulty: "easy", gem10Pct: 65.8, grade10Chance: "high", avgDaysToGrade: 15, tier: "Express", estCost: "$75", tierReason: "PSA 10 comps at $741–$848. Express ($75) halves turnaround vs Regular ($50) — worth $25 on a $700+ card." }, diff --git a/lib/grading/grading.js b/lib/grading/grading.js index 9d0f90e..6934dbe 100644 --- a/lib/grading/grading.js +++ b/lib/grading/grading.js @@ -1,41 +1,135 @@ import axios from "axios"; import { sha256 } from "../data/cache.js"; import { cacheGet, cacheSet } from "../data/firestore.js"; +import { cropCorners, cornerCropsToImageBlocks } from "./preprocessing.js"; const CACHE_COLLECTION = "cache-grades"; const CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000; const SUBGRADE_PROMPTS = { - centering: `Grade ONLY the centering of this Pokémon trading card photo. Measure how even the borders are on all sides. PSA 10 requires 55/45 or better front, 75/25 or better back. Look at left vs right and top vs bottom border widths. Be precise — centering is measurable, not subjective. + centering: `Grade ONLY the centering of this Pokémon trading card photo. + +PERSPECTIVE CORRECTION: Listing photos are rarely taken perfectly flat. Before assessing centering, identify the camera angle from the card's shape — if edges converge (trapezoid instead of rectangle), the card is tilted. Mentally project the card to a flat top-down view before comparing borders. Do NOT penalize centering for perspective distortion caused by camera angle. + +TECHNIQUE: Describe what you observe rather than computing exact ratios. Compare left vs right borders, then top vs bottom borders. Note which direction any shift goes (e.g. "shifted slightly left" or "heavier bottom border"). + +PSA CENTERING THRESHOLDS (front / back): +- 10 (Gem Mint): 55/45 or better front, 75/25 or better back +- 9 (Mint): 60/40 front, 90/10 back +- 8 (NM-MT): 65/35 front, 90/10 back +- 7 (NM): 70/30 front, 90/10 back +- 6 (EX-MT): 80/20 front, 90/10 back + +SCORING GUIDE: +- 10: Borders appear equal on all sides within normal printing tolerance +- 9: Slight shift in one direction, barely noticeable without close inspection +- 8: Noticeable shift — one border clearly wider than its opposite +- 7: Obvious shift — one border roughly 2x its opposite +- 6 or below: Severe shift visible at a glance + +If the photo angle is steep or the card is heavily tilted, set confidence below 0.5 — precise centering cannot be reliably assessed from angled photos. Respond ONLY with valid JSON (no markdown): -{"score": , "confidence": , "detail": ""}`, +{"score": , "confidence": , "detail": ""}`, + + corners: `Grade ONLY the corners of this Pokémon trading card photo. Examine each of the 4 corners individually: top-left, top-right, bottom-left, bottom-right. + +WHAT TO LOOK FOR per corner: +- Whitening: white fibers visible along the corner edge (most common defect) +- Rounding: corner lost its sharp point, appears soft or curved +- Dings/dents: physical impact marks, often small indentations +- Lifting: layers of cardstock separating at the corner +- Fuzzing: frayed edge fibers at the corner point - corners: `Grade ONLY the corners of this Pokémon trading card photo. Check all 4 corners for whitening, rounding, dings, or softness. PSA 10 corners must be sharp with no visible wear. If no close-up is available, note reduced confidence but still estimate. +PSA CORNER THRESHOLDS: +- 10 (Gem Mint): All 4 corners sharp and clean, no whitening under magnification +- 9 (Mint): Corners sharp, may have one tiny spot of whitening only visible under magnification +- 8 (NM-MT): Minor whitening on 1-2 corners, still sharp points +- 7 (NM): Slight whitening or softness on 2-3 corners +- 6 (EX-MT): Noticeable whitening or slight rounding on multiple corners +- 5 (EX): Moderate whitening, possible rounding on 1+ corners + +DARK-BORDERED CARDS: Whitening is more visible and more harshly penalized on dark/black borders. Grade these more critically. + +If the photo lacks close-up detail, note which corners you can and cannot assess clearly, and set confidence accordingly. Respond ONLY with valid JSON (no markdown): -{"score": , "confidence": , "detail": ""}`, +{"score": , "confidence": , "detail": ""}`, + + edges: `Grade ONLY the edges of this Pokémon trading card photo. Examine each of the 4 edges individually: top, bottom, left, right. + +WHAT TO LOOK FOR per edge: +- Whitening: white line along the edge where color has worn away +- Chipping: small chips or flakes missing from the edge +- Nicks: tiny cuts or indentations along the edge +- Roughness: uneven or jagged edge surface +- Peeling: cardstock layers separating along the edge + +PSA EDGE THRESHOLDS: +- 10 (Gem Mint): All edges clean and smooth, no whitening or wear +- 9 (Mint): Edges clean, one minor spot of whitening only visible under magnification +- 8 (NM-MT): Minor whitening on 1-2 edges, no chipping +- 7 (NM): Light whitening along 2+ edges, or one small chip +- 6 (EX-MT): Noticeable whitening on multiple edges, minor chipping possible +- 5 (EX): Moderate whitening, possible chipping on 1+ edges + +DARK-BORDERED CARDS: Edge whitening is far more visible against dark/black card borders. Apply stricter standards. - edges: `Grade ONLY the edges of this Pokémon trading card photo. Check all 4 edges for whitening, chipping, nicks, or roughness. PSA 10 edges must be clean with no visible wear. If no close-up is available, note reduced confidence but still estimate. +BACK EDGES: If a back image is provided, back edges often show more wear than the front — check carefully. + +If the photo lacks close-up detail, note which edges you can assess and set confidence accordingly. Respond ONLY with valid JSON (no markdown): -{"score": , "confidence": , "detail": ""}`, +{"score": , "confidence": , "detail": ""}`, + + surface: `Grade ONLY the surface of this Pokémon trading card photo. Assess the entire printable area of the card. + +WHAT TO LOOK FOR: +- Scratches: linear marks across the surface, often visible when light catches them +- Print lines: factory printing defects, thin lines running through the card +- Ink spots/blotches: spots of excess ink or missing ink +- Dents/indentations: depressions in the cardstock visible as shadows +- Holo wear/scratching: wear patterns on holographic/foil areas +- Surface contamination: fingerprints, residue, sticker marks, or foreign material +- Creasing: any crease, even minor, severely limits grade (PSA 5 max for crease <1 inch) - surface: `Grade ONLY the surface of this Pokémon trading card photo. Check for scratches, dents, print lines, ink spots, holo wear, or surface contamination. PSA 10 surface must be free of visible defects. Holo patterns can mask flaws — note if this limits your assessment. +PSA SURFACE THRESHOLDS: +- 10 (Gem Mint): Surface immaculate, no defects visible even under magnification +- 9 (Mint): Surface clean, one minor print imperfection allowed if not immediately noticeable +- 8 (NM-MT): Minor surface wear or one small print line, no scratches +- 7 (NM): Light surface wear, minor print defects, or one faint scratch +- 6 (EX-MT): Noticeable surface wear, light scratches, or print defects +- 5 (EX): Moderate scratches, print defects, or minor surface damage + +HOLOGRAPHIC/FOIL CARDS: Holo surfaces reflect light differently at various angles, which can both mask and reveal scratches. Note when holo patterns limit your assessment — glare in the photo may hide real scratches, so lower confidence if holo area is washed out or reflective. Do NOT assume a clean surface just because glare obscures it. + +PHOTO QUALITY: Listing photos are often low-resolution or poorly lit. Surface defects are the hardest to detect from photos. If the image quality prevents confident surface assessment, set confidence below 0.5. Respond ONLY with valid JSON (no markdown): -{"score": , "confidence": , "detail": ""}`, +{"score": , "confidence": , "detail": ""}`, }; -const GRADING_PROMPT = `You are estimating the PSA grade for a Pokémon trading card based on listing photos. You may receive 1-2 images (front, back, or both). PSA grades cards 1-10 on: -- CENTERING: borders should be even on all sides (PSA 10 ≈ 55/45 or better). Check both front AND back if available — back centering is often the grade limiter. -- CORNERS: should be sharp, no whitening or rounding -- EDGES: should be clean, no whitening or chipping -- SURFACE: no scratches, dents, print defects, or holo wear +const GRADING_PROMPT = `You are estimating the PSA grade for a Pokémon trading card based on listing photos. You may receive 1-2 images (front, back, or both). + +PERSPECTIVE CORRECTION: Listing photos are rarely taken flat. If the card appears as a trapezoid rather than a rectangle, mentally project it to a top-down view before assessing centering. Do NOT penalize centering for camera-angle distortion. -Be conservative. eBay listing photos are often poor quality, glare-heavy, or hide defects. When uncertain, grade lower and note low confidence. Most listed raw cards grade between PSA 6-9; PSA 10 is rare. +PSA GRADE SCALE: +- 10 (Gem Mint): Virtually perfect. Centering 55/45+, sharp corners, clean edges, flawless surface. +- 9 (Mint): One minor flaw. Centering 60/40, one tiny whitening spot, or one faint print line. +- 8 (NM-MT): Minor wear. Slight centering shift, whitening on 1-2 corners, minor edge wear. +- 7 (NM): Noticeable wear. Off-center, whitening on 2-3 corners, light edge/surface wear. +- 6 (EX-MT): Moderate wear across multiple attributes. +- 5 (EX) or below: Significant wear, possible creases, heavy whitening. -If only front/back full shots are provided (no corner/edge close-ups), you CAN still grade centering and surface reliably. For corners and edges, grade what you can see but note reduced confidence. Do NOT refuse to grade — give your best estimate. +SUBGRADE GUIDANCE: +- CENTERING: Describe border asymmetry rather than computing exact ratios. Check front AND back — back centering is often the grade limiter. +- CORNERS: Check all 4 individually. Dark-bordered cards show whitening more — grade stricter. +- EDGES: Check all 4 individually. Back edges often worse than front. +- SURFACE: Holo/foil cards may hide scratches under glare — do NOT assume clean when glare obscures. Creases cap at PSA 5. + +Be conservative. Most listed raw cards grade PSA 6-9; PSA 10 is rare. When uncertain, grade lower and note low confidence. + +If only full shots are provided (no close-ups), you CAN still grade centering and surface reliably. For corners and edges, grade what you can see but lower confidence. Do NOT refuse to grade — give your best estimate. Respond ONLY with valid JSON in this exact shape (no markdown, no prose): { @@ -44,9 +138,9 @@ Respond ONLY with valid JSON in this exact shape (no markdown, no prose): "corners": , "edges": , "surface": , - "confidence": , + "confidence": , "notes": "", - "limitations": "" + "limitations": "" }`; let lastLlmAt = 0; @@ -374,7 +468,7 @@ export async function gradeViaSite(imageUrl, config) { } } -async function gradeSubgrade(subgrade, imageUrls, config) { +async function gradeSubgrade(subgrade, imageUrls, config, extraBlocks = []) { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) return null; await throttleLlm(); @@ -382,7 +476,7 @@ async function gradeSubgrade(subgrade, imageUrls, config) { const body = { model: config.aiGrading.llm.model, max_tokens: 200, - messages: [{ role: "user", content: [...imageBlocks, { type: "text", text: SUBGRADE_PROMPTS[subgrade] }] }], + messages: [{ role: "user", content: [...imageBlocks, ...extraBlocks, { type: "text", text: SUBGRADE_PROMPTS[subgrade] }] }], }; const res = await withLlm429Backoff(() => axios.post("https://api.anthropic.com/v1/messages", body, { @@ -396,14 +490,26 @@ async function gradeSubgrade(subgrade, imageUrls, config) { return { score: clampSub(parsed.ok.score), confidence: clampConf(parsed.ok.confidence), detail: parsed.ok.detail || "" }; } -export async function gradeDetailedLLM(frontUrl, backUrl, config) { - const frontImages = [frontUrl].filter(Boolean); - const backImages = [backUrl].filter(Boolean); - const allImages = [...frontImages, ...backImages]; +export async function gradeDetailedLLM(frontUrl, backUrl, config, extraImages = []) { + const known = new Set([frontUrl, backUrl].filter(Boolean)); + const deduped = extraImages + .map(e => (typeof e === "string" ? e : e?.imageUrl)) + .filter(u => u && !known.has(u)); + const allImages = [frontUrl, backUrl, ...deduped].filter(Boolean); + + let cornerBlocks = []; + try { + const cropJobs = [frontUrl, backUrl].filter(Boolean).map(url => cropCorners(url)); + const allCrops = (await Promise.all(cropJobs)).flat(); + cornerBlocks = cornerCropsToImageBlocks(allCrops); + } catch (e) { + console.warn(`[grade] corner crop failed, using full images: ${e.message || e}`); + } + const primaryImages = [frontUrl, backUrl].filter(Boolean); const [centering, corners, edges, surface] = await Promise.all([ gradeSubgrade("centering", allImages, config), - gradeSubgrade("corners", allImages, config), + gradeSubgrade("corners", primaryImages, config, cornerBlocks), gradeSubgrade("edges", allImages, config), gradeSubgrade("surface", allImages, config), ]); @@ -455,8 +561,9 @@ export async function gradeImage(imageUrl, config, extraImages = []) { try { if (config.aiGrading.mode === "llm") { const backImg = extraImages[0]?.imageUrl || extraImages[0] || null; + const remainingExtras = extraImages.slice(1); if (backImg && config.aiGrading.llm.provider === "claude") { - result = await gradeDetailedLLM(imageUrl, backImg, config); + result = await gradeDetailedLLM(imageUrl, backImg, config, remainingExtras); } if (!result) { result = await gradeViaLLM(imageUrl, config, extraImages); diff --git a/lib/grading/preprocessing.js b/lib/grading/preprocessing.js new file mode 100644 index 0000000..4b356ad --- /dev/null +++ b/lib/grading/preprocessing.js @@ -0,0 +1,53 @@ +import sharp from "sharp"; +import axios from "axios"; + +const CORNER_RATIO = 0.20; + +export async function cropCorners(imageUrl) { + const res = await axios.get(imageUrl, { + responseType: "arraybuffer", + timeout: 15_000, + maxRedirects: 5, + }); + + const img = sharp(Buffer.from(res.data)); + const { width, height } = await img.metadata(); + if (!width || !height) throw new Error("could not read image dimensions"); + + const cw = Math.round(width * CORNER_RATIO); + const ch = Math.round(height * CORNER_RATIO); + + const regions = [ + { name: "top-left", left: 0, top: 0 }, + { name: "top-right", left: width - cw, top: 0 }, + { name: "bottom-left", left: 0, top: height - ch }, + { name: "bottom-right", left: width - cw, top: height - ch }, + ]; + + const crops = await Promise.all( + regions.map(async (r) => { + const buf = await sharp(Buffer.from(res.data)) + .extract({ left: r.left, top: r.top, width: cw, height: ch }) + .jpeg({ quality: 90 }) + .toBuffer(); + return { + name: r.name, + base64: buf.toString("base64"), + mediaType: "image/jpeg", + }; + }), + ); + + return crops; +} + +export function cornerCropsToImageBlocks(crops) { + return crops.map((c) => ({ + type: "image", + source: { + type: "base64", + media_type: c.mediaType, + data: c.base64, + }, + })); +} diff --git a/lib/sources/ebay.js b/lib/sources/ebay.js index 633de1b..6a5ee8f 100644 --- a/lib/sources/ebay.js +++ b/lib/sources/ebay.js @@ -267,6 +267,11 @@ function itemHasFixedPriceOption(item) { return true; } +function upgradeEbayImageRes(url) { + if (typeof url !== "string") return url; + return url.replace(/\/s-l\d+\.(jpg|png|webp)/gi, "/s-l1600.$1"); +} + function normalizeBrowseItem(item) { const priceVal = parseFloat(item.price?.value ?? "NaN"); const currency = item.price?.currency ?? "USD"; @@ -292,7 +297,7 @@ function normalizeBrowseItem(item) { const totalCost = (Number.isNaN(priceVal) ? 0 : priceVal) + (Number.isNaN(shippingCost) ? 0 : shippingCost); - const add = (item.additionalImages || []).map((x) => x.imageUrl).filter(Boolean); + const add = (item.additionalImages || []).map((x) => upgradeEbayImageRes(x.imageUrl)).filter(Boolean); const lids = item.leafCategoryIds; const conditionTop = item.condition || item.conditionId || ""; const conditionFacetStr = listingConditionFacetFromItem(item); @@ -316,7 +321,7 @@ function normalizeBrowseItem(item) { condition: conditionTop, conditionFacet: conditionFacetStr, listingGradeLabel, - imageUrl: item.image?.imageUrl || null, + imageUrl: upgradeEbayImageRes(item.image?.imageUrl) || null, additionalImages: add, raw: item, }; @@ -345,7 +350,7 @@ function insightsToSold(entry) { entry.transactionPrice?.currency || "USD", endedDate: date, - imageUrl: entry.image?.imageUrl || null, + imageUrl: upgradeEbayImageRes(entry.image?.imageUrl) || null, raw: entry, }; } @@ -759,7 +764,7 @@ function parseSoldTilesFromHtml(html) { price, currency: "USD", endedDate: (ended || "").trim(), - imageUrl: img || null, + imageUrl: upgradeEbayImageRes(img) || null, raw: { priceText }, }); } diff --git a/test/unit-test.js b/test/unit-test.js index 5d9b8bf..4456959 100644 --- a/test/unit-test.js +++ b/test/unit-test.js @@ -1,4 +1,5 @@ import { parseGradeJSON } from "../lib/grading/grading.js"; +import { cornerCropsToImageBlocks } from "../lib/grading/preprocessing.js"; import { buildEbaySearchQuery, describeListingSearch } from "../lib/search/listingQuery.js"; import { filterByCondition, @@ -703,6 +704,85 @@ test("condition filter with detectedCondition works", () => { assert(items.length === 5, `expected 5 mint, got ${items.length}`); }); +// ── cornerCropsToImageBlocks ── + +console.log("\n\x1b[1m=== cornerCropsToImageBlocks ===\x1b[0m"); + +test("converts crops to Anthropic image blocks", () => { + const crops = [ + { name: "top-left", base64: "abc123", mediaType: "image/jpeg" }, + { name: "top-right", base64: "def456", mediaType: "image/jpeg" }, + ]; + const blocks = cornerCropsToImageBlocks(crops); + eq(blocks.length, 2); + eq(blocks[0].type, "image"); + eq(blocks[0].source.type, "base64"); + eq(blocks[0].source.media_type, "image/jpeg"); + eq(blocks[0].source.data, "abc123"); +}); + +test("returns empty array for empty input", () => { + const blocks = cornerCropsToImageBlocks([]); + eq(blocks.length, 0); +}); + +// ── Demo image resolution ── + +console.log("\n\x1b[1m=== demo image resolution ===\x1b[0m"); + +test("eBay demo images use s-l1600 resolution", () => { + for (const query of listDemoCards()) { + const r = getDemoResult(query); + const items = r.activeByCountry?.US || []; + for (const item of items) { + if (item.imageUrl?.includes("ebayimg.com")) { + assert(item.imageUrl.includes("s-l1600"), `${item.itemId} still uses low-res: ${item.imageUrl}`); + } + } + for (const s of r.sold || []) { + if (s.imageUrl?.includes("ebayimg.com")) { + assert(s.imageUrl.includes("s-l1600"), `sold ${s.itemId} still uses low-res`); + } + } + } +}); + +test("no s-l500 URLs remain in demo data", () => { + for (const query of listDemoCards()) { + const r = getDemoResult(query); + const json = JSON.stringify(r); + assert(!json.includes("s-l500"), `${query} still has s-l500 URLs`); + } +}); + +// ── Demo grade confidence (conservative with new prompts) ── + +console.log("\n\x1b[1m=== demo grade confidence ===\x1b[0m"); + +test("graded demo listings have conservative confidence", () => { + for (const key of ["mega greninja ex sar", "umbreon ex sar 217/187"]) { + const r = getDemoResult(key); + const items = (r.activeByCountry?.US || []).filter(i => i.grade); + for (const item of items) { + assert(item.grade.confidence <= 0.7, `${item.itemId} confidence ${item.grade.confidence} too high for listing photos`); + } + } +}); + +test("graded demos have descriptive detail text", () => { + for (const key of ["mega greninja ex sar", "umbreon ex sar 217/187"]) { + const r = getDemoResult(key); + const items = (r.activeByCountry?.US || []).filter(i => i.grade); + for (const item of items) { + const details = item.grade.subgradeDetails; + assert(details.centering.detail.length > 30, `${item.itemId} centering detail too short`); + assert(details.corners.detail.length > 30, `${item.itemId} corners detail too short`); + assert(details.edges.detail.length > 30, `${item.itemId} edges detail too short`); + assert(details.surface.detail.length > 30, `${item.itemId} surface detail too short`); + } + } +}); + // ── Summary ── console.log(`\n\x1b[1m=== ${passed} passed, ${failed} failed ===\x1b[0m\n`);