Escrito para otro

Ochenta y dos descripciones

La vez pasada adiviné qué escribiría un LLM si alguien le pidiera describir una tabla. No tuve que esperar mucho para salir de dudas.

El 14 de octubre revisé el modelo semántico de un cliente. El cliente es una empresa regional con operaciones en varios países, y cada país tiene su propia moneda. Mi propio equipo en Primus construyó el modelo, y el modelo todavía estaba en desarrollo. El equipo le pidió a Claude que escribiera la descripción de cada columna y de cada medida. Claude lo hizo a través del servidor MCP de Power BI (Power BI Authoring MCP server), que le permite leer y editar un modelo semántico. El modelo tenía 443 columnas y 82 medidas, y todas tenían descripción. Un reporte de cobertura sobre ese modelo iba a decir 100%.

Mi equipo hizo lo que la herramienta facilita. Yo probablemente haría lo mismo en su lugar. Pidieron descripciones, recibieron descripciones y pasaron al siguiente ticket. Este post no es sobre ellos.

Acá va una de las descripciones:

Cuentas con Saldo: Cantidad de cuentas de cliente distintas con saldo pendiente al corte actual.

Leela otra vez. ¿Qué aprendiste? Nada que el nombre no te dijera ya. Mi adivinanza de la vez pasada tenía la misma forma: “Contiene todos los movimientos de inventario.” Adiviné la oración. No adiviné que 41 medidas iban a tener esa forma.

Conté las 82 medidas una por una. 41 repetían el nombre como oración, igual que esa. 28 agregaban algo cierto, pero nada que un usuario pudiera usar. 13 decían algo falso sobre el negocio. Los tres grupos suman 82. Las referencias rotas y los números vencidos son otra clase de error, y aparecen en los tres grupos. Ninguna de las 82 le decía a un usuario cuándo usar la medida, ni con qué número debía cuadrar.

Los contadores y los usuarios de negocio aprobaron estas descripciones. Vuelvo a eso más adelante.

Antes de seguir

Este post cubre un modelo, un cliente, una herramienta y un revisor: yo. No comparo a Claude con otras herramientas. No corrí un experimento controlado. Estas descripciones las escribieron modelos de la misma familia que uso todos los días. También los uso para esta serie. Este post no dice que la AI sea mala.

Cuando preparé este post, la investigación apuntaba a un peligro concreto: una descripción fluida, plausible y falsa. Hay trabajo publicado justo sobre ese peligro. (Terrenzi y Ayvaz publicaron un preprint en julio de este año. Calificaron metadata escrita por un LLM en dos ejes: utilidad para la búsqueda y anclaje semántico. La versión más fácil de encontrar era la menos fiel. Apenas la mitad de sus afirmaciones tenía respaldo. La búsqueda mejoró por “expansión semántica sin respaldo, en vez de … una representación más precisa del dataset”. El juez era otro LLM, no una persona, lo cual tiene su propia ironía.) Esperaba encontrar ese peligro. Lo encontré en 13 de 82. El problema más grande estaba en las otras 69, y no lo esperaba.

Seis patrones

Clasifiqué las 82 de dos maneras. Los tres grupos dicen qué tan mal está una descripción. Los seis patrones de abajo dicen qué contiene una descripción. Una descripción puede contener varios patrones. Las 41 son las tautologías. La mayoría de las 28 son mecánica e historia. Las referencias rotas y los números de DEV pueden aparecer en cualquier grupo. Las columnas siguieron los mismos patrones. En la dimensión de sucursales, casi todas las descripciones de columna eran tautologías.

Patrón Cómo se ve Por qué le falla al usuario
Tautología “Monto gravado de los movimientos convertido a dólares” Repite el nombre
Narración del DAX “en el contexto de filtro actual” aparece unas 40 veces. “devuelve vacío si hay más de una moneda” aparece unas 35 veces El lector es el desarrollador que escribió el DAX. Hasta las advertencias útiles usan las palabras del desarrollador
Números medidos en DEV “4.700.329 filas”, “509.842 documentos” DEV tiene solo una parte de la historia. Los números estaban mal desde el primer día y envejecen con cada carga
Historia del desarrollo “Reemplaza a la tabla Transacciones” El usuario nunca vio la tabla vieja
Referencias rotas Apunta a medidas como [Total USD] o [Débito] Esas medidas no existen. Unas 15 descripciones hacen esto, en los tres grupos
Sin vocabulario común “Cobrado”, “aplicado”, “facturado”, “moneda local” Cada medida define otra vez estas palabras, con palabras distintas cada vez

Si leíste el primer post de esta serie, ya viste esta tabla con otro nombre: ROT. ROT son datos redundantes, obsoletos o triviales. Los números de DEV y la historia del desarrollo eran obsoletos el día en que alguien los escribió. Las referencias rotas apuntan a medidas de versiones tempranas del modelo. Esas medidas nunca llegaron a producción, así que eran obsoletas antes de que alguien pudiera usarlas. Las tautologías son triviales: ciertas, y vacías. Esto es ROT de metadata. Esta vez está en el campo que dice documentado.

Tres patrones llenan casi todo el campo: las tautologías, la narración del DAX y la historia del desarrollo. Las tautologías le hacen perder el tiempo al lector. También rompen una regla que DAMA escribió hace años: “no definir un término usando el mismo término”. El modelo la rompe 41 veces. La narración del DAX y la historia del desarrollo tienen la misma causa.

Quién estaba en la sala

Leé juntos la narración del DAX, la historia del desarrollo y los números de DEV. “En el contexto de filtro actual.” “Reemplaza a la tabla Transacciones.” “4.700.329 filas.” ¿Quién necesita saber eso?

El desarrollador necesita saberlo. El desarrollador estaba en la sesión con Claude. Esa persona acababa de escribir el DAX, renombrar la tabla y cargar DEV. El agente escribió para la persona que estaba en la sala, y la sala era una sesión de desarrollo. Las descripciones son la bitácora de la conversación que construyó el modelo. La bitácora quedó en el campo donde debería ir la definición.

El defecto principal está en el contexto. El modelo puede tener una parte. Nadie en esa sesión sabía qué tiene que leer un analista de cobro antes de usar la medida. Nadie lo dijo. La sesión tenía el DAX, pero no el negocio. (El propio Copilot de Microsoft para descripciones de medidas funciona igual. Su documentación dice que Copilot escribe la descripción “a partir de la fórmula DAX”. El prompt de ejemplo del propio servidor MCP pide “la lógica detrás del DAX en términos de negocio sencillos”.)

Las 13 descripciones falsas tienen la misma causa, vista desde el otro lado. Fallaron donde el nombre no ayudaba, o donde el negocio usa una palabra a su manera. El agente adivinó desde el esquema. El esquema no dice lo que esa empresa quiere decir con esa palabra. No muestro ninguna de las 13, a propósito. Las falsas son las que identificarían al cliente. Una adivinanza equivocada sobre un término local es local.

Lo que nadie escribió, porque nadie lo decidió

La revisión siguió más allá de las descripciones. Mientras las leía, llevé una segunda lista. La lista tiene reglas que el modelo aplica, o debería aplicar, y que ninguna descripción menciona.

  • Una medida de saldo usaba el filtro “mayor que cero”. El filtro correcto es “distinto de cero”. Un cliente puede tener notas de crédito abiertas o adelantos a su favor, y eso también es saldo. El desarrollador leyó saldo como facturas, porque un saldo casi siempre son facturas. La definición decía una cosa y el DAX decía otra. Escribí sobre esta clase de traición silenciosa en Cuando el código traiciona al modelo. Nadie registró la regla, y no pude encontrar quién aprobó la versión que está en el código.
  • ¿Qué tipo de cambio aplica: el de la fecha de la factura, o el de la fecha del pago? Nadie registró la respuesta.
  • Algunos documentos quedan con unos céntimos a favor del cliente, o en contra del cliente. ¿Una regla liquida esos montos? Nadie registró la respuesta. Yo tampoco la sé.
  • Los días hábiles y los feriados salen de un solo país. El modelo no tiene calendario para los otros países.

Leé esa lista otra vez. Cada punto de la lista es una decisión. Alguien tomó algunas de estas decisiones en algún momento, y nadie las registró. Las otras nunca las tomó nadie. El código eligió una opción, y el código no es una persona. Un agente no puede describir una decisión que nadie tomó. Solo puede describir el DAX que alguien escribió. (Es el mismo problema que el número sin perímetro, un nivel más arriba.)

La aprobación

Los contadores y los usuarios aprobaron las descripciones unos días antes, esa misma semana, sin un solo comentario.

No sé cómo las leyeron. Tampoco creo que el problema sean ellos. Recibieron 82 medidas, 443 columnas y párrafos sobre contexto de filtro y valores vacíos. Ese volumen no invita a leer. Bajás, ves que cada campo tiene texto, y firmás. Sospecho que eso fue lo que pasó, porque yo probablemente haría lo mismo. El requisito se cumplió.

La aprobación me preocupa más que las tautologías. Un modelo con 30% de cobertura y sin descripciones en el resto es honesto. Sabés lo que no sabés. Un modelo con 100% de cobertura y una firma encima se ve terminado. El registro muestra la aprobación. No muestra si alguien decidió algo.

Cuando planeé esta serie, la pregunta de este post era: ¿cuántas definiciones de tu catálogo escribió un LLM sin aprobación humana? Este caso contestó con cero. Alguien aprobó cada una de las 82. Entonces la pregunta estaba mal. La pregunta útil es: ¿cuánto vale una aprobación cuando nadie puede saber si alguien leyó el texto?

(DAMA advirtió sobre esto desde el otro lado. Cuando hacés ingeniería inversa de la metadata de sistemas viejos, el riesgo más grande es “no saber cuánto cuidado se puso en desarrollar y refinar las definiciones en primer lugar”. Eso era un problema del legado. Ahora lo fabricamos para nuestro propio futuro, en el catálogo que construimos este trimestre.)

La gente aprende a ignorar. Los agentes no.

Ricardo, un colega de Primus, suele traer la planta de producción a nuestras revisiones de diseño. Usa dos ideas. La primera es el poka-yoke de Shigeo Shingo: diseñar el proceso para que el error sea difícil de cometer, o fácil de ver. La segunda son los dos tipos de error de James Reason. Un error activo falla de inmediato: la línea se detiene, y alguien se da cuenta. Un error latente se queda quieto en el sistema. Falla después, en otro lugar, frente a alguien que no tuvo nada que ver.

Estas descripciones son errores latentes. En la versión de Reason, una condición latente viene de una decisión lejos de la escena, de un diseñador o un gerente. Acá la condición latente viene de mi segunda lista: decisiones que nadie tomó, debajo de un texto que dice que no falta nada. Hoy no se rompe nada.

Los usuarios van a estar bien, más o menos. Algún día van a mostrarle un número mal a su jefe. Aprenden qué significa de verdad “Factura Cobrada” en esa empresa. También aprenden una segunda lección: que las descripciones no vale la pena leerlas. Después el analista se va de la empresa, y el conocimiento se va también. ¿Qué saldo deja afuera las notas de crédito? ¿Quién en Crédito y Cobro puede contestar? El analista nuevo abre el modelo, y solo quedan las descripciones. En ese momento, el error latente falla.

El agente LLM es ese analista nuevo, todas las mañanas. Cada sesión arranca de cero. El agente abre el modelo. Lee que la tabla de movimientos tiene 4.700.329 filas, o que una medida cuadra contra [Total USD]. Cree las dos cosas, porque para eso existen las descripciones. Las dos cosas son falsas. El conteo sale de un DEV recortado, y la medida no existe. El analista nuevo se quema una vez y deja de confiar en el campo. El agente recibe el mismo texto en cada sesión, con la aprobación del catálogo y ninguna señal de error. No aprende como aprendió el analista. En el mejor de los casos, alguien escribe la lección para el agente. Pero registrar esa lección era el trabajo de la descripción desde el principio.

Excluí los números vencidos y las referencias rotas. Las otras 69 descripciones no pueden engañar al agente. Le cuestan algo menos visible: tokens. Este texto no miente sobre el negocio, y tampoco ayuda. Lo pagás una vez cuando lo escribís. Lo pagás otra vez cada vez que un agente carga el modelo, para siempre. Nadie decidió que estuviera ahí.

Lo que debería decir una descripción

La reescritura de las descripciones me dio una receta. Es tosca, pero funciona, y tiene cinco líneas:

  1. Qué es en el negocio, con el vocabulario de la propia empresa. Esta línea da el significado.
  2. Cuándo usarla, cuándo no usarla, y qué usar en su lugar. Esta línea da el perímetro.
  3. La trampa: valores centinela, filtros que sacan filas, moneda. Esta línea también da el perímetro.
  4. Contra qué cuadra: el reporte o la cifra del ERP con la que tiene que coincidir. Esta línea da una verificación.
  5. Quién decide: el dueño de la regla. Escribí el dueño donde el dueño cambia. Normalmente es en la tabla, una vez para todas sus medidas. Escribilo en una sola medida solo cuando esa medida es la excepción.

El mismo dueño en 82 medidas es ruido. Pero sin un dueño, nadie podía contestar preguntas sobre el filtro “mayor que cero”.

Las ideas de Ricardo también aplican a la receta. El orden fijo es un estándar. El significado va primero para que el lector pueda parar temprano. El dueño va al final porque solo lo necesitás cuando algo se ve mal. La propia guía de Microsoft coincide en poner el significado primero. Agrega que Copilot lee solo los primeros 200 caracteres. La línea cuatro hace que la descripción se pueda verificar. La línea cinco le da al próximo filtro “mayor que cero” una persona que lo tiene que aprobar.

Acá va una medida, antes y después. Cambié los nombres y el campo del ERP.

Factura Cobrada

Antes (escrita por el agente): Monto total cobrado y aplicado directamente contra facturas en las liquidaciones del contexto de filtro actual. Devuelve vacío si hay más de una moneda en el contexto.

Después: Capital de las facturas a crédito que los clientes pagaron en caja con un recibo de dinero, en la moneda de la factura y por fecha del recibo. No incluye intereses ([Intereses Liquidados]), el vuelto, las ventas de contado ni las notas de crédito aplicadas, que no son cobros. Sirve para medir la recuperación de cartera por período, sucursal o cliente. Para todo lo que entró a caja por cualquier medio de pago, ver [Efectivo Recibido]. Límites: con varias monedas en el reporte se muestra vacía (filtrar una moneda); no tiene versión en dólares ni en moneda local. Fuente: la tabla de liquidaciones del ERP, campo de total de factura. Por factura, cuadra con [Cobros Aplicados], salvo intereses y reversiones. Cifra de control del ERP: por definir con Crédito y Cobro.

La advertencia de la moneda sobrevivió. El agente también la escribió: “Devuelve vacío si hay más de una moneda en el contexto.” El dato es el mismo, pero el lector es otro. La versión del agente te dice qué hace el DAX. La versión nueva te dice qué hacer con eso.

La última línea muestra que la mitad de la línea cuatro sigue abierta. La descripción nombra la medida con la que cuadra, pero no la cifra del ERP. La descripción dice este hueco abiertamente. También nombra a quién le toca cerrarlo: Crédito y Cobro, el dueño de la línea cinco. Escribimos el dueño en la tabla. Aparece acá solo porque tiene trabajo pendiente. La descripción del agente no menciona ningún hueco, porque el DAX no muestra ningún hueco.

¿Cómo escribimos el “después”? Usamos AI, pero de otra manera que en las 82. Primero escribí dos o tres páginas de contexto del negocio: dónde opera la empresa, cómo vende y cómo cobra, y sus límites técnicos. Este contexto es mi lectura del negocio como arquitecto de datos. DAMA describe ese rol como el puente entre la estrategia del negocio y la tecnología. Después un agente perfiló los atributos principales con SQL. Le dimos a la AI la receta, el contexto y el perfil. Le pedimos preguntas en vez de descripciones. Devolvió unas 45 preguntas. El desarrollador senior y yo contestamos un poco más de 20. No sabíamos las respuestas de las otras 20 y tantas. Una hora con los data stewards (la gente del negocio que responde por cada dato) y TI contestó todas menos cuatro. Esas cuatro se fueron a investigar. El resultado es la versión que vamos a liberar pronto. Esta vez, el negocio estaba en la sala.

El “después” es unas cinco veces más largo, así que cuesta unas cinco veces los tokens. Copilot lee poco más que la primera oración, por eso el significado va ahí. El analista y el agente leen el resto. Esos tokens compran algo. El texto que no ayuda desperdicia tokens, en cada sesión.

En casa de herrero

En Primus Data, la mayoría de las reglas de negocio viven en plata, en dos tablas de metadata. Una tabla tiene las reglas, y la otra tiene sus dependencias. Cada regla tiene columnas de gobierno: dominio, entidad, dueño y fechas. También tiene tres campos: un nombre, una descripción en términos de negocio para el usuario, y la lógica en código o pseudocódigo para el desarrollador. Mantenemos estas tablas como parte del entregable. Si escribís un procedimiento almacenado, una vista o un notebook, también entregás los INSERT y UPDATE al repositorio de reglas.

Construimos el repositorio para gobierno. Su propósito es que el pedido del negocio y nuestro código digan lo mismo. Hace poco consiguió un segundo trabajo. Los agentes leen las reglas para entender qué hace el código, sin leer el código.

Nuestro repositorio tiene dos campos: uno para el usuario, otro para el desarrollador. El agente que escribió las 82 descripciones tenía un campo y dos lectores. Escribió para el lector que estaba en la sala. Nosotros ya tenemos campos separados para los dos lectores, pero solo en plata.

Mi propia práctica tiene un hueco. El filtro “mayor que cero” no estaba en plata. El desarrollador lo aplicó en DAX, en el modelo semántico, una capa más allá del alcance de nuestra práctica. El repositorio nunca vio el filtro. Así que nadie lo aprobó, y nada lo describió. No vi ese hueco antes de esta revisión.

El arreglo es cambiar lo que la AI lee cuando escribe descripciones. Una descripción desde el DAX es una adivinanza. Una descripción desde reglas que el negocio aprobó es una traducción. La herramienta es la misma, y solo cambia la fuente. El agente deja de inventar significado. Traslada una decisión que alguien tomó, con el dueño ya adjunto. El repositorio de reglas ya hace esto con nuestros modelos semánticos. Un desarrollador y una AI trabajan juntos en él, no un agente solo. El filtro en DAX muestra el límite: el método solo funciona con las reglas que llegan al repositorio.

Cuando una regla no está en el repositorio, la AI tiene otro trabajo. Hace preguntas en vez de escribir descripciones. Así escribimos el “después”, y las preguntas encontraron más que descripciones. Nuestro objetivo era arreglar texto. Pero cuatro o cinco respuestas cambiaron reglas en el repositorio, y el código cambió con ellas. Eran requisitos que refinamos tarde. Las cuatro preguntas abiertas se parecen. En esos lugares, el software no hace lo que los stewards creían. Los dueños de producto tienen que decidir qué cambiar: el modelo de datos, el software o el proceso de negocio. Estos son puntos de mi segunda lista, y llegaron puntuales.

Terrenzi y Ayvaz encontraron la misma palanca en su investigación. Cuando el modelo reescribió la metadata a partir de un perfil de los datos reales, las afirmaciones respaldadas subieron de más o menos la mitad a más del 80%. Anclar funciona. Nuestro repositorio ancla un nivel más arriba: en lo que alguien decidió que significan los datos.

“¿Aprobadas por quién?” es una pregunta justa acá, después de lo que dije de las 82. Un desarrollador escribe cada regla a partir del requisito y del código. El dueño de producto aprueba las reglas en lotes chicos, una entidad o un grupo de medidas a la vez. Cada lote viene después de una revisión de código con los data stewards. Las reglas viven en SQL, y la versión aprobada vive en un documento versionado. Al final de cada sprint, un agente revisa que los dos sigan diciendo lo mismo. Alguien también puede firmar un lote chico sin leerlo. Pero eso cuesta mucho más después de una hora de discusión sobre esas mismas reglas con los stewards.

Tu planilla del lunes

¿Quién escribió esta definición? Treinta minutos, un modelo.

  1. Elegí las diez medidas que tus usuarios o tu agente usan más. Ordenalas por uso, no alfabéticamente.

  2. Marcá cada medida con una de cinco etiquetas:

    • tautología: repite el nombre.
    • mecánica: explica el DAX o la carga de datos.
    • historia: cuenta cómo alguien la construyó.
    • falsa: dice algo falso sobre el negocio.
    • útil.

    Aparte, marcá todo lo vencido: números de DEV, o referencias a medidas que no existen. El texto vencido engaña a un agente.

  3. En las útiles, revisá las primeras cuatro líneas de la receta: significado, cuándo usarla, la trampa y contra qué cuadra. Contá cuántas tienen las cuatro.

  4. Agregá cuatro propiedades como metadata, no como prosa: author_type (humano, AI, editada por AI), reviewed_by, reviewed_on y owner. En Power BI, usá una anotación. En otras herramientas, usá una etiqueta. Un botón de Aceptar no alcanza:

    • Ninguna herramienta que encontré registra quién revisó el texto.
    • Un agente que edita por MCP nunca ve el botón.
    • La historia de Git (si tu modelo está en TMDL) muestra al desarrollador, no al negocio.

    owner y reviewed_by son personas distintas. El revisor revisó el texto. El dueño decide la regla. Poné owner en cada dimensión. Ponelo una vez en la tabla que agrupa las medidas del proceso. Ponelo en una medida solo cuando otra persona es dueña de esa regla. Por ejemplo, en un modelo de cobro, un conteo de clientes puede ser del dueño del cliente, no de cobro.

  5. Opcional, para los desarrolladores: al inicio del DAX de cada medida con una regla de negocio, agregá un comentario de una línea. El comentario dice quién aprueba los cambios. Los usuarios nunca lo ven, y Copilot ignora los comentarios del DAX cuando escribe descripciones. Pero el agente que edita el modelo lee la expresión. Así la advertencia le llega al lector que estaba en la sala.

  6. Reportá la cobertura como dos números. El primero cuenta las descripciones que revisó una persona que conoce el negocio. El segundo cuenta todo lo demás. Solo el primer número cuenta. (La métrica de calidad de la documentación de DAMA ya tiene dos partes. La parte automática es “el porcentaje de atributos que tienen definiciones”. La parte manual es una “encuesta aleatoria o completa”. Los reportes de cobertura corren solo la parte fácil.)

  7. Poné toda regla del DAX que filtra, deriva o convierte en un repositorio de reglas, con su dueño. Si no tenés repositorio, empezá uno en SQL, Excel o Markdown. Con el prompt correcto, una AI puede leer tu SQL, tus notebooks y tu DAX y encontrar las reglas de negocio que tienen. El resultado es un primer borrador para que los dueños decidan. Si una regla no está en el repositorio, nadie la decidió.

  8. Antes de que un agente lea el modelo, dale a cada medida que va a ver un nombre en reviewed_by. Dale también un owner, propio o de su tabla.

Mi regla práctica: si menos de la mitad de las diez tiene las primeras cuatro líneas, el número de cobertura es decoración.

El último post de esta serie pregunta qué te debe ese dueño una vez que un agente lee sus reglas.

¿Quién escribió esa descripción?

¿Quién decidió que la descripción de “Cuentas con Saldo” repitiera su nombre? Nadie. ¿Quién decidió que el saldo dejara afuera las notas de crédito del cliente? Un desarrollador, sin saber que estaba decidiendo. Nadie lo aprobó. ¿Quién aprobó 82 descripciones? Todos, yo incluido. Era la entrega de mi equipo. El registro no dice qué decidió cada uno. El requisito se cumplió.

El primer post fue sobre datos que pesan. El segundo post fue sobre datos que compiten por la atención del agente. Este post cierra la primera parte de la serie. Su tema son datos bien vestidos: documentados, aprobados, al 100%, y escritos para otro.

El próximo post mira el otro lado: no todo lo que parece ROT es ROT.

Este es el tercero de diez posts de ¿Quién lo decidió? La nube y la AI abarataron crear, copiar y guardar datos. No abarataron decidir.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *