feat(orchestrator): Kandidaten-Dedup + balancierter Cap fuer Hybrid-Buchplanung (#346, PR 2/3)#347
Merged
Merged
Conversation
…uchplanung (#346, PR 2/3)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 2/3 zu #346 — isolierte Hybrid-Buchplanungs-Bausteine
Teil des Implementierungsplans zu #346 (Hybrid-Buchplanung: Planner je Hauptkapitel, Extraktion global). Dieser PR liefert zwei reine, vollständig getestete Funktionen in
generative/orchestrator.py(Nachbarschaft vondedup_exact) — ohne Orchestrator-Wiring. Die Verdrahtung (--book-mode, Budget-Formel, Per-Kapitel-Planner) folgt in PR 3. Bewusst noch tote, aber verifizierte Funktionen: isolierte Review-Fläche.Ref #346 (schließt das Issue NICHT — das erledigt erst PR 3).
Funktion 1:
dedup_concept_candidates(concepts) -> tuple[list, int]Globale Kandidaten-Dedup über den normalisierten Titel (dieselbe
_normalize()-Logik wiededup_exact), damit dasselbe Konzept aus mehreren Kapiteln EIN Konzept wird.origin == "secondary_mention"ist ein eigener Kanal: passiert unangetastet, wird nie dedupliziert/verworfen und kollidiert nicht mit primären Titeln.priority(high=2, medium=1, low=0) schlägt niedrigere; bei Gleichstand schlägt eine non-skip-Fassung einen skip-Survivor; sonst Erstauftreten. Der Survivor behält die Erstauftreten-Position (stabile Reihenfolge)._trace_stage_outcome(titel, "planner_dedup", "dropped", drop_reason="chapter_duplicate", detail="survivor=<titel>").Funktion 2:
cap_candidates_balanced(concepts, budget, chapter_word_counts, chapter_keys) -> tuple[list, list]Wortanteil-balancierter, prioritätssortierter Kandidaten-Cap.
budgetist fertig berechnet (Budget-Formel lebt beim Aufrufer, PR 3).secondary_mentionzählt nicht gegen das Budget und wird nie gekappt.Die Slot-Verteilung nutzt das D'Hondt-Höchstzahlverfahren (
weight/(alloc+1)) — deterministisch, Tie-Break Richtung frühere Kapitel-Reihenfolge.Design-Entscheidungen
Kapitel-Key: paralleler Parameter statt
ConceptItem.chapter. Der Auftrag verlangte zu sichten, ob das vorhandenechapter-Feld als Kapitel-Key taugt. Entscheidung: nein, stattdessenchapter_keys: list[str](index-aligned zuconcepts). Begründung:ConceptItem.chapterist laut Schema-Kommentar LLM-Freitext ("Kapitel/Abschnitt wo das Konzept erwartet wird") in möglicher Unterabschnitts-Granularität — nicht garantiert deckungsgleich mit den deterministischen Outline-Kapitel-Keys, aus denenchapter_word_countsin PR 1 gebildet wird. Eine Kopplung an dieses Feld würde die Balancierung von unzuverlässigem LLM-Text abhängig machen und mitchapter_word_countsfehlausrichten. PR 3 kennt beim Per-Kapitel-Planner-Call den autoritativen Key ohnehin und reicht ihn durch. Vorteil: Funktion bleibt rein/deterministisch, keine Schema-Änderung nötig (Sperrzone respektiert).Abgrenzung zu
cap_actionable_concepts(runtime_config.py:273). Das bestehende Muster (uniformer Cap ohne Wortanteil-Balancierung) bleibt unangetastet — es bedient weiter Normal-/by-chapter-Pfad.cap_candidates_balancedist ein Sibling für den book-mode-Pfad; gleiche(kept, capped)-Rückgabesemantik. Budget-neutral ist hier — dem Plan folgend — nursecondary_mention; skip-action-Kandidaten (falls vorhanden) werden wie normale Kandidaten budgetiert. PR 3 kann Skips vor dem Cap filtern, wie es der Normalpfad tut (orchestrator.py,actionable-Filter).TDD / RED-Nachweis
Neue Testdatei
generative/tests/test_candidate_dedup_and_cap.py(14 Tests, Capture-Muster wietest_stage_outcome_events.py). Erst RED (Funktionen fehlten):Nach Implementierung GREEN:
Abgedeckte Fälle — Dedup: Einzelvorkommen; normalisiertes Duplikat über Kapitel; Priority-Upgrade; skip→actionable-Upgrade (+ Umkehrung); secondary_mention unangetastet (inkl. Kollision mit primary); Funnel-Event mit stage/drop_reason/detail-Assertions; leere Eingabe. Cap: highs>Budget-Überlauf proportional; Wortanteil-Verteilung groß vs. klein; Priorität innerhalb Kapitel; secondary_mention budget-neutral; Budget > Kandidatenzahl = no-op; deterministische Stabilität bei Gleichstand.
Verifikation
uv sync --extra dev --locked— ok,uv.lockunverändert.generative.orchestrator→C:\tmp\wt-346a\generative\orchestrator.py(Worktree).uv run pytest generative lib/decision_engine/tests shared/tests -q: 6267 passed, 3 skipped, 9 deselected, 45 warnings in 669.00s (exit 0).ruff check+ruff format --checkauf beiden Dateien: grün.Geänderte Dateien
generative/orchestrator.py(+176; nur neue Funktionen, kein Aufruf aus main()/Stages)generative/tests/test_candidate_dedup_and_cap.py(neu)Kein Anfassen von
pdf_chunker.py(paralleler PR-1-Builder) und keine Schema-Änderung.