Gramática de fórmulas
El criterio number_check acepta fórmulas estilo Excel para comparar valores numéricos.
El grader number_check acepta fórmulas estilo Excel: un valor que empieza con = (ignorando espacios iniciales) se interpreta como una expresión aritmética en lugar de un literal, una plantilla de columna o un JSON path.
Las fórmulas se aceptan en dos lugares:
- El objetivo — el valor que se extrae de la salida del modelo (
target.scope: 'formula'). - La referencia — el valor esperado que viene del dataset.
El resultado siempre es un solo número. Sobre ese número, el grader aplica la comparación configurada (eq, ne, gt, gte, lt, lte) con su tolerancia y su epsilon de punto flotante.
Sintaxis
formula → '=' expr
expr → term (('+' | '-') term)*
term → unary (('*' | '/') unary)*
unary → '-' unary | primary
primary → number
| '(' expr ')'
| function '(' expr ')'
| path // total, claim.amount
| base '[]' ('.' path)? // items[].qty — proyección de arreglo
| '{{' column '}}' // columna del datasetOperadores
* y / tienen más precedencia que + y -; los paréntesis la sobreescriben. El menos unario es el que más liga. Los cuatro operadores binarios son asociativos por la izquierda.
Funciones
Cinco agregaciones: sum, avg, min, max y count. Los nombres son insensibles a mayúsculas (SUM( equivale a sum() y toman exactamente un argumento.
Números
Solo punto decimal: 0.5 y .5 son válidos. La coma decimal (0,5) es un error explícito — igual que cualquier coma, porque las funciones reciben un único argumento.
Rutas
Rutas con punto dentro del JSON de salida, resueltas desde la raíz: total, claim.amount. Si el campo no existe, el criterio falla nombrando el campo faltante.
Proyecciones
base[].campo lee un campo por cada elemento del arreglo base; base[] a secas lee los elementos mismos. Solo son válidas dentro de una agregación.
Columnas
{{ nombre }} lee la fila del dataset (el contexto de calificación), con la misma coerción numérica que el resto del grader: una celda con "5" cuenta como 5. Las columnas se validan al lanzar el run, con el mismo chequeo de variables desconocidas que el resto de las plantillas {{ }}.
Semántica de las agregaciones
| Forma del argumento | Comportamiento |
|---|---|
fn(ruta) | la ruta debe resolver a un arreglo; los elementos se convierten a número (count solo mide la longitud) |
fn(base[].campo) | un valor por elemento; si el campo falta o el elemento no es numérico, falla nombrando la posición (empezando en 1) |
fn(<expr con base[].x>) | la expresión se evalúa una vez por elemento del arreglo compartido: sum(items[].qty * items[].unit_price) |
| arreglo vacío | sum y count dan 0; avg, min y max fallan explícitamente |
Coerción numérica
Las mismas reglas que en el resto del grader: los números JSON se usan tal cual, las cadenas numéricas pasan por Number() ("42" es 42) y cualquier otra cosa no es un número.
Las comparaciones conservan el epsilon de punto flotante del grader (1e-9) y la tolerancia que hayas configurado. Por eso no hace falta una función round() para totales redondeados: para eso está la tolerancia.
Límites de la versión 1
Estos límites son deliberados:
- Un solo nivel de arreglo por ruta:
items[].qtysí,a[].b[].cno. - Las proyecciones no pueden aparecer fuera de una agregación.
- Las agregaciones no se anidan, y cada agregación recorre exactamente un arreglo base.
- No hay condicionales, funciones de texto,
round/abs/%, ni coma decimal.
Catálogo de errores
Los errores se dividen en dos familias según a quién señalan.
Problemas de la regla — la fórmula misma está mal escrita. Se registran como error de calificación (passed: null), no como fallo de la fila:
empty_formula, unexpected_char, unexpected_token, unclosed_paren, unknown_function, decimal_comma, nested_array, array_outside_aggregation, nested_aggregation, mixed_arrays, division_by_zero.
Problemas de los datos — la fórmula es válida pero la salida no encaja. El criterio falla (passed: false):
missing_field, not_a_number, not_an_array, element_not_number, empty_array, column_not_number, column_not_allowed.
Cada error lleva la posición exacta dentro de la fórmula, para que la interfaz pueda resaltarla, y se traduce a español e inglés. La vista previa en vivo del editor y la justificación que guarda el run muestran exactamente el mismo mensaje.
Ejemplos
La columna Resultado es el valor numérico o error: <tipo>. Columnas es la fila disponible para los operandos {{ }} (— = ninguna).
| Fórmula | JSON de entrada | Columnas | Resultado |
|---|---|---|---|
=2+3*4 | {} | — | 14 |
=(2+3)*4 | {} | — | 20 |
=-(1+2) | {} | — | -3 |
=10-3-2 | {} | — | 5 |
=.5 + 0.25 | {} | — | 0.75 |
=total | {"total": 25.5} | — | 25.5 |
=sum(items[].qty) | {"items":[{"qty":2},{"qty":1}]} | — | 3 |
=SUM(items[].qty) | {"items":[{"qty":2},{"qty":1}]} | — | 3 |
=sum(items[].qty * items[].unit_price) | {"items":[{"qty":2,"unit_price":10},{"qty":1,"unit_price":5.5}]} | — | 25.5 |
=sum(items[].duration_sec) / 60 | {"items":[{"duration_sec":90},{"duration_sec":30}]} | — | 2 |
=count(options) | {"options":["a","b","c"]} | — | 3 |
=avg(nums[]) | {"nums":[2,4]} | — | 3 |
=sum(empty) | {"empty":[]} | — | 0 |
={{base}} * 1.21 | {} | {"base":"100"} | 121 |
=0,5 | {} | — | error: decimal_comma |
=suma(items[].qty) | {} | — | error: unknown_function |
=(1+2 | {} | — | error: unclosed_paren |
=a[].b[].c | {} | — | error: nested_array |
=items[].qty * 2 | {} | — | error: array_outside_aggregation |
=sum(avg(x[])) | {} | — | error: nested_aggregation |
=sum(a[].x * b[].y) | {} | — | error: mixed_arrays |
=avg(empty) | {"empty":[]} | — | error: empty_array |
=1/0 | {} | — | error: division_by_zero |
=missing + 1 | {} | — | error: missing_field |
Ver también
- Criterios y graders — la configuración completa de
number_check.