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:
| Feld | Typ | Zweck |
|---|---|---|
dienst | String | z. B. "Cloudflare", "Anthropic/LLM", "Domain" (frei + Vorschlagsliste, G-3) |
monat | YYYY-MM | Abrechnungsmonat |
betragCent | Int | ganzzahlig in Cent (kein Float-Drift) |
waehrung | String | EUR/USD … (FX = späterer Punkt) |
quelle | 'api' | 'manuell' | Herkunft; api = automatisch gezogen |
quelleRef | String? | Idempotenz-Key je API-Pull (<dienstKey>:YYYY-MM) |
erfasstAm | Timestamp | Anlage/Update des Postens |
letzterPullAm | Timestamp? | 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)
| Dienst | Quelle | Bezug |
|---|---|---|
| Cloudflare (Worker/D1/R2) | GraphQL Analytics/Billing-API | A-4 Stack |
| LLM/KI-Spend (Medidentas-GPT) | Provider Usage-/Cost-API (Anthropic/Mistral …, EU-Residenz) | OP-AI-1 |
| Domain-Registrar | Registrar-API bzw. manuell | OP-DEPLOY-1 |
| Signatur-Provider (DocuSign/PandaDoc) | Provider-Billing bzw. manuell | A-2/OP-SIGN-1 |
| NextCloud-/Ablage-Hosting | Hoster-Rechnung, i. d. R. manuell | A-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 = vertraulich → Default: 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)
- Slice 1 — Datenmodell + manuelle Erfassung (Bootstrap):
kosten-Tabelle (D1, Migration) + Admin-CRUD + abgeleitete Auswertung (Summe/Dienst/Monat/Trend) + Self-Test. Rollen-Gate (Admin). - Slice 2 — erster Auto-Pull (LLM oder Cloudflare): pure Mapper + dormanter Pull (on-demand-Button), Idempotenz-Upsert, Audit, "zuletzt gezogen"-Stempel.
- Slice 3 — weitere Provider + Cron-Automatik (periodischer Pull statt nur on-demand).
- 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.