@umriss-ui/calculation 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +6 -0
- package/dist/calculation.css +1 -1
- package/dist/calculation.js +133 -133
- package/docs/llms-full.md +1083 -0
- package/package.json +4 -3
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
# @umriss-ui/calculation 0.3.0
|
|
2
|
+
|
|
3
|
+
> The umriss calculation - a derivation written as it is shown, evaluated by the library, folded and read line by line.
|
|
4
|
+
|
|
5
|
+
Install with `pnpm add @umriss-ui/calculation`. This text is generated from the package's demo (https://romanhaendler.github.io/umriss-ui/calculation/): every page with its import line, its examples - the source exactly as it runs, with the package name where the demo imports its own source - its props tables generated from the code, and why it is built the way it is. The pages index stands in https://romanhaendler.github.io/umriss-ui/calculation/llms.txt.
|
|
6
|
+
|
|
7
|
+
## Writing a calculation
|
|
8
|
+
|
|
9
|
+
Two forms written as elements: a tree for a figure put together from factors, a chain for a sheet read top to bottom.
|
|
10
|
+
|
|
11
|
+
### Calculation
|
|
12
|
+
|
|
13
|
+
How a figure came about, as a statement down to the numbers it came from - evaluated by the library, never handed in.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Calculation, Given, Sum, Product, Quotient, Ref, Chain, Plus, Interim } from "@umriss-ui/calculation";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/calculation/#/calculation
|
|
20
|
+
|
|
21
|
+
#### Examples
|
|
22
|
+
|
|
23
|
+
##### Two numbers and a sum
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Calculation, Given, Sum } from "@umriss-ui/calculation";
|
|
27
|
+
|
|
28
|
+
/* The smallest calculation there is: two givens and the operator that joins
|
|
29
|
+
them. The result stands beneath its operands under a rule, and the double
|
|
30
|
+
rule says that this is where the calculation ends. Nothing here is typed in
|
|
31
|
+
twice - the 1,000 is computed by the calculation, never handed to it. */
|
|
32
|
+
export default function FirstSum() {
|
|
33
|
+
return (
|
|
34
|
+
<Calculation aria-label="Output of press 3, Tuesday">
|
|
35
|
+
<Sum label="Output, Tuesday" unit="pcs">
|
|
36
|
+
<Given label="Early shift" value={512} unit="pcs" />
|
|
37
|
+
<Given label="Late shift" value={488} unit="pcs" />
|
|
38
|
+
</Sum>
|
|
39
|
+
</Calculation>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
##### OEE of an early shift
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { Calculation, Difference, Given, Product, Quotient, Ref } from "@umriss-ui/calculation";
|
|
48
|
+
|
|
49
|
+
/* A tree: overall equipment effectiveness as availability × performance ×
|
|
50
|
+
quality, each derived from the shift's numbers. The three factors stand
|
|
51
|
+
above the result, each folded, with the formula it hides beneath its name;
|
|
52
|
+
click a name to open it.
|
|
53
|
+
|
|
54
|
+
Planned production time, run time and total count are each used twice. They
|
|
55
|
+
are defined once, where they belong, and stand elsewhere as a `<Ref>` - a
|
|
56
|
+
reference shows the quantity's name and number, never its derivation again.
|
|
57
|
+
Hover a line and its operands and every place it is used light up. */
|
|
58
|
+
export default function Oee() {
|
|
59
|
+
return (
|
|
60
|
+
<Calculation aria-label="OEE, early shift">
|
|
61
|
+
<Product label="OEE" format="percent" target={0.85}>
|
|
62
|
+
<Quotient label="Availability" format="percent">
|
|
63
|
+
<Difference id="runtime" label="Run time" unit="min">
|
|
64
|
+
<Given id="planned" label="Planned production time" value={450} unit="min" />
|
|
65
|
+
<Given label="Downtime" value={38} unit="min" />
|
|
66
|
+
</Difference>
|
|
67
|
+
<Ref to="planned" />
|
|
68
|
+
</Quotient>
|
|
69
|
+
<Quotient label="Performance" format="percent">
|
|
70
|
+
<Product label="Ideal run time" unit="min">
|
|
71
|
+
<Given label="Ideal cycle time" value={0.8} unit="min/pc" />
|
|
72
|
+
<Given id="total" label="Total count" value={480} unit="pcs" />
|
|
73
|
+
</Product>
|
|
74
|
+
<Ref to="runtime" />
|
|
75
|
+
</Quotient>
|
|
76
|
+
<Quotient label="Quality" format="percent">
|
|
77
|
+
<Given label="Good count" value={461} unit="pcs" />
|
|
78
|
+
<Ref to="total" />
|
|
79
|
+
</Quotient>
|
|
80
|
+
</Product>
|
|
81
|
+
</Calculation>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
##### A costing sheet
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { Calculation, Chain, Given, Interim, Plus, Product, Ref, Times } from "@umriss-ui/calculation";
|
|
90
|
+
|
|
91
|
+
/* The same calculation read the other way: as a chain, top to bottom, the way
|
|
92
|
+
a costing sheet stands on paper. Each line works its operand into the value
|
|
93
|
+
before it, and an `<Interim>` names that value where it stands.
|
|
94
|
+
|
|
95
|
+
The chain stands open, as on paper: lines, a rule, the interim, and on.
|
|
96
|
+
A surcharge on an earlier line is a `<Plus>` holding a product with a
|
|
97
|
+
`<Ref>` - the overhead is 120 % of the direct labour, not of the running
|
|
98
|
+
value - and that product is a tree: it folds, showing the formula it hides,
|
|
99
|
+
and opens beneath its line on a click. */
|
|
100
|
+
export default function CostingSheet() {
|
|
101
|
+
return (
|
|
102
|
+
<Calculation aria-label="Offer price, order A-2041">
|
|
103
|
+
<Chain>
|
|
104
|
+
<Given id="material" label="Direct material" value={1840} unit="€" decimals={2} />
|
|
105
|
+
<Plus>
|
|
106
|
+
<Product label="Material overhead" unit="€" decimals={2}>
|
|
107
|
+
<Given label="Material overhead rate" value={0.12} format="percent" />
|
|
108
|
+
<Ref to="material" />
|
|
109
|
+
</Product>
|
|
110
|
+
</Plus>
|
|
111
|
+
<Interim label="Material cost" unit="€" decimals={2} />
|
|
112
|
+
<Plus id="labour" label="Direct labour" value={960} unit="€" decimals={2} />
|
|
113
|
+
<Plus>
|
|
114
|
+
<Product label="Production overhead" unit="€" decimals={2}>
|
|
115
|
+
<Given label="Production overhead rate" value={1.2} format="percent" />
|
|
116
|
+
<Ref to="labour" />
|
|
117
|
+
</Product>
|
|
118
|
+
</Plus>
|
|
119
|
+
<Interim id="production" label="Production cost" unit="€" decimals={2} />
|
|
120
|
+
<Plus>
|
|
121
|
+
<Product label="Administration and sales overhead" unit="€" decimals={2}>
|
|
122
|
+
<Given label="Overhead rate" value={0.15} format="percent" />
|
|
123
|
+
<Ref to="production" />
|
|
124
|
+
</Product>
|
|
125
|
+
</Plus>
|
|
126
|
+
<Interim label="Cost price" unit="€" decimals={2} />
|
|
127
|
+
<Times label="Profit mark-up" value={1.08} />
|
|
128
|
+
<Interim label="Net offer price" unit="€" decimals={2} target={5000} />
|
|
129
|
+
</Chain>
|
|
130
|
+
</Calculation>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### API
|
|
136
|
+
|
|
137
|
+
##### `CalculationProps`
|
|
138
|
+
|
|
139
|
+
| Prop | Type | Default | Description |
|
|
140
|
+
|---|---|---|---|
|
|
141
|
+
| `children` (required) | `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. |
|
|
142
|
+
|
|
143
|
+
Also takes every attribute of the rendered element – without `children`.
|
|
144
|
+
|
|
145
|
+
#### Why it is like this
|
|
146
|
+
|
|
147
|
+
##### The library computes what it shows
|
|
148
|
+
|
|
149
|
+
There is no prop for a result. Every number on a derived line is computed with the operator the line names, by the calculation itself - so a page cannot say "412 ÷ 450" beside a figure that was computed from something else. That is also why there is no formula text and no operator of your own: a written-out operation the library did not perform could say something the number does not.
|
|
150
|
+
|
|
151
|
+
##### Written as it is shown
|
|
152
|
+
|
|
153
|
+
The nesting is the fold structure and the child order is the operand order. A quantity used twice is defined once, where you place it, and stands everywhere else as a `<Ref>`. What TypeScript cannot check - a reference to nothing, a circle through references, a wrapper component the calculation cannot look into - fails on the first render with a message saying which. See `docs/adr/0027-a-calculation-is-written-as-it-is-shown.md`.
|
|
154
|
+
|
|
155
|
+
### Tree
|
|
156
|
+
|
|
157
|
+
Sum, difference, product and quotient, each holding its operands - and a quantity used twice, defined once.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { Sum, Difference, Product, Quotient, Ref } from "@umriss-ui/calculation";
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/calculation/#/tree
|
|
164
|
+
|
|
165
|
+
#### Examples
|
|
166
|
+
|
|
167
|
+
##### A share with a target
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { Calculation, Given, Quotient } from "@umriss-ui/calculation";
|
|
171
|
+
|
|
172
|
+
/* `format="percent"` presents a plain ratio as per cent - the quotient stays
|
|
173
|
+
0.9604 in every operation. The target is assessed through core's
|
|
174
|
+
`assess()` and stands beneath the number: missed, never violated. */
|
|
175
|
+
export default function ShareWithTarget() {
|
|
176
|
+
return (
|
|
177
|
+
<Calculation aria-label="First-pass yield, press 3">
|
|
178
|
+
<Quotient label="First-pass yield" format="percent" target={0.95}>
|
|
179
|
+
<Given label="Good parts" value={461} unit="pcs" />
|
|
180
|
+
<Given label="Parts made" value={480} unit="pcs" />
|
|
181
|
+
</Quotient>
|
|
182
|
+
</Calculation>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
##### A difference of three
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { Calculation, Difference, Given } from "@umriss-ui/calculation";
|
|
191
|
+
|
|
192
|
+
/* `<Difference>` takes the first operand and subtracts every other: a − b − c.
|
|
193
|
+
Each operand after the first carries its minus in the operator column. */
|
|
194
|
+
export default function DifferenceOfThree() {
|
|
195
|
+
return (
|
|
196
|
+
<Calculation aria-label="Planned production time, early shift">
|
|
197
|
+
<Difference label="Planned production time" unit="min">
|
|
198
|
+
<Given label="Shift length" value={480} unit="min" />
|
|
199
|
+
<Given label="Breaks" value={30} unit="min" />
|
|
200
|
+
<Given label="Planned maintenance" value={20} unit="min" />
|
|
201
|
+
</Difference>
|
|
202
|
+
</Calculation>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
##### A product with units
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { Calculation, Given, Product } from "@umriss-ui/calculation";
|
|
211
|
+
|
|
212
|
+
/* A unit is a label the caller writes; nothing converts or checks it. The
|
|
213
|
+
product of kW and h is written as kWh because the caller says so - the
|
|
214
|
+
calculation does no unit algebra, and does not pretend to. `decimals` fixes
|
|
215
|
+
the places shown; the operation uses the full value. */
|
|
216
|
+
export default function ProductWithUnits() {
|
|
217
|
+
return (
|
|
218
|
+
<Calculation aria-label="Energy of the furnace, one shift">
|
|
219
|
+
<Product label="Energy, one shift" unit="kWh" decimals={1}>
|
|
220
|
+
<Given label="Mean power" value={86.35} unit="kW" />
|
|
221
|
+
<Given label="Hours at temperature" value={7.25} unit="h" />
|
|
222
|
+
</Product>
|
|
223
|
+
</Calculation>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
##### A quantity used twice
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { Calculation, Given, Product, Quotient, Ref } from "@umriss-ui/calculation";
|
|
232
|
+
|
|
233
|
+
/* The material per part enters both products. It is defined once, where it
|
|
234
|
+
first belongs, and stands as a `<Ref>` in the second place: a reference
|
|
235
|
+
shows the quantity's name and number, in italics, never its derivation
|
|
236
|
+
again. Open both products and hover either place - both light up. */
|
|
237
|
+
export default function UsedTwice() {
|
|
238
|
+
return (
|
|
239
|
+
<Calculation aria-label="Material lost to scrap, week 12">
|
|
240
|
+
<Quotient label="Material lost to scrap" format="percent" decimals={2}>
|
|
241
|
+
<Product label="Scrap cost" unit="€" decimals={2}>
|
|
242
|
+
<Given label="Scrapped parts" value={38} unit="pcs" />
|
|
243
|
+
<Given id="material" label="Material per part" value={2.8} unit="€" decimals={2} />
|
|
244
|
+
</Product>
|
|
245
|
+
<Product label="Material cost" unit="€" decimals={2}>
|
|
246
|
+
<Given label="Parts made" value={1278} unit="pcs" />
|
|
247
|
+
<Ref to="material" />
|
|
248
|
+
</Product>
|
|
249
|
+
</Quotient>
|
|
250
|
+
</Calculation>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
##### First-pass yield across three stations
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import { Calculation, Given, Product, Quotient } from "@umriss-ui/calculation";
|
|
259
|
+
|
|
260
|
+
/* A product of three quotients, one per station, each built from the
|
|
261
|
+
station's own counts - the rolled throughput yield. Every factor stands
|
|
262
|
+
folded with its formula beneath it; open one to redo it. */
|
|
263
|
+
|
|
264
|
+
const STATIONS = [
|
|
265
|
+
{ name: "Turning", good: 982, made: 1000 },
|
|
266
|
+
{ name: "Grinding", good: 961, made: 982 },
|
|
267
|
+
{ name: "Final inspection", good: 948, made: 961 },
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
export default function FirstPassYield() {
|
|
271
|
+
return (
|
|
272
|
+
<Calculation aria-label="Rolled throughput yield, shaft line">
|
|
273
|
+
<Product label="Rolled throughput yield" format="percent" target={0.95}>
|
|
274
|
+
{STATIONS.map((station) => (
|
|
275
|
+
<Quotient key={station.name} label={`Yield, ${station.name.toLowerCase()}`} format="percent">
|
|
276
|
+
<Given label={`Good, ${station.name.toLowerCase()}`} value={station.good} unit="pcs" />
|
|
277
|
+
<Given label={`Into ${station.name.toLowerCase()}`} value={station.made} unit="pcs" />
|
|
278
|
+
</Quotient>
|
|
279
|
+
))}
|
|
280
|
+
</Product>
|
|
281
|
+
</Calculation>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
##### Energy per piece, with a limit
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
import { Calculation, Given, Quotient, Sum } from "@umriss-ui/calculation";
|
|
290
|
+
|
|
291
|
+
/* Three meters summed and divided by the good pieces. The limit is assessed
|
|
292
|
+
through core's `assess()`: the verdict stands as a word beneath the number,
|
|
293
|
+
never as a colour alone. */
|
|
294
|
+
export default function EnergyPerPiece() {
|
|
295
|
+
return (
|
|
296
|
+
<Calculation aria-label="Energy per good piece, hall 2">
|
|
297
|
+
<Quotient
|
|
298
|
+
label="Energy per good piece"
|
|
299
|
+
unit="kWh/pc"
|
|
300
|
+
decimals={3}
|
|
301
|
+
limits={[{ value: 0.45, side: "upper", severity: "warning" }]}
|
|
302
|
+
>
|
|
303
|
+
<Sum label="Energy, hall 2" unit="kWh">
|
|
304
|
+
<Given label="Presses" value={1840} unit="kWh" />
|
|
305
|
+
<Given label="Compressed air" value={612} unit="kWh" />
|
|
306
|
+
<Given label="Lighting and HVAC" value={295} unit="kWh" />
|
|
307
|
+
</Sum>
|
|
308
|
+
<Given label="Good pieces" value={5920} unit="pcs" />
|
|
309
|
+
</Quotient>
|
|
310
|
+
</Calculation>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### API
|
|
316
|
+
|
|
317
|
+
##### `OperatorProps`
|
|
318
|
+
|
|
319
|
+
| Prop | Type | Default | Description |
|
|
320
|
+
|---|---|---|---|
|
|
321
|
+
| `children` | `ReactNode` | — | The operands, in order: quantities, givens and references. |
|
|
322
|
+
| `label` (required) | `string` | — | What the quantity is called. It stands on its line, wherever it is an operand, and in the accessible sentence. From `QuantityProps`. |
|
|
323
|
+
| `id` | `string` | — | Makes the quantity referable: `<Ref to>` stands for it elsewhere. Needed only for a quantity used more than once. From `QuantityProps`. |
|
|
324
|
+
| `unit` | `string` | — | A label written after the number - "min", "pcs", "€". Never converted and never checked; the calculation does no unit algebra. From `QuantityProps`. |
|
|
325
|
+
| `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`. |
|
|
326
|
+
| `decimals` | `number` | — | Fixed number of fraction digits as shown. Rounding happens only in presentation; every operation uses the full value. Without it: one digit for a percentage, at most two for a derived number, and a given as it was given. From `QuantityProps`. |
|
|
327
|
+
| `target` | `number` | — | The value this quantity is meant to reach - missed, never violated. From `QuantityProps`. |
|
|
328
|
+
| `limits` | `readonly Limit[]` | — | Bounds the quantity is assessed against, through core's `assess()`. From `QuantityProps`. |
|
|
329
|
+
| `explanation` | `string` | — | A sentence of plain text beside the line: why the quantity is here, or what it means. From `QuantityProps`. |
|
|
330
|
+
| `aside` | `ReactNode` | — | Content of the caller's own beside the line - a sparkline, a link. It never takes part in the calculation. From `QuantityProps`. |
|
|
331
|
+
|
|
332
|
+
##### `RefProps`
|
|
333
|
+
|
|
334
|
+
| Prop | Type | Default | Description |
|
|
335
|
+
|---|---|---|---|
|
|
336
|
+
| `to` (required) | `string` | — | The `id` of the quantity this reference stands for. |
|
|
337
|
+
|
|
338
|
+
### Chain
|
|
339
|
+
|
|
340
|
+
A sheet read top to bottom: each line worked into the value before it, and interims naming the value where they stand.
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { Chain, Plus, Minus, Times, DividedBy, Interim } from "@umriss-ui/calculation";
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/calculation/#/chain
|
|
347
|
+
|
|
348
|
+
#### Examples
|
|
349
|
+
|
|
350
|
+
##### A payslip
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
import { Calculation, Chain, Given, Interim, Minus, Plus, Product, Ref, Sum } from "@umriss-ui/calculation";
|
|
354
|
+
|
|
355
|
+
/* A chain as a payslip reads: the gross salary, each deduction on a line of
|
|
356
|
+
its own with its minus, and the net salary only once they all stand. A
|
|
357
|
+
chain in view never folds - it is the working itself.
|
|
358
|
+
|
|
359
|
+
What a reader should see only on request goes into ONE line as a tree: the
|
|
360
|
+
four social security contributions are a `<Sum>` held by a `<Minus>`. The
|
|
361
|
+
line shows their total and the formula it hides; a click opens the four
|
|
362
|
+
beneath it, each computed from its rate and the gross salary. */
|
|
363
|
+
|
|
364
|
+
const CONTRIBUTIONS = [
|
|
365
|
+
{ name: "Pension insurance", rate: 0.093 },
|
|
366
|
+
{ name: "Unemployment insurance", rate: 0.013 },
|
|
367
|
+
{ name: "Health insurance", rate: 0.082 },
|
|
368
|
+
{ name: "Long-term care insurance", rate: 0.017 },
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
export default function Payslip() {
|
|
372
|
+
return (
|
|
373
|
+
<Calculation aria-label="Payslip, March">
|
|
374
|
+
<Chain>
|
|
375
|
+
<Given id="gross" label="Gross salary" value={4200} unit="€" decimals={2} />
|
|
376
|
+
<Minus label="Income tax" value={612.5} unit="€" decimals={2} />
|
|
377
|
+
<Minus label="Church tax" value={49} unit="€" decimals={2} />
|
|
378
|
+
<Minus>
|
|
379
|
+
<Sum label="Social security contributions" unit="€" decimals={2}>
|
|
380
|
+
{CONTRIBUTIONS.map((contribution) => (
|
|
381
|
+
<Product key={contribution.name} label={contribution.name} unit="€" decimals={2}>
|
|
382
|
+
<Given label={`Rate, ${contribution.name.toLowerCase()}`} value={contribution.rate} format="percent" />
|
|
383
|
+
<Ref to="gross" />
|
|
384
|
+
</Product>
|
|
385
|
+
))}
|
|
386
|
+
</Sum>
|
|
387
|
+
</Minus>
|
|
388
|
+
<Interim label="Net salary" unit="€" decimals={2} />
|
|
389
|
+
<Minus label="Capital-forming benefits" value={40} unit="€" decimals={2} />
|
|
390
|
+
<Plus label="Travel allowance" value={60} unit="€" decimals={2} />
|
|
391
|
+
<Interim label="Amount paid out" unit="€" decimals={2} />
|
|
392
|
+
</Chain>
|
|
393
|
+
</Calculation>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
##### Net weight
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import { Calculation, Chain, Given, Interim, Minus } from "@umriss-ui/calculation";
|
|
402
|
+
|
|
403
|
+
/* The simplest chain: a first quantity with no operator, lines that take
|
|
404
|
+
something away, and an `<Interim>` that names the value. `<Minus>` and
|
|
405
|
+
`<Plus>` take a label and a value directly - a given written in place. */
|
|
406
|
+
export default function NetWeight() {
|
|
407
|
+
return (
|
|
408
|
+
<Calculation aria-label="Net weight, delivery 4471">
|
|
409
|
+
<Chain>
|
|
410
|
+
<Given label="Gross weight" value={1250} unit="kg" decimals={1} />
|
|
411
|
+
<Minus label="Pallet" value={25} unit="kg" decimals={1} />
|
|
412
|
+
<Minus label="Crates" value={144} unit="kg" decimals={1} />
|
|
413
|
+
<Minus label="Packaging" value={11.5} unit="kg" decimals={1} />
|
|
414
|
+
<Interim label="Net weight" unit="kg" decimals={1} />
|
|
415
|
+
</Chain>
|
|
416
|
+
</Calculation>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
##### The cost of a shift
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
import { Calculation, Chain, Given, Interim, Minus, Plus } from "@umriss-ui/calculation";
|
|
425
|
+
|
|
426
|
+
/* A flat chain of cost items, plus and minus mixed: a credit for energy fed
|
|
427
|
+
back is taken away where it stands. Written as a tree this would be one
|
|
428
|
+
`<Sum>` whose line repeats all seven names; as a chain every item is a line
|
|
429
|
+
of its own, and the interim is the sum a reader looks for. */
|
|
430
|
+
export default function ShiftCost() {
|
|
431
|
+
return (
|
|
432
|
+
<Calculation aria-label="Cost of the early shift, line 2">
|
|
433
|
+
<Chain>
|
|
434
|
+
<Given label="Operators" value={1152} unit="€" decimals={2} />
|
|
435
|
+
<Plus label="Shift supervisor" value={264} unit="€" decimals={2} />
|
|
436
|
+
<Plus label="Energy" value={418.4} unit="€" decimals={2} />
|
|
437
|
+
<Plus label="Compressed air" value={96.2} unit="€" decimals={2} />
|
|
438
|
+
<Plus label="Tooling wear" value={185} unit="€" decimals={2} />
|
|
439
|
+
<Plus label="Cleaning" value={48} unit="€" decimals={2} />
|
|
440
|
+
<Minus label="Energy fed back" value={37.6} unit="€" decimals={2} />
|
|
441
|
+
<Interim label="Cost of the shift" unit="€" decimals={2} />
|
|
442
|
+
</Chain>
|
|
443
|
+
</Calculation>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
##### Fifteen items from data
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
import { Calculation, Chain, Given, Interim, Plus } from "@umriss-ui/calculation";
|
|
452
|
+
|
|
453
|
+
/* `.map` over data inside a chain: each cost item becomes a `<Plus>`, and all
|
|
454
|
+
fifteen stand in view above their interim. Where only the total should show
|
|
455
|
+
until a reader asks, the items go into one line as a `<Sum>` instead - as
|
|
456
|
+
the payslip does with its contributions; folded, such a line says how many
|
|
457
|
+
operands it holds rather than a formula of fifteen terms. */
|
|
458
|
+
|
|
459
|
+
const ITEMS = [
|
|
460
|
+
["Steel sheet", 412.8],
|
|
461
|
+
["Tube 40×2", 186.4],
|
|
462
|
+
["Flat bar", 94.1],
|
|
463
|
+
["Screws M8", 22.6],
|
|
464
|
+
["Nuts M8", 9.8],
|
|
465
|
+
["Washers", 4.2],
|
|
466
|
+
["Hinges", 61.5],
|
|
467
|
+
["Gas springs", 88],
|
|
468
|
+
["Seals", 17.3],
|
|
469
|
+
["Powder coating", 143.9],
|
|
470
|
+
["Primer", 38.5],
|
|
471
|
+
["Labels", 6.4],
|
|
472
|
+
["Packaging", 29.7],
|
|
473
|
+
["Pallet", 14],
|
|
474
|
+
["Freight", 120],
|
|
475
|
+
] as const;
|
|
476
|
+
|
|
477
|
+
export default function FifteenItems() {
|
|
478
|
+
const [[firstName, firstAmount], ...rest] = ITEMS;
|
|
479
|
+
return (
|
|
480
|
+
<Calculation aria-label="Bought-in cost, order A-2052">
|
|
481
|
+
<Chain>
|
|
482
|
+
<Given label={firstName} value={firstAmount} unit="€" decimals={2} />
|
|
483
|
+
{rest.map(([name, amount]) => (
|
|
484
|
+
<Plus key={name} label={name} value={amount} unit="€" decimals={2} />
|
|
485
|
+
))}
|
|
486
|
+
<Interim label="Bought-in cost" unit="€" decimals={2} />
|
|
487
|
+
</Chain>
|
|
488
|
+
</Calculation>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
##### Mark-up and VAT
|
|
494
|
+
|
|
495
|
+
```tsx
|
|
496
|
+
import { Calculation, Chain, Given, Interim, Plus, Times } from "@umriss-ui/calculation";
|
|
497
|
+
|
|
498
|
+
/* A chain has no precedence: every line works into the value before it. So a
|
|
499
|
+
`<Times>` stands alone between two named values - directly after an
|
|
500
|
+
interim and directly before the next. Writing it after a `<Plus>` fails on
|
|
501
|
+
the first render; nobody multiplies a number they have not seen. */
|
|
502
|
+
export default function MarkUpAndVat() {
|
|
503
|
+
return (
|
|
504
|
+
<Calculation aria-label="Price of a spare part">
|
|
505
|
+
<Chain>
|
|
506
|
+
<Given label="Cost price" value={148.2} unit="€" decimals={2} />
|
|
507
|
+
<Plus label="Handling" value={12} unit="€" decimals={2} />
|
|
508
|
+
<Interim label="Cost with handling" unit="€" decimals={2} />
|
|
509
|
+
<Times label="Mark-up" value={1.25} />
|
|
510
|
+
<Interim label="Net price" unit="€" decimals={2} />
|
|
511
|
+
<Times label="VAT factor" value={1.19} />
|
|
512
|
+
<Interim label="Gross price" unit="€" decimals={2} />
|
|
513
|
+
</Chain>
|
|
514
|
+
</Calculation>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
##### A chain inside a tree
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
import { Calculation, Chain, Given, Interim, Minus, Plus, Quotient, Ref } from "@umriss-ui/calculation";
|
|
523
|
+
|
|
524
|
+
/* The two forms mix. Here a quotient's numerator is a chain: the net output of
|
|
525
|
+
a line, from what it made to what could be shipped. As an operand the chain
|
|
526
|
+
folds and stands for its last interim; opened, it stands whole beneath that
|
|
527
|
+
line, indented, every interim in view. The denominator is the chain's own
|
|
528
|
+
first line, referred to by its id. */
|
|
529
|
+
export default function ChainInATree() {
|
|
530
|
+
return (
|
|
531
|
+
<Calculation aria-label="Shipping yield, line 4">
|
|
532
|
+
<Quotient label="Shipping yield" format="percent" target={0.97}>
|
|
533
|
+
<Chain>
|
|
534
|
+
<Given id="made" label="Parts made" value={2400} unit="pcs" />
|
|
535
|
+
<Minus label="Scrap" value={41} unit="pcs" />
|
|
536
|
+
<Minus label="Held for inspection" value={26} unit="pcs" />
|
|
537
|
+
<Plus label="Released after rework" value={18} unit="pcs" />
|
|
538
|
+
<Interim label="Parts shipped" unit="pcs" />
|
|
539
|
+
</Chain>
|
|
540
|
+
<Ref to="made" />
|
|
541
|
+
</Quotient>
|
|
542
|
+
</Calculation>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
##### An energy balance with limits
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
import { Calculation, Chain, Given, Interim, Minus, Plus } from "@umriss-ui/calculation";
|
|
551
|
+
|
|
552
|
+
/* Interims take targets and limits like any quantity. The balance of a hall:
|
|
553
|
+
what came in, what went out, and what is left unaccounted for - a number
|
|
554
|
+
that should be small. Its limit is assessed; the verdict stands beneath it
|
|
555
|
+
as a word. */
|
|
556
|
+
export default function EnergyBalance() {
|
|
557
|
+
return (
|
|
558
|
+
<Calculation aria-label="Energy balance, hall 2, week 12">
|
|
559
|
+
<Chain>
|
|
560
|
+
<Given label="Grid supply" value={48200} unit="kWh" />
|
|
561
|
+
<Plus label="Photovoltaics" value={6150} unit="kWh" />
|
|
562
|
+
<Interim label="Energy in" unit="kWh" />
|
|
563
|
+
<Minus label="Presses" value={31480} unit="kWh" />
|
|
564
|
+
<Minus label="Compressed air" value={9820} unit="kWh" />
|
|
565
|
+
<Minus label="Heating and ventilation" value={8870} unit="kWh" />
|
|
566
|
+
<Minus label="Lighting" value={2210} unit="kWh" />
|
|
567
|
+
<Interim
|
|
568
|
+
label="Unaccounted for"
|
|
569
|
+
unit="kWh"
|
|
570
|
+
limits={[
|
|
571
|
+
{ value: 1500, side: "upper", severity: "warning" },
|
|
572
|
+
{ value: 3000, side: "upper", severity: "alarm" },
|
|
573
|
+
]}
|
|
574
|
+
/>
|
|
575
|
+
</Chain>
|
|
576
|
+
</Calculation>
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### API
|
|
582
|
+
|
|
583
|
+
##### `ChainProps`
|
|
584
|
+
|
|
585
|
+
| Prop | Type | Default | Description |
|
|
586
|
+
|---|---|---|---|
|
|
587
|
+
| `children` | `ReactNode` | — | The operands, top to bottom: a first quantity with no operator, then `Plus`, `Minus`, `Times` and `DividedBy` with their operands and `Interim`s naming the value where they stand. The last line is an `Interim`; a `Times` or `DividedBy` stands alone between two named values. |
|
|
588
|
+
|
|
589
|
+
##### `ChainOperandProps`
|
|
590
|
+
|
|
591
|
+
| Prop | Type | Default | Description |
|
|
592
|
+
|---|---|---|---|
|
|
593
|
+
| `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. |
|
|
594
|
+
| `value` | `number \| null` | — | 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. |
|
|
595
|
+
| `children` | `ReactNode` | — | Instead of `label` and `value`: exactly one quantity - a tree, a `<Ref>` or a `<Chain>`. |
|
|
596
|
+
| `source` | `string` | — | Where the number came from: a system, a report, a person. From `GivenProps`. |
|
|
597
|
+
| `asOf` | `Date \| number` | — | When the number was true - not when it was fetched. From `GivenProps`. |
|
|
598
|
+
| `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`. |
|
|
599
|
+
| `id` | `string` | — | Makes the quantity referable: `<Ref to>` stands for it elsewhere. Needed only for a quantity used more than once. From `QuantityProps`. |
|
|
600
|
+
| `unit` | `string` | — | A label written after the number - "min", "pcs", "€". Never converted and never checked; the calculation does no unit algebra. From `QuantityProps`. |
|
|
601
|
+
| `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`. |
|
|
602
|
+
| `decimals` | `number` | — | Fixed number of fraction digits as shown. Rounding happens only in presentation; every operation uses the full value. Without it: one digit for a percentage, at most two for a derived number, and a given as it was given. From `QuantityProps`. |
|
|
603
|
+
| `target` | `number` | — | The value this quantity is meant to reach - missed, never violated. From `QuantityProps`. |
|
|
604
|
+
| `limits` | `readonly Limit[]` | — | Bounds the quantity is assessed against, through core's `assess()`. From `QuantityProps`. |
|
|
605
|
+
| `explanation` | `string` | — | A sentence of plain text beside the line: why the quantity is here, or what it means. From `QuantityProps`. |
|
|
606
|
+
| `aside` | `ReactNode` | — | Content of the caller's own beside the line - a sparkline, a link. It never takes part in the calculation. From `QuantityProps`. |
|
|
607
|
+
|
|
608
|
+
##### `QuantityProps`
|
|
609
|
+
|
|
610
|
+
| Prop | Type | Default | Description |
|
|
611
|
+
|---|---|---|---|
|
|
612
|
+
| `label` (required) | `string` | — | What the quantity is called. It stands on its line, wherever it is an operand, and in the accessible sentence. |
|
|
613
|
+
| `id` | `string` | — | Makes the quantity referable: `<Ref to>` stands for it elsewhere. Needed only for a quantity used more than once. |
|
|
614
|
+
| `unit` | `string` | — | A label written after the number - "min", "pcs", "€". Never converted and never checked; the calculation does no unit algebra. |
|
|
615
|
+
| `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. |
|
|
616
|
+
| `decimals` | `number` | — | Fixed number of fraction digits as shown. Rounding happens only in presentation; every operation uses the full value. Without it: one digit for a percentage, at most two for a derived number, and a given as it was given. |
|
|
617
|
+
| `target` | `number` | — | The value this quantity is meant to reach - missed, never violated. |
|
|
618
|
+
| `limits` | `readonly Limit[]` | — | Bounds the quantity is assessed against, through core's `assess()`. |
|
|
619
|
+
| `explanation` | `string` | — | A sentence of plain text beside the line: why the quantity is here, or what it means. |
|
|
620
|
+
| `aside` | `ReactNode` | — | Content of the caller's own beside the line - a sparkline, a link. It never takes part in the calculation. |
|
|
621
|
+
|
|
622
|
+
#### Why it is like this
|
|
623
|
+
|
|
624
|
+
##### No precedence, and a times alone
|
|
625
|
+
|
|
626
|
+
A chain works every line into the value before it, strictly in order - the way a costing sheet is read. There is no precedence, so `a + b × c` would mean `(a + b) × c` here and `a + (b × c)` in a reader's head. Rather than explain that, the chain makes it unwritable: a `<Times>` or `<DividedBy>` stands alone between two named values, and anything else fails on the first render. Nobody multiplies a number they have not seen.
|
|
627
|
+
|
|
628
|
+
##### A chain does not fold
|
|
629
|
+
|
|
630
|
+
A chain is the working itself - folded to its interims it would only repeat the numbers and hide the steps it exists to show. So a chain in view stands open, and what a reader should see only on request goes into one line as a tree: a `<Sum>` of contributions held by a `<Minus>` folds like any tree, and opens beneath its line. Whoever writes the chain decides, line by line, what is always in view.
|
|
631
|
+
|
|
632
|
+
##### Only an interim shows the running value
|
|
633
|
+
|
|
634
|
+
A line shows its own number. A running value without a name is a number nobody can quote; where you want to see it, set an `<Interim>`. See `docs/adr/0028-a-calculation-is-a-tree-or-a-chain.md`.
|
|
635
|
+
|
|
636
|
+
### Given
|
|
637
|
+
|
|
638
|
+
The numbers a calculation starts from: where they came from, how old they are, and the language they are read in.
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
import { Given } from "@umriss-ui/calculation";
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/calculation/#/given
|
|
645
|
+
|
|
646
|
+
#### Examples
|
|
647
|
+
|
|
648
|
+
##### Where a number came from
|
|
649
|
+
|
|
650
|
+
```tsx
|
|
651
|
+
import { useState } from "react";
|
|
652
|
+
import { Sparkline } from "@umriss-ui/core";
|
|
653
|
+
import { Calculation, Given, Quotient } from "@umriss-ui/calculation";
|
|
654
|
+
|
|
655
|
+
/* A given can say where it came from and when it was true. With `ages` it
|
|
656
|
+
carries a freshness, as a Stat does: fresh, stale or lost - beside the
|
|
657
|
+
number, never instead of its verdict. `explanation` is a sentence of plain
|
|
658
|
+
text, and `aside` holds content of your own - here the last shifts'
|
|
659
|
+
scrap as a sparkline. Neither takes part in the calculation. */
|
|
660
|
+
|
|
661
|
+
const AGES = { stale: 15 * 60_000, lost: 60 * 60_000 };
|
|
662
|
+
|
|
663
|
+
export default function WhereItCameFrom() {
|
|
664
|
+
const [now] = useState(() => Date.now());
|
|
665
|
+
return (
|
|
666
|
+
<Calculation aria-label="Scrap rate, press 3">
|
|
667
|
+
<Quotient
|
|
668
|
+
label="Scrap rate"
|
|
669
|
+
format="percent"
|
|
670
|
+
limits={[{ value: 0.03, side: "upper", severity: "warning" }]}
|
|
671
|
+
explanation="Scrapped parts over every part the press made."
|
|
672
|
+
aside={<Sparkline data={[0.021, 0.024, 0.019, 0.028, 0.033, 0.036]} aria-hidden="true" />}
|
|
673
|
+
>
|
|
674
|
+
<Given label="Scrapped parts" value={18} unit="pcs" source="Quality station QS-2" asOf={now - 4 * 60_000} ages={AGES} />
|
|
675
|
+
<Given label="Parts made" value={512} unit="pcs" source="Press counter" asOf={now - 22 * 60_000} ages={AGES} />
|
|
676
|
+
</Quotient>
|
|
677
|
+
</Calculation>
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
##### An as-of time without ages
|
|
683
|
+
|
|
684
|
+
```tsx
|
|
685
|
+
import { Calculation, Given, Product } from "@umriss-ui/calculation";
|
|
686
|
+
|
|
687
|
+
/* `asOf` alone states when the number was true, as a time. With `ages` as
|
|
688
|
+
well, the given carries a freshness through core, as a `Stat` does - the
|
|
689
|
+
library has no default ages, because "stale" at a furnace means something
|
|
690
|
+
else than on a monthly report. */
|
|
691
|
+
export default function AsOf() {
|
|
692
|
+
return (
|
|
693
|
+
<Calculation aria-label="Value of the stock, bar steel">
|
|
694
|
+
<Product label="Value of the stock" unit="€" decimals={2}>
|
|
695
|
+
<Given label="Bar steel in stock" value={18.4} unit="t" source="Stocktaking" asOf={new Date(2026, 2, 13, 16, 0)} />
|
|
696
|
+
<Given label="Price" value={812} unit="€/t" source="Price list 2026-Q1" asOf={new Date(2026, 0, 2, 8, 0)} />
|
|
697
|
+
</Product>
|
|
698
|
+
</Calculation>
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
##### In German
|
|
704
|
+
|
|
705
|
+
```tsx
|
|
706
|
+
import { LanguageProvider } from "@umriss-ui/core";
|
|
707
|
+
import { GERMAN_FORMATS, GERMAN_WORDING } from "@umriss-ui/core/wording/de";
|
|
708
|
+
import { Calculation, Chain, Given, Interim, Plus, Product, Ref } from "@umriss-ui/calculation";
|
|
709
|
+
|
|
710
|
+
/* The operator words, the reasons for absence, the approximation note and the
|
|
711
|
+
disclosure labels are core's wording, English by default and German behind
|
|
712
|
+
the subpath (ADR-0019). The numbers follow the formats: 1.840,00 €. The
|
|
713
|
+
labels are yours and stay as you write them. */
|
|
714
|
+
export default function InGerman() {
|
|
715
|
+
return (
|
|
716
|
+
<LanguageProvider wording={GERMAN_WORDING} formats={GERMAN_FORMATS}>
|
|
717
|
+
<Calculation aria-label="Materialkosten, Auftrag A-2041">
|
|
718
|
+
<Chain>
|
|
719
|
+
<Given id="mek" label="Materialeinzelkosten" value={1840} unit="€" decimals={2} />
|
|
720
|
+
<Plus>
|
|
721
|
+
<Product label="Materialgemeinkosten" unit="€" decimals={2}>
|
|
722
|
+
<Given label="Zuschlagssatz" value={0.12} format="percent" />
|
|
723
|
+
<Ref to="mek" />
|
|
724
|
+
</Product>
|
|
725
|
+
</Plus>
|
|
726
|
+
<Interim label="Materialkosten" unit="€" decimals={2} />
|
|
727
|
+
</Chain>
|
|
728
|
+
</Calculation>
|
|
729
|
+
</LanguageProvider>
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
#### API
|
|
735
|
+
|
|
736
|
+
##### `GivenProps`
|
|
737
|
+
|
|
738
|
+
| Prop | Type | Default | Description |
|
|
739
|
+
|---|---|---|---|
|
|
740
|
+
| `value` (required) | `number \| null \| undefined` | — | The number the calculation starts from. Absent (`null`, `undefined`, not finite) makes every quantity that depends on it absent, with the reason - never zero. |
|
|
741
|
+
| `source` | `string` | — | Where the number came from: a system, a report, a person. |
|
|
742
|
+
| `asOf` | `Date \| number` | — | When the number was true - not when it was fetched. |
|
|
743
|
+
| `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. |
|
|
744
|
+
| `label` (required) | `string` | — | What the quantity is called. It stands on its line, wherever it is an operand, and in the accessible sentence. From `QuantityProps`. |
|
|
745
|
+
| `id` | `string` | — | Makes the quantity referable: `<Ref to>` stands for it elsewhere. Needed only for a quantity used more than once. From `QuantityProps`. |
|
|
746
|
+
| `unit` | `string` | — | A label written after the number - "min", "pcs", "€". Never converted and never checked; the calculation does no unit algebra. From `QuantityProps`. |
|
|
747
|
+
| `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`. |
|
|
748
|
+
| `decimals` | `number` | — | Fixed number of fraction digits as shown. Rounding happens only in presentation; every operation uses the full value. Without it: one digit for a percentage, at most two for a derived number, and a given as it was given. From `QuantityProps`. |
|
|
749
|
+
| `target` | `number` | — | The value this quantity is meant to reach - missed, never violated. From `QuantityProps`. |
|
|
750
|
+
| `limits` | `readonly Limit[]` | — | Bounds the quantity is assessed against, through core's `assess()`. From `QuantityProps`. |
|
|
751
|
+
| `explanation` | `string` | — | A sentence of plain text beside the line: why the quantity is here, or what it means. From `QuantityProps`. |
|
|
752
|
+
| `aside` | `ReactNode` | — | Content of the caller's own beside the line - a sparkline, a link. It never takes part in the calculation. From `QuantityProps`. |
|
|
753
|
+
|
|
754
|
+
## In practice
|
|
755
|
+
|
|
756
|
+
What a calculation does when the numbers are not what they should be, and two figures worked in full.
|
|
757
|
+
|
|
758
|
+
### What can go wrong
|
|
759
|
+
|
|
760
|
+
A missing number, a division by zero, rounded figures that do not add up, and an alarm folded out of sight.
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
import { Calculation } from "@umriss-ui/calculation";
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/calculation/#/what-can-go-wrong
|
|
767
|
+
|
|
768
|
+
#### Examples
|
|
769
|
+
|
|
770
|
+
##### A missing number
|
|
771
|
+
|
|
772
|
+
```tsx
|
|
773
|
+
import { Calculation, Difference, Given, Product, Quotient, Ref } from "@umriss-ui/calculation";
|
|
774
|
+
|
|
775
|
+
/* The downtime has not been booked yet. It shows as missing - and so does
|
|
776
|
+
every figure that depends on it, each with the reason, up to the result.
|
|
777
|
+
Nothing is carried on as zero: an OEE computed with no downtime would be a
|
|
778
|
+
number, and a wrong one. */
|
|
779
|
+
export default function Missing() {
|
|
780
|
+
return (
|
|
781
|
+
<Calculation aria-label="OEE, late shift">
|
|
782
|
+
<Product label="OEE" format="percent" target={0.85}>
|
|
783
|
+
<Quotient label="Availability" format="percent">
|
|
784
|
+
<Difference id="runtime" label="Run time" unit="min">
|
|
785
|
+
<Given id="planned" label="Planned production time" value={450} unit="min" />
|
|
786
|
+
<Given label="Downtime" value={null} unit="min" />
|
|
787
|
+
</Difference>
|
|
788
|
+
<Ref to="planned" />
|
|
789
|
+
</Quotient>
|
|
790
|
+
<Quotient label="Performance" format="percent">
|
|
791
|
+
<Product label="Ideal run time" unit="min">
|
|
792
|
+
<Given label="Ideal cycle time" value={0.8} unit="min/pc" />
|
|
793
|
+
<Given id="total" label="Total count" value={455} unit="pcs" />
|
|
794
|
+
</Product>
|
|
795
|
+
<Ref to="runtime" />
|
|
796
|
+
</Quotient>
|
|
797
|
+
<Quotient label="Quality" format="percent">
|
|
798
|
+
<Given label="Good count" value={441} unit="pcs" />
|
|
799
|
+
<Ref to="total" />
|
|
800
|
+
</Quotient>
|
|
801
|
+
</Product>
|
|
802
|
+
</Calculation>
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
##### A division by zero
|
|
808
|
+
|
|
809
|
+
```tsx
|
|
810
|
+
import { Calculation, Given, Quotient, Sum } from "@umriss-ui/calculation";
|
|
811
|
+
|
|
812
|
+
/* A machine that did not run has no output per hour - not zero, and not
|
|
813
|
+
infinity. The quotient is absent with its own reason, and so is everything
|
|
814
|
+
that depends on it. */
|
|
815
|
+
export default function DivisionByZero() {
|
|
816
|
+
return (
|
|
817
|
+
<Calculation aria-label="Output per hour, cell 7">
|
|
818
|
+
<Sum label="Output per hour, cell 7" unit="pcs/h">
|
|
819
|
+
<Quotient label="Robot A" unit="pcs/h">
|
|
820
|
+
<Given label="Parts, robot A" value={412} unit="pcs" />
|
|
821
|
+
<Given label="Hours run, robot A" value={7.5} unit="h" />
|
|
822
|
+
</Quotient>
|
|
823
|
+
<Quotient label="Robot B" unit="pcs/h">
|
|
824
|
+
<Given label="Parts, robot B" value={0} unit="pcs" />
|
|
825
|
+
<Given label="Hours run, robot B" value={0} unit="h" />
|
|
826
|
+
</Quotient>
|
|
827
|
+
</Sum>
|
|
828
|
+
</Calculation>
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
##### When the rounded figures do not add up
|
|
834
|
+
|
|
835
|
+
```tsx
|
|
836
|
+
import { Calculation, Given, Product, Quotient, Sum } from "@umriss-ui/calculation";
|
|
837
|
+
|
|
838
|
+
/* Every operation uses the full value; rounding happens only on the screen.
|
|
839
|
+
Three shares of 33.3 % each add up to 100 % - not to the 99.9 % a reader
|
|
840
|
+
gets from the rounded figures. The line says so with "≈" and a note,
|
|
841
|
+
instead of leaving the reader to find it. */
|
|
842
|
+
export default function Rounding() {
|
|
843
|
+
return (
|
|
844
|
+
<Calculation aria-label="Shares of the shift">
|
|
845
|
+
<Sum label="All three lines" format="percent">
|
|
846
|
+
{["Line 1", "Line 2", "Line 3"].map((line) => (
|
|
847
|
+
<Quotient key={line} label={`Share of ${line}`} format="percent">
|
|
848
|
+
<Product label={`Hours on ${line}`} unit="h">
|
|
849
|
+
<Given label="Machines" value={2} />
|
|
850
|
+
<Given label="Hours each" value={4} unit="h" />
|
|
851
|
+
</Product>
|
|
852
|
+
<Given label="Hours in the shift" value={24} unit="h" />
|
|
853
|
+
</Quotient>
|
|
854
|
+
))}
|
|
855
|
+
</Sum>
|
|
856
|
+
</Calculation>
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
##### A limit violated deep inside
|
|
862
|
+
|
|
863
|
+
```tsx
|
|
864
|
+
import { Calculation, Chain, Given, Interim, Plus, Product } from "@umriss-ui/calculation";
|
|
865
|
+
|
|
866
|
+
/* The scrap rate on line 2 is beyond its alarm limit, inside a folded line.
|
|
867
|
+
The folded line says so quietly - "Inside: Alarm limit exceeded" - without
|
|
868
|
+
taking on the alarm's colour itself: its own number is not the one in
|
|
869
|
+
alarm. Open it to find the line that is. */
|
|
870
|
+
export default function LimitInside() {
|
|
871
|
+
const scrapLimits = [{ value: 0.03, side: "upper" as const, severity: "alarm" as const }];
|
|
872
|
+
return (
|
|
873
|
+
<Calculation aria-label="Scrap cost, hall 1">
|
|
874
|
+
<Chain>
|
|
875
|
+
<Product label="Scrap cost, line 1" unit="€" decimals={2}>
|
|
876
|
+
<Given label="Scrap rate, line 1" value={0.018} format="percent" limits={scrapLimits} />
|
|
877
|
+
<Given label="Material, line 1" value={24800} unit="€" decimals={2} />
|
|
878
|
+
</Product>
|
|
879
|
+
<Plus>
|
|
880
|
+
<Product label="Scrap cost, line 2" unit="€" decimals={2}>
|
|
881
|
+
<Given label="Scrap rate, line 2" value={0.041} format="percent" limits={scrapLimits} />
|
|
882
|
+
<Given label="Material, line 2" value={19650} unit="€" decimals={2} />
|
|
883
|
+
</Product>
|
|
884
|
+
</Plus>
|
|
885
|
+
<Interim label="Scrap cost, hall 1" unit="€" decimals={2} />
|
|
886
|
+
</Chain>
|
|
887
|
+
</Calculation>
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Worked examples
|
|
893
|
+
|
|
894
|
+
The cost per piece of a production order and the OEE of a hall - some fifty quantities each, from data.
|
|
895
|
+
|
|
896
|
+
```ts
|
|
897
|
+
import { Calculation, Chain, Given, Ref } from "@umriss-ui/calculation";
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/calculation/#/worked-examples
|
|
901
|
+
|
|
902
|
+
#### Examples
|
|
903
|
+
|
|
904
|
+
##### The cost per piece of a production order
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
import { Calculation, Chain, Given, Interim, Plus, Product, Quotient, Ref, Sum, Times } from "@umriss-ui/calculation";
|
|
908
|
+
|
|
909
|
+
/* Everything at once, as an order is actually costed. The bill of material
|
|
910
|
+
and the routing come from data; each becomes a tree of its own - quantity
|
|
911
|
+
times price, minutes times the machine's rate per minute - and the two
|
|
912
|
+
trees are lines of the costing sheet. Setup is spread over the lot size,
|
|
913
|
+
the scrap surcharge and the overheads refer back to named interims, and the
|
|
914
|
+
sheet ends in a net and a gross price.
|
|
915
|
+
|
|
916
|
+
The scrap rate is three days old: its line says so, beside the number, and
|
|
917
|
+
keeps its verdict. Some fifty quantities: the sheet itself stands open, a
|
|
918
|
+
dozen lines, and every tree in it waits folded until a reader asks. */
|
|
919
|
+
|
|
920
|
+
const BILL_OF_MATERIAL = [
|
|
921
|
+
{ part: "Steel sheet 2 mm", quantity: 1.84, unit: "kg", price: 1.35 },
|
|
922
|
+
{ part: "Tube 40×2", quantity: 0.62, unit: "m", price: 4.1 },
|
|
923
|
+
{ part: "Hinge", quantity: 2, unit: "pcs", price: 1.9 },
|
|
924
|
+
{ part: "Gas spring", quantity: 1, unit: "pcs", price: 6.4 },
|
|
925
|
+
{ part: "Screw M8×20", quantity: 8, unit: "pcs", price: 0.07 },
|
|
926
|
+
{ part: "Powder, RAL 7016", quantity: 0.21, unit: "kg", price: 9.8 },
|
|
927
|
+
];
|
|
928
|
+
|
|
929
|
+
const ROUTING = [
|
|
930
|
+
{ station: "Laser cutting", minutes: 2.4, rate: 96 },
|
|
931
|
+
{ station: "Bending", minutes: 1.8, rate: 72 },
|
|
932
|
+
{ station: "Welding", minutes: 4.2, rate: 84 },
|
|
933
|
+
{ station: "Powder coating", minutes: 1.5, rate: 60 },
|
|
934
|
+
{ station: "Assembly", minutes: 3.0, rate: 54 },
|
|
935
|
+
];
|
|
936
|
+
|
|
937
|
+
const AGES = { stale: 24 * 3_600_000, lost: 14 * 24 * 3_600_000 };
|
|
938
|
+
|
|
939
|
+
export default function CostPerPiece() {
|
|
940
|
+
return (
|
|
941
|
+
<Calculation aria-label="Cost per piece, order A-2041, lot of 250">
|
|
942
|
+
<Chain>
|
|
943
|
+
<Sum id="material" label="Direct material" unit="€" decimals={2}>
|
|
944
|
+
{BILL_OF_MATERIAL.map((line) => (
|
|
945
|
+
<Product key={line.part} label={line.part} unit="€" decimals={2}>
|
|
946
|
+
<Given label={`Quantity, ${line.part}`} value={line.quantity} unit={line.unit} />
|
|
947
|
+
<Given label={`Price, ${line.part}`} value={line.price} unit={`€/${line.unit}`} />
|
|
948
|
+
</Product>
|
|
949
|
+
))}
|
|
950
|
+
</Sum>
|
|
951
|
+
<Plus>
|
|
952
|
+
<Product label="Material overhead" unit="€" decimals={2}>
|
|
953
|
+
<Given label="Material overhead rate" value={0.12} format="percent" source="Cost centre accounting 2026" />
|
|
954
|
+
<Ref to="material" />
|
|
955
|
+
</Product>
|
|
956
|
+
</Plus>
|
|
957
|
+
<Interim label="Material cost" unit="€" decimals={2} />
|
|
958
|
+
<Plus>
|
|
959
|
+
<Sum label="Machine time" unit="€" decimals={2}>
|
|
960
|
+
{ROUTING.map((step) => (
|
|
961
|
+
<Product key={step.station} label={step.station} unit="€" decimals={2}>
|
|
962
|
+
<Given label={`Minutes, ${step.station.toLowerCase()}`} value={step.minutes} unit="min" decimals={1} />
|
|
963
|
+
<Quotient label={`Rate per minute, ${step.station.toLowerCase()}`} unit="€/min" decimals={3}>
|
|
964
|
+
<Given label={`Machine hour rate, ${step.station.toLowerCase()}`} value={step.rate} unit="€/h" />
|
|
965
|
+
<Given label="Minutes per hour" value={60} unit="min/h" />
|
|
966
|
+
</Quotient>
|
|
967
|
+
</Product>
|
|
968
|
+
))}
|
|
969
|
+
</Sum>
|
|
970
|
+
</Plus>
|
|
971
|
+
<Plus>
|
|
972
|
+
<Quotient label="Setup per piece" unit="€" decimals={2}>
|
|
973
|
+
<Product label="Setup cost" unit="€" decimals={2}>
|
|
974
|
+
<Given label="Setup time" value={1.5} unit="h" />
|
|
975
|
+
<Given label="Setup rate" value={78} unit="€/h" />
|
|
976
|
+
</Product>
|
|
977
|
+
<Given label="Lot size" value={250} unit="pcs" />
|
|
978
|
+
</Quotient>
|
|
979
|
+
</Plus>
|
|
980
|
+
<Interim id="production" label="Production cost" unit="€" decimals={2} />
|
|
981
|
+
<Plus>
|
|
982
|
+
<Product label="Scrap surcharge" unit="€" decimals={2}>
|
|
983
|
+
<Given
|
|
984
|
+
label="Scrap rate"
|
|
985
|
+
value={0.032}
|
|
986
|
+
format="percent"
|
|
987
|
+
source="MES, last 30 days"
|
|
988
|
+
asOf={new Date(2026, 2, 14, 6, 0)}
|
|
989
|
+
ages={AGES}
|
|
990
|
+
/>
|
|
991
|
+
<Ref to="production" />
|
|
992
|
+
</Product>
|
|
993
|
+
</Plus>
|
|
994
|
+
<Plus>
|
|
995
|
+
<Product label="Administration and sales overhead" unit="€" decimals={2}>
|
|
996
|
+
<Given label="Overhead rate" value={0.14} format="percent" source="Cost centre accounting 2026" />
|
|
997
|
+
<Ref to="production" />
|
|
998
|
+
</Product>
|
|
999
|
+
</Plus>
|
|
1000
|
+
<Interim label="Cost price" unit="€" decimals={2} />
|
|
1001
|
+
<Times label="Profit mark-up" value={1.1} decimals={2} />
|
|
1002
|
+
<Interim label="Net price per piece" unit="€" decimals={2} target={85} />
|
|
1003
|
+
<Times label="VAT factor" value={1.19} />
|
|
1004
|
+
<Interim label="Gross price per piece" unit="€" decimals={2} />
|
|
1005
|
+
</Chain>
|
|
1006
|
+
</Calculation>
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
##### OEE of a hall, weighted by planned time
|
|
1012
|
+
|
|
1013
|
+
```tsx
|
|
1014
|
+
import { Calculation, Difference, Given, Product, Quotient, Ref, Sum } from "@umriss-ui/calculation";
|
|
1015
|
+
|
|
1016
|
+
/* Three lines, each with its own OEE derived from its own counts, weighted by
|
|
1017
|
+
how long each was planned to run. A tree three levels deep, built from
|
|
1018
|
+
data, each line's quantities named once and referred to by id - and line
|
|
1019
|
+
C's downtime has not been booked. Its OEE is missing, so is
|
|
1020
|
+
the hall's, and every line on the way says why; lines A and B stand as
|
|
1021
|
+
they are. Nothing is averaged over a gap as if it were zero. */
|
|
1022
|
+
|
|
1023
|
+
interface Line {
|
|
1024
|
+
name: string;
|
|
1025
|
+
planned: number;
|
|
1026
|
+
downtime: number | null;
|
|
1027
|
+
idealCycle: number;
|
|
1028
|
+
total: number;
|
|
1029
|
+
good: number;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const LINES: Line[] = [
|
|
1033
|
+
{ name: "A", planned: 450, downtime: 38, idealCycle: 0.8, total: 480, good: 461 },
|
|
1034
|
+
{ name: "B", planned: 450, downtime: 62, idealCycle: 1.2, total: 300, good: 291 },
|
|
1035
|
+
{ name: "C", planned: 240, downtime: null, idealCycle: 0.5, total: 402, good: 395 },
|
|
1036
|
+
];
|
|
1037
|
+
|
|
1038
|
+
function lineOee(line: Line) {
|
|
1039
|
+
const n = line.name;
|
|
1040
|
+
return (
|
|
1041
|
+
<Product key={n} label={`Weighted OEE, line ${n}`} unit="min">
|
|
1042
|
+
<Product label={`OEE, line ${n}`} format="percent">
|
|
1043
|
+
<Quotient label={`Availability, line ${n}`} format="percent">
|
|
1044
|
+
<Difference id={`runtime-${n}`} label={`Run time, line ${n}`} unit="min">
|
|
1045
|
+
<Given id={`planned-${n}`} label={`Planned time, line ${n}`} value={line.planned} unit="min" />
|
|
1046
|
+
<Given label={`Downtime, line ${n}`} value={line.downtime} unit="min" source="Shift log" />
|
|
1047
|
+
</Difference>
|
|
1048
|
+
<Ref to={`planned-${n}`} />
|
|
1049
|
+
</Quotient>
|
|
1050
|
+
<Quotient label={`Performance, line ${n}`} format="percent">
|
|
1051
|
+
<Product label={`Ideal run time, line ${n}`} unit="min">
|
|
1052
|
+
<Given label={`Ideal cycle time, line ${n}`} value={line.idealCycle} unit="min/pc" />
|
|
1053
|
+
<Given id={`total-${n}`} label={`Total count, line ${n}`} value={line.total} unit="pcs" />
|
|
1054
|
+
</Product>
|
|
1055
|
+
<Ref to={`runtime-${n}`} />
|
|
1056
|
+
</Quotient>
|
|
1057
|
+
<Quotient label={`Quality, line ${n}`} format="percent">
|
|
1058
|
+
<Given label={`Good count, line ${n}`} value={line.good} unit="pcs" />
|
|
1059
|
+
<Ref to={`total-${n}`} />
|
|
1060
|
+
</Quotient>
|
|
1061
|
+
</Product>
|
|
1062
|
+
<Ref to={`planned-${n}`} />
|
|
1063
|
+
</Product>
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
export default function HallOee() {
|
|
1068
|
+
return (
|
|
1069
|
+
<Calculation aria-label="OEE of hall 2, early shift">
|
|
1070
|
+
<Quotient label="OEE, hall 2" format="percent" target={0.8}>
|
|
1071
|
+
<Sum label="Weighted OEE, all lines" unit="min">
|
|
1072
|
+
{LINES.map(lineOee)}
|
|
1073
|
+
</Sum>
|
|
1074
|
+
<Sum label="Planned time, all lines" unit="min">
|
|
1075
|
+
{LINES.map((line) => (
|
|
1076
|
+
<Ref key={line.name} to={`planned-${line.name}`} />
|
|
1077
|
+
))}
|
|
1078
|
+
</Sum>
|
|
1079
|
+
</Quotient>
|
|
1080
|
+
</Calculation>
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
```
|