/** * Margin — the ledger, per block and in total. * * The table scrolls inside its own pane on narrow screens rather than making * the page scroll sideways; a card list would lose the column comparison that * is the entire value of this view. * * The page also has to say the quiet part out loud. A blended margin in the low * single digits reads as a thin but healthy book, while one commitment sits * barely half sold and has not paid for itself — the totals average that away * by construction. So the blocks whose cost is still uncovered are named above * the table with the price their remaining hours have to fetch, rather than * left to be reconstructed by reading a percentage column against a price * column two columns away. */ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { AlertTriangle, ArrowRight } from 'lucide-react'; import { Link } from 'react-router-dom'; import { compactNumber, get, money, percent, unitPrice } from '@/lib/api'; import { PiggyAskButton } from '@/components/PiggyChat'; import { Badge, Button, Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat, cn } from '@/components/ui'; import { usePageTitle } from '@/lib/title'; import { usePiggyContext } from '@/lib/piggy-context'; interface MarginReport { totals: { committedGpuHours: number; allocatedGpuHours: number; idleGpuHours: number; utilisation: number; costCents: number; revenueCents: number; grossMarginCents: number; grossMarginPct: number | null; marginPerAllocatedGpuHourCents: number | null; }; blocks: MarginBlock[]; } interface MarginBlock { commitmentId: string; name: string; gpuType: string; gpuCount: number; totalGpuHours: number; soldGpuHours: number; availableGpuHours: number; costPerGpuHourCents: number; utilisation: number; breakEvenPriceCents: number | null; } /** * A block has covered its cost when there is nothing left to break even on. * * The colour on the sold-ratio column used to key off `utilisation < 0.5`, * which is an arbitrary line: the block this page exists to flag is 55% sold * and would have rendered as unremarkable, while a block 40% sold on cheap * hours it has already earned back would have rendered as a problem. Break-even * is the honest test — it is zero exactly when revenue has already covered the * whole commitment, and null when there is nothing left to sell. */ function isUncovered(block: MarginBlock): boolean { return block.breakEvenPriceCents != null && block.breakEvenPriceCents > 0; } export function Margin() { usePageTitle('Margin'); const { data, isLoading, error, refetch } = useQuery({ queryKey: ['margin'], queryFn: () => get('/api/capacity/margin'), }); const [focusedId, setFocusedId] = useState(null); const blocks = data?.blocks ?? []; // Resolved from the current data rather than held in state, so a block that // disappears on a refetch quietly returns Piggy to the page instead of // leaving it pointed at a commitment nobody can see any more. const focused = blocks.find((block) => block.commitmentId === focusedId); // Ambient context for the dock: the block the user selected, else this page. // Published before the early returns below, because a hook that runs only on // the happy path is a hook that changes order the first time the query fails. usePiggyContext( focused ? { type: 'commitment', id: focused.commitmentId, label: focused.name } : { type: 'page', route: '/margin', label: 'Margin' }, ); const uncovered = blocks.filter(isUncovered); const toggleFocus = (commitmentId: string) => setFocusedId((current) => (current === commitmentId ? null : commitmentId)); if (isLoading) { return
{Array.from({ length: 4 }).map((_, index) => )}
; } if (error || !data) { return ( ); } if (data.blocks.length === 0) { return ( Go to the capacity book } /> ); } const t = data.totals; return (

Margin

Revenue from what we sold, against the full cost of what we bought. Every commitment is charged in full, so a block keeps paying for the hours nobody has bought yet.

{/* No `context` prop, deliberately. This button asks about whatever the page has published — the selected block, else /margin — and pinning it to the page here would make selecting a block change the dock and not this button, which is the one the user just pressed. */}
= 0 ? 'positive' : 'danger'} />
{uncovered.length > 0 ? (
{uncovered.length} of {data.blocks.length} commitments have not covered their cost {/* Not "an average of the blocks": the blended figure is a ratio of sums, and describing it as an average invites exactly the per-block averaging `aggregateMargin` refuses to do. */}

{t.grossMarginPct == null ? 'Nothing has sold yet, so every commitment below is still owed its whole cost.' : `The book clears ${percent(t.grossMarginPct, 1)} blended because the blocks that have earned their money back carry the ones that have not.`}{' '} These are the blocks still owed something, and the price the rest of each has to fetch to get there.

{uncovered.map((block) => (

{block.name}

{block.gpuCount}× {block.gpuType} · {percent(block.utilisation)} sold ·{' '} {compactNumber(block.availableGpuHours)} hrs still sellable

{unitPrice(block.breakEvenPriceCents)} /GPU-hr to break even {/* Break-even sits below cost once part of the block has sold — the hours already invoiced have paid down some of it. Said here because the two prices are otherwise read as a contradiction rather than as progress. */} against {unitPrice(block.costPerGpuHourCents)}/GPU-hr paid

))}
) : null}
By commitment

Sold ratio describes contracted capacity sold, not workload utilization. Select a commitment to point Piggy at it.

Capacity
{data.blocks.map((block) => ( {/* A zero break-even means the block's cost is already covered, so any further sale is upside. Printing "$0.00" is technically true and reads like a bug — the same fix already applied on the capacity cards. */} ))}
Commitment Sold Sellable Sold ratio Cost/hr Break even
toggleFocus(block.commitmentId)} /> {compactNumber(block.soldGpuHours)} {compactNumber(block.availableGpuHours)} {percent(block.utilisation)} {unitPrice(block.costPerGpuHourCents)}
{data.blocks.map((block) => (
toggleFocus(block.commitmentId)} /> {percent(block.utilisation)} sold
Sold capacity
{compactNumber(block.soldGpuHours)} hrs
Sellable capacity
{compactNumber(block.availableGpuHours)} hrs
Our cost
{unitPrice(block.costPerGpuHourCents)}/GPU-hr
Break even
))}
); } /** * The commitment name, as the control that points Piggy at that commitment. * * A row is the thing a reader is already looking at when they want to ask about * it, so the name carries the selection rather than a separate button in a * seventh column that would push the table wider than the pane it scrolls in. */ function FocusButton({ block, focused, onToggle }: { block: MarginBlock; focused: boolean; onToggle(): void }) { return ( ); } function BreakEven({ value }: { value: number | null }) { if (value == null) return Sold out; if (value === 0) return Cost covered; return {unitPrice(value)}/GPU-hr; }