@elaraai/e3-ui 1.0.94 → 1.0.96
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/dist/src/allocation/adapters.d.ts +159 -0
- package/dist/src/allocation/adapters.d.ts.map +1 -0
- package/dist/src/allocation/adapters.js +241 -0
- package/dist/src/allocation/adapters.js.map +1 -0
- package/dist/src/allocation/functions.d.ts +664 -0
- package/dist/src/allocation/functions.d.ts.map +1 -0
- package/dist/src/allocation/functions.js +782 -0
- package/dist/src/allocation/functions.js.map +1 -0
- package/dist/src/allocation/index.d.ts +2985 -0
- package/dist/src/allocation/index.d.ts.map +1 -0
- package/dist/src/allocation/index.js +499 -0
- package/dist/src/allocation/index.js.map +1 -0
- package/dist/src/allocation/types.d.ts +832 -0
- package/dist/src/allocation/types.d.ts.map +1 -0
- package/dist/src/allocation/types.js +360 -0
- package/dist/src/allocation/types.js.map +1 -0
- package/dist/src/bind/func.d.ts +14 -0
- package/dist/src/bind/func.d.ts.map +1 -1
- package/dist/src/bind/func.js +22 -1
- package/dist/src/bind/func.js.map +1 -1
- package/dist/src/calendar/index.d.ts +510 -1639
- package/dist/src/calendar/index.d.ts.map +1 -1
- package/dist/src/calendar/index.js +56 -29
- package/dist/src/calendar/index.js.map +1 -1
- package/dist/src/flowchart/canvas.d.ts +9 -21
- package/dist/src/flowchart/canvas.d.ts.map +1 -1
- package/dist/src/flowchart/canvas.js +10 -22
- package/dist/src/flowchart/canvas.js.map +1 -1
- package/dist/src/flowchart/flowchart.d.ts +19 -13
- package/dist/src/flowchart/flowchart.d.ts.map +1 -1
- package/dist/src/flowchart/flowchart.js.map +1 -1
- package/dist/src/flowchart/index.d.ts +44 -21
- package/dist/src/flowchart/index.d.ts.map +1 -1
- package/dist/src/flowchart/index.js +43 -20
- package/dist/src/flowchart/index.js.map +1 -1
- package/dist/src/flowchart/library.d.ts +166 -81
- package/dist/src/flowchart/library.d.ts.map +1 -1
- package/dist/src/flowchart/library.js +232 -105
- package/dist/src/flowchart/library.js.map +1 -1
- package/dist/src/flowchart/over.d.ts +9 -16
- package/dist/src/flowchart/over.d.ts.map +1 -1
- package/dist/src/flowchart/over.js +9 -16
- package/dist/src/flowchart/over.js.map +1 -1
- package/dist/src/flowchart/payload.d.ts +710 -508
- package/dist/src/flowchart/payload.d.ts.map +1 -1
- package/dist/src/flowchart/payload.js +128 -74
- package/dist/src/flowchart/payload.js.map +1 -1
- package/dist/src/flowchart/window.d.ts +132 -347
- package/dist/src/flowchart/window.d.ts.map +1 -1
- package/dist/src/flowchart/window.js +48 -55
- package/dist/src/flowchart/window.js.map +1 -1
- package/dist/src/index.d.ts +11 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +14 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +18 -7
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +28 -9
- package/dist/src/internal.js.map +1 -1
- package/dist/src/plan/event-rows.d.ts +26 -10
- package/dist/src/plan/event-rows.d.ts.map +1 -1
- package/dist/src/plan/event-rows.js +39 -13
- package/dist/src/plan/event-rows.js.map +1 -1
- package/dist/src/plan/ir.d.ts +244 -815
- package/dist/src/plan/ir.d.ts.map +1 -1
- package/dist/src/plan/ir.js +8 -6
- package/dist/src/plan/ir.js.map +1 -1
- package/dist/src/plan/library.d.ts +3 -0
- package/dist/src/plan/library.d.ts.map +1 -1
- package/dist/src/plan/library.js +1 -1
- package/dist/src/plan/library.js.map +1 -1
- package/dist/src/plan/ordered-source.d.ts +1 -0
- package/dist/src/plan/ordered-source.d.ts.map +1 -1
- package/dist/src/plan/plan.d.ts +559 -1667
- package/dist/src/plan/plan.d.ts.map +1 -1
- package/dist/src/plan/plan.js +123 -45
- package/dist/src/plan/plan.js.map +1 -1
- package/dist/src/plan/root.d.ts +16 -20
- package/dist/src/plan/root.d.ts.map +1 -1
- package/dist/src/plan/root.js +23 -33
- package/dist/src/plan/root.js.map +1 -1
- package/dist/src/roster/coverage.d.ts +17 -231
- package/dist/src/roster/coverage.d.ts.map +1 -1
- package/dist/src/roster/coverage.js +94 -90
- package/dist/src/roster/coverage.js.map +1 -1
- package/dist/src/roster/fit.d.ts +59 -0
- package/dist/src/roster/fit.d.ts.map +1 -0
- package/dist/src/roster/fit.js +155 -0
- package/dist/src/roster/fit.js.map +1 -0
- package/dist/src/roster/index.d.ts +2862 -3662
- package/dist/src/roster/index.d.ts.map +1 -1
- package/dist/src/roster/index.js +383 -81
- package/dist/src/roster/index.js.map +1 -1
- package/dist/src/roster/library.d.ts +37 -92
- package/dist/src/roster/library.d.ts.map +1 -1
- package/dist/src/roster/library.js +25 -64
- package/dist/src/roster/library.js.map +1 -1
- package/dist/src/roster/lists.d.ts +195 -0
- package/dist/src/roster/lists.d.ts.map +1 -0
- package/dist/src/roster/lists.js +233 -0
- package/dist/src/roster/lists.js.map +1 -0
- package/dist/src/roster/rules.d.ts +17 -260
- package/dist/src/roster/rules.d.ts.map +1 -1
- package/dist/src/roster/rules.js +135 -132
- package/dist/src/roster/rules.js.map +1 -1
- package/dist/src/roster/targets.d.ts +115 -0
- package/dist/src/roster/targets.d.ts.map +1 -0
- package/dist/src/roster/targets.js +213 -0
- package/dist/src/roster/targets.js.map +1 -0
- package/dist/src/roster/times.d.ts +251 -0
- package/dist/src/roster/times.d.ts.map +1 -0
- package/dist/src/roster/times.js +174 -0
- package/dist/src/roster/times.js.map +1 -0
- package/dist/src/roster/types.d.ts +828 -183
- package/dist/src/roster/types.d.ts.map +1 -1
- package/dist/src/roster/types.js +224 -97
- package/dist/src/roster/types.js.map +1 -1
- package/dist/src/schedule/accessor.d.ts +28 -0
- package/dist/src/schedule/accessor.d.ts.map +1 -0
- package/dist/src/schedule/accessor.js +39 -0
- package/dist/src/schedule/accessor.js.map +1 -0
- package/dist/src/schedule/events.d.ts +170 -42
- package/dist/src/schedule/events.d.ts.map +1 -1
- package/dist/src/schedule/events.js +431 -171
- package/dist/src/schedule/events.js.map +1 -1
- package/dist/src/schedule/index.d.ts +41 -4
- package/dist/src/schedule/index.d.ts.map +1 -1
- package/dist/src/schedule/index.js +35 -3
- package/dist/src/schedule/index.js.map +1 -1
- package/dist/src/schedule/library.d.ts +13 -3
- package/dist/src/schedule/library.d.ts.map +1 -1
- package/dist/src/schedule/library.js +11 -4
- package/dist/src/schedule/library.js.map +1 -1
- package/dist/src/schedule/periods.d.ts +115 -0
- package/dist/src/schedule/periods.d.ts.map +1 -0
- package/dist/src/schedule/periods.js +215 -0
- package/dist/src/schedule/periods.js.map +1 -0
- package/dist/src/schedule/record-window.d.ts +25 -3
- package/dist/src/schedule/record-window.d.ts.map +1 -1
- package/dist/src/schedule/record-window.js +31 -18
- package/dist/src/schedule/record-window.js.map +1 -1
- package/dist/src/schedule/resources.d.ts +82 -4
- package/dist/src/schedule/resources.d.ts.map +1 -1
- package/dist/src/schedule/resources.js +44 -14
- package/dist/src/schedule/resources.js.map +1 -1
- package/dist/src/schedule/session.d.ts +69 -0
- package/dist/src/schedule/session.d.ts.map +1 -0
- package/dist/src/schedule/session.js +121 -0
- package/dist/src/schedule/session.js.map +1 -0
- package/dist/src/schedule/templates.d.ts +14 -2
- package/dist/src/schedule/templates.d.ts.map +1 -1
- package/dist/src/schedule/templates.js +5 -3
- package/dist/src/schedule/templates.js.map +1 -1
- package/dist/src/schedule/types.d.ts +1671 -37
- package/dist/src/schedule/types.d.ts.map +1 -1
- package/dist/src/schedule/types.js +196 -5
- package/dist/src/schedule/types.js.map +1 -1
- package/dist/src/schedule/window.d.ts +14 -0
- package/dist/src/schedule/window.d.ts.map +1 -1
- package/dist/src/schedule/window.js +14 -4
- package/dist/src/schedule/window.js.map +1 -1
- package/dist/src/schedule/words.d.ts +22 -0
- package/dist/src/schedule/words.d.ts.map +1 -0
- package/dist/src/schedule/words.js +25 -0
- package/dist/src/schedule/words.js.map +1 -0
- package/dist/src/sheet/index.d.ts +61 -27
- package/dist/src/sheet/index.d.ts.map +1 -1
- package/dist/src/sheet/index.js +59 -25
- package/dist/src/sheet/index.js.map +1 -1
- package/dist/src/sheet/library.d.ts +21 -3
- package/dist/src/sheet/library.d.ts.map +1 -1
- package/dist/src/sheet/library.js +23 -6
- package/dist/src/sheet/library.js.map +1 -1
- package/dist/src/sheet/registers.d.ts +100 -85
- package/dist/src/sheet/registers.d.ts.map +1 -1
- package/dist/src/sheet/registers.js +100 -85
- package/dist/src/sheet/registers.js.map +1 -1
- package/dist/src/sheet/root.d.ts +8 -12
- package/dist/src/sheet/root.d.ts.map +1 -1
- package/dist/src/sheet/root.js +6 -40
- package/dist/src/sheet/root.js.map +1 -1
- package/dist/src/sheet/sheet.d.ts +598 -2349
- package/dist/src/sheet/sheet.d.ts.map +1 -1
- package/dist/src/sheet/sheet.js +124 -37
- package/dist/src/sheet/sheet.js.map +1 -1
- package/dist/src/sheet/sub-rows.d.ts +74 -28
- package/dist/src/sheet/sub-rows.d.ts.map +1 -1
- package/dist/src/sheet/sub-rows.js +74 -28
- package/dist/src/sheet/sub-rows.js.map +1 -1
- package/dist/src/sheet/templates.d.ts +98 -34
- package/dist/src/sheet/templates.d.ts.map +1 -1
- package/dist/src/sheet/templates.js +102 -58
- package/dist/src/sheet/templates.js.map +1 -1
- package/dist/src/sheet/types.d.ts +289 -1359
- package/dist/src/sheet/types.d.ts.map +1 -1
- package/dist/src/sheet/types.js +20 -26
- package/dist/src/sheet/types.js.map +1 -1
- package/dist/src/sheet/views.d.ts +10 -11
- package/dist/src/sheet/views.d.ts.map +1 -1
- package/dist/src/sheet/views.js +15 -20
- package/dist/src/sheet/views.js.map +1 -1
- package/dist/src/{roster/visibility.d.ts → utils/key-visibility.d.ts} +42 -7
- package/dist/src/utils/key-visibility.d.ts.map +1 -0
- package/dist/src/utils/key-visibility.js +36 -0
- package/dist/src/utils/key-visibility.js.map +1 -0
- package/dist/src/utils/ordered-source.d.ts +1 -0
- package/dist/src/utils/ordered-source.d.ts.map +1 -1
- package/dist/src/utils/ordered-source.js +8 -3
- package/dist/src/utils/ordered-source.js.map +1 -1
- package/dist/src/utils/record-sources.d.ts +107 -0
- package/dist/src/utils/record-sources.d.ts.map +1 -0
- package/dist/src/utils/record-sources.js +132 -0
- package/dist/src/utils/record-sources.js.map +1 -0
- package/dist/test/allocation/allocation.examples.d.ts +2902 -0
- package/dist/test/allocation/allocation.examples.d.ts.map +1 -0
- package/dist/test/allocation/allocation.examples.js +643 -0
- package/dist/test/allocation/allocation.examples.js.map +1 -0
- package/dist/test/allocation/fit.examples.d.ts +546 -0
- package/dist/test/allocation/fit.examples.d.ts.map +1 -0
- package/dist/test/allocation/fit.examples.js +171 -0
- package/dist/test/allocation/fit.examples.js.map +1 -0
- package/dist/test/allocation/kiln-yard.d.ts +181 -0
- package/dist/test/allocation/kiln-yard.d.ts.map +1 -0
- package/dist/test/allocation/kiln-yard.js +228 -0
- package/dist/test/allocation/kiln-yard.js.map +1 -0
- package/dist/test/bind/data/data.examples.d.ts +651 -1
- package/dist/test/bind/data/data.examples.d.ts.map +1 -1
- package/dist/test/bind/data/data.examples.js +140 -2
- package/dist/test/bind/data/data.examples.js.map +1 -1
- package/dist/test/bind/func/func.examples.d.ts +10 -1
- package/dist/test/bind/func/func.examples.d.ts.map +1 -1
- package/dist/test/bind/func/func.examples.js +8 -1
- package/dist/test/bind/func/func.examples.js.map +1 -1
- package/dist/test/bind/record/record.examples.d.ts +633 -1
- package/dist/test/bind/record/record.examples.d.ts.map +1 -1
- package/dist/test/bind/record/record.examples.js +26 -2
- package/dist/test/bind/record/record.examples.js.map +1 -1
- package/dist/test/calendar/calendar-records.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar-records.examples.js +18 -14
- package/dist/test/calendar/calendar-records.examples.js.map +1 -1
- package/dist/test/calendar/calendar.examples.d.ts +521 -16
- package/dist/test/calendar/calendar.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar.examples.js +182 -135
- package/dist/test/calendar/calendar.examples.js.map +1 -1
- package/dist/test/examples-package.d.ts +75 -0
- package/dist/test/examples-package.d.ts.map +1 -0
- package/dist/test/examples-package.js +263 -0
- package/dist/test/examples-package.js.map +1 -0
- package/dist/test/flowchart/flowchart.examples.d.ts +211 -19
- package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -1
- package/dist/test/flowchart/flowchart.examples.js +102 -75
- package/dist/test/flowchart/flowchart.examples.js.map +1 -1
- package/dist/test/plan/plan-events.examples.d.ts +1439 -209
- package/dist/test/plan/plan-events.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-events.examples.js +199 -59
- package/dist/test/plan/plan-events.examples.js.map +1 -1
- package/dist/test/plan/plan-records.examples.d.ts +420 -0
- package/dist/test/plan/plan-records.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-records.examples.js +70 -19
- package/dist/test/plan/plan-records.examples.js.map +1 -1
- package/dist/test/plan/plan-windows.examples.d.ts +636 -55
- package/dist/test/plan/plan-windows.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-windows.examples.js +69 -33
- package/dist/test/plan/plan-windows.examples.js.map +1 -1
- package/dist/test/plan/plan.examples.d.ts +153 -31
- package/dist/test/plan/plan.examples.d.ts.map +1 -1
- package/dist/test/plan/plan.examples.js +178 -101
- package/dist/test/plan/plan.examples.js.map +1 -1
- package/dist/test/roster/domain.examples.d.ts +467 -250
- package/dist/test/roster/domain.examples.d.ts.map +1 -1
- package/dist/test/roster/domain.examples.js +92 -29
- package/dist/test/roster/domain.examples.js.map +1 -1
- package/dist/test/roster/roster-records.examples.d.ts +430 -586
- package/dist/test/roster/roster-records.examples.d.ts.map +1 -1
- package/dist/test/roster/roster-records.examples.js +102 -43
- package/dist/test/roster/roster-records.examples.js.map +1 -1
- package/dist/test/roster/roster.examples.d.ts +2319 -606
- package/dist/test/roster/roster.examples.d.ts.map +1 -1
- package/dist/test/roster/roster.examples.js +456 -148
- package/dist/test/roster/roster.examples.js.map +1 -1
- package/dist/test/roster/warehouse.d.ts +172 -63
- package/dist/test/roster/warehouse.d.ts.map +1 -1
- package/dist/test/roster/warehouse.js +172 -39
- package/dist/test/roster/warehouse.js.map +1 -1
- package/dist/test/schedule/schedule.examples.d.ts +3499 -0
- package/dist/test/schedule/schedule.examples.d.ts.map +1 -0
- package/dist/test/schedule/schedule.examples.js +192 -0
- package/dist/test/schedule/schedule.examples.js.map +1 -0
- package/dist/test/sheet/sheet-records.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet-records.examples.js +22 -18
- package/dist/test/sheet/sheet-records.examples.js.map +1 -1
- package/dist/test/sheet/sheet.examples.d.ts +715 -26
- package/dist/test/sheet/sheet.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet.examples.js +204 -130
- package/dist/test/sheet/sheet.examples.js.map +1 -1
- package/package.json +17 -10
- package/dist/src/roster/people.d.ts +0 -50
- package/dist/src/roster/people.d.ts.map +0 -1
- package/dist/src/roster/people.js +0 -32
- package/dist/src/roster/people.js.map +0 -1
- package/dist/src/roster/visibility.d.ts.map +0 -1
- package/dist/src/roster/visibility.js +0 -25
- package/dist/src/roster/visibility.js.map +0 -1
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Allocation's pure functions (`Allocation Spec.md` §5.1, §12): a day's
|
|
7
|
+
* loads, a fit's candidates, the built-in rules, an answer to a fit's
|
|
8
|
+
* question, a greedy look-ahead, the built-in checks and the day's slots, and
|
|
9
|
+
* a bin as the board reads it from the app's row. Tasks, the app's
|
|
10
|
+
* `e3.function` fit and the renderer run these same East functions, so the
|
|
11
|
+
* screen and e3 agree on every figure and every word.
|
|
12
|
+
*
|
|
13
|
+
* Those over a day take the app's bin row type (#1371, `Builder Interfaces
|
|
14
|
+
* Spec.md` §6.3), as `Editing.apply` takes its source type:
|
|
15
|
+
* `Allocation.loads(KilnBin)` is an East function over
|
|
16
|
+
* `Allocation.Types.Day(KilnBin)`. They read each bin's roles, never its row,
|
|
17
|
+
* which they carry for the app's own functions. `Allocation.fit` reads one
|
|
18
|
+
* candidate, so it needs none.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
import { ArrayType, BooleanType, DateTimeType, DictType, East, FloatType, FunctionType, IntegerType, OptionType, SetType, StringType, StructType, none, some, variant, } from "@elaraai/east";
|
|
23
|
+
import { StatusTokenType, TimeStepType } from "@elaraai/east-ui";
|
|
24
|
+
import { ScheduleAllocationRolesType } from "../schedule/types.js";
|
|
25
|
+
import { scheduleNumberText } from "../schedule/words.js";
|
|
26
|
+
import { AllocationArrivalType, AllocationBinTypeFor, AllocationCandidateType, AllocationContextType, AllocationDayTypeFor, AllocationFitRequestTypeFor, AllocationFitResultType, AllocationFitType, AllocationFixType, AllocationIntervalType, AllocationIssueType, AllocationJobType, AllocationLoadType, AllocationResourceLineType, AllocationResourceType, AllocationRulesType, AllocationSlotType, } from "./types.js";
|
|
27
|
+
/** A day, in milliseconds. */
|
|
28
|
+
const DAY_MS = 86_400_000;
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Words and time
|
|
31
|
+
// ============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* A duration in milliseconds — a month counts 30 days, as Schedule's do.
|
|
34
|
+
*
|
|
35
|
+
* @param step - The duration
|
|
36
|
+
* @returns Its length in milliseconds
|
|
37
|
+
*/
|
|
38
|
+
export const allocationMilliseconds = East.function([TimeStepType], IntegerType, (_$, step) => East.Float.roundHalf(step.match({
|
|
39
|
+
minutes: (_$2, n) => n.multiply(60_000.0),
|
|
40
|
+
hours: (_$2, n) => n.multiply(3_600_000.0),
|
|
41
|
+
days: (_$2, n) => n.multiply(86_400_000.0),
|
|
42
|
+
weeks: (_$2, n) => n.multiply(604_800_000.0),
|
|
43
|
+
months: (_$2, n) => n.multiply(2_592_000_000.0),
|
|
44
|
+
})));
|
|
45
|
+
/**
|
|
46
|
+
* A quantity and its unit.
|
|
47
|
+
*
|
|
48
|
+
* @param value - The quantity
|
|
49
|
+
* @param unit - The unit, or empty
|
|
50
|
+
* @returns `21 m³`, or `21` with no unit
|
|
51
|
+
*/
|
|
52
|
+
export const allocationQuantityText = East.function([FloatType, StringType], StringType, ($, value, unit) => {
|
|
53
|
+
const num = $.const(scheduleNumberText);
|
|
54
|
+
return East.equal(unit, "").ifElse(() => num(value), () => East.str `${num(value)} ${unit}`);
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* A day as the board names it.
|
|
58
|
+
*
|
|
59
|
+
* @param at - An instant on the day
|
|
60
|
+
* @returns `Wed 19`
|
|
61
|
+
*/
|
|
62
|
+
export const allocationDayText = East.function([DateTimeType], StringType, (_$, at) => at.printFormatted("ddd D"));
|
|
63
|
+
/**
|
|
64
|
+
* A resource's load, naming the limit it breaks (A19).
|
|
65
|
+
*
|
|
66
|
+
* @param fill - What its bins put on it
|
|
67
|
+
* @param resource - The resource
|
|
68
|
+
* @param unit - The quantity's unit
|
|
69
|
+
* @returns `11 m³ · max 9 · 2 over`, `16 m³ · min 40 · 24 short`, or `45 m³ · takes 26–48`
|
|
70
|
+
*/
|
|
71
|
+
export const allocationLoadText = East.function([FloatType, AllocationResourceType, StringType], StringType, ($, fill, resource, unit) => {
|
|
72
|
+
const num = $.const(scheduleNumberText);
|
|
73
|
+
const qty = $.const(allocationQuantityText);
|
|
74
|
+
const f = $.const(East.Float.roundHalf(fill).toFloat());
|
|
75
|
+
const out = $.let(East.str `${qty(f, unit)} · takes ${num(resource.min)}–${num(resource.max)}`);
|
|
76
|
+
$.if(East.greater(f, resource.max), ($2) => {
|
|
77
|
+
$2.assign(out, East.str `${qty(f, unit)} · max ${num(resource.max)} · ${num(f.subtract(resource.max))} over`);
|
|
78
|
+
}).else(($2) => {
|
|
79
|
+
$2.if(East.greater(f, 0.0).and(() => East.less(f, resource.min)), ($3) => {
|
|
80
|
+
$3.assign(out, East.str `${qty(f, unit)} · min ${num(resource.min)} · ${num(resource.min.subtract(f))} short`);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
return out;
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* The job that makes a resource busy on a day: one covering the day's start,
|
|
87
|
+
* the latest to end of them.
|
|
88
|
+
*
|
|
89
|
+
* @param resource - The resource
|
|
90
|
+
* @param day - The plan day
|
|
91
|
+
* @returns The job, or `none`
|
|
92
|
+
*/
|
|
93
|
+
export const allocationBusyJob = East.function([AllocationResourceType, DateTimeType], OptionType(AllocationJobType), ($, resource, day) => {
|
|
94
|
+
const found = $.let(none, OptionType(AllocationJobType));
|
|
95
|
+
$.for(resource.jobs, ($2, job) => {
|
|
96
|
+
$2.if(East.lessEqual(job.start, day).and(() => East.greater(job.end, day)), ($3) => {
|
|
97
|
+
$3.match(found, {
|
|
98
|
+
none: ($4) => { $4.assign(found, some(job)); },
|
|
99
|
+
some: ($4, current) => { $4.if(East.greater(job.end, current.end), ($5) => { $5.assign(found, some(job)); }); },
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
return found;
|
|
104
|
+
});
|
|
105
|
+
/**
|
|
106
|
+
* A resource's next job after a day's start: the first to start.
|
|
107
|
+
*
|
|
108
|
+
* @param resource - The resource
|
|
109
|
+
* @param day - The plan day
|
|
110
|
+
* @returns The job, or `none`
|
|
111
|
+
*/
|
|
112
|
+
export const allocationNextJob = East.function([AllocationResourceType, DateTimeType], OptionType(AllocationJobType), ($, resource, day) => {
|
|
113
|
+
const found = $.let(none, OptionType(AllocationJobType));
|
|
114
|
+
$.for(resource.jobs, ($2, job) => {
|
|
115
|
+
$2.if(East.greater(job.start, day), ($3) => {
|
|
116
|
+
$3.match(found, {
|
|
117
|
+
none: ($4) => { $4.assign(found, some(job)); },
|
|
118
|
+
some: ($4, current) => { $4.if(East.less(job.start, current.start), ($5) => { $5.assign(found, some(job)); }); },
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
return found;
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* A resource's line on the plan day (A19), and the marks its bins' cards carry
|
|
126
|
+
* (A16).
|
|
127
|
+
*
|
|
128
|
+
* @param day - The plan day
|
|
129
|
+
* @param resource - The resource
|
|
130
|
+
* @param load - Its load
|
|
131
|
+
* @param unit - The quantity's unit
|
|
132
|
+
* @returns Its line, its tone, whether it is over its maximum, under its minimum, or held into its next job, and that job
|
|
133
|
+
*
|
|
134
|
+
* @remarks
|
|
135
|
+
* Loading, the line is its load (`45 m³ · takes 26–48`; `reserved · takes
|
|
136
|
+
* 26–48` with only reservations) and `· 2 d into next job` when its hold runs
|
|
137
|
+
* into the job; busy, `busy until Fri 14`; free, its meta (or its group) and
|
|
138
|
+
* what it takes (`Hall B · 40 m³ · takes 20–40 m³`).
|
|
139
|
+
*/
|
|
140
|
+
export const allocationResourceLine = East.function([DateTimeType, AllocationResourceType, AllocationLoadType, StringType], AllocationResourceLineType, ($, day, resource, load, unit) => {
|
|
141
|
+
const busyOf = $.const(allocationBusyJob);
|
|
142
|
+
const nextOf = $.const(allocationNextJob);
|
|
143
|
+
const dayText = $.const(allocationDayText);
|
|
144
|
+
const num = $.const(scheduleNumberText);
|
|
145
|
+
const qty = $.const(allocationQuantityText);
|
|
146
|
+
const loadText = $.const(allocationLoadText);
|
|
147
|
+
const over = $.const(East.greater(load.fill, resource.max));
|
|
148
|
+
const under = $.const(East.greater(load.fill, 0.0).and(() => East.less(load.fill, resource.min)));
|
|
149
|
+
const next = $.const(nextOf(resource, day));
|
|
150
|
+
const overlap = $.let(false, BooleanType);
|
|
151
|
+
const into = $.let(0.0, FloatType);
|
|
152
|
+
$.match(load.hold, {
|
|
153
|
+
some: ($2, hold) => {
|
|
154
|
+
$2.match(next, {
|
|
155
|
+
some: ($3, job) => {
|
|
156
|
+
$3.if(East.greater(hold.to, job.start), ($4) => {
|
|
157
|
+
$4.assign(overlap, true);
|
|
158
|
+
$4.assign(into, job.start.durationMilliseconds(hold.to).toFloat().divide(DAY_MS));
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
const text = $.let("", StringType);
|
|
165
|
+
$.match(load.state, {
|
|
166
|
+
loading: ($2) => {
|
|
167
|
+
const held = $2.const(East.greater(load.fill, 0.0).ifElse(() => loadText(load.fill, resource, unit), () => East.str `reserved · takes ${num(resource.min)}–${num(resource.max)}`));
|
|
168
|
+
$2.assign(text, overlap.ifElse(() => East.str `${held} · ${num(into)} d into next job`, () => held));
|
|
169
|
+
},
|
|
170
|
+
busy: ($2) => {
|
|
171
|
+
$2.assign(text, busyOf(resource, day).match({
|
|
172
|
+
some: (_$3, job) => East.str `busy until ${dayText(job.end)}`,
|
|
173
|
+
none: (_$3) => East.str `busy`,
|
|
174
|
+
}));
|
|
175
|
+
},
|
|
176
|
+
free: ($2) => {
|
|
177
|
+
const takes = $2.const(East.str `takes ${num(resource.min)}–${qty(resource.max, unit)}`);
|
|
178
|
+
$2.assign(text, resource.meta.match({
|
|
179
|
+
some: (_$3, meta) => East.str `${meta} · ${takes}`,
|
|
180
|
+
none: (_$3) => resource.group.match({ some: (_$4, group) => East.str `${group} · ${takes}`, none: (_$4) => takes }),
|
|
181
|
+
}));
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
text,
|
|
186
|
+
tone: over.ifElse(() => East.value(variant("danger", null), StatusTokenType), () => overlap.or(() => under).ifElse(() => East.value(variant("warning", null), StatusTokenType), () => East.value(variant("neutral", null), StatusTokenType))),
|
|
187
|
+
over, under, overlap, next,
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// A bin, as the board reads it from the app's row
|
|
192
|
+
// ============================================================================
|
|
193
|
+
/**
|
|
194
|
+
* One bin, as the board reads it on its plan day (#1371, B12): its kind's
|
|
195
|
+
* roles beside its row — a stored bin's roles as the kind's `rows` gives them,
|
|
196
|
+
* a drafted one's as its `read` does.
|
|
197
|
+
*
|
|
198
|
+
* @typeParam R - The app's bin row type
|
|
199
|
+
* @param row - The app's bin row type
|
|
200
|
+
* @returns An East function `(roles, row, day) → Option<Bin(R)>`: the bin, when its `at` falls in the day `[day, day + 1 d)`; `none` for a bin of another day
|
|
201
|
+
*
|
|
202
|
+
* @remarks
|
|
203
|
+
* A bin's UTC day is its plan day (`Builder Interfaces Spec.md` §6.2), so the
|
|
204
|
+
* board's day holds the bins whose `at` falls in it, `day` its UTC midnight.
|
|
205
|
+
* Each bin's `at` is read as it is, so a batch's bins, booked a minute apart,
|
|
206
|
+
* read back a minute apart, in one slot (`Allocation.slots`).
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
export function allocationBinOf(row) {
|
|
210
|
+
const Bin = AllocationBinTypeFor(row);
|
|
211
|
+
return East.function([ScheduleAllocationRolesType, row, DateTimeType], OptionType(Bin), ($, roles, value, day) => {
|
|
212
|
+
$.if(East.less(roles.at, day).or(() => East.greaterEqual(roles.at, day.addDays(1n))), ($2) => { $2.return(none); });
|
|
213
|
+
return some({
|
|
214
|
+
lane: roles.lane, at: roles.at, arrival: roles.arrival, quantity: roles.quantity, kind: roles.kind, hold: roles.hold, parts: roles.parts,
|
|
215
|
+
row: value,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Mates: what a resource already holds, without the app's rows
|
|
221
|
+
// ============================================================================
|
|
222
|
+
/**
|
|
223
|
+
* One bin's part on one resource, as loads and candidates read it.
|
|
224
|
+
*
|
|
225
|
+
* @property bin - The bin's id; empty for a placement the look-ahead adds
|
|
226
|
+
* @property quantity - Its part's quantity
|
|
227
|
+
* @property hold - Its hold, in milliseconds
|
|
228
|
+
* @property kind - Its kind
|
|
229
|
+
* @property reserve - Whether it is a reservation
|
|
230
|
+
* @internal
|
|
231
|
+
*/
|
|
232
|
+
export const AllocationMateType = StructType({ bin: StringType, quantity: FloatType, hold: IntegerType, kind: OptionType(StringType), reserve: BooleanType });
|
|
233
|
+
/** The bins' parts on every resource, by the resource's key. @internal */
|
|
234
|
+
const MatesType = DictType(StringType, ArrayType(AllocationMateType));
|
|
235
|
+
/**
|
|
236
|
+
* Every resource's parts of the day's bins, an entry for every resource.
|
|
237
|
+
*
|
|
238
|
+
* @typeParam R - The app's bin row type
|
|
239
|
+
* @param row - The app's bin row type
|
|
240
|
+
* @returns An East function from the day and the context to the parts on each resource
|
|
241
|
+
* @internal
|
|
242
|
+
*/
|
|
243
|
+
export function allocationMates(row) {
|
|
244
|
+
return East.function([AllocationDayTypeFor(row), AllocationContextType], MatesType, ($, plan, context) => {
|
|
245
|
+
const step = $.const(allocationMilliseconds);
|
|
246
|
+
const out = $.let(context.resources.map((_$2, _resource) => East.value([], ArrayType(AllocationMateType))));
|
|
247
|
+
$.for(plan.bins, ($2, bin, id) => {
|
|
248
|
+
const hold = $2.const(step(bin.hold));
|
|
249
|
+
const reserve = $2.const(bin.arrival.hasTag("none"));
|
|
250
|
+
$2.for(bin.parts, ($3, quantity, resource) => {
|
|
251
|
+
$3.if(out.has(resource), ($4) => {
|
|
252
|
+
$4(out.get(resource).pushLast({ bin: id, quantity, hold, kind: bin.kind, reserve }));
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
return out;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* A resource's load from its parts.
|
|
261
|
+
*
|
|
262
|
+
* @param day - The plan day
|
|
263
|
+
* @param resource - The resource
|
|
264
|
+
* @param mates - Its parts
|
|
265
|
+
* @returns Its load
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
export const allocationLoadOf = East.function([DateTimeType, AllocationResourceType, ArrayType(AllocationMateType)], AllocationLoadType, ($, day, resource, mates) => {
|
|
269
|
+
const step = $.const(allocationMilliseconds);
|
|
270
|
+
const busyOf = $.const(allocationBusyJob);
|
|
271
|
+
const fill = $.let(0.0, FloatType);
|
|
272
|
+
const longest = $.let(none, OptionType(IntegerType));
|
|
273
|
+
$.for(mates, ($2, mate) => {
|
|
274
|
+
$2.assign(fill, fill.add(mate.quantity));
|
|
275
|
+
$2.if(mate.reserve.not(), ($3) => {
|
|
276
|
+
$3.match(longest, {
|
|
277
|
+
none: ($4) => { $4.assign(longest, some(mate.hold)); },
|
|
278
|
+
some: ($4, held) => { $4.assign(longest, some(East.max(held, mate.hold))); },
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
// A resource holding only reservations is held for its own hold.
|
|
283
|
+
const length = $.const(longest.unwrap("some", () => step(resource.hold)));
|
|
284
|
+
const hold = $.let(none, OptionType(AllocationIntervalType));
|
|
285
|
+
$.if(East.greater(mates.size(), 0n), ($2) => { $2.assign(hold, some({ from: day, to: day.addMilliseconds(length) })); });
|
|
286
|
+
const busy = $.const(busyOf(resource, day).hasTag("some"));
|
|
287
|
+
return {
|
|
288
|
+
resource: resource.key, fill, bins: mates.size(), hold,
|
|
289
|
+
state: East.greater(mates.size(), 0n).ifElse(() => East.value(variant("loading", null), AllocationLoadType.fields.state), () => busy.ifElse(() => East.value(variant("busy", null), AllocationLoadType.fields.state), () => East.value(variant("free", null), AllocationLoadType.fields.state))),
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
/**
|
|
293
|
+
* One candidate: a quantity on a resource, against the parts it already holds.
|
|
294
|
+
*
|
|
295
|
+
* @param day - The plan day
|
|
296
|
+
* @param bin - The bin it is for, if any
|
|
297
|
+
* @param reserve - Whether it reserves a slot
|
|
298
|
+
* @param quantity - What the resource would hold of it
|
|
299
|
+
* @param arrival - Its arrival, if any
|
|
300
|
+
* @param kind - Its kind
|
|
301
|
+
* @param hold - Its hold, in milliseconds
|
|
302
|
+
* @param resource - The resource
|
|
303
|
+
* @param mates - The resource's other parts
|
|
304
|
+
* @param load - The resource's load
|
|
305
|
+
* @param rules - The built-in rules
|
|
306
|
+
* @param unit - The quantity's unit
|
|
307
|
+
* @returns The candidate
|
|
308
|
+
* @internal
|
|
309
|
+
*/
|
|
310
|
+
export const allocationCandidateOf = East.function([
|
|
311
|
+
DateTimeType, OptionType(StringType), BooleanType, FloatType, OptionType(AllocationArrivalType), OptionType(StringType), IntegerType,
|
|
312
|
+
AllocationResourceType, ArrayType(AllocationMateType), AllocationLoadType, AllocationRulesType, StringType,
|
|
313
|
+
], AllocationCandidateType, ($, day, bin, reserve, quantity, arrival, kind, hold, resource, mates, load, rules, unit) => {
|
|
314
|
+
const nextOf = $.const(allocationNextJob);
|
|
315
|
+
const fill = $.let(0.0, FloatType);
|
|
316
|
+
const length = $.let(hold, IntegerType);
|
|
317
|
+
const kindMates = $.let(false, BooleanType);
|
|
318
|
+
$.for(mates, ($2, mate) => {
|
|
319
|
+
$2.assign(fill, fill.add(mate.quantity));
|
|
320
|
+
$2.assign(length, East.max(length, mate.hold));
|
|
321
|
+
$2.if(kind.hasTag("some").and(() => East.equal(mate.kind, kind)), ($3) => { $3.assign(kindMates, true); });
|
|
322
|
+
});
|
|
323
|
+
const to = $.const(day.addMilliseconds(length));
|
|
324
|
+
const spare = $.let(none, OptionType(FloatType));
|
|
325
|
+
$.match(nextOf(resource, day), {
|
|
326
|
+
some: ($2, job) => { $2.assign(spare, some(to.durationMilliseconds(job.start).toFloat().divide(DAY_MS))); },
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
day, bin, reserve, quantity, arrival, kind, resource, load, after: fill.add(quantity), hold: { from: day, to }, spare, kindMates,
|
|
330
|
+
otherMates: East.greater(mates.size(), 0n).and(() => kindMates.not()), rules, unit,
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// The built-in rules
|
|
335
|
+
// ============================================================================
|
|
336
|
+
/**
|
|
337
|
+
* The built-in rules (§12.3): one verdict for one candidate.
|
|
338
|
+
*
|
|
339
|
+
* @param candidate - A quantity on a resource, against the drafted day
|
|
340
|
+
* @returns Its verdict
|
|
341
|
+
*
|
|
342
|
+
* @remarks
|
|
343
|
+
* In order: a job covering the day's start refuses it (`busy until Fri 14`);
|
|
344
|
+
* a hold running past the next job's start refuses it (`gap too short · next
|
|
345
|
+
* job Wed 19`); a fill over the maximum refuses it (`over max by 4 m³`); a
|
|
346
|
+
* reservation on a resource already at its maximum is refused (`full · …`).
|
|
347
|
+
* Otherwise it fits: its score is what it fills of the maximum, plus 0.25 when
|
|
348
|
+
* a bin of its kind is already there, less 0.1 when only other kinds are, less
|
|
349
|
+
* 0.5 under the minimum, less 0.04 a spare day up to six; its reason the spare
|
|
350
|
+
* days and the load it leaves; under the minimum it warns, when the rules ask.
|
|
351
|
+
*/
|
|
352
|
+
export const allocationFit = East.function([AllocationCandidateType], AllocationFitType, ($, candidate) => {
|
|
353
|
+
const busyOf = $.const(allocationBusyJob);
|
|
354
|
+
const nextOf = $.const(allocationNextJob);
|
|
355
|
+
const dayText = $.const(allocationDayText);
|
|
356
|
+
const qty = $.const(allocationQuantityText);
|
|
357
|
+
const num = $.const(scheduleNumberText);
|
|
358
|
+
const loadText = $.const(allocationLoadText);
|
|
359
|
+
const resource = $.const(candidate.resource);
|
|
360
|
+
$.match(busyOf(resource, candidate.day), {
|
|
361
|
+
some: ($2, job) => {
|
|
362
|
+
$2.return(variant("refused", { group: "Busy", reason: East.str `busy until ${dayText(job.end)}`, measure: variant("day", { lead: "until", at: job.end }) }));
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
const next = $.const(nextOf(resource, candidate.day));
|
|
366
|
+
$.match(next, {
|
|
367
|
+
some: ($2, job) => {
|
|
368
|
+
$2.if(East.greater(candidate.hold.to, job.start), ($3) => {
|
|
369
|
+
$3.return(variant("refused", {
|
|
370
|
+
group: "Gap too short", reason: East.str `gap too short · next job ${dayText(job.start)}`,
|
|
371
|
+
measure: variant("day", { lead: "next job", at: job.start }),
|
|
372
|
+
}));
|
|
373
|
+
});
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
$.if(East.greater(candidate.after, resource.max), ($2) => {
|
|
377
|
+
const by = $2.const(East.Float.roundHalf(candidate.after.subtract(resource.max)).toFloat());
|
|
378
|
+
$2.return(variant("refused", { group: "Over max", reason: East.str `over max by ${qty(by, candidate.unit)}`, measure: variant("amount", { lead: "by", value: by }) }));
|
|
379
|
+
});
|
|
380
|
+
const fill = $.const(candidate.after.subtract(candidate.quantity));
|
|
381
|
+
$.if(candidate.reserve.and(() => East.greaterEqual(fill, resource.max)), ($2) => {
|
|
382
|
+
$2.return(variant("refused", { group: "Full", reason: East.str `full · ${loadText(fill, resource, candidate.unit)}`, measure: variant("none", null) }));
|
|
383
|
+
});
|
|
384
|
+
// The score, term for term in the mock's order, so equal fits tie exactly.
|
|
385
|
+
const spare = $.const(candidate.spare.unwrap("some", () => 9.0));
|
|
386
|
+
const score = $.const(candidate.after.divide(resource.max)
|
|
387
|
+
.add(candidate.kindMates.ifElse(() => 0.25, () => 0.0))
|
|
388
|
+
.subtract(candidate.otherMates.ifElse(() => 0.1, () => 0.0))
|
|
389
|
+
.subtract(East.less(candidate.after, resource.min).ifElse(() => 0.5, () => 0.0))
|
|
390
|
+
.subtract(East.min(spare, $.const(6.0, FloatType)).multiply(0.04)));
|
|
391
|
+
const gap = $.const(next.match({
|
|
392
|
+
some: (_$2, job) => East.str `${num(spare)} d spare to ${dayText(job.start)}`,
|
|
393
|
+
none: (_$2) => East.str `open gap`,
|
|
394
|
+
}));
|
|
395
|
+
return variant("fits", {
|
|
396
|
+
score, why: East.str `${gap} · leaves ${loadText(candidate.after, resource, candidate.unit)}`,
|
|
397
|
+
warn: East.less(candidate.after, resource.min).and(() => candidate.rules.under).ifElse(() => East.value(some("under min"), OptionType(StringType)), () => East.value(none, OptionType(StringType))),
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
// ============================================================================
|
|
401
|
+
// Loads, candidates and answers
|
|
402
|
+
// ============================================================================
|
|
403
|
+
/**
|
|
404
|
+
* Every resource's load on the plan day.
|
|
405
|
+
*
|
|
406
|
+
* @typeParam R - The app's bin row type
|
|
407
|
+
* @param row - The app's bin row type
|
|
408
|
+
* @returns An East function `(day, plan, context) → Dict<String, Load>`: an entry for every resource, by key
|
|
409
|
+
*/
|
|
410
|
+
export function allocationLoads(row) {
|
|
411
|
+
const matesOf = allocationMates(row);
|
|
412
|
+
return East.function([DateTimeType, AllocationDayTypeFor(row), AllocationContextType], DictType(StringType, AllocationLoadType), ($, day, plan, context) => {
|
|
413
|
+
const parts = $.const(matesOf);
|
|
414
|
+
const loadOf = $.const(allocationLoadOf);
|
|
415
|
+
const mates = $.const(parts(plan, context));
|
|
416
|
+
return context.resources.map(($2, resource, key) => loadOf(day, resource, mates.get(key)));
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* The candidates for a fit's question.
|
|
421
|
+
*
|
|
422
|
+
* @typeParam R - The app's bin row type
|
|
423
|
+
* @param row - The app's bin row type
|
|
424
|
+
* @returns An East function `(request) → Array<Candidate>`: a place's subject on every resource, in the resources' key order; or for a take, every bin on its resource, then every arrival the day has not booked, then a reservation
|
|
425
|
+
*
|
|
426
|
+
* @remarks
|
|
427
|
+
* A bin is placed with its own kind and hold, an arrival with its, and a bin
|
|
428
|
+
* booking no arrival — a reservation — with no quantity and each resource's
|
|
429
|
+
* own hold (§12.3, rule 4); the resource's other parts are every bin's but
|
|
430
|
+
* the subject's. A take places each bin's unallocated quantity (with its part
|
|
431
|
+
* already there), or the whole bin when it has none; then each arrival no bin
|
|
432
|
+
* of the day books, whole, as a place of it would; and last a reservation with
|
|
433
|
+
* the resource's hold.
|
|
434
|
+
*/
|
|
435
|
+
export function allocationCandidates(row) {
|
|
436
|
+
const matesOf = allocationMates(row);
|
|
437
|
+
return East.function([AllocationFitRequestTypeFor(row)], ArrayType(AllocationCandidateType), ($, request) => {
|
|
438
|
+
const parts = $.const(matesOf);
|
|
439
|
+
const loadOf = $.const(allocationLoadOf);
|
|
440
|
+
const candidateOf = $.const(allocationCandidateOf);
|
|
441
|
+
const step = $.const(allocationMilliseconds);
|
|
442
|
+
const context = $.const(request.context);
|
|
443
|
+
const day = $.const(request.day);
|
|
444
|
+
const mates = $.const(parts(request.plan, context));
|
|
445
|
+
const out = $.let([], ArrayType(AllocationCandidateType));
|
|
446
|
+
$.match(request.ask, {
|
|
447
|
+
place: ($2, place) => {
|
|
448
|
+
const bin = $2.let(none, OptionType(StringType));
|
|
449
|
+
const arrival = $2.let(none, OptionType(AllocationArrivalType));
|
|
450
|
+
const kind = $2.let(none, OptionType(StringType));
|
|
451
|
+
const hold = $2.let(0n, IntegerType);
|
|
452
|
+
const reserve = $2.let(false, BooleanType);
|
|
453
|
+
$2.match(place.subject, {
|
|
454
|
+
bin: ($3, id) => {
|
|
455
|
+
const subject = $3.const(request.plan.bins.get(id));
|
|
456
|
+
$3.assign(bin, some(id));
|
|
457
|
+
$3.match(subject.arrival, { some: ($4, key) => { $4.assign(arrival, context.arrivals.tryGet(key)); } });
|
|
458
|
+
$3.assign(kind, subject.kind);
|
|
459
|
+
$3.assign(hold, step(subject.hold));
|
|
460
|
+
$3.assign(reserve, subject.arrival.hasTag("none"));
|
|
461
|
+
},
|
|
462
|
+
arrival: ($3, key) => {
|
|
463
|
+
const subject = $3.const(context.arrivals.get(key));
|
|
464
|
+
$3.assign(arrival, some(subject));
|
|
465
|
+
$3.assign(kind, subject.kind);
|
|
466
|
+
$3.assign(hold, step(subject.hold));
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
const subject = $2.const(bin.unwrap("some", () => ""));
|
|
470
|
+
$2.for(context.resources, ($3, resource, key) => {
|
|
471
|
+
const all = $3.const(mates.get(key));
|
|
472
|
+
const others = $3.const(all.filter(($4, mate) => East.notEqual(mate.bin, subject)));
|
|
473
|
+
const length = $3.const(reserve.ifElse(() => step(resource.hold), () => hold));
|
|
474
|
+
$3(out.pushLast(candidateOf(day, bin, reserve, place.quantity, arrival, kind, length, resource, others, loadOf(day, resource, all), context.rules, context.unit)));
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
take: ($2, take) => {
|
|
478
|
+
const resource = $2.const(context.resources.get(take.resource));
|
|
479
|
+
const all = $2.const(mates.get(take.resource));
|
|
480
|
+
const load = $2.const(loadOf(day, resource, all));
|
|
481
|
+
$2.for(request.plan.bins, ($3, bin, id) => {
|
|
482
|
+
const allocated = $3.let(0.0, FloatType);
|
|
483
|
+
$3.for(bin.parts, ($4, part) => { $4.assign(allocated, allocated.add(part)); });
|
|
484
|
+
const reserve = $3.const(bin.arrival.hasTag("none"));
|
|
485
|
+
const quantity = $3.let(bin.quantity, FloatType);
|
|
486
|
+
$3.if(reserve, ($4) => { $4.assign(quantity, 0.0); }).else(($4) => {
|
|
487
|
+
$4.if(East.less(allocated, bin.quantity), ($5) => {
|
|
488
|
+
$5.assign(quantity, bin.quantity.subtract(allocated).add(bin.parts.get(take.resource, () => 0.0)));
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
const arrival = $3.let(none, OptionType(AllocationArrivalType));
|
|
492
|
+
$3.match(bin.arrival, { some: ($4, key) => { $4.assign(arrival, context.arrivals.tryGet(key)); } });
|
|
493
|
+
const others = $3.const(all.filter(($4, mate) => East.notEqual(mate.bin, id)));
|
|
494
|
+
$3(out.pushLast(candidateOf(day, some(id), reserve, quantity, arrival, bin.kind, step(bin.hold), resource, others, load, context.rules, context.unit)));
|
|
495
|
+
});
|
|
496
|
+
// Every arrival the day has not booked, whole on the resource.
|
|
497
|
+
const booked = $2.let(new Set(), SetType(StringType));
|
|
498
|
+
$2.for(request.plan.bins, ($3, bin) => {
|
|
499
|
+
$3.match(bin.arrival, { some: ($4, key) => { $4.if(booked.has(key).not(), ($5) => { $5(booked.insert(key)); }); } });
|
|
500
|
+
});
|
|
501
|
+
$2.for(context.arrivals, ($3, arrival, key) => {
|
|
502
|
+
$3.if(booked.has(key).not(), ($4) => {
|
|
503
|
+
$4(out.pushLast(candidateOf(day, none, false, arrival.quantity, some(arrival), arrival.kind, step(arrival.hold), resource, all, load, context.rules, context.unit)));
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
$2(out.pushLast(candidateOf(day, none, true, 0.0, none, none, step(resource.hold), resource, all, load, context.rules, context.unit)));
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
return out;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Answers a fit's question with a fit per candidate.
|
|
514
|
+
*
|
|
515
|
+
* @typeParam R - The app's bin row type
|
|
516
|
+
* @param row - The app's bin row type
|
|
517
|
+
* @returns An East function `(request, fit, basis) → FitResult`: a place's verdict for every resource, or a take's for every bin, every arrival the day has not booked, and reserving; `basis` says in words how the fit ranks (`ranked by load, kind and spare days`)
|
|
518
|
+
*/
|
|
519
|
+
export function allocationAnswer(row) {
|
|
520
|
+
const candidatesOf = allocationCandidates(row);
|
|
521
|
+
return East.function([
|
|
522
|
+
AllocationFitRequestTypeFor(row), FunctionType([AllocationCandidateType], AllocationFitType), StringType,
|
|
523
|
+
], AllocationFitResultType, ($, request, fit, basis) => {
|
|
524
|
+
const candidates = $.const(candidatesOf);
|
|
525
|
+
const resources = $.let(new Map(), DictType(StringType, AllocationFitType));
|
|
526
|
+
const bins = $.let(new Map(), DictType(StringType, AllocationFitType));
|
|
527
|
+
const arrivals = $.let(new Map(), DictType(StringType, AllocationFitType));
|
|
528
|
+
const reserve = $.let(none, OptionType(AllocationFitType));
|
|
529
|
+
$.for(candidates(request), ($2, candidate) => {
|
|
530
|
+
$2.match(request.ask, {
|
|
531
|
+
place: ($3) => { $3(resources.insert(candidate.resource.key, fit(candidate))); },
|
|
532
|
+
take: ($3) => {
|
|
533
|
+
$3.match(candidate.bin, {
|
|
534
|
+
some: ($4, id) => { $4(bins.insert(id, fit(candidate))); },
|
|
535
|
+
none: ($4) => {
|
|
536
|
+
$4.match(candidate.arrival, {
|
|
537
|
+
some: ($5, arrival) => { $5(arrivals.insert(arrival.key, fit(candidate))); },
|
|
538
|
+
none: ($5) => { $5.assign(reserve, some(fit(candidate))); },
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
return { ask: request.ask, resources, bins, arrivals, reserve, basis };
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* A greedy look-ahead over a fit (§12.4): what a placement leaves for the
|
|
550
|
+
* day's other arrivals.
|
|
551
|
+
*
|
|
552
|
+
* @typeParam R - The app's bin row type
|
|
553
|
+
* @param row - The app's bin row type
|
|
554
|
+
* @returns An East function `(request, candidate, fit) → Fit`
|
|
555
|
+
*
|
|
556
|
+
* @remarks
|
|
557
|
+
* A refusal is returned as `fit` gives it. A fit places the candidate — a bin
|
|
558
|
+
* moves whole onto the candidate's resource with the candidate's quantity —
|
|
559
|
+
* then each of the day's unbooked arrivals, largest first (ties in key
|
|
560
|
+
* order), on the resource `fit` scores best for it (ties in key order), and
|
|
561
|
+
* lowers the score by one for each arrival left with nowhere to go, adding
|
|
562
|
+
* `· leaves 1 load with no fit` to its reason.
|
|
563
|
+
*/
|
|
564
|
+
export function allocationLookahead(row) {
|
|
565
|
+
const matesOf = allocationMates(row);
|
|
566
|
+
return East.function([
|
|
567
|
+
AllocationFitRequestTypeFor(row), AllocationCandidateType, FunctionType([AllocationCandidateType], AllocationFitType),
|
|
568
|
+
], AllocationFitType, ($, request, candidate, fit) => {
|
|
569
|
+
const parts = $.const(matesOf);
|
|
570
|
+
const loadOf = $.const(allocationLoadOf);
|
|
571
|
+
const candidateOf = $.const(allocationCandidateOf);
|
|
572
|
+
const step = $.const(allocationMilliseconds);
|
|
573
|
+
const context = $.const(request.context);
|
|
574
|
+
const day = $.const(request.day);
|
|
575
|
+
const verdict = $.const(fit(candidate));
|
|
576
|
+
$.match(verdict, { refused: ($2) => { $2.return(verdict); } });
|
|
577
|
+
const fits = $.const(verdict.unwrap("fits"));
|
|
578
|
+
// The candidate placed: its bin off every resource, its quantity on its resource.
|
|
579
|
+
const subject = $.const(candidate.bin.unwrap("some", () => ""));
|
|
580
|
+
const placed = $.const(parts(request.plan, context).map(($2, list) => list.filter(($3, mate) => East.notEqual(mate.bin, subject))));
|
|
581
|
+
$(placed.get(candidate.resource.key).pushLast({
|
|
582
|
+
bin: "", quantity: candidate.quantity, hold: day.durationMilliseconds(candidate.hold.to), kind: candidate.kind, reserve: candidate.reserve,
|
|
583
|
+
}));
|
|
584
|
+
// The day's unbooked arrivals, largest first.
|
|
585
|
+
const booked = $.let(new Set(), SetType(StringType));
|
|
586
|
+
$.for(request.plan.bins, ($2, bin) => {
|
|
587
|
+
$2.match(bin.arrival, { some: ($3, key) => { $3.if(booked.has(key).not(), ($4) => { $4(booked.insert(key)); }); } });
|
|
588
|
+
});
|
|
589
|
+
$.match(candidate.arrival, { some: ($2, arrival) => { $2.if(booked.has(arrival.key).not(), ($3) => { $3(booked.insert(arrival.key)); }); } });
|
|
590
|
+
const waiting = $.const(context.arrivals.filter(($2, _arrival, key) => booked.has(key).not()).toArray(($2, arrival) => arrival)
|
|
591
|
+
.sort(($2, arrival) => ({ quantity: arrival.quantity.negate(), key: arrival.key })));
|
|
592
|
+
const left = $.let(0n, IntegerType);
|
|
593
|
+
$.for(waiting, ($2, arrival) => {
|
|
594
|
+
const hold = $2.const(step(arrival.hold));
|
|
595
|
+
const best = $2.let(none, OptionType(StringType));
|
|
596
|
+
const bestScore = $2.let(0.0, FloatType);
|
|
597
|
+
$2.for(context.resources, ($3, resource, key) => {
|
|
598
|
+
const mates = $3.const(placed.get(key));
|
|
599
|
+
const there = $3.const(candidateOf(day, none, false, arrival.quantity, some(arrival), arrival.kind, hold, resource, mates, loadOf(day, resource, mates), context.rules, context.unit));
|
|
600
|
+
$3.match(fit(there), {
|
|
601
|
+
fits: ($4, it) => {
|
|
602
|
+
$4.if(best.hasTag("none").or(() => East.greater(it.score, bestScore)), ($5) => {
|
|
603
|
+
$5.assign(best, some(key));
|
|
604
|
+
$5.assign(bestScore, it.score);
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
$2.match(best, {
|
|
610
|
+
some: ($3, key) => { $3(placed.get(key).pushLast({ bin: "", quantity: arrival.quantity, hold, kind: arrival.kind, reserve: false })); },
|
|
611
|
+
none: ($3) => { $3.assign(left, left.add(1n)); },
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
const leaves = $.const(East.equal(left, 1n).ifElse(() => East.str ` · leaves 1 load with no fit`, () => East.str ` · leaves ${left} loads with no fit`));
|
|
615
|
+
return East.greater(left, 0n).ifElse(() => East.value(variant("fits", { score: fits.score.subtract(left.toFloat()), why: East.str `${fits.why}${leaves}`, warn: fits.warn }), AllocationFitType), () => verdict);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
// ============================================================================
|
|
619
|
+
// The built-in checks
|
|
620
|
+
// ============================================================================
|
|
621
|
+
/**
|
|
622
|
+
* The built-in checks (§12.3), over committed and drafted bins alike.
|
|
623
|
+
*
|
|
624
|
+
* @typeParam R - The app's bin row type
|
|
625
|
+
* @param row - The app's bin row type
|
|
626
|
+
* @returns An East function `(day, plan, context) → Array<Issue>`: bins not linked, then partly allocated, then resources whose hold overlaps their next job, over their maximum, and under their minimum when the rules ask
|
|
627
|
+
*
|
|
628
|
+
* @remarks
|
|
629
|
+
* Issues never block Save or Publish. A resource's issue offers to move its
|
|
630
|
+
* latest bin; the app's `check` follows these wherever issues show.
|
|
631
|
+
*/
|
|
632
|
+
export function allocationCheck(row) {
|
|
633
|
+
const loadsOf = allocationLoads(row);
|
|
634
|
+
return East.function([DateTimeType, AllocationDayTypeFor(row), AllocationContextType], ArrayType(AllocationIssueType), ($, day, plan, context) => {
|
|
635
|
+
const loadsFor = $.const(loadsOf);
|
|
636
|
+
const nextOf = $.const(allocationNextJob);
|
|
637
|
+
const qty = $.const(allocationQuantityText);
|
|
638
|
+
const num = $.const(scheduleNumberText);
|
|
639
|
+
const dayText = $.const(allocationDayText);
|
|
640
|
+
const loadText = $.const(allocationLoadText);
|
|
641
|
+
const loads = $.const(loadsFor(day, plan, context));
|
|
642
|
+
const lanes = $.const(context.lanes.toDict(($2, lane) => lane.key, ($2, lane) => lane.label));
|
|
643
|
+
const issues = $.let([], ArrayType(AllocationIssueType));
|
|
644
|
+
const where = $.const(East.function([StringType, DateTimeType], StringType, ($2, lane, at) => East.str `${lanes.get(lane, () => lane)} ${at.printFormatted("HH:mm")}`));
|
|
645
|
+
const labelOf = $.const(East.function([StringType], StringType, ($2, key) => context.arrivals.tryGet(key).match({
|
|
646
|
+
some: (_$3, arrival) => arrival.label, none: (_$3) => key,
|
|
647
|
+
})));
|
|
648
|
+
// Bins: not linked, then partly allocated.
|
|
649
|
+
$.for(plan.bins, ($2, bin, id) => {
|
|
650
|
+
$2.match(bin.arrival, {
|
|
651
|
+
some: ($3, key) => {
|
|
652
|
+
$3.if(East.equal(bin.parts.size(), 0n), ($4) => {
|
|
653
|
+
$4(issues.pushLast({ kind: variant("unlinked", null), tone: variant("warning", null),
|
|
654
|
+
title: East.str `${labelOf(key)} · ${qty(bin.quantity, context.unit)}`, detail: East.str `${where(bin.lane, bin.at)} · no resource yet`,
|
|
655
|
+
bin: some(id), resource: none, flag: none, fix: some(variant("choose", null)) }));
|
|
656
|
+
});
|
|
657
|
+
},
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
$.for(plan.bins, ($2, bin, id) => {
|
|
661
|
+
$2.match(bin.arrival, {
|
|
662
|
+
some: ($3, key) => {
|
|
663
|
+
const allocated = $3.let(0.0, FloatType);
|
|
664
|
+
$3.for(bin.parts, ($4, part) => { $4.assign(allocated, allocated.add(part)); });
|
|
665
|
+
$3.if(East.greater(bin.parts.size(), 0n).and(() => East.less(allocated, bin.quantity)), ($4) => {
|
|
666
|
+
$4(issues.pushLast({ kind: variant("unallocated", null), tone: variant("warning", null),
|
|
667
|
+
title: East.str `${labelOf(key)} · ${qty(bin.quantity, context.unit)}`,
|
|
668
|
+
detail: East.str `${qty(bin.quantity.subtract(allocated), context.unit)} unallocated · ${where(bin.lane, bin.at)}`,
|
|
669
|
+
bin: some(id), resource: none, flag: none, fix: some(variant("find", null)) }));
|
|
670
|
+
});
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
// The latest bin booking an arrival on each resource: what its issues offer to move.
|
|
675
|
+
const lastOf = $.const(East.function([StringType], OptionType(StringType), ($2, resource) => {
|
|
676
|
+
const last = $2.let(none, OptionType(StringType));
|
|
677
|
+
const lastAt = $2.let(day, DateTimeType);
|
|
678
|
+
$2.for(plan.bins, ($3, bin, id) => {
|
|
679
|
+
$3.if(bin.arrival.hasTag("some").and(() => bin.parts.has(resource)), ($4) => {
|
|
680
|
+
$4.if(last.hasTag("none").or(() => East.greaterEqual(bin.at, lastAt)), ($5) => {
|
|
681
|
+
$5.assign(last, some(id));
|
|
682
|
+
$5.assign(lastAt, bin.at);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
return last;
|
|
687
|
+
}));
|
|
688
|
+
const moveOf = $.const(East.function([StringType], OptionType(AllocationFixType), ($2, resource) => {
|
|
689
|
+
const fix = $2.let(none, OptionType(AllocationFixType));
|
|
690
|
+
$2.match(lastOf(resource), { some: ($3, id) => { $3.assign(fix, some(variant("move", id))); } });
|
|
691
|
+
return fix;
|
|
692
|
+
}));
|
|
693
|
+
$.for(context.resources, ($2, resource, key) => {
|
|
694
|
+
const load = $2.const(loads.get(key));
|
|
695
|
+
$2.match(load.hold, {
|
|
696
|
+
some: ($3, hold) => {
|
|
697
|
+
$3.match(nextOf(resource, day), {
|
|
698
|
+
some: ($4, job) => {
|
|
699
|
+
$4.if(East.greater(hold.to, job.start), ($5) => {
|
|
700
|
+
const length = $5.const(day.durationMilliseconds(hold.to).toFloat().divide(DAY_MS));
|
|
701
|
+
const into = $5.const(job.start.durationMilliseconds(hold.to).toFloat().divide(DAY_MS));
|
|
702
|
+
$5(issues.pushLast({ kind: variant("overlap", null), tone: variant("warning", null), title: resource.label,
|
|
703
|
+
detail: East.str `${num(length)}-day hold to ${dayText(hold.to.addMilliseconds(-1n))} · runs ${num(into)} d into the job planned ${dayText(job.start)}`,
|
|
704
|
+
bin: none, resource: some(key), flag: none, fix: moveOf(key) }));
|
|
705
|
+
});
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
$.for(context.resources, ($2, resource, key) => {
|
|
712
|
+
const load = $2.const(loads.get(key));
|
|
713
|
+
$2.if(East.greater(load.fill, resource.max), ($3) => {
|
|
714
|
+
$3(issues.pushLast({ kind: variant("over", null), tone: variant("danger", null), title: resource.label,
|
|
715
|
+
detail: East.str `${loadText(load.fill, resource, context.unit)} · over max`, bin: none, resource: some(key), flag: none, fix: moveOf(key) }));
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
$.for(context.resources, ($2, resource, key) => {
|
|
719
|
+
const load = $2.const(loads.get(key));
|
|
720
|
+
$2.if(context.rules.under.and(() => East.greater(load.fill, 0.0)).and(() => East.less(load.fill, resource.min))
|
|
721
|
+
.and(() => East.lessEqual(load.fill, resource.max)), ($3) => {
|
|
722
|
+
$3(issues.pushLast({ kind: variant("under", null), tone: variant("warning", null), title: resource.label,
|
|
723
|
+
detail: East.str `${loadText(load.fill, resource, context.unit)} · a warning, Save still works`,
|
|
724
|
+
bin: none, resource: some(key), flag: none, fix: moveOf(key) }));
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
return issues;
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
// ============================================================================
|
|
731
|
+
// Slots
|
|
732
|
+
// ============================================================================
|
|
733
|
+
/**
|
|
734
|
+
* The plan day's lane slots: one per lane per `every`, in time then lane order.
|
|
735
|
+
*
|
|
736
|
+
* @typeParam R - The app's bin row type
|
|
737
|
+
* @param row - The app's bin row type
|
|
738
|
+
* @returns An East function `(day, plan, context) → Array<Slot>`
|
|
739
|
+
*
|
|
740
|
+
* @remarks
|
|
741
|
+
* A bin is in the slot whose time it follows: from ten minutes before the
|
|
742
|
+
* slot's time to ten minutes before the next one's, as a batch follows its
|
|
743
|
+
* slot by a minute a bin. A slot holding a bin booking an arrival is booked,
|
|
744
|
+
* one holding only reservations reserved, an empty one free. A bin in a lane
|
|
745
|
+
* the context does not have is in no slot.
|
|
746
|
+
*/
|
|
747
|
+
export function allocationSlots(row) {
|
|
748
|
+
return East.function([DateTimeType, AllocationDayTypeFor(row), AllocationContextType], ArrayType(AllocationSlotType), ($, day, plan, context) => {
|
|
749
|
+
const step = $.const(allocationMilliseconds);
|
|
750
|
+
const every = $.const(step(context.every));
|
|
751
|
+
$.if(East.lessEqual(every, 0n), ($2) => { $2.error("Allocation: `every` must be longer than no time"); });
|
|
752
|
+
const count = $.const(East.value(BigInt(DAY_MS)).divide(every));
|
|
753
|
+
const laneIndex = $.const(context.lanes.toDict(($2, lane) => lane.key, ($2, _lane, i) => i));
|
|
754
|
+
// Each lane's slots, each its bins' ids.
|
|
755
|
+
const held = $.let(context.lanes.map((_$2) => East.Array.generate(count, ArrayType(StringType), (_$3) => East.value([], ArrayType(StringType)))));
|
|
756
|
+
$.for(plan.bins, ($2, bin, id) => {
|
|
757
|
+
$2.match(laneIndex.tryGet(bin.lane), {
|
|
758
|
+
some: ($3, lane) => {
|
|
759
|
+
const offset = $3.const(step(context.lanes.get(lane).offset));
|
|
760
|
+
const since = $3.const(day.durationMilliseconds(bin.at).subtract(offset).add(600000n));
|
|
761
|
+
const index = $3.const(East.max(since, $3.const(0n, IntegerType)).divide(every).remainder(count));
|
|
762
|
+
$3(held.get(lane).get(index).pushLast(id));
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
const out = $.let([], ArrayType(AllocationSlotType));
|
|
767
|
+
$.for(context.lanes, ($2, lane, li) => {
|
|
768
|
+
const offset = $2.const(step(lane.offset));
|
|
769
|
+
$2.for(held.get(li), ($3, ids, k) => {
|
|
770
|
+
const sorted = $3.const(ids.sort(($4, id) => ({ at: plan.bins.get(id).at, id })));
|
|
771
|
+
const booked = $3.const(sorted.some(($4, id) => plan.bins.get(id).arrival.hasTag("some")));
|
|
772
|
+
$3(out.pushLast({
|
|
773
|
+
lane: lane.key, at: day.addMilliseconds(k.multiply(every).add(offset)),
|
|
774
|
+
status: booked.ifElse(() => East.value(variant("booked", null), AllocationSlotType.fields.status), () => East.greater(sorted.size(), 0n).ifElse(() => East.value(variant("reserved", null), AllocationSlotType.fields.status), () => East.value(variant("free", null), AllocationSlotType.fields.status))),
|
|
775
|
+
bins: sorted,
|
|
776
|
+
}));
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
return out.sort(($2, slot) => ({ at: slot.at, lane: laneIndex.get(slot.lane) }));
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
//# sourceMappingURL=functions.js.map
|