card-grader
Centering ceiling, sold value and the best live copies for a trading card, from one photo. Not a grade.

Photo -> measured centering ceiling, sold values, a grade-or-not verdict and the cheapest live copies, for trading cards (Pokemon, Yu-Gi-Oh, MTG, sports).
Status: 0.2.2, no grade claim. Iteration 2 missed the bar for a grade predictor (on 64 PSA slab photos: answered 21/64, within one grade 4/21 as a guess, the centering cap below the real grade on 3/21). So this is a centering + grade-or-not economics + best live copies tool. card_grade returns the measured centering (perspective-corrected, refused unless two independent measurers agree) and centering_ceiling, the highest grade PSA's centering table allows, never a predicted grade. Iteration 3 benched a vision model's grade on the same 64 slabs (card cropped, label hidden). It answered 60/64 and was within one grade 23/60 (38%), alone or fused with the ceiling (22/60). That equals always guessing PSA 6 (23/60), so no grade or range claim was restored. Details: BENCH.md.
Proof (live run, 2026-11-07, Pikachu 001/SV-P promo photo)
Before: a collector looks at a raw card and guesses whether grading pays. After, from one photo:
card_grade -> centering_ceiling 10 (PSA centering table), vision_opinion off
card_value -> PSA ladder: 7 $19.00 · 8 $23.15 · 9 $35.06 · 10 $68.50, raw $24.58
card_verdict -> expected slab $30.50 - raw $24.58 - fee $25 - ship $20 = net -$39.08 -> grade_it: false
card_hunt -> Mercari JP live median ask ¥2,450; best copy ¥800 (0.33x median)
The verdict's odds are the bench prior, capped at the measured ceiling, and say so (odds_basis). card-grader never
outputs a predicted grade: on 64 PSA slab photos the best model tested was within one grade on 38%, the same as always
guessing PSA 6 (see BENCH.md).
Disclosure
Tools only, no hooks or middleware; network calls to www.pricecharting.com (public pages, browser User-Agent), api.tcgdex.net, jp.mercari.com (through the mercapi package) and api.ebay.com (only with your own EBAY_OAUTH_TOKEN, read through Hermes's secret scope); reads only the photo paths you pass; writes rendered measurement JPEGs to the system temp dir; the optional vision opinion sends the card photo to your own configured model through ctx.llm (vision=false skips it); no telemetry; no grade prediction.
Install
Requires Hermes >=0.21.6.
hermes plugins install <path-or-repo>/card-grader
hermes plugins validate card-grader --install-deps
Dependencies (pyproject): opencv-python-headless, numpy, mercapi. Optional: EBAY_OAUTH_TOKEN, your own eBay Browse API token.
Tools
| tool | what it does |
|---|---|
| card_grade(image, back?, vision?) | finds the card (bare, or inside a PSA slab), warps it flat from its 4 corners, measures L/R and T/B centering three ways and reports it only when two agree, gives centering_ceiling per PSA's published table (front 55/45 for a 10, 60/40 for a 9 ...), checks corner/edge whitening and photo clarity, adds an optional surface/holo/whitening vision_opinion from your own configured model (ctx.llm), and renders the measured lines |
| card_value(name, set, number) | PriceCharting sold-price averages (raw, PSA 7-10) with source URL and date; eBay active listings if you set your own key. PriceCharting answers HTTP 403 (Cloudflare) to datacenter servers: then the 403 is reported plainly and TCGdex supplies raw (ungraded) TCGplayer/Cardmarket market prices for Pokemon, flagged as not graded and not sold. Errors are returned, never replaced by a guess |
| card_verdict(values or name, centering_ceiling or image) | expected slab value at the bench's prior grade odds (bench_odds.json), cut at the centering ceiling, minus raw, PSA fee and shipping -> grade_it true/false with the odds and their basis |
| card_hunt(query) | live Mercari JP listings ranked by price against the listing median |
Example
> card_verdict {"values": {"raw": 40, "psa8": 70, "psa9": 120, "psa10": 400}, "centering_ceiling": 8}
{"expected_slab_value": ..., "net": ..., "grade_it": ..., "odds": {...}, "odds_basis": "bench prior (all 64 slabs; too few bench cards at this pre-grade), n=64"}
Centering is deterministic OpenCV; the only model call is the optional vision opinion, through your own configured model. Tests: python -m pytest tests (12: synthetic cards centred/off-centre/worn/no card, no-grade-claim, vision opinion with a fake and a broken host model, ceiling-capped verdict, parser, manifest match).
License: MIT.
README from the reviewed commit 0fb4c4c; it updates when the author re-pins.

Agenziamaia