diff --git a/DEPLOY.md b/DEPLOY.md
index 2ab5d06..1257434 100644
--- a/DEPLOY.md
+++ b/DEPLOY.md
@@ -149,7 +149,7 @@ Neuer Proxy Host:
| Feld | Wert |
|---|---|
-| Domain Names | `business-card-scanner.lucas-orth.de` |
+| Domain Names | `scanner.lucas-orth.de` |
| Scheme | `http` |
| Forward Hostname / IP | `business-card-scanner` (= `container_name`) |
| Forward Port | `8080` |
diff --git a/README.md b/README.md
index 9e3cd00..e7be845 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ Tag-Push nach den Konventionen in [DEPLOY.md](DEPLOY.md). Vor dem ersten Tag:
nach dem Muster in [.env.example](.env.example). `SECRET_KEY` mit
`openssl rand -hex 32` erzeugen.
2. **Proxy Host** im Nginx Proxy Manager:
- `business-card-scanner.lucas-orth.de` → `business-card-scanner` : `8080`,
+ `scanner.lucas-orth.de` → `business-card-scanner` : `8080`,
danach Let's-Encrypt-Zertifikat und „Force SSL“.
3. **Deployen:** `git tag v1.0.0 && git push origin v1.0.0`
diff --git a/app/extract.py b/app/extract.py
index 54cfda7..74b2d62 100644
--- a/app/extract.py
+++ b/app/extract.py
@@ -52,9 +52,22 @@ Regeln:
PROMPT = "Lies diese Visitenkarte aus."
+# Nicht jedes Modell nimmt jeden Parameter an: die kleinen Modelle kennen
+# output_config.effort nicht (400), der server-seitige Fallback gilt nur fuer
+# die groessten.
+_NO_EFFORT = ("claude-haiku-", "claude-sonnet-4-5")
+_WITH_FALLBACK = ("claude-opus-5", "claude-fable-")
+
_client = None
+def _output_config(with_effort: bool) -> dict:
+ settings = {"format": {"type": "json_schema", "schema": SCHEMA}}
+ if with_effort:
+ settings["effort"] = "low" # reines Ablesen, keine Denkarbeit
+ return settings
+
+
def available() -> bool:
return bool(config.ANTHROPIC_API_KEY)
@@ -92,27 +105,28 @@ async def _ask(image_jpeg: bytes) -> dict:
],
}
]
- common = dict(
- model=config.ANTHROPIC_MODEL,
- max_tokens=2000,
- system=SYSTEM,
- messages=messages,
- output_config={
- "format": {"type": "json_schema", "schema": SCHEMA},
- "effort": "low", # reines Ablesen, keine Denkarbeit
- },
- )
+ model = config.ANTHROPIC_MODEL
+ common = dict(model=model, max_tokens=2000, system=SYSTEM, messages=messages)
+ effort = not model.startswith(_NO_EFFORT)
try:
- response = await client.beta.messages.create(
- betas=["server-side-fallback-2026-07-01"],
- fallbacks="default",
- **common,
+ if model.startswith(_WITH_FALLBACK):
+ response = await client.beta.messages.create(
+ betas=["server-side-fallback-2026-07-01"],
+ fallbacks="default",
+ output_config=_output_config(effort),
+ **common,
+ )
+ else:
+ response = await client.messages.create(
+ output_config=_output_config(effort), **common
+ )
+ except anthropic.BadRequestError as exc:
+ # Ein Modell mit engerem Parametersatz, als die Listen oben kennen.
+ # Einmal ohne Zusatzparameter nachfassen, statt die Karte zu verlieren.
+ log.warning("Anfrage abgelehnt (%s) - Wiederholung ohne Zusatzparameter", exc)
+ response = await client.messages.create(
+ output_config=_output_config(with_effort=False), **common
)
- except anthropic.BadRequestError:
- # Der Fallback-Parameter haengt am Modell. Ohne ihn laeuft die Anfrage
- # weiter, sie bricht bei einer Ablehnung nur ohne Ersatzmodell ab.
- log.warning("Server-side fallback abgelehnt, Anfrage ohne Fallback")
- response = await client.messages.create(**common)
if response.stop_reason == "refusal":
return _empty("Vom Modell abgelehnt")
diff --git a/static/app.js b/static/app.js
index 0e5f668..df762fa 100644
--- a/static/app.js
+++ b/static/app.js
@@ -111,6 +111,20 @@
});
}
+ /** Zustand einer Karte als abgestufte Pillen: schwarz, was erledigt ist,
+ * grau umrandet, was noch offen oder auffaellig ist. */
+ function cardTags(card) {
+ const tags = [];
+ if (card.place) tags.push(["", card.place]);
+ tags.push(
+ card.exported_at ? ["pill-solid", "exportiert"] : ["", "nicht exportiert"]
+ );
+ if (card.extract_status !== "ok") tags.push(["", "Lesefehler"]);
+ return tags
+ .map(([extra, text]) => ``)
+ .join("");
+ }
+
// ----------------------------------------------------------------- Routing
function go(route, cardId) {
@@ -167,7 +181,7 @@
`
@@ -234,15 +248,7 @@
${esc([card.job_title, card.company].filter(Boolean).join(" · ") || "Keine Firma erkannt")}
- ${esc(
- [
- card.place,
- card.exported_at ? "exportiert" : "nicht exportiert",
- card.extract_status !== "ok" ? "Lesefehler" : null,
- ]
- .filter(Boolean)
- .join(" · ")
- )}
+ ${cardTags(card)}
`
)
.join("")}
@@ -367,7 +373,7 @@
Karte entfernen
-