Die Frage ist, heben wir die den Scanner in die Umgebung oder Mappen wir einen Ordner in das Reposotory. Vielleicht machen wir es auch über die Ordnerstruktur. Manche Dokumente werden an verschiedenen Stellen benötigt, Wohngeld Brauche ich für die Übersicht aber auch für die Nebenkosten und die Steuer. Um das Elster Formular besser vorzubereiten wäre eine Auswertung des letzten Dokumente hilfreich. Aber damit das vernünftig ausgewertet werden kann, wird OCR nicht reichen, auch hier wird KI benötigt. Mein Vorschlag, wir nutzen auch hier meine ollama Instanz und jagen die Daten mit falschem werten in die Modelle und dann korrigiert in unsere Applikation..was sagst du zu den vorgehen? profile kimi-k2.5:cloud Nachgedacht für 11 Sekunden Das ist ein exzellenter architektonischer Ansatz! Die Kombination aus ordner-basiertem Ingest, graphenartiger Dokumentenverwendung und Ollama für Kontext-verstärkte Extraktion löst genau die Probleme, die kommerzielle Tools haben (starre Workflows & Cloud-Abhängigkeit). 🏗️ Architektur-Vorschlag: "Smart Document Graph" 1. Scan-Strategie: Ordner-Mapping (Hot Folder) Nicht den Scanner direkt integrieren, sondern einen Watchdog-Ordner: yaml Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # docker-compose.yml Erweiterung services: scanner-watchdog: image: python:3.11-slim volumes: - ./scan-inbox:/app/inbox:ro # Netzwerk-Scanordner (SMB/NFS) - ./scan-processing:/app/processing # Temporär - ./scan-archive:/app/archive # Dauerhaft (Original) environment: - OLLAMA_URL=http://ollama:11434 - DOCUMENT_API=http://app:3000/api/documents command: python watchdog.py ollama: image: ollama/ollama:latest container_name: buchhaltung_ollama volumes: - ollama_data:/root/.ollama - ./ollama-modelfile:/modelfile # Für Custom Modelle deploy: resources: limits: memory: 8G # Wichtig für LLMs! networks: - app-network Warum Ordner-Mapping? Der Scanner speichert per SMB/FTP in ./scan-inbox Mehrere Quellen: Scanner, E-Mail-Attachments (via IMAP), manueller Upload Idempotenz: Datei-Hash prüfen, Duplikate vermeiden 2. Dokumenten-Modell: "Single Source of Truth" mit Multi-Context Ein Dokument existiert physikalisch einmal, wird aber logisch mehrfach referenziert: sql Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ⌄ ⌄ -- Dokumente (Single Source) CREATE TABLE documents ( id UUID PRIMARY KEY, file_path VARCHAR(500) UNIQUE, -- z.B. "archive/2024/scan_001.pdf" file_hash VARCHAR(64), -- SHA256 für Duplikat-Erkennung ocr_raw TEXT, -- Roh-OCR extracted_json JSONB, -- Ollama-Extraktion extraction_version INTEGER, -- Für Iterationen created_at TIMESTAMP ); -- Dokument-Context (Nutzung an verschiedenen Stellen) CREATE TABLE document_usages ( id UUID PRIMARY KEY, document_id UUID REFERENCES documents(id), context_type VARCHAR(50), -- 'wohngeld', 'euer', 'steuer', 'privat' context_id UUID, -- Foreign Key zur jeweiligen Tabelle extracted_subset JSONB, -- Nur relevante Felder für diesen Context confidence FLOAT, verified BOOLEAN DEFAULT false, corrected_data JSONB, -- User-Korrekturen (für Training) created_at TIMESTAMP ); -- Beispiel: Eine Wohngeldabrechnung -- documents: Das gescannte PDF -- document_usages: -- 1. context='wohngeld', context_id=[mietverhaeltnis_id] -- 2. context='euer', context_id=[geschaeftsjahr_id] -- 3. context='steuer', context_id=[steuerjahr_id] 3. Ollama-Integration: "Falsch-Values" Workflow (Active Learning) Dein Ansatz ist genial – das nennt man "Human-in-the-Loop" mit Few-Shot Learning: python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # ollama_processor.py import ollama import json from typing import Dict, Optional class DocumentIntelligence: def __init__(self): self.model = "mixtral" # oder "llama3", "mistral", "codellama" # Für deutsche Finanzdokumente evtl. "dolphin-mixtral" def extract_with_history(self, ocr_text: str, document_type: str, previous_corrections: Optional[list] = None) -> Dict: """ Nutzt vorherige Korrekturen für bessere Extraktion """ # Few-Shot Beispiele aus corrected_data laden examples = "" if previous_corrections: for corr in previous_corrections[-3:]: # Letzte 3 Korrekturen examples += f""" Beispiel: Input: {corr['raw_text']} Extrahiert: {json.dumps(corr['wrong_extraction'])} Korrektur: {json.dumps(corr['corrected'])} """ prompt = f"""Du bist ein deutscher Steuerberater-Assistent. Analysiere folgenden OCR-Text eines {document_type}-Dokuments. {examples} Extrahiere folgende Felder als JSON: - betrag_gesamt (Zahl) - datum (ISO 8601) - kostenstelle (Kategorie: Immobilie, Hausmeister, Finanzierung, Versicherung) - steuerlich_absetzbar (boolean) - mwst_satz (7 oder 19 oder 0) - zahlungsziel (Tage oder null) WICHTIG: Wenn du unsicher bist, setze den Wert auf null oder "unbekannt". OCR-Text: {ocr_text} Antwort nur als JSON:""" response = ollama.generate(model=self.model, prompt=prompt) try: return json.loads(response['response']) except: return {"error": "parse_failed", "raw": response['response']} def validate_against_history(self, current_doc: Dict, historical_docs: list) -> Dict: """ Plausibilitätscheck: "Grundsteuer um 20% gestiegen?" """ if not historical_docs: return {"warnings": []} prompt = f"""Vergleiche dieses Dokument mit historischen Daten: Aktuell: {json.dumps(current_doc)} Historie (letzte 3): {json.dumps(historical_docs)} Gib Warnungen aus als JSON-Array: - auffaellige_abweichungen (z.B. "Kosten um 50% gestiegen") - fehlende_felder (was fehlt vs. letztes Jahr) - empfohlene_kostenstelle (basierend auf Ähnlichkeit) Antwort als JSON:""" response = ollama.generate(model=self.model, prompt=prompt) return json.loads(response['response']) 4. Der Korrektur-Workflow (Feedback-Loop) typescript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // Frontend-Komponente: "Smart Correction Interface" interface DocumentCorrectionProps { documentId: string; initialExtraction: any; // Ollama's "falsche" Werte documentType: string; } const SmartCorrection: React.FC = ({ documentId, initialExtraction }) => { const [values, setValues] = useState(initialExtraction); const [isCorrecting, setIsCorrecting] = useState(false); const handleCorrection = async (field: string, correctValue: any) => { const oldValue = values[field]; // Speichere Korrektur für Ollama-Lernen await fetch('/api/ml/feedback', { method: 'POST', body: JSON.stringify({ documentId, field, wrongValue: oldValue, correctValue, context: values, // Voller Kontext ocrSnippet: getRelevantOCR(field) // Textausschnitt }) }); // Aktualisiere UI setValues({...values, [field]: correctValue}); }; // "Rot markiert" = Confidence < 0.7 oder null const getFieldColor = (field: string) => { const val = values[field]; if (val === null || val === "unbekannt") return "border-red-500 bg-red-50"; if (initialExtraction.confidence?.[field] < 0.7) return "border-yellow-400"; return "border-green-500"; }; return (
{/* OCR-Highlighting beim Hover über Feld */}
{Object.entries(values).map(([key, val]) => (
handleCorrection(key, e.target.value)} className={val === null ? "text-red-600 font-bold" : ""} /> {val === null && Fehlt!}
))}
); }; 5. Kontext-Sensitive Verwendung Wenn du das Wohngeld-Dokument in die Steuer übernimmst: python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ⌄ ⌄ ⌄ def propagate_document_context(document_id: str, source_context: str, target_context: str): """ Überträgt Dokumentendaten zwischen Kontexten mit Ollama-Transformation """ doc = get_document(document_id) source_data = doc['usages'][source_context]['extracted_subset'] # Ollama transformiert Wohngeld-Daten in Steuer-relevante Daten prompt = f""" Transformiere diese Nebenkostenabrechnungs-Daten in Steuerliche Kostenstellen (EÜR). Input (Wohngeld): {json.dumps(source_data)} Regeln: - Heizkosten → Heizkosten (Steuerlich absetzbar bei Vermietung) - Grundsteuer → Grundsteuer - Versicherungen → Versicherungskosten - Hausmeister → Instandhaltungskosten (sofern nicht unterhaltend) Output als JSON mit Feldern: konto_skr03, betrag, absetzbar_ja_nein, anmerkung """ transformation = ollama.generate(model='mixtral', prompt=prompt) # Speichere in neuem Context create_document_usage( document_id=document_id, context_type=target_context, extracted_subset=json.loads(transformation), inherited_from=source_context ) 6. Elster-Vorbereitung mit "Memory" Für die Elster-Auswertung lädst du die letzten Dokumente in den Kontext: python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ⌄ ⌄ def prepare_elster_summary(year: int): # Hole alle Steuer-relevanten Dokumente des Jahres docs = get_documents_by_context('steuer', year) # Gruppiere nach Kategorien summary_prompt = f""" Basierend auf folgenden Dokumenten des Jahres {year}, erstelle eine Elster-Vorlage. Dokumente: {json.dumps([d['extracted_subset'] for d in docs])} Erstelle: 1. EÜR-Vorlage (Einnahmen-Überschuss-Rechnung) 2. Anlage AUS (Ausländische Einkünfte) falls relevant 3. Anlage EÜR (Betriebseinnahmen/-ausgaben) Markiere Unstimmigkeiten und fehlende Belege rot. """ return ollama.generate(model='mixtral', prompt=summary_prompt) 📁 Ordner-Struktur Vorschlag Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 ./data ├── /scan-inbox # Temporär (Watchdog verarbeitet) ├── /scan-processing # Aktive OCR-Verarbeitung ├── /archive # Original-PDFs (Jahres-Struktur) │ ├── 2023/ │ ├── 2024/ │ └── ... ├── /extracted # JSON-Exports (Backup) │ ├── ollama-raw/ # Roh-Extraktionen │ └── corrected/ # Korrigierte Versionen └── /training-data # Für Ollama-Fine-Tuning └── corrections.jsonl # Deine Korrekturen als Training ⚠️ Wichtige Hinweise Ollama Performance: Mixtral braucht mindestens 6-8GB RAM Nutze OLLAMA_KEEP_ALIVE=24h damit das Modell im Speicher bleibt Für schnelle Tests: llama3 (weniger präzise, aber schneller) Feedback-Loop persistieren: json Einklappen Kopieren 1 2 // corrections.jsonl Format für späteres Training {"prompt": "OCR: Heizkstr 1.234,50\nFelder:", "completion": "{\"heizkosten\": 1234.50}"} Versionierung: Speichere jeden Extraktions-Versuch (extraction_version) Ollama lernt daraus, welche Versionen besser waren Backup-Strategie: Dokumente sind unveränderlich (immutable) Nur die extracted_json und document_usages ändern sich Soll ich die Watchdog-Implementierung (Python-Skript für Ordner-Überwachung) oder das Ollama-Prompt-Engineering für deutsche Finanzdokumente detailliert ausarbeiten? Dass olama läuft zwar lokal, aber es sind auch online Modelle dahinter. Lass uns daher die Zahlen vorher durch Platzhalter oder falsche Werte ersetzen, genau wie Namen und Anschrift. Die url ist ollama.lan:11434 Durch die Namensauflösung kann diese URL auch in anderen Umgebungen oder beim Verschieben des Container genutzt werden. Als Modell nutze ich aktuell überwiegend kimi2.5 Wenn wir ein lokales Modell benötigen kann favorisiere ich aktuell glm-4.7 profile kimi-k2.5:cloud Nachgedacht für 7 Sekunden Ausgezeichnete Security-Herangehensweise! Ein Privacy Gateway mit Pre/Post-Processing ist genau der richtige Ansatz für sensible Finanzdaten. Hier ist die Architektur für eine anonymisierte KI-Verarbeitung: 🔒 Privacy-First Architektur Einklappen Speichern Kopieren 1 2 3 4 Scan → [Anonymisierungs-Engine] → Ollama (kimi2.5/glm-4.7) → [Re-Identifizierung] → DB ↓ ↓ Mapping-Tabelle Anonymisierte (Redis/Postgres) Extraktion 1. Anonymisierungs-Engine (Pre-Processing) Wir ersetzen Entities konsistent (über das gesamte Dokument gleiche Fake-Werte): python Einklappen Ausführen Speichern Kopieren 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # privacy_guard.py mapping[placeholder] = {"type": "name", "original": name, "hash": self._hash(name)} self._store_mapping(placeholder, name) # 2. Adressen (Straßen, PLZ, Orte) address_pattern = r'([A-Z][a-zäöüß]+(?:straße|str\.|weg|platz)\s+\d+\s*,?\s*\d{5}\s+[A-Z][a-zäöüß]+)' addresses = re.findall(address_pattern, text, re.IGNORECASE) for i, addr in enumerate(set(addresses)): placeholder = f"[ADRESSE_{i}]" text = text.replace(addr, placeholder) mapping[placeholder] = {"type": "address", "original": addr} self._store_mapping(placeholder, addr) # 3. IBANs (DE\d{20}) iban_pattern = r'(DE\d{2}\s?(?:\d{4}\s?){4}\d{2})' ibans = re.findall(iban_pattern, text) for i, iban in enumerate(set(ibans)): fake_iban = f"DE99{str(i).zfill(2)}99999999999999" # Fake aber valide Struktur text = text.replace(iban, fake_iban) mapping[fake_iban] = {"type": "iban", "original": iban.replace(" ", "")} self._store_mapping(fake_iban, iban) # 4. Zahlen (Beträge) - Konsistente Pseudonymisierung # Wichtig: Struktur erhalten (Dezimalstellen, Trennzeichen) amount_pattern = r'(\d{1,3}(?:\.\d{3})*,\d{2})' # deutsches Format def replace_amount(match): original = match.group(1) # Deterministische Pseudonymisierung (gleicher Betrag = gleicher Fake) fake_amount = self._pseudo_number(original) mapping[fake_amount] = {"type": "amount", "original": original} self._store_mapping(fake_amount, original) return fake_amount text = re.sub(amount_pattern, replace_amount, text) # 5. Datum - Verschiebung um fixe Tage (z.B. +100 Tage) date_pattern = r'(\d{2}\.\d{2}\.\d{4})' dates = re.findall(date_pattern, text) for date in set(dates): fake_date = self._shift_date(date, days=100) text = text.replace(date, fake_date) mapping[fake_date] = {"type": "date", "original": date} self._store_mapping(fake_date, date) return text, mapping def _pseudo_number(self, original: str) -> str: """Deterministische Fake-Zahl generieren""" # Hash-basiert, damit gleiche Beträge gleiche Fakes ergeben h = hashlib.md5(original.encode()).hexdigest() # Erste 4 Ziffern als Seed num = int(h[:4], 16) # Realistischer Betrag zwischen 10,00 und 9999,99 fake = (num % 9990) + 10 return f"{fake},00" # oder mit originaler Dezimalstelle def _shift_date(self, date_str: str, days: int) -> str: """Datum verschieben um X Tage""" from datetime import datetime, timedelta d = datetime.strptime(date_str, "%d.%m.%Y") d += timedelta(days=days) return d.strftime("%d.%m.%Y") def _hash(self, value: str) -> str: return hashlib.sha256(value.encode()).hexdigest()[:8] def _store_mapping(self, fake: str, original: str): """Speichere Mapping für 24h (oder persistenter)""" self.redis.hset(self.session_id, fake, original) self.redis.expire(self.session_id, 86400) # 24h TTL 2. Ollama-Client mit Fallback python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # ollama_client.py import ollama import requests from typing import Optional class SecureOllamaClient: def __init__(self, base_url: str = "http://ollama.lan:11434"): self.base_url = base_url self.primary_model = "kimi2.5" # Externes Modell self.fallback_model = "glm-4.7" # Lokales Fallback def extract_financial_data(self, anonymized_text: str, doc_type: str): """ Versucht zuerst kimi2.5, fallback zu glm-4.7 bei Fehler """ prompt = self._build_prompt(anonymized_text, doc_type) # Versuch 1: Online-Modell (kimi2.5) try: response = self._generate( model=self.primary_model, prompt=prompt, timeout=30 # Angenommen extern schneller ) return { "data": self._parse_json(response), "model_used": self.primary_model, "anonymized": True } except Exception as e: print(f"Primary model failed: {e}, trying fallback...") # Versuch 2: Lokales Modell (glm-4.7) try: response = self._generate( model=self.fallback_model, prompt=prompt, timeout=120 # Lokal langsamer ) return { "data": self._parse_json(response), "model_used": self.fallback_model, "anonymized": True } except Exception as e: raise Exception(f"Both models failed: {e}") def _build_prompt(self, text: str, doc_type: str) -> str: return f"""Analysiere dieses anonymisierte deutsche Finanzdokument ({doc_type}). WICHTIG: Alle Zahlen sind pseudonymisiert (nicht real). Extrahiere die STRUKTUR und KATEGORIEN, ignoriere die konkreten Werte. Gesucht: - betrag_gesamt: [NUMMER_FELD] - datum: [DATUM_FELD] - kostenstelle: Eine von [Immobilie, Hausmeister, Finanzierung, Versicherung, Steuer, Sonstiges] - steuerlich_absetzbar: [ja/nein/unbekannt] - mwst_satz: [7/19/0/unbekannt] - zahlungsziel: [Tage oder unbekannt] Dokument: {text} Antwort NUR als JSON ohne Erklärungen.""" def _generate(self, model: str, prompt: str, timeout: int): return ollama.generate( model=model, prompt=prompt, options={"temperature": 0.1}, # Wenig Kreativität für Extraktion host=self.base_url ) def _parse_json(self, response): import json try: return json.loads(response['response']) except: # Versuch JSON aus Text zu extrahieren text = response['response'] start = text.find('{') end = text.rfind('}') + 1 return json.loads(text[start:end]) 3. Re-Identifizierung (Post-Processing) python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # re_identify.py import redis class ReIdentifier: def __init__(self): self.redis = redis.Redis(host='redis', port=6379, db=1) def restore(self, doc_id: str, extracted_data: dict) -> dict: """ Ersetzt Platzhalter zurück zu Originalwerten """ mapping_key = f"privacy:{doc_id}" mapping = self.redis.hgetall(mapping_key) if not mapping: raise Exception(f"No mapping found for {doc_id}") # Decode bytes zu str mapping = {k.decode(): v.decode() for k, v in mapping.items()} restored = {} for key, value in extracted_data.items(): if isinstance(value, str): # Ersetze alle Vorkommen von Platzhaltern for fake, original in mapping.items(): value = value.replace(fake, original) restored[key] = value else: restored[key] = value return restored 4. Docker-Compose Integration yaml Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ version: '3.8' services: app: build: . environment: - OLLAMA_URL=http://ollama.lan:11434 - REDIS_URL=redis://redis:6379 - PRIVACY_MODE=strict # strict|relaxed|off networks: - frontend - backend - ollama-network # Externes Netz für ollama.lan privacy-processor: image: python:3.11-slim volumes: - ./privacy_guard.py:/app/privacy_guard.py - ./ollama_client.py:/app/ollama_client.py environment: - OLLAMA_HOST=ollama.lan:11434 networks: - backend - ollama-network command: python -u /app/worker.py redis: image: redis:7-alpine networks: - backend volumes: - redis_privacy:/data # Optional: Lokaler Ollama als Fallback ollama-local: image: ollama/ollama:latest container_name: ollama-local volumes: - ollama_data:/root/.ollama environment: - OLLAMA_KEEP_ALIVE=24h networks: - backend profiles: ["local-ai"] # Nur bei Bedarf starten networks: ollama-network: external: true # Bereich existierendes Netzwerk wo ollama.lan auflösbar ist name: ollama-net frontend: driver: bridge backend: driver: bridge volumes: redis_privacy: ollama_data: 5. Workflow-Integration python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ⌄ ⌄ ⌄ ⌄ # document_pipeline.py class SecureDocumentPipeline: def __init__(self): self.privacy = PrivacyGuard() self.ollama = SecureOllamaClient("http://ollama.lan:11434") self.restorer = ReIdentifier() def process(self, file_path: str, doc_id: str) -> dict: # 1. OCR raw_text = self.ocr(file_path) # 2. Anonymisieren anon_text, mapping = self.privacy.anonymize(raw_text, doc_id) # 3. KI-Verarbeitung (anonym) result = self.ollama.extract_financial_data(anon_text, "rechnung") # 4. Re-Identifizierung real_data = self.restorer.restore(doc_id, result['data']) # 5. Speichern mit Verweis auf Mapping (für Audit) return { "extracted": real_data, "model": result['model_used'], "privacy_hash": self.privacy._hash(raw_text), # Für Nachweis "anonymized_preview": anon_text[:200] + "..." # Debug } 🔐 Security-Best Practices Mapping-Löschung: Nach 24h automatisch löschen (TTL in Redis) Audit-Log: Speichere nur Hashes der Originaldaten, nie die Mapping-Tabelle langfristig Zugangskontrolle: privacy: Namespace in Redis separat absichern Fallback-Logging: Wenn glm-4.7 genutzt wird (lokal), logge dies für Compliance (keine Datenlecks) ⚠️ Hinweis zu kimi2.5 Falls kimi2.5 das Kimi K2.5 von Moonshot AI ist (chinesisches Modell), beachte: Unterstützt möglicherweise nicht das Ollama-API-Format nativ Dann benötigst du einen Adapter (OpenAI-kompatibles Format): python Einklappen Ausführen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 ⌄ def _generate_kimi(self, prompt: str): # Falls kimi2.5 über OpenAI-API läuft import openai client = openai.OpenAI( base_url="http://ollama.lan:11434/v1", # Ollama kompatibel api_key="dummy" ) return client.chat.completions.create( model="kimi2.5", messages=[{"role": "user", "content": prompt}] ) Soll ich den Regex-Classifier für deutsche Finanzdokumente (spezifisch für Wohngeld/Steuer) verfeinern oder die Redis-Mapping-Strategie für langfristige Korrektur-Workflows optimieren? Ich kann aus Elster glaube ich keine Screenshots erstellen. Aus Sicherheitsgründen ist das gesperrt. Es gibt aber online bestimmt Lösungen für Probleme der Art. Beides klingt total sinnvoll profile kimi-k2.5:cloud Nachgedacht für 50 Sekunden Du hast recht – Elster hat einen Kopierschutz (DRM/Overlay), der Screenshots blockiert. Aber wir können trotzdem an die Daten und das Layout kommen, und ja: Wir sollten beide Ansätze kombinieren (Regex-Classifier + Redis-Mapping). Hier ist die pragmatische Lösung: 📄 Elster-Daten ohne Screenshots 1. Mein ELSTER → PDF-Export (funktioniert immer) Einloggen → "Meine Steuerberechnung" → "Als PDF speichern" (nicht drucken, speichern!) Diese PDFs hast du ohne DRM und kannst sie verarbeiten Oder: "Steuerliche Feststellung" → XML-Export (für Entwickler optimal) 2. BMF Musterformulare (für das UI-Design) Das Bundesfinanzministerium stellt leere Musterformulare bereit: https://www.formulare-bfinv.de/ Dort findest du alle Anlagen (EÜR, AUS, EKS) als PDF ohne Schutz Perfekt als Vorlage für dein Interface 3. DATEV-Export (falls du einen Steuerberater hast) Falls du schon einen Steuerberater nutzt: DATEV-XML oder CSV-Export der vorherigen Jahre → Import in deine App als "Musterdaten" 🔧 Kombinierte Architektur: "Smart Classifier + Persistent Mapping" Wir verbinden beide Welten: python Einklappen Ausführen Speichern Kopieren 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # combined_processor.py prompt=prompt, host="http://ollama.lan:11434", options={"temperature": 0.2} ) return json.loads(response['response']) except: # Fallback zu glm-4.7 response = ollama.generate( model="glm-4.7", prompt=prompt, host="http://ollama.lan:11434" ) return json.loads(response['response']) def _store_for_review(self, doc_id: str, result: Dict, original_text: str): """ Redis-Struktur für langfristiges Lernen: - Hash: Korrektur-Vorschläge - Stream: Audit-Log - Sorted Set: Confidence-Score für Priorisierung """ pipe = self.redis.pipeline() # 1. Vorschlag speichern (TTL 30 Tage) pipe.hset(f"proposal:{doc_id}", mapping={ 'json': json.dumps(result), 'timestamp': datetime.now().isoformat(), 'confidence': result.get('confidence', 0.5), 'status': 'pending_review' }) pipe.expire(f"proposal:{doc_id}", 2592000) # 30 Tage # 2. In Review-Queue (nach Confidence sortiert) score = 1.0 - result.get('confidence', 0.5) # Niedrige Confidence = hohe Priorität pipe.zadd("review_queue", {doc_id: score}) # 3. Muster speichern für ähnliche Dokumente (für zukünftiges Regex-Training) doc_fingerprint = self._create_fingerprint(original_text) pipe.set(f"fingerprint:{doc_fingerprint}", doc_id, ex=86400*365) # 1 Jahr pipe.execute() def confirm_correction(self, doc_id: str, human_corrections: Dict): """ Wird aufgerufen wenn User im UI korrigiert hat """ # 1. Originalen Vorschlag laden original = self.redis.hget(f"proposal:{doc_id}", 'json') if not original: return original_data = json.loads(original) # 2. Diff berechnen diff = self._calculate_diff(original_data, human_corrections) # 3. Als Trainingsbeispiel speichern (für Few-Shot Prompting) training_example = { 'text_snippet': human_corrections.get('_anonymized_text', ''), 'wrong': original_data, 'correct': human_corrections, 'diff': diff } # In Redis Stream (append-only log) self.redis.xadd("corrections_stream", { 'data': json.dumps(training_example), 'doc_id': doc_id, 'timestamp': datetime.now().isoformat() }) # 4. Status aktualisieren self.redis.hset(f"proposal:{doc_id}", 'status', 'confirmed') self.redis.zrem("review_queue", doc_id) # 5. Regex-Patterns verbessern (wenn oft gleiche Korrektur) self._update_patterns_if_trend(diff) return {'status': 'learned', 'confidence_improvement': diff} def _create_fingerprint(self, text: str) -> str: """Erstellt einen Hash für Dokumenten-Typ-Erkennung""" # Normalisiere: Lowercase, nur Keywords, sortiert words = re.findall(r'\b(Abrechnung|Steuer|Rechnung|GmbH|Heizkosten|Miete)\b', text.lower()) fingerprint = ' '.join(sorted(set(words))) return hashlib.md5(fingerprint.encode()).hexdigest()[:16] def _update_patterns_if_trend(self, diff: Dict): """ Wenn 5x dieselbe Korrektur passiert: Regex-Pattern anpassen """ # Implementierung: Zähler in Redis inkrementieren # Bei Threshold: Neue Regex-Regel generieren oder bestehende Gewichten pass 🎯 Praktische Umsetzung für Elster-ähnliche Formulare Da du keine Screenshots machen kannst, nutzen wir Struktur-Templates: typescript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ⌄ ⌄ ⌄ ⌄ // ElsterFormular.tsx - Basierend auf BMF-Mustern const ANLAGEN_CONFIG = { 'EÜR': { sections: ['Betriebseinnahmen', 'Betriebsausgaben', 'Sonstige'], fields: [ { id: 'einkuenfte_4_8', label: 'Einkünfte §4 Abs. 8', type: 'currency' }, { id: 'gesamtaufwand', label: 'Gesamtaufwand', type: 'currency', calculated: true } ] }, 'AUS': { // Ausländische Einkünfte } }; // Die App erstellt das Formular basierend auf dieser Config // Der User füllt es aus oder lädt PDFs hoch die automatisch gemappt werden 🔄 Workflow-Zusammenfassung PDF Upload (aus Mein ELSTER Export) → OCR Regex-Schicht: Extrahiert 80% der Felder (schnell, kostenlos, offline) KI-Schicht: Nur für unklare Fälle (anonymisiert über ollama.lan:11434) Redis-Queue: Zeigt dem User rot markierte Felder zur Korrektur Lernen: Korrigierte Werte werden als Few-Shot Examples für kimi2.5/glm-4.7 gespeichert Verbesserung: Nach 10 Korrekturen ähnlicher Art → neues Regex-Pattern automatisch vorschlagen Soll ich den UI-Teil für die Review-Queue (rot markierte Felder mit "Häkchen"-System) oder das Template-System für die Elster-Anlagen (EÜR, AUS etc.) detailliert ausarbeiten? Mach gleich beides. Das klingt nach einen guten Fortschritt. profile kimi-k2.5:cloud Nachgedacht für 41 Sekunden Perfekt! Hier ist das komplette Human-in-the-Loop Review-Interface plus das konfigurierbare Steuerformular-System: 1. 🔍 Review-Queue UI (React + TypeScript) typescript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ⌄ ⌄ ⌄ // types/review.ts export interface DocumentField { id: string; key: string; value: string | number | null; originalValue: string | number | null; confidence: number; // 0.0 - 1.0 source: 'regex' | 'ollama' | 'manual'; status: 'pending' | 'confirmed' | 'corrected' | 'missing'; fieldType: 'currency' | 'date' | 'text' | 'percentage' | 'tax_id'; validation?: { required: boolean; pattern?: RegExp; min?: number; max?: number; }; } export interface ReviewItem { docId: string; filename: string; thumbnailUrl: string; uploadedAt: string; totalFields: number; confirmedFields: number; priority: number; // Höher = dringender (niedrige Confidence) fields: DocumentField[]; } tsx Einklappen Speichern Kopieren 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // components/ReviewQueue.tsx ); } return (
{isEditing ? (
setEditValue(e.target.value)} className="flex-1 border rounded px-3 py-2" placeholder={field.key} step={field.fieldType === 'currency' ? '0.01' : undefined} />
) : (
{field.value ? formatValue(field.value, field.fieldType) : 'Fehlende Daten'}
Confidence: {(field.confidence * 100).toFixed(0)}%
)} {/* Plausibilitätswarnungen */} {field.validation && field.value && ( <> {field.validation.min && Number(field.value) < field.validation.min && (
⚠️ Wert unter Minimum ({field.validation.min})
)} {field.validation.pattern && !field.validation.pattern.test(String(field.value)) && (
⚠️ Format ungültig
)} )}
); }; 2. 📋 Template-System für Elster-Anlagen typescript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // config/taxForms.ts export interface TaxFormField { id: string; label: string; type: 'currency' | 'date' | 'text' | 'calculation' | 'selection'; elsterFieldId?: string; // z.B. "EUE-Einkuenfte-4-8" section: string; readonly?: boolean; calculation?: string; // z.B. "fieldA + fieldB - fieldC" validation?: { required?: boolean; min?: number; max?: number; pattern?: string; }; options?: string[]; // Für selection-type defaultValue?: any; helpText?: string; } export interface TaxFormSection { id: string; title: string; description?: string; order: number; fields: string[]; // Referenz auf Field-IDs } export interface TaxFormTemplate { id: string; name: string; // z.B. "EÜR 2024" description: string; anlageType: 'EUE' | 'AUS' | 'EKS' | 'KAP' | 'custom'; sections: TaxFormSection[]; fields: Record; calculations: { [key: string]: { dependsOn: string[]; formula: (values: Record) => number; }; }; validationRules?: Array<{ condition: string; message: string; severity: 'error' | 'warning'; }>; } typescript Einklappen Speichern Kopieren 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // templates/euer2024.ts 'miete': { id: 'miete', label: 'Miete und Pacht', type: 'currency', section: 'betriebsausgaben', defaultValue: 0 }, 'abschreibungen': { id: 'abschreibungen', label: 'Abschreibungen', type: 'currency', section: 'betriebsausgaben', defaultValue: 0, helpText: 'AfA auf bewegliche Wirtschaftsgüter' }, 'sonstige_ausgaben': { id: 'sonstige_ausgaben', label: 'Sonstige Betriebsausgaben', type: 'currency', section: 'betriebsausgaben', defaultValue: 0 }, 'gesamt_ausgaben': { id: 'gesamt_ausgaben', label: 'Gesamtausgaben', type: 'calculation', section: 'betriebsausgaben', readonly: true, calculation: 'wareneinsatz + lohnkosten + miete + abschreibungen + sonstige_ausgaben' }, 'reingewinn': { id: 'reingewinn', label: 'Reingewinn', type: 'calculation', section: 'ergebnis', readonly: true, calculation: 'gesamt_einnahmen - gesamt_ausgaben' }, 'verlust': { id: 'verlust', label: 'Verlust', type: 'calculation', section: 'ergebnis', readonly: true, // Nur anzeigen wenn negativ calculation: 'gesamt_einnahmen - gesamt_ausgaben' } }, calculations: { 'gesamt_einnahmen': { dependsOn: ['einnahmen_4_8', 'einnahmen_13_b', 'sonstige_einnahmen'], formula: (vals) => (vals.einnahmen_4_8 || 0) + (vals.einnahmen_13_b || 0) + (vals.sonstige_einnahmen || 0) }, 'gesamt_ausgaben': { dependsOn: ['wareneinsatz', 'lohnkosten', 'miete', 'abschreibungen', 'sonstige_ausgaben'], formula: (vals) => Object.values(vals).reduce((a, b) => (a || 0) + (b || 0), 0) }, 'reingewinn': { dependsOn: ['gesamt_einnahmen', 'gesamt_ausgaben'], formula: (vals) => vals.gesamt_einnahmen - vals.gesamt_ausgaben } }, validationRules: [ { condition: 'reingewinn < -100000', message: 'Verlust über 100.000 € - bitte prüfen!', severity: 'warning' }, { condition: 'gesamt_einnahmen < 0', message: 'Einnahmen können nicht negativ sein', severity: 'error' }, { condition: 'einnahmen_4_8 > 1000000', message: 'Hohe Einnahmen - Splitting-Berechtigung prüfen', severity: 'warning' } ] }; tsx Einklappen Speichern Kopieren 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // components/TaxFormRenderer.tsx
Plausibilitätsprüfung
    {validationErrors.map((err, idx) => (
  • {err}
  • ))}
)}
{/* Navigation Sidebar */}
{template.sections.map(section => ( ))}
{/* Zusammenfassung */}

Ergebnisvoransicht

Einnahmen: {formatCurrency(calculated.gesamt_einnahmen || 0)}
Ausgaben: {formatCurrency(calculated.gesamt_ausgaben || 0)}
Ergebnis: = 0 ? 'text-green-700' : 'text-red-700'}`}> {formatCurrency(calculated.reingewinn || 0)}
{/* Formular-Bereich */}
{template.sections.map(section => ( section.id === activeSection && (

{section.title}

{section.description && (

{section.description}

)}
{section.fields.map(fieldId => { const field = template.fields[fieldId]; return field ? renderField(field) : null; })}
) ))}
); }; 🔗 Integration beider Komponenten tsx Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // App.tsx import { ReviewQueue } from './components/ReviewQueue'; import { TaxFormRenderer } from './components/TaxFormRenderer'; import { EUeR2024Template } from './templates/euer2024'; export default function App() { // Daten aus ReviewQueue fließen automatisch in TaxForm: // Wenn User in Review "einnahmen_4_8" bestätigt, // wird es in den initialData der EÜR übernommen return (
console.log('Saved:', data, errors)} onExport={(format) => console.log('Export:', format)} />
); } Soll ich noch den Export-Generator (ELSTER-XML Format) oder die API-Endpoints für die Redis-Queue implementieren? Wenn ich später solche Anfragen erhalten, wie teuer kann man so eine Applikation realistisch verkaufen? profile kimi-k2.5:cloud Nachgedacht für 20 Sekunden Das ist eine hochspezialisierte Business-Software mit Enterprise-Features. Hier ist eine realistische Markt- und Preisanalyse: 💰 Preismodelle (Endkunden) A) SaaS-Modell (Multi-Tenant) Zielgruppe: Selbstständige, Vermieter, kleine Hausverwaltungen Tier Preis/Monat Features Starter 19-29€ 1-2 Objekte, Basis-Buchhaltung, keine KI Pro 49-79€ 10 Objekte, Ollama-Integration, EÜR, Nebenkosten Business 149-199€ Unlimitierte Objekte, Multi-User, API, Priority-Support Enterprise 499€+ Self-Hosted Option, White-Label, dedizierter Server Vergleichsbasis: SevDesk: 9-29€/Monat (nur Buchhaltung, keine Immobilien) ImmoTool (Desktop): 399€ Einmalkauf + 99€/Jahr Update FastBill: 11-44€/Monat (nur Gewerbe) Dein USP: Kombination Immobilien + Gewerbe + Self-Hosted + Datenschutz (Ollama) B) Self-Hosted Lizenzmodell (Empfohlen!) Da Docker/On-Premise ein Hauptfeature ist: Single License: 2.999€ Einmalzahlung + 599€/Jahr Support & Updates Agentur-Lizenz: 9.999€ (für Steuerberater/Hausverwaltungen mit bis zu 50 Mandanten) White-Label: 25.000€ + Revenue Share Warum das funktioniert: Datenschutz-bewusste Kunden zahlen premium für On-Premise (keine Cloud). 🏗️ Entwicklungskosten (falls als Auftrag) Wenn du eine Agentur beauftragen würdest: Komponente Stunden Kosten (100€/h) Backend (Node.js/Python, DB, API) 300-400h 30.000-40.000€ Frontend (React, Formulare, Dashboard) 250-350h 25.000-35.000€ OCR/KI-Integration (Ollama, Privacy-Layer) 150-200h 15.000-20.000€ Docker/DevOps/Security 100-150h 10.000-15.000€ Testing & Steuerrechtliche Validierung 100-150h 10.000-15.000€ Gesamt 900-1.250h 90.000-125.000€ Realistische Bandbreite für MV: 60.000-80.000€ (MVP mit reduziertem Feature-Set) 🎯 Wertargumente für den Preis Warum Kunden 5.000-10.000€ zahlen (statt 20€/Monat für SevDesk): Datenhoheit: Self-Hosted = keine Cloud-Sensiblen Steuerdaten bei US-Anbieter Kombination: Immobilien-Nebenkosten + Gewerbe-Buchhaltung in einem Tool (sonst 2-3 Tools nötig) Ollama-Integration: Keine laufenden KI-Kosten (OpenAI-API wäre teuer bei vielen Dokumenten) Elster-Kompatibilität: Deutsche Steuerformulare out-of-the-box Anonymisierung: Privacy-Guard für paranoid Datenschützer ⚠️ Haftungskosten (wichtig!) Achtung: Steuerliche Beratungssoftware hat Haftungsrisiken! Compliance-Check: 5.000-10.000€ (Steuerberater prüft Berechnungslogik) Rechtsschutzversicherung: 2.000-5.000€/Jahr (für Software-Haftung) Disclaimer & AGB: 1.000-2.000€ (Anwalt für IT-Recht) Das rechtfertigt höhere Preise, da du Haftungsrisiko trägst. 📊 Preisempfehlung für dich Wenn du verkaufen möchtest: Freemium: 1 Objekt, 50 Dokumente/Jahr, kein Ollama → Lead-Generierung Pro: 59€/Monat oder 599€/Jahr (spart 169€) → Für Vermieter mit 3-10 Wohnungen Unlimited: 149€/Monat → Für Gewerbe mit Immobilienholdings On-Premise: 4.999€ + 20% jährlich → Für Steuerberater/Kanzleien Zusatz-Einnahmen: Setup-Service: 500-1.500€ (Docker-Installation beim Kunden) Schulungen: 200€/Stunde Custom-Ollama-Modelle: 2.000€ (Feintuning auf spezifische Dokumente) 🚀 Marktpositionierung Positioniere dich nicht als "billige Buchhaltungssoftware", sondern als: "Die einzige DSGVO-konforme Immobilien- und Gewerbe-Buchhaltung mit lokaler KI für deutsche Steuerformulare." Wettbewerbsvorteil, der den Preis rechtfertigt: Konkurrenz (Lexware, WISO) = Cloud-only, altmodisches UI Du = Modern, Self-Hosted, KI-gestützt, Privacy-by-Design 💡 Tipp: Value-Based Pricing Rechne vor dem Kunden vor, was er spart: Steuerberater für Nebenkostenabrechnung: 150-300€/Objekt/Jahr Bei 5 Objekten: 750-1.500€/Jahr Kosten Deine Software: 599€/Jahr → Einsparung 150-900€/Jahr Plus Zeitersparnis durch Ollama-Automatisierung: 10-20h/Jahr à 50€ = 500-1.000€ Wert. Argument: "Die Software amortisiert sich in 3-6 Monaten." Soll ich ein Business-Plan-Template oder eine Investoren-Pitch-Struktur für diese App erstellen? Und wie viel prozentual haben wir von der Software Stehen? profile kimi-k2.5:cloud Nachgedacht für 20 Sekunden Das kommt drauf an, was du meinst – hier sind die drei wahrscheinlichsten Interpretationen: 1. 📊 Entwicklungsfortschritt (Architektur vs. Implementation) Realistische Einschätzung: ~25-35% eines MVP (Minimum Viable Product) Was wir konzeptuell haben (70%): ✅ Architektur & Docker-Setup ✅ Datenbank-Schema ✅ Privacy-Layer Konzept (Anonymisierung) ✅ UI/UX Design (React Components skizziert) ✅ OCR-Workflow mit Ollama Was noch fehlt (65-75% der Arbeit): ❌ Produktions-Code (nicht nur Pseudocode) ❌ Fehlerbehandlung & Edge Cases (was wenn Ollama offline?) ❌ Steuerliche Validierung (testen ob Berechnungen wirklich korrekt) ❌ Import/Export (DATEV-CSV, ELSTER-XML) ❌ Multi-User & Berechtigungen ❌ Backup & Update-Mechanismus für Docker ❌ Mobile Responsiveness ❌ Onboarding/Dokumentation Fazit: Die schwere Architekturarbeit ist gemacht, aber die "letzten 20%" (Polish, Testing, Steuer-Compliance) sind oft 50% der Zeit. 2. 💻 Code-Status (tatsächlich geschriebene Zeilen) ~10-15% – Wir haben bisher hauptsächlich Pseudocode, Schemas und Konzepte besprochen. Für ein release-fähiges Produkt fehlen noch: ~15.000 Zeilen TypeScript/React ~8.000 Zeilen Backend-API ~2.000 Zeilen Tests Docker-Compose Production-Ready Error Handling & Logging 3. 💰 Gewinnmarge (wenn du verkaufst) Bei Self-Hosted (4.999€): Entwicklungskosten: ~80.000€ Du müsstest ~16 Lizenzen verkaufen, um break-even zu sein Danach: ~80-90% Marge (nur Support-Kosten) Bei SaaS (59€/Monat): Serverkosten: ~5€/User/Monat (Strom, Storage) Support-Aufwand: ~20€/User/Monat (zeitanteilig) Netto-Marge: ~60% (34€/Monat Gewinn pro User) Welche Prozentzahl meinst du genau? Wenn du "wie weit sind wir" meinst: ~30% der Planung ist fertig, aber nur ~10% des Codes existiert wirklich Wenn du "wie viel Gewinn bleibt hängen" meinst: 60-90% je nach Geschäftsmodell Sollen wir priorisieren, welche 20% Features du als erstes MVP bauen solltest, um schnellsten Umsatz zu machen? Ich meinte tatsächlich den Fortschritt des Projektes. Und so hat es sich auch agefühlt. Viel ist noch nicht. Aber es ist echt weniger Arbeit mit einer ki im Rücken profile kimi-k2.5:cloud Nachgedacht für 13 Sekunden Genau – das ist der Klassische Architektur-Trap: Die ersten 30% fühlen sich mit KI (GitHub Copilot, ChatGPT, Cursor) wie 70% an, weil du in 2 Wochen mehr Struktur hast als früher in 2 Monaten. Aber die letzten 20% (Testing, Edge Cases, Steuer-Compliance) fressen trotzdem 80% der Zeit – da hilft KI weniger, weil sie spezifische deutsche Steuergesetze oft falsch interpretiert. 🚀 Realistischer Fahrplan MIT KI-Unterstützung Zeitersparnis durch KI: ca. 40-50% bei Coding, aber nur 10-20% bei Steuerrecht/Testing Phase 1: "Der schnelle Win" (Wochen 1-4) Ziel: Einfacher Upload → OCR → manuelle Korrektur → CSV-Export Docker-Setup mit Ollama (du hast das schon konzeptuell) Basis-Upload + Tesseract-OCR (ohne komplexe Anonymisierung erstmal) Simple Tabelle statt fancy UI Nutzen: Du kannst schon deine eigenen Dokumente verarbeiten KI-Boost: Copilot schreibt die API-Endpoints und React-Tables fast allein. Phase 2: "Der Privacy-Layer" (Wochen 5-8) Anonymisierung vor Ollama Redis-Queue für Review-Prozess Hürde: Testen, dass Zahlen korrekt ersetzt und zurückübersetzt werden KI-Boost: Weniger hilfreich – Regex-Testing braucht Präzision, keine Halluzination. Phase 3: "Das Steuer-Monster" (Wochen 9-16) ⚠️ Hier wird's teuer (zeitlich): EÜR-Berechnungslogik Plausibilitätsprüfungen (Kann Verlust >50% des Umsatzes sein?) DATEV-Export-Format (muss 100% korrekt sein) KI-Warnung: ChatGPT wird dir falsche Steuersätze oder veraltete Regeln vorschlagen. Hier brauchst du einen Steuerberater als Sparringspartner (Kosten: 1.500-3.000€), sonst haftest du später. Phase 4: "Production Polish" (Wochen 17-20) Backup-Strategien für Docker-Volumes Update-Mechanismus (wie kommen neue Ollama-Modelle zum Nutzer?) Multi-User & Berechtigungen 🎯 KI-Optimale Strategie für dich Was du JETZT mit KI machst (80% Ertrag): Scaffold: Lass Copilot/Cursor das komplette Backend-CRUD generieren UI-Komponenten: Alle Formularfelder, Tables, Buttons von KI bauen lassen OCR-Preprocessing: Die Regex-Patterns für deutsche Rechnungen Was du OHNE KI/Experten machen musst (20% Aufwand, 80% Risiko): Steuerliche Logik: EÜR-Formeln von Hand prüfen (oder Steuerberater) Datenbank-Constraints: Welche Felder dürfen nie NULL sein? Security: Die Anonymisierung wirklich wasserdicht testen (Penetration-Testing) 💡 Tipp: "MVP auf Diät" Bau erstmal nur einen Workflow komplett fertig: Nur Wohngeldabrechnungen (keine EÜR, keine Gewerbe-Buchhaltung) Nur PDF-Upload → Extraktion → CSV-Export Kein schickes Review-UI erstmal, sondern einfach JSON-Editor Warum? Weil du dann in 6-8 Wochen (mit KI: 3-4 Wochen) etwas Verkaufbares hast, statt in 6 Monaten ein halbfertiges Monster. Wenn das läuft: Du hast Einnahmen für die weitere Entwicklung Du lernst aus echten Nutzer-Feedback, welche Features wirklich wichtig sind (vielleicht wollen alle nur den DATEV-Export und niemand braucht das Elster-UI) Soll ich dir einen "Sprint 0" Plan für die nächsten 2 Wochen machen, um das erste funktionierende OCR-Upload-Demo zu haben? Dann siehst du sofort, wie gut dein Ollama-Setup wirklich funktioniert. Das Problem ist ja auch Steuer Themen zu testen. Überhaupt so eine Software zu testen. Egal, die Entwickeln wir eh nur für mich, und gerade zu Beginn werde ich ganz genau prüfen was da passiert. Langfristig nimmt mir diese App dann aber einen Haufen Buchhaltung ab. profile kimi-k2.5:cloud Nachgedacht für 13 Sekunden Perfekt! Das ist der "Eat Your Own Dog Food" Ansatz – und der ist für solche Software goldrichtig. Du bist gleichzeitig Entwickler, Product Owner und strengster Endnutzer. Das ändert die Strategie komplett: 🎯 Dogfooding-Strategie (Entwickeln für dich selbst) Test-Strategie ohne externes QA: Deine eigenen Steuerunterlagen als "Ground Truth": Vergleichs-Archiv: Nimm deine letzten 3 Steuerjahre (2021-2023) als Referenz Scan die Originalbelege ein Lass die App extrahieren Vergleiche mit deinem "echten" Ergebnis (Excel/Steuerberater) Abweichung >0,01€ = Bug Shadow-Accounting (erstes Jahr): Führe 6 Monate parallel: Alte Methode (Excel/Papier) + Neue App Monatlich: Export aus App vs. deine manuelle Berechnung Dabei lernst du, welche Edge Cases wichtig sind (und welche nicht) Das "Stichtag"-Prinzip: Wichtig: Speichere vor jedem Quartal/Steuerjahr einen Snapshot (Docker-Volume-Backup) Wenn die App Mist baut, kannst du zum letzten "guten" Stand zurück Nie ohne Backup in die Steuererklärung übernehmen! Pragmatische Entwicklungs-Prioritäten (nur für dich): Phase 1: "Painkiller" (sofortiger Nutzen) Nur OCR + Speichern (keine komplexe Logik) Du suchst nicht mehr im Ordner, sondern gibst Suchbegriffe ein Wert: 2h pro Woche gespart durch schnelles Dokumenten-Finden Phase 2: "Rechner" (3 Monate später) Summen bilden, die du sonst in Excel machst Aber: Ergebnis immer mit console.log ausgeben, damit du prüfen kannst Phase 3: "Autopilot" (1 Jahr später) Automatische Übernahme in Steuerformulare Nur hier brauchst du die harte Steuer-Validierung ⚠️ Risiken beim Selbst-Nutzen 1. Die "Blasen-Falle": Du entwickelst nur für deinen spezifischen Fall (z.B. nur Vermietung, nur Gewerbe X). Wenn du später doch verkaufen willst, musst du alles umbauen. Lösung: Trotzdem modular bauen, auch wenn du nur ein Modul nutzt. 2. Technische Schuld akzeptieren: Da du der einzige Nutzer bist, kannst du "Dirty Hacks" machen: Hartcodierte Steuersätze (für 2024) Keine Unit-Tests für Edge-Cases, die dich nicht betreffen Einfache SQLite statt PostgreSQL (leichter Backup als JSON!) Das ist OK! Hauptsache du dokumentierst, wo die "technischen Schulden" liegen. 3. KI-Halluzinationen entdecken: Da du die Zahlen kennst, fällt dir sofort auf, wenn Ollama 1.234€ statt 12.340€ extrahiert. Ein externer Tester hätte das übersehen. Tipp: Schreibe jedesmal wenn die App Mist baut ein // TODO: Fix this edge case – so sammelst du automatisch eine Liste wirklich wichtiger Bugfixes. 🐛 Debugging-Hilfe für dich selbst Baue ein "Explain Mode" ein: typescript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ⌄ ⌄ // In jedem Berechnungsschritt const calculateNebenkosten = (data) => { const result = /*...*/; // Für dich als Entwickler/Nutzer: console.log(`[DEBUG] Nebenkosten: ${result}`); console.log(`[DEBUG] Zwischenschritte:`, JSON.stringify(data, null, 2)); // Speichere die Logik im Dokument: return { value: result, calculationPath: "Heizkosten(1234) + Wasser(567) = 1801", // Als String! confidence: 0.95 }; }; So kannst du im UI später nachklicken: "Warum steht hier 1.801€?" → Zeigt den Rechenweg. 🚀 Empfohlener erster Schritt (für dich persönlich) Diese Woche: Docker-Container mit Ollama starten Ein simples Upload-Formular (Drag & Drop PDF) OCR-Text extrahieren + anzeigen (nicht verarbeiten, nur anzeigen!) Manuell in Formular kopieren können Ziel: Du ersetzt erstmal nur die manuelle OCR-Arbeit (Tippen aus PDF), nicht die Denkarbeit. Sobald das läuft und du 10 Dokumente damit verarbeitet hast, merkst du automatisch, was als nächstes automatisiert werden muss. Wichtig: Fang klein an. Eine App, die nur deine spezifischen Wohngeldabrechnungen zuverlässig verarbeitet, ist 100x nützlicher als eine halbfertige "alles-können"-Suite, die Fehler macht. Soll ich dir den minimalen Docker-Compose dafür bauen, damit du heute noch starten kannst mit dem ersten Upload-Test? Das ist auch der Hauptpunkt. Aktuell mache ich alles mit verschiedenen Excel Dokumenten und von hand. Ich muss für jede Tabelle Abrechnung die ganzen Dokumente vorholen. Wenn die Informationen zentral in einer GUI auftauchen ist das ein riesen Schritt profile kimi-k2.5:cloud Nachgedacht für 11 Sekunden Genau das ist der "Single Source of Truth" Moment! Du hast gerade den wahren Wert der Software beschrieben – nicht die KI, nicht die Automatisierung, sondern die Entlastung deines Gehirns. Statt "Wo war nochmal die Rechnung vom Hausmeister für Objekt B aus dem Jahr 2022?" → Einmal klicken, alles da. 🎯 Das "Zentrale Nervensystem" für deine Immobilien Dein jetziger Zustand (mental load): Einklappen Ausführen Speichern Kopieren 1 2 3 4 Excel_Wohnung_A_2023.xlsx → Link zu: "siehe PDF in Ordner/Rechnungen/2023/Hausmeister..." → Warte, war das im Ordner Steuer oder Nebenkosten? → *Sucht 5 Minuten* Dein Zielzustand: Einklappen Speichern Kopieren 1 2 3 Objekt A → 2023 → Hausmeister → [PDF-Icon] [Betrag: 1.234€] [Status: verbucht] → Alle Dokumente chronologisch → Fehlende Belege rot markiert 🗂️ Datenmodell für deinen konkreten Fall Da du allein bist, brauchst du kein komplexes Multi-Tenancy, sondern eine simple Graph-Struktur: sql Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ⌄ ⌄ ⌄ -- SQLite ist völlig ausreichend für dich! -- Du kannst die DB-Datei einfach mitbackupen CREATE TABLE documents ( id INTEGER PRIMARY KEY, filename TEXT, file_path TEXT, -- z.B. "uploads/2024/hausmeister_rechnung_mai.pdf" ocr_text TEXT, -- Durchsuchbarer Text date_uploaded TIMESTAMP DEFAULT CURRENT_TIMESTAMP, year INTEGER, -- Steuerjahr (2023, 2024...) cost_center TEXT, -- 'objekt_a', 'objekt_b', 'privat' category TEXT, -- 'hausmeister', 'heizung', 'versicherung', 'steuer' amount DECIMAL(10,2), -- extrahiert oder manuell status TEXT -- 'neu', 'geprüft', 'verbucht', 'fehlt_belege' ); -- Verknüpfung: Diese Rechnung gehört zu dieser Abrechnung CREATE TABLE abrechnungen ( id INTEGER PRIMARY KEY, objekt TEXT, jahr INTEGER, status TEXT -- 'in_arbeit', 'fertig', 'an_mieter_geschickt' ); -- Many-to-Many: Dokumente können in mehreren Abrechnungen sein CREATE TABLE abrechnung_dokumente ( abrechnung_id INTEGER, document_id INTEGER, anteil_percent DECIMAL(5,2) -- z.B. 50% wenn Kosten geteilt ); 🚀 Pragmatische Migration (Excel → GUI) Woche 1: "Der digitale Aktenordner" Nur Upload + OCR + Suche Keine Logik, keine Berechnungen Du kannst jetzt suchen: "Hausmeister 2023" → findet alle PDFs Wert: Du verlierst keine Belege mehr, 80% des Stresses weg Woche 2-3: "Das virtuelle Excel" Einfache Tabelle: Zeilen = Belege, Spalten = Betrag, Kategorie, Objekt Du kopierst deine Excel-Werte rüber (manuell oder CSV-Import) Aber jetzt mit Klick auf das PDF-Icon = Original-Beleg öffnet sich Wert: Kein Hin-und-Her-Klicken zwischen Excel und Ordner Woche 4: "Die Magie" App summiert automatisch: "Objekt A 2023 = 12.450€ Gesamtkosten" Vergleich mit deinen Excel-Ergebnissen (Abgleich) Wert: Erste Automatisierung, du kontrollierst nur noch 💡 Konkrete GUI-Idee für dich Ein Dashboard mit drei Spalten (wie Kanban-Board): Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌─────────────────────────────────────────────────────────────┐ │ Objekt auswählen: [Objekt A ▼] Jahr: [2024 ▼] │ ├───────────────┬───────────────┬─────────────────────────────┤ │ EINGANG │ IN PRÜFUNG │ VERBUCHT │ │ (Neue PDFs) │ (Du prüfst) │ (Abgeschlossen) │ ├───────────────┼───────────────┼─────────────────────────────┤ │ 🔴 Rechnung │ 🟡 Hausmeister│ 🟢 Heizkosten Q1 │ │ Heizung │ März (KI: │ 1.234€ │ │ (noch │ 1.200€?) │ │ │ nicht │ │ 🟢 Grundsteuer │ │ geprüft) │ 🟡 Versicherung│ 450€ │ │ │ (fehlt │ │ │ 🔴 Rechnung │ Betrag) │ │ │ Strom │ │ │ │ │ │ │ │ [+ Upload] │ [Nach rechts │ [In Abrechnung │ │ │ ziehen] │ übernehmen] │ └───────────────┴───────────────┴─────────────────────────────┘ Drag & Drop: Rechnung von "Eingang" → "In Prüfung" → "Verbucht" Der Clou: Wenn du auf eine Karte klickst, öffnet sich rechts das PDF + das Extraktions-Formular (die roten/gelben Felder aus unserer UI). 🎯 Dein erster konkreter Schritt (heute noch machbar) Ordner-Struktur anlegen: Einklappen Kopieren 1 2 3 /data/documents/2024/ /data/documents/2023/ /data/db/steuer.db Simples Python-Script oder Node.js: Watched den Upload-Ordner Macht OCR (Tesseract) Speichert in SQLite Zeigt Web-Interface (ein einfaches HTML mit Suche) Deine ersten 10 PDFs vom Schreibtisch einscannen und testen Nach 2 Stunden Arbeit hast du schon: Zentrale Suche über alle Belege Keine Excel-Links mehr zu kaputten Pfaden Übersicht: "Was habe ich schon, was fehlt noch?" Soll ich dir ein minimal funktionierendes Python-Script (oder Node.js) schreiben, das genau das macht? Upload → OCR → SQLite → Simple Web-Suche? Das wäre dein "MVP des MVP" – ohne Docker, ohne Ollama, ohne komplexe Architektur. Nur: Nie wieder nach PDFs suchen. Das klingt nach einem Ansatz wir bauen bei der Nutzung. Das klingt gut profile kimi-k2.5:cloud Nachgedacht für 11 Sekunden Perfekt! Das ist der "Walking Skeleton" Ansatz – das dünneste System, das trotzdem durchläuft (Upload → Speichern → Anzeigen). Kein Over-Engineering, sondern funktionierende Software in 30 Minuten. 🚀 Der "Build as you use" Start (heute Abend machbar) Technik-Stack für den ersten Abend: Node.js (ein einfaches Script, kein Framework-Wahnsinn) SQLite (eine Datei, kein Server) HTML + Vanilla JS (kein React-Build-Prozess) Kein Docker erstmal (kommt später, wenn es läuft) Schritt 0: Die "Leiche" (Walking Skeleton) Erstelle eine Datei server.js: javascript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 ⌄ ⌄ ⌄ ⌄ const express = require('express'); const multer = require('multer'); const sqlite3 = require('sqlite3').verbose(); const path = require('path'); const app = express(); const upload = multer({ dest: 'uploads/' }); // SQLite DB (eine Datei!) const db = new sqlite3.Database('./steuer.db'); // Tabelle erstellen (falls nicht da) db.run(` CREATE TABLE IF NOT EXISTS documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT, originalname TEXT, upload_date DATETIME DEFAULT CURRENT_TIMESTAMP, year INTEGER, category TEXT, amount REAL, notes TEXT ) `); // Statische Dateien (Frontend) app.use(express.static('public')); app.use(express.json()); // Upload-Endpunkt app.post('/upload', upload.single('file'), (req, res) => { const { year, category, amount, notes } = req.body; db.run( `INSERT INTO documents (filename, originalname, year, category, amount, notes) VALUES (?, ?, ?, ?, ?, ?)`, [req.file.filename, req.file.originalname, year, category, amount, notes], function(err) { if (err) return res.status(500).json({ error: err.message }); res.json({ id: this.lastID, message: 'Gespeichert' }); } ); }); // Alle Dokumente laden app.get('/documents', (req, res) => { db.all('SELECT * FROM documents ORDER BY upload_date DESC', [], (err, rows) => { if (err) return res.status(500).json({ error: err.message }); res.json(rows); }); }); app.listen(3000, () => console.log('Läuft auf http://localhost:3000')); Schritt 1: Das hässliche aber funktionierende Frontend Erstelle Ordner public/ und darin index.html: html Einklappen Speichern Kopieren Vorschau 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ Meine Steuer-Docs

📁 Steuer-Dokumente

Neues Dokument


Installation & Start (5 Minuten): bash Einklappen Speichern Kopieren 1 2 3 4 5 6 7 mkdir steuer-app cd steuer-app npm init -y npm install express multer sqlite3 mkdir uploads public # kopiere server.js und public/index.html node server.js Ergebnis: Du hast jetzt eine Seite auf localhost:3000 wo du PDFs hochlädst, mit Jahr/Kategorie/Betrag taggst, und danach suchen kannst. 🔄 Die nächsten Iterationen (wenn du es nutzt) Iteration 2 (wenn du merkst, dass Beträge wichtig sind): Automatische Summen pro Kategorie ("Wie viel Heizung 2024?") Einfaches Dashboard mit Balken Iteration 3 (wenn OCR notwendig wird): Tesseract.js im Browser hinzufügen Upload zeigt sofort den OCR-Text neben dem PDF Du kopierst den Betrag raus (manuell), aber die Suche funktioniert schon über den OCR-Text Iteration 4 (wenn du die Abrechnung machst): "Projekte" oder "Abrechnungen" hinzufügen Checkboxen: "Diese Rechnung gehört zur Abrechnung 2024 Objekt A" Export als CSV für Excel (damit du deinen Steuerberater füttern kannst) Iteration 5+ (wenn Basics laufen): Dann Docker, dann Ollama, dann Automatisierung 🎯 Das "Build as you use" Prinzip Regel: Nach jeder Iteration musst du die Software echt nutzen können für deine nächste Buchhaltungs-Aufgabe. Wenn nicht, war sie zu groß. Beispiel: ❌ Falsch: "Erst baue ich 3 Monate das perfekte System, dann benutze ich es" ✅ Richtig: "Heute speichere ich damit 5 Rechnungen, nächste Woche suche ich damit statt in Ordnern" Technische Schuld ist erlaubt: Hartcodierte Steuersätze? Egal, ändert sich eh nur jährlich Keine Tests? Egal, du merkst ja wenn es kaputt ist SQLite statt PostgreSQL? Perfekt, eine Datei, einfach backupbar Wichtig: Wenn du merkst, dass du etwas brauchst (z.B. "Shit, ich muss das PDF auch öffnen können"), baust du es dann, nicht vorher. Soll ich dir für Iteration 2 (die Summen/Dashboard) schon den Code vorbereiten, oder willst du erstmal mit dem Skeleton spielen und mir sagen, was als erstes weh tut? Warum nicht direkt in Docker entwickeln? Das Stelle ich mit als einzig pragmatischen weg vor. Und auch die GUI. Vielleicht nicht direkt ein Traum, aber hässlich muss ja nicht sein profile kimi-k2.5:cloud Heute um 20:05 Nachgedacht für 16 Sekunden Du hast absolut recht! Wenn du mit Docker vertraut bist, ist das der professionelle Weg – und es verhindert späteres "Läuft auf meinem Rechner"-Chaos. Und eine hässliche GUI motiviert niemanden, sie zu nutzen. Hier ist der "Docker-First + schönes UI" Stack: 🐳 Docker-Dev-Setup (mit Hot-Reload) Vorteil: Du entwickelst direkt im Container, aber mit Instant-Reload beim Code-Ändern. Projektstruktur Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 steuer-app/ ├── docker-compose.yml ├── Dockerfile.dev # Für Entwicklung mit Hot-Reload ├── backend/ │ ├── package.json │ ├── server.js # Express API │ └── uploads/ # Gemountetes Volume └── frontend/ ├── package.json ├── vite.config.js # Vite (schneller als CRA) ├── tailwind.config.js # Schönes UI └── src/ ├── App.jsx └── components/ 1. Backend (Docker + Express + SQLite) dockerfile Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 # Dockerfile.dev FROM node:20-alpine WORKDIR /app RUN npm install -g nodemon COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD ["nodemon", "--legacy-watch", "server.js"] javascript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 ⌄ ⌄ ⌄ ⌄ ⌄ // backend/server.js const express = require('express'); const multer = require('multer'); const sqlite3 = require('sqlite3'); const cors = require('cors'); const path = require('path'); const app = express(); app.use(cors()); app.use(express.json()); // Upload-Config const storage = multer.diskStorage({ destination: (req, file, cb) => cb(null, '/app/uploads'), filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname) }); const upload = multer({ storage }); // DB const db = new sqlite3.Database('/app/data/steuer.db'); db.run(` CREATE TABLE IF NOT EXISTS docs ( id INTEGER PRIMARY KEY, filename TEXT, originalname TEXT, year INTEGER, category TEXT, amount REAL, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // Routes app.get('/api/docs', (req, res) => { db.all('SELECT * FROM docs ORDER BY created_at DESC', [], (err, rows) => { res.json(rows || []); }); }); app.post('/api/upload', upload.single('file'), (req, res) => { const { year, category, amount, notes } = req.body; db.run( 'INSERT INTO docs (filename, originalname, year, category, amount, notes) VALUES (?, ?, ?, ?, ?, ?)', [req.file.filename, req.file.originalname, year, category, amount, notes], function(err) { if (err) return res.status(500).json({ error: err }); res.json({ id: this.lastID }); } ); }); // Statische Dateien für Uploads app.use('/uploads', express.static('/app/uploads')); app.listen(3000, () => console.log('API läuft auf :3000')); json Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 ⌄ ⌄ // backend/package.json { "dependencies": { "express": "^4.18.2", "multer": "^1.4.5-lts.1", "sqlite3": "^5.1.6", "cors": "^2.8.5" } } 2. Frontend (React + Vite + Tailwind - Schnell & Hübsch) dockerfile Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 # frontend/Dockerfile.dev FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 5173 CMD ["npm", "run", "dev", "--", "--host"] json Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ⌄ ⌄ ⌄ ⌄ // frontend/package.json { "scripts": { "dev": "vite", "build": "vite build" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "lucide-react": "^0.294.0" }, "devDependencies": { "@vitejs/plugin-react": "^4.2.0", "autoprefixer": "^10.4.16", "postcss": "^8.4.32", "tailwindcss": "^3.3.6", "vite": "^5.0.8" } } javascript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 ⌄ // frontend/tailwind.config.js /** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {} }, plugins: [], } css Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 ⌄ /* frontend/src/index.css */ @tailwind base; @tailwind components; @tailwind utilities; body { @apply bg-gray-50 text-gray-900; } javascript Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ⌄ ⌄ ⌄ // frontend/vite.config.js import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { host: '0.0.0.0', port: 5173, proxy: { '/api': 'http://backend:3000', '/uploads': 'http://backend:3000' } } }) 3. Schöne React-Components (nicht hässlich!) jsx Einklappen Speichern Kopieren 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ // frontend/src/App.jsx
{/* Search */}
setSearch(e.target.value)} className="w-full pl-10 pr-4 py-3 rounded-xl border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition" />
{/* Document List */}
{filtered.map(doc => (

{doc.originalname}

{doc.category}
{doc.notes &&

{doc.notes}

}
{doc.year} {doc.amount && ( {parseFloat(doc.amount).toFixed(2)} € )}
Ansehen →
))}
); } export default App; 4. Docker Compose (Dev-Version mit Hot-Reload) yaml Einklappen Speichern Kopieren 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ ⌄ # docker-compose.yml version: '3.8' services: backend: build: context: ./backend dockerfile: Dockerfile.dev volumes: - ./backend:/app # Hot-reload für Code - ./backend/node_modules:/app/node_modules # Node_modules nicht überschreiben - uploads:/app/uploads # Persistente Uploads - db_data:/app/data # Persistente DB ports: - "3000:3000" environment: - NODE_ENV=development networks: - app-net frontend: build: context: ./frontend dockerfile: Dockerfile.dev volumes: - ./frontend:/app - /app/node_modules # Anonymous volume für node_modules ports: - "5173:5173" environment: - CHOKIDAR_USEPOLLING=true # Für Hot-reload in Docker networks: - app-net depends_on: - backend volumes: uploads: db_data: networks: app-net: driver: bridge 🚀 Starten bash Einklappen Speichern Kopieren 1 2 3 4 5 mkdir steuer-app && cd steuer-app # Ordner erstellen wie oben beschrieben # Dateien kopieren docker-compose up --build Ergebnis: Frontend: http://localhost:5173 (React + Tailwind, schick!) Backend: http://localhost:3000 (API) Hot-Reload: Code ändern → sofort sichtbar im Browser Persistenz: Uploads und DB bleiben auch bei docker-compose down erhalten Das ist der professionelle Weg – und es sieht gleich aus wie moderne SaaS-Apps (Notion/Linear-Stil). Ist das der Richtige Ansatz für dich?