about summary refs log tree commit diff stats
path: root/js/baba-yaga/experimental/availability.baba
blob: 4bc59f8861a87e5ab2d16f8926a4f59458925772 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Availability via 24-block boolean mask
// WIP

// range0: [0..n-1]
range0 : n ->
  when n is
    0 then []
    _ then append (range0 (n - 1)) (n - 1);

// overlaps: does busy interval b overlap block i?
overlaps : b i dayStart blockSize ->
  (b.start < (dayStart + (i * blockSize) + blockSize)) and (b.end > (dayStart + (i * blockSize)));

// anyBusyOnBlock: reduce OR over busy intervals for block i
anyBusyOnBlock : busyList i dayStart blockSize ->
  reduce (acc b -> acc or (overlaps b i dayStart blockSize)) false busyList;

// allFreeOnBlock: everyone free on block i?
allFreeOnBlock : busyLists i dayStart blockSize ->
  reduce (acc bl -> acc and (when (anyBusyOnBlock bl i dayStart blockSize) is true then false _ then true)) true busyLists;

// stepRuns: accumulate contiguous free runs over blocks with min-block filtering
stepRuns : busyLists dayStart blockSize minBlocks acc i ->
  when acc.inRun is
    true then
      when (allFreeOnBlock busyLists i dayStart blockSize) is
        true then { inRun: true,  start: acc.start, runs: acc.runs }
        _    then (
          when ((i - acc.start) >= minBlocks) is
            true then { inRun: false, start: 0, runs: append acc.runs { start: acc.start, end: i } }
            _    then { inRun: false, start: 0, runs: acc.runs }
        )
    _ then
      when (allFreeOnBlock busyLists i dayStart blockSize) is
        true then { inRun: true,  start: i,        runs: acc.runs }
        _    then acc;

// finalizeRuns: close trailing run if needed (with min-block filtering)
finalizeRuns : acc totalBlocks minBlocks ->
  when acc.inRun is
    true then (
      when ((totalBlocks - acc.start) >= minBlocks) is
        true then append acc.runs { start: acc.start, end: totalBlocks }
        _    then acc.runs
    )
    _    then acc.runs;

// convertRunsToMinutes: map block runs to minute intervals (shape-guarded)
convertRunsToMinutes : runs dayStart blockSize ->
  when (shape runs).kind is
    "List" then map (r -> { start: dayStart + (r.start * blockSize), end: dayStart + (r.end * blockSize) }) runs
    _      then [];

// takeLimit: slice helper
takeLimit : limit lst -> slice lst 0 (math.min (length lst) limit);

// findCommonAvailability using mask approach
// runnerFor: folder factory capturing inputs, returns (acc i -> ...)
runnerFor : calendars dayStart dayEnd minMinutes acc i ->
  stepRuns (values calendars) dayStart ((dayEnd - dayStart) / 24) (math.ceil (minMinutes / ((dayEnd - dayStart) / 24))) acc i;

// buildRuns: produce runs list from inputs
buildRuns : calendars dayStart dayEnd minMinutes ->
  finalizeRuns (
    reduce (runnerFor calendars dayStart dayEnd minMinutes)
      { inRun: false, start: 0, runs: [] }
      (range0 24)
  ) 24 (math.ceil (minMinutes / ((dayEnd - dayStart) / 24)));

// slotsFor: convert runs to minute intervals
slotsFor : calendars dayStart dayEnd minMinutes ->
  convertRunsToMinutes (buildRuns calendars dayStart dayEnd minMinutes) dayStart ((dayEnd - dayStart) / 24);

// findCommonAvailability: top-level pipeline
findCommonAvailability : calendars dayStart dayEnd minMinutes limit ->
  takeLimit limit (slotsFor calendars dayStart dayEnd minMinutes);

// ---------- Example usage ----------

// Working window 09:00-17:00
dayStart : 9 * 60;    // 540
dayEnd   : 17 * 60;   // 1020
minSlot  : 30;        // minutes
limit    : 5;

// Calendars: each value is a sorted list of busy intervals { start, end } in minutes
calendars : {
  alice: [
    { start: 570, end: 630 }
    { start: 720, end: 780 }
    { start: 960, end: 1020 }
  ],
  bob: [
    { start: 540, end: 555 }
    { start: 660, end: 720 }
    { start: 840, end: 870 }
  ],
  carol: [
    { start: 600, end: 660 }
    { start: 750, end: 810 }
    { start: 915, end: 960 }
  ]
};

io.out "loaded";
available : findCommonAvailability calendars dayStart dayEnd minSlot limit;
io.out "done";
io.out available;