---
name: summarize_fleet_transitions
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: pure
signature: "def summarize_fleet_transitions(by_classification: dict, max_items: int = 6) -> str"
description: "Resume en una sola linea las transiciones accionables de la flota de Claudes para inyectar por un hook UserPromptSubmit al orquestador. Toma el dict by_classification de drain_fleet_events (agrupado por campo `to`) y condensa SOLO las tres categorias accionables: DICE_TERMINADO (terminados), RECLAMA (reclaman) y ESTANCADO (estancados); ignora TRABAJANDO, GONE y MAL_LANZADO. Funcion pura, determinista y defensiva: deduplica por session_id, trunca el goal y limita el total con overflow `(+N mas)`."
tags: [orchestration, claude-fleet, fleet, summary, hook, orchestrator]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: by_classification
    desc: "dict agrupado por campo `to` tal como lo devuelve drain_fleet_events. Solo se leen las claves accionables DICE_TERMINADO, RECLAMA y ESTANCADO; cada valor es una lista de eventos (dicts) con al menos session_id (str UUID), goal (str) y to (str). Puede ser None o {} (devuelve 'sin cambios')."
  - name: max_items
    desc: "limite total de eventos mostrados sumando las tres categorias (default 6). Al superarse se trunca y se añade ' (+N mas)'. El cupo se llena en orden de categorias terminados -> reclaman -> estancados."
output: "una cadena de UNA sola linea. 'FLEET-STATE: sin cambios' si no hay nada accionable; en caso contrario 'FLEET-STATE: terminados=[<sid8>:<goal>] reclaman=[...] estancados=[...] (+N mas) (drain con ./fn run drain_fleet_events para consumir)' omitiendo las categorias vacias."
tested: true
tests:
  - "test_golden_tres_categorias_con_datos"
  - "test_categorias_vacias_sin_cambios"
  - "test_solo_no_accionables_sin_cambios"
  - "test_dedup_por_session_id_se_queda_ultima"
  - "test_truncado_de_goal_mayor_de_40"
  - "test_max_items_con_overflow"
  - "test_entrada_none_sin_cambios"
  - "test_entrada_dict_vacio_sin_cambios"
  - "test_valor_no_lista_se_salta"
  - "test_eventos_no_dict_se_saltan"
  - "test_session_id_ausente_usa_placeholder"
  - "test_goal_ausente_usa_placeholder"
  - "test_omite_categorias_vacias_solo_renderiza_con_datos"
  - "test_varios_eventos_misma_categoria_separados_por_coma"
test_file_path: "python/functions/infra/summarize_fleet_transitions_test.py"
file_path: "python/functions/infra/summarize_fleet_transitions.py"
---

## Ejemplo

```python
from summarize_fleet_transitions import summarize_fleet_transitions

by_classification = {
    "DICE_TERMINADO": [
        {"session_id": "a1b2c3d4-aaaa-bbbb", "goal": "Migrar tabla salesforce a europe-west1", "to": "DICE_TERMINADO"},
    ],
    "RECLAMA": [
        {"session_id": "e5f6g7h8-cccc-dddd", "goal": "Necesito clave API de Metabase", "to": "RECLAMA", "urgent": True},
    ],
    "ESTANCADO": [],
    "TRABAJANDO": [
        {"session_id": "99887766-eeee-ffff", "goal": "Indexando registry", "to": "TRABAJANDO"},
    ],
}

linea = summarize_fleet_transitions(by_classification)
# FLEET-STATE: terminados=[a1b2c3d4:Migrar tabla salesforce a europe-w…] reclaman=[e5f6g7h8:Necesito clave API de Metabase] (drain con ./fn run drain_fleet_events para consumir)
print(linea)
```

## Cuando usarla

Cuando un hook (tipico `UserPromptSubmit`) necesita resumir en una sola linea las
transiciones pendientes de la flota para inyectarlas al orquestador-Claude. Se
encadena despues de `drain_fleet_events` (que produce el `by_classification`) para
darle al orquestador, sin coste de lectura extra, el estado accionable de un
vistazo: quien dice haber terminado, quien reclama atencion y quien esta estancado.

## Gotchas

- **Dedup por session_id**: dentro de cada categoria, si un mismo `session_id`
  aparece en varios eventos se conserva solo la ultima aparicion (la transicion
  mas reciente en la lista). Eventos sin `session_id` valido no se deduplican
  entre si y cada uno cuenta como entrada propia.
- **Truncado de goal**: el `goal` se recorta a 40 caracteres con elipsis `…`; un
  `goal` ausente o vacio se muestra como `(sin objetivo)` y un `session_id`
  ausente/vacio como `????????`.
- **Solo categorias accionables**: TRABAJANDO, GONE y MAL_LANZADO se ignoran a
  proposito. Si las tres categorias accionables estan vacias o no existen,
  devuelve `FLEET-STATE: sin cambios`.
- **Reparto de max_items**: el cupo total se llena recorriendo las categorias en
  orden (terminados -> reclaman -> estancados); los sobrantes se cuentan en
  `(+N mas)`. Es defensiva ante datos mal formados (valores no-lista o eventos
  no-dict se omiten sin lanzar excepcion).
