"""Queue pull NextAction strategy — AP2.0d.""" from __future__ import annotations from typing import Any from db import get_connection from psycopg2.extras import RealDictCursor 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() def _queue_backlog_candidates( ctx: TenantContext, initiative_id: str, *, limit: int ) -> list[dict[str, Any]]: conn = get_connection() try: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute( """ SELECT 'convert_backlog' AS kind, bi.title AS title, 'Freigegebenes Backlog-Item aus Queue ziehen' AS summary, bi.initiative_id, NULL::uuid AS action_id, bi.id AS backlog_item_id, 'backlog_accepted_not_converted' AS reason_code, 'In Maßnahme umwandeln' AS recommended_action FROM backlog_items bi WHERE bi.tenant_id = %s AND bi.initiative_id = %s AND bi.status = 'accepted' AND bi.converted_action_id IS NULL ORDER BY bi.updated_at ASC LIMIT %s """, (ctx.tenant_id, initiative_id, limit), ) items: list[dict[str, Any]] = [] for row in cur.fetchall(): item = dict(row) item["initiative_id"] = str(item["initiative_id"]) item["backlog_item_id"] = str(item["backlog_item_id"]) items.append(item) return items finally: conn.close() class QueuePullStrategy: key = "queue_pull" 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) queue = _queue_backlog_candidates(ctx, initiative_id, limit=limit) remaining = limit - len(queue) 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(queue, 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) queue_pull_strategy = QueuePullStrategy() def register_queue_pull_strategy() -> None: if not get_next_action_strategy(queue_pull_strategy.key): register_next_action_strategy(queue_pull_strategy)