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.
This commit is contained in:
Barely Removable 2026-08-25 08:00:48 -07:00
parent deabe74593
commit 6c612c1f4a
3 changed files with 79 additions and 12 deletions

View file

@ -85,12 +85,14 @@ MODELS = {
"adaptive_thinking": False,
"fallbacks": False,
"in_per_mtok": 2.00, "out_per_mtok": 10.00,
# A rough estimate, unlike the Anthropic figures (which were true'd
# up against real usage — see the README's grading-cost note). Only
# affects the ADVERTISED per-grade estimate in Settings; the actual
# billed cost always comes from the real usage this API call
# reports, never from this number.
"approx_image_tokens": 1500,
# Calibrated against 8 real single/two-photo grades once this model
# had actually been used (median 18.4k in / 1.2k out). The initial
# 1500 was a guess made before any real call existed and advertised
# roughly 2.2x under the true cost — a guess is fine to start from,
# but it has to be trued up once real usage exists, exactly as the
# Anthropic figures were.
"approx_image_tokens": 3560,
"approx_output_tokens": 1180,
},
}
@ -853,6 +855,9 @@ def grade_card(images, api_key=None, model=None, effort=None, zoom_details=True)
measured = cardimage.edge_wear_profile(images[0][0]) if can_measure else None
centering = cardimage.centering_profile(images[0][0]) if can_measure else None
aspect = cardimage.aspect_profile(images[0][0]) if can_measure else None
# Same detection failure that makes the measurements refuse also
# mis-frames every crop — see cardimage.framing_warning.
framing = cardimage.framing_warning(images[0][0]) if (crops or can_measure) else None
prompt_parts = []
if centering and centering.get("reliable") is False:
@ -986,6 +991,20 @@ def grade_card(images, api_key=None, model=None, effort=None, zoom_details=True)
"when you say which corner or edge a problem is on. They add no "
"information the full photo lacked, only easier viewing, so do not "
"read resampling softness as card wear.")
if framing:
prompt_parts.append(
"IMPORTANT CAVEAT ON THOSE CROPS: {} Because the crop "
"rectangle is derived from that same detection, each close-up "
"is cut relative to the wrong boundary — a corner close-up may "
"well be showing the CORNER OF A CASE, SLEEVE OR HOLDER with "
"the card's own corner sitting somewhere inside the frame, or "
"out of it. Do not assume the crop's own edge is the card's "
"edge. Find the actual card edge inside each close-up first "
"and judge only that; if a given close-up doesn't clearly "
"contain the card's real corner or edge, say cannot_assess "
"for that category rather than grading the holder. Damage, "
"scuffing or whitening on a case is not damage to the "
"card.".format(framing))
prompt_parts.append("Estimate the PSA grade this trading card would likely receive.")
parsed, usage = _call_vision(
@ -1084,8 +1103,11 @@ def price_guide():
guide = {}
for model_id, caps in MODELS.items():
# 1 supplied photo + ~12 generated close-ups, JSON verdict out.
# Per-model output estimate where one is known: GPT-5.6 Sol
# reliably writes ~1.2k tokens of verdict against Sonnet's ~0.9k,
# enough to matter at these prices.
est_in = caps["approx_image_tokens"] * 5 + 600
est_out = 700
est_out = caps.get("approx_output_tokens", 700)
rate_in, rate_out = current_rates(caps)
guide[model_id] = {
"label": caps["label"],