Skip to content

Commit dbaf0f1

Browse files
jeremymanningclaude
andcommitted
Fix biweekly slot sharing: max 2 per slot + calendar week offset
- Mark biweekly slots as fully used (in used_times) after 2nd meeting is placed, preventing 3rd/4th meetings from being scheduled in same slot - Calendar: biweekly events sharing a slot get week_offset so they alternate (1st starts week 1, 2nd starts week 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 853efc2 commit dbaf0f1

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

cdl_bot/services/calendar_service.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def create_recurring_event(
254254
term_end: str,
255255
location: str = PROJECT_LOCATION,
256256
is_biweekly: bool = False,
257+
week_offset: int = 0,
257258
attendee_emails: Optional[list] = None,
258259
description: str = "",
259260
) -> tuple[bool, Optional[str], Optional[str]]:
@@ -270,6 +271,8 @@ def create_recurring_event(
270271
term_end: Term end date "YYYY-MM-DD"
271272
location: Event location string
272273
is_biweekly: If True, event repeats every 2 weeks
274+
week_offset: Number of weeks to offset the start (for alternating
275+
biweekly meetings sharing the same slot)
273276
attendee_emails: Optional list of attendee email addresses
274277
description: Optional event description
275278
@@ -288,6 +291,10 @@ def create_recurring_event(
288291
days_ahead += 7
289292
first_date = start_date + timedelta(days=days_ahead)
290293

294+
# Offset for alternating biweekly meetings sharing the same slot
295+
if week_offset:
296+
first_date += timedelta(weeks=week_offset)
297+
291298
# Build RRULE
292299
end_date = date.fromisoformat(term_end)
293300
until_str = end_date.strftime("%Y%m%dT235959Z")
@@ -356,6 +363,10 @@ def create_schedule_events(
356363
if schedule_df is None or schedule_df.empty:
357364
return results
358365

366+
# Track biweekly slots to offset alternating meetings
367+
# Key: (day, start_time) -> count of biweekly meetings already placed
368+
biweekly_slot_count = {}
369+
359370
for meeting_name, row in schedule_df.iterrows():
360371
day = row["Day"]
361372
start = row["Start Time"][:5]
@@ -366,6 +377,15 @@ def create_schedule_events(
366377
is_individual = meeting_name.endswith(" one-on-one")
367378
location = INDIVIDUAL_LOCATION if is_individual else PROJECT_LOCATION
368379

380+
# For biweekly meetings, check if another biweekly already occupies
381+
# this slot and offset by 1 week so they alternate
382+
week_offset = 0
383+
if is_biweekly:
384+
slot_key = (day, start)
385+
count = biweekly_slot_count.get(slot_key, 0)
386+
week_offset = count # 0 for first, 1 for second
387+
biweekly_slot_count[slot_key] = count + 1
388+
369389
success, error, event_id = self.create_recurring_event(
370390
calendar_id=calendar_id,
371391
summary=meeting_name,
@@ -376,6 +396,7 @@ def create_schedule_events(
376396
term_end=term_end,
377397
location=location,
378398
is_biweekly=is_biweekly,
399+
week_offset=week_offset,
379400
)
380401

381402
results.append({

cdl_bot/services/scheduling_service.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ def calculate_pi_time_score(day, block_times, current_day_blocks, days_with_meet
100100
# Tracking state
101101
scheduled = {}
102102
used_times = set()
103-
biweekly_times = {}
103+
biweekly_times = {} # (day, time) -> first meeting name
104+
biweekly_full = set() # (day, time) slots that already have 2 biweekly meetings
104105
meeting_end_times = {}
105106
days_with_meetings = set()
106107
day_meeting_blocks = defaultdict(list)
@@ -165,6 +166,9 @@ def process_meeting(meeting_name, group_members, is_biweekly_meeting=False):
165166
else:
166167
if any(t in used_times for t in block_index):
167168
continue
169+
# Slot already has 2 biweekly meetings — full
170+
if any(t in biweekly_full for t in block_index):
171+
continue
168172
overlapping = [biweekly_times[t] for t in block_index if t in biweekly_times]
169173
if overlapping:
170174
unique_overlapping = set(overlapping)
@@ -263,7 +267,15 @@ def schedule_meeting(meeting_name, group_members, is_biweekly_meeting):
263267

264268
if is_biweekly_meeting:
265269
for t in best["times"]:
266-
biweekly_times[(best["day"], t)] = meeting_name
270+
slot = (best["day"], t)
271+
if slot in biweekly_times:
272+
# 2nd biweekly meeting — slot is now full (2 alternating weeks used)
273+
biweekly_full.add(slot)
274+
# Also add to used_times so no further meetings (weekly OR biweekly)
275+
# can be placed here
276+
used_times.add(slot)
277+
else:
278+
biweekly_times[slot] = meeting_name
267279
else:
268280
for t in best["times"]:
269281
used_times.add((best["day"], t))

0 commit comments

Comments
 (0)