"""Implementation Playbook — the Berater renderer ("wie komme ich dort hin?"). After the Capability Delta Engine says WHAT is missing and the Optimization renderer says WHICH measure first, the Playbook renderer says HOW to implement it. For one capability it assembles the full journey from three sources: - curated playbook KNOWLEDGE (why / tools / process steps / evidence / how others do it) — the Reasoning Knowledge Acquisition layer under `knowledge/implementation_playbooks/`, - the regulatory LEVERAGE (which regulations a delivered capability closes) — reused from the Optimization renderer, - injected Procedure/Control/Evidence links (Execution-owned; empty until linked). Pure, deterministic, computed-not-stored. Chains optimization -> playbook (acyclic). No new corpus, no new meta-model class (freeze v1.0). Python 3.9 compatible. The curated content is an EXPERT DRAFT, never a normative requirement. When no playbook knowledge exists for a capability yet, the renderer emits a `status: missing` stub — the honest signal that the bottleneck is CONTENT (Knowledge Acquisition), not software. """ from __future__ import annotations from typing import Any, Dict, List, Optional from ..optimization import OptimizationPlan from .schemas import Playbook, PlaybookStep _MISSING_WHY = "(Playbook-Inhalt fehlt — Knowledge Acquisition offen.)" _DRAFT_DISCLAIMER = ( "Kuratiertes Experten-Wissen (Erstentwurf), KEINE normative Anforderung. Tools/Schritte sind " "Empfehlungen, kein Pflichtkatalog; Controls werden aus der Execution-Schicht injiziert." ) def _steps(raw: Any) -> List[PlaybookStep]: steps: List[PlaybookStep] = [] for i, s in enumerate(raw or [], 1): steps.append(PlaybookStep(order=i, title=str(s.get("title", "")), detail=str(s.get("detail", "")))) return steps def build_playbook( capability_id: str, knowledge: Optional[Dict[str, Any]] = None, closes_regulations: Optional[List[str]] = None, control_links: Optional[List[str]] = None, ) -> Playbook: """Assemble the implementation journey for ONE capability. `knowledge`: the curated playbook dict (None/empty -> a `missing` stub). `closes_regulations`: the regulations a delivered capability closes (leverage, from `covers_targets`). `control_links`: Execution-owned control refs, injected (default empty — no Execution data in Reasoning code). """ closes = sorted(set(closes_regulations or [])) if not knowledge: return Playbook( capability_id=capability_id, title=capability_id, why=_MISSING_WHY, closes_regulations=closes, leverage=len(closes), controls=list(control_links or []), status="missing", disclaimer=_DRAFT_DISCLAIMER, ) return Playbook( capability_id=capability_id, title=str(knowledge.get("title", capability_id)), why=str(knowledge.get("why", "")), closes_regulations=closes, leverage=len(closes), tools=list(knowledge.get("tools", [])), process_steps=_steps(knowledge.get("process_steps")), expected_evidence=list(knowledge.get("expected_evidence", [])), controls=list(control_links or []), how_others_do_it=str(knowledge.get("how_others_do_it", "")), status=str(knowledge.get("status", "draft")), disclaimer=str(knowledge.get("disclaimer", _DRAFT_DISCLAIMER)), ) def playbooks_for_plan( plan: OptimizationPlan, knowledge_by_cap: Dict[str, Dict[str, Any]], top_k: Optional[int] = None, control_links_by_cap: Optional[Dict[str, List[str]]] = None, ) -> List[Playbook]: """Render playbooks for the highest-leverage measures of an OptimizationPlan (Roadmap -> How). Walks the ranked measures (top_k, or all) and builds each capability's playbook, using the measure's own `covers` as the regulations it closes. Measures without curated knowledge become `missing` stubs — surfacing exactly where playbook content is still owed. """ links = control_links_by_cap or {} measures = plan.ranked_measures if top_k is None else plan.ranked_measures[: max(0, top_k)] return [ build_playbook( m.capability_id, knowledge_by_cap.get(m.capability_id), closes_regulations=m.covers, control_links=links.get(m.capability_id), ) for m in measures ]