Add OpenAI (GPT-5.6 Sol) as a second vision provider; gate server settings to a named admin

vision.py now dispatches per-model to _call_anthropic or _call_openai --
same prompt, same schema, same cardimage.py measurements either way, only
the request/response shape differs. Confirmed the existing GRADING_SCHEMA
already satisfies OpenAI's strict-mode requirement (every property listed
in required, additionalProperties:false at every level) with no changes.

Settings gained a second axis: which server key applies now depends on the
selected model's provider, and friends' personal keys are stored per
provider (with a one-time migration from the old single-key localStorage
slot) since a Claude key and an OpenAI key aren't interchangeable.

CARD_GRADER_ADMIN_USER names one username (read from the proxy's forwarded
basic-auth header) who alone may write server settings; everyone else keeps
the same read-only view CARD_GRADER_LOCK used to give everyone, while still
being able to set their own personal key. Deployed here as ninja_hippo.
CARD_GRADER_LOCK remains the fallback when no admin is named.
This commit is contained in:
Barely Removable 2026-08-23 07:39:03 -07:00
parent 9d5c68b093
commit 034761e145
7 changed files with 351 additions and 122 deletions

View file

@ -101,41 +101,51 @@ graded — so regrading a card keeps its photos for another full window.
### Optional packages
Two packages unlock real functionality. Each is checked for at runtime, so
Three packages unlock real functionality. Each is checked for at runtime, so
the app runs without them — you just lose that capability.
```bash
python3 -m pip install --user anthropic # required — this is what does the grading
python3 -m pip install --user anthropic # Claude models — Sonnet 5, Haiku 4.5, Opus 5
python3 -m pip install --user openai # OpenAI models — GPT-5.6 Sol
python3 -m pip install --user pillow # the corner/edge/surface measurement pipeline
```
Without `anthropic`, grading simply doesn't work — add a key in Settings once
it's installed. Without `pillow`, grading still runs, but on the full photo
alone: no measured centering, no measured edge whitening, no magnified
close-ups. Corners and edges will come back `cannot_assess` far more often,
because a plain full-card photo genuinely doesn't show enough detail in those
regions for a model to judge them from. Install `pillow` — it's the
difference between an answer and a shrug.
You only need whichever provider package matches the model you actually use
— install both if you want the option to switch. Without `pillow`, grading
still runs, but on the full photo alone: no measured centering, no measured
edge whitening, no magnified close-ups. Corners and edges will come back
`cannot_assess` far more often, because a plain full-card photo genuinely
doesn't show enough detail in those regions for a model to judge them from.
Install `pillow` — it's the difference between an answer and a shrug.
### API key
### API keys
Get one at [console.anthropic.com](https://console.anthropic.com), then paste
it into Settings in the app. Grading costs a few cents per card (Sonnet 5,
~$0.04-0.06 depending on how many photos you send) — nothing is charged until
you click "Estimate the grade."
Get an Anthropic key at [console.anthropic.com](https://console.anthropic.com)
and/or an OpenAI key at [platform.openai.com](https://platform.openai.com),
then paste whichever you use into Settings in the app — each provider has its
own field, since the keys aren't interchangeable and you may want to hold
both. Grading costs a few cents per card either way (Sonnet 5 and GPT-5.6 Sol
are both currently ~$0.04-0.06 depending on how many photos you send) —
nothing is charged until you click "Estimate the grade."
## Why Sonnet 5, not a cheaper model
Settings lets you switch to Haiku 4.5 (about a third of the cost) or Opus 5
(more expensive, marginally more careful). Sonnet is the default for a
tested reason, not a hunch: run head-to-head against Haiku on the same
photos, Haiku inverted a PSA centering-tolerance comparison — read 59/41 as
*exceeding* the tolerance for a 9, when 59/41 is actually well inside it —
and the error alone dragged its estimate three grade levels below Sonnet's.
Haiku also lacks extended thinking entirely, which matters for exactly the
judgment calls this task is full of: is this a scratch or a print texture,
a reflection or real edge wear, a print line or a crease. If cost matters
enough to switch, that's a real, deliberate tradeoff — not a free lunch.
Settings lets you switch to Haiku 4.5 (about a third of the cost), Opus 5
(more expensive, marginally more careful), or GPT-5.6 Sol (OpenAI, similar
price to Sonnet). Sonnet is the default for a tested reason, not a hunch: run
head-to-head against Haiku on the same photos, Haiku inverted a PSA
centering-tolerance comparison — read 59/41 as *exceeding* the tolerance for
a 9, when 59/41 is actually well inside it — and the error alone dragged its
estimate three grade levels below Sonnet's. Haiku also lacks extended
thinking entirely, which matters for exactly the judgment calls this task is
full of: is this a scratch or a print texture, a reflection or real edge
wear, a print line or a crease. If cost matters enough to switch, that's a
real, deliberate tradeoff — not a free lunch.
GPT-5.6 Sol hasn't been run through the same head-to-head testing — it's
wired up and usable, but nobody has checked it against real graded cards the
way Haiku was checked here. Treat its results with the scrutiny that implies
until someone does that comparison.
## The workflow
@ -224,12 +234,14 @@ includes a `Dockerfile`, `docker-compose.yml`, and an nginx config
-d card-grader.yourdomain.com
docker restart nginx
```
5. **Set your Anthropic API key** by opening `https://card-grader.yourdomain.com`
yourself first, going to Settings, and pasting it in — this only works
*before* `CARD_GRADER_LOCK=1` takes effect, so either set the key first
and then bring the lock up, or temporarily comment out that line in
`docker-compose.yml`, restart, set the key, uncomment it, and restart
again.
5. **Set your API key(s)** by opening `https://card-grader.yourdomain.com`
yourself first, going to Settings, and pasting them in. If you're using
`CARD_GRADER_ADMIN_USER`, this just works — settings stay writable for
that one username indefinitely. If you're using the blanket
`CARD_GRADER_LOCK=1` instead, that only works *before* the lock takes
effect, so either set the key first and then bring the lock up, or
temporarily comment out that line in `docker-compose.yml`, restart, set
the key, uncomment it, and restart again.
If Unraid already has an nginx-based reverse proxy set up (Nginx Proxy
Manager, SWAG, etc.), skip step 4 and just add a proxy host there pointing
@ -268,11 +280,25 @@ key is being used**. Two things in the app deal with this:
CARD_GRADER_LOCK=1 python3 app.py
```
which refuses all settings writes. Without it, any visitor could overwrite
your stored API key or switch the model to the most expensive one. Grading
still works normally; only the settings write is blocked.
which refuses all settings writes from everyone. Without it, any visitor
could overwrite your stored API key or switch the model to the most
expensive one. Grading still works normally; only the settings write is
blocked.
Your stored API key is never sent to any browser regardless — the settings
If your reverse proxy does per-user basic auth (as opposed to one shared
password for everyone), you can name a single admin instead of locking
settings out entirely:
```bash
CARD_GRADER_ADMIN_USER=yourname python3 app.py
```
Only that username may write settings; everyone else gets the same
read-only view `CARD_GRADER_LOCK` gives everyone. Either way, everyone can
still set their *own* personal key (point 1 above) — that never touches
server settings at all.
Your stored API keys are never sent to any browser regardless — the settings
endpoint returns only a boolean saying whether one is configured.
Everyone shares one `grades.db`, so the History list is communal. That's