feat(PROJ-35): Spec — OCR & Anhang-Volltext-Indexierung
This commit is contained in:
@@ -51,6 +51,8 @@
|
|||||||
| PROJ-33 | IMAP-Modus: Gemeinsames Archiv vs. Persönlicher Posteingang | Deployed | [PROJ-33](PROJ-33-imap-modus-shared-personal.md) | 2026-03-31 |
|
| PROJ-33 | IMAP-Modus: Gemeinsames Archiv vs. Persönlicher Posteingang | Deployed | [PROJ-33](PROJ-33-imap-modus-shared-personal.md) | 2026-03-31 |
|
||||||
| PROJ-34 | Retention-Policy + Löschsperre (GoBD-Compliance) | Deployed | [PROJ-34](PROJ-34-retention-policy.md) | 2026-03-31 |
|
| PROJ-34 | Retention-Policy + Löschsperre (GoBD-Compliance) | Deployed | [PROJ-34](PROJ-34-retention-policy.md) | 2026-03-31 |
|
||||||
|
|
||||||
|
| PROJ-35 | OCR & Anhang-Volltext-Indexierung | Planned | [PROJ-35](PROJ-35-ocr-anhang-volltext.md) | 2026-04-04 |
|
||||||
|
|
||||||
<!-- Add features above this line -->
|
<!-- Add features above this line -->
|
||||||
|
|
||||||
## Next Available ID: PROJ-35
|
## Next Available ID: PROJ-35
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
# PROJ-35: OCR & Anhang-Volltext-Indexierung
|
||||||
|
|
||||||
|
## Status: Planned
|
||||||
|
**Created:** 2026-04-04
|
||||||
|
**Last Updated:** 2026-04-04
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Requires: PROJ-5 (Speicherung & Indexierung) — Mailparser + Manticore-Index
|
||||||
|
- Requires: PROJ-30 (Manticore Migration) — Volltext-Index als Basis
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
PDF- und Bild-Anhänge (Rechnungen, Verträge, eingescannte Dokumente) sind bisher nicht durchsuchbar — nur der Dateiname wird indexiert. OCR macht den Inhalt dieser Anhänge volltext-durchsuchbar ohne den normalen Mail-Eingang zu verlangsamen.
|
||||||
|
|
||||||
|
## User Stories
|
||||||
|
|
||||||
|
- Als Nutzer möchte ich in archivierten Rechnungs-PDFs nach Beträgen oder Kundennummern suchen können.
|
||||||
|
- Als Nutzer möchte ich eingescannte Dokumente (JPG/PNG/TIFF) im Volltext durchsuchen können.
|
||||||
|
- Als Admin möchte ich OCR per Mandant aktivieren oder deaktivieren können.
|
||||||
|
- Als Nutzer soll der Mail-Eingang nicht durch OCR verlangsamt werden (asynchrone Verarbeitung).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] PDF-Anhänge mit eingebettetem Text: Text via `pdftotext` extrahieren (kein OCR nötig, schnell)
|
||||||
|
- [ ] PDF-Anhänge ohne Text (Scan): OCR via Tesseract (deutsch + englisch)
|
||||||
|
- [ ] Bild-Anhänge (JPG, PNG, TIFF): OCR via Tesseract
|
||||||
|
- [ ] OCR läuft asynchron — Mail wird sofort gespeichert, OCR-Text nachgeliefert
|
||||||
|
- [ ] OCR-Text wird in Manticore-Index im Feld `attachment_text` gespeichert (neues Feld)
|
||||||
|
- [ ] Volltext-Suche findet Mails anhand von OCR-Text in Anhängen
|
||||||
|
- [ ] OCR-Status pro Mail in PostgreSQL: `ocr_status` (pending / done / failed / skipped)
|
||||||
|
- [ ] OCR kann per Mandant deaktiviert werden (`ocr_enabled` in tenants-Tabelle)
|
||||||
|
- [ ] `archivmail ocr-reprocess` — OCR für alle oder einzelne Mandanten nachholen
|
||||||
|
- [ ] Keine neue externe Service-Abhängigkeit bei Laufzeit — nur System-Pakete (`tesseract-ocr`, `poppler-utils`)
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
- Sehr große PDFs (>50 MB): Timeout nach 60s, Status `failed`, Mail bleibt auffindbar per Metadaten
|
||||||
|
- Passwortgeschützte PDFs: OCR überspringen, Status `skipped`
|
||||||
|
- Anhänge ohne OCR-fähiges Format (ZIP, EXE, ...): überspringen
|
||||||
|
- Tesseract nicht installiert: OCR deaktiviert, Warnung beim Start, kein Absturz
|
||||||
|
|
||||||
|
## Tech Design
|
||||||
|
|
||||||
|
### Neue Komponenten
|
||||||
|
|
||||||
|
**`internal/ocr/ocr.go`**
|
||||||
|
```go
|
||||||
|
// ExtractText extrahiert Text aus einem Anhang.
|
||||||
|
// Für PDFs: pdftotext zuerst, dann Tesseract als Fallback.
|
||||||
|
// Für Bilder: direkt Tesseract.
|
||||||
|
// Gibt leeren String zurück wenn Format nicht unterstützt oder Fehler.
|
||||||
|
func ExtractText(data []byte, contentType string, langs []string) (string, error)
|
||||||
|
|
||||||
|
// IsAvailable prüft ob Tesseract installiert ist.
|
||||||
|
func IsAvailable() bool
|
||||||
|
```
|
||||||
|
|
||||||
|
**`internal/ocr/worker.go`** — Async-Worker
|
||||||
|
```go
|
||||||
|
// Worker liest aus einem Channel und verarbeitet OCR-Jobs.
|
||||||
|
// Läuft als Goroutine im Hintergrund.
|
||||||
|
type Worker struct { ... }
|
||||||
|
func NewWorker(store *storage.Store, idxMgr index.TenantIndexer) *Worker
|
||||||
|
func (w *Worker) Submit(mailID string, tenantID *int64)
|
||||||
|
func (w *Worker) Start(ctx context.Context)
|
||||||
|
```
|
||||||
|
|
||||||
|
### DB-Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- OCR-Status pro Mail
|
||||||
|
ALTER TABLE emails ADD COLUMN IF NOT EXISTS ocr_status TEXT DEFAULT 'pending';
|
||||||
|
-- Werte: pending | done | failed | skipped | disabled
|
||||||
|
|
||||||
|
-- OCR pro Tenant konfigurierbar
|
||||||
|
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS ocr_enabled BOOLEAN DEFAULT TRUE;
|
||||||
|
|
||||||
|
-- Index für OCR-Queue
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_emails_ocr_status ON emails (ocr_status) WHERE ocr_status = 'pending';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manticore-Schema-Erweiterung
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Neues Feld im RT-Index
|
||||||
|
ALTER TABLE emails_tenant_1 ADD COLUMN attachment_text text;
|
||||||
|
```
|
||||||
|
|
||||||
|
In `internal/index/manticore.go`:
|
||||||
|
- `MailDocument.AttachmentText string` hinzufügen
|
||||||
|
- `ensureTable()` — neues Feld im CREATE TABLE
|
||||||
|
- `IndexSync()` — `attachment_text` befüllen
|
||||||
|
|
||||||
|
### Verarbeitungs-Ablauf
|
||||||
|
|
||||||
|
```
|
||||||
|
Mail eingehend (SMTP/IMAP/Import)
|
||||||
|
→ mailparser.Parse() → Anhänge erkannt
|
||||||
|
→ mailStore.Save() → Mail gespeichert, ocr_status = 'pending'
|
||||||
|
→ Manticore-Index ohne attachment_text
|
||||||
|
→ OCR-Worker.Submit(mailID)
|
||||||
|
|
||||||
|
OCR-Worker (async, Goroutine):
|
||||||
|
→ mailStore.Load(mailID) → Rohdaten
|
||||||
|
→ mailparser.Parse() → Anhänge
|
||||||
|
→ für jeden Anhang:
|
||||||
|
→ ocr.ExtractText(data, contentType, ["deu","eng"])
|
||||||
|
→ idxMgr.ForTenant(tenantID).UpdateAttachmentText(mailID, text)
|
||||||
|
→ mailStore.SetOCRStatus(mailID, "done")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Neues CLI-Subkommando
|
||||||
|
|
||||||
|
```bash
|
||||||
|
archivmail ocr-reprocess --config /etc/archivmail/config.yml
|
||||||
|
archivmail ocr-reprocess --config /etc/archivmail/config.yml --tenant 1
|
||||||
|
archivmail ocr-reprocess --config /etc/archivmail/config.yml --status failed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation auf Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get install -y tesseract-ocr tesseract-ocr-deu poppler-utils
|
||||||
|
# Prüfen
|
||||||
|
tesseract --version
|
||||||
|
pdftotext -v
|
||||||
|
```
|
||||||
|
|
||||||
|
`update.sh` — optionale Installation (kein Abbruch wenn nicht verfügbar):
|
||||||
|
```bash
|
||||||
|
apt-get install -y tesseract-ocr tesseract-ocr-deu poppler-utils 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance-Überlegungen
|
||||||
|
|
||||||
|
| Format | Tool | Dauer (A4-Seite) |
|
||||||
|
|--------|------|-----------------|
|
||||||
|
| PDF mit Text | pdftotext | < 100ms |
|
||||||
|
| PDF als Scan | Tesseract | 1–3s |
|
||||||
|
| JPG (300 DPI) | Tesseract | 0.5–2s |
|
||||||
|
|
||||||
|
OCR-Worker mit konfigurierbarer Worker-Anzahl (Standard: 2 Goroutinen).
|
||||||
|
Keine Blockierung des Mail-Eingangs — Submit() ist non-blocking.
|
||||||
|
|
||||||
|
## Nicht in Scope
|
||||||
|
|
||||||
|
- Microsoft Word / Excel / PowerPoint direkt (nur wenn als PDF geliefert)
|
||||||
|
- Layout-Analyse oder Tabellen-Extraktion
|
||||||
|
- Sprachen außer Deutsch und Englisch (erweiterbar via Config)
|
||||||
|
- Cloud-OCR-Services (bewusst: nur lokale Tools)
|
||||||
|
|
||||||
|
## Tech Design
|
||||||
|
_Vollständig oben beschrieben_
|
||||||
|
|
||||||
|
## QA Test Results
|
||||||
|
_To be added_
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
_To be added_
|
||||||
Reference in New Issue
Block a user