feat(infra): grupo claude-fleet — FleetView TUI + orquestacion de Claudes
Sistema FleetView para centralizar la flota de procesos Claude Code vivos en una sola ventana kitty + tmux (socket aislado -L fleet) con un panel TUI: - list_claude_fleet (+ tipo claude_fleet): escanea ~/.claude/sessions + goals + runtime, valida procesos vivos (anti-PID-reciclado), join por sessionId. - list_resumable_claudes (+ tipo resumable_claude): sesiones cerradas reanudables. - wrappers tmux: tmux_new_claude_window (con --resume), tmux_swap_window_into_console (preserva ancho del sidebar), tmux_map_claude_panes. - launch_kittyclaude: comando entrypoint; instala atajos alt+flechas/enter/n/0/k/r, mouse on, remain-on-exit off; fija el ancho del sidebar con hooks. - docs/capabilities/claude-fleet.md + entrada en el INDEX. Incluye ademas funciones datascience en progreso (excel/duckdb/postgres) y ajustes varios de docs e infra de otra sesion, agrupados aqui para no perderlos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""Lee un archivo Excel (.xlsx) a estructuras en memoria con openpyxl.
|
||||
|
||||
Funcion impura: abre un libro Excel y devuelve sus hojas como listas de Python
|
||||
(headers + rows), no como markdown. Es el espejo en lectura de
|
||||
`write_xlsx_sheets`: lo que aquella escribe desde un dict {hoja: filas}, esta lo
|
||||
recupera al mismo shape. Maneja los tipos de celda nativos de Excel (fechas a
|
||||
ISO 8601, numeros int/float, bool, None) y lee el valor calculado de las
|
||||
formulas con data_only=True.
|
||||
|
||||
No lanza: cualquier fallo (archivo inexistente, hoja inexistente, openpyxl
|
||||
ausente) se devuelve como dict {"status": "error", "error": "..."}.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
|
||||
def read_xlsx(
|
||||
path: str,
|
||||
sheet: str = None,
|
||||
max_rows: int = None,
|
||||
header: bool = True,
|
||||
) -> dict:
|
||||
"""Lee un .xlsx a estructuras en memoria (headers + rows).
|
||||
|
||||
Args:
|
||||
path: Ruta al archivo .xlsx a leer.
|
||||
sheet: Nombre de la hoja a leer. Si None (default) se leen TODAS las
|
||||
hojas del libro.
|
||||
max_rows: Maximo de filas a devolver por hoja (cuenta de filas de datos,
|
||||
sin contar la cabecera cuando header=True). None (default) = sin
|
||||
limite.
|
||||
header: Si True (default) la primera fila de cada hoja se interpreta como
|
||||
cabecera y va en "headers"; el resto va en "rows". Si False, no hay
|
||||
cabecera ("headers" es []) y todas las filas van en "rows".
|
||||
|
||||
Returns:
|
||||
Dict. En exito:
|
||||
{"status": "ok",
|
||||
"sheets": {nombre_hoja: {"headers": [...], "rows": [[...], ...]}}}
|
||||
En error:
|
||||
{"status": "error", "error": "<mensaje>"}.
|
||||
Los valores de celda se devuelven como tipos nativos de Python:
|
||||
fechas/datetimes como str ISO 8601, int/float, bool, str y None.
|
||||
"""
|
||||
if not path:
|
||||
return {"status": "error", "error": "path no puede estar vacio"}
|
||||
|
||||
abs_path = os.path.abspath(path)
|
||||
if not os.path.exists(abs_path):
|
||||
return {"status": "error", "error": f"archivo no encontrado: {abs_path}"}
|
||||
|
||||
try:
|
||||
from openpyxl import load_workbook
|
||||
except ImportError: # pragma: no cover - dependencia del entorno
|
||||
return {
|
||||
"status": "error",
|
||||
"error": (
|
||||
"openpyxl es requerido para read_xlsx. "
|
||||
"Instalar con: cd python && uv add openpyxl"
|
||||
),
|
||||
}
|
||||
|
||||
try:
|
||||
# data_only=True devuelve el valor calculado de las formulas (no la
|
||||
# formula). read_only acelera y reduce memoria en libros grandes.
|
||||
wb = load_workbook(abs_path, data_only=True, read_only=True)
|
||||
except Exception as exc: # noqa: BLE001 - el contrato del grupo es no lanzar
|
||||
return {"status": "error", "error": f"no se pudo abrir el libro: {exc}"}
|
||||
|
||||
try:
|
||||
if sheet is not None:
|
||||
if sheet not in wb.sheetnames:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": (
|
||||
f"hoja '{sheet}' no existe. "
|
||||
f"Hojas disponibles: {wb.sheetnames}"
|
||||
),
|
||||
}
|
||||
target = [sheet]
|
||||
else:
|
||||
target = list(wb.sheetnames)
|
||||
|
||||
sheets = {}
|
||||
for name in target:
|
||||
ws = wb[name]
|
||||
sheets[name] = _read_sheet(ws, max_rows, header)
|
||||
|
||||
return {"status": "ok", "sheets": sheets}
|
||||
finally:
|
||||
# En modo read_only conviene cerrar para liberar el archivo subyacente.
|
||||
wb.close()
|
||||
|
||||
|
||||
def _read_sheet(ws, max_rows, header) -> dict:
|
||||
"""Lee una hoja a {"headers": [...], "rows": [[...]]} aplicando max_rows."""
|
||||
headers = []
|
||||
rows = []
|
||||
first = True
|
||||
|
||||
for raw_row in ws.iter_rows(values_only=True):
|
||||
row = [_coerce(v) for v in raw_row]
|
||||
if header and first:
|
||||
headers = row
|
||||
first = False
|
||||
continue
|
||||
first = False
|
||||
if max_rows is not None and len(rows) >= max_rows:
|
||||
break
|
||||
rows.append(row)
|
||||
|
||||
return {"headers": headers, "rows": rows}
|
||||
|
||||
|
||||
def _coerce(value):
|
||||
"""Convierte un valor de celda openpyxl a un tipo nativo de Python.
|
||||
|
||||
Reglas: None se conserva; bool/int/float/str se conservan; fechas y
|
||||
datetimes se serializan a ISO 8601 (date a YYYY-MM-DD, datetime sin
|
||||
componente horario a YYYY-MM-DD, con hora a YYYY-MM-DDTHH:MM:SS); cualquier
|
||||
otro tipo se serializa a str.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
# bool es subclase de int: comprobarlo antes que int.
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, (int, float, str)):
|
||||
return value
|
||||
if isinstance(value, datetime.datetime):
|
||||
if value.hour == 0 and value.minute == 0 and value.second == 0:
|
||||
return value.date().isoformat()
|
||||
return value.isoformat()
|
||||
if isinstance(value, datetime.date):
|
||||
return value.isoformat()
|
||||
return str(value)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover - smoke manual
|
||||
import tempfile
|
||||
|
||||
from openpyxl import Workbook
|
||||
|
||||
tmp = os.path.join(tempfile.gettempdir(), "read_xlsx_demo.xlsx")
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Ventas"
|
||||
ws.append(["Producto", "Unidades", "Precio", "Activo"])
|
||||
ws.append(["Teclado", 12, 29.99, True])
|
||||
ws.append(["Raton", 30, 14.5, False])
|
||||
wb.save(tmp)
|
||||
|
||||
print(read_xlsx(tmp))
|
||||
print(read_xlsx(tmp, sheet="Ventas", max_rows=1))
|
||||
Reference in New Issue
Block a user