Der Preis eines Knotens ist seine angegebene (oder D66-geschätzte) Größe; die frühere Rekursion (eigene Größe plus Summe/Minimum der Kinder) zählte doppelt und bestrafte sorgfältig zerlegte Pakete — eine zerlegte S-Alternative verlor gegen ein grobes L. Das Vereinigungs-Maß der Closure-Suche (D42) zählt je Knoten nur noch die Marge über seine nötigen Teilpakete hinaus. Mitgelieferter Plan wählt identisch (per Vergleich gemessen); SPEC §9 und llms.md nachgezogen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
3.2 KiB
JavaScript
91 lines
3.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { parse } from '../src/parser.js';
|
|
import { cheapestCost, computeCheapSet, computeCheapPlan } from '../src/model.js';
|
|
|
|
/* Die Größe bepreist den ganzen Teilbaum (SPEC §9, D69): Der Preis eines
|
|
Knotens ist seine angegebene (oder nach D66 geschätzte) Größe — die
|
|
Teilpakete kommen nicht noch einmal obendrauf. Ob sie hineinpassen, prüft
|
|
der Größen-Konflikt (§5/D62); die Pfadrechnung zweifelt die Bewertung
|
|
nicht an. */
|
|
|
|
const roots = txt => parse(txt).roots;
|
|
const cheapLabels = txt => {
|
|
const r = roots(txt);
|
|
return [...computeCheapSet(r)].map(n => n.label).sort();
|
|
};
|
|
|
|
describe('Die Größe bepreist den Teilbaum (D69)', () => {
|
|
it('der gemeldete Fall: die zerlegte S-Alternative schlägt das L', () => {
|
|
/* Vorher: Manuell = S(2) + XS(1) + S(2) = 5 > Failover L(4) — die
|
|
sorgfältige Zerlegung wurde bestraft. Jetzt zählt die Bewertung:
|
|
S(2) < L(4). */
|
|
const txt = `- kc
|
|
- Verfügbarkeit
|
|
| Manuell mit Downtime (S)
|
|
- Maintainance Mode (XS)
|
|
- Recovery Plan (S)
|
|
+ Seite Wartungsarbeiten (S)
|
|
| Failover (L)
|
|
| HA (XXL)`;
|
|
const labels = cheapLabels(txt);
|
|
expect(labels).toContain('Manuell mit Downtime');
|
|
expect(labels).not.toContain('Failover');
|
|
});
|
|
|
|
it('der Preis eines bewerteten Knotens ist seine Größe, Kinder egal', () => {
|
|
const [n] = roots(`- [ ] Paket (S)
|
|
- [ ] A (XS)
|
|
- [ ] B (S)`);
|
|
expect(cheapestCost(n)).toBe(2);
|
|
});
|
|
|
|
it('die Bewertung gilt auch, wenn die Kinder zu groß sind (D62 warnt dann)', () => {
|
|
/* Vier S unter einem S sind ein sizeConflict — der Fehlermarker meldet
|
|
es, aber der Knoten bleibt bewertet, wie er bewertet wurde. */
|
|
const txt = `- [ ] Wahl (XS)
|
|
| [ ] A (S)
|
|
- [ ] a1 (S)
|
|
- [ ] a2 (S)
|
|
- [ ] a3 (S)
|
|
- [ ] a4 (S)
|
|
| [ ] B (M)`;
|
|
expect(parse(txt).warnings.some(w => w.type === 'sizeConflict')).toBe(true);
|
|
const labels = cheapLabels(txt);
|
|
expect(labels).toContain('A');
|
|
expect(labels).not.toContain('B');
|
|
});
|
|
|
|
it('ohne Größe vertritt die D66-Schätzung die Bewertung', () => {
|
|
/* Alternative ohne Größe mit einem L-Kind wird als L geschätzt und
|
|
verliert gegen das bewertete M. */
|
|
const labels = cheapLabels(`- [ ] Wahl (XS)
|
|
| [ ] Unbewertet
|
|
- [ ] Kind (L)
|
|
| [ ] Bewertet (M)`);
|
|
expect(labels).toContain('Bewertet');
|
|
expect(labels).not.toContain('Unbewertet');
|
|
});
|
|
|
|
it('das Vereinigungs-Maß bestraft Zerlegungstiefe nicht (D42/D69)', () => {
|
|
/* Beide Alternativen ziehen je ein M-schweres Ziel: A ein zerlegtes
|
|
(M mit zwei S-Kindern), B ein grobes (L). Über die Marginal-Summe
|
|
wiegen beide 4 — Gleichstand, die erste Alternative (A) gewinnt.
|
|
Die frühere Knoten-Summe zählte das zerlegte Ziel mit 3+2+2 = 7 und
|
|
ließe B gewinnen. */
|
|
const txt = `- [ ] Wurzel (XS)
|
|
- [ ] Wahl (XS)
|
|
| [ ] A (S) :#t1
|
|
| [ ] B (S) :#t2
|
|
+ [ ] Extras (XS)
|
|
- [ ] #t1: Zerlegt (M)
|
|
- [ ] t1a (S)
|
|
- [ ] t1b (S)
|
|
- [ ] #t2: Grob (L)`;
|
|
const { set, exact } = computeCheapPlan(roots(txt));
|
|
const labels = [...set].map(n => n.label);
|
|
expect(exact).toBe(true);
|
|
expect(labels).toContain('A');
|
|
expect(labels).not.toContain('B');
|
|
});
|
|
});
|