feat: AcroForm-Fill via Claude CLI, Multi-Source, Kanagawa, Docker-Deploy
Komplettes Rework der AI-Studio-Vorlage zu einem produktiven Werkzeug fuer
deutsche AcroForm-Formulare (Reha-Antraege, Arzt-Befundberichte):
- Backend: Express spawnt headless Claude CLI ('claude -p --output-format json'
via stdin-Pipe). Prompt enthaelt die Feldnamen als Ziel-Schema plus die
Arbeitsregeln (Stichwortstil, feste Zeichen-Kaestchen ohne Leerzeichen,
Vordrucke respektieren, keine geratenen Werte, nur medizinisch).
- PDF-Handling: pdfjs-dist statt pdf-lib — pdf-lib scheitert an verschluesselten
Object-Streams in DRV-Formularen. annotationStorage + saveDocument, kein
Flatten. Worker-Patch zur Laufzeit forciert Auto-Size und schwarze Schrift.
- Multi-Source-Upload: beliebig viele PDFs/Bilder + optional Freitext.
- Design: Kanagawa Design System (Preset aus ../kanagawa-design-system),
Tailwind lokal gebaut statt CDN, Dark/Light-Toggle, Progress-Indicator.
- Deployment: Multi-Stage-Dockerfile, docker-compose in matrix_default-Netz,
Claude-Credentials vom Host per Volume. PLAN.md + AGENTS.md (Alex-Schema).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d6cab4aeb5
commit
3c669fb003
28 changed files with 6756 additions and 934 deletions
200
AGENTS.md
Normal file
200
AGENTS.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# AGENTS.md - Rentenversicherer Repository Guidelines
|
||||
|
||||
## Overview
|
||||
|
||||
Rentenversicherer ist ein lokales Werkzeug zum halb-automatischen
|
||||
Ausfüllen von AcroForm-PDFs. Browser-UI + lokaler Node-Server. Der
|
||||
Server ruft die Claude Code CLI (`claude -p`) als Subprozess auf, um
|
||||
Daten aus einem Quelldokument in die AcroForm-Feldnamen eines Ziel-PDFs
|
||||
zu mappen. Das ausgefüllte PDF bleibt editierbar (kein Flatten).
|
||||
|
||||
## Run / Dev Commands
|
||||
|
||||
Requires: Node.js 20+, Claude Code CLI im PATH, gültiges Claude-Code-Login.
|
||||
|
||||
```bash
|
||||
# Install
|
||||
npm install
|
||||
|
||||
# Development — Vite (5173) + Server (3001) parallel
|
||||
npm run dev
|
||||
|
||||
# Production build und starten
|
||||
npm run build
|
||||
npm start
|
||||
|
||||
# Typecheck
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
## Lint / Test / Validation
|
||||
|
||||
Kein dediziertes Test-Framework. Validation:
|
||||
|
||||
```bash
|
||||
# Typecheck (Client + Server, shared tsconfig)
|
||||
npx tsc --noEmit
|
||||
|
||||
# Claude CLI verfügbar?
|
||||
claude --version
|
||||
|
||||
# Smoke-Test (manuell):
|
||||
# 1. npm run dev
|
||||
# 2. http://localhost:5173
|
||||
# 3. Beispiel-PDF hochladen (G2210-11_Aerztlicher_Befundbericht_Anforderung_WAG.pdf)
|
||||
# 4. Quelldokument hochladen
|
||||
# 5. Download
|
||||
# 6. PDF in Acrobat öffnen → Feld klicken → editieren möglich?
|
||||
```
|
||||
|
||||
## Repo Layout
|
||||
|
||||
```
|
||||
Rentenversicherer/
|
||||
├── App.tsx # React-Einstieg, State-Machine (IDLE/PROCESSING/REVIEW)
|
||||
├── components/
|
||||
│ ├── FileUpload.tsx # Drag & Drop + base64-Encoding
|
||||
│ └── ReviewPanel.tsx # List-View für extrahierte Felder + PDF-Preview
|
||||
├── services/
|
||||
│ ├── api.ts # fetch('/api/process'), multipart/form-data
|
||||
│ └── pdfService.ts # pdfjs-dist: Widgets lesen + saveDocument
|
||||
├── server/
|
||||
│ ├── index.ts # Express + multer, POST /api/process
|
||||
│ └── claudeRunner.ts # spawn('claude', ['-p', ...]), JSON-Parsing
|
||||
├── types.ts # ExtractedField, FormResponse, AppStatus, FileData
|
||||
├── vite.config.ts
|
||||
├── tsconfig.json
|
||||
├── package.json
|
||||
└── PLAN.md
|
||||
```
|
||||
|
||||
## Code Style - TypeScript / React
|
||||
|
||||
**Komponenten:**
|
||||
|
||||
- Functional Components mit Hooks; keine Class-Components.
|
||||
- Props-Interfaces am Anfang der Datei, Name endet auf `Props`.
|
||||
- Dateinamen: `PascalCase.tsx` für Komponenten, `camelCase.ts` für Services.
|
||||
|
||||
**Imports:**
|
||||
|
||||
- React-Imports zuerst, dann 3rd-Party, dann relativ.
|
||||
- Keine `*.tsx`-/`*.ts`-Extensions in Imports.
|
||||
|
||||
**Styling:**
|
||||
|
||||
- Tailwind ausschließlich. Keine CSS-Module, kein inline-style außer für
|
||||
dynamisch berechnete Werte.
|
||||
- Lucide-Icons (`lucide-react`) für alle Icons.
|
||||
|
||||
**Types:**
|
||||
|
||||
- Shared Types in `types.ts`. Keine Duplikate in Komponenten.
|
||||
- `any` vermeiden. `unknown` + Type-Guards bevorzugen.
|
||||
|
||||
## Code Style - Server (Node/TypeScript)
|
||||
|
||||
- Module: ESM (`"type": "module"` in package.json).
|
||||
- `tsx watch` für dev; kein ts-node, kein nodemon.
|
||||
- Async/await überall, kein callback-style.
|
||||
- Fehler aus dem Claude-Subprozess als 502 ans Frontend weiterreichen,
|
||||
mit `message` im JSON-Body — niemals stdout/stderr roh leaken.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- Komponenten: `PascalCase` (`ReviewPanel`, `FileUpload`).
|
||||
- Services: `camelCase` (`pdfService`, `claudeRunner`, `api`).
|
||||
- Types/Interfaces: `PascalCase` (`ExtractedField`, `FormResponse`).
|
||||
- Enums: `PascalCase` Key, `UPPER_CASE` Values (`AppStatus.IDLE`).
|
||||
- Routes: `kebab-case` (`/api/process`).
|
||||
|
||||
## Security Rules
|
||||
|
||||
1. Kein API-Key im Code und nicht in `.env.local`. Auth läuft über das
|
||||
bestehende Claude-Code-Login.
|
||||
2. Uploads nur in `os.tmpdir()`, pro Request eigenes Subverzeichnis,
|
||||
Cleanup im `finally`-Block.
|
||||
3. `multer` mit `limits.fileSize` (z.B. 20 MB) — keine unbegrenzten
|
||||
Uploads.
|
||||
4. `multer` mit Allowlist für MIME-Types (PDF, PNG, JPEG, WEBP).
|
||||
5. Server lauscht nur auf `127.0.0.1`, nicht auf `0.0.0.0`.
|
||||
6. Dateinamen aus dem Upload niemals als Pfad verwenden — immer
|
||||
UUID/Counter.
|
||||
|
||||
## Architecture Rules (non-negotiable)
|
||||
|
||||
1. **Nie** flatten — bricht das Kernversprechen, dass das PDF
|
||||
im Reader editierbar bleibt. pdfjs' `saveDocument()` flacht nicht,
|
||||
das ist genau der richtige Modus.
|
||||
2. Werte **immer** über `annotationStorage.setValue(widgetId, { value })`
|
||||
setzen, nicht versuchen einzelne Annotation-Objekte zu mutieren.
|
||||
3. Original-PDF-Bytes bleiben strukturell unverändert. Nur Feldwerte
|
||||
werden gesetzt. Keine Seiten-Mutationen, keine neuen Objekte, keine
|
||||
Annotations.
|
||||
4. Wenn Ziel-PDF keine AcroForm-Felder hat: Hard-Fail mit klarer
|
||||
User-Meldung. **Kein** Fallback auf Overlay/Koordinaten-Modus.
|
||||
5. `claude`-Subprozess **immer** mit `--permission-mode bypassPermissions`,
|
||||
sonst blockiert er auf Tool-Prompts.
|
||||
6. Der Server gibt **nur** strukturiertes JSON an das Frontend weiter —
|
||||
Claude-Stdout nie ungeparst durchreichen.
|
||||
7. Temp-Verzeichnisse werden nach jedem Request gelöscht, auch im
|
||||
Fehlerfall.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Client zeigt alle Fehler im roten Banner auf der Upload-Seite.
|
||||
- Server-Errors: JSON `{ error: string, details?: string }`, HTTP-Status
|
||||
4xx/5xx passend.
|
||||
- Claude-CLI-Exitcode ≠ 0 oder Timeout → 502 + `message: "Claude CLI
|
||||
failed"` + Exit-Code.
|
||||
- Kein Retry auf Server-Seite. Bei Bedarf triggert der User den Request
|
||||
erneut.
|
||||
- Unhandled rejections im Server: `process.on('unhandledRejection', ...)`
|
||||
loggen, nicht crashen.
|
||||
|
||||
## Claude CLI Interface
|
||||
|
||||
Der Aufruf aus `server/claudeRunner.ts`:
|
||||
|
||||
```bash
|
||||
claude \
|
||||
-p "<prompt>" \
|
||||
--output-format json \
|
||||
--permission-mode bypassPermissions
|
||||
```
|
||||
|
||||
Prompt-Grobskizze (Deutsch, weil Formulare deutsch sind):
|
||||
|
||||
```
|
||||
Du füllst ein deutsches Behördenformular aus.
|
||||
|
||||
TARGET-FORM: <tempdir>/form.pdf
|
||||
SOURCE: <tempdir>/source.pdf
|
||||
|
||||
Die Feldnamen im TARGET-Formular sind:
|
||||
- feld1 (PDFTextField)
|
||||
- feld2 (PDFCheckBox)
|
||||
...
|
||||
|
||||
Lies beide Dateien mit dem Read-Tool, extrahiere die Werte aus dem
|
||||
SOURCE und gib NUR ein JSON-Objekt zurück im Format:
|
||||
|
||||
{
|
||||
"summary": "...",
|
||||
"fields": [
|
||||
{ "key": "feldname", "label": "...", "value": "...",
|
||||
"sourceContext": "...",
|
||||
"validation": { "status": "VALID"|"WARNING"|"INVALID",
|
||||
"message": "...", "suggestion": "..." } }
|
||||
]
|
||||
}
|
||||
|
||||
Deutsches Format:
|
||||
- Datum: DD.MM.YYYY
|
||||
- Zahlen: Komma als Dezimaltrenner
|
||||
- Checkbox-Wert: "X" wenn angekreuzt, "" sonst
|
||||
```
|
||||
|
||||
Das JSON landet als String im `result`-Feld des CLI-Output-Wrappers
|
||||
(`--output-format json`). `claudeRunner.ts` zieht es raus und
|
||||
`JSON.parse`t es.
|
||||
Loading…
Add table
Add a link
Reference in a new issue