diff --git a/docs/api-reference.md b/docs/api-reference.md index b4d1288..fb3cbfb 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -603,6 +603,13 @@ but valid "last scanned at site X" is more useful than nulling it. - `meta` — `{ provider, model }` (first) - `delta` — `{ text }` (repeated, as the model streams) - `tool` — `{ name }` (when a tool is called) +- `chart` — a `ChartSpec` (`src/lib/reports/chart-spec.ts`: + `{ id, kind, title, subtitle?, xKey, xLabel?, series[{ key, label, format }], stacked?, rows }`), + once per chart a report tool returned. Sent **instead of** handing the + chart to the model: the tool result the model sees has `charts` replaced + by `chartsAvailable: { id, title }[]`, and the model places a chart by + writing `[[chart:]]` on its own line. `id` matches + `/^[a-z0-9-]{1,64}$/`; specs with any other id are dropped. - `done` — `{ text, iterations, stoppedOnLimit }` (final) - `error` — `{ message }` (on failure) diff --git a/docs/modules.md b/docs/modules.md index 1075719..916c358 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -343,17 +343,81 @@ arithmetic** — every figure comes from a metric, every money string is pre-formatted. The "you must not compute" rule is the load-bearing correctness property of this module. -**Tools exposed** (full list in `src/lib/agent/tools.ts:125-...`): +**Tools exposed** (all read-only; definitions in `src/lib/agent/tools.ts`, +also served by the MCP server `src/mcp/server.ts`): -- `list_sites` — portfolio financials -- `site_financials` — one site's full P&L -- `attention` — current vehicle flags -- `list_purchase_orders`, `purchase_order` — PO lookup -- `list_delivery_notes`, `delivery_note` — DN lookup -- `list_vehicles`, `vehicle_fuel_logs` — fleet drill-down -- `workforce_hours` — payroll and weekly hours -- `site_expenses` / `site_income` — focused views -- `import_batches` — what the importers are doing right now +- Lookups: `list_sites`, `get_site_financials`, `get_site_trend`, + `vehicles_needing_attention`, `get_fuel_summary`, `get_vehicle_fuel_log`, + `get_pending_verification`, `get_purchase_order`, `list_purchase_orders`, + `get_delivery_note`, `list_delivery_notes`, `list_plant_hours`, + `list_fixed_costs`, `list_income_claims`, `list_employees`, `top_workers`, + `list_imports`. +- Knowledge tools (2026-09): `attendance_today` (the On-site page's grouping, + `src/lib/attendance.ts`), `supplier_balances` (the Suppliers page's roll-up, + `src/lib/metrics/supplier-balance.ts`), `delivery_note_queue` (inbox board + counts via `src/lib/dn-pipeline/board.ts` + open work-queue exceptions), + `plant_equipment_summary` (guarded `hoursBetweenReadings`, `isPlant`; + matches the Equipment page's hours and value), `rain_outlook` + (`loadRainOutlook`, as on the Overview rain card). +- Report tools (return charts): `site_report`, `portfolio_report`, + `problem_report`, `improvement_report`. Pure builders live in + `src/lib/reports/` (`site-report.ts`, `portfolio-report.ts`, + `problems.ts`, `improvements.ts`, `findings.ts`); `src/lib/reports/load.ts` + is that folder's only network code. `list_sites` and `portfolio_report` + share `computeSiteSummaries` (`src/lib/reports/site-summary.ts`). + +**Findings.** Problem and improvement reports return `Finding`s +(`{ id, severity: high|medium|low, area, title, detail, href?, site? }`), +ranked high → low, plus a `summary` count per severity. Every title and +detail is display-ready; a rule that spans sites returns one finding with +the total in its title and the per-site split in its detail, so the model +never adds counts. A site "on track" but projecting a loss is flagged as a +high-severity loss, and its status KPI carries a "Projected loss" note. + +**Charts.** A report tool returns `charts: ChartSpec[]` +(`src/lib/reports/chart-spec.ts`: `id`, `kind` bar/line/area/donut, +`title`, `xKey`, `series[{ key, label, format }]`, `rows`). The chat route +runs every tool result through `extractCharts`: the specs are streamed to +the browser as `chart` SSE events and the model receives only +`chartsAvailable: { id, title }[]` — it never sees a chart's numbers. The +model places a chart by writing `[[chart:]]` alone on a line; +`splitAnswer` (`src/lib/reports/answer-parts.ts`) swaps the token for +`` (`src/app/dashboard/chat/report-chart.tsx`, Recharts). +Tokens for unknown ids stay as text; charts the model never placed are +appended after the answer finishes. Each chart has a Table view and a +Download CSV button (raw numbers). Colours are the `--series-N` slots on +`.report-chart` in `globals.css`, validated separately for light and dark. +"Save as PDF" (browser print) pins chart size via the `.printing-answer` +print rule. + +**Prompt.** `buildSystemPrompt(now)` is built per request (the date is +Johannesburg's today, not the server's boot date). Besides the arithmetic +ban it carries a business glossary (P&G, burn vs elapsed, fleet km/L vs +plant L/h, DN/PO fulfilment, MEGA Clock), report layout rules (headline, +KPI table, charts, findings by severity; "My read" and "What I'd do next" +for `/findings` and improvement reports only), the chart-token grammar and +a rule to link `href`s. + +**Slash shortcuts.** Typed in the chat input; expanded client-side by +`src/lib/chat/slash-commands.ts` into a prompt that names the tools to +call. Typing `/` opens a menu (↑/↓ move, Tab or Enter completes, Esc +closes). The user's turn shows the short command; the model gets the +expanded prompt. `/help` is answered locally with no model call. The empty +state shows `/today`, `/portfolio`, `/problems`, `/findings` chips. + +| Command | Args | Tools it asks for | +| --- | --- | --- | +| `/site ` | site name or code (required) | `site_report`, `problem_report` for that site | +| `/portfolio` | — | `portfolio_report` | +| `/problems [site]` | optional site | `problem_report` | +| `/improvements [site]` | optional site | `improvement_report` (+ "My read") | +| `/findings` | — | `portfolio_report`, `problem_report`, `improvement_report` (+ "My read", "What I'd do next") | +| `/fuel [site]` | optional site | `get_fuel_summary` + `vehicles_needing_attention` (site: `site_report` + attention) | +| `/plant [site]` | optional site | `plant_equipment_summary` | +| `/workforce [site]` | optional site | `top_workers`, `attendance_today` | +| `/purchasing [site]` | optional site | `list_purchase_orders`, `problem_report`, `delivery_note_queue`, `supplier_balances` | +| `/today` | — | `attendance_today`, `problem_report`, `rain_outlook`, `delivery_note_queue` | +| `/help` | — | none (local) | Each tool's free-text site query is resolved through `resolveSite` first — ambiguity is returned as an error string that the model relays to the user @@ -363,7 +427,8 @@ verbatim. (vision is model-gated — see the `MiniMax-M3` note in [`operations.md §1`](./operations.md#1-environment-variables)). -**API routes called.** `POST /api/chat` (Server-Sent Events stream). +**API routes called.** `POST /api/chat` (Server-Sent Events stream, +including `chart` events — see [api-reference §12](./api-reference.md#12-chat-gated)). **Gating.** Both the page (`src/app/dashboard/chat/page.tsx:31`) and the route (`src/app/api/chat/route.ts:69-71`) 404 unless `ENABLE_CHAT=true`.