Versionierung und Aenderungshistorie je Szenario
Deploy App / deploy (push) Successful in 1m48s

Version A.B: B automatisch je Bearbeitungssitzung (10-Minuten-Fenster,
unveraenderte Staende erzeugen keine), A manuell mit Pflichtkommentar.

Eine Version haelt den vollstaendigen Zustand als PlanInput-JSON -- dadurch
ist die Versionsauswahl in allen vier Analysewerkzeugen fast kostenlos,
bei Monte-Carlo je Szenario einzeln.

Wiederherstellen erhaelt die IDs (sonst verlieren Kind-Szenarien ihre
Diff-Basis) und legt den Stand selbst als neue Version an. Wo ein Bezug
trotzdem bricht, warnt der Dialog vorher namentlich.

Statischer Waechter-Test: jeder schreibende Endpunkt loest eine Version aus.
Migration gegen echtes Postgres verifiziert.

Spezifikation 0.19 (3.8 und 9.28 neu), 25 Tests (139 -> 164).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 22:09:10 +02:00
parent d04e07fdfb
commit d023534a03
28 changed files with 2021 additions and 13 deletions
+171 -4
View File
@@ -4,7 +4,7 @@
| | |
|---|---|
| **Dokument** | Funktionale und Technische Spezifikation FPT |
| **Version** | 0.18 |
| **Version** | 0.19 |
| **Datum** | 2026-07-18 |
| **Status** | Lebendes Dokument |
| **Codestand** | Arbeitsstand nach `1d046e9` inkl. zwei Monte-Carlo-Fragestellungen (Branch `main`) |
@@ -17,6 +17,7 @@
| Version | Datum | Autor | Änderung |
|---|---|---|---|
| 0.19 | 2026-07-19 | Claude (Opus 4.8) | **Versionierung und Änderungshistorie je Szenario** (neues Kapitel 3.8). Jedes Szenario trägt eine Version **A.B**: **B** entsteht automatisch, **A** manuell mit Pflichtkommentar. **Der zentrale Entwurfsentscheid:** FPT hat keinen Speichern-Knopf jede Änderung schreibt sofort, ein Assistenten-Durchlauf macht ~14 Schreibvorgänge, ein Verteil-Klick einen je Zielelement. Eine Version je Schreibvorgang wäre ein Tastenprotokoll gewesen; stattdessen werden alle Schreibvorgänge innerhalb von **10 Minuten zu einer** Nebenversion zusammengefasst, inhaltlich unveränderte Stände erzeugen gar keine, und verschiedene Benutzer laufen nie in einer Version zusammen. Eine Version hält den **vollständigen** Zustand als JSON in der Form `PlanInput` dadurch ist die **Versionsauswahl in allen vier Analysewerkzeugen** (Grafiken, Live-Simulation, Monte-Carlo, Einflussfaktoren) fast kostenlos; bei Monte-Carlo **je Szenario einzeln**, weil dort mehrere gleichzeitig laufen. **Wiederherstellen** ist ungefährlich gebaut: Es legt den zurückgesetzten Stand selbst als neue Version an («Wiederhergestellt aus A.B»), löscht also nichts, und **erhält die IDs** von Phasen und Elementen sonst verlören alle Kind-Szenarien ihre Diff-Basis und zeigten schlagartig alles als «neu». Wo ein Bezug trotzdem bricht (der alte Stand kannte das Element noch nicht), **warnt der Dialog vorher namentlich**. Die destruktive Logik liegt als reine Funktion `planRestore` vor und ist dort getestet; `versioning-db.ts` führt sie nur aus. Ein **statischer Wächter-Test** liest alle Route-Dateien und verlangt, dass jeder schreibende Endpunkt eine Version auslöst eine vergessene Stelle wäre eine stille Lücke. Neue Tabelle `ScenarioVersion` + `Scenario.currentMajor` (Migration gegen echtes Postgres verifiziert), neue Endpunkte unter `/api/scenarios/<id>/versions`. Neue Kapitel 3.8 und 9.28; 25 Tests ergänzt (139 → 164). |
| 0.18 | 2026-07-19 | Claude (Opus 4.8) | **Live-Simulation** (Roadmap Nr. 22). Neuer Button und Dialog als Zweispalter: links Schieberegler, rechts eine wählbare Grafik, darüber eine Kennzahlenleiste. Dreht man an einem Regler, wird der Plan **sofort** neu gerechnet ohne für jede Variante eine Szenario-Kopie anzulegen. **Keine eigene Rechenlogik:** Die Regler benutzen dieselben Transformationen wie der Tornado (`applyDriver`), können also gar nicht etwas anderes zeigen als die Einflussfaktoren-Analyse. Neu ist nur `applyElementDriver` dieselbe Verschiebung auf ein **einzelnes** Element statt auf eine ganze Kategorie: Standardmässig gibt es einen Sammelregler «Rendite», ein Klick auf «Renditen einzeln aufschlüsseln» ersetzt ihn durch je einen Regler pro Anlage (der Sammelregler wird dabei **entfernt**, nicht ergänzt, sonst zählte eine Bewegung doppelt; ein Test sichert ab, dass beide Ansichten denselben Plan beschreiben). Der unveränderte Plan wird als **Referenzlinie** mitgezeichnet, und die Kennzahlenleiste weist Endvermögen nominal/real **mit Differenz zum Plan** aus sowie als eigene Karte ob das Kapital reicht; ein gekippter Plan ist einer Verlaufslinie sonst nicht anzusehen. Gemessene Laufzeit von `computePlan`: **0.2 ms** auf einem 60-Jahres-Plan mit 10 Elementen, also rund 1 % des 16-ms-Frame-Budgets deshalb wird synchron gerechnet, **ohne Debounce und ohne Worker**. Anders als der Tornado haben die Regler **Standardbereiche** (Begründung des scheinbaren Widerspruchs zu 9.18: neues Kapitel 9.27), beide Enden editierbar. **Das Pensionsalter fehlt weiterhin** (9.18, eigener Roadmap-Punkt); «Als Szenario speichern» ist bewusst zurückgestellt, ersatzweise zeigt der Dialog die aktive Einstellung als lesbare Zeile. Die Vermögensaufteilung wurde als `AllocationChart` aus dem Dashboard herausgelöst, damit beide sie nutzen. Neue Kapitel 4.15 und 9.27; 15 Tests ergänzt (124 → 139). Keine API-, DB- oder Schreib-Änderung das Feature liest ausschliesslich. Nebenbei dieselbe vom Umlaut-Sweep verstümmelte Hex-Farbe (`#7c3aed`) wie in 0.17, diesmal im Dashboard. |
| 0.17 | 2026-07-19 | Claude (Opus 4.8) | **Monte-Carlo: zwei Welten, vier Fälle.** Behebt einen Darstellungs-Widerspruch: Zuvor konnte «Planung 69 % erreicht» neben «Ziel 3 Mio nur 41 %» stehen, obwohl 3 Mio unter dem Plan-Endbetrag von 3.7 Mio lag die beiden Zahlen stammten aus **verschiedenen simulierten Welten**. Neu läuft die Simulation **immer zweimal** (historische Renditen / geplante Werte, gemeinsamer Seed) und liest aus **jeder** Verteilung **beide** Schwellen ab: Plan-Endbetrag und Zielbetrag. Fall 1 und Fall 3 stammen damit aus derselben Verteilung, wodurch ein tieferes Ziel **nie** unwahrscheinlicher sein kann als ein höheres der Widerspruch ist strukturell ausgeschlossen (Test). Zweite Korrektur: Der Nullpunkt für das Urteil ist **nicht 50 %**, sondern **Fall 2** (derselbe Schwellwert in der eigenen geplanten Welt); durch den Volatilitäts-Drag liegt der je nach Streuung bei 2748 %. Verglichen wird Fall 1 gegen Fall 2 mit ± 5 pp Toleranzband → «zurückhaltend / realistisch / zu optimistisch». Darstellung: Fall 1 prominent mit Urteil, Fall 3+4 als Satzpaar untergeordnet, Fall 2 und die Mediane klein als Referenz. Der Drei-Wege-Umschalter aus 0.16 entfällt; historische Mittelwerte **und** Zielbetrag sind jetzt beide Pflicht. Technisch: `MonteCarloResult.finalWealthSorted` (alle Endvermögen sortiert) plus neuer Helfer `probabilityAtLeast` (Binärsuche) vier Zahlen aus zwei Läufen statt vier Läufen. Kapitel 4.12.7 und 9.26 neu gefasst; 3 Tests ergänzt (121 → 124). Keine Änderung am Rechenkern. |
| 0.16 | 2026-07-19 | Claude (Opus 4.8) | **Monte-Carlo mit zwei Fragestellungen** (Roadmap Nr. 46). Ein Umschalter oben trennt: **«Planung prüfen»** (Fall 1, wie bisher) würfelt um die **historischen** Renditen und prüft gegen den **Planungs-Endbetrag** (read-only) «wie realistisch ist meine Planung?». **«Ziel prüfen»** (Fall 2, neu) würfelt um die **geplanten** Werte aus dem Plan und prüft gegen einen **manuellen Zielbetrag** «erreiche ich mein Ziel?»; hier sind keine historischen Mittelwerte nötig, nur die Streuung. **«Beides»** rechnet beide Durchgänge gleichzeitig (gemeinsamer Seed). Neue Ergebnis-**Deutungstexte** je Fall (weich formuliert wegen des Volatilitäts-Drags). Der Fächer stammt aus dem historischen Durchgang; der Zielbetrag ist in Fall 2 **einer für alle** Szenarien. `runMonteCarloMulti` nimmt neu die Inflation **je Szenario** (`inflationMeanFor`); neuer Helfer `plannedReturnOf`. Kapitel 4.12 überarbeitet, 4.12.7 und 9.26 neu; 8 Tests ergänzt (119 → 121). Nebenbei eine vom Umlaut-Sweep verstümmelte Hex-Farbe (`#7c3aed`) korrigiert. Keine Änderung am Rechenkern. |
@@ -1204,6 +1205,104 @@ Jede Element-Zeile der Matrix trägt eine **Sparkline** (`Sparkline.tsx`): der W
(`ElementPhaseComputed.yearly`, seit 0.11) keine Neuberechnung, reine Darstellung. Flache
Verläufe werden nicht gezeichnet (keine Information).
## 3.8 Versionierung und Änderungshistorie
Jedes **Szenario** trägt eine Version **A.B** und eine vollständige Änderungshistorie. Zugang
über den Knopf **«Änderungshistorie»** in der Szenario-Leiste.
| | Bedeutung | Entsteht |
|---|---|---|
| **B** (Nebenversion) | ein Bearbeitungsstand | **automatisch**, eine je Bearbeitungssitzung |
| **A** (Hauptversion) | ein bewusst gesetzter Meilenstein | **manuell**, mit Pflichtkommentar; setzt B auf 0 |
### 3.8.1 Eine Version je Sitzung nicht je Änderung
FPT hat **keinen Speichern-Knopf**: Jede Änderung schreibt sofort. Eine Version je
Schreibvorgang wäre deshalb ein Tastenprotokoll und keine Historie ein Durchlauf des
Plan-Assistenten macht rund **14** Schreibvorgänge, ein Klick im Verteil-Dialog einen **je
Zielelement**.
Stattdessen werden alle Schreibvorgänge innerhalb eines **Zeitfensters von 10 Minuten** zu
**einer** Nebenversion zusammengefasst: Der erste legt sie an, alle weiteren aktualisieren
sie. Zwei Sicherungen ergänzen das:
- **Unverändert = keine Version.** Ergibt ein Schreibvorgang inhaltlich denselben Stand
(Dialog geöffnet und unverändert geschlossen), entsteht nichts. Verglichen wird über eine
Serialisierung mit **sortierten Schlüsseln** ohne das würden identische Stände als
verschieden gelten, weil die Reihenfolge der Werte aus der Datenbank-Zeilenfolge stammt.
- **Benutzerwechsel trennt immer.** Änderungen verschiedener Benutzer laufen nie in einer
Version zusammen, auch nicht innerhalb des Fensters Vorbereitung auf die spätere Freigabe
an einen Finanzberater.
Eine **Hauptversion** wird nie zusammengefasst und nie nachträglich verändert; die nächste
Änderung beginnt bei A.1.
### 3.8.2 Was die Historie zeigt
Je Version: **wer** (Benutzername), **wann** (Zeitpunkt der letzten Änderung dieser Sitzung)
und bei Hauptversionen der **Kommentar**. Die oberste Zeile ist der aktuelle Stand.
Zwei Aktionen je Version:
- **Anzeigen** ein Nur-Lese-Fenster mit der Matrix dieses Standes ([VersionMatrix](#552-komponenten)),
Phasen als Spalten, Elemente als Zeilen, dazu Endvermögen und Ruinalter. Bewusst **nicht**
die Bearbeitungs-Matrix: Inspector, Übergangs-Ampeln und Diff-Markierung haben für einen
alten Stand keine Bedeutung.
- **Wiederherstellen** setzt das Szenario vollständig auf diesen Stand zurück.
### 3.8.3 Wiederherstellen
Zwei Eigenschaften machen den Knopf ungefährlich:
**Es geht nichts verloren.** Der wiederhergestellte Stand wird selbst als **neue Version**
festgehalten, mit dem Kommentar «Wiederhergestellt aus A.B». Die bisherige Historie bleibt
vollständig auch alles, was nach dem Zielstand kam.
**IDs bleiben erhalten.** Phasen und Elemente behalten ihre IDs (der Snapshot trägt sie mit).
Das ist keine Kosmetik: Kind-Szenarien zeigen über `sourceElementId` / `sourcePhaseId` auf
genau diese IDs, und daran hängt die Abweichungs-Markierung
([3.2.6](#326-abweichungs-markierung-diff)). Würden neue IDs entstehen, erschiene in
jedem Kind-Szenario schlagartig **alles als «neu»** statt als «geändert».
Restlos vermeiden lässt sich das nicht: Geht man auf einen Stand zurück, in dem ein Element
noch gar nicht existierte, auf das ein Kind verweist, bricht dieser eine Bezug zwangsläufig.
Der Dialog **prüft das vorher** und benennt die betroffenen Szenarien namentlich, statt es
stillschweigend zu tun.
### 3.8.4 Analyse auf einer bestimmten Version
Alle vier Analysewerkzeuge **Grafiken**, **Live-Simulation**, **Monte-Carlo** und
**Einflussfaktoren** haben oben einen Wähler **«Berechnungsgrundlage»**: aktueller Stand
(Vorgabe) oder eine festgehaltene Version.
In der **Monte-Carlo-Simulation** ist die Auswahl **zweidimensional**: Sie rechnet mehrere
Szenarien gleichzeitig, und der Wähler hängt deshalb an der jeweiligen Szenario-Zeile man
kann Szenario A in Version 1.2 gegen Szenario B in Version 2.0 stellen.
Das war fast kostenlos: Der Snapshot ist bereits ein **`PlanInput`**, also genau der Typ, den
alle Werkzeuge ohnehin entgegennehmen. Es genügt, den Arbeitsstand durch den Snapshot zu
ersetzen; das Gerechnete entsteht lokal über `computePlan`.
**Eine Ausnahme ist beschriftet:** Der CSV-Export liest immer das Szenario aus der Datenbank
und liefert deshalb den **aktuellen** Stand, nicht die betrachtete Version. Der Knopf sagt das
in diesem Fall ausdrücklich.
### 3.8.5 Vollständigkeit der Historie
Die Versionierung hängt daran, dass **jeder** inhaltsverändernde Endpunkt sie auslöst
(`touchScenario`). Ein vergessener Pfad fiele nicht auf er erzeugte still keine Version, und
die Lücke bemerkte man erst Wochen später. Ein **Test liest deshalb alle Route-Dateien** und
verlangt, dass jeder schreibende Endpunkt entweder `touchScenario` aufruft oder mit Begründung
in einer Ausnahmeliste steht; verwaiste Ausnahmen meldet er ebenfalls.
Das Festhalten einer Version ist bewusst **fehlertolerant**: Schlägt es fehl, wird es
protokolliert, die eigentliche Änderung des Nutzers aber nicht zurückgewiesen. Die Historie
ist Begleitinformation, nicht der Zweck der Anfrage.
Referenz: `src/lib/versioning.ts` (reine Logik), `src/lib/versioning-db.ts` (Datenbank),
`src/components/VersionHistoryDialog.tsx`, `src/components/VersionMatrix.tsx`,
`src/components/VersionPicker.tsx`.
---
# 4. Berechnungsmodell
@@ -2406,7 +2505,7 @@ Referenz: `src/lib/livesim.ts`, `src/lib/sensitivity.ts` (`applyElementDriver`,
FPT/
├── prisma/
│ ├── schema.prisma Datenmodell
│ └── migrations/ 12 Migrationen (chronologisch, siehe 5.4.6)
│ └── migrations/ 13 Migrationen (chronologisch, siehe 5.4.6)
├── src/
│ ├── app/
│ │ ├── api/ Route Handlers (siehe Kapitel 6)
@@ -2447,6 +2546,8 @@ PlanComputed ← an den Client geliefert
| `constants.ts` | Schweizer Systemparameter |
| `montecarlo.ts` | Monte-Carlo-Simulation (Sampler + Treiber), Szenario-Vergleich und Element-Gruppierung über die Herkunfts-Kette. Keine I/O, läuft im Browser. |
| `sensitivity.ts` | Sensitivitätsanalyse / Tornado: Treiber-Katalog, Parameter-Transformationen, `computeTornado`; zusätzlich `applyElementDriver` / `tunableElements` für die einzeln regelbaren Element-Renditen. Rein, läuft im Browser. |
| `versioning.ts` | Versionierung: Nummerierung A.B, Zusammenfassung je Sitzung, Bauplan des Wiederherstellens, Auswirkung auf Kind-Szenarien. Rein, ohne I/O. |
| `versioning-db.ts` | Datenbank-Anbindung der Versionierung: Snapshot festhalten, Hauptversion, Wiederherstellen. Führt den Bauplan aus `versioning.ts` nur aus. |
| `livesim.ts` | Live-Simulation: Reglerkatalog mit Standardbereichen, Anwenden mehrerer Regler, Kennzahlen (Kap. 4.15). Rein, läuft im Browser. |
| `distribution.ts` | Kapitaltopf und Quoten-Zerlegung, Anwenden von Entwurfswerten für die Verteil-Werkzeuge (Kap. 3.6.9/3.6.10). Rein. |
| `phaseplan.ts` | Ableitung der Lebensabschnitte (Erwerb/Misch/Pension) aus den fixen Pensionierungszeitpunkten für den Assistenten (Kap. 3.2.8). Rein. |
@@ -2658,6 +2759,7 @@ sondern zu leeren Werten.
| `20260716230000_phase_cash_transition` | `Phase.cashTransition` (JSONB) für einmalige Sonderein-/ausgaben |
| `20260718090000_plan_scenario_hierarchy` | **V6**: `Plan``Scenario` (IDs erhalten), neuer Behälter `Plan`, `planId``scenarioId`, Herkunfts-Verweise |
| `20260718140000_scenario_start_year` | `Scenario.startYear` (Kalenderjahr des Planbeginns), bestehende auf das laufende Jahr gesetzt |
| `20260719210000_scenario_versioning` | Tabelle `ScenarioVersion` (Snapshot als JSONB, A.B eindeutig je Szenario) und `Scenario.currentMajor` |
**Zur V6-Migration:** Sie benennt die bisherige `Plan`-Tabelle in `Scenario` um dadurch
bleiben alle IDs und damit sämtliche Kind-Fremdschlüssel gültig. Für jedes bisherige
@@ -2705,6 +2807,9 @@ wird der Plan neu geladen; die Berechnung kommt immer vom Server.
| `PhaseDetail` | 95 | Phase bearbeiten/löschen |
| `SparquoteChart` | 64 | Einkommen/Ausgaben pro Jahr mit Spar-/Verzehrbändern |
| `MonteCarloDialog` | ~600 | Monte-Carlo-Dialog: Erklärung, Szenario-Auswahl, Eingaben, Lauf, Vergleichstabelle + Fächer |
| `VersionHistoryDialog` | ~290 | Änderungshistorie: Liste, Hauptversion festlegen, Anzeigen, Wiederherstellen mit Warnung ([3.8](#38-versionierung-und-änderungshistorie)) |
| `VersionMatrix` | ~110 | Nur-Lese-Matrix eines festgehaltenen Standes |
| `VersionPicker` | ~140 | Wahl der Berechnungsgrundlage in den vier Analysewerkzeugen |
| `LiveSimDialog` | ~330 | Live-Simulation: Regler links, Grafikwahl rechts, Kennzahlenleiste mit Differenz zum Plan ([4.15](#415-live-simulation-was-wäre-wenn-regler)) |
| `AllocationChart` | ~80 | Gestapelte Vermögensaufteilung je Phase; aus dem Dashboard herausgelöst, damit die Live-Simulation sie mitbenutzen kann |
| `SensitivityDialog` | ~330 | Einflussfaktoren: Erklärung, Treiber-Auswahl mit Bandbreiten, Tornado + Tabelle |
@@ -2838,7 +2943,34 @@ Akzeptiert eine **Union** von zwei Formen:
→ 201 `{ scenarioId: "<neue Id>" }`
### `GET /api/scenarios/<scenarioId>/export`
`text/csv; charset=utf-8`, `Content-Disposition: attachment`.
`text/csv; charset=utf-8`, `Content-Disposition: attachment`. Liefert immer den **aktuellen**
Stand, auch wenn im Client eine ältere Version betrachtet wird ([3.8.4](#384-analyse-auf-einer-bestimmten-version)).
### `GET /api/scenarios/<scenarioId>/versions`
Änderungshistorie, neueste zuerst **ohne** die Snapshots (je zig Kilobyte).
```json
{ "currentMajor": 2,
"versions": [ { "id", "major", "minor", "comment", "isMajor",
"createdAt", "updatedAt", "author" } ] }
```
### `POST /api/scenarios/<scenarioId>/versions`
`{ comment }` (Pflicht, ≥ 3 Zeichen) legt den aktuellen Stand als **Hauptversion** fest und
setzt `Scenario.currentMajor`. → 201 `{ version: { major, minor } }` · 400 ohne Kommentar.
### `GET /api/scenarios/<scenarioId>/versions/<versionId>`
Ein einzelner Stand samt Berechnung und der Vorwarnung für das Wiederherstellen:
```json
{ "version": { "id", "major", "minor", "comment", "isMajor", "createdAt" },
"plan": <PlanInput>, "computed": <PlanComputed>,
"impact": { "lostElementIds": [], "lostPhaseIds": [], "affectedChildren": [] } }
```
`impact` benennt die Kind-Szenarien, die durch ein Wiederherstellen ihre Diff-Basis verlören.
### `POST /api/scenarios/<scenarioId>/versions/<versionId>`
Setzt das Szenario auf diesen Stand zurück **IDs bleiben erhalten**, und der wiederhergestellte
Stand wird selbst als neue Version festgehalten («Wiederhergestellt aus A.B»).
→ 200 `{ version: { major, minor } }`
## 6.4 Phasen
@@ -2949,12 +3081,14 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
| `explain.test.ts` | 12 | Verlaufswerte je Element, Vollständigkeit beider Wasserfall-Zerlegungen, Rechenweg-Protokoll, Gültigkeit der Spezifikations-Verweise |
| `montecarlo.test.ts` | 18 | Determinismus, Volatilität/Vol-Drag, Böden, Reproduzierbarkeit; Element-Gruppierung, Szenario-Vergleich, Inflation je Szenario, plannedReturnOf; Schwellen-Ablesung (probabilityAtLeast), Monotonie über Schwellen, Fall 2 systematisch unter 50 % |
| `distribution.test.ts` | 8 | Kapitaltopf und Quoten-Zerlegung gegen die Cash-Brücke; Entwurfswerte anwenden ohne Verlust bestehender Felder |
| `versioning.test.ts` | 22 | Nummerierung und Zusammenfassung je Sitzung, unveränderte Stände, Benutzertrennung, Bauplan des Wiederherstellens, Auswirkung auf Kind-Szenarien |
| `versioning-coverage.test.ts` | 3 | statischer Wächter: jeder schreibende Endpunkt löst eine Version aus |
| `livesim.test.ts` | 15 | Element-Regler bewegt genau ein Element; Aufschlüsselung deckt sich mit dem Sammelregler; neutrale Stellung verändert den Plan nicht; Ruinmeldung |
| `phaseplan.test.ts` | 8 | Ableitung der Lebensabschnitte aus den Pensionierungszeitpunkten (Einzel/Paar/bereits pensioniert); letzter Teil immer offen |
| `bridges.test.ts` | 10 | Vermögens- und Cash-Brücke gehen über sieben Plankonstellationen ohne Restgrösse auf; Umbuchungen bleiben aus der Vermögensbrücke heraus |
| `diff.test.ts` | 9 | Abweichungs-Erkennung gegen das Eltern-Szenario |
| `migrations.test.ts` | 1 | spielt alle Migrationen gegen echtes PostgreSQL (PGlite) ein |
| **Total** | **139** | |
| **Total** | **164** | |
## 8.2 Testfälle
@@ -3416,6 +3550,39 @@ neben jedem Regler steht sein **Neutralpunkt** der Wert, bei dem der Plan un
Bei allen Verschiebungs-Treibern ist das 0, bei der Inflation der Planwert selbst, weil sie als
einzige absolut und nicht als Differenz eingegeben wird.
## 9.28 Was die Versionierung nicht leistet
**Das Zeitfenster ist eine Konvention, keine Wahrheit.** Zehn Minuten sind gesetzt, weil FPT
keinen Speichern-Knopf hat und der Nutzer den Schnitt sonst nie selbst zieht
([3.8.1](#381-eine-version-je-sitzung--nicht-je-änderung)). Wer nach acht Minuten Pause
weiterarbeitet, landet in derselben Version; wer nach zwölf Minuten eine Kleinigkeit ändert,
bekommt eine neue. Beides ist gelegentlich nicht das, was man gemeint hätte. Ein
Speichern-Knopf wäre die exaktere Lösung, würde aber die Bedienung des ganzen Werkzeugs
umkrempeln.
**Eine Nebenversion hält den Stand am ENDE der Sitzung fest**, nicht jeden Zwischenschritt
darin. Wer innerhalb einer Sitzung etwas ändert und wieder zurücknimmt, findet den
Zwischenstand nirgends. Die Historie ist eine Folge von Arbeitsständen, kein Undo.
**Es gibt keinen Versionsvergleich.** Zwei Stände lassen sich nur nacheinander ansehen, nicht
nebeneinander stellen. Der bestehende Diff ([3.2.6](#326-abweichungs-markierung-diff))
vergleicht Szenarien gegen ihr Eltern-Szenario, nicht Versionen gegeneinander technisch
wäre beides verwandt, aber es ist bewusst nicht Teil dieser Stufe.
**Die Historie wird nie beschnitten.** Jede Version hält den vollständigen Zustand als JSON
(Grössenordnung 40 KB bei sechs Phasen und zwölf Elementen). Bei intensiver Nutzung wächst
das linear; eine Aufräumregel (etwa: Nebenversionen älter als ein Jahr verdichten, Haupt-
versionen behalten) gibt es noch nicht. Bei den heutigen Datenmengen ist das unkritisch.
**«Wer» unterscheidet heute nichts.** Ein Plan gehört genau einem Benutzer, es gibt keine
Freigabe und keine Rollen die Spalte zeigt also immer denselben Namen. Sie ist Vorbereitung
auf den Finanzberater, und die Logik trennt Benutzer bereits sauber (Änderungen verschiedener
Benutzer werden nie in einer Version zusammengefasst).
**Der Plan selbst ist nicht versioniert**, nur seine Szenarien. Wird ein Szenario gelöscht,
verschwindet seine Historie mit ihm (Cascade). Das ist gewollt: Eine Historie ohne das Objekt,
das sie beschreibt, wäre nicht wiederherstellbar.
---
# 10. Glossar