---
name: data_factory_record_run
kind: function
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def data_factory_record_run(node_id, function_id, args=None, db_path=None, trigger='manual') -> dict"
description: "Wrappea `fn run <function_id>` capturando rows/kb/duration y persiste el resultado en data_factory.db.runs. Requiere que el node_id exista previamente en nodes."
tags: [data-pipeline, factory, record-run, pipelines, subprocess, registry]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: node_id
    desc: "ID del nodo en data_factory.db.nodes que es propietario de esta ejecucion. FK enforced — debe existir antes de llamar."
  - name: function_id
    desc: "ID de funcion del registry a ejecutar (se pasa a `fn run`). Ejemplo: 'bq_query_py_infra'."
  - name: args
    desc: "Lista de args CLI adicionales que se reenvian a `fn run` despues del function_id. Default None = sin args extra."
  - name: db_path
    desc: "Ruta absoluta a data_factory.db. Default: ${FN_REGISTRY_ROOT}/apps/data_factory/data_factory.db."
  - name: trigger
    desc: "Origen de la ejecucion: 'manual'|'cron'|'dag'|'api'. Default 'manual'."
output: "dict con claves: run_id (str), status ('success'|'failed'), rows_out (int), kb_out (int), duration_ms (int), stdout (str), stderr (str)."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/data_factory_record_run.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.environ["FN_REGISTRY_ROOT"] + "/python/functions/pipelines")
from data_factory_record_run import data_factory_record_run

result = data_factory_record_run(
    node_id="bq_users_extractor",
    function_id="bq_query_py_infra",
    args=["--project", "my-gcp", "--sql", "SELECT * FROM users LIMIT 1000"],
)
print(f"run {result['run_id']}: {result['rows_out']} rows in {result['duration_ms']}ms")
# run a3f1c8e2d7b04e91: 1000 rows in 4230ms
```

## Cuando usarla

Cuando un nodo del data_factory deba ejecutar una funcion del registry y dejar trazabilidad completa (duration, rows, error) en `data_factory.db`. Usa este wrapper en lugar de llamar `fn run` directamente desde el DAG engine o desde scripts de ingesta.

## Gotchas

- `FN_REGISTRY_ROOT` debe estar en el entorno — sin ella la funcion lanza `RuntimeError` inmediato.
- El `node_id` debe existir en `nodes` antes del INSERT (FK con `ON DELETE CASCADE`). Si no existe, la funcion devuelve error claro en vez de silencio.
- `rows_out` se parsea buscando patron `^(rows|extracted|written|count)[:= ]+(\d+)` en stdout. Si la funcion destino no imprime nada con ese patron, `rows_out=0` — esto es correcto, no un bug.
- El binario `fn` se busca en `${FN_REGISTRY_ROOT}/fn`. Si no esta compilado, compilar con `CGO_ENABLED=1 go build -tags fts5 -o fn ./cmd/fn/` desde la raiz del registry.
- `db_path` apunta a la BD de la app data_factory, NO a `registry.db`.
- Solo stdlib Python — sin pandas, polars ni dependencias externas.
