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,100 @@
|
||||
---
|
||||
name: pg_upsert
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def pg_upsert(dsn: str, table: str, rows: list[dict], key_cols: list[str], update_cols: list[str] = None) -> dict"
|
||||
description: "UPSERT idempotente en lote en una tabla PostgreSQL con ownership selectivo de columnas. Construye INSERT INTO <table> (cols) VALUES %s ON CONFLICT (key_cols) DO UPDATE SET col = EXCLUDED.col, ... (o DO NOTHING) y lo ejecuta con psycopg2.extras.execute_values. update_cols=None actualiza todas menos key_cols; update_cols=[] hace DO NOTHING; lista explicita = ownership selectivo (las no listadas conservan su valor). Distingue insert vs update via el pseudo-columna xmax (RETURNING (xmax = 0) AS inserted). Valida que table y columnas casen ^[A-Za-z_][A-Za-z0-9_]*$ antes de interpolarlas; los valores van por placeholders. Commit al exito, rollback al fallo, cierre en try/finally. Devuelve {status:'ok', inserted, updated} o {status:'error', error} sin lanzar. Espejo de duckdb_upsert para Postgres. key_cols deben tener PRIMARY KEY o UNIQUE. Depende de psycopg2 (2.9.x en python/.venv)."
|
||||
tags: [postgres, postgresql, sql, upsert, idempotent, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_py_core"
|
||||
imports: [re, psycopg2]
|
||||
params:
|
||||
- name: dsn
|
||||
desc: "Cadena de conexion PostgreSQL en formato postgresql://user:pass@host:port/dbname."
|
||||
- name: table
|
||||
desc: "Nombre de la tabla destino. Validado como identificador SQL [A-Za-z_][A-Za-z0-9_]*; un nombre raro devuelve {status:'error'}. La tabla debe existir y key_cols debe tener PRIMARY KEY o UNIQUE."
|
||||
- name: rows
|
||||
desc: "Lista de dicts, un dict por fila (clave = nombre de columna). El esquema de insercion lo fija la PRIMERA fila; todas deben tener exactamente las mismas claves o se devuelve error. Lista vacia -> {status:'ok', inserted:0, updated:0}."
|
||||
- name: key_cols
|
||||
desc: "Columnas de la clave de conflicto (no vacia). Deben existir como PRIMARY KEY o UNIQUE en la tabla y estar presentes en las claves de cada fila."
|
||||
- name: update_cols
|
||||
desc: "Columnas a actualizar en conflicto. None (default) = todas menos key_cols. [] = DO NOTHING (inserta nuevas, no toca existentes). Lista = DO UPDATE SET solo esas (ownership selectivo: las no listadas conservan su valor previo)."
|
||||
output: "dict. En exito: {status:'ok', inserted:int, updated:int} (inserted = filas con xmax=0 en RETURNING, updated = filas en conflicto actualizadas). Con DO NOTHING las filas en conflicto no se devuelven por RETURNING y no cuentan en ninguno. En error (sin lanzar): {status:'error', error:str}."
|
||||
tested: true
|
||||
tests: ["test_skip_sin_pg_test_dsn", "test_identificador_invalido_devuelve_status_error", "test_inserta_filas_nuevas_cuenta_inserted", "test_conflicto_actualiza_y_cuenta_updated", "test_ownership_selectivo_no_pisa_columna_excluida", "test_do_nothing_no_actualiza"]
|
||||
test_file_path: "python/functions/infra/pg_upsert_test.py"
|
||||
file_path: "python/functions/infra/pg_upsert.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from infra.pg_upsert import pg_upsert
|
||||
|
||||
dsn = "postgresql://user:pass@localhost:5433/trends"
|
||||
# La tabla leads(email PRIMARY KEY, name TEXT, score INT) ya existe.
|
||||
|
||||
# Re-ingest 1: inserta el lead.
|
||||
print(pg_upsert(
|
||||
dsn, "leads",
|
||||
[{"email": "ana@x.com", "name": "Ana", "score": 0}],
|
||||
key_cols=["email"],
|
||||
))
|
||||
# {'status': 'ok', 'inserted': 1, 'updated': 0}
|
||||
|
||||
# Re-ingest 2: el feed trae name actualizado y score=0 (default del feed),
|
||||
# pero solo autorizamos actualizar 'name'. 'score' lo posee la DB y NO se pisa.
|
||||
print(pg_upsert(
|
||||
dsn, "leads",
|
||||
[{"email": "ana@x.com", "name": "Ana Lopez", "score": 0}],
|
||||
key_cols=["email"],
|
||||
update_cols=["name"],
|
||||
))
|
||||
# {'status': 'ok', 'inserted': 0, 'updated': 1}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Usala cuando un re-ingest periodico no debe pisar campos que ya posee la DB: pasa
|
||||
`update_cols` SIN esos campos (ownership selectivo). Tipico en pipelines de ingesta
|
||||
idempotente (catalogo, leads, precios competencia, entidades OSINT) donde una fila
|
||||
se reinserta y ciertas columnas se enriquecieron despues (score calculado, anotacion
|
||||
manual, flag derivado) y deben sobrevivir al refresco. `update_cols=None` para un
|
||||
upsert "todo" clasico, `update_cols=[]` para insertar solo filas nuevas. Es el espejo
|
||||
de `duckdb_upsert` para Postgres. Para append-only puro usa `pg_insert_rows`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Escritura real en disco (impura). `ON CONFLICT (key_cols)` solo funciona si esas
|
||||
columnas tienen **PRIMARY KEY o UNIQUE** en la tabla; sin esa restriccion Postgres
|
||||
lanza error y vuelve como `{status:'error', ...}`. La tabla debe existir de antemano
|
||||
(la funcion NO la crea — usa `pg_create_table_from_rows`).
|
||||
- **Fiabilidad de inserted/updated**: el conteo usa el pseudo-columna del sistema
|
||||
`xmax` (`RETURNING (xmax = 0)`). Es la tecnica estandar y fiable en el caso normal
|
||||
(single-writer, sin triggers raros): xmax = 0 = INSERT puro, xmax != 0 = UPDATE por
|
||||
conflicto. Caveats conocidos: (1) con `update_cols=[]` (DO NOTHING) las filas en
|
||||
conflicto NO se devuelven por RETURNING, asi que ni cuentan como insert ni como
|
||||
update — solo se reportan las filas nuevas en `inserted`; (2) si la tabla tiene
|
||||
BEFORE INSERT/UPDATE triggers, REPLICA IDENTITY o subtransacciones que tocan la
|
||||
fila, el valor de xmax puede no ser 0 en un insert real y desviar el conteo.
|
||||
- **Inyeccion SQL**: `table` y los nombres de columna se validan contra
|
||||
`^[A-Za-z_][A-Za-z0-9_]*$` antes de interpolarlos (no se pueden parametrizar
|
||||
identificadores). Un nombre con espacios, comillas, puntos o vacio devuelve
|
||||
`{status:'error'}`. Los valores de las filas siempre van por los placeholders de
|
||||
`execute_values`.
|
||||
- **Esquema fijo por la primera fila**: el conjunto de columnas de insercion lo
|
||||
determina `rows[0]`. Todas las filas deben tener exactamente las mismas claves; si
|
||||
una difiere, se devuelve error (no se hace insercion parcial).
|
||||
- **Single-statement por lote**: todo el lote va en un solo `INSERT ... VALUES %s`
|
||||
dentro de una transaccion. Si una fila viola una constraint (FK, NOT NULL en una
|
||||
columna ausente), Postgres aborta el lote entero y se hace rollback.
|
||||
- Nunca lanza: DSN invalido, tabla sin UNIQUE, tipo invalido o falta de psycopg2
|
||||
vuelven como `{status:'error', error:str}`.
|
||||
Reference in New Issue
Block a user