@umriss-ui/calculation 0.2.3 → 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.
@@ -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
+ ```