"""Agile iteration NextAction strategy — Komposition (Primary ∩ Sprint).""" from __future__ import annotations from typing import Any from steering.methods.registry import get_method from steering.strategies.next_action.default_strategy import DefaultNextActionStrategy from steering.strategies.next_action.execution_ready import ( build_execution_ready_candidates, filter_actions_for_work_cycle, gate_horizon_scope_for_method, resolve_initiative_method_key, ) from steering.strategies.next_action.registry import ( get_next_action_strategy, register_next_action_strategy, ) from services.work_cycle import get_active_work_cycle from tenant_context import TenantContext _default = DefaultNextActionStrategy() def _primary_strategy_for_initiative(ctx: TenantContext, initiative_id: str): method_key = resolve_initiative_method_key(ctx, initiative_id) method = get_method(method_key) strategy_key = method.next_action_strategy_key if method else _default.key return get_next_action_strategy(strategy_key) or _default class AgileIterationStrategy: key = "agile_iteration" def evaluate( self, ctx: TenantContext, *, initiative_id: str | None = None, limit: int = 10, ) -> list[dict[str, Any]]: if limit < 1: limit = 1 if not initiative_id: return _default.evaluate(ctx, limit=limit) primary = _primary_strategy_for_initiative(ctx, initiative_id) active = get_active_work_cycle( tenant_id=ctx.tenant_id, initiative_id=initiative_id ) if not active: return primary.evaluate(ctx, initiative_id=initiative_id, limit=limit) primary_key = resolve_initiative_method_key(ctx, initiative_id) gate_scope = gate_horizon_scope_for_method(primary_key, ctx, initiative_id) cycle_id = str(active["id"]) from services import actions as action_service from steering.graph.execution_engine import load_initiative_execution_graph_state actions = action_service.list_actions_for_initiative( tenant_id=ctx.tenant_id, initiative_id=initiative_id ) scoped = filter_actions_for_work_cycle(actions, work_cycle_id=cycle_id) if gate_scope: scope = str(gate_scope) scoped = [ a for a in scoped if a.get("roadmap_item_id") and str(a["roadmap_item_id"]) == scope ] if not scoped: return [] graph_state = load_initiative_execution_graph_state( tenant_id=ctx.tenant_id, initiative_id=initiative_id, scope_roadmap_item_id=gate_scope, ) ready = build_execution_ready_candidates( actions=scoped, graph_state=graph_state, limit=limit, prefer_critical_path=True, ) for item in ready: item["reason_code"] = "work_cycle_ready" item["summary"] = ( f"Aktive Zeitbox „{active['title']}“ — ausführungsbereit im Horizont" ) item["recommended_action"] = "In dieser Iteration ausführen" return ready[:limit] agile_iteration_strategy = AgileIterationStrategy() def register_agile_iteration_strategy() -> None: if not get_next_action_strategy(agile_iteration_strategy.key): register_next_action_strategy(agile_iteration_strategy)