Some checks failed
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Failing after 3m33s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
evaluate_steering() vereinheitlicht Horizon, Next Work und Attention; Snapshot und Signal Engine delegieren dorthin; slot_map deklarativ fuer sequential_dependency und generic_operating. Co-authored-by: Cursor <cursoragent@cursor.com>
163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
"""Attention evaluation (Q5) — method- and horizon-aware for one initiative."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from steering.kernel.models import HorizonMarker, SteeringBinding
|
|
from steering.strategies.next_action.execution_ready import gate_horizon_scope_for_method
|
|
from tenant_context import TenantContext
|
|
|
|
_GATE_METHODS = frozenset(
|
|
{
|
|
"sequential_dependency",
|
|
"program_delivery",
|
|
"maturity_progression",
|
|
}
|
|
)
|
|
|
|
|
|
def evaluate_attention(
|
|
ctx: TenantContext,
|
|
*,
|
|
initiative_id: str,
|
|
binding: SteeringBinding,
|
|
horizon: HorizonMarker,
|
|
next_work: list[dict[str, Any]],
|
|
limit: int = 20,
|
|
) -> list[dict[str, Any]]:
|
|
"""Kernel attention for a single initiative — no fake next when planning debt."""
|
|
from services import actions as action_service
|
|
from services import blockers as blocker_service
|
|
from services import roadmap as roadmap_service
|
|
from services.execution_plan import list_dependencies_for_initiative
|
|
from steering.graph.execution_engine import (
|
|
compute_execution_graph_state,
|
|
compute_planning_debt,
|
|
execution_waiting_to_attention_items,
|
|
planning_debt_to_attention_items,
|
|
)
|
|
|
|
items: list[dict[str, Any]] = []
|
|
|
|
blockers = blocker_service.list_blockers_for_initiative(
|
|
tenant_id=ctx.tenant_id, initiative_id=initiative_id
|
|
)
|
|
for blocker in blockers:
|
|
if blocker.get("status") not in ("open", "in_progress"):
|
|
continue
|
|
items.append(
|
|
{
|
|
"kind": "open_blocker",
|
|
"severity": "warning",
|
|
"title": blocker.get("title") or "Blocker",
|
|
"summary": "Offener Blocker im Vorhaben",
|
|
"scope_type": "blocker",
|
|
"scope_id": str(blocker["id"]),
|
|
"initiative_id": initiative_id,
|
|
"action_id": (
|
|
str(blocker["action_id"]) if blocker.get("action_id") else None
|
|
),
|
|
"blocker_id": str(blocker["id"]),
|
|
"reason_code": "path_blocked",
|
|
"data_source": "steering_kernel",
|
|
}
|
|
)
|
|
|
|
actions = action_service.list_actions_for_initiative(
|
|
tenant_id=ctx.tenant_id, initiative_id=initiative_id
|
|
)
|
|
for action in actions:
|
|
if action.get("status") != "blocked":
|
|
continue
|
|
items.append(
|
|
{
|
|
"kind": "blocked_action",
|
|
"severity": "critical",
|
|
"title": action.get("title") or "Arbeitspaket",
|
|
"summary": "Maßnahme ist blockiert",
|
|
"scope_type": "action",
|
|
"scope_id": str(action["id"]),
|
|
"initiative_id": initiative_id,
|
|
"action_id": str(action["id"]),
|
|
"reason_code": "path_blocked",
|
|
"data_source": "steering_kernel",
|
|
}
|
|
)
|
|
|
|
gate_scope = gate_horizon_scope_for_method(
|
|
binding.primary_method_key, ctx, initiative_id
|
|
)
|
|
roadmap_items = roadmap_service.list_roadmap_items_for_initiative(
|
|
tenant_id=ctx.tenant_id, initiative_id=initiative_id
|
|
)
|
|
|
|
scoped_roadmap = roadmap_items
|
|
scoped_actions = actions
|
|
if gate_scope:
|
|
scope = str(gate_scope)
|
|
scoped_roadmap = [ri for ri in roadmap_items if str(ri.get("id")) == scope]
|
|
scoped_actions = [
|
|
a
|
|
for a in actions
|
|
if a.get("roadmap_item_id") and str(a["roadmap_item_id"]) == scope
|
|
]
|
|
|
|
if binding.primary_method_key in _GATE_METHODS:
|
|
debts = compute_planning_debt(
|
|
actions=scoped_actions, roadmap_items=scoped_roadmap
|
|
)
|
|
items.extend(
|
|
planning_debt_to_attention_items(
|
|
initiative_id=initiative_id, debts=debts
|
|
)
|
|
)
|
|
|
|
dependencies = list_dependencies_for_initiative(
|
|
tenant_id=ctx.tenant_id, initiative_id=initiative_id
|
|
)
|
|
graph_state = compute_execution_graph_state(
|
|
actions=actions,
|
|
dependencies=dependencies,
|
|
scope_roadmap_item_id=gate_scope,
|
|
)
|
|
items.extend(
|
|
execution_waiting_to_attention_items(
|
|
initiative_id=initiative_id,
|
|
actions=actions,
|
|
graph_state=graph_state,
|
|
)
|
|
)
|
|
|
|
if (
|
|
not next_work
|
|
and binding.primary_method_key in _GATE_METHODS
|
|
and horizon.kind == "gate"
|
|
):
|
|
has_planning = any(i.get("reason_code") == "planning_debt" for i in items)
|
|
has_waiting = any(i.get("reason_code") == "execution_waiting" for i in items)
|
|
if not has_planning and not has_waiting and not graph_state.get("ready_actions"):
|
|
items.append(
|
|
{
|
|
"kind": "initiative_without_next_action",
|
|
"severity": "info",
|
|
"title": horizon.gate_title or "Aktiver Horizont",
|
|
"summary": "Keine ausführungsbereite Arbeit im aktiven Horizont",
|
|
"scope_type": "milestone",
|
|
"scope_id": horizon.gate_roadmap_item_id,
|
|
"initiative_id": initiative_id,
|
|
"reason_code": "horizon_no_ready_work",
|
|
"data_source": "steering_kernel",
|
|
}
|
|
)
|
|
|
|
for item in items:
|
|
item.setdefault("data_source", "steering_kernel")
|
|
|
|
items.sort(
|
|
key=lambda x: {"critical": 0, "warning": 1, "info": 2}.get(
|
|
x.get("severity", "info"), 9
|
|
)
|
|
)
|
|
return items[:limit]
|