Built a schedule-aware PM copilot that actually respects
The core loop is dead simple: every morning at 7 AM a Cloud Function pulls the next 14 days of events, computes "focus windows" (contiguous blocks ≥ 90 min, no meetings, no recurring holds), then cross-references the current sprint backlog. If a ticket's estimate exceeds the sum of available focus windows before its due date, the copilot flags it in Slack with a concrete suggestion — split the ticket, move the date, or negotiate scope. No vague "at risk" badges. Just math.
def compute_focus_windows(calendar_events, min_block_minutes=90):
"""Return list of (start, end) tuples where deep work can happen."""
busy = sorted([(e.start, e.end) for e in calendar_events])
windows = []
day_start = datetime.combine(date.today(), time(9, 0))
day_end = datetime.combine(date.today(), time(18, 0))
cursor = day_start
for b_start, b_end in busy:
if b_start - cursor >= timedelta(minutes=min_block_minutes):
windows.append((cursor, b_start))
cursor = max(cursor, b_end)
if day_end - cursor >= timedelta(minutes=min_block_minutes):
windows.append((cursor, day_end))
return windowsSurprising side effect: the team stopped padding estimates "just in case" once they saw the copilot would call out the slack immediately. Velocity didn't drop — accuracy went up. We also added a "protect focus" toggle that auto-declines meeting invites during claimed deep-work blocks (with a polite auto-reply), which cut context-switching by roughly 40% in the first two weeks.
Biggest limitation right now: it doesn't model energy levels. A 3-hour window at 4 PM isn't the same as 9 AM, but the current heuristic treats them equally. Next iteration will weight windows by personal productivity curves — probably just a simple multiplier per hour-of-day learned from past commit timestamps.
If you're running a small team drowning in meeting creep, the whole thing is ~200 lines of Python + a Firestore cache. Happy to share the repo structure if anyone wants to fork it.