feat: add operational eligibility and scheduled follow-up

This commit is contained in:
plx
2026-08-15 07:56:06 +00:00
parent 71452b1e5c
commit ee8e2de606
13 changed files with 812 additions and 26 deletions

View File

@@ -13,6 +13,7 @@ from typing import Any, Iterable, Mapping
from app.operation_noise import is_low_value_no_opportunity_item, is_noise_operation_item
from app.work_center_action_policy import canonical_action_code
from app.operational_eligibility import apply_operational_eligibility
WAITING_ACTION_CODES = {
@@ -173,15 +174,22 @@ def partition_canonical_items(
items: Iterable[Mapping[str, Any]], *, display_limit: int
) -> dict[str, Any]:
all_items = [dict(item) for item in items]
actionable = [item for item in all_items if item.get("operational_queue") != "waiting"]
actionable_queues = {"do_now", "review", "blocked", "exception"}
actionable = [item for item in all_items if item.get("operational_queue") in actionable_queues]
waiting = [item for item in all_items if item.get("operational_queue") == "waiting"]
backlog = [item for item in all_items if item.get("operational_queue") == "backlog"]
not_current = [item for item in all_items if item.get("operational_queue") == "not_current"]
return {
"actionable_items": actionable,
"waiting_items": waiting,
"visible_actionable_items": actionable[:display_limit],
"visible_waiting_items": waiting[:display_limit],
"backlog_items": backlog,
"not_current_items": not_current,
"work_queue_total": len(actionable),
"waiting_total": len(waiting),
"backlog_total": len(backlog),
"not_current_total": len(not_current),
}
@@ -313,6 +321,22 @@ def _opportunity_item(
rows: list[Mapping[str, Any]],
decision: Mapping[str, Any],
) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
scheduled_call = next((
row for row in rows
if row.get("source") == "task"
and _s(row.get("status")).lower() == "pending"
and _canonical_code(row.get("action_code")) == "CALL_CUSTOMER"
), None)
if scheduled_call:
decision = {
**dict(decision),
"action_code": "CALL_CUSTOMER",
"label": "Ligar ao cliente",
"description": _s(scheduled_call.get("detail")) or "Contacto telefónico agendado pelo operador.",
"reason": "Existe um contacto telefónico explicitamente agendado.",
"priority": _s(scheduled_call.get("priority")) or "normal",
"target_url": _s(scheduled_call.get("href")),
}
action_code = _canonical_code(decision.get("action_code")) or "REVIEW"
independent_exceptions: list[dict[str, Any]] = []
evidence_rows: list[Mapping[str, Any]] = []
@@ -338,7 +362,7 @@ def _opportunity_item(
"target_url": f"/opportunities/{opportunity_id}",
}
matching_task = _matching_task(evidence_rows, action_code)
matching_task = scheduled_call or _matching_task(evidence_rows, action_code)
primary = matching_task or next((row for row in evidence_rows if row.get("source") == "task"), None)
primary = primary or (evidence_rows[0] if evidence_rows else {})
process_key = f"opportunity:{opportunity_id}"
@@ -455,6 +479,7 @@ def canonicalize_operations(
# intermediate groups. Exact process/action identity is nevertheless the
# final safe uniqueness boundary.
items = _merge_exact_work_items(items)
items = apply_operational_eligibility(items)
# Preserve the existing Operations ordering: newest first inside each
# priority band, without allowing supporting/stale rows to set the band.