Whole figures as a screen shows them: the cost per stop of a day's tours, invoices with discount and VAT, and the equipment effectiveness of a hall, each built from data.
import { Calculation, Chain, Given, Ref } from "@umriss-ui/calculation";
Eight tours from data, some sixty quantities: the sheet stands open, every tree in it folded, and the stale diesel price says so.
import { Calculation, Chain, DividedBy, Given, Interim, Plus, Product, Sum } from "@umriss-ui/calculation";
/* Data from the logistics world, written out here so the example runs on its own. */
/** A small LCG - the same numbers on every computer. */
function random(seed: number): () => number {
let s = seed >>> 0;
return () => {
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
return s / 4294967296;
};
}
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const MINUTE = 60_000;
interface Vehicle {
id: string;
plate: string;
type: "van" | "e-van" | "truck";
/** Payload, in kg. */
capacity: number;
depot: string;
}
const VEHICLES: readonly Vehicle[] = [
{ id: "v1", plate: "FP 214 K", type: "van", capacity: 1200, depot: "north" },
{ id: "v2", plate: "FP 377 K", type: "e-van", capacity: 900, depot: "north" },
{ id: "v3", plate: "FP 118 R", type: "truck", capacity: 7500, depot: "north" },
{ id: "v4", plate: "FP 402 R", type: "van", capacity: 1200, depot: "river" },
{ id: "v5", plate: "FP 455 R", type: "e-van", capacity: 900, depot: "river" },
{ id: "v6", plate: "FP 290 E", type: "van", capacity: 1200, depot: "east" },
{ id: "v7", plate: "FP 311 E", type: "e-van", capacity: 900, depot: "east" },
{ id: "v8", plate: "FP 520 E", type: "truck", capacity: 7500, depot: "east" },
];
const DRIVERS = [
{ id: "d1", name: "Martin Hale", depot: "north" },
{ id: "d2", name: "Nadia Petrova", depot: "north" },
{ id: "d3", name: "Owen Carter", depot: "north" },
{ id: "d4", name: "Lucia Romero", depot: "river" },
{ id: "d5", name: "Ben Adeyemi", depot: "river" },
{ id: "d6", name: "Hanna Berg", depot: "east" },
{ id: "d7", name: "Yusuf Demir", depot: "east" },
{ id: "d8", name: "Clara Wendt", depot: "east" },
] as const;
const CUSTOMERS = [
"Holloway Garden Supplies",
"Marlow & Finch Books",
"Oakridge Pharmacy",
"Brixley Cycles",
"Tamsin's Bakery",
"Northfold Office",
"Pellham Hardware",
"Greywick Studio",
"Juniper Lane Florist",
"Ashcombe Dental",
] as const;
interface Stop {
id: string;
shipment: string;
customer: string;
/** The window the customer was promised, `[from, to]`. */
window: readonly [number, number];
/** When the tour plans to be there. */
arrival: number;
}
interface Tour {
id: string;
vehicle: string;
driver: string;
depot: string;
/** Leaves the depot, loaded; back at the depot. */
from: number;
to: number;
/** Minutes of loading before `from`. */
loading: number;
km: number;
stops: readonly Stop[];
}
/** One tour per vehicle. Stops every half hour or so, each with a two-hour
window; every seventh arrival misses its window - the late ones a
dispatcher looks for. */
function tour(index: number): Tour {
const r = random(700 + index);
const vehicle = VEHICLES[index]!;
const truck = vehicle.type === "truck";
const from = at(truck ? 6 : 7, index % 2 === 0 ? 0 : 30);
const count = truck ? 5 : 8 + Math.floor(r() * 4);
const stops: Stop[] = [];
let arrival = from + (20 + Math.floor(r() * 15)) * MINUTE;
for (let i = 0; i < count; i++) {
const n = index * 20 + i;
const slot = Math.floor((arrival - at(0)) / (2 * 60 * MINUTE)) * 2;
/* A late stop's window closed half an hour or more before the arrival. */
const late = i >= 2 && n % 7 === 3;
const halfHour = 30 * MINUTE;
const windowFrom = late ? Math.floor(arrival / halfHour) * halfHour - 5 * halfHour : at(slot);
stops.push({
id: `s-${index + 1}-${i + 1}`,
shipment: `FP-${(1_004_210 + n * 13).toString()}`,
customer: CUSTOMERS[(n * 3) % CUSTOMERS.length]!,
window: [windowFrom, windowFrom + 2 * 60 * MINUTE],
arrival,
});
arrival += (truck ? 45 : 22) * MINUTE + Math.floor(r() * 18) * MINUTE;
}
return {
id: `T-${String(index + 1).padStart(2, "0")}`,
vehicle: vehicle.id,
driver: DRIVERS[index]!.id,
depot: vehicle.depot,
from,
to: arrival + 30 * MINUTE,
loading: truck ? 45 : 30,
km: Math.round((truck ? 140 : 70) + r() * 40),
stops,
};
}
const TOURS: readonly Tour[] = VEHICLES.map((_, index) => tour(index));
/** What a tour costs, per unit - the inputs of a cost per tour. */
const COST_RATES = {
/** Diesel, per litre; electricity, per kWh. */
dieselPerLitre: 1.68,
electricityPerKwh: 0.31,
/** Consumption per 100 km. */
per100Km: { van: 9.5, truck: 24, "e-van": 22 },
/** A driver's hour, all in. */
driverPerHour: 34.5,
/** Lease, insurance and upkeep, per day. */
vehiclePerDay: { van: 58, truck: 145, "e-van": 72 },
} as const;
const typeOf = (vehicleId: string) => VEHICLES.find((vehicle) => vehicle.id === vehicleId)!.type;
const hours = (tour: (typeof TOURS)[number]) => (tour.to - tour.from) / 3_600_000 + tour.loading / 60;
const DIESEL = TOURS.filter((tour) => typeOf(tour.vehicle) !== "e-van");
const ELECTRIC = TOURS.filter((tour) => typeOf(tour.vehicle) === "e-van");
const STOPS = TOURS.reduce((sum, tour) => sum + tour.stops.length, 0);
const AGES = { stale: 24 * 3_600_000, lost: 14 * 24 * 3_600_000 };
/* The price was read on Saturday at 06:00, seen from Tuesday 10:30; its
freshness counts from page load, the tours keep their Tuesday. */
const LOADED = Date.now();
const DIESEL_READ = LOADED - (at(10, 30) - new Date(2026, 2, 14, 6, 0).getTime());
export default function CostPerTour() {
return (
<Calculation aria-label="Cost per stop, all tours, Tuesday">
<Chain>
<Product label="Drivers" unit="€" decimals={2}>
<Sum label="Driver hours" unit="h" decimals={1}>
{TOURS.map((tour) => (
<Given key={tour.id} label={`Hours, ${tour.id}`} value={hours(tour)} unit="h" decimals={1} />
))}
</Sum>
<Given label="Driver rate, all in" value={COST_RATES.driverPerHour} unit="€/h" decimals={2} />
</Product>
<Plus>
<Product label="Diesel" unit="€" decimals={2}>
<Sum label="Diesel used" unit="l" decimals={1}>
{DIESEL.map((tour) => (
<Product key={tour.id} label={`Diesel, ${tour.id}`} unit="l" decimals={1}>
<Given label={`Distance, ${tour.id}`} value={tour.km} unit="km" />
<Given label={`Consumption, ${tour.id}`} value={COST_RATES.per100Km[typeOf(tour.vehicle)] / 100} unit="l/km" />
</Product>
))}
</Sum>
<Given
label="Diesel price"
value={COST_RATES.dieselPerLitre}
unit="€/l"
decimals={2}
source="Fuel card, weekly price"
asOf={DIESEL_READ}
ages={AGES}
/>
</Product>
</Plus>
<Plus>
<Product label="Electricity" unit="€" decimals={2}>
<Sum label="Electricity used" unit="kWh" decimals={1}>
{ELECTRIC.map((tour) => (
<Product key={tour.id} label={`Electricity, ${tour.id}`} unit="kWh" decimals={1}>
<Given label={`Distance, ${tour.id}`} value={tour.km} unit="km" />
<Given label={`Consumption, ${tour.id}`} value={COST_RATES.per100Km["e-van"] / 100} unit="kWh/km" />
</Product>
))}
</Sum>
<Given label="Electricity price" value={COST_RATES.electricityPerKwh} unit="€/kWh" decimals={2} source="Depot tariff 2026" />
</Product>
</Plus>
<Plus>
<Sum label="Vehicle days" unit="€" decimals={2}>
{TOURS.map((tour) => (
<Given key={tour.id} label={`Vehicle day, ${tour.id}`} value={COST_RATES.vehiclePerDay[typeOf(tour.vehicle)]} unit="€" decimals={2} />
))}
</Sum>
</Plus>
<Interim label="Cost of the day's tours" unit="€" decimals={2} />
<DividedBy label="Stops" value={STOPS} unit="stops" />
<Interim
label="Cost per stop"
unit="€/stop"
decimals={2}
limits={[{ value: 30, side: "upper", severity: "warning" }]}
/>
</Chain>
</Calculation>
);
}
Each invoice is a chain from its lines, discounts and VAT by rate; as operands of one sum they fold to their totals.
import type { ReactElement } from "react";
import { Calculation, Chain, Difference, Given, Interim, Plus, Product, Ref, Sum } from "@umriss-ui/calculation";
/* Data from the controlling world, written out here so the example runs on its own. */
const on = (month: number, day: number) => new Date(2026, month - 1, day).getTime();
interface InvoiceLine {
description: string;
quantity: number;
unitPrice: number;
/** A fraction: 0.1 is ten per cent off. */
discount: number;
/** A fraction: 0.19 or 0.07, or 0 where no VAT is charged. */
vatRate: number;
}
interface Invoice {
id: string;
supplier: string;
costCentre: string;
received: number;
due: number;
status: "awaiting approval" | "approved" | "paid" | "rejected";
lines: readonly InvoiceLine[];
}
const INVOICES: readonly Invoice[] = [
{
id: "INV-26-0318", supplier: "Brandlow Office Supply", costCentre: "CC-4400", received: on(3, 16), due: on(4, 15), status: "awaiting approval",
lines: [
{ description: "Desk lamps, LED", quantity: 24, unitPrice: 48.9, discount: 0.1, vatRate: 0.19 },
{ description: "Printer paper, box of 5 reams", quantity: 40, unitPrice: 21.5, discount: 0, vatRate: 0.19 },
{ description: "Delivery", quantity: 1, unitPrice: 35, discount: 0, vatRate: 0.19 },
],
},
{
id: "INV-26-0317", supplier: "Kettering & Shaw Events", costCentre: "CC-1200", received: on(3, 13), due: on(4, 12), status: "awaiting approval",
lines: [
{ description: "Trade fair stand, 3 days", quantity: 1, unitPrice: 12_400, discount: 0.05, vatRate: 0.19 },
{ description: "Catering, per guest", quantity: 180, unitPrice: 18.5, discount: 0, vatRate: 0.07 },
],
},
{
id: "INV-26-0309", supplier: "Nimbrel Software", costCentre: "CC-4300", received: on(3, 9), due: on(4, 8), status: "approved",
lines: [
{ description: "Design tool licences, annual", quantity: 12, unitPrice: 540, discount: 0.15, vatRate: 0.19 },
{ description: "Onboarding session", quantity: 2, unitPrice: 890, discount: 0, vatRate: 0.19 },
],
},
{
id: "INV-26-0302", supplier: "Fenwright Legal", costCentre: "CC-4100", received: on(3, 2), due: on(3, 16), status: "paid",
lines: [{ description: "Contract review, hours", quantity: 14.5, unitPrice: 260, discount: 0, vatRate: 0.19 }],
},
{
id: "INV-26-0226", supplier: "Corrin Travel", costCentre: "CC-1100", received: on(2, 26), due: on(3, 12), status: "paid",
lines: [
{ description: "Rail tickets, sales conference", quantity: 22, unitPrice: 139, discount: 0, vatRate: 0.07 },
{ description: "Hotel, nights", quantity: 44, unitPrice: 112, discount: 0.08, vatRate: 0.07 },
],
},
{
id: "INV-26-0221", supplier: "Stellbrook Consulting", costCentre: "CC-2100", received: on(2, 21), due: on(3, 23), status: "rejected",
lines: [{ description: "Architecture review, days", quantity: 6, unitPrice: 1450, discount: 0, vatRate: 0 }],
},
];
const OPEN = INVOICES.filter((invoice) => invoice.status === "awaiting approval" || invoice.status === "approved");
const percent = (rate: number) => `${Math.round(rate * 100)} %`;
/* A sum needs two operands; one stands for itself. */
const sumOf = (label: string, operands: ReactElement[]) =>
operands.length === 1 ? operands[0] : (
<Sum label={label} unit="€" decimals={2}>
{operands}
</Sum>
);
function invoiceChain(invoice: Invoice) {
const lineId = (index: number) => `${invoice.id}-line-${index}`;
const rates = [...new Set(invoice.lines.map((line) => line.vatRate))];
return (
<Chain key={invoice.id}>
{sumOf(
`Net, ${invoice.id}`,
invoice.lines.map((line, index) => {
const amount = (
<Product key={index} id={line.discount === 0 ? lineId(index) : `${lineId(index)}-list`} label={line.description} unit="€" decimals={2}>
<Given label={`Quantity, ${line.description}`} value={line.quantity} />
<Given label={`Unit price, ${line.description}`} value={line.unitPrice} unit="€" decimals={2} />
</Product>
);
if (line.discount === 0) return amount;
return (
<Difference key={index} id={lineId(index)} label={`${line.description}, after discount`} unit="€" decimals={2}>
{amount}
<Product label={`Discount, ${line.description}`} unit="€" decimals={2}>
<Given label={`Discount rate, ${line.description}`} value={line.discount} format="percent" />
<Ref to={`${lineId(index)}-list`} />
</Product>
</Difference>
);
}),
)}
<Plus>
{sumOf(
`VAT, ${invoice.id}`,
rates.map((rate) => {
const lines = invoice.lines.flatMap((line, index) => (line.vatRate === rate ? [<Ref key={index} to={lineId(index)} />] : []));
return (
<Product key={rate} label={`VAT at ${percent(rate)}, ${invoice.id}`} unit="€" decimals={2}>
<Given label={`VAT rate ${percent(rate)}`} value={rate} format="percent" />
{sumOf(`Net at ${percent(rate)}, ${invoice.id}`, lines)}
</Product>
);
}),
)}
</Plus>
<Interim label={`${invoice.id}, ${invoice.supplier}`} unit="€" decimals={2} />
</Chain>
);
}
export default function Invoices() {
return (
<Calculation aria-label="Invoices to be paid, March">
<Sum label="To be paid" unit="€" decimals={2}>
{OPEN.map(invoiceChain)}
</Sum>
</Calculation>
);
}
Three lines from data, three levels deep; line C's downtime is null, so its OEE and the hall's are missing while A and B stand.
import { Calculation, Difference, Given, Product, Quotient, Ref, Sum } from "@umriss-ui/calculation";
/* Data from the plant world, written out here so the example runs on its own. */
/** The early shift, 06:00 to 14:00. */
const SHIFT_MINUTES = 480;
interface Line {
name: string;
planned: number;
downtime: number | null;
idealCycle: number;
total: number;
good: number;
}
const BREAKS = 30;
const LINES: Line[] = [
{ name: "A", planned: SHIFT_MINUTES - BREAKS, downtime: 38, idealCycle: 0.8, total: 480, good: 461 },
{ name: "B", planned: SHIFT_MINUTES - BREAKS, downtime: 62, idealCycle: 1.2, total: 300, good: 291 },
{ name: "C", planned: SHIFT_MINUTES / 2, downtime: null, idealCycle: 0.5, total: 402, good: 395 },
];
function lineOee(line: Line) {
const n = line.name;
return (
<Product key={n} label={`Weighted OEE, line ${n}`} unit="min">
<Product label={`OEE, line ${n}`} format="percent">
<Quotient label={`Availability, line ${n}`} format="percent">
<Difference id={`runtime-${n}`} label={`Run time, line ${n}`} unit="min">
<Given id={`planned-${n}`} label={`Planned time, line ${n}`} value={line.planned} unit="min" />
<Given label={`Downtime, line ${n}`} value={line.downtime} unit="min" source="Shift log" />
</Difference>
<Ref to={`planned-${n}`} />
</Quotient>
<Quotient label={`Performance, line ${n}`} format="percent">
<Product label={`Ideal run time, line ${n}`} unit="min">
<Given label={`Ideal cycle time, line ${n}`} value={line.idealCycle} unit="min/pc" />
<Given id={`total-${n}`} label={`Total count, line ${n}`} value={line.total} unit="pcs" />
</Product>
<Ref to={`runtime-${n}`} />
</Quotient>
<Quotient label={`Quality, line ${n}`} format="percent">
<Given label={`Good count, line ${n}`} value={line.good} unit="pcs" />
<Ref to={`total-${n}`} />
</Quotient>
</Product>
<Ref to={`planned-${n}`} />
</Product>
);
}
export default function HallOee() {
return (
<Calculation aria-label="OEE of hall 2, early shift">
<Quotient label="OEE, hall 2" format="percent" target={0.8}>
<Sum label="Weighted OEE, all lines" unit="min">
{LINES.map(lineOee)}
</Sum>
<Sum label="Planned time, all lines" unit="min">
{LINES.map((line) => (
<Ref key={line.name} to={`planned-${line.name}`} />
))}
</Sum>
</Quotient>
</Calculation>
);
}
The keys of Calculation apply here.
From CalculationProps — the full table stands on Calculation.
| Name | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | The result: exactly one quantity - a tree (<Sum>, <Difference>,
<Product>, <Quotient> with their operands, <Given> as leaves) or a
<Chain>, the two mixing freely; <Ref> for a quantity defined
elsewhere. |
From OperatorProps — the full table stands on Tree.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The operands, in order: quantities, givens and references. |
labelrequired | string | — | What the quantity is called. It stands on its line, wherever it is an
operand, and in the accessible sentence. from QuantityProps |
id | string | — | Makes the quantity referable: <Ref to> stands for it elsewhere. Needed
only for a quantity used more than once. from QuantityProps |
unit | string | — | A label written after the number - "min", "pcs", "€". Never converted
and never checked; the calculation does no unit algebra. from QuantityProps |
format | "percent" | — | percent presents a plain ratio as per cent: 0.916 is shown as 91.6 %.
The quantity stays a ratio in every operation. from QuantityProps |
decimals | number | one digit for a percentage, at most two for a derived number, a given as it was given | Fixed number of fraction digits as shown. Rounding happens only in
presentation; every operation uses the full value. from QuantityProps |
target | number | — | The value this quantity is meant to reach - missed, never violated. from QuantityProps |
limits | readonly Limit[] | — | Bounds the quantity is assessed against, through core's assess(). from QuantityProps |
From RefProps — the full table stands on Tree.
| Name | Type | Default | Description |
|---|---|---|---|
torequired | string | — | The id of the quantity this reference stands for. |
From ChainProps — the full table stands on Chain.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The operands, top to bottom: a first quantity with no operator, then
Plus, Minus, Times and DividedBy with their operands and
Interims naming the value where they stand. The last line is an
Interim; a Times or DividedBy stands alone between two named
values. |
From ChainOperandProps — the full table stands on Chain.
| Name | Type | Default | Description |
|---|---|---|---|
label | string | — | The operand's name, where the line is a given written in place. Left out where the line holds a quantity as its child. |
value | | number | — | The operand's number, where the line is a given written in place.
Absent as for a Given: the line and every interim after it are
absent, with the reason. With metrics, an object by metric id, as on
a Given. |
children | ReactNode | — | Instead of label and value: exactly one quantity - a tree, a
<Ref> or a <Chain>. |
source | string | — | Where the number came from: a system, a report, a person. from GivenProps |
asOf | Date | number | — | When the number was true - not when it was fetched. from GivenProps |
ages | FreshnessAges | — | The ages at which asOf turns stale and lost. With both, the given
carries a freshness through core; with asOf alone, only the time. from GivenProps |
id | string | — | Makes the quantity referable: <Ref to> stands for it elsewhere. Needed
only for a quantity used more than once. from QuantityProps |
unit | string | — | A label written after the number - "min", "pcs", "€". Never converted
and never checked; the calculation does no unit algebra. from QuantityProps |
format | "percent" | — | percent presents a plain ratio as per cent: 0.916 is shown as 91.6 %.
The quantity stays a ratio in every operation. from QuantityProps |
decimals | number | one digit for a percentage, at most two for a derived number, a given as it was given | Fixed number of fraction digits as shown. Rounding happens only in
presentation; every operation uses the full value. from QuantityProps |
target | number | — | The value this quantity is meant to reach - missed, never violated. from QuantityProps |
limits | readonly Limit[] | — | Bounds the quantity is assessed against, through core's assess(). from QuantityProps |
From QuantityProps — the full table stands on Chain.
| Name | Type | Default | Description |
|---|---|---|---|
labelrequired | string | — | What the quantity is called. It stands on its line, wherever it is an operand, and in the accessible sentence. |
id | string | — | Makes the quantity referable: <Ref to> stands for it elsewhere. Needed
only for a quantity used more than once. |
unit | string | — | A label written after the number - "min", "pcs", "€". Never converted and never checked; the calculation does no unit algebra. |
format | "percent" | — | percent presents a plain ratio as per cent: 0.916 is shown as 91.6 %.
The quantity stays a ratio in every operation. |
decimals | number | one digit for a percentage, at most two for a derived number, a given as it was given | Fixed number of fraction digits as shown. Rounding happens only in presentation; every operation uses the full value. |
target | number | — | The value this quantity is meant to reach - missed, never violated. |
limits | readonly Limit[] | — | Bounds the quantity is assessed against, through core's assess(). |
From GivenProps — the full table stands on Given.
| Name | Type | Default | Description |
|---|---|---|---|
valuerequired | | number | — | The number the calculation starts from. Absent (null, undefined,
not finite) makes every quantity that depends on it absent, with the
reason - never zero. Where the calculation has metrics, an object with
a number for each metric id instead; absence then stays in its metric. |
source | string | — | Where the number came from: a system, a report, a person. |
asOf | Date | number | — | When the number was true - not when it was fetched. |
ages | FreshnessAges | — | The ages at which asOf turns stale and lost. With both, the given
carries a freshness through core; with asOf alone, only the time. |
labelrequired | string | — | What the quantity is called. It stands on its line, wherever it is an
operand, and in the accessible sentence. from QuantityProps |
id | string | — | Makes the quantity referable: <Ref to> stands for it elsewhere. Needed
only for a quantity used more than once. from QuantityProps |
unit | string | — | A label written after the number - "min", "pcs", "€". Never converted
and never checked; the calculation does no unit algebra. from QuantityProps |
format | "percent" | — | percent presents a plain ratio as per cent: 0.916 is shown as 91.6 %.
The quantity stays a ratio in every operation. from QuantityProps |
decimals | number | one digit for a percentage, at most two for a derived number, a given as it was given | Fixed number of fraction digits as shown. Rounding happens only in
presentation; every operation uses the full value. from QuantityProps |
target | number | — | The value this quantity is meant to reach - missed, never violated. from QuantityProps |
limits | readonly Limit[] | — | Bounds the quantity is assessed against, through core's assess(). from QuantityProps |