demo D288
Explain a figure in plain language
A plain-language reading of the grid’s own computed figures, per KPI, per column and for the whole view, with every number reconciled against what the grid actually computed so an invented figure never reaches the screen
createAI · ai.explain · ai.insights
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.43.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.43.0/lattice-grid.min.js"></script>
<div id="grid" style="height: 420px"></div>
<div id="insights"></div>
<script type="module">
import { createAI } from '@toclocoinc/lattice-grid/modules/ai';
const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
rowKey: 'id',
columns: [
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'stage', title: 'Stage', filter: { type: 'set' } },
{ field: 'amount', title: 'Amount', type: 'number',
format: { style: 'currency', currency: 'USD', decimals: 0 }, total: 'sum' },
{ field: 'probability', title: 'Probability', type: 'number',
format: { style: 'percent', decimals: 0 } },
],
rows, // sales opportunities: region, stage, amount, probability
});
// Your model, your key, your network call. The module makes none: it hands
// ask() the grid's computed figures and validates whatever text comes back.
// Swap this body for a call to the provider of your choice.
const ask = async ({ system, messages, message }) => {
const reply = await myProvider.chat({ system, messages });
return { text: reply.text };
};
const ai = createAI(grid, { ask });
// The insights panel over the current (filtered) view. Its button generates a
// plain-language reading; filter the grid and press it again to narrate less.
ai.insights(document.getElementById('insights'));
// Explain one figure on demand: a column's profile, a KPI, or a chart datum.
// Every number in the reply is reconciled against a value the grid computed
// this render, so an ungrounded figure is stripped before it is shown.
const { text, flagged } = await ai.explain({ kind: 'column', colId: 'amount' });
// A drop-in "Explain" button beside a KPI tile, grounded on the tile's figure.
ai.attachExplain(
{ kind: 'kpi', facts: [{ label: 'Total pipeline', value: total, display: '$1,240,000' }] },
{ label: 'Explain', mount: document.getElementById('kpi'), onResult: (r) => console.log(r.text) },
);
</script>
Understand what your data is telling you, in a sentence you can trust
A table answers “what are the numbers”. It rarely answers “so what”. This turns the grid’s own figures into a short, plain-language reading of them: press Explain on a KPI, a column or the whole filtered view and get a few sentences that say what the numbers show, in words you can paste into an email. The reading is built for a person who has thirty seconds, not thirty minutes.
The reason you can act on it is that it cannot make anything up. Every number in the sentence is reconciled against a figure the grid actually computed for what is on screen right now: the totals, the averages, the counts, the spread. Anything that does not trace back to a real computation is stripped before you ever see it, and the panel tells you when it removed something rather than hiding the fact. So the narrative is only ever a plain-language rendering of figures you could check yourself, never a guess dressed up as one.
It reads the current view, so it follows your filters. Narrow the grid to one region or one stage and press Explain again, and the reading is about that narrower set, because it is drawn from the same figures the grid is showing you.
Your model, your key, nothing sent that you did not send
The grid ships no model and holds no key, and it makes no AI call of its own. You supply one small function, ask(), that wraps the model and key you already use. The grid hands it the figures it computed and the question being asked; what leaves the browser is only what your ask() sends, and you decide what that is. There is no separate service to buy and no data quietly leaving on the side: the plain-language layer is part of the grid you already have, and the privacy decision stays yours.
Because the whole thing is grounded in figures the grid computed in the browser, the sensitive part - the row values - never has to leave to get a useful reading. The figures are aggregates and profiles, not the underlying rows.
How do I explain a figure in plain language?
Load the AI module and call createAI(grid, { ask }), passing the live grid and your own model callback. Then reach for ai.explain({ kind, ... }) for a single figure, or ai.insights(element) to mount a panel that narrates the whole filtered view on a button press. Point explain at a column with { kind: 'column', colId }, at a headline number with { kind: 'kpi', facts: [...] }, or at a chart datum the same way. Each call returns the narrative text and the list of any figures the guard removed, so you can show both.
How does it stop the model inventing numbers?
The grid builds the set of figures it computed for the current render and checks the model’s reply against it. A number that matches a computed value, allowing for the rounding a model naturally does, is kept; a number that matches nothing is treated as ungrounded and taken out. That check is the whole point: it is what lets you trust a plain-language summary enough to forward it, because it can only ever repeat figures the grid stands behind.
Wire your own model in production
The demo on this page uses a stand-in ask() that runs in your browser with no key, so the page works offline: it reads the grid’s figures back and writes sentences around them. In a product you replace that one function with a call to the model you choose. Everything else on the page stays the same, and the grounding guarantee is the same whichever model you wire in, because the reconciliation happens on this side against the grid’s own numbers.