Files
business-card-scanner/README.md
Lucas Orth 1e8918e0cd
All checks were successful
Build & Deploy / deploy (push) Successful in 1m29s
Bildeditor, klickbare Felder, Einstellungen; Kopfzeile und Namensfeld raus
Bildbearbeitung im Nachgang: Drehen in 90-Grad-Schritten, Ausrichten per
Schieberegler, Zuschneiden per Rahmen mit anfassbaren Ecken. Der Server
rechnet jede Korrektur vom unbearbeiteten Original, das beim ersten
Eingriff daneben abgelegt wird - sonst summieren sich die Verluste über
mehrere Korrekturen, und Zurücksetzen wäre nicht möglich. Zwei
Zuschnitte hintereinander werden ineinander verrechnet.

Der Ort ließ sich nicht übernehmen, weil beide Quellen versagen können:
iOS entfernt beim Weitergeben an eine Webseite oft die GPS-Daten aus dem
Foto, und der Browserstandort scheiterte bisher stumm. Jetzt meldet er
seinen Grund ("Standortfreigabe fehlt"), die Detailansicht zeigt die
Herkunft des Ortes, und der Scan-Ort ist von Hand nachtragbar.

E-Mail, Web, Telefon und Mobil sind in der Tabelle jetzt Links; Straße,
PLZ, Ort und Land stehen als eine Adresszeile, die die eingestellte
Navigations-App öffnet.

Einstellungen als vierter Reiter: Navigations-App (Apple Karten, Google
Maps, OpenStreetMap), Statusübersicht und Abmelden - der Knopf saß
vorher in der Kopfzeile.

Die Kopfzeile ist weg. Sie kostete auf dem Telefon eine Bildschirmzeile
und wiederholte nur, was die Tableiste schon sagt.

Kein full_name mehr, nur Vor- und Nachname; akademische Titel gehören in
den Vornamen. Bestehende Datensätze werden bei der Migration am letzten
Leerzeichen aufgeteilt. Die Spalte bleibt ungenutzt stehen: SQLite baut
zum Löschen die ganze Tabelle neu, der Gewinn wären ein paar Byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 15:19:10 +02:00

146 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Business Card Scanner
Visitenkarten abfotografieren, automatisch freistellen, auslesen und als
Kontakt aufs iPhone holen. Selbst gehostete PWA, ein Container.
## Was die App macht
**Stapelscan.** Ein Foto von zwanzig Karten auf dem Tisch reicht. OpenCV findet
die Kartenrechtecke, entzerrt sie perspektivisch und schneidet sie einzeln aus –
ohne manuelle Nacharbeit. Der Zuschnitt wird dabei leicht enger gesetzt als die
erkannte Kante, damit kein Untergrund stehen bleibt, und die Beleuchtung wird
ausgeglichen, damit das Papier weiß wird statt grau.
Jeder Zuschnitt geht als Bild an das Vision-Modell, das Name, Firma, Position
und Kontaktdaten als strukturiertes JSON zurückgibt. Ein vorgeschalteter
OCR-Schritt wäre kontraproduktiv: er würde Layout und Schriftgrößen wegwerfen,
also genau die Information, aus der die Feldzuordnung entsteht. Das Modell sagt
außerdem, wie herum die Karte gehört – geometrisch ist das nicht bestimmbar –
und das gespeicherte Bild wird entsprechend gedreht.
**Die Antwort wartet nicht auf das Modell.** Der Scan liefert die freigestellten
Karten sofort zurück, das Auslesen läuft danach im Hintergrund weiter; die
Oberfläche lädt nach. Ein Zwanzigerstapel blockiert damit keine Minute im
Upload. Wer eine Karte in der Zwischenzeit von Hand ergänzt, verliert die
Eingabe nicht – das Modell füllt nur leere Felder. Bricht der Server mitten im
Lauf ab, holt er die offenen Karten beim nächsten Start nach.
**Metadaten.** Aufnahmezeit und GPS kommen aus den EXIF-Daten des Fotos, nicht
aus dem Browser – Karten werden oft erst abends am Schreibtisch abfotografiert,
der Browserstandort wäre dann das Wohnzimmer statt der Messe. Die Koordinaten
löst Nominatim in einen Ortsnamen auf. Fehlen EXIF-Daten, greift der
Browserstandort als Rückfallebene.
**Anlass.** Ein Feld auf der Startseite („Hannover Messe 2026“) gilt für alle
folgenden Scans und wird im Browser gemerkt. Verlässlicher als der Ortsname aus
den Koordinaten und die eigentliche Ordnung über den Bestand.
**Dubletten.** Auf Messen trifft man Leute wieder. Die Detailansicht zeigt
Karten, die dieselbe Person meinen könnten – gleiche E-Mail, gleiche
Telefonnummer (Schreibweise egal, verglichen werden die letzten acht Ziffern)
oder gleicher Nachname bei gleicher Firma.
**Rückseite.** Nachträglich ein zweites Bild zur Karte aufnehmen. Es wird
ausgelesen, füllt aber nur Felder, die die Vorderseite offen gelassen hat –
dort steht oft nur die Mobilnummer.
**Übersicht.** Alle Karten mit Volltextsuche über Name, Firma, Anlass, Ort und
Notiz. Filter für „nicht exportiert“, „unvollständig“ und „mit Erinnerung“.
**Bild nachbearbeiten.** Drehen in 90-Grad-Schritten, Ausrichten über einen
Schieberegler und Zuschneiden per Rahmen. Jede Korrektur rechnet der Server vom
unbearbeiteten Original, das beim ersten Eingriff daneben abgelegt wird – so
summieren sich keine Verluste und *Zurücksetzen* führt zurück zum Scan.
**Klickbare Felder.** E-Mail, Web, Telefon und Mobil sind Handlungen, keine
Zeichenketten. Die Adresse öffnet die Navigations-App, die unter *Mehr*
eingestellt ist (Apple Karten, Google Maps oder OpenStreetMap).
**Export und Teilen.** *In Kontakte speichern* liefert eine vCard 3.0, die iOS
direkt in die Kontakte-App übernimmt. *Als vCard teilen* geht über das
System-Teilenblatt – per AirDrop, Mail oder Nachricht, ohne Umweg über den
Download. Bewusst einzeln: ein Sammelexport schiebt zwanzig Kontakte ungeprüft
ins Adressbuch.
**Notizen und Erinnerungen.** Freitext pro Karte. Erinnerungen mit Termin gehen
als Mail raus; verpasste Termine holt der Scheduler beim nächsten Start nach.
Schnellwahl für die üblichen Abstände, damit das Datumsfeld selten gebraucht
wird.
## Stack
Python 3.12, FastAPI, SQLite, OpenCV, Anthropic SDK. Frontend ohne Build-Schritt:
statisches HTML, CSS und JavaScript, als PWA installierbar.
## Lokal starten
```bash
python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
cp .env.example .env # APP_PASSWORD leer lassen schaltet die Anmeldung ab
DATA_DIR=./data .venv/bin/uvicorn app.main:app --reload --port 8099
```
Ohne `ANTHROPIC_API_KEY` läuft alles außer der Extraktion: Karten werden
freigestellt und gespeichert, die Felder bleiben leer.
```bash
.venv/bin/pytest -q
```
## Deployment
Tag-Push nach den Konventionen in [DEPLOY.md](DEPLOY.md). Vor dem ersten Tag:
1. **Secret `DOTENV`** anlegen (Repo → Einstellungen → Actions → Secrets), Inhalt
nach dem Muster in [.env.example](.env.example). `SECRET_KEY` mit
`openssl rand -hex 32` erzeugen.
2. **Proxy Host** im Nginx Proxy Manager:
`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`
### Zugang
Die App hängt hinter dem öffentlichen Proxy, nicht im Tailnet. Sie enthält
personenbezogene Daten Dritter, deshalb ist `APP_PASSWORD` auf dieser Domain
Pflicht – ohne Passwort ist sie für jeden erreichbar, der die Adresse kennt.
Wer zusätzlich absichern will, trägt im NPM unter *Access List* eine
IP-Beschränkung auf den Tailnet-Bereich ein.
### Kosten
Ein Kartenzuschnitt kostet ungefähr zwei Cent bei `claude-opus-5` (Standard).
Ein Stapel von zwanzig Karten liegt damit bei etwa 40 Cent.
`ANTHROPIC_MODEL=claude-haiku-4-5` in der `.env` drückt das auf einen Bruchteil,
zulasten der Trefferquote bei kleiner Schrift und unruhigen Layouts.
## Aufnahmebedingungen für den Stapelscan
Die Freistellung ist reine Geometrie und braucht Kontrast zum Untergrund:
- dunkler, matter, einfarbiger Untergrund
- Karten berühren sich nicht
- Kamera möglichst parallel zur Tischplatte
- volle Auflösung – deshalb die native Kamera, nicht der Livestream
Findet die Pipeline kein Kartenrechteck, behandelt sie das ganze Foto als eine
Karte und sagt das in der Oberfläche. Einzelne Fehlgriffe lassen sich pro Karte
über *Bearbeiten* korrigieren.
## Verzeichnisse
```
app/ FastAPI-Anwendung
segment.py OpenCV-Pipeline: Karten finden und entzerren
extract.py Vision-Modell, strukturiertes JSON
duplicates.py Ähnliche Karten finden
imaging.py EXIF, Drehen, Zuschneiden
vcard.py vCard 3.0
reminders.py Scheduler für die Erinnerungsmails
static/ PWA: HTML, CSS, JavaScript, Service Worker
tests/ pytest, inklusive synthetischer Stapelfotos
```
Persistente Daten liegen im Volume `business-card-scanner-data` unter `/data`:
`cards.db` und die Kartenbilder in `images/`.