Commit graph

8 commits

Author SHA1 Message Date
Barely Removable
22bb14e5d9 Detect the card INSIDE a holder, not the holder's outline
_detect_card_box works from 'what isn't background', so on a card shot in a
toploader, sleeve or slab it finds the HOLDER — the card is never the
outermost non-background thing. Every downstream crop was then cut against
plastic instead of cardstock.

Per-side edge detection can't disambiguate: a holder's rim is as strong a
step as the card's cut, and on a card sitting off-centre in its holder the
two land at different insets per side (verified on the Jordan: card's left
edge at 75px, top edge at ~40px, holder rim at ~34px). What separates them
is that a CARD has known proportions and a holder does not -- so this
searches combinations of candidate edges and keeps whichever rectangle best
matches a real card. Jordan 7.0% -> 0.3% off standard; Ohtani 5.8% -> 0.4%.
Verified visually: both crops are the bare card, holder gone.

Conservative by construction -- the inner box must land within 2.5% of a
standard ratio AND beat the outer by 2.5 points. A correctly-detected bare
card already sits near 0%, so nothing inside it can clear that bar and the
refinement declines. Confirmed against six normal-photo variants (tight
margin, large margin, dark background, die-cut, near-square): none refined.

Two consequences handled rather than papered over:

- aspect_profile deliberately keeps using the RAW box. The refinement picks
  the rectangle that best matches a standard ratio, so measuring that and
  asking 'is this a standard ratio?' is circular and would clear a genuinely
  trimmed card. It now refuses on holdered cards instead.

- edge_wear_profile refuses on holdered cards even though the card is now
  located. Whitening is detected as the outermost band reading lighter than
  the one inside it, and a holder's inner edge sits exactly there: measured
  58%/53% 'whitening' on two Jordan edges the model called clean reading the
  same strips. Centering does NOT refuse -- it compares border widths, which
  clear plastic doesn't distort (Ohtani now reads 51/49, having previously
  refused outright).
2026-08-25 08:42:53 -07:00
Barely Removable
0cbeb26665 Audit round 2: aspect measured the case too; defensive image decode; own-key state reset
4. aspect_profile had the same root cause as edges/centering but no guard.
   On the cased photo it reported 5.8% off standard -- the CASE's
   proportions -- and the UI would surface that as a trimming hint about a
   card it never measured. This one is more directly load-bearing than the
   other two, since the measurement IS the detected box. Now refuses, with
   both consumers (prompt block and UI hint) checking reliable.

5. _decode_images could KeyError/raise out of a plain read on a malformed
   row, and separately b64decode returns b'' rather than raising on some
   corrupt input -- which would have fed a zero-byte 'photo' into grading
   to fail confusingly deep in the pipeline. Both now degrade to the
   existing re-pick path.

6. _last_used_own_key is instance state on a handler that serves every
   request on a keep-alive connection, so it outlives the request that set
   it. Currently safe (every logging path assigns first), but a future path
   that logged without reaching the assignment would bill the previous
   request's payer. Cleared up-front now.

Full regression suite re-run: die-cut exclusion, uniform cards, centering,
aspect, cased-photo refusals, and the store layer all still behave.
2026-08-25 08:05:33 -07:00
Barely Removable
6c612c1f4a Audit fixes: mis-framed crops, GPT cost 2.2x low, camera button losing photos
1. detail_crops had no equivalent of the margin guard the measurements got.
   When box detection fails (card still in a case), every crop is cut
   relative to the wrong rectangle -- verified the 'TOP-LEFT CORNER'
   close-up of the cased Ohtani is actually the CASE's corner bracket. The
   measurements refuse and explain; the crops kept being produced and
   captioned authoritatively, and the prompt tells the model to judge
   corners/edges/surface *from* them. Now surfaces a framing caveat telling
   the model to locate the real card edge inside each crop and say
   cannot_assess rather than grade the holder.

2. gpt-5.6-sol's approx_image_tokens was a pre-launch guess (1500) that
   advertised ~2.2x under true cost across all 9 real calls. Recalibrated
   to 3560 against median real usage, and added a per-model output estimate
   since GPT writes ~1.2k tokens of verdict vs Sonnet's ~0.9k. Both models
   now advertise within ~3% of observed cost.

3. 'Take photo' didn't reset after a completed grade, unlike 'Choose from
   library'. The result view has no photo strip, so new photos piled up
   invisibly behind the old verdict, silently, to the 6-photo cap. Also
   fixed the cap itself being a silent no-op with no explanation.
2026-08-25 08:00:48 -07:00
Barely Removable
d026cce91a Refuse edge/centering measurement when box detection found no real margin
A card photographed still inside a black display case exposed this: the
case filled the entire frame with no background anywhere, so
_detect_card_box returned a box at essentially zero margin from the photo's
own edges (0px left, 1px right on a 986px-wide photo). Every downstream
measurement band -- a few percent of the card's own short dimension, by
design, since real wear lives in the outermost sliver -- then sampled
entirely within the case's embossed plastic texture, never reaching the
actual card. That texture read as 98.9% edge whitening on two sides.

The per-edge material-consistency check added for the earlier die-cut fix
didn't catch this: the case is uniformly dark on all four sides at the
shallow sampling depth used, so no edge disagreed with the others -- the
false signal came from local texture noise within one uniform (wrong)
material, not a mismatch between materials. This needed a different,
earlier check: whether box detection could plausibly have found the real
card boundary at all, gated on margin as a fraction of the photo before any
per-edge analysis runs.

Also fixed a related bug in centering_profile's existing die-cut check: it
compared each side against the median of the other three and returned on
the FIRST hit, so a genuine 2-vs-2 split (both left and right reading the
case, both top and bottom reading the real card) blamed a single side and
never even examined whether the second was equally wrong. Replaced with a
best-single-exclusion search that correctly distinguishes a true one-side
outlier from an unexplainable split.

Verified against the actual photo that exposed this (card in a scalloped
black case) -- both now correctly refuse instead of measuring the case.
Regression-tested: normal photos with reasonable margin, and the earlier
die-cut single-outlier case, are both unaffected.
2026-08-23 08:05:51 -07:00
Barely Removable
fa5467e55d Refuse centering measurement on die-cut/clear-window cards
Same failure family as the edge-whitening fix: centering_profile assumed a
uniform printed border, so on an E-X/SPx-style die-cut the side midpoint
samples landed on clear acetate and the border-width walk measured two
different materials, producing a confident 70/30 the model then treated as
authoritative. Now the four side samples must read as the same material or
the measurement refuses with the reason; the prompt tells the model to judge
die-cut centering as design-to-cut registration instead.
2026-08-22 11:34:36 -07:00
Barely Removable
552f862f4c Fix false edge-whitening on die-cut/mixed-material cards (e.g. SPx clear acetate window)
The pooled baseline that scores edge whitening was global across all four
edges, so a card that's normal printed border on three sides and clear
acetate on the fourth had the three normal edges anchor a baseline that made
the clear side read as extreme whitening -- correctly detecting a real pixel
difference, just the wrong one. Now excludes an edge from both the pool and
its own scoring when its median brightness/saturation reads as a
fundamentally different material, and surfaces why in the prompt instead of
silently dropping it.
2026-08-22 11:09:44 -07:00
Barely Removable
f464faca43 Add aspect-ratio measurement as a soft trimming signal, reported against every standard card size 2026-08-22 11:03:54 -07:00
Barely Removable
c7bd71a3e1 Initial commit: Card Grader deployed to hippofam.com/cards
PWA card-grading app, deployed behind Nginx Proxy Manager on Unraid with
basic auth. Includes CARD_GRADER_BASE_PATH support for running under a
sub-path, and Docker/compose config for the Unraid deployment.
2026-08-22 09:22:41 -07:00