@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 +2 -2
- package/changes/2026/January/PR10_add__change.md +22 -0
- package/changes/2026/January/PR8_add__change.md +39 -0
- package/changes/2026/January/PR9_add__change.md +20 -0
- package/package.json +1 -1
- package/processing/timeblocksAvailable.js +17 -2
- package/simulateTableAssignment.js +72 -62
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(
|
|
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
|
@@ -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
|
-
|
|
158
|
-
|
|
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,
|
|
153
|
-
//
|
|
154
|
-
|
|
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
|
-
|
|
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 = `${
|
|
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
|
-
|
|
197
|
-
|
|
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 <=
|
|
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 (
|
|
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,
|
|
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:
|
|
221
|
-
if (overCapacity.length > 0
|
|
222
|
-
// Sort by closest to
|
|
223
|
-
overCapacity.sort((a, b) =>
|
|
224
|
-
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
|