Backend de datos
IronFlock aprovisiona una base de datos privada para cada proyecto, impulsada por TimescaleDB. Tu aplicación define el esquema de datos; IronFlock crea las tablas y comienza a recopilar datos en el momento en que se agrega un dispositivo a la aplicación.
Cómo funciona
- Define tu esquema de datos en
.ironflock/data-template.yml. - Usa el SDK de IronFlock para publicar datos desde tu código edge.
- IronFlock configura automáticamente las tablas de la base de datos en cada proyecto donde se instala la aplicación.
- Los datos fluyen desde los dispositivos a través del sistema de mensajería hacia la base de datos del proyecto.
Cada proyecto obtiene su propia base de datos física — no hay intercambio de datos entre proyectos.
El usuario tiene control total sobre los datos recopilados por tu aplicación en su proyecto. Como desarrollador, no tienes acceso a estos datos.
Definir el esquema de datos
Crea un archivo data-template.yml en el directorio .ironflock/:
data:
tables:
- tablename: sensordata
columns:
- id: tsp
name: Timestamp
description: Timestamp of measurement
path: args[0].timestamp
dataType: timestamp
- id: temperature
name: Temperature
description: Temperature reading in Celsius
path: args[0].temperature
dataType: numeric
- id: humidity
name: Humidity
description: Relative humidity percentage
path: args[0].humidity
dataType: numeric
- id: device_id
name: Device ID
description: Source device identifier
path: args[0].device_id
dataType: stringOpciones de columna
| Campo | Descripción |
|---|---|
id | Identificador interno de la columna (usa tsp para columnas de marca temporal) |
name | Nombre legible de la columna que se muestra en los paneles |
description | Descripción opcional |
path | Ruta al valor en el objeto de datos publicado (por ejemplo, args[0].temperature) |
dataType | Uno de: timestamp, numeric, string, boolean |
Opciones de tabla
Además de columns, una tabla admite algunas claves opcionales que controlan cómo se describe y cómo envejecen sus datos:
data:
tables:
- tablename: sensordata
description: Lecturas ambientales de la planta
chunkTimeInterval: 1 hour
dropAfter: 30 days
columns:
# ...| Campo | Descripción |
|---|---|
tablename | Nombre de la tabla |
description | Descripción opcional, mostrada en la interfaz y usada por los agentes de IA para entender la tabla |
chunkTimeInterval | Tamaño de las particiones temporales en que se divide la tabla. Por defecto 7 days |
dropAfter | Ventana de retención — las particiones más antiguas se eliminan automáticamente |
downsample | Mantener una copia preagregada para gráficos rápidos de ventana larga — consulta Submuestreo continuo más abajo |
maintainLatestFlagFor | Columnas que identifican una entidad única — consulta Seguimiento del último estado de una entidad más abajo |
private | Ocultar esta tabla a las demás apps — consulta Compartir datos con otras apps más abajo |
chunkTimeInterval controla cómo se particionan en disco los datos de series temporales. Elígelo de modo que una partición se corresponda aproximadamente con lo que consultas de una vez: los datos de alta frecuencia recogidos cada segundo se benefician de chunks pequeños (de minutos a horas), y los datos que cambian despacio, de chunks grandes (semanas). Esto es solo el valor por defecto de la app — el propietario del proyecto puede ajustarlo después en su propio data backend.
dropAfter convierte la tabla en una ventana deslizante. Se eliminan particiones enteras más antiguas que el intervalo indicado, lo que resulta mucho más económico que borrar filas una a una. La tarea de limpieza se ejecuta con una frecuencia de dropAfter / 4, así que un registro puede sobrevivir a su caducidad hasta un cuarto del intervalo antes de que desaparezca su partición. Omite dropAfter para conservar los datos indefinidamente.
Ambos aceptan cadenas de intervalo de PostgreSQL — 30 minutes, 1 hour, 7 days, 6 months.
Submuestreo continuo
Los dashboards pueden pedir a la base de datos que agregue los datos — promedios horarios, totales diarios, recuentos por máquina. Calcularlos a partir de los registros en bruto está bien para un día y sale caro para un año. Añade downsample a una tabla y la plataforma mantiene una copia preagregada de ella, actualizada de forma continua, y responde desde esa copia las consultas de ventana larga:
data:
tables:
- tablename: sensordata
dropAfter: 30 days
downsample:
bucket: 1 minute
keepFor: 2 years
paths:
- payload.temperature
columns:
# ...| Campo | Descripción |
|---|---|
bucket | Granularidad de la copia preagregada. Por defecto 1 minute |
keepFor | Cuánto tiempo se conserva el historial submuestreado. Omítelo para conservarlo indefinidamente |
paths | Rutas de campos JSON a incluir, en la misma notación que usan los dashboards |
bucket es la resolución más fina desde la que se puede servir un gráfico — un gráfico que pida intervalos mucho más finos que este lee la tabla en bruto en su lugar. Acepta intervalos de anchura fija desde 1 second hasta 1 day que dividan el día de forma exacta (1 minute, 5 minutes, 1 hour). El valor por defecto de 1 minute sirve para prácticamente cualquier dashboard; un intervalo más grueso cuesta menos almacenamiento y menos rendimiento de escritura.
keepFor es lo que hace posibles los historiales largos. Los registros en bruto desaparecen con dropAfter, pero la copia submuestreada tiene su propia retención: conserva los datos en bruto 30 días y los submuestreados 2 años, y un board podrá seguir mostrando dos años de promedios horarios con una fracción del almacenamiento. Configúralo más largo que dropAfter — la plataforma rechaza lo contrario como una configuración errónea.
paths extiende el submuestreo a valores dentro de columnas JSON. Las columnas numéricas se incluyen automáticamente; los campos JSON hay que nombrarlos explícitamente, ya que una columna JSON no tiene un conjunto fijo de claves. Los campos no declarados siguen funcionando en los dashboards — simplemente se calculan a partir de la tabla en bruto.
Todo lo demás es automático. Cada columna numérica mantiene sus estadísticas (promedio, suma, mínimo, máximo, primer valor, último valor y un recuento de registros), agrupadas por la clave de entidad de la tabla (maintainLatestFlagFor, o el dispositivo que publica). Los dashboards no necesitan configuración alguna ni saber nada de esto: un widget consulta como siempre, y la plataforma decide en cada consulta si la copia preagregada puede responderla — recurriendo de forma transparente a la tabla en bruto cuando no puede, por ejemplo cuando un filtro hace referencia a una columna por la que la copia no agrupa.
Los cambios de esquema reconstruyen la copia. Añadir, eliminar o cambiar el tipo de una columna de una tabla submuestreada — o editar el propio bloque
downsample— reconstruye la copia preagregada a partir de la tabla en bruto. Todo lo más antiguo quedropAfterno se puede reconstruir y se pierde. Configura el bloque junto con la tabla siempre que puedas, y trata los cambios de esquema posteriores en tablas de larga vida como una decisión deliberada.
Publicar datos desde el código edge
Usa el SDK de IronFlock para enviar datos desde tu aplicación:
Python
from ironflock import IronFlock
flock = IronFlock()
flock.publish_to_table("sensordata", {
"timestamp": "2025-01-15T10:30:00Z",
"temperature": 23.5,
"humidity": 62.1,
"device_id": "sensor-001"
})Para datos de alta frecuencia, envía muchas filas en un solo mensaje en lugar de una ida y vuelta por fila usando publish_rows_to_table / publishRowsToTable (fire-and-forget) o append_rows_to_table / appendRowsToTable (devuelve el resultado de la inserción). Cada lote se inserta de forma atómica — todo o nada. Consulta la referencia del SDK para más detalles.
Tablas de transformación
Puedes definir transformaciones SQL que agreguen o procesen automáticamente tus datos en bruto:
data:
tables:
- tablename: sensordata
columns:
# ... columnas de datos en bruto ...
transforms:
- tablename: hourly_averages
materialize: true
schedule: "0 * * * *"
sql: >
SELECT
time_bucket('1 hour', tsp) AS hour,
avg(temperature) AS avg_temp,
avg(humidity) AS avg_humidity
FROM sensordata
GROUP BY hour
columns:
- id: hour
name: Hour
dataType: timestamp
- id: avg_temp
name: Average Temperature
dataType: numeric
- id: avg_humidity
name: Average Humidity
dataType: numeric| Campo | Descripción |
|---|---|
tablename | Nombre de la tabla derivada |
materialize | Si es true, los resultados se persisten como una tabla |
schedule | Expresión cron que indica cuándo se ejecuta la transformación |
sql | Consulta SQL que calcula la transformación |
columns | Definiciones de columnas para la salida |
Las tablas de transformación son accesibles en los paneles y a través del SDK, al igual que las tablas regulares.
Seguimiento del último estado de una entidad
Para tablas que representan el estado actual de entidades del mundo real — máquinas, activos, órdenes de producción — IronFlock soporta un patrón llamado seguimiento del último estado.
En lugar de sobrescribir una fila cuando algo cambia, siempre agregas una nueva fila. Tú declaras qué columnas identifican una entidad única, e IronFlock deriva la fila más reciente de cada entidad cada vez que se lee la tabla. Esto te da un historial completo de cada cambio y al mismo tiempo facilita consultar solo el estado actual.
Habilítalo en una tabla con maintainLatestFlagFor:
- tablename: machineform
maintainLatestFlagFor: ['machinename']
columns:
- id: tsp
dataType: timestamp
- id: machinename
dataType: string
- id: machinetype
dataType: string
- id: active
dataType: boolean
- id: description
dataType: stringmaintainLatestFlagFor recibe una lista de columnas que juntas identifican una entidad única. No se escribe nada en la fila misma: IronFlock indexa la tabla por esa clave de entidad más la marca temporal, y elige la fila más reciente de cada entidad en el momento de la consulta. Por eso, una fila que llega tarde o fuera de orden nunca puede dejar tras de sí una marca obsoleta.
Para consultar solo los estados actuales de las máquinas:
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESCPara ver el historial completo de una máquina específica:
SELECT * FROM machineform WHERE machinename = 'Assembly-Line-01' ORDER BY tspRara vez escribirás esa consulta a mano. Los widgets de un panel que se conectan a esta tabla tienen un interruptor latest en sus ajustes de filtro, de modo que los usuarios siempre ven los valores actuales sin trabajo adicional. Desde el SDK, solicita el mismo modo añadiendo {"latest": true} a filterAnd — consulta getHistory.
Migración desde
latest_flag: las versiones anteriores de IronFlock almacenaban una columna booleana física llamadalatest_flag. Esa columna ya no existe — el estado actual se deriva en SQL, lo que lo mantiene correcto cuando las filas llegan fuera de orden. Los paneles y las llamadas del SDK existentes que filtran porlatest_flag = truesiguen funcionando: IronFlock los reconoce y aplica el modo de último estado. El código nuevo debería usar el interruptor latest o la entrada de filtro{"latest": true}.
Eliminación lógica de registros
El modelo de solo inserción de IronFlock significa que los registros nunca se eliminan físicamente. En su lugar, usa una columna booleana deleted para marcar un registro como eliminado. Esto preserva la pista de auditoría completa mientras oculta los registros eliminados de los dashboards.
Agrega una columna deleted a cualquier tabla de entidades:
- id: deleted
name: Deleted
dataType: booleanCuando un usuario elimina un registro (por ejemplo, a través de un formulario en el panel), tu aplicación publica una nueva fila para esa entidad con deleted: true. Combinado con maintainLatestFlagFor, esta nueva fila se convierte en el último estado.
Para consultar solo los registros actuales activos (no eliminados):
SELECT * FROM (
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESC
) latest
WHERE deleted IS NULL OR deleted = falseLa comprobación de deleted se ejecuta después de haber elegido la fila más reciente de cada máquina. Ese orden importa: filtrar primero las filas eliminadas haría que la fila anterior, no eliminada, reapareciera como el estado actual.
Los widgets del panel y el SDK aplican el mismo orden automáticamente — combina el interruptor latest (o {"latest": true}) con un filtro sobre deleted y obtendrás exactamente este comportamiento. Los registros eliminados desaparecen del dashboard inmediatamente después de enviar el formulario, pero permanecen en la base de datos para fines de historial y auditoría.
Compartir datos con otras apps
Tu data backend es privado para tu app: ninguna otra app instalada en el proyecto puede ver tus tablas. Dos claves opcionales en data-template.yml cambian eso.
Para leer los datos de otra app, enumera las apps de las que quieres leer en una sección consumes: de nivel superior — junto a data:, no dentro de ella:
consumes:
- app: machine-monitor
reason: "Calcula el OEE a partir de los flujos de estado de máquina y contadores del monitor"
data:
tables:
- tablename: oee_results
columns:
# ... las tablas propias de tu app, como siempreapp es el nombre técnico de la app proveedora, o "*" (las comillas son obligatorias) para todas las apps del proyecto. reason se muestra al usuario en el diálogo de consentimiento — la declaración por sí sola no concede nada hasta que él la aprueba.
Para reservarte tablas concretas, márcalas con private: true. Todo lo que definas es compartible por defecto; una tabla o transformación privada nunca aparece en el catálogo que ven las demás apps.
data:
tables:
- tablename: measurements # compartida (por defecto)
columns: [ ... ]
- tablename: calibration_state # interna — nunca visible para otras apps
private: true
columns: [ ... ]El acceso es de solo lectura, lo concede el usuario por proyecto y es revocable en cualquier momento. Consulta Consumir datos de otras apps para el modelo completo y las llamadas del SDK que leen el historial y los flujos en vivo de una app proveedora.