Kostenkontrolle & Kosten-Dashboard — Architektur-Design (OP-COST-1)
Status: in Umsetzung — Architektur-Entwurf festgezurrt (#187, erfasst als OP-COST-1 in #184); Sicht B gebaut (Slice B1 #192, 06-25, v0.5.0/Build 7 + OP-COST-2 Slices 2–3, s. §11 Stand des Baus), Sicht A vorgemerkt · Bezug: OP-COST-1 (Owning-OP) · OP-COST-2 · OP-R7-6 · OP-R10-1 · OP-BILLING-1/OP-PAY-1 (Abgrenzung) · Zielgruppe: Business/Management · IT-Dev/Architektur · True North: nur indirekt — schärft Frage 1 (Auslastung) um die Wirtschaftlichkeit (lohnt die Auslastung, wo entsteht Marge?), beantwortet keine Leitfrage selbst.
Kernaussage. Ein Kosten-Dashboard mit zwei getrennten Sichten — A betriebswirtschaftlich (Kosten/Marge je Auftrag) und B Infra-/Cloud-Kosten — macht Wirtschaftlichkeit sichtbar, rollen-gegated (Scope-Entscheidung 06-25: zwei Sichten, kein Misch-KPI; Tiefe: Design + inkrementeller Bau in Slices). Sicht B ist gebaut, Sicht A vorgemerkt. Dieses Dokument bleibt der verbindliche Entwurf; Bau-Abweichungen sind in §11 nachgezogen.
1. Zweck & Abgrenzung
Kosten-Transparenz in der App als operative Entscheidungsgrundlage. True-North-Bezug: schärft Frage 1 („Wie hoch ist die Auslastung?") um die Wirtschaftlichkeit — lohnt die Auslastung, wo entsteht Marge, wo Verlust? Ergänzt OP-R9-6 (margenstärkste Kapazitäts-Angebote).
Abgrenzung — was OP-COST-1 NICHT ist:
- ≠ Rechnungsstellung/Mahnwesen → das ist SevDesk (OP-BILLING-1, System-of-Record für Umsatz + E-Rechnung). OP-COST-1 liest Umsatz/Auftragswert für die Marge, erzeugt keine Rechnungen.
- ≠ SaaS-Paygate (Vermarktung/Abrechnung von Taktano selbst) → OP-PAY-1 (Lastenheft §11.8).
- ≠ Betriebs-Logs (OP-LOG-1) und ≠ Audit-Log (OP-AUDIT-1).
- Keine Finanzbuchhaltung — die Werte sind eine sprechende betriebliche Näherung für die operative Steuerung, nicht buchhalterisch maßgeblich.
2. Scope-Entscheidung: zwei getrennte Sichten (06-25)
Bewusst getrennt gehalten — unterschiedliche Datenquellen, Aggregationsebenen, Zielgruppen und Sichtbarkeits-Rollen → zwei Bereiche/Tabs, kein Misch-KPI.
| Sicht | Inhalt | Zielgruppe | Datenquellen |
|---|---|---|---|
| A — Betriebswirtschaftlich | Arbeits- + Materialkosten + Marge je Auftrag/Leistung, Soll-Ist | Werkstatt-Management | Leistungsnachweise (R1-2) · Materialerfassung (R10-1) · Preismodell (R7-6)/SevDesk |
| B — Infra/Cloud | Cloudflare/Fly/D1/LLM-API-Spend (Ops) | Betreiber/Owner | manuelle Posten (interim) → Provider-Billing-APIs (später) |
Primärer Mehrwert liegt in A (baut auf vorhandenen Fakten auf); B ist die Ops-Sicht.
3. Sicht A — Betriebswirtschaftliche Kosten & Marge
3.1 Datenmodell — gespeicherte Fakten (neu) vs. abgeleitet (D-2)
- Stundensatz-Stammdaten (neu) — Nutzer-Entscheidung 06-26: ein €/h-Satz je Abteilung
(
Mitarbeiter.abteilungId→TaxonomyValue('abteilung'), strukturiert + admin-editierbar, G-1; der FreitextMitarbeiter.roleist als Schlüssel ungeeignet) mit optionalem Override je einzelnem Mitarbeiter (Mitarbeiter-Satz gewinnt vor dem Abteilungs-Satz). - Zeitliche Gültigkeit (von–bis) — Nutzer-Entscheidung 06-26: jeder Satz (Abteilung und Override)
trägt eine Gültigkeitsspanne (
gueltigVon/gueltigBis, halb-offen[von, bis);bisoffen = aktuell gültig). Sätze werden historisiert, nie überschrieben — Lohnerhöhungen/rückwirkende Korrekturen legen einen neuen Zeitabschnitt an. Für eine Berechnung wird der zum Leistungs-Zeitpunkt gültige Satz gewählt: Mitarbeiter-Override (falls am Stichtag gültig) → sonst Abteilungs-Satz am Stichtag. - Berechnung nur im Report (D-2) — Nutzer-Entscheidung 06-26: Sätze sind gespeicherte Fakten; die tatsächliche Kostenberechnung (istMin × Satz) ist abgeleitet und entsteht ausschließlich im Report, wird nie persistiert. Skill-basierte Sätze bleiben spätere Verfeinerung.
- Materialpreise + Material-Verbrauch: kommen aus OP-R10-1 (strukturierte Materialerfassung — Menge/Charge/Verschnitt je Auftrag/Teilschritt) × Material-Preis-Stammdaten. OP-COST-1 konsumiert OP-R10-1, dupliziert es nicht.
- Auftragswert/Umsatz: kommt aus OP-R7-6 (Preismodell auf Leistungen / manueller Auftragswert) bzw. SevDesk (OP-BILLING-1, Ist-Umsatz). Nötig für die Marge.
- Abgeleitet (nie gespeichert, D-2): Arbeitskosten, Materialkosten, Gesamtkosten, Marge, Soll-Ist —
analog zur bestehenden Statistik/XP-Ableitung in
server/src/operativ/leistung.ts(dort gilt schon ausdrücklich: „Leistungsnachweise = gespeicherte Fakten, Statistik IMMER abgeleitet, nie persistiert").
3.2 Berechnungslogik (abgeleitet, pure Funktion + Self-Test)
- Arbeitskosten je Auftrag = Σ über Leistungsnachweise
n:(n.istMin / 60) × stundensatz(n.mitarbeiterId, zeitpunkt(n))—stundensatz(...)wählt den zum Leistungs-Zeitpunkt gültigen Satz (Mitarbeiter-Override → sonst Abteilung; je Stichtag, §3.1). Basis bereits vorhanden (Leistungsnachweis.istMin+ Ist-Zeitstempel, R1-2). Helfer (alsHelfer) zählen mit ihren realen Lohnkosten (unabhängig vom Lern-lernanteil, der nur die Erfahrung gewichtet). - Materialkosten je Auftrag = Σ (Verbrauchsmenge × Materialpreis) — gated auf OP-R10-1.
- Gesamtkosten = Arbeits- + Materialkosten (+ optionaler Gemeinkosten-Zuschlag, später).
- Marge je Auftrag/Leistung = Umsatz/Auftragswert − Gesamtkosten — gated auf OP-R7-6/SevDesk.
- Soll-Ist = kalkuliert (
vorgabeMin × Satz) vs. tatsächlich (istMin × Satz); die Vorgabe-Ist-Spanne steckt bereits je Nachweis invorgabeMin/istMin. - Analyse-Dimensionen: Güteklasse (1–3), Anspruch (1–3), Leistung, Abteilung, Zeitraum.
3.3 Was jetzt baubar ist
Die Arbeitskosten-Sicht (Slice A1) ist ohne neue Abhängigkeit baubar — nur Stundensatz-Stammdaten
- Ableitung aus vorhandenen Leistungsnachweisen. Material (A2) und Marge (A3) brauchen OP-R10-1 bzw. OP-R7-6/SevDesk.
4. Sicht B — Infra-/Cloud-Kosten (Ops)
- Quellen: Cloudflare (Worker/DO/D1/R2/Pages), Fly.io (Solver-Service), LLM-API-Spend (Anthropic; Bezug OP-AI-1/OP-AI-4), Domains/Sonstiges.
- Erfassung — VERBINDLICHE RICHTUNG (Nutzer 06-26): automatischer Pull über die jeweiligen Provider-APIs.
Die Infra-Kosten werden je Dienst direkt aus dessen API gezogen (Cloudflare GraphQL Analytics/Billing,
Fly.io Machines-/Billing-API, Anthropic Usage/Cost-API, Domain-Registrar …) — periodisch (Cron-Worker),
je Dienst × Monat. Manuelle Erfassung (Slice B1) bleibt Fallback/Bootstrap (Dienste ohne API, Korrekturen,
„Sonstiges"). API-Keys/Tokens je Provider als Secrets (kein Repo-PII). Datenmodell-Erweiterung:
quelle: 'api' | 'manuell'+ Provider-Referenz/Idempotenz-Key (kein Doppel-Pull). Bezug OP-OBS-1 (Usage-Metriken) · OP-AI-1/OP-AI-4 (LLM-Spend). Als OP-COST-2 geführt. - Stand — OP-COST-2 Slice 1 (GitHub) GEBAUT (#202, v0.12.0/Build 15): GitHub als erster Provider mit
projekt-bezogenem Auto-Pull.
InfraKostenPostenumquelle:'api'|'manuell'+quelleRef(Idempotenz, Upsert ersetzt byquelleRef); pureserver/src/kosten/github-billing.tsmappt die Enhanced-Billing- Usage-API (/organizations/<org>/settings/billing/usage) je Monat auf einen „GitHub"-Posten, gefiltert auf das Repo (GITHUB_BILLING_REPO); Self-Testtest:kosten. DOpullGithubKosten()ist dormant/deploy- sicher (No-op ohne SecretGITHUB_BILLING_TOKEN+GITHUB_BILLING_ORG-[var], wie der D1-/R2-Pfad). Trigger interim on-demand (Admin-ActioninfraKosten.pullGithub+ Button „↻ GitHub ziehen" +API-Badge); Pull = OP-AUDIT-1-Event (infraKosten.pull). Aktivierung = Pre-Prod/Go-Live-Requirement OP-COST-4:wrangler secret put GITHUB_BILLING_TOKEN(Billing/Plan-Read-Scope; Org/Repo bereits als [vars]). Folge-Slices: Cron-Automatik (verbindliche Richtung) + weitere Provider (Cloudflare/Fly/Anthropic). Hinweis: GitHub rechnet in USD ab → Mehrwährung (FX = OP-I18N-1). - Stand — OP-COST-2 Slice 2 (LLM-API-Spend) GEBAUT (#TBD, v0.43.0): Zweiter Provider mit Auto-Pull.
Pure
server/src/kosten/llm-billing.tsmappt die Anthropic Usage-/Cost-API (/v1/organizations/cost_report, Admin-Key) je Zeitbucket (starting_at+results[].amount/currency) auf einen Monatsposten (Dienst-Label „Anthropic API (LLM)", perLLM_BILLING_DIENSTüberschreibbar),quelle:'api'+quelleRef=llm:YYYY-MM(Idempotenz-Upsert). DOpullLlmKosten()ist dormant/deploy-sicher (No-op ohne SecretLLM_BILLING_KEY; Endpoint perLLM_BILLING_URL[var] provider-flexibel). Trigger interim on-demand (Admin-ActioninfraKosten.pullLlm+ Button „↻ LLM ziehen"); Pull = OP-AUDIT-1-Event (infraKosten.pull). Self-Testtest:kostendeckt Monats-Aggregation, Idempotenz, Label/Währungs-Konfiguration und Betrag-Robustheit ab. Zeitstempel des letzten erfolgreichen Abholens (Nutzer-Wunsch): je Auto-Pull-Komponente wird der Zeitpunkt des letzten erfolgreichen Pulls persistiert (infraKostenPull: Dienst→epoch-ms, schemaVersion 17→18, Merge-Default) und dezent angezeigt — als Caption unter dem jeweiligen „↻ …ziehen"-Button, als Inline-Stempel jeAPI-Posten-Zeile und imAPI-Badge-Tooltip (relativ „vor 3 Min", ab 1 Tag absolut). Aktivierung = Pre-Prod-Requirement OP-COST-4: GitHub-Actions-SecretLLM_BILLING_KEYsetzen — der CI-deploy-Job spiegelt es (wieASSISTENT_LLM_KEY) vorwrangler deployviawrangler secret putnach Cloudflare (Everything-as-Code; dormant-sicher übersprungen, wenn ungesetzt). - Stand — OP-COST-2 Slice 3 (Mistral = realer Anbieter, ECHTE Kosten) GEBAUT (#TBD, v0.44.0): Da der
In-App-Assistent tatsächlich über Mistral (EU) läuft (nicht Anthropic), zieht Taktano die tatsächlichen
LLM-Kosten aus Mistrals Admin-Usage-API (
GET https://console.mistral.ai/api/admin/usage?month=M&year=Y, Admin-API-Key ≠ASSISTENT_LLM_KEY) — echte Beträge je Modell/Monat, nicht Token×Listenpreis. Pureserver/src/kosten/mistral-billing.ts(mistralUsageZuPosten) mappt die Antwort auf einen Monatsposten (Dienst „Mistral (LLM)",quelle:'api',quelleRef=mistral:YYYY-MM, idempotenter Upsert). DOpullMistralKosten()zieht laufenden + Vormonat (Monatswechsel-sicher), dormant ohne SecretMISTRAL_ADMIN_KEY; AktioninfraKosten.pullMistral+ Button „↻ Mistral ziehen". Schema-Unsicherheit (Beta-Console-API, öffentlich nicht dokumentiert): der Kosten-Extraktor ist defensiv (bevorzugt ein explizites Gesamt-Kostenfeld, sonst Summe der Posten-Liste; Feldnamen/Einheit an EINER Stelle justierbar,MISTRAL_BETRAG_IN_CENTfür Cent-Antworten) — nach dem ersten echten Pull Feld/Einheit bestätigen. Key ebenfalls via GitHub-SecretMISTRAL_ADMIN_KEY(CI-Sync). Hinweis: öffentliche Listen-Preise stehen nur aufmistral.ai/pricing; die API liefert die realen Kosten. Der Anthropic-Pfad (Slice 2) bleibt parallel dormant nutzbar, falls der Betrieb auf Anthropic wechselt. - Aggregation: je Dienst × Zeitraum + Trend; optionale Umlage auf Mandanten = Future (Multi-Tenant).
- Zielgruppe/Rolle: Betreiber/Owner — strenger gated als Sicht A.
5. Datenmodell-Konventionen
- Fakten gespeichert, Sichten abgeleitet (D-2) — wie
leistung.ts. Kosten/Margen NIE persistieren. - Stammdaten admin-editierbar (G-1): Stundensätze, Materialpreise, Infra-Posten = stammdaten-artig → editierbar; Zuschläge/Schwellen dokumentiert.
- Versioniert + idempotente Migration (
schemaVersion-Muster inserver/party/leitstand.ts) für die neuen Stammdaten; Persistenz zunächst DO-Blob (wie übrige Stammdaten), wandert mit OP-DATA-1 (Drizzle/D1) mit.
6. Rollen & Sichtbarkeit (sensibel!)
- Kostendaten = vertraulich (betriebswirtschaftlich; abteilungs-/mitarbeiterbezogene Sätze potenziell personensensibel). Default: nur Management/Owner sieht Sicht A; Sicht B nur Betreiber/Owner.
- Abhängigkeit OP-AUTH-1: ein granulares Rollenmodell existiert noch nicht (Cloudflare Access interim; Better Auth + Organizations = future). Interim: server-seitiges Gate hinter einem Admin/Manager-Flag; keine Kostendaten im Broadcast-State an Nicht-Berechtigte (kein Leak, vgl. OP-SEC-1 #6).
- Audit (OP-AUDIT-1): Anlegen/Ändern von Stundensätzen/Materialpreisen ist margen-relevant → als Audit-Event (Stammdaten-Änderung); optional Zugriff auf Kosten-Sichten protokollieren.
- PII/DSGVO: mitarbeiterbezogene Sätze datensparsam; in Logs/Audit keine Klartext-Sätze/-Gehälter (PII-Scrubbing, OP-LOG-1).
7. UI / Dashboard
- Eigener Workspace „Kosten" (Sidebar, eigene Signaturfarbe
--tk-ws-costs; nur--tk-*-Tokens, kein Hardcode-Hex; Mono-Font für alle Zahlen — Design-Vertragdesign/CLAUDE.md). - Zwei Tabs: „Betrieb" (Sicht A) · „Infra" (Sicht B) — getrennt, kein Misch-KPI.
- Sicht A: KPI-Kacheln (Kosten/Umsatz/Marge gesamt + je Auftrag/Leistung), Top-/Flop-Margen, Soll-Ist-Bänder, Trends, Filter (Zeitraum/Leistung/Abteilung/Güteklasse). Read-only, abgeleitet.
- Sicht B: Spend je Dienst × Monat, Trend, größte Posten.
- True-North-Platzierung: verdichtete Marge-/Kosten-Kennzahl optional im Überblick-Raum (rollen-gegated).
8. Build-Slices (Vorschlag)
- Slice A1 — Arbeitskosten je Auftrag (baubar JETZT): Stundensatz-Stammdaten je Abteilung (CRUD, G-1)
- abgeleitete Arbeitskosten aus vorhandenen Leistungsnachweisen (
istMin × Satz) → read-only KPI/Panel - Soll-Ist (Vorgabe vs. Ist). Pure Ableitungs-Funktion + Self-Test (
test:kosten, CI). Keine neue Abhängigkeit. → entsperrt sofort einen Teil von True-North-„Wirtschaftlichkeit".
- abgeleitete Arbeitskosten aus vorhandenen Leistungsnachweisen (
- Slice A2 — Materialkosten: gated auf OP-R10-1 (strukturierte Materialerfassung) + Materialpreis-Stammdaten → Gesamtkosten je Auftrag/Leistung.
- Slice A3 — Marge & Dashboard: gated auf OP-R7-6/OP-BILLING-1 (Auftragswert/Umsatz) → Marge je Auftrag/Leistung + volles Dashboard (Trends/Filter/Top-Flop).
- Slice B1 — Infra-Kosten (Ops): manuelle Erfassung je Posten/Monat + Ops-Dashboard; später Provider-API-Pull (Bezug OP-OBS-1).
- Rollen-Gate zieht sich durch alle Slices; harte server-seitige Durchsetzung sobald OP-AUTH-1 steht (interim Admin-Flag).
9. Compliance & Risiko
- OP-COMPLIANCE-1: Kostendaten-Vertraulichkeit + Rollen-Gate = Beleg fürs Zugriffs-/Rollenmodell
(ISO 27001) → in
docs/Compliance.mdnachziehen, sobald gebaut. - Risiko (→
docs/betrieb/Risikoregister.md): (a) Garbage-in — falsche Stundensätze/Materialpreise → irreführende Margen/Fehlentscheidungen (Maßnahme: sprechende Stammdaten-Pflege, Soll-Ist-Plausibilisierung, Audit der Änderungen); (b) Sichtbarkeit ohne Rollenmodell — sensible Kosten vor OP-AUTH-1 (Maßnahme: interim server-seitiges Admin-Gate, kein Broadcast-Leak).
10. Bezüge
OP-R1-2 (Leistungsnachweise/Ist-Zeit = Arbeitskosten-Basis) · OP-R10-1 (Materialerfassung = Materialkosten) · OP-R7-6 (Preismodell/Auftragswert = Umsatz/Marge) · OP-BILLING-1 (SevDesk = Ist-Umsatz) · OP-AUTH-1 (Rollen/Sichtbarkeit) · OP-AUDIT-1 (Stammdaten-/Zugriffs-Audit) · OP-OBS-1 (Infra-Usage-Metriken) · OP-AI-1/OP-AI-4 (LLM-Spend) · OP-R9-6 (margenstärkste Kapazitäts-Angebote) · OP-PAY-1 (SaaS-Paygate, abgegrenzt) · G-1 (editierbare Stammdaten) · D-2 (derived state) · True North (Wirtschaftlichkeit/Auslastung).
11. Stand des Baus
Slice B1 — Infra-/Cloud-Kosten (Sicht B) — gebaut (#192, 06-25, Version 0.5.0/Build 7)
- Datenmodell (Fakten):
InfraKostenPosten(server/src/kosten/infra.ts) —{ dienst, monat (YYYY-MM), betragCent, waehrung, notiz?, erfasstAm }, Beträge ganzzahlig in Cent (kein Float-Drift). Persistiert im DO-Blob (infraKostenPosten,schemaVersion12→13, idempotent leer initialisiert; wandert mit OP-DATA-1 nach D1). - Ableitung (D-2, nie gespeichert): pure
infraKostenAuswertung()— Summe gesamt · je Dienst (+ Anteil) · je Monat (Verlauf) · Monats-Trend (kalendarischer Vormonat; Lücke = kein Trend) · größte Posten · Mehrwährungs-Flag. Self-Testnpm run test:kosten(+ CI) — deckt Aggregation, Trend, Top-Posten, Mehrwährung, Jahreswechsel, leeren Fall ab. - Aktionen (admin-gegated):
infraKosten.hinzufuegen|aktualisieren|entfernen(Server validiert Monat/Betrag, speichert Cent). UI: Dienst frei + Vorschlagsliste (G-1), Betrag inline editierbar, Zeile entfernbar. - Rollen-Gate (kein Leak) — interim, bis OP-AUTH-1 A-3: Der DO bestimmt je Verbindung die Fähigkeit
kostenaus der im Menü gewählten Rolle (?rolle=-Query beim Verbinden →capsAusRolle; Buchhaltung/Inhaber-Chef =kosten) und filtert die Infra-Kosten je Verbindung aus dem Broadcast — Nicht-Berechtigte erhalten das FeldinfraKostennie, auch Schreib-Aktionen (infraKosten.*) sind gegated. → erfüllt §6 („kein Broadcast-Leak"). ⚠ Interim self-declared (A-1.5): die Rolle ist selbst gewählt (kein Nachweis) — der Zugang ist am Edge durch Cloudflare Access geschützt (Golden Rule), die verifizierte Durchsetzung (Rolle aus Better-Auth-JWT statt Selbstauskunft) und Sicht A folgen mit OP-AUTH-1 A-3. Die frühereKOSTEN_ADMIN_EMAILS-E-Mail-Allowlist wurde entfernt (v0.156.0). Der Audit-Actor („wer erfasste?") kommt weiterhin aus der Access-verifiziertenX-Taktano-User-E-Mail (client-Kopie gestrippt, Anti-Spoofing). - UI: eigener Workspace „Kosten" (Sidebar, nur für
kostenAdminsichtbar; Signaturfarbe--tk-ws-costs=#84cc16/ Light#4d7c0f— provisorisch, distinkte Hue in der vollen Palette). Tab Infra (KPI-Kacheln · Spend je Dienst · Monats-Verlauf · größte Posten · Erfassungs-Tabelle) gebaut; Tab Betrieb (Sicht A) als Platzhalter vorgemerkt. Mono-Zahlen, nur--tk-*-Tokens (No-Hex-Gate grün). - Bewusste Vereinfachungen / offen: Mehrwährungs-Umrechnung (FX) noch nicht → ungewichtete Summe + sichtbarer Hinweis (OP-I18N-1);
dienstals freier String + Vorschlagsliste statt eigener Taxonomie-Persistenz; Provider-Billing-/Usage-API-Pull statt manueller Erfassung = später (OP-OBS-1). Audit der Posten-Änderungen GEBAUT (#199): jedeinfraKosten.*-Aktion → Ops-Log-Zeile (interimconsole) und dauerhaftes D1-Audit-Event (OP-AUDIT-1,event_type=infraKosten.*); „wer hat erfasst/geändert" (Actor) wird vollständig, sobald das Access-Gate scharf ist (OP-COST-3) — sonstunbekannt.
OP-COST-2 Slice 2 — LLM-API-Spend-Auto-Pull + „zuletzt gezogen"-Stempel — gebaut (v0.43.0)
- Zweiter Auto-Pull-Provider: pure
server/src/kosten/llm-billing.ts(llmCostZuPosten) bildet die Anthropic Usage-/Cost-API (data[]-Zeitbuckets) je Monat auf einenInfraKostenPostenab (Dienst „Anthropic API (LLM)"),quelle:'api'+quelleRef=llm:YYYY-MM(idempotenter Upsert, kein Doppel-Pull). Beträge ganzzahlig in Cent; Betrag/Datum defensiv geparst (kaputt ⇒ 0/übersprungen statt Crash). DOpullLlmKosten()dormant ohneLLM_BILLING_KEY(Secret); Endpoint/Label perLLM_BILLING_URL/LLM_BILLING_DIENST([vars]) überschreibbar. UI: Button „↻ LLM ziehen" neben „↻ GitHub ziehen" (admin-gegated), AktioninfraKosten.pullLlm, Audit-EventinfraKosten.pull(Actorllm-pull). - Zeitstempel des letzten erfolgreichen Abholens (je Auto-Pull-Komponente, Nutzer-Wunsch): persistiert als
infraKostenPull: Record<Dienst, epoch-ms>(State, schemaVersion 17→18, Merge-Default; gesetzt bei jedem erfolgreichen HTTP-Abruf — auch ohne inhaltliche Änderung). Im DTO alsletzterPullzum Client. Dezent angezeigt: Caption unter dem jeweiligen Pull-Button (zuletzt: vor 3 Minbzw.noch nie gezogen), Inline-Stempel jeAPI-Posten-Zeile und imAPI-Badge-Tooltip (relativ, ab 1 Tag absolutdd.mm.yyyy, hh:mm). Nur--tk-*-Tokens, Mono-Zahlen (No-Hex-Gate grün). Self-Testnpm run test:kostenerweitert (LLM-Mapper) +test:do-migrations(v→18).
OP-COST-2 Slice 3 — Mistral (realer Anbieter): ECHTE LLM-Kosten via Admin-Usage-API — gebaut (v0.44.0)
- Warum: Der Assistent läuft real über Mistral (EU), nicht Anthropic → die tatsächlichen LLM-Kosten
kommen aus Mistrals Admin-Usage-API (
GET console.mistral.ai/api/admin/usage?month=M&year=Y, Bearer Admin-Key). Wir nehmen die echten Beträge der API (je Modell/Monat), nicht Token×Listenpreis. - Umsetzung: pure
server/src/kosten/mistral-billing.ts(mistralUsageZuPosten,mistralUsageUrl) → ein Monatsposten „Mistral (LLM)",quelle:'api',quelleRef=mistral:YYYY-MM(idempotent). DOpullMistralKosten()zieht laufenden + Vormonat, dormant ohneMISTRAL_ADMIN_KEY; AktioninfraKosten.pullMistral+ Button „↻ Mistral ziehen"; AuditinfraKosten.pull(Actormistral-pull); „zuletzt gezogen"-Stempel wie GitHub/Anthropic. Key via GitHub-SecretMISTRAL_ADMIN_KEY(CI-Sync). - Schema-Unsicherheit (bewusst): Die Beta-Console-API ist öffentlich nicht dokumentiert (docs.mistral.ai
bot-blockiert; das offizielle SDK exponiert die Route nicht). Der Kosten-Extraktor ist deshalb defensiv
(Gesamt-Kostenfeld bevorzugt, sonst Summe der Posten-Liste; Feldnamen/Einheit an einer Stelle justierbar,
MISTRAL_BETRAG_IN_CENT) und best-effort — nach dem ersten echten Pull Feld/Einheit einmalig verifizieren. Self-Testtest:kostendeckt beide Antwort-Formen, Cent-Einheit, URL-Bau und den Leer-/Kaputt-Fall ab.
Sicht A (betriebswirtschaftlich) — offen
Slice A1 (Arbeitskosten je Auftrag) ist laut §3.3/§8 ohne neue Abhängigkeit baubar (Stundensatz-Stammdaten je Abteilung + Ableitung aus vorhandenen Leistungsnachweisen). A2 (Material, gated OP-R10-1) und A3 (Marge + volles Dashboard, gated OP-R7-6/SevDesk) folgen. Der „Betrieb"-Tab im Kosten-Workspace ist bereits angelegt. Slice-A1-Datenmodell (Nutzer 06-26, §3.1): Stundensatz je Abteilung + Mitarbeiter-Override, je mit Von-Bis-Gültigkeit (historisiert); Berechnung nur im Report (D-2).
12. Kosten-Gate — Rolle statt E-Mail-Allowlist (seit v0.156.0)
Umstellung 2026-07-26 (OP-AUTH-1 A-1.5): Das Kosten-Gate hängt nicht mehr an einer E-Mail-Allowlist (
KOSTEN_ADMIN_EMAILSentfernt), sondern an der im ⚙-Menü gewählten Rolle: die Fähigkeitkostenhaben Buchhaltung und Inhaber/Chef. Der DO filtert die Infra-Kosten je Verbindung aus der Rolle (?rolle=-Query →capsAusRolle) — Nicht-Berechtigte erhalten das FeldinfraKostennie, Schreib-Aktionen ebenso gegated (kein Broadcast-Leak, §6). Das frühere allowlist-basierte Owner-only-Runbook entfällt.
⚠ Interim self-declared (A-1.5): die Rolle ist selbst gewählt (kein Nachweis). Der Zugang ist am Edge durch Cloudflare Access geschützt (Golden Rule „Zero Trust vor Public" — WER erreicht die App überhaupt); die verifizierte Kosten-Durchsetzung (Rolle aus dem Better-Auth-JWT-Claim statt Selbstauskunft) folgt mit OP-AUTH-1 A-3. Bis dahin geführt als RISK-12 (Risikoregister).
Access produktiv aktivieren (weiterhin sinnvoll als Netz-Gate, Golden Rule) — ACCESS_TEAM_DOMAIN +
ACCESS_AUD in server/wrangler.toml [vars] setzen + mergen (CD redeployt). Bereitliegende Werte (drkv-com,
kein PII): ACCESS_TEAM_DOMAIN = "drkv-com.cloudflareaccess.com" · ACCESS_AUD = "ffafb06fcf4d3da1b46a700ca146d488a3a930c647fca7106e8dd71ee5ac9ba6". ⚠ Lockout: falsche Werte sperren
alle Requests (401) → vor dem Merge prüfen; Rollback = Werte wieder leeren + redeploy. Access liefert zudem
die verifizierte X-Taktano-User-E-Mail als Audit-Actor (OP-AUDIT-1).
Verifikation: im ⚙-Menü Rolle Buchhaltung/Inhaber-Chef wählen → Workspace „Kosten" + Posten sichtbar; Rolle Werker/Werkstattleitung → keine Kosten-Daten im State, Workspace fehlt.
13. Reports & Export (OP-EXPORT-1, Nutzer 06-26)
Verbindlich für ALLE Reports (Kosten und darüber hinaus): jeder Report kommt mit Diagramm und ist exportierbar als (a) PDF (formatierte Sicht inkl. Diagramm) und (b) Rohdaten — CSV und Excel (.xlsx). Gilt für die Kosten-Sichten A/B ebenso wie für künftige Reports (Auslastung, Leistung, Management-Summary OP-REPORT-1). Cross-cutting als OP-EXPORT-1 geführt; Architektur folgt (Diagramm-Lib · serverseitiges PDF/XLSX-Rendering vs. Client · Rohdaten direkt aus den abgeleiteten Aggregaten, D-2). Bezug: OP-REPORT-1, OP-COST-1.
↩ Zurück zur Doku-Landkarte · Lesepfade · Register (alle OPs)