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
@@ -120,6 +120,10 @@ One node per line. Everything except the label is optional.
|
||||
the conflict check below, except unsized children count too — estimated
|
||||
recursively); three or more children at that largest size raise it by one
|
||||
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
|
||||
this one check each size is read as a range (lower bounds double, XXL is
|
||||
open-ended):
|
||||
|
||||
+22
-3
@@ -99,19 +99,38 @@ export function assumedSize(n){
|
||||
if(n.size) return n.size;
|
||||
const memo = ASSUMED.get(n);
|
||||
if(memo) return memo;
|
||||
const kids = n.children.filter(k =>
|
||||
const counting = n.children.filter(k =>
|
||||
!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';
|
||||
if(kids.length){
|
||||
if(gateOf(kids) !== 'and'){
|
||||
if(gateOf(counting) !== 'and'){
|
||||
/* 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 {
|
||||
const ranks = kids.map(k => SIZE_RANK[assumedSize(k)]);
|
||||
const max = Math.max(...ranks);
|
||||
const atMax = ranks.filter(r => r === max).length;
|
||||
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);
|
||||
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', () => {
|
||||
it('ownCost eines größenlosen Elternknotens folgt der Schätzung', () => {
|
||||
const [n] = roots(`[ ] Eltern
|
||||
|
||||
Reference in New Issue
Block a user