Uebersicht der offenen Punkte statt Assistent, Bestaetigung je Phasenzelle
Deploy App / deploy (push) Successful in 1m55s
Deploy App / deploy (push) Successful in 1m55s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
// Was ist in diesem Plan noch offen?
|
||||
//
|
||||
// Ersetzt den FPT-Assistenten. Der Unterschied ist nicht die Groesse, sondern der Charakter:
|
||||
// Der Assistent war ein ABLAUF ("tu dies, dann das") und funktionierte nur beim ersten
|
||||
// Aufsetzen -- wer einen bestehenden Plan oeffnete, bekam Schritte angeboten, die laengst
|
||||
// erledigt waren. Diese Uebersicht ist ein ZUSTAND. Sie leitet ab, was tatsaechlich fehlt,
|
||||
// und traegt damit bei jedem Plan, in jeder Reihenfolge, auch beim zwanzigsten Szenario.
|
||||
//
|
||||
// Der Mechanismus dahinter ist derselbe, den die Uebergaenge seit 0.35 haben, jetzt auf
|
||||
// PHASENWERTE ausgeweitet: Eine neue Lebensphase uebernimmt alle Werte der Vorphase, aber sie
|
||||
// gelten als unbestaetigt, bis jemand hingeschaut hat. Vorbelegen ja, stillschweigend
|
||||
// uebernehmen nein.
|
||||
//
|
||||
// WICHTIG: Bestaetigen heisst "ich habe hingeschaut", NICHT "festnageln". Der Haken steht
|
||||
// neben den Werten, er kopiert sie nicht in die Phase -- sonst waere jede bestaetigte Phase
|
||||
// eingefroren und die Feld-Vererbung (SPEZIFIKATION 3.12.4) waere hinfaellig.
|
||||
|
||||
import { openTransitionCount, type DecisionCounts } from "@/lib/decisions";
|
||||
import type { PlanComputed } from "@/lib/calculations";
|
||||
import type { PlanInput } from "@/lib/types";
|
||||
|
||||
// Wo steht der Plan insgesamt? Die ersten beiden Zustaende sind KEINE Zaehlung offener
|
||||
// Punkte -- ohne Elemente und ohne Phasen gibt es naturgemaess nichts Offenes, der Plan ist
|
||||
// aber trotzdem leer. Diese beiden Faelle brauchen eine Aufforderung, keine Statistik.
|
||||
export type ReviewStage = "NO_ELEMENTS" | "NO_PHASES" | "OPEN" | "DONE";
|
||||
|
||||
export interface ReviewGroup {
|
||||
// Sprungziel: Phasen-Id bzw. die Von-Phase eines Uebergangs.
|
||||
id: string;
|
||||
kind: "phase" | "transition";
|
||||
title: string;
|
||||
open: number;
|
||||
// Kurze Klartexte, was genau fehlt.
|
||||
reasons: string[];
|
||||
}
|
||||
|
||||
export interface PlanReview {
|
||||
stage: ReviewStage;
|
||||
total: number;
|
||||
groups: ReviewGroup[];
|
||||
}
|
||||
|
||||
// Braucht dieses Element in dieser Phase eine Bestaetigung? Alles, was in der Phase AKTIV ist:
|
||||
// Ein verkauftes Haus oder eine getilgte Schuld traegt keine Annahmen mehr.
|
||||
export function needsConfirmation(
|
||||
computed: PlanComputed,
|
||||
phaseId: string,
|
||||
elementId: string
|
||||
): boolean {
|
||||
const ph = computed.phases.find((p) => p.id === phaseId);
|
||||
const ce = ph?.elements.find((e) => e.elementId === elementId);
|
||||
return !!ce && ce.status === "ACTIVE";
|
||||
}
|
||||
|
||||
// Ist der Phasenwert dieses Elements bestaetigt?
|
||||
export function isCellConfirmed(plan: PlanInput, phaseId: string, elementId: string): boolean {
|
||||
return plan.elements.find((e) => e.id === elementId)?.phaseValues[phaseId]?.confirmed === true;
|
||||
}
|
||||
|
||||
// Unbestaetigte Zellen einer Phase.
|
||||
export function unconfirmedCells(plan: PlanInput, computed: PlanComputed, phaseId: string): string[] {
|
||||
return plan.elements
|
||||
.filter((e) => needsConfirmation(computed, phaseId, e.id) && !isCellConfirmed(plan, phaseId, e.id))
|
||||
.map((e) => e.id);
|
||||
}
|
||||
|
||||
export function reviewPlan(plan: PlanInput, computed: PlanComputed): PlanReview {
|
||||
if (plan.elements.length === 0) return { stage: "NO_ELEMENTS", total: 0, groups: [] };
|
||||
if (computed.phases.length === 0) return { stage: "NO_PHASES", total: 0, groups: [] };
|
||||
|
||||
const groups: ReviewGroup[] = [];
|
||||
|
||||
computed.phases.forEach((ph, i) => {
|
||||
const reasons: string[] = [];
|
||||
let open = 0;
|
||||
|
||||
// 1. Annahmen je Element -- Renditen, Lohnentwicklung, Teuerung, Zins.
|
||||
const cells = unconfirmedCells(plan, computed, ph.id);
|
||||
if (cells.length > 0) {
|
||||
open += cells.length;
|
||||
reasons.push(`${cells.length} ${cells.length === 1 ? "Annahme" : "Annahmen"} nicht bestätigt`);
|
||||
}
|
||||
|
||||
// 2. Die Spar- bzw. Verzehrquote. Zaehlt eigens: Man kann jede Zelle angeschaut haben und
|
||||
// die Verteilung trotzdem nie getroffen haben -- dann bleibt alles still auf dem Cash.
|
||||
const phase = plan.phases.find((p) => p.id === ph.id);
|
||||
if (phase && phase.ratesConfirmed !== true) {
|
||||
open += 1;
|
||||
reasons.push(ph.isConsumption ? "Bezüge nicht verteilt" : "Sparquote nicht verteilt");
|
||||
}
|
||||
|
||||
if (open > 0) groups.push({ id: ph.id, kind: "phase", title: ph.name, open, reasons });
|
||||
|
||||
// 3. Der Uebergang NACH dieser Phase.
|
||||
const next = computed.phases[i + 1];
|
||||
if (!next) return;
|
||||
const counts: DecisionCounts = openTransitionCount(plan, computed, ph, next);
|
||||
const tOpen = counts.open + counts.unconfirmed;
|
||||
if (tOpen > 0) {
|
||||
const r: string[] = [];
|
||||
if (counts.open > 0) r.push(`${counts.open} ${counts.open === 1 ? "Entscheid" : "Entscheide"} offen`);
|
||||
if (counts.unconfirmed > 0)
|
||||
r.push(`${counts.unconfirmed} ${counts.unconfirmed === 1 ? "Vorgabe" : "Vorgaben"} ungeprüft`);
|
||||
groups.push({
|
||||
id: ph.id,
|
||||
kind: "transition",
|
||||
title: `Übergang nach ${next.name}`,
|
||||
open: tOpen,
|
||||
reasons: r,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const total = groups.reduce((n, g) => n + g.open, 0);
|
||||
return { stage: total === 0 ? "DONE" : "OPEN", total, groups };
|
||||
}
|
||||
Reference in New Issue
Block a user