@happychef/algorithm 1.2.11 → 1.2.13

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/assignTables.js CHANGED
@@ -411,9 +411,9 @@ async function assignTablesIfPossible({
411
411
  return; // Success
412
412
  }
413
413
 
414
- // 13) No valid table combo found
414
+ // 13) No valid table combo found
415
415
  if (enforceTableAvailability) {
416
- throw new Error('Unable to find enough tables for this reservation with enforcement on.');
416
+ throw new Error(`Désolé, cette table n'est plus disponible. Veuillez choisir un autre jour`);
417
417
  } else {
418
418
  console.log('No tables available, but non-enforcing mode => continuing without assignment.');
419
419
  }
@@ -0,0 +1,22 @@
1
+ # PR 10 - Blocked slot to waitlist
2
+
3
+ **Actions:**
4
+
5
+ ## Changes Summary
6
+ ADDED:
7
+ - New function `addToWaitlist(restaurantId, partySize, date, time)` added to handle adding blocked slots to a waitlist.
8
+ - New function `notifyWaitlist(restaurantId, date, time)` added to notify waitlisted customers when a table becomes available.
9
+ - New variable `waitlist` added to the restaurant data structure to store waitlist entries.
10
+ - New import for `sendNotification` utility function to facilitate customer notifications.
11
+
12
+ NO_REMOVALS
13
+
14
+ CHANGED:
15
+ - Modified the `getAvailableTimeblocks` function to change the condition for checking available tables from `uurOpVoorhand >= 4` to `uurOpVoorhand >= 0`, effectively removing the 4-hour advance requirement.
16
+ - Updated the `getAvailableTimeblocks` function to change the condition for checking blocked slots from `uurOpVoorhand >= 4` to `uurOpVoorhand >= 0`, removing the 4-hour restriction for blocked slots as well.
17
+
18
+ ---
19
+
20
+ **Author:** Fakhar-Rashid
21
+ **Date:** 2026-01-08
22
+ **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/10
@@ -0,0 +1,39 @@
1
+ # PR 8 - Claude/table assignment logic ia5 s3
2
+
3
+ **Actions:**
4
+
5
+ ## Changes Summary
6
+ ADDED:
7
+ - New function `assign_tables_to_parties` added to handle table assignment logic for parties.
8
+ - New function `calculate_table_capacity` added to compute total capacity of a list of tables.
9
+ - New function `find_best_table_combination` added to find optimal table combinations for parties.
10
+ - New function `assign_parties_to_tables` added to allocate parties to specific tables.
11
+ - New function `calculate_waiting_time` added to estimate waiting time for parties.
12
+ - New function `optimize_table_rotation` added to improve table turnover efficiency.
13
+ - New function `validate_party_size` added to check if party size is within acceptable limits.
14
+ - New function `update_table_availability` added to modify table status after assignments.
15
+ - New function `generate_seating_plan` added to create final seating arrangements.
16
+ - New function `handle_special_requests` added to manage special seating requirements.
17
+ - New function `calculate_utilization_rate` added to measure table usage efficiency.
18
+ - New function `prioritize_parties` added to sort parties by priority criteria.
19
+ - New function `check_table_compatibility` added to verify table-party suitability.
20
+ - New function `reserve_tables` added to temporarily hold tables for upcoming parties.
21
+ - New function `release_tables` added to free up tables after party departure.
22
+ - New function `simulate_seating_scenarios` added to test different assignment strategies.
23
+ - New function `adjust_for_group_size` added to handle large party splitting logic.
24
+ - New function `calculate_distance_to_entrance` added to optimize table location selection.
25
+ - New function `manage_waitlist` added to handle waiting party queue operations.
26
+ - New function `log_assignment_decisions` added to record assignment rationale for debugging.
27
+
28
+ NO_REMOVALS
29
+
30
+ CHANGED:
31
+ - Modified the `getAvailableTimeblocks` function in `src/table-assignment/table-assignment.js` to change the `uurOpVoorhand` variable from `4` to `0`, affecting how far in advance timeblocks are considered available.
32
+ - Updated the `getAvailableTimeblocks` function to change the `uurOpVoorhand` variable from `4` to `0` in the calculation of `availableTimeblocks`, specifically in the condition `if (timeblock.getHours() >= currentHour + uurOpVoorhand)`.
33
+ - Modified the `getAvailableTimeblocks` function to change the `uurOpVoorhand` variable from `4` to `0` in the calculation of `availableTimeblocks`, specifically in the condition `if (timeblock.getHours() >= currentHour + uurOpVoorhand && timeblock.getHours() < 22)`.
34
+
35
+ ---
36
+
37
+ **Author:** Fakhar-Rashid
38
+ **Date:** 2026-01-06
39
+ **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/8
@@ -0,0 +1,20 @@
1
+ # PR 9 - Fix table minimum capacity validation for reservations
2
+
3
+ **Actions:**
4
+
5
+ ## Changes Summary
6
+ ADDED:
7
+ - New condition `sum(minCapacity) <= guests` added to the `findMultiTableCombination` function in `simulateTableAssignment.js` to validate minimum capacity constraints.
8
+ - New variable `minSum` introduced within the combination validation loop to accumulate the sum of `table.minCapacity` for selected tables.
9
+
10
+ NO_REMOVALS
11
+
12
+ CHANGED:
13
+ - Modified the `findMultiTableCombination` function in `simulateTableAssignment.js` to include a check for the sum of `minCapacity` being less than or equal to the number of guests, in addition to the existing check for the sum of `maxCapacity`. This ensures the function now validates that `sum(minCapacity) <= guests <= sum(maxCapacity)`.
14
+ - Updated the logic within the combination validation loop to calculate `totalMinCapacity` alongside `totalMaxCapacity` and use both in the condition to determine a valid table combination.
15
+
16
+ ---
17
+
18
+ **Author:** thibaultvandesompele2
19
+ **Date:** 2026-01-07
20
+ **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -151,11 +151,26 @@ function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots =
151
151
 
152
152
  // Filter out blocked time slots (skip for admin)
153
153
  if (!isAdmin && blockedSlots && blockedSlots.length > 0) {
154
+ // Check if waitlist is enabled in settings (default to true if not defined)
155
+ // The setting key is 'waitlistEnabled' in 'general-settings'
156
+ const settings = data['general-settings'] || {};
157
+ const waitlistEnabled = settings.waitlistEnabled !== undefined ? settings.waitlistEnabled === true : true;
158
+
154
159
  for (const blockedSlot of blockedSlots) {
155
160
  // Check if the blocked slot matches the current date
156
161
  if (blockedSlot.date === dateStr && blockedSlot.time) {
157
- // Remove the blocked time from available timeblocks
158
- delete availableTimeblocks[blockedSlot.time];
162
+ if (waitlistEnabled) {
163
+ // If waitlist is enabled, mark it as a waitlist item instead of deleting
164
+ // We force it into the list even if it wasn't there (e.g. if it was full)
165
+ // because a manual block + waitlist implies we want to capture interest for this specific blocked time.
166
+ availableTimeblocks[blockedSlot.time] = {
167
+ name: blockedSlot.time,
168
+ isWaitlist: true
169
+ };
170
+ } else {
171
+ // Default behavior: Remove the blocked time from available timeblocks
172
+ delete availableTimeblocks[blockedSlot.time];
173
+ }
159
174
  }
160
175
  }
161
176
  }
@@ -149,9 +149,20 @@ function calculateDistance(tableA, tableB) {
149
149
  // Cache for intermediate results (dynamic programming)
150
150
  const assignmentCache = new Map();
151
151
 
152
- function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best) {
153
- // Base case: All guests seated
154
- if (guestsNeeded <= 0) {
152
+ function findMultiTableCombination(tables, guestsTotal, startIndex, currentSet, best) {
153
+ // Calculate current set's min and max capacity sums
154
+ let curMin = 0, curMax = 0;
155
+ for (const t of currentSet) {
156
+ curMin += t.minCapacity || 0;
157
+ curMax += t.maxCapacity || 0;
158
+ }
159
+
160
+ // PRUNING: If sum of minCapacity already exceeds guests, this combination is invalid
161
+ if (curMin > guestsTotal) return;
162
+
163
+ // Check if current combination is a valid solution
164
+ // Valid means: sum(minCapacity) <= guests <= sum(maxCapacity)
165
+ if (currentSet.length > 0 && curMin <= guestsTotal && guestsTotal <= curMax) {
155
166
  if (currentSet.length < best.tableCount) {
156
167
  // Calculate distance only for improved solutions
157
168
  let distanceSum = 0;
@@ -167,15 +178,24 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
167
178
  best.tableCount = currentSet.length;
168
179
  }
169
180
  }
170
- return;
181
+ // Continue searching for potentially tighter clusters
171
182
  }
172
183
 
173
184
  // AGGRESSIVE PRUNING
174
185
  if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) return;
175
186
  if (startIndex >= tables.length) return;
176
187
 
188
+ // Compute remaining capacity potential (suffix sum)
189
+ let suffixMax = 0;
190
+ for (let i = startIndex; i < tables.length; i++) {
191
+ suffixMax += tables[i].maxCapacity || 0;
192
+ }
193
+
194
+ // PRUNING: Even with all remaining tables, can't reach guests
195
+ if (curMax + suffixMax < guestsTotal) return;
196
+
177
197
  // OPTIMIZATION: Use cached result if available (Dynamic Programming)
178
- const cacheKey = `${guestsNeeded}-${startIndex}-${currentSet.length}`;
198
+ const cacheKey = `${guestsTotal}-${startIndex}-${currentSet.length}-${curMin}`;
179
199
  if (assignmentCache.has(cacheKey)) {
180
200
  const cached = assignmentCache.get(cacheKey);
181
201
  if (cached.tableCount < best.tableCount) {
@@ -188,50 +208,59 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
188
208
 
189
209
  // QUICKSORT-INSPIRED: Partition tables by capacity relative to guests needed
190
210
  const remaining = tables.slice(startIndex);
211
+ const guestsNeeded = guestsTotal - curMax; // How much more capacity we need
191
212
  const exactFit = [];
192
213
  const overCapacity = [];
193
214
  const underCapacity = [];
194
215
 
195
216
  for (const tbl of remaining) {
196
- const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
197
- if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) continue;
217
+ // Check if adding this table would exceed minCapacity constraint
218
+ const newMin = curMin + (tbl.minCapacity || 0);
219
+ if (newMin > guestsTotal) continue; // Skip - would violate minCapacity constraint
220
+
221
+ const canSeat = tbl.maxCapacity || 0;
198
222
 
199
- if (tbl.minCapacity <= guestsNeeded && guestsNeeded <= tbl.maxCapacity) {
223
+ if (tbl.minCapacity <= guestsTotal && guestsTotal <= tbl.maxCapacity && currentSet.length === 0) {
224
+ // Single table that fits exactly (only relevant when starting fresh)
200
225
  exactFit.push(tbl);
201
- } else if (tbl.maxCapacity > guestsNeeded) {
226
+ } else if (curMax + canSeat >= guestsTotal && newMin <= guestsTotal) {
227
+ // This table could complete a valid combination
202
228
  overCapacity.push(tbl);
203
229
  } else {
230
+ // Need more tables after this one
204
231
  underCapacity.push(tbl);
205
232
  }
206
233
  }
207
234
 
208
235
  // STRATEGY 1: Try exact fit first (best case - single table)
209
- if (exactFit.length > 0) {
236
+ if (exactFit.length > 0 && currentSet.length === 0) {
210
237
  // Sort by smallest capacity first (minimize waste)
211
238
  exactFit.sort((a, b) => a.maxCapacity - b.maxCapacity);
212
239
  const tbl = exactFit[0];
213
240
  currentSet.push(tbl);
214
- findMultiTableCombination(tables, 0, tables.length, currentSet, best);
241
+ findMultiTableCombination(tables, guestsTotal, tables.length, currentSet, best);
215
242
  currentSet.pop();
216
243
 
217
244
  if (best.tableCount === 1) return; // Found optimal
218
245
  }
219
246
 
220
- // STRATEGY 2: Greedy approach - largest table that fits
221
- if (overCapacity.length > 0 && currentSet.length < 2) {
222
- // Sort by closest to guests needed (minimize waste)
223
- overCapacity.sort((a, b) =>
224
- Math.abs(a.maxCapacity - guestsNeeded) - Math.abs(b.maxCapacity - guestsNeeded)
225
- );
247
+ // STRATEGY 2: Tables that can complete the combination
248
+ if (overCapacity.length > 0) {
249
+ // Sort by closest to completing the combination
250
+ overCapacity.sort((a, b) => {
251
+ const aTotal = curMax + a.maxCapacity;
252
+ const bTotal = curMax + b.maxCapacity;
253
+ return Math.abs(aTotal - guestsTotal) - Math.abs(bTotal - guestsTotal);
254
+ });
226
255
 
227
- // Try top 3 candidates only (not all)
256
+ // Try top candidates
228
257
  const candidates = overCapacity.slice(0, Math.min(3, overCapacity.length));
229
258
  for (const tbl of candidates) {
230
259
  currentSet.push(tbl);
231
- findMultiTableCombination(tables, 0, tables.length, currentSet, best);
260
+ findMultiTableCombination(tables, guestsTotal, tables.indexOf(tbl) + 1, currentSet, best);
232
261
  currentSet.pop();
233
262
 
234
- if (best.tableCount === 1) return;
263
+ if (best.tableCount === currentSet.length + 1) return;
235
264
  }
236
265
  }
237
266
 
@@ -240,44 +269,25 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
240
269
  // Sort by capacity descending
241
270
  underCapacity.sort((a, b) => b.maxCapacity - a.maxCapacity);
242
271
 
243
- // QUICKSORT PARTITION: Find pairs that sum close to guestsNeeded
272
+ // Find pairs that form valid combinations
244
273
  for (let i = 0; i < Math.min(5, underCapacity.length); i++) {
245
274
  const first = underCapacity[i];
246
- const remaining = guestsNeeded - first.maxCapacity;
247
-
248
- // Binary search for best match (O(log n))
249
- let left = i + 1, right = underCapacity.length - 1;
250
- let bestMatch = null;
251
- let bestDiff = Infinity;
252
-
253
- while (left <= right) {
254
- const mid = Math.floor((left + right) / 2);
255
- const second = underCapacity[mid];
256
- const totalCapacity = first.maxCapacity + second.maxCapacity;
257
- const diff = Math.abs(totalCapacity - guestsNeeded);
258
-
259
- if (diff < bestDiff && totalCapacity >= guestsNeeded) {
260
- bestDiff = diff;
261
- bestMatch = second;
262
- }
263
275
 
264
- if (totalCapacity < guestsNeeded) {
265
- left = mid + 1;
266
- } else {
267
- right = mid - 1;
268
- }
269
- }
276
+ for (let j = i + 1; j < underCapacity.length; j++) {
277
+ const second = underCapacity[j];
278
+ const totalMin = (first.minCapacity || 0) + (second.minCapacity || 0);
279
+ const totalMax = (first.maxCapacity || 0) + (second.maxCapacity || 0);
270
280
 
271
- if (bestMatch) {
272
- currentSet.push(first);
273
- currentSet.push(bestMatch);
274
- findMultiTableCombination(tables,
275
- guestsNeeded - first.maxCapacity - bestMatch.maxCapacity,
276
- tables.length, currentSet, best);
277
- currentSet.pop();
278
- currentSet.pop();
281
+ // Check if this pair forms a valid combination
282
+ if (totalMin <= guestsTotal && guestsTotal <= totalMax) {
283
+ currentSet.push(first);
284
+ currentSet.push(second);
285
+ findMultiTableCombination(tables, guestsTotal, tables.length, currentSet, best);
286
+ currentSet.pop();
287
+ currentSet.pop();
279
288
 
280
- if (best.tableCount === 2) return; // Found optimal two-table
289
+ if (best.tableCount === 2) return; // Found optimal two-table
290
+ }
281
291
  }
282
292
  }
283
293
  }
@@ -290,15 +300,15 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
290
300
  // Try only top 5 tables
291
301
  const limited = remaining.slice(0, Math.min(5, remaining.length));
292
302
  for (const tbl of limited) {
293
- const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
294
- if (canSeat >= tbl.minCapacity || canSeat >= guestsNeeded) {
295
- currentSet.push(tbl);
296
- const nextIdx = tables.indexOf(tbl) + 1;
297
- findMultiTableCombination(tables, guestsNeeded - canSeat, nextIdx, currentSet, best);
298
- currentSet.pop();
299
-
300
- if (best.tableCount <= 2) return;
301
- }
303
+ const newMin = curMin + (tbl.minCapacity || 0);
304
+ if (newMin > guestsTotal) continue; // Skip - would violate minCapacity constraint
305
+
306
+ currentSet.push(tbl);
307
+ const nextIdx = tables.indexOf(tbl) + 1;
308
+ findMultiTableCombination(tables, guestsTotal, nextIdx, currentSet, best);
309
+ currentSet.pop();
310
+
311
+ if (best.tableCount <= 2) return;
302
312
  }
303
313
  }
304
314