Zum Hauptinhalt springen

Betriebskosten automatisch ziehen — Architektur-Design (MED-OP-COST-1/2)

Kernaussage: Die laufenden Infrastruktur-/Betriebskosten von Medidentas (Cloudflare, LLM/KI, Domain, Signatur-/Ablage-Hosting …) werden je Dienst direkt aus dessen Billing-/Usage-API gezogen — periodisch (Cron) und on-demand — statt von Hand gepflegt. Das ist die allgemeine Guidance G-7 (cost-pull-at-source), aus Taktano (OP-COST-2, dort gebaut) übernommen. Dieses Dokument ist der verbindliche Entwurf; der Bau folgt als eigener Slice (MED-OP-COST-2).

Status: Entwurf (Guidance festgezurrt, MED-D-62) · Bau offen (MED-OP-COST-2). Referenz-Implementierung: Taktano docs/architektur/Kosten.md §4 + server/src/kosten/*-billing.ts.

1. Zweck & Abgrenzung

Medidentas soll wissen, was der Betrieb kostet, ohne dass jemand Rechnungen abtippt. Die Guidance: keine manuelle Kostenpflege, wo eine Provider-API existiert — die Zahl kommt aus der Quelle, die sie ohnehin führt.

Abgrenzung — was MED-OP-COST-1 NICHT ist:

  • ≠ Rechnungsstellung/Mahnwesen an Mandanten → das ist SevDesk (OP-INVOICE-1, ausgehende Mandanten-Rechnung + Leistungszeit R13). Hier geht es um eingehende Betriebskosten (was wir an Cloudflare/LLM/Domain zahlen), nicht um Umsatz.
  • ≠ Leistungszeit-Erfassung (R13/OP-TIME-1) und ≠ gesetzliche Arbeitszeit (OP-TIME-2).
  • Audit-Log (R8/G-4) und ≠ Betriebs-Logs/Observability (OP-OBS-1) — der Kosten-Pull erzeugt aber je Abruf ein Audit-Event.
  • Keine Finanzbuchhaltung — die Werte sind eine betriebliche Näherung zur operativen Steuerung, nicht buchhalterisch maßgeblich.

2. Guidance G-7 — cost-pull-at-source (Prinzip)

Analog zu G-6 (Scrubbing-at-Source), aber für Kosten statt PII: die Kosten-Wahrheit wird an der Quelle abgeholt, nicht dupliziert.

  • Automatischer Pull je Provider-API ist die verbindliche Richtung; manuelle Erfassung bleibt nur Fallback/Bootstrap (Dienste ohne API, Korrekturen, "Sonstiges").
  • Derived State nie doppelt persistieren (G-2): gespeichert werden die gezogenen Fakten je Dienst × Monat (in Cent, kein Float-Drift); Summen/Trends/Anteile sind abgeleitet, nie gespeichert.
  • Idempotenz: je Pull ein quelleRef (z. B. cloudflare:YYYY-MM) → Upsert statt Doppel-Posten.
  • Secrets, kein Repo-PII (G-5): Provider-Keys/Tokens als Worker-Secrets, via CI aus GitHub-Secrets gespiegelt (Everything-as-Code) — nie im Repo.
  • Dormant/deploy-sicher: ohne gesetztes Secret ist der Pull ein No-op (bricht Deploy/Laufzeit nie).
  • Audit (G-4): jeder Pull ist ein Audit-Event (kosten.pull), PII-arm (nur Dienst/Monat/Betrag).
  • Zeitstempel des letzten erfolgreichen Pulls je Dienst wird persistiert und dezent angezeigt ("zuletzt gezogen: vor 3 Min").

3. Datenmodell (Fakten gespeichert, Sichten abgeleitet)

Ein schlanker Posten je Dienst × Monat — angelehnt an Taktanos InfraKostenPosten:

FeldTypZweck
dienstStringz. B. "Cloudflare", "Anthropic/LLM", "Domain" (frei + Vorschlagsliste, G-3)
monatYYYY-MMAbrechnungsmonat
betragCentIntganzzahlig in Cent (kein Float-Drift)
waehrungStringEUR/USD … (FX = späterer Punkt)
quelle'api' | 'manuell'Herkunft; api = automatisch gezogen
quelleRefString?Idempotenz-Key je API-Pull (<dienstKey>:YYYY-MM)
erfasstAmTimestampAnlage/Update des Postens
letzterPullAmTimestamp?Zeitpunkt des letzten erfolgreichen API-Pulls je Dienst — speist den "zuletzt gezogen"-Stempel (nicht erfasstAm, das driftet bei manuellen Korrekturen)

Persistenz: D1 (EU-Residenz, G-5), versioniert + idempotente Migration (bestehendes Muster server/src/db/). Abgeleitet (nie gespeichert): Summe gesamt · je Dienst (+ Anteil) · Monats-Verlauf · Trend ggü. Vormonat · größte Posten · Mehrwährungs-Flag.

4. Provider (Pull-Quellen für Medidentas)

DienstQuelleBezug
Cloudflare (Worker/D1/R2)GraphQL Analytics/Billing-APIA-4 Stack
LLM/KI-Spend (Medidentas-GPT)Provider Usage-/Cost-API (Anthropic/Mistral …, EU-Residenz)OP-AI-1
Domain-RegistrarRegistrar-API bzw. manuellOP-DEPLOY-1
Signatur-Provider (DocuSign/PandaDoc)Provider-Billing bzw. manuellA-2/OP-SIGN-1
NextCloud-/Ablage-HostingHoster-Rechnung, i. d. R. manuellA-1

Reihenfolge (Vorschlag): zuerst die APIs mit dem größten/variabelsten Spend (LLM, Cloudflare), den Rest manuell/Fallback. Jeder neue Provider = ein Mapper server/src/kosten/<dienstKey>-billing.ts (pure Funktion + Self-Test), dormant ohne Secret. dienstKey = stabiler, normalisierter Slug (lowercase, [a-z0-9-], z. B. cloudflare, llm, domain) — kanonischer Identifier für Dateiname + quelleRef; der freie dienst-String bleibt reines Anzeigelabel (kann Leerzeichen// enthalten, taugt nicht für Dateinamen).

5. Rollen & Sichtbarkeit (sensibel)

  • Kostendaten = vertraulichDefault: nur Admin/Owner (RBAC domain/rechte.ts, D-48). Kein Kosten-Feld an Nicht-Berechtigte (kein Broadcast-/DTO-Leak).
  • Anlegen/Ändern manueller Posten + jeder Pull → Audit-Event (G-4).
  • Keine Klartext-Beträge in Logs (PII-/Geschäftsdaten-arm, OP-OBS-1).

6. Compliance & Risiko (DE/AT/CH)

  • AVV/Residenz je Provider-API beachten (Kosten-API greift auf Account-Daten zu, nicht auf Mandanten-PII).
  • Risiko (→ docs/betrieb/Risikoregister.md): (a) falsche/fehlende Pulls → irreführende Kostensicht (Maßnahme: "zuletzt gezogen"-Stempel + Lücken sichtbar); (b) Secret-Leak (Maßnahme: nur Secrets, CI-Sync, nie Repo); (c) Mehrwährung ungewichtet summiert bis FX gebaut ist → sichtbarer Hinweis.

7. Build-Slices (Vorschlag, MED-OP-COST-2)

  1. Slice 1 — Datenmodell + manuelle Erfassung (Bootstrap): kosten-Tabelle (D1, Migration) + Admin-CRUD + abgeleitete Auswertung (Summe/Dienst/Monat/Trend) + Self-Test. Rollen-Gate (Admin).
  2. Slice 2 — erster Auto-Pull (LLM oder Cloudflare): pure Mapper + dormanter Pull (on-demand-Button), Idempotenz-Upsert, Audit, "zuletzt gezogen"-Stempel.
  3. Slice 3 — weitere Provider + Cron-Automatik (periodischer Pull statt nur on-demand).
  4. Später: FX/Mehrwährung, Report/Export (analog Taktano OP-EXPORT-1), optionale Mandanten-Umlage.

8. Bezüge

G-7 (cost-pull-at-source, dieses Doc) · G-2 (derived state) · G-4 (Audit) · G-5 (Secrets/Residenz) · A-4 (Cloudflare) · OP-AI-1 (LLM-Spend) · OP-INVOICE-1/R13 (ausgehende Rechnung, abgegrenzt) · OP-OBS-1 (Usage-Metriken) · D-48 (RBAC) · Referenz: Taktano OP-COST-1/2.