feat: Die Größe bepreist den ganzen Teilbaum — Kinder zählen nicht obendrauf (D69)
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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7278e74393
commit
4aaa19becd
@@ -19,6 +19,7 @@ reverse.
|
|||||||
|
|
||||||
## 2026-08-25
|
## 2026-08-25
|
||||||
|
|
||||||
|
- 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
|
||||||
- Fix: Ctrl+click on a dependency preceded by a non-breaking space did nothing, although the parser reads it as a dependency
|
- Fix: Ctrl+click on a dependency preceded by a non-breaking space did nothing, although the parser reads it as a dependency
|
||||||
- Ctrl+click a dependency `:#id` in the text jumps to the line that defines the ID — Ctrl+Enter does the same at the caret
|
- Ctrl+click a dependency `:#id` in the text jumps to the line that defines the ID — Ctrl+Enter does the same at the caret
|
||||||
|
|||||||
@@ -5098,3 +5098,65 @@ selben Zug (Schritt-3-Regel, Größen-Abschnitt, Zitier-Faustregel).
|
|||||||
per Mutation: erster statt letzter Treffer → genau der Letztes-gewinnt-Test
|
per Mutation: erster statt letzter Treffer → genau der Letztes-gewinnt-Test
|
||||||
fällt; Anker entfernt → genau die zwei Alleinstehend-/Zitier-Tests. Alle
|
fällt; Anker entfernt → genau die zwei Alleinstehend-/Zitier-Tests. Alle
|
||||||
Snapshots (kanonisches Beispiel §10) unverändert.
|
Snapshots (kanonisches Beispiel §10) unverändert.
|
||||||
|
|
||||||
|
## D69 — Die Größe bepreist den ganzen Teilbaum: Kinder kommen nicht obendrauf
|
||||||
|
Gemeldet am eigenen Beispiel: Eine `|`-Gruppe mit `Manuell mit Downtime (S)`
|
||||||
|
(zerlegt in XS + S + eine Zugabe) verlor gegen `Failover (L)` — der Pfad
|
||||||
|
rechnete S(2) + XS(1) + S(2) = 5 gegen L(4). Der Einwand des Nutzers trifft
|
||||||
|
das Modell im Kern: **„Manuell mit Downtime" ist mit (S) bepreist — dann ist
|
||||||
|
egal, wie teuer die Summe der Teilpakete ist.** Ob die Zerlegung in die
|
||||||
|
Größe passt, prüft seit D62 der Größen-Konflikt; die Pfadrechnung hat die
|
||||||
|
Bewertung nicht anzuzweifeln. Das alte Modell (D18: „eigene Größe plus
|
||||||
|
Summe/Minimum der Kinder") zählte doppelt und bestrafte damit systematisch
|
||||||
|
genau die Pläne, die sorgfältig zerlegen — je ehrlicher die Zerlegung, desto
|
||||||
|
teurer sah das Paket aus.
|
||||||
|
|
||||||
|
**Neue Regel: Der Preis eines Knotens ist seine Größe.** Angegeben oder —
|
||||||
|
seit D66 — aus den Teilpaketen geschätzt; die Schätzung ist dieselbe Sorte
|
||||||
|
Gesamtaussage und übernimmt die Rolle nahtlos. Erledigtes bleibt 0 (D46).
|
||||||
|
`cheapestCost()` kollabiert damit auf `ownCost()`: Die Rekursion über die
|
||||||
|
Kinder entfällt, die Aggregation für größenlose Knoten trägt allein die
|
||||||
|
D66-Schätzregel. Die Wahl in einer Alternativgruppe vergleicht schlicht die
|
||||||
|
Größen der Alternativen.
|
||||||
|
|
||||||
|
**Die Bewertung gilt auch, wenn die Kinder sie sprengen.** Vier `(S)` unter
|
||||||
|
einem `(S)` sind ein `sizeConflict` — der Marker zeigt es, aber der Knoten
|
||||||
|
bleibt bewertet, wie er bewertet wurde (ausdrückliche Nutzer-Entscheidung:
|
||||||
|
„der übergeordnete Knoten bleibt dennoch S"). Die Alternative — die Kinder
|
||||||
|
könnten den Preis anheben (max-Variante) — war vorgeschlagen und ist damit
|
||||||
|
verworfen: Sie hätte die Bewertung des Autors stillschweigend überstimmt,
|
||||||
|
und für den Widerspruch gibt es bereits den lauten Kanal.
|
||||||
|
|
||||||
|
**Das Vereinigungs-Maß der Closure-Suche (D42) zieht mit.** Die Suche über
|
||||||
|
die gekoppelten Gruppen verglich Belegungen über die Summe der Knoten-Preise
|
||||||
|
der nötigen Menge — mit Teilbaum-Bepreisung zählte ein zerlegtes
|
||||||
|
Abhängigkeits-Ziel dann mehrfach (Eltern plus Kinder), ein grobes nur
|
||||||
|
einmal: dieselbe Zerlegungs-Strafe, nur im Suchmaß. Jeder nötige Knoten
|
||||||
|
zählt jetzt nur mit dem, was seine Größe **über die nötigen Teilpakete
|
||||||
|
hinaus** behauptet (nie negativ); die Summe dieser Margen bepreist einen
|
||||||
|
vollständig zerlegten Teilbaum mit seiner Spitzengröße, gemeinsam
|
||||||
|
Gebrauchtes zählt über die Mengen-Vereinigung weiterhin einmal. Benannte
|
||||||
|
Grenze: Wo Kinder ihre Elterngröße im Rangraum übersteigen, liegt das
|
||||||
|
Suchmaß über dem Preis der lokalen Wahl — die beiden Maße sind dann nicht
|
||||||
|
identisch; das betrifft genau die Teilbäume, die der Größen-Konflikt ohnehin
|
||||||
|
anmahnt oder die eng bepreist sind, und die lokale Wahl (der Regelfall)
|
||||||
|
folgt strikt der Nutzer-Regel.
|
||||||
|
|
||||||
|
**Benannter Verlust:** Erledigte Teilpakete unter einem offenen Knoten
|
||||||
|
senken dessen Preis nicht mehr (kein Kinder-Summieren, keine Bruchteile —
|
||||||
|
D46 lehnte anteilige Restkosten schon ab). Für die Wahl trägt das kaum:
|
||||||
|
Sobald an einer Alternative selbst gearbeitet ist, entscheidet ohnehin
|
||||||
|
`chosenPool` (D61); Stationen und Markierung behalten ihre Knoten-Genauigkeit
|
||||||
|
unverändert.
|
||||||
|
|
||||||
|
**Nachgemessen:** Der gemeldete Fall wählt jetzt „Manuell mit Downtime"
|
||||||
|
(S=2 gegen L=4). Der mitgelieferte Werkbaum-Plan wählt unter altem und neuem
|
||||||
|
Modell **identisch** (137 Pfadknoten, 57 Stationen, gleiche Menge — per
|
||||||
|
git-stash-Vergleich gemessen, nicht angenommen). 450 Tests, davon 5 neue in
|
||||||
|
`tests/pricing.test.js`; drei Alt-Tests, die die Summen-Semantik festhielten,
|
||||||
|
sind auf die neue Regel umgeschrieben. Gegenproben per Mutation: alte
|
||||||
|
Rekursion zurückgebaut → genau die drei danach benannten Zusicherungen
|
||||||
|
fallen; Vereinigungs-Maß auf schlichte Summe zurück → genau der eine
|
||||||
|
Marginal-Test (dessen erste Fassung nicht unterschied, weil Wurzeln immer
|
||||||
|
nötig sind — der Testbaum musste die Ziele unter einen unangetasteten
|
||||||
|
`+`-Zweig legen).
|
||||||
|
|||||||
+26
-12
@@ -598,18 +598,28 @@ wird die **noch offene** Arbeit — der Pfad beantwortet „was ist als Nächste
|
|||||||
am günstigsten?", nicht „was hätte der Plan von vorn gekostet?". Ermittelt
|
am günstigsten?", nicht „was hätte der Plan von vorn gekostet?". Ermittelt
|
||||||
werden die für die günstigste Realisierung **nötigen** Knoten:
|
werden die für die günstigste Realisierung **nötigen** Knoten:
|
||||||
|
|
||||||
|
- **Die Größe bepreist den ganzen Teilbaum.** Der Preis eines Knotens ist
|
||||||
|
seine **angegebene Größe** (§5) — die Teilpakete kommen **nicht** noch
|
||||||
|
einmal obendrauf: Wer ein Paket mit `(S)` bewertet hat, hat den Teilbaum
|
||||||
|
bewertet. Ob die Zerlegung in die Größe passt, prüft der Größen-Konflikt
|
||||||
|
(§5) — die Pfadrechnung zweifelt die Bewertung nicht an. Fehlt die Größe,
|
||||||
|
vertritt die Schätzung aus den Teilpaketen sie (unten). Siehe D69 (bis
|
||||||
|
dahin galt: eigene Größe **plus** Summe/Minimum der Kinder — das
|
||||||
|
bestrafte gerade die sorgfältig zerlegten Pakete).
|
||||||
- **Erledigtes kostet nichts mehr:** Ein Knoten mit `[x]` oder `[^]` (§4) geht
|
- **Erledigtes kostet nichts mehr:** Ein Knoten mit `[x]` oder `[^]` (§4) geht
|
||||||
mit **0** in die Rechnung, unabhängig von seiner Größe. Angefangenes (`[~]`,
|
mit **0** in die Rechnung, unabhängig von seiner Größe. Angefangenes (`[~]`,
|
||||||
`[/]`) zählt dagegen **voll** — die Arbeit ist noch offen. Maßgeblich ist der
|
`[/]`) zählt dagegen **voll** — die Arbeit ist noch offen. Maßgeblich ist der
|
||||||
**intrinsische** Status: Investiert ist investiert, auch wenn Abhängigkeiten
|
**intrinsische** Status: Investiert ist investiert, auch wenn Abhängigkeiten
|
||||||
den Knoten effektiv zurückhalten (§4). Abgezogen werden nur die **eigenen**
|
den Knoten effektiv zurückhalten (§4). Offene Teilpakete unter einem
|
||||||
Kosten des Knotens, nicht die seines Teilbaums. Folge in Alternativgruppen:
|
erledigten Knoten bleiben auf dem Pfad und behalten ihre Stationen — die
|
||||||
Eine bereits realisierte Alternative gewinnt, auch wenn eine unangetastete
|
Kostenfrage stellt sich dort nicht mehr, die Wahl ist realisiert. Folge in
|
||||||
nominell billiger wäre — die Wahl ist getroffen und bezahlt. Siehe D46.
|
Alternativgruppen: Eine bereits realisierte Alternative gewinnt, auch wenn
|
||||||
|
eine unangetastete nominell billiger wäre — die Wahl ist getroffen und
|
||||||
|
bezahlt. Siehe D46.
|
||||||
- **all of:** alle Kinder sind nötig.
|
- **all of:** alle Kinder sind nötig.
|
||||||
- **any of:** nur die **günstigste** Alternative ist nötig. „Günstig" =
|
- **any of:** nur die **günstigste** Alternative ist nötig. „Günstig" ist die
|
||||||
kleinste rekursive Kosten (eigene T-Shirt-Größe plus — je Gate — Summe bzw.
|
**Größe der Alternative selbst** — angegeben oder geschätzt (oben/unten).
|
||||||
Minimum der Kinder). Bei Gleichstand gewinnt die **erste** Alternative.
|
Bei Gleichstand gewinnt die **erste** Alternative.
|
||||||
**Ist in der Gruppe etwas realisiert** (§3: `[~]`, `[/]`, `[x]`, `[^]`), ist
|
**Ist in der Gruppe etwas realisiert** (§3: `[~]`, `[/]`, `[x]`, `[^]`), ist
|
||||||
die Wahl damit getroffen — gewählt wird nur noch unter den realisierten
|
die Wahl damit getroffen — gewählt wird nur noch unter den realisierten
|
||||||
Alternativen, auch wenn eine unangetastete nominell billiger wäre. Sind es
|
Alternativen, auch wenn eine unangetastete nominell billiger wäre. Sind es
|
||||||
@@ -617,10 +627,10 @@ werden die für die günstigste Realisierung **nötigen** Knoten:
|
|||||||
`|`-Gruppe zulässig), entscheidet unter ihnen wieder die Kostenregel.
|
`|`-Gruppe zulässig), entscheidet unter ihnen wieder die Kostenregel.
|
||||||
- **Optionale Knoten (`+`, §3) sind nur nötig, solange an ihnen gearbeitet
|
- **Optionale Knoten (`+`, §3) sind nur nötig, solange an ihnen gearbeitet
|
||||||
wird** — also wenn sie realisiert (§3), aber noch nicht erledigt sind:
|
wird** — also wenn sie realisiert (§3), aber noch nicht erledigt sind:
|
||||||
`[~]` und `[/]`. Sonst zählen sie weder zu den Kosten ihres Elternknotens
|
`[~]` und `[/]`. Sonst liegen sie nicht auf dem Pfad (der Teilbaum unter
|
||||||
noch liegen sie auf dem Pfad, und der Teilbaum unter ihnen ebenso wenig.
|
ihnen ebenso wenig) und zählen nicht zur Größen-Schätzung (unten).
|
||||||
Genau dafür gibt es das Zeichen: Ohne `+` rechnet der günstigste Pfad jede
|
Genau dafür gibt es das Zeichen: Ohne `+` läge jede
|
||||||
Zugabe ins Minimum ein und überschätzt es. Die Ausnahme hält den umgekehrten
|
Zugabe auf dem günstigsten Pfad. Die Ausnahme hält den umgekehrten
|
||||||
Fehler heraus — eine angefangene Zugabe ist offene Arbeit, und der Pfad
|
Fehler heraus — eine angefangene Zugabe ist offene Arbeit, und der Pfad
|
||||||
zeigt die offene Front. **Erledigte Zugaben bleiben draußen:** Dort ist
|
zeigt die offene Front. **Erledigte Zugaben bleiben draußen:** Dort ist
|
||||||
nichts mehr zu tun, und was darunter offen blieb, ist mit ihnen zusammen
|
nichts mehr zu tun, und was darunter offen blieb, ist mit ihnen zusammen
|
||||||
@@ -654,7 +664,11 @@ Gebrauchtes zählt über die Mengen-Vereinigung nur **einmal**. Regeln:
|
|||||||
bezahlt werden. **Verfahren (D42): erschöpfende Suche** über die
|
bezahlt werden. **Verfahren (D42): erschöpfende Suche** über die
|
||||||
**gekoppelten** Gruppen (Gruppen, deren Teilbäume Abhängigkeiten enthalten
|
**gekoppelten** Gruppen (Gruppen, deren Teilbäume Abhängigkeiten enthalten
|
||||||
oder gebraucht werden); alle übrigen wählen lokal wie bisher, bei
|
oder gebraucht werden); alle übrigen wählen lokal wie bisher, bei
|
||||||
Gleichstand die erste. Wird die Suche zu groß, rechnet die Anzeige **gierig
|
Gleichstand die erste. Verglichen werden die Belegungen über die Summe
|
||||||
|
dessen, was jeder nötige Knoten **über seine nötigen Teilpakete hinaus**
|
||||||
|
behauptet (nie negativ) — so bepreist auch die Vereinigung einen Teilbaum
|
||||||
|
mit seiner Größe, statt Zerlegungstiefe zu bestrafen (D69). Wird die Suche
|
||||||
|
zu groß, rechnet die Anzeige **gierig
|
||||||
und sagt es** — Warnung `cheapApprox`, zeilenlos.
|
und sagt es** — Warnung `cheapApprox`, zeilenlos.
|
||||||
|
|
||||||
Darstellung per **Inversion**: nicht benötigte Knoten (nicht-gewählte
|
Darstellung per **Inversion**: nicht benötigte Knoten (nicht-gewählte
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
- [^] #ed.deplink.calm: Visually secondary — the tree carries the message (S)
|
- [^] #ed.deplink.calm: Visually secondary — the tree carries the message (S)
|
||||||
- [^] #ed.deplink.focus: Highlight what a selected node needs and feeds (S)
|
- [^] #ed.deplink.focus: Highlight what a selected node needs and feeds (S)
|
||||||
- [^] #ed.path: Cheapest path (XL)
|
- [^] #ed.path: Cheapest path (XL)
|
||||||
- [^] #ed.path.cost: Cost model, sizes summed and minimized (S)
|
- [x] #ed.path.cost: Cost model, a size prices its subtree (S)
|
||||||
- [^] #ed.path.est: Missing size estimated from the sub-packages (S) %% see D66
|
- [^] #ed.path.est: Missing size estimated from the sub-packages (S) %% see D66
|
||||||
- [^] #ed.path.front: Only the open front: what is done costs nothing (S) %% see D46
|
- [^] #ed.path.front: Only the open front: what is done costs nothing (S) %% see D46
|
||||||
- [^] #ed.path.started: Started work is on the path, extras included (S) %% see D61
|
- [^] #ed.path.started: Started work is on the path, extras included (S) %% see D61
|
||||||
@@ -394,8 +394,9 @@
|
|||||||
the cheapest alternative. This is the feature the whole notation exists for.
|
the cheapest alternative. This is the feature the whole notation exists for.
|
||||||
|
|
||||||
#ed.path.cost
|
#ed.path.cost
|
||||||
Cost is the T-shirt size, summed along and-groups and minimized along
|
The cost of a node is its stated (or estimated) T-shirt size — the size
|
||||||
or-groups.
|
prices the whole subtree, sub-packages are not added on top. Whether they
|
||||||
|
fit is the size conflict check's job.
|
||||||
|
|
||||||
#ed.path.est
|
#ed.path.est
|
||||||
A node without a size is estimated from its sub-packages: at least the
|
A node without a size is estimated from its sub-packages: at least the
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ One node per line. Everything except the label is optional.
|
|||||||
label. `"(L)"` and `((L))` mention a size literally (quoting convention).
|
label. `"(L)"` and `((L))` mention a size literally (quoting convention).
|
||||||
- From `(M)` upward a node **should be decomposed further**; a node ≥ M
|
- From `(M)` upward a node **should be decomposed further**; a node ≥ M
|
||||||
without children gets a placeholder hint in the diagram.
|
without children gets a placeholder hint in the diagram.
|
||||||
|
- **A size prices its whole subtree.** For the cheapest path, the cost of a
|
||||||
|
node is its own stated size — sub-packages are **not** added on top; the
|
||||||
|
conflict check below guards whether they fit. A done node (`[x]`/`[^]`)
|
||||||
|
costs 0.
|
||||||
- For cost estimation a missing size is **estimated from the sub-packages**:
|
- For cost estimation a missing size is **estimated from the sub-packages**:
|
||||||
at least the largest size among the counting children (same children as
|
at least the largest size among the counting children (same children as
|
||||||
the conflict check below, except unsized children count too — estimated
|
the conflict check below, except unsized children count too — estimated
|
||||||
|
|||||||
+22
-10
@@ -118,15 +118,14 @@ export function assumedSize(n){
|
|||||||
}
|
}
|
||||||
/* fehlende Größe wird geschätzt (assumedSize); Erledigtes kostet nichts mehr */
|
/* fehlende Größe wird geschätzt (assumedSize); Erledigtes kostet nichts mehr */
|
||||||
export function ownCost(n){ return isDone(n) ? 0 : SIZE_RANK[assumedSize(n)] + 1; }
|
export function ownCost(n){ return isDone(n) ? 0 : SIZE_RANK[assumedSize(n)] + 1; }
|
||||||
export function cheapestCost(n){
|
/* Die Größe bepreist den GANZEN Teilbaum (SPEC §9, D69): Wer ein Paket mit
|
||||||
const kids = pathChildren(n);
|
(S) bewertet hat, hat den Teilbaum bewertet — die Teilpakete kommen nicht
|
||||||
let c = ownCost(n);
|
noch einmal obendrauf. Ob sie in die Größe passen, prüft der
|
||||||
if(kids.length){
|
Größen-Konflikt (§5/D62); fehlt die Größe, vertritt die Schätzung sie
|
||||||
if(gateOf(kids) !== 'and') c += Math.min(...chosenPool(kids).map(cheapestCost));
|
(assumedSize, D66). Der Preis einer Alternative ist damit schlicht ihr
|
||||||
else c += kids.reduce((s, k) => s + cheapestCost(k), 0);
|
eigener — die frühere Rekursion (eigene Größe plus Summe/Minimum der
|
||||||
}
|
Kinder) bestrafte gerade die sorgfältig zerlegten Pakete. */
|
||||||
return c;
|
export function cheapestCost(n){ return ownCost(n); }
|
||||||
}
|
|
||||||
/* ---------- Günstigster Pfad auf der Dependency Closure (SPEC §9, D42) ----
|
/* ---------- Günstigster Pfad auf der Dependency Closure (SPEC §9, D42) ----
|
||||||
Mit Abhängigkeiten zählt nicht mehr der gewählte Teilbaum, sondern die
|
Mit Abhängigkeiten zählt nicht mehr der gewählte Teilbaum, sondern die
|
||||||
HÜLLE: Jeder nötige Knoten zieht seine `:#…`-Ziele samt deren Realisierung
|
HÜLLE: Jeder nötige Knoten zieht seine `:#…`-Ziele samt deren Realisierung
|
||||||
@@ -216,7 +215,20 @@ export function computeCheapPlan(roots){
|
|||||||
}
|
}
|
||||||
return set;
|
return set;
|
||||||
};
|
};
|
||||||
const costOf = set => { let c = 0; set.forEach(n => c += ownCost(n)); return c; };
|
/* Preis einer Belegung (D69): Jeder nötige Knoten zählt nur mit dem, was
|
||||||
|
seine Größe über die nötigen Teilpakete HINAUS behauptet (nie negativ) —
|
||||||
|
die Summe bepreist einen Teilbaum so mit seiner Größe, statt
|
||||||
|
Zerlegungstiefe zu bestrafen; gemeinsam Gebrauchtes zählt über die
|
||||||
|
Mengen-Vereinigung weiterhin nur einmal. */
|
||||||
|
const costOf = set => {
|
||||||
|
let c = 0;
|
||||||
|
set.forEach(n => {
|
||||||
|
let kids = 0;
|
||||||
|
for(const k of pathChildren(n)) if(set.has(k)) kids += ownCost(k);
|
||||||
|
c += Math.max(0, ownCost(n) - kids);
|
||||||
|
});
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
let product = 1;
|
let product = 1;
|
||||||
for(const grp of coupled){ product *= grp.pool.length; if(product > EXACT_LIMIT) break; }
|
for(const grp of coupled){ product *= grp.pool.length; if(product > EXACT_LIMIT) break; }
|
||||||
|
|||||||
@@ -65,10 +65,15 @@ describe('Kosten — Erledigtes zählt nicht mehr', () => {
|
|||||||
expect(ownCost(a)).toBe(0);
|
expect(ownCost(a)).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('zieht nur die eigenen Kosten ab, nicht den Teilbaum', () => {
|
it('der erledigte Knoten bepreist 0; sein offenes Kind bleibt auf dem Pfad', () => {
|
||||||
|
/* Seit D69 bepreist die Größe den ganzen Teilbaum — der erledigte
|
||||||
|
Elternknoten kostet nichts mehr, das offene Kind bleibt trotzdem
|
||||||
|
nötig und behält seinen eigenen Preis. */
|
||||||
const [n] = roots(`[x] Eltern (L)
|
const [n] = roots(`[x] Eltern (L)
|
||||||
- [ ] Kind (S)`);
|
- [ ] Kind (S)`);
|
||||||
expect(cheapestCost(n)).toBe(cheapestCost(roots('[ ] Kind (S)')[0]));
|
expect(cheapestCost(n)).toBe(0);
|
||||||
|
const set = computeCheapSet([n]);
|
||||||
|
expect(set.has(n.children[0])).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,11 @@ describe('Günstigster Pfad — unangetastete Zugaben sind nicht nötig', () =>
|
|||||||
expect(cheapLabels(BAUM)).toEqual(['Pflicht', 'Wurzel']);
|
expect(cheapLabels(BAUM)).toEqual(['Pflicht', 'Wurzel']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rechnet die Zugabe nicht in die Kosten des Elternknotens', () => {
|
it('die bewertete Wurzel bepreist ihren Teilbaum — die Zugabe erst recht nicht', () => {
|
||||||
/* Wurzel (S=2) + Pflicht (S=2) = 4; mit der XXL-Zugabe wären es 10. */
|
/* Seit D69 ist der Preis die eigene Größe: Wurzel (S) = 2, unabhängig
|
||||||
expect(cheapestCost(roots(BAUM)[0])).toBe(4);
|
von Pflicht und XXL-Zugabe. Dass Zugaben auch die GESCHÄTZTE Größe
|
||||||
|
eines größenlosen Knotens nicht erhöhen, sichern die D66-Tests. */
|
||||||
|
expect(cheapestCost(roots(BAUM)[0])).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('vergleicht Alternativen ohne deren Zugaben', () => {
|
it('vergleicht Alternativen ohne deren Zugaben', () => {
|
||||||
@@ -105,9 +107,12 @@ describe('Günstigster Pfad — an einer angefangenen Zugabe wird gearbeitet', (
|
|||||||
.toEqual(['Teil', 'Wurzel', 'Zugabe']);
|
.toEqual(['Teil', 'Wurzel', 'Zugabe']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rechnet sie damit auch in die Kosten des Elternknotens', () => {
|
it('die angefangene Zugabe behält ihren eigenen Preis auf dem Pfad', () => {
|
||||||
/* Wurzel (S=2) + Zugabe (S=2) = 4; unangetastet wären es 2. */
|
/* Seit D69 bepreist die Wurzel nur sich selbst (S = 2); die angefangene
|
||||||
expect(cheapestCost(roots(`[ ] Wurzel (S)\n + [~] Zugabe (S)`)[0])).toBe(4);
|
Zugabe liegt mit ihrem eigenen Preis daneben auf dem Pfad. */
|
||||||
|
const [wurzel] = roots(`[ ] Wurzel (S)\n + [~] Zugabe (S)`);
|
||||||
|
expect(cheapestCost(wurzel)).toBe(2);
|
||||||
|
expect(cheapestCost(wurzel.children[0])).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('macht sie zur Station statt des Elternknotens', () => {
|
it('macht sie zur Station statt des Elternknotens', () => {
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user