File size: 8,209 Bytes
62851e9 | 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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | """
Utility functions for time parsing, conflict detection, and metrics computation.
"""
from datetime import datetime, timedelta
from typing import List, Dict, Tuple, Optional
# βββ Time Utilities βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TIME_SLOTS = [
"08:00", "08:30", "09:00", "09:30", "10:00", "10:30",
"11:00", "11:30", "12:00", "12:30", "13:00", "13:30",
"14:00", "14:30", "15:00", "15:30", "16:00", "16:30",
"17:00", "17:30", "18:00"
]
DURATIONS = [30, 60, 90, 120] # minutes
def parse_time(time_str: str) -> datetime:
"""Parse a HH:MM time string into a datetime object (date fixed to today)."""
return datetime.strptime(time_str, "%H:%M")
def time_to_minutes(time_str: str) -> int:
"""Convert HH:MM to total minutes since midnight."""
dt = parse_time(time_str)
return dt.hour * 60 + dt.minute
def minutes_to_time(minutes: int) -> str:
"""Convert total minutes since midnight to HH:MM string."""
h = minutes // 60
m = minutes % 60
return f"{h:02d}:{m:02d}"
def get_end_time(start_time: str, duration_minutes: int) -> str:
"""Calculate end time given start time and duration in minutes."""
start_mins = time_to_minutes(start_time)
end_mins = start_mins + duration_minutes
return minutes_to_time(end_mins)
def time_ranges_overlap(
start1: str, dur1: int, start2: str, dur2: int
) -> bool:
"""Check if two time ranges overlap.
Args:
start1: Start time of first range (HH:MM).
dur1: Duration of first range in minutes.
start2: Start time of second range (HH:MM).
dur2: Duration of second range in minutes.
Returns:
True if the ranges overlap.
"""
s1 = time_to_minutes(start1)
e1 = s1 + dur1
s2 = time_to_minutes(start2)
e2 = s2 + dur2
return s1 < e2 and s2 < e1
def advance_time_slot(current_time: str, steps: int = 1) -> str:
"""Advance to the next time slot(s)."""
current_mins = time_to_minutes(current_time)
new_mins = current_mins + (30 * steps)
# Clamp to end of day
new_mins = min(new_mins, time_to_minutes("18:00"))
return minutes_to_time(new_mins)
# βββ Conflict Detection ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_conflict_graph(tasks: List[Dict]) -> Dict[int, List[int]]:
"""Build an adjacency list of task conflicts based on time overlaps.
Args:
tasks: List of task dicts with 'id', 'time', 'duration', 'status'.
Returns:
Dict mapping task_id β list of conflicting task_ids.
"""
scheduled = [
t for t in tasks
if t["status"] in ("pending", "scheduled", "completed")
]
conflicts: Dict[int, List[int]] = {t["id"]: [] for t in scheduled}
for i, t1 in enumerate(scheduled):
for t2 in scheduled[i + 1:]:
if time_ranges_overlap(
t1["time"], t1.get("duration", 30),
t2["time"], t2.get("duration", 30)
):
conflicts[t1["id"]].append(t2["id"])
conflicts[t2["id"]].append(t1["id"])
return conflicts
def count_conflicts(tasks: List[Dict]) -> int:
"""Count the total number of unique conflict pairs."""
graph = build_conflict_graph(tasks)
count = 0
seen = set()
for tid, neighbors in graph.items():
for nid in neighbors:
pair = (min(tid, nid), max(tid, nid))
if pair not in seen:
seen.add(pair)
count += 1
return count
# βββ Metrics Computation βββββββββββββββββββββββββββββββββββββββββββββββββββββ
def compute_metrics(state_dict: Dict) -> Dict[str, float]:
"""Compute evaluation metrics from a terminal state.
Metrics:
- task_completion_rate: fraction of tasks completed.
- high_priority_completion: fraction of high-priority tasks completed.
- conflict_count: number of scheduling conflicts remaining.
- message_response_rate: fraction of inbox messages replied to.
- efficiency_score: weighted composite score (0β100).
"""
tasks = state_dict.get("tasks", [])
inbox = state_dict.get("inbox", [])
# Task completion
total_tasks = len(tasks) if tasks else 1
completed = sum(1 for t in tasks if t["status"] == "completed")
task_completion_rate = completed / total_tasks
# High-priority completion
high_tasks = [t for t in tasks if t["priority"] == "high"]
high_completed = sum(1 for t in high_tasks if t["status"] == "completed")
high_priority_completion = (
high_completed / len(high_tasks) if high_tasks else 1.0
)
# Conflicts
conflict_count = count_conflicts(tasks)
# Message response
total_messages = len(inbox) if inbox else 1
replied = sum(1 for m in inbox if m.get("replied", False))
message_response_rate = replied / total_messages
# Efficiency score (weighted composite)
efficiency_score = (
task_completion_rate * 35
+ high_priority_completion * 30
+ message_response_rate * 20
+ max(0, (1 - conflict_count / max(total_tasks, 1))) * 15
)
return {
"task_completion_rate": round(task_completion_rate, 3),
"high_priority_completion": round(high_priority_completion, 3),
"conflict_count": conflict_count,
"message_response_rate": round(message_response_rate, 3),
"efficiency_score": round(efficiency_score, 1),
}
# βββ Task/Message Titles βββββββββββββββββββββββββββββββββββββββββββββββββββββ
MEETING_TITLES = [
"Q4 Strategy Review", "1:1 with Manager", "Sprint Planning",
"Client Call β Acme Corp", "Board Presentation Prep",
"Design Review", "Weekly Standup", "Investor Update",
"Product Roadmap Sync", "Cross-team Alignment",
"Budget Review Meeting", "Hiring Panel Interview",
"Vendor Negotiation", "Architecture Review",
"Marketing Campaign Kickoff"
]
WORK_TITLES = [
"Finalize Q3 Report", "Review PR #247", "Update Documentation",
"Prepare Slide Deck", "Analyze Sales Data",
"Write Technical Spec", "Code Review Session",
"Database Migration Plan", "Security Audit Follow-up",
"Performance Optimization", "Deploy Hotfix v2.3.1",
"Update CI/CD Pipeline", "Refactor Auth Module",
"Write Unit Tests", "API Integration Testing"
]
PERSONAL_TITLES = [
"Dentist Appointment", "Gym Session", "Lunch with Friend",
"Pick Up Dry Cleaning", "Car Service Appointment",
"Call Insurance Company", "Grocery Shopping",
"Yoga Class", "Parent-Teacher Conference",
"Home Repair β Plumber"
]
MESSAGE_CONTENTS_URGENT = [
"Need the quarterly figures ASAP for the board meeting.",
"Critical bug in production β customer-facing. Please advise.",
"Client escalation: contract renewal at risk. Call me.",
"Server outage alert: all hands on deck.",
"Investor meeting moved to tomorrow. Need deck by EOD.",
"Legal flagged compliance issue. Immediate review needed.",
"VP requesting project status update within the hour.",
]
MESSAGE_CONTENTS_NORMAL = [
"FYI: Updated the shared drive with new templates.",
"Team lunch this Friday β please RSVP.",
"Quick question about the API changes in v2.4.",
"Sharing meeting notes from yesterday's sync.",
"Reminder: timesheets due by Friday.",
"New onboarding docs are ready for review.",
"Coffee chat next week? Let me know your availability.",
]
SENDERS = [
"CEO", "VP Engineering", "Product Manager", "HR Director",
"CFO", "Team Lead", "Client β Acme Corp", "External Counsel",
"Marketing Director", "CTO", "Board Member",
"Direct Report β Alex", "Direct Report β Priya",
"Colleague β Jordan", "Colleague β Sam"
]
|