feat: Die geschätzte Größe bepreist den Rest — Erledigtes fällt aus der Schätzung (D70)
Die D66-Schätzung war status-blind: Ein größenloses Paket mit [x] (L) und [ ] (S) wurde als L geschätzt, obwohl nur S offen ist. Erledigte Kinder fallen jetzt heraus, eine erledigte realisierte Alternative stellt ihre Gruppe fertig, und sind alle benannten Kinder erledigt, gilt XS (der eigene Rest — M-Rückfall erhöhte den Preis beim Fertigwerden). Angegebene Größen bleiben unangetastet (D69). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4d80feb4e8
commit
0de7f5f24e
@@ -19,6 +19,7 @@ reverse.
|
|||||||
|
|
||||||
## 2026-08-25
|
## 2026-08-25
|
||||||
|
|
||||||
|
- The estimated size of an unsized node now prices the remaining work — done sub-packages drop out, and a subtree with everything named done counts as XS
|
||||||
- The fold chip is solid white with an ink glyph now — the translucent box vanished on the pastel node colours
|
- The fold chip is solid white with an ink glyph now — the translucent box vanished on the pastel node colours
|
||||||
- A size prices its whole subtree on the cheapest path — sub-packages no longer add on top, so a carefully decomposed `(S)` beats a coarse `(L)`
|
- A size prices its whole subtree on the cheapest path — sub-packages no longer add on top, so a carefully decomposed `(S)` beats a coarse `(L)`
|
||||||
- The size is now the last free-standing `(L)`-style token of the line — earlier ones stay in the title, and `"(L)"` or `((L))` mention a size literally
|
- The size is now the last free-standing `(L)`-style token of the line — earlier ones stay in the title, and `"(L)"` or `((L))` mention a size literally
|
||||||
|
|||||||
@@ -5170,3 +5170,52 @@ fallen; Vereinigungs-Maß auf schlichte Summe zurück → genau der eine
|
|||||||
Marginal-Test (dessen erste Fassung nicht unterschied, weil Wurzeln immer
|
Marginal-Test (dessen erste Fassung nicht unterschied, weil Wurzeln immer
|
||||||
nötig sind — der Testbaum musste die Ziele unter einen unangetasteten
|
nötig sind — der Testbaum musste die Ziele unter einen unangetasteten
|
||||||
`+`-Zweig legen).
|
`+`-Zweig legen).
|
||||||
|
|
||||||
|
## D70 — Die geschätzte Größe bepreist den Rest: Erledigtes fällt aus der Schätzung
|
||||||
|
Nachgefragt vom Nutzer zum benannten D69-Verlust (erledigte Teilpakete
|
||||||
|
senken den Preis eines offenen Knotens nicht mehr): „Wenn der Überknoten
|
||||||
|
keine explizite, sondern nur eine implizit berechnete Größe hat, dann ist
|
||||||
|
das doch kein Problem?" Die Prüfung ergab: **Es war eines** — die
|
||||||
|
D66-Schätzung war status-blind. Ein größenloses Paket mit `[x] (L)` und
|
||||||
|
`[ ] (S)` wurde als L geschätzt (Preis 4), obwohl nur noch S offen ist.
|
||||||
|
|
||||||
|
**Die Unterscheidung des Nutzers ist genau die tragfähige Linie:** Eine
|
||||||
|
**angegebene** Größe ist die Aussage des Autors — D69 erklärt sie für
|
||||||
|
maßgeblich, dort bleibt der Verlust bewusst bestehen (anteilige Restkosten
|
||||||
|
hat D46 verworfen). Die **geschätzte** Größe ist dagegen eine Kostenannahme
|
||||||
|
des Werkzeugs, und der Pfad fragt seit D46 „was ist noch offen?" — eine
|
||||||
|
Annahme, die Erledigtes einpreist, beantwortet die falsche Frage.
|
||||||
|
|
||||||
|
Die Regeln:
|
||||||
|
|
||||||
|
- **Erledigte Kinder (`[x]`/`[^]`, intrinsisch) fallen aus der Schätzung**
|
||||||
|
wie verworfene — geschätzt wird die noch offene Arbeit, auch für die
|
||||||
|
Stufe-ab-drei-Regel zählen nur die offenen.
|
||||||
|
- **Disjunktiv stellt eine erledigte realisierte Alternative die Gruppe
|
||||||
|
fertig.** Kein eigener Sonderfall, sondern die Wahl des Pfads
|
||||||
|
nachvollzogen: Unter mehreren realisierten entscheidet die Kostenregel
|
||||||
|
(D61), und die erledigte kostet 0 — sie würde gewählt, die Gruppe trägt
|
||||||
|
nichts mehr bei. Das gilt auch neben einer angefangenen zweiten
|
||||||
|
Alternative.
|
||||||
|
- **Alles Benannte erledigt, der Knoten selbst offen → `XS`**, nicht der
|
||||||
|
M-Rückfall. Die Restarbeit ist dann seine eigene Abschlussarbeit (er ist
|
||||||
|
die Station, D46) — und der M-Rückfall erzeugte eine Absurdität: Das
|
||||||
|
Fertigstellen des letzten S-Kindes *erhöbe* den Preis von S auf M. Die
|
||||||
|
Schätzung muss beim Fertigwerden monoton sinken; XS ist der Boden der
|
||||||
|
Skala. Nur der echte Blattknoten ohne Kinder bleibt beim M-Rückfall —
|
||||||
|
dort gibt es keine Information.
|
||||||
|
|
||||||
|
**Sichtbare Nebenwirkung, gewollt:** Das invertierte Größen-Badge zeigt an
|
||||||
|
solchen Knoten die **Rest**-Schätzung. Der Tooltip sagt ohnehin „für die
|
||||||
|
Kostenschätzung mindestens {size} angenommen" — die Aussage bleibt wahr.
|
||||||
|
Größen-Konflikt (D62) und Falt-Voreinstellung (D44) arbeiten nur mit
|
||||||
|
angegebenen Größen und bleiben unberührt.
|
||||||
|
|
||||||
|
**Nachgemessen:** 457 Tests, davon 7 neue in `tests/assumed.test.js`
|
||||||
|
(darunter die Monotonie und die Zusicherung, dass eine ANGEGEBENE Größe
|
||||||
|
trotz erledigter Kinder stehen bleibt). Gegenproben per Mutation:
|
||||||
|
Done-Filter entfernt → genau die drei danach benannten Zusicherungen
|
||||||
|
fallen; XS-Boden auf M zurück → genau die drei XS-Tests; „Gruppe fertig"
|
||||||
|
entfernt → genau die zwei disjunktiven. Der mitgelieferte Plan ist
|
||||||
|
unberührt (sein einziger größenloser Knoten ist ein Blatt): weiterhin
|
||||||
|
0 Warnungen, 137 Pfadknoten, 57 Stationen, exakt gerechnet.
|
||||||
|
|||||||
@@ -648,6 +648,16 @@ werden die für die günstigste Realisierung **nötigen** Knoten:
|
|||||||
Größe zählen hier mit, ihre Größe wird nach derselben Regel **rekursiv**
|
Größe zählen hier mit, ihre Größe wird nach derselben Regel **rekursiv**
|
||||||
mitgeschätzt — geschätzt wird ohnehin. Ein Knoten ohne Größe und ohne
|
mitgeschätzt — geschätzt wird ohnehin. Ein Knoten ohne Größe und ohne
|
||||||
zählende Kinder wird weiterhin als `M` gewertet.
|
zählende Kinder wird weiterhin als `M` gewertet.
|
||||||
|
**Erledigte Kinder zählen nicht mit** (D70): Die Schätzung ist eine
|
||||||
|
Kostenannahme des Werkzeugs, keine Autoren-Aussage — anders als eine
|
||||||
|
angegebene Größe (die bleibt, wie sie geschrieben ist) schätzt sie die
|
||||||
|
noch **offene** Arbeit. Kinder mit `[x]`/`[^]` (intrinsisch) fallen
|
||||||
|
heraus wie verworfene; in einer disjunktiven Gruppe stellt eine
|
||||||
|
**erledigte** realisierte Alternative die Gruppe fertig — sie trägt
|
||||||
|
nichts mehr bei. Gibt es zählende Kinder, sind aber **alle** erledigt,
|
||||||
|
wird **`XS`** angenommen: Die Restarbeit ist dann die eigene des
|
||||||
|
Elternknotens (er ist die Station, oben) — ein `M`-Rückfall hieße, dass
|
||||||
|
das Fertigstellen des letzten Kindes den Preis *erhöht*.
|
||||||
|
|
||||||
**Mit Abhängigkeiten (§1) zählt die Dependency Closure.** Jeder nötige Knoten
|
**Mit Abhängigkeiten (§1) zählt die Dependency Closure.** Jeder nötige Knoten
|
||||||
zieht seine `:#…`-Ziele samt deren Realisierung in die nötige Menge; gemeinsam
|
zieht seine `:#…`-Ziele samt deren Realisierung in die nötige Menge; gemeinsam
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ One node per line. Everything except the label is optional.
|
|||||||
the conflict check below, except unsized children count too — estimated
|
the conflict check below, except unsized children count too — estimated
|
||||||
recursively); three or more children at that largest size raise it by one
|
recursively); three or more children at that largest size raise it by one
|
||||||
step, capped at `XXL`. Without counting children it stays `M`.
|
step, capped at `XXL`. Without counting children it stays `M`.
|
||||||
|
The estimate prices the **remaining** work: done children (`[x]`/`[^]`)
|
||||||
|
drop out, and a done realized alternative completes its `|`/`=` group.
|
||||||
|
When counting children exist but all are done, `XS` is assumed — the rest
|
||||||
|
is the parent's own wrap-up. A stated size is never adjusted this way.
|
||||||
- **Size conflict check:** a given size must fit the direct children. For
|
- **Size conflict check:** a given size must fit the direct children. For
|
||||||
this one check each size is read as a range (lower bounds double, XXL is
|
this one check each size is read as a range (lower bounds double, XXL is
|
||||||
open-ended):
|
open-ended):
|
||||||
|
|||||||
+22
-3
@@ -99,19 +99,38 @@ export function assumedSize(n){
|
|||||||
if(n.size) return n.size;
|
if(n.size) return n.size;
|
||||||
const memo = ASSUMED.get(n);
|
const memo = ASSUMED.get(n);
|
||||||
if(memo) return memo;
|
if(memo) return memo;
|
||||||
const kids = n.children.filter(k =>
|
const counting = n.children.filter(k =>
|
||||||
!k.optional && (!k.status || k.status.key !== 'verworfen'));
|
!k.optional && (!k.status || k.status.key !== 'verworfen'));
|
||||||
|
/* Erledigte Kinder zählen nicht mit (D70): Die Schätzung ist eine
|
||||||
|
Kostenannahme, keine Autoren-Aussage — sie schätzt die noch OFFENE
|
||||||
|
Arbeit (D46). Eine ANGEGEBENE Größe bleibt dagegen, wie sie geschrieben
|
||||||
|
ist (D69). In einer disjunktiven Gruppe stellt eine erledigte
|
||||||
|
realisierte Alternative die Gruppe fertig — genau die würde der Pfad
|
||||||
|
wählen (Kosten 0), die Gruppe trägt also nichts mehr bei. */
|
||||||
|
let kids;
|
||||||
|
if(counting.length && gateOf(counting) !== 'and'){
|
||||||
|
const pool = chosenPool(counting);
|
||||||
|
kids = pool.some(isDone) ? [] : pool;
|
||||||
|
} else {
|
||||||
|
kids = counting.filter(k => !isDone(k));
|
||||||
|
}
|
||||||
let size = 'M';
|
let size = 'M';
|
||||||
if(kids.length){
|
if(kids.length){
|
||||||
if(gateOf(kids) !== 'and'){
|
if(gateOf(counting) !== 'and'){
|
||||||
/* nur eine Alternative wird realisiert — die kleinste ist der Boden */
|
/* nur eine Alternative wird realisiert — die kleinste ist der Boden */
|
||||||
size = SIZE_BY_RANK[Math.min(...chosenPool(kids).map(k => SIZE_RANK[assumedSize(k)]))];
|
size = SIZE_BY_RANK[Math.min(...kids.map(k => SIZE_RANK[assumedSize(k)]))];
|
||||||
} else {
|
} else {
|
||||||
const ranks = kids.map(k => SIZE_RANK[assumedSize(k)]);
|
const ranks = kids.map(k => SIZE_RANK[assumedSize(k)]);
|
||||||
const max = Math.max(...ranks);
|
const max = Math.max(...ranks);
|
||||||
const atMax = ranks.filter(r => r === max).length;
|
const atMax = ranks.filter(r => r === max).length;
|
||||||
size = SIZE_BY_RANK[Math.min(max + (atMax >= 3 ? 1 : 0), SIZE_RANK.XXL)];
|
size = SIZE_BY_RANK[Math.min(max + (atMax >= 3 ? 1 : 0), SIZE_RANK.XXL)];
|
||||||
}
|
}
|
||||||
|
} else if(counting.length){
|
||||||
|
/* Alles Benannte ist erledigt, der Knoten selbst nicht: Der Rest ist
|
||||||
|
seine eigene Abschlussarbeit (er ist die Station, D46) — XS statt des
|
||||||
|
M-Rückfalls, sonst ERHÖHTE das Fertigstellen des letzten Kindes den
|
||||||
|
Preis. Nur der echte Blattknoten ohne Kinder bleibt bei M. */
|
||||||
|
size = 'XS';
|
||||||
}
|
}
|
||||||
ASSUMED.set(n, size);
|
ASSUMED.set(n, size);
|
||||||
return size;
|
return size;
|
||||||
|
|||||||
@@ -115,6 +115,60 @@ describe('assumedSize — Schätzung aus den Teilpaketen (D66)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('die Schätzung zählt nur offene Kinder (D70)', () => {
|
||||||
|
it('erledigte Kinder fallen heraus — geschätzt wird der Rest', () => {
|
||||||
|
const [n] = roots(`[ ] Paket
|
||||||
|
- [x] Großteil (L)
|
||||||
|
- [ ] Rest (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('die Stufe ab drei zählt nur die offenen', () => {
|
||||||
|
const [n] = roots(`[ ] Paket
|
||||||
|
- [x] A (S)
|
||||||
|
- [x] B (S)
|
||||||
|
- [ ] C (S)
|
||||||
|
- [ ] D (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('alles Benannte erledigt: XS — der eigene Rest, nicht der M-Rückfall', () => {
|
||||||
|
/* Mit M-Rückfall ERHÖHTE das Fertigstellen des letzten Kindes den
|
||||||
|
Preis von S auf M — die Schätzung muss monoton sinken. */
|
||||||
|
const [n] = roots(`[ ] Paket
|
||||||
|
- [x] A (S)
|
||||||
|
- [x] B (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('XS');
|
||||||
|
expect(ownCost(n)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ein echtes Blatt ohne Kinder bleibt beim M-Rückfall', () => {
|
||||||
|
expect(assumedSize(roots('[ ] Blatt')[0])).toBe('M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disjunktiv: eine erledigte realisierte Alternative stellt die Gruppe fertig', () => {
|
||||||
|
const [n] = roots(`[ ] Paket
|
||||||
|
| [x] Gemacht (S)
|
||||||
|
| [ ] Teuer (M)`);
|
||||||
|
expect(assumedSize(n)).toBe('XS');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disjunktiv: erledigt neben angefangen — die Gruppe trägt nichts mehr bei', () => {
|
||||||
|
/* Der Pfad wählte unter den realisierten die erledigte (Kosten 0, D61) —
|
||||||
|
die Schätzung folgt derselben Wahl. */
|
||||||
|
const [n] = roots(`[ ] Paket
|
||||||
|
| [x] Fertig (L)
|
||||||
|
| [~] Angefangen (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('XS');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('eine ANGEGEBENE Größe bleibt trotz erledigter Kinder stehen (D69)', () => {
|
||||||
|
const [n] = roots(`[ ] Paket (L)
|
||||||
|
- [x] Großteil (L)`);
|
||||||
|
expect(assumedSize(n)).toBe('L');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('ownCost und Pfad rechnen mit der Schätzung', () => {
|
describe('ownCost und Pfad rechnen mit der Schätzung', () => {
|
||||||
it('ownCost eines größenlosen Elternknotens folgt der Schätzung', () => {
|
it('ownCost eines größenlosen Elternknotens folgt der Schätzung', () => {
|
||||||
const [n] = roots(`[ ] Eltern
|
const [n] = roots(`[ ] Eltern
|
||||||
|
|||||||
Reference in New Issue
Block a user