Merge gitea/main into the Motion branch

Motion was written against a base five commits behind main, so the
integration is the interesting part of this commit:

- The migration is renumbered 0014 -> 0015. Main shipped
  0014_piggy_conversations, and two migrations sharing an index is a
  journal that applies one of them.
- The seed-idempotency gate keeps main's all-tables diff rather than the
  motion_templates counter this branch added; the general check subsumes
  the specific one.
- Nav gains a Motion group alongside main's new Workspace group, and
  Piggy keeps the mark main gave it.
- Stat keeps main's container-scaled figure, which already carries the
  min-w-0 this branch added for the same reason.
- Piggy's page labels keep main's refusal wording for the four pages with
  no tool of their own, and gain the three Motion routes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 18:30:45 -07:00
149 changed files with 37440 additions and 3502 deletions
+481 -34
View File
@@ -26,6 +26,7 @@
* leaves this process.
*/
import {
ACCOUNT_SIDES,
CONSUMING_ALLOCATION_STATUSES,
DEMAND_OPEN_STAGES,
DEMAND_STAGES,
@@ -48,6 +49,7 @@ import {
accounts,
allocations,
capacityCommitments,
contacts,
demandDeals,
engagementArtifacts,
engagements,
@@ -57,7 +59,19 @@ import {
type Database,
} from '@pig/db';
import { CalendarService } from '@pig/api/src/services/calendar';
import { and, desc, eq, gte, ilike, inArray, isNotNull, isNull, or, type SQL } from 'drizzle-orm';
import {
and,
count,
desc,
eq,
gte,
ilike,
inArray,
isNotNull,
isNull,
or,
type SQL,
} from 'drizzle-orm';
import { z } from 'zod';
import { likeFragment } from './chat-tools';
import { piggyPageGuide, type PiggyPageToolName } from './page-routes';
@@ -94,8 +108,111 @@ const TRUNCATION_NOTE =
'book — present them as a lower bound, not as the whole.';
/** Prefixes a count the model must not read as exact. */
function atLeast(count: number, truncated: boolean): string {
return truncated ? `at least ${count}` : `${count}`;
export function atLeast(rows: number, truncated: boolean): string {
return truncated ? `at least ${rows}` : `${rows}`;
}
/**
* What a result covers, and what it was drawn from.
*
* Measured in production on /capacity, an hour before this was written. Asked
* "How many capacity commitments are on the book?", the model called
* `pig_get_idle_capacity` — the only tool that page offers — and answered "3".
* The book held 5. The tool filters to blocks at least 25% unsold, so 3 was the
* size of a filter; nothing in the payload said so, and reading the length of
* the list it had been handed as the size of the book was the only reading the
* data supported.
*
* The system prompt already forbade exactly that, naming this exact tool, and
* the model did it anyway. Prompting a 30B model out of a mistake its data
* invites does not work, so the data stopped inviting it: every result here
* that carries a count or a collection carries this object beside it, naming
* the filter, the denominator it was drawn from, and how much of the matched
* set is actually listed. A filtered count is therefore never the only number
* in its own result.
*
* One shape to learn rather than one per tool — a different shape per tool is
* how this happened. The result's primary subject gets a top-level `scope`;
* every other count or collection in the same payload gets its own, nested
* where the payload already groups it and named `<field>Scope` where it does
* not. And `summary` restates the numbers as prose on purpose — it is the
* field a small model quotes, and a figure it has to assemble out of three
* other fields is a figure it will assemble wrongly.
*
* `filters` is not decoration either. This product has shipped three different
* idle figures across three surfaces because each applied its own threshold, so
* a result that does not name the threshold it used cannot be reconciled with
* the screen beside it.
*/
export interface ResultScope {
/** The whole scope in one sentence, figures included. Quote this. */
summary: string;
/** What made a row match, as a clause: "are at least 25% unsold". */
covers: string;
/** How many rows matched. Never the answer to "how many are there". */
matched: number;
/** The set `matched` was drawn from. This is the total. */
total: number;
/** What `total` counts, as a noun phrase. */
totalLabel: string;
/** How many of `matched` this payload lists. The rest are counted only. */
listed: number;
/** Every filter applied, named, so a threshold is never invisible. */
filters: Record<string, string | number | boolean | null>;
/** True when a read hit its row cap, so both figures are lower bounds. */
truncated: boolean;
}
export function resultScope(input: {
covers: string;
matched: number;
total: number;
totalLabel: string;
listed: number;
filters?: Record<string, string | number | boolean | null>;
truncated?: boolean;
}): ResultScope {
const { covers, matched, total, totalLabel, listed } = input;
const filters = input.filters ?? {};
const truncated = input.truncated ?? false;
// Nothing was filtered out, so `matched` IS the total and saying otherwise
// would teach the model to distrust a figure that is exact.
const unfiltered = matched === total && Object.keys(filters).length === 0;
const listedClause = listed > 0 ? `; ${listed} listed here` : '';
// Both figures are hedged together when a read was cut. Hedging only the
// total would present a capped `matched` as exact, which is the same class of
// overstatement this whole object exists to stop.
const summary = unfiltered
? `All ${atLeast(total, truncated)} ${totalLabel}${listedClause}.`
: `${atLeast(matched, truncated)} of ${atLeast(total, truncated)} ${totalLabel} ` +
`${covers}${listedClause}. That is a filtered count — the total is ` +
`${atLeast(total, truncated)} ${totalLabel}.`;
return {
summary: truncated ? `${summary} ${TRUNCATION_NOTE}` : summary,
covers,
matched,
total,
totalLabel,
listed,
filters,
truncated,
};
}
/** The denominator every commitment figure in this file is drawn from. */
const COMMITMENTS_LABEL = 'live capacity commitment(s) on the book';
/** The denominators the two pipelines are drawn from. */
const DEMAND_DEALS_LABEL = 'demand deal(s) on the book';
const SUPPLY_DEALS_LABEL = 'supply deal(s) on the book';
/** The denominators the two party tables are drawn from. */
const ACCOUNTS_LABEL = 'account(s) on the book';
const CONTACTS_LABEL = 'contact(s) in the CRM';
/** One whole-table count, for use as a denominator. */
function rowCount(rows: readonly { value: number }[]): number {
return rows[0]?.value ?? 0;
}
/**
@@ -241,9 +358,11 @@ function pageTool(db: Database, name: PiggyPageToolName): AgentTool {
return defineTool({
name,
description:
'Read a bounded overview of the PIG workspace: book margin and utilisation, open ' +
'deal counts on both sides, and the worst idle capacity. This cannot inspect the ' +
'filesystem or external systems.',
'Read a bounded overview of the PIG workspace: how many accounts (by side) and ' +
'contacts are on the book, book margin and utilisation, how many deals exist and how ' +
'many are open on each side, and the worst idle capacity. Counts only — it returns no ' +
'account, contact or deal rows, and it cannot see the team, settings, imports or ' +
'facts. This cannot inspect the filesystem or external systems.',
inputSchema: noInput,
execute: async () => readWorkspaceSummary(db),
});
@@ -382,9 +501,24 @@ async function readMarginSummary(db: Database): Promise<unknown> {
headline:
`Revenue ${formatCents(totals.revenueCents)} against cost ${formatCents(totals.costCents)}; ` +
`gross margin ${formatCents(totals.grossMarginCents)} (${percent(totals.grossMarginPct)}) ` +
`at ${percent(totals.utilisation)} utilisation across ` +
`${atLeast(blocks.length, truncated)} live commitment(s).` +
`at ${percent(totals.utilisation)} utilisation across all ` +
`${atLeast(blocks.length, truncated)} ${COMMITMENTS_LABEL} — the whole book, unfiltered. ` +
`The ${largest.length} largest by cost are listed; the book holds ` +
`${atLeast(blocks.length, truncated)}.` +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
/**
* Unfiltered, and the only tool here that is: `matched` equals `total`, so
* `liveCommitments` below is a real answer to "how many are on the book".
* `largestBlocks` is still a slice, which is what `listed` is for.
*/
scope: resultScope({
covers: 'are live',
matched: blocks.length,
total: blocks.length,
totalLabel: COMMITMENTS_LABEL,
listed: largest.length,
truncated,
}),
truncated,
totals: {
revenueCents: totals.revenueCents,
@@ -408,13 +542,27 @@ async function readMarginSummary(db: Database): Promise<unknown> {
};
}
/**
* The threshold and horizon this tool filters on.
*
* The same defaults the API and MCP use — and NOT the same as the workspace
* summary's worst-idle list, which takes any block with idle hours at all.
* Both are correct for what they answer and they return different counts, so
* each states its own threshold in `filters` rather than leaving the reader to
* reconcile two figures that were never the same figure.
*/
const IDLE_THRESHOLD_PCT = 0.25;
const IDLE_WITHIN_DAYS = 30;
/** Idle blocks, on the same defaults the API and MCP use: 25% within 30 days. */
async function readIdleCapacity(db: Database): Promise<unknown> {
const now = new Date();
const horizon = new Date(now.getTime() + 30 * 86_400_000);
const horizon = new Date(now.getTime() + IDLE_WITHIN_DAYS * 86_400_000);
const { blocks, truncated } = await readLiveBlocks(db, now);
const idle = blocks
.filter((block) => block.startsAt <= horizon && 1 - block.margin.utilisation >= 0.25)
.filter(
(block) => block.startsAt <= horizon && 1 - block.margin.utilisation >= IDLE_THRESHOLD_PCT,
)
.map((block) => ({
block,
idleGpuHours: block.margin.idleGpuHours,
@@ -424,19 +572,42 @@ async function readIdleCapacity(db: Database): Promise<unknown> {
.sort((a, b) => b.idleCostCents - a.idleCostCents);
const totalIdleCostCents = idle.reduce((sum, row) => sum + row.idleCostCents, 0);
const listed = idle.slice(0, EXEMPLARS);
const filter =
`are at least ${IDLE_THRESHOLD_PCT * 100}% unsold and start within ` +
`${IDLE_WITHIN_DAYS} day(s)`;
return {
/**
* The sentence the production defect was answered from, so it carries the
* denominator first and the filtered figure second. "3" alone was true of
* the filter and false of the book; "3 of 5" cannot be misread as 5.
*/
headline:
(idle.length === 0
? 'No live block is more than 25% unsold within the next 30 days.'
: `${atLeast(idle.length, truncated)} block(s) at least 25% unsold within 30 days, ` +
`${formatCents(totalIdleCostCents)} of capacity bought and not yet earning.`) +
? `None of the ${atLeast(blocks.length, truncated)} ${COMMITMENTS_LABEL} ${filter}.`
: `${idle.length} of ${atLeast(blocks.length, truncated)} ${COMMITMENTS_LABEL} ${filter}, ` +
`${formatCents(totalIdleCostCents)} of capacity bought and not yet earning. ` +
`${idle.length} is a filtered count — the book holds ` +
`${atLeast(blocks.length, truncated)} live commitment(s) in total.`) +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
scope: resultScope({
covers: filter,
matched: idle.length,
total: blocks.length,
totalLabel: COMMITMENTS_LABEL,
listed: listed.length,
filters: { idleThresholdPct: IDLE_THRESHOLD_PCT, withinDays: IDLE_WITHIN_DAYS },
truncated,
}),
truncated,
thresholdPct: 0.25,
withinDays: 30,
/** The denominator, repeated as a bare field: this is the size of the book. */
liveCommitments: blocks.length,
thresholdPct: IDLE_THRESHOLD_PCT,
withinDays: IDLE_WITHIN_DAYS,
idleBlocks: idle.length,
totalIdleCostCents,
blocks: idle.slice(0, EXEMPLARS).map((row) => ({
blocks: listed.map((row) => ({
name: row.block.name,
gpuType: row.block.gpuType,
gpuCount: row.block.gpuCount,
@@ -455,7 +626,10 @@ async function readIdleCapacity(db: Database): Promise<unknown> {
// ---------------------------------------------------------------------------
async function readPipeline(db: Database): Promise<unknown> {
const [demandRead, supplyRead] = await Promise.all([
// The two whole-table counts are the denominators. Without them "12 open
// demand deals" is a filtered count with nothing to be filtered from, and
// "how many deals do we have" is answered with the number of open ones.
const [demandRead, supplyRead, demandAll, supplyAll] = await Promise.all([
db
.select()
.from(demandDeals)
@@ -466,10 +640,17 @@ async function readPipeline(db: Database): Promise<unknown> {
.from(supplyDeals)
.where(inArray(supplyDeals.stage, [...SUPPLY_OPEN_STAGES]))
.limit(SCAN_LIMIT + 1),
db.select({ value: count() }).from(demandDeals),
db.select({ value: count() }).from(supplyDeals),
]);
const { rows: demand, truncated: demandTruncated } = bounded(demandRead);
const { rows: supply, truncated: supplyTruncated } = bounded(supplyRead);
const truncated = demandTruncated || supplyTruncated;
const demandTotal = rowCount(demandAll);
const supplyTotal = rowCount(supplyAll);
const demandListed = Math.min(demand.length, EXEMPLARS);
const supplyListed = Math.min(supply.length, EXEMPLARS);
const openStages = 'are at an open stage (not closed, not lost)';
// Total contract value where it is known, annual value otherwise: a deal
// valued only by ACV is still worth counting, and treating it as zero would
@@ -479,13 +660,35 @@ async function readPipeline(db: Database): Promise<unknown> {
return {
headline:
`${atLeast(demand.length, demandTruncated)} open demand deal(s) worth ` +
`${formatCents(demandValueCents)} and ${atLeast(supply.length, supplyTruncated)} ` +
'open supply deal(s).' +
`${demand.length} of ${atLeast(demandTotal, demandTruncated)} ${DEMAND_DEALS_LABEL} are open, ` +
`worth ${formatCents(demandValueCents)}, and ${supply.length} of ` +
`${atLeast(supplyTotal, supplyTruncated)} ${SUPPLY_DEALS_LABEL} are open — ` +
`${demand.length + supply.length} of ${atLeast(demandTotal + supplyTotal, truncated)} ` +
'deal(s) on the book in all. Those are open-stage counts, not the size of either pipeline.' +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
// Both sides together, so a question about "deals" has a denominator too.
scope: resultScope({
covers: openStages,
matched: demand.length + supply.length,
total: demandTotal + supplyTotal,
totalLabel: 'deal(s) on the book, demand and supply together',
listed: demandListed + supplyListed,
filters: { stages: 'open only' },
truncated,
}),
truncated: { demandDeals: demandTruncated, supplyDeals: supplyTruncated },
demand: {
scope: resultScope({
covers: openStages,
matched: demand.length,
total: demandTotal,
totalLabel: DEMAND_DEALS_LABEL,
listed: demandListed,
filters: { stages: [...DEMAND_OPEN_STAGES].join(', ') },
truncated: demandTruncated,
}),
openDeals: demand.length,
totalDeals: demandTotal,
valueCents: demandValueCents,
byStage: countByStage(demand.map((deal) => deal.stage)),
largest: [...demand]
@@ -499,7 +702,17 @@ async function readPipeline(db: Database): Promise<unknown> {
})),
},
supply: {
scope: resultScope({
covers: openStages,
matched: supply.length,
total: supplyTotal,
totalLabel: SUPPLY_DEALS_LABEL,
listed: supplyListed,
filters: { stages: [...SUPPLY_OPEN_STAGES].join(', ') },
truncated: supplyTruncated,
}),
openDeals: supply.length,
totalDeals: supplyTotal,
byStage: countByStage(supply.map((deal) => deal.stage)),
largest: [...supply]
.sort((a, b) => (b.gpuCount ?? 0) - (a.gpuCount ?? 0))
@@ -586,10 +799,22 @@ async function readCalendarAhead(db: Database, withinDays: number): Promise<unkn
const overdue = behind.events.filter((event) => event.state === 'overdue');
const truncated = ahead.truncated || behind.truncated;
const upcomingByKind = countByKind(upcoming);
const upcomingListed = Math.min(upcoming.length, EXEMPLARS * 2);
const overdueListed = Math.min(overdue.length, EXEMPLARS);
/**
* Both denominators are windows, not the book, and the labels say so. Nothing
* here can answer "how many obligations are there" — only how many fall in
* these dates — so a label that read "obligations on the book" would be the
* same lie in a different tool.
*/
const windowLabel = `dated item(s) falling in the next ${withinDays} day(s), done or not`;
const lookbackLabel = `dated item(s) in the last ${OVERDUE_LOOKBACK_DAYS} day(s) that can fall late`;
return {
headline:
`Next ${withinDays} day(s): ${atLeast(upcoming.length, ahead.truncated)} dated item(s) ` +
`Next ${withinDays} day(s) only, not the whole book: ` +
`${atLeast(upcoming.length, ahead.truncated)} of ` +
`${atLeast(ahead.events.length, ahead.truncated)} dated item(s) ` +
`across ${Object.keys(upcomingByKind).length} kind(s), of which ` +
`${ahead.totals.obligationCount} obligation(s) due, ${ahead.totals.closingCount} demand ` +
`deal(s) expected to close worth ${formatCents(ahead.totals.weightedPipelineCents)} ` +
@@ -598,6 +823,15 @@ async function readCalendarAhead(db: Database, withinDays: number): Promise<unkn
`${atLeast(overdue.length, behind.truncated)} item(s) overdue in the last ` +
`${OVERDUE_LOOKBACK_DAYS} day(s).` +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
scope: resultScope({
covers: 'are still outstanding',
matched: upcoming.length,
total: ahead.events.length,
totalLabel: windowLabel,
listed: upcomingListed,
filters: { withinDays, state: 'excludes done' },
truncated: ahead.truncated,
}),
withinDays,
truncated,
/**
@@ -612,8 +846,11 @@ async function readCalendarAhead(db: Database, withinDays: number): Promise<unkn
renewalNotices: ahead.totals.renewalCount,
expiringExportAuthorizations: ahead.totals.expiringAuthorizationCount,
},
// The top-level `scope` is this list's: upcoming work is what the tool is
// for, and a second copy of the same eight fields is eight fields of budget.
upcoming: {
count: upcoming.length,
inWindow: ahead.events.length,
truncated: ahead.truncated,
byKind: upcomingByKind,
byState: countByState(upcoming),
@@ -621,6 +858,18 @@ async function readCalendarAhead(db: Database, withinDays: number): Promise<unkn
events: upcoming.slice(0, EXEMPLARS * 2).map(exemplar),
},
overdue: {
scope: resultScope({
covers: 'have lapsed without being completed',
matched: overdue.length,
total: behind.events.length,
totalLabel: lookbackLabel,
listed: overdueListed,
filters: {
lookbackDays: OVERDUE_LOOKBACK_DAYS,
kinds: [...OVERDUE_KINDS].join(', '),
},
truncated: behind.truncated,
}),
count: overdue.length,
truncated: behind.truncated,
lookbackDays: OVERDUE_LOOKBACK_DAYS,
@@ -662,6 +911,93 @@ function countByState(events: readonly CalendarEvent[]): Record<string, number>
}
// ---------------------------------------------------------------------------
// The parties
// ---------------------------------------------------------------------------
interface BookParties {
/** What the /accounts list shows: every account that is not archived. */
onBook: number;
/** Archived accounts, excluded from `onBook` and counted so the gap is visible. */
archived: number;
/** The book partitioned by side. The three buckets sum to `onBook`. */
bySide: Record<string, number>;
/** Every contact row, which is what the contacts tab lists. */
contacts: number;
}
/**
* How many accounts and contacts the book holds.
*
* Measured in production on /accounts, minutes before this was written. Asked
* "How many accounts are on the book in total? One sentence.", Piggy answered
* "The book contains 7 demand deals (accounts) in total." The book held 17
* accounts and 7 demand deals — so the figure was real, the payload had
* correctly scoped it as deals, and the prose relabelled it as accounts.
*
* This tool is the fallback for /accounts and five other routes, and it carried
* commitments, deals, margin and idle hours: no count of accounts or contacts
* anywhere. Asked about accounts with no account figure in front of it, the
* model reached for the nearest countable thing. That is the sibling of the
* defect `ResultScope` was built for — one substitutes the size of a filter for
* a total, this one substitutes another noun's total for a total that is simply
* absent — and the cure for an absent number is not a firmer instruction. It is
* the number.
*
* Counted in SQL rather than by measuring a list, so these figures are exact
* and cannot truncate; every other count in this file rides on a capped read.
*
* Archived accounts are excluded because `/api/accounts` excludes them, and
* Piggy contradicting the list the user is looking at is the failure that costs
* the tool its credibility. They are counted rather than silently dropped, so a
* figure that differs from a raw table count can still be reconciled. Contacts
* are deliberately NOT filtered the same way: `/api/contacts` applies no archive
* filter, so every contact row is the denominator that matches the screen.
*/
async function readParties(db: Database): Promise<BookParties> {
const [sides, archived, contactRows] = await Promise.all([
db
.select({ side: accounts.side, value: count() })
.from(accounts)
.where(isNull(accounts.archivedAt))
.groupBy(accounts.side),
db.select({ value: count() }).from(accounts).where(isNotNull(accounts.archivedAt)),
db.select({ value: count() }).from(contacts),
]);
// Every side is present at zero rather than absent: a missing key reads as
// "not known" to a model quoting the payload, and this breakdown is only
// trustworthy if it visibly adds up.
const bySide: Record<string, number> = Object.fromEntries(
ACCOUNT_SIDES.map((side) => [side, 0]),
);
for (const row of sides) bySide[row.side] = row.value;
// Summed from the same grouped read the breakdown is printed from. A total
// read by a second query can disagree with its own parts under a concurrent
// write, and a breakdown that does not add up invites the reader to pick.
const onBook = Object.values(bySide).reduce((sum, value) => sum + value, 0);
return { onBook, archived: rowCount(archived), bySide, contacts: rowCount(contactRows) };
}
/**
* The side split as prose, for the headline.
*
* `supply`, `demand` and `both` partition the book, so these three figures sum
* to the total and no account is counted twice. The /accounts side tabs do not
* partition it — each tab matches `side = X or side = both`, so the two tabs
* overlap — which is why the note below travels with the numbers rather than
* being left for the reader to work out from a screen that disagrees.
*/
function sideClause(bySide: Record<string, number>): string {
return ACCOUNT_SIDES.map((side) => `${bySide[side] ?? 0} ${side}`).join(', ');
}
const BY_SIDE_NOTE =
'supply, demand and both partition the book: these three figures sum to the total and ' +
'no account is counted twice. An account whose side is both trades on each side of the ' +
'market and is counted once, under both. The side tabs on the /accounts page instead show ' +
'supply plus both, and demand plus both, so those two figures overlap and do not sum.';
// The motion
// ---------------------------------------------------------------------------
@@ -992,8 +1328,9 @@ async function readEngagements(db: Database, query: string | null): Promise<unkn
* answered from the fragment it happened to receive.
*/
async function readWorkspaceSummary(db: Database): Promise<unknown> {
const [book, demandRead, supplyRead] = await Promise.all([
const [book, parties, demandRead, supplyRead, demandAll, supplyAll] = await Promise.all([
readLiveBlocks(db),
readParties(db),
db
.select({ id: demandDeals.id })
.from(demandDeals)
@@ -1004,8 +1341,12 @@ async function readWorkspaceSummary(db: Database): Promise<unknown> {
.from(supplyDeals)
.where(inArray(supplyDeals.stage, [...SUPPLY_OPEN_STAGES]))
.limit(SCAN_LIMIT + 1),
db.select({ value: count() }).from(demandDeals),
db.select({ value: count() }).from(supplyDeals),
]);
const { blocks } = book;
const demandTotal = rowCount(demandAll);
const supplyTotal = rowCount(supplyAll);
const { rows: demand, truncated: demandTruncated } = bounded(demandRead);
const { rows: supply, truncated: supplyTruncated } = bounded(supplyRead);
const truncated = {
@@ -1015,24 +1356,94 @@ async function readWorkspaceSummary(db: Database): Promise<unknown> {
};
const anyTruncated = Object.values(truncated).some(Boolean);
const totals = bookTotals(blocks);
const worstIdle = [...blocks]
/**
* Any idle at all, which is a THIRD threshold — `pig_get_idle_capacity` uses
* 25% and the web uses its own. Three surfaces have quoted three different
* idle counts for one book because of exactly this, so the scope below names
* the threshold rather than leaving the reader to guess which one produced
* the number in front of them.
*/
const withIdle = [...blocks]
.filter((block) => block.margin.idleGpuHours > 0)
.sort(
(a, b) =>
b.margin.idleGpuHours * b.costPerGpuHourCents -
a.margin.idleGpuHours * a.costPerGpuHourCents,
)
.slice(0, 3);
);
const worstIdle = withIdle.slice(0, 3);
return {
/**
* The parties lead the headline, and that ordering is the fix.
*
* This sentence is what a small model quotes, and the production answer was
* assembled by taking the first countable thing in it. Every count in it now
* states the noun it counts immediately beside the figure, and the noun the
* six fallback routes are most often asked about — accounts — is no longer
* missing from it.
*/
headline:
`${atLeast(blocks.length, truncated.commitments)} live commitment(s) at ` +
`${parties.onBook} ${ACCOUNTS_LABEL} (${sideClause(parties.bySide)}) and ` +
`${parties.contacts} ${CONTACTS_LABEL}` +
(parties.archived > 0
? `, with a further ${parties.archived} account(s) archived and off the book`
: '') +
`. All ${atLeast(blocks.length, truncated.commitments)} ${COMMITMENTS_LABEL} at ` +
`${percent(totals.utilisation)} utilisation; ` +
`gross margin ${formatCents(totals.grossMarginCents)}; ` +
`${atLeast(demand.length, demandTruncated)} open demand and ` +
`${atLeast(supply.length, supplyTruncated)} open supply deal(s).` +
`${demand.length} of ${atLeast(demandTotal, demandTruncated)} ${DEMAND_DEALS_LABEL} ` +
`and ${supply.length} of ${atLeast(supplyTotal, supplyTruncated)} ${SUPPLY_DEALS_LABEL} ` +
`are open. The ${worstIdle.length} block(s) listed below are the worst idle of ` +
`${withIdle.length} with any idle hours, not the whole book.` +
(anyTruncated ? ` ${TRUNCATION_NOTE}` : ''),
// The book, unfiltered: `matched` equals `total`, so this is the figure to
// quote when someone asks how large the book is.
scope: resultScope({
covers: 'are live',
matched: blocks.length,
total: blocks.length,
totalLabel: COMMITMENTS_LABEL,
listed: 0,
truncated: truncated.commitments,
}),
truncated,
/**
* The two figures whose absence produced the /accounts defect, first in the
* payload as well as first in the headline, each with its own scope. Both
* are exact: they are SQL counts, so neither can be a lower bound the way
* the capped reads below can.
*/
accounts: {
scope: resultScope({
covers: 'are on the book and not archived',
matched: parties.onBook,
total: parties.onBook,
totalLabel: ACCOUNTS_LABEL,
listed: 0,
}),
onBook: parties.onBook,
/** Excluded from `onBook`, and from the /accounts list, but not hidden. */
archived: parties.archived,
bySide: parties.bySide,
bySideNote: BY_SIDE_NOTE,
/**
* Said in the payload because the route guide cannot say it often enough:
* this tool counts accounts, it does not read them. A question about a
* named account is a `pig_search_records` question.
*/
rows: 'not available from this tool — counts only, no account rows',
},
contacts: {
scope: resultScope({
covers: 'are in the CRM',
matched: parties.contacts,
total: parties.contacts,
totalLabel: CONTACTS_LABEL,
listed: 0,
}),
total: parties.contacts,
rows: 'not available from this tool — counts only, no contact rows',
},
book: {
liveCommitments: blocks.length,
revenueCents: totals.revenueCents,
@@ -1041,14 +1452,50 @@ async function readWorkspaceSummary(db: Database): Promise<unknown> {
utilisation: totals.utilisation,
idleGpuHours: Math.round(totals.idleGpuHours),
},
// Two filtered counts, each next to the denominator it came from. Without
// `totalDemandDeals` beside it, `openDemandDeals` is the only deal figure
// in the payload and becomes the answer to "how many deals do we have".
openDemandDeals: demand.length,
totalDemandDeals: demandTotal,
openDemandDealsScope: resultScope({
covers: 'are at an open stage (not closed, not lost)',
matched: demand.length,
total: demandTotal,
totalLabel: DEMAND_DEALS_LABEL,
listed: 0,
filters: { stages: [...DEMAND_OPEN_STAGES].join(', ') },
truncated: demandTruncated,
}),
openSupplyDeals: supply.length,
worstIdleBlocks: worstIdle.map((block) => ({
name: block.name,
gpuType: block.gpuType,
idleGpuHours: Math.round(block.margin.idleGpuHours),
idleCostCents: Math.round(block.margin.idleGpuHours * block.costPerGpuHourCents),
})),
totalSupplyDeals: supplyTotal,
openSupplyDealsScope: resultScope({
covers: 'are at an open stage (not closed, not lost)',
matched: supply.length,
total: supplyTotal,
totalLabel: SUPPLY_DEALS_LABEL,
listed: 0,
filters: { stages: [...SUPPLY_OPEN_STAGES].join(', ') },
truncated: supplyTruncated,
}),
worstIdle: {
scope: resultScope({
covers: 'have any unsold hours at all',
matched: withIdle.length,
total: blocks.length,
totalLabel: COMMITMENTS_LABEL,
listed: worstIdle.length,
// Not 0.25. This list and pig_get_idle_capacity answer different
// questions and will disagree; the thresholds say which is which.
filters: { idleThresholdPct: 0 },
truncated: truncated.commitments,
}),
blocks: worstIdle.map((block) => ({
name: block.name,
gpuType: block.gpuType,
idleGpuHours: Math.round(block.margin.idleGpuHours),
idleCostCents: Math.round(block.margin.idleGpuHours * block.costPerGpuHourCents),
})),
},
};
}