--- 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=[:] 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).