// KI-Assistent — provider-agnostic AI assistant with insights feed + chat const AI_PROVIDERS = [ { id: "claude", name: "Claude", vendor: "Anthropic", models: ["claude-sonnet-4.5", "claude-haiku-4.5"], color: "#cc785c" }, { id: "openai", name: "GPT", vendor: "OpenAI", models: ["gpt-4o", "gpt-4o-mini"], color: "#10a37f" }, { id: "gemini", name: "Gemini", vendor: "Google", models: ["gemini-2.5-pro", "gemini-2.5-flash"], color: "#4285f4" }, { id: "mistral", name: "Mistral", vendor: "Mistral AI", models: ["mistral-large", "mistral-small"], color: "#ff7000" }, { id: "ollama", name: "Ollama", vendor: "Lokal", models: ["llama3.1:70b", "qwen2.5:32b"], color: "#888888" }, ]; // Curated demo insights — these read like real findings an LLM would surface const AI_INSIGHTS = [ { id: "i1", category: "Sparpotenzial", priority: "hoch", date: "2026-04-29", title: "Heizungswartungskosten 38 % über Marktdurchschnitt", summary: "Heizung Berger berechnet 240 € pro Wartung in der Lindenstraße. Der regionale Durchschnitt liegt bei 145–180 €. Empfehlung: 2 Vergleichsangebote einholen.", saving: 720, // EUR/year action: "Vergleichsangebote anfordern", refs: [{ kind: "contact", id: "c3", label: "Heizung Berger" }, { kind: "property", id: "p1", label: "Lindenstraße 42" }], confidence: 0.86, }, { id: "i2", category: "Steuer", priority: "mittel", date: "2026-04-28", title: "AfA Lindenstraße: 2 % statt 2,5 % anwendbar", summary: "Da das Gebäude vor 1925 errichtet wurde (Baujahr 1908), kannst du die erhöhte lineare AfA von 2,5 % geltend machen. Aktuell sind 2 % angesetzt — zusätzliche Werbungskosten ca. 7.250 €/Jahr.", saving: 2900, action: "Mit Steuerberater klären", refs: [{ kind: "property", id: "p1", label: "Lindenstraße 42" }, { kind: "contact", id: "c6", label: "Kanzlei Wiegand" }], confidence: 0.78, }, { id: "i3", category: "Beleg", priority: "hoch", date: "2026-04-22", title: "Beleg fehlt: Bad-Renovierung 3.200 €", summary: "Anzahlung an Klempnerei Heinrich am 19.04. ist als Buchung erfasst, aber kein Rechnungsdokument hinterlegt. Für den steuerlichen Nachweis und §35a EStG benötigst du die Originalrechnung.", action: "Rechnung anfordern", refs: [{ kind: "contact", id: "c1", label: "Klempnerei Heinrich" }, { kind: "property", id: "p1", label: "Lindenstraße 42" }], confidence: 0.99, }, { id: "i4", category: "Recht", priority: "hoch", date: "2026-04-20", title: "Mietspiegel: Wohnung 4 unter Marktmiete", summary: "Lindenstraße 42, Wohnung 4 (84 m², 1. OG) wird voraussichtlich für 1.080 € neu vermietet. Der Berliner Mietspiegel 2025 erlaubt für vergleichbare Lagen bis zu 14,80 €/m² Kaltmiete (≈ 1.243 €). Mieterhöhung möglich, aber Kappungsgrenze beachten.", saving: 1956, action: "Mietangebot anpassen", refs: [{ kind: "property", id: "p1", label: "Lindenstraße 42" }, { kind: "unit", id: "u4", label: "Wohnung 4" }], confidence: 0.82, }, { id: "i5", category: "Anomalie", priority: "mittel", date: "2026-04-15", title: "Stromverbrauch Wohnung 1 unplausibel niedrig", summary: "Letzte Ablesung 4.821 kWh — das entspricht ca. 2.100 kWh/Jahr für eine 2,5-Zimmer-Wohnung. Erwartungsbereich: 2.800–3.500 kWh. Möglich: defekter Zähler, Untermietung oder Übermittlungsfehler.", action: "Zähler prüfen lassen", refs: [{ kind: "unit", id: "u1", label: "Wohnung 1" }], confidence: 0.71, }, { id: "i6", category: "Frist", priority: "hoch", date: "2026-04-12", title: "Nebenkostenabrechnung 2024 — letzte 30 Tage", summary: "§ 556 Abs. 3 BGB: Die Abrechnung muss spätestens 12 Monate nach Ende des Abrechnungszeitraums dem Mieter vorliegen. Für Stadtpark 7 (3 Mieter) endet die Frist am 31.05.2026.", action: "Abrechnung erstellen", refs: [{ kind: "property", id: "p2", label: "Am Stadtpark 7" }], confidence: 0.95, }, { id: "i7", category: "Sparpotenzial", priority: "niedrig", date: "2026-04-08", title: "Versicherungsprämie Eichenallee bündelbar", summary: "Die Gebäudeversicherung bei Allianz für Eichenallee fehlt — alle anderen 3 Objekte sind bei Allianz versichert. Sammelvertrag erfahrungsgemäß 8–12 % günstiger.", saving: 280, action: "Sammelangebot einholen", refs: [{ kind: "property", id: "p4", label: "Eichenallee 3" }, { kind: "contact", id: "c7", label: "Allianz" }], confidence: 0.74, }, ]; const TOTAL_SAVINGS = AI_INSIGHTS.reduce((s, i) => s + (i.saving || 0), 0); window.AI_PROVIDERS = AI_PROVIDERS; window.AI_INSIGHTS = AI_INSIGHTS; window.AI_TOTAL_SAVINGS = TOTAL_SAVINGS; // ─────────────────────────────────────────────────────────────────── function AssistantPage({ data, setRoute }) { const [tab, setTab] = React.useState('insights'); const [health, setHealth] = React.useState(null); const [insights, setInsights] = React.useState({ loading: true, items: [], generatedAt: null, error: null }); const reloadHealth = React.useCallback(async () => { try { setHealth(await window.getHealth()); } catch { setHealth({ ok: false, ocr: { enabled: false } }); } }, []); const reloadInsights = React.useCallback(async (force) => { setInsights(s => ({ ...s, loading: true, error: null })); try { const r = await window.getAiInsights(force); setInsights({ loading: false, items: r.insights || [], generatedAt: r.generatedAt, cached: r.cached, error: null }); } catch (e) { setInsights({ loading: false, items: [], generatedAt: null, error: e.detail || e.message }); } }, []); React.useEffect(() => { reloadHealth(); reloadInsights(false); }, [reloadHealth, reloadInsights]); const aiEnabled = !!health?.ocr?.enabled; const model = health?.ocr?.model || ''; return (
OPENAI_API_KEY in der .env auf dem Server, dann analysiert die KI dein Portfolio automatisch.
OPENAI_API_KEY in der .env, dann startet der Chat.
') .replace(/^(.+)$/, '
$1
') .replace(/•/g, '·'); } // ───── Audit (Belegprüfung) tab ───── const AUDIT_BELEGE = [ { id: 1, file: "Rechnung_Klempner_2026-04.pdf", contact: "Klempnerei Heinrich", amount: 380, date: "2026-04-12", propertyId: "p1", unitId: "u3", scope: "einheit", category: "Reparatur", status: "ok", findings: [], tips: ["§35a EStG: Lohnanteil (geschätzt 220 €) als haushaltsnahe Dienstleistung steuerlich ansetzbar."] }, { id: 2, file: "Heizungswartung_Berger.pdf", contact: "Heizung Berger", amount: 240, date: "2026-04-04", propertyId: "p1", unitId: null, scope: "allgemein", category: "Wartung", status: "warn", findings: ["Stundensatz 38 % über Markt", "Kein Wartungsbericht angehängt"], tips: ["Wartungskosten sind als Allgemeinkosten umlagefähig (BetrKV §2 Nr. 4a).", "Vergleichsangebot anfordern — Einsparpotenzial ~95 €."] }, { id: 3, file: "Versicherung_Allianz_2026.pdf", contact: "Allianz", amount: 1240, date: "2026-01-15", propertyId: "p1", unitId: null, scope: "allgemein", category: "Versicherung", status: "ok", findings: [], tips: ["Wohngebäudeversicherung voll als Werbungskosten absetzbar.", "Anteilig auf Mieter umlegbar."] }, { id: 4, file: "Steuerberater_Q1.pdf", contact: "Kanzlei Wiegand", amount: 480, date: "2026-03-18", propertyId: "p1", unitId: null, scope: "allgemein", category: "Beratung", status: "ok", findings: [], tips: ["100 % als Werbungskosten absetzbar."] }, { id: 5, file: "Schornsteinfeger_2026-02.pdf", contact: "Schornsteinfeger Maier", amount: 95, date: "2026-02-09", propertyId: "p2", unitId: null, scope: "allgemein", category: "Wartung", status: "ok", findings: [], tips: ["Voll umlagefähig auf Mieter."] }, { id: 6, file: "Fenstertausch_W3.pdf", contact: "Elektro Voss", amount: 2400, date: "2026-03-14", propertyId: "p1", unitId: "u3", scope: "einheit", category: "Modernisierung", status: "warn", findings: ["Lieferant ist Elektriker — Gewerk passt nicht zur Leistung"], tips: ["Modernisierung > 4.000 €: Über AfA verteilen, nicht sofort absetzen.", "Bis zu 8 % auf Miete umlegbar (§559 BGB)."] }, { id: 7, file: "Hausmeister_April.pdf", contact: "Service Klar", amount: 180, date: "2026-04-30", propertyId: "p2", unitId: null, scope: "allgemein", category: "Hausmeister", status: "ok", findings: [], tips: ["Lohnkostenanteil (~70 %) für §35a EStG ansetzen."] }, { id: 8, file: "Bad_Renovierung_Anzahlung.pdf", contact: "Klempnerei Heinrich", amount: 3200, date: "2026-04-19", propertyId: "p1", unitId: "u3", scope: "einheit", category: "Renovierung", status: "missing", findings: ["Originalrechnung nicht hochgeladen"], tips: ["Ohne Originalbeleg keine §35a-Anrechnung möglich.", "Rechnung muss Lohn- und Materialkosten getrennt ausweisen."] }, { id: 9, file: "Strom_Allgemein_Q1.pdf", contact: "Stadtwerke Berlin", amount: 145, date: "2026-04-02", propertyId: "p1", unitId: null, scope: "allgemein", category: "Strom", status: "ok", findings: [], tips: ["Allgemeinstrom (Treppenhaus, Keller) auf Mieter umlegbar."] }, { id: 10, file: "Gartenarbeit_April.pdf", contact: "Grünwerk GmbH", amount: 320, date: "2026-04-22", propertyId: "p4", unitId: null, scope: "allgemein", category: "Gartenpflege", status: "warn", findings: ["Rechnung weist Lohn- und Materialkosten nicht getrennt aus"], tips: ["Trennung anfordern — sonst kein §35a EStG-Abzug möglich."] }, ]; const AUDIT_GLOBAL_TIPS = [ { tone: "pos", title: "92 % der Belege vollständig", body: "Sehr gute Quote. Reichst du den Steuerberater-Ordner zum Quartalsende ein, sparst du dir die hektische Sammlung im März." }, { tone: "warn", title: "2 Belege ohne Lohn-/Materialtrennung", body: "Bei haushaltsnahen Dienstleistungen müssen Rechnungen den Lohnanteil separat ausweisen — sonst gibt es keinen §35a-Steuerbonus." }, { tone: "info", title: "Originalbelege scannen reicht", body: "Seit der GoBD-Novelle 2020 sind digitale Belege gleichwertig, solange sie unverändert archiviert werden. Hausgut erfüllt das automatisch." }, { tone: "neg", title: "1 fehlender Beleg ab 2.000 €", body: "Anzahlung Bad-Renovierung 3.200 € — fordere die Originalrechnung umgehend an. Anzahlungsbelege gelten beim Finanzamt nur als Zahlungsbeleg." }, ]; function AuditTab({ data }) { const [filter, setFilter] = React.useState({ status: 'all', property: 'all', scope: 'all' }); const [view, setView] = React.useState('belege'); // belege | mappen | tipps const cloudBelege = useDocuments({}); const belegeInputRef = React.useRef(null); const [pendingFiles, setPendingFiles] = React.useState(null); const onBelegFiles = (e) => { const files = Array.from(e.target.files || []).filter(Boolean); if (files.length) setPendingFiles(files); if (belegeInputRef.current) belegeInputRef.current.value = ''; }; const filtered = AUDIT_BELEGE.filter(a => { if (filter.status !== 'all' && a.status !== filter.status) return false; if (filter.property !== 'all' && a.propertyId !== filter.property) return false; if (filter.scope !== 'all' && a.scope !== filter.scope) return false; return true; }); const byProp = {}; AUDIT_BELEGE.forEach(b => { if (!byProp[b.propertyId]) byProp[b.propertyId] = { allgemein: 0, einheit: 0, count: 0, missing: 0, warn: 0 }; byProp[b.propertyId][b.scope] += b.amount; byProp[b.propertyId].count++; if (b.status === 'missing') byProp[b.propertyId].missing++; if (b.status === 'warn') byProp[b.propertyId].warn++; }); const totalOk = AUDIT_BELEGE.filter(a => a.status === 'ok').length; const totalWarn = AUDIT_BELEGE.filter(a => a.status === 'warn').length; const totalMiss = AUDIT_BELEGE.filter(a => a.status === 'missing').length; return (Pro Hauptobjekt und Monat eine Sammelmappe. Enthält alle Belege, Buchungsliste, Mietnachweise und Allgemein-/Einheits-Aufteilung — fertig zum Versand an deinen Steuerberater.