"""Recurring control NextAction strategy — AP2.0d.""" from __future__ import annotations from typing import Any from steering.strategies.next_action.recurring_helpers import recurring_due_candidates from steering.signals import default_rules from steering.strategies.next_action.default_strategy import DefaultNextActionStrategy from steering.strategies.next_action.execution_ready import ( list_execution_ready_candidates, merge_candidates, ) from steering.strategies.next_action.registry import ( get_next_action_strategy, register_next_action_strategy, ) from tenant_context import TenantContext _default = DefaultNextActionStrategy() class RecurringControlStrategy: key = "recurring_control" 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) recurring = recurring_due_candidates(ctx, initiative_id, limit=limit) remaining = limit - len(recurring) ready: list[dict[str, Any]] = [] if remaining > 0: ready = list_execution_ready_candidates( ctx, initiative_id, limit=remaining, prefer_critical_path=False ) merged = merge_candidates(recurring, ready, limit=limit) if len(merged) >= limit: return merged[:limit] rest = default_rules.get_next_action_candidates_for_initiative( ctx, initiative_id=initiative_id, limit=limit - len(merged) ) return merge_candidates(merged, rest, limit=limit) recurring_control_strategy = RecurringControlStrategy() def register_recurring_control_strategy() -> None: if not get_next_action_strategy(recurring_control_strategy.key): register_next_action_strategy(recurring_control_strategy)