demo D292
AI Project Risk Summary: Grounded Delivery Risk From Gantt and Kanban in Angular
Turn a project plan and a board into the one line a delivery lead wants: how many tasks are at risk on the critical path, how the schedule and cost are tracking, and how many service limits have slipped, each figure computed by the plan and the board and reconciled so no number is invented, with task names and money held back unless you opt in
createAI · ai.riskSummary(sources)
This turns a project plan and a board into the one line a delivery lead wants to read: how many tasks are at risk on the critical path, how the schedule and cost are tracking, and how many service limits have slipped. Every figure is computed by the plan and the board, then reconciled so a number the model did not ground is stripped before you see it.
This is the Angular version. A standalone component takes the whole configuration through one config input, surfaces grid events as outputs, and exposes the live grid on a getter for anything the inputs do not cover.
The configuration
import { Component, signal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createHeadlessGrid } from '@toclocoinc/lattice-grid';
import { createAI } from '@toclocoinc/lattice-grid/modules/ai';
// The Gantt and Kanban modules are separate; the AI layer imports neither.
// You pass their already-computed outputs and it reads them duck-typed, so
// this sample needs no grid or viewer component of its own, and no factory
// provider beyond what its own AppComponent uses directly.
@Component({
selector: 'app-root',
standalone: true,
template: '<div>{{ text() }}</div>',
})
export class AppComponent {
text = signal('');
constructor() {
// Your model, your key, your network call. The module makes none: it
// hands ask() only the aggregate figures below and validates the text
// that comes back.
const ask = async ({ system, messages }: any) => {
const reply = await (window as any).myProvider.chat({ system, messages });
return { text: reply.text };
};
const headlessGrid = createHeadlessGrid({ rowKey: 'id' });
const ai = createAI(headlessGrid, { ask, tools: false });
// The plan and the board have already done the maths: the schedule
// reports its critical path, earned value reports SPI/CPI, the board
// reports its own SLA breach and warning counts. None of this is asked
// of the model.
const schedule = (window as any).gantt.schedule;
const earnedValue = (window as any).gantt.earnedValue({ statusDate: (window as any).statusDate });
const breaches = (window as any).board.sla.breaches();
const warnings = (window as any).board.sla.warnings();
// One grounded line: "3 tasks at risk on the critical path, SPI 0.66, 2
// SLA breaches." Every figure is reconciled against the computed values
// above, so a number the modules did not produce is stripped before it
// is shown.
ai.riskSummary({ schedule, earnedValue, breaches, warnings }).then(({ text }: any) => this.text.set(text));
}
}
bootstrapApplication(AppComponent);
The status update, without the status meeting
A plan and a board hold everything you need to know whether delivery is on track, but reading it means opening two tools and doing the arithmetic in your head. This writes the summary instead: one plain line that leads with the tasks still open on the critical path, the schedule and cost performance, and the service limits that have already slipped. It is the sentence a delivery lead would say out loud, produced from the numbers rather than a feeling about them.
The point is where the numbers come from. The plan is scheduled by the project module, which reports its critical path and its earned-value indices; the board is aged by the board module, which reports its own breach and warning counts. Those computed outputs are the ground the summary must stand on. Every figure in the line is checked back against them, so a number that was not computed is removed before it reaches you. The model writes the sentence; it does not invent the arithmetic.
Trust is the default, not a setting. A summary carries aggregates only unless you say otherwise: the counts and the indices, never a task name or a money figure. When the audience is right for more, you add the at-risk task names or the cost figures on purpose, and the summary reports exactly what it exposed.
How do I summarise delivery risk?
Hand the AI layer the outputs the plan and the board have already produced: the schedule, the earned value for a status date, and the board’s breach and warning lists. Call ai.riskSummary({ schedule, earnedValue, breaches, warnings }), or the { kind: 'risk' } target of explain, and you get the grounded line back. Your own model, wrapped in ask, writes the words; the grid holds no key and makes no call of its own, and hands the model only the aggregate figures. To widen what the summary may say, pass includeTaskNames or includeCost, each reported back in meta.exposed so the exposure is never silent.