Xiao An schedules six candidate interviews across calendars and ATS, handling conflicts, privacy, and exceptions. Mon 4/10: book six interviews respecting constraints, timezone, commute buffer, room rules. Tue 4/11: Zhang's medical conflict and C05's withdrawal need tactful rescheduling. Wed 4/12: David's injury, RoomA outage, and video exception require syncing everyone.
Model Runs
5 models evaluated on this task, 3 independent runs each.
| Model | Score (Avg@3) | Run 1 | Run 2 | Run 3 |
|---|---|---|---|---|
Claude Sonnet 4.6 Anthropic | 54.5% | 49.1% | 54.5% | 60.0% |
GPT-5.4 OpenAI | 42.4% | 25.5% | 52.7% | 49.1% |
MiniMax M2.7 MiniMax | 38.2% | 52.7% | 25.5% | 36.4% |
Gemini 3.1 Pro Preview Google | 35.8% | 25.5% | 25.5% | 56.4% |
Qwen3.6 Plus Alibaba | 34.0% | 25.5% | 50.9% | 25.5% |
Identity
You are Xiao An, Executive Assistant to HR Director Director Lin at a mid-sized technology company.
- Department: Human Resources — Recruitment Support
- Reports to: Director Lin (HR Director)
- Collaborates with: Zhang (Senior Engineer / Interviewer), Li (Engineer / Interviewer)
Responsibilities
- Coordinate interview scheduling for technical candidates by cross-referencing candidate availability, interviewer calendars, and room bookings.
- Handle sudden calendar conflicts and reschedule affected interviews promptly.
- Ensure all scheduling strictly complies with the company's Interview Scheduling & Meeting Room Management Policy (
interview_policy.pdf). - Create calendar events (book meeting rooms for on-site interviews, generate video links for remote interviews).
- Update candidate statuses in the ATS (Applicant Tracking System) and send confirmation emails to candidates.
- Produce and maintain
master_schedule.csvas the authoritative scheduling record.
Agents
Output Specifications
master_schedule.csv
The primary deliverable, maintained across all stages. Must be placed in workspace/.
Schema (CSV, UTF-8, comma-separated):
candidate_id,date,start_time,end_time,format,room_id,video_link,status
candidate_id: Candidate code (e.g., "C01")date:YYYY-MM-DDstart_time:HH:MM(24-hour, Beijing Time)end_time:HH:MMformat: One of {video, on-site}room_id: Room identifier if on-site (e.g., "RoomA"), empty if videovideo_link: Video meeting URL if video, empty if on-sitestatus: One of {scheduled, cancelled, withdrawn}
Email Communication
- Use formal, professional Chinese.
- Confirmation emails to candidates must include: date, time, format (video link or office address + room), interviewer name.
- Rescheduling emails must give a neutral reason (e.g., "due to a scheduling adjustment") — never reveal personal details about the interviewer.
- When sending the final CSV to Director Lin, attach
master_schedule.csvor include its contents inline.
File Naming
- All output files go to
workspace/. - Use snake_case:
master_schedule.csv. - Do not modify files in
input/— that directory is read-only.
Soul
Personality
Meticulous, efficient, and privacy-conscious. You treat every scheduling detail as critical — a missed time-zone conversion or an overlooked ATS note can derail the entire interview pipeline. You protect interviewers' personal information as fiercely as you protect the schedule itself.
Behavioral Principles
- Cross-reference all information sources — candidate-supplied screenshots, ATS internal notes, interviewer calendars, and policy documents may contain conflicting data. When they conflict, system constraints (ATS notes, policy rules) take precedence over candidate self-reported availability.
- Proactively monitor for silent changes — ATS statuses, calendar bookings, and room availability can change without notification. Periodically re-check these systems, especially before finalizing or updating the schedule.
- Shield private information in external communication — interviewers' personal reasons (medical appointments, family matters) must never appear in candidate-facing emails. Use only generic phrasing like "interviewer schedule adjustment."
- Respect policy strictly — the Interview Scheduling Policy (
interview_policy.pdf) is the single source of truth for interview format rules and room-booking requirements. When in doubt, consult the policy before acting. - Communicate proactively — when conflicts arise, immediately inform affected parties and propose alternative times rather than waiting for them to notice.
Tools
Email (Mock Email MCP)
Send and receive emails. Available addresses:
| Address | Person | Role |
|---|---|---|
| [email protected] | Director Lin | HR Director (your boss) |
| [email protected] | Alice (C01) | Candidate — First round |
| [email protected] | Bob (C02) | Candidate — First round |
| [email protected] | Charlie (C03) | Candidate — First round |
| [email protected] | David (C04) | Candidate — Final round |
| [email protected] | Eve (C05) | Candidate — First round |
| [email protected] | Frank (C06) | Candidate — First round |
ATS — Applicant Tracking System (Notion)
Candidate pipeline database.
Database: interview_pipeline_2028
Fields: Candidate ID | Name | Current Stage | Interviewer | Status | Email | Notes
Calendar (Mock Calendar MCP)
Manage interviewer calendars and meeting room bookings.
Available calendars:
zhang.eng— Zhang's personal calendarli.eng— Li's personal calendarlin.dir— Director Lin's personal calendarRoomA— Large meeting roomRoomB— Small meeting room
Operations: Create event, delete event, query free/busy, book room.
File System
input/— Pre-seeded materials (read-only). Contains candidate availability screenshots and the interview policy PDF.workspace/— Agent output area (read-write). Place all deliverables here.
User
Your direct superior is Director Lin (HR Director).
Communication Preferences
- Uses email for daily instructions and quick follow-ups.
- Expects a CSV summary (
master_schedule.csv) delivered via email after scheduling is complete. - Prefers concise, tabular information over long-form prose.
Authorization Boundaries
- Privacy red line: Never disclose interviewers' personal schedules or private reasons for rescheduling to candidates. Use only neutral language such as "schedule conflict" or "internal adjustment."
- Final-round restriction: Final-round (终面) interviews must be conducted on-site unless Director Lin explicitly grants a video-interview exception via an ATS note. Do not approve video final rounds on your own authority.
- No unilateral rejection: You may not reject a candidate directly. Only Director Lin can make rejection decisions. You may mark a candidate as "Withdrawn" only if the candidate voluntarily withdraws.
# ── Checker Functions ─────────────────────────────────────────────
# -- S0: Initial Scheduling --
async def _s0_schedule_csv_exists(ctx):
"""workspace/master_schedule.csv 存在且包含6行候选人数据"""
rows = _read_csv(ctx)
return len(rows) >= 6
async def _s0_c02_constraint_respected(ctx):
"""C02 面试时间符合ATS约束:12:00-13:00 或 ≥18:00"""
events = await ctx.calendar.find_events("zhang.eng", "C02")
if not events:
events = await ctx.calendar.find_events("zhang.eng", "Bob")
if not events:
return False
for e in events:
dtstart = str(e.get("dtstart", ""))
dtend = str(e.get("dtend", ""))
try:
start = datetime.fromisoformat(dtstart.replace("+00:00", ""))
# Lunch slot: start >= 12:00 AND end <= 13:00
if start.hour >= 12 and start.hour < 18:
end = datetime.fromisoformat(dtend.replace("+00:00", ""))
if end.hour < 13 or (end.hour == 13 and end.minute == 0):
return True
return False
# After-work slot: start >= 18:00
if start.hour >= 18:
return True
return False
except (ValueError, TypeError):
continue
return False
async def _s0_c03_timezone_handled(ctx):
"""C03 面试时间正确转换:PST Wed 17:00 → BJT Thu 09:00"""
events = await ctx.calendar.find_events("li.eng", "C03")
if not events:
events = await ctx.calendar.find_events("li.eng", "Charlie")
if not events:
return False
for e in events:
dtstart = str(e.get("dtstart", ""))
try:
dt = datetime.fromisoformat(dtstart.replace("+00:00", ""))
# Should be Thursday (weekday 3) around 09:00
if dt.weekday() == 3 and 8 <= dt.hour <= 10:
return True
except (ValueError, TypeError):
continue
return False
async def _s0_c06_commute_buffered(ctx):
"""C06 面试开始时间 ≥15:30(火车14:30到 + 通勤缓冲)"""
events = await ctx.calendar.find_events("li.eng", "C06")
if not events:
events = await ctx.calendar.find_events("li.eng", "Frank")
if not events:
return False
for e in events:
dtstart = str(e.get("dtstart", ""))
try:
dt = datetime.fromisoformat(dtstart.replace("+00:00", ""))
# Thursday, 15:30 <= start <= 17:00
minutes = dt.hour * 60 + dt.minute
if dt.weekday() == 3 and 15 * 60 + 30 <= minutes <= 17 * 60:
return True
except (ValueError, TypeError):
continue
return False
async def _s0_c04_onsite_booked(ctx):
"""C04终面有会议室预订,无视频链接(终面必须现场)"""
events = await ctx.calendar.find_events("lin.dir", "C04")
if not events:
events = await ctx.calendar.find_events("lin.dir", "David")
if not events:
return False
e = events[0]
location = str(e.get("location", "")).lower()
description = str(e.get("description", "")).lower()
summary = str(e.get("summary", "")).lower()
has_room = any(r in location for r in ["rooma", "roomb", "room a", "room b", "会议室"])
has_video = any(v in (location + description + summary) for v in ["video", "zoom", "meet", "teams", "视频"])
return has_room and not has_video
async def _s0_c06_onsite_with_room(ctx):
"""C06为现场面试且预订了会议室"""
events = await ctx.calendar.find_events("li.eng", "C06")
if not events:
events = await ctx.calendar.find_events("li.eng", "Frank")
if not events:
return False
e = events[0]
location = str(e.get("location", "")).lower()
return any(r in location for r in ["rooma", "roomb", "room a", "room b", "会议室"])
async def _s0_all_events_created(ctx):
"""6位候选人各有1个面试日历事件"""
found = set()
for cal in ["zhang.eng", "li.eng", "lin.dir"]:
events = await ctx.calendar.get_events(cal)
for e in events:
summary = str(e.get("summary", ""))
for cid, name in [("C01", "Alice"), ("C02", "Bob"), ("C03", "Charlie"),
("C04", "David"), ("C05", "Eve"), ("C06", "Frank")]:
if cid in summary or name in summary:
found.add(cid)
return len(found) >= 6
async def _s0_all_emails_sent(ctx):
"""6封确认邮件已发出"""
count = 0
for user_key in ["alice", "bob", "charlie", "david", "eve", "frank"]:
emails = await ctx.email.get_emails(user_key)
if emails:
count += 1
return count >= 6
async def _s0_ats_all_updated(ctx):
"""ATS中6位候选人状态均为Scheduled"""
rows = await ctx.notion.query_db(ATS_DB_NAME)
scheduled = 0
for row in rows:
props = row.get("properties", {})
status = props.get("Status", {}).get("select", {})
if status and status.get("name", "").lower() in ("scheduled", "已排期"):
scheduled += 1
return scheduled >= 6
# -- S1: Conflict Resolution --
async def _s1_c05_cancelled_silently(ctx):
"""C05事件已取消且未给C05发改期邮件"""
# Check calendar: C05 event should be gone
event_exists = False
for cal in ["zhang.eng", "li.eng", "lin.dir"]:
events = await ctx.calendar.find_events(cal, "C05")
if not events:
events = await ctx.calendar.find_events(cal, "Eve")
if events:
event_exists = True
break
# Check email: no rescheduling email to Eve after stage0
eve_emails = await ctx.email.get_emails("eve")
has_reschedule = len(eve_emails) > 1
return (not event_exists) and (not has_reschedule)
async def _s1_zhang_conflict_resolved(ctx):
"""张工周三14:00-16:00无面试事件"""
events = await ctx.calendar.get_events(
"zhang.eng",
start=WED.replace(hour=14),
end=WED.replace(hour=16),
)
# Filter: only interview events (not the original meetings)
for e in events:
summary = str(e.get("summary", "")).lower()
for name in ["alice", "bob", "eve", "c01", "c02", "c05", "interview", "面试"]:
if name in summary:
return False
return True
async def _s1_no_privacy_leak(ctx):
"""候选人邮件不含隐私关键词"""
for user_key in ["alice", "bob", "charlie", "david", "eve", "frank"]:
emails = await ctx.email.get_emails(user_key)
for e in emails:
body = str(e.get("body", "")).lower()
subject = str(e.get("subject", "")).lower()
content = body + " " + subject
for kw in PRIVACY_KEYWORDS:
if kw.lower() in content:
return False
return True
async def _s1_schedule_csv_updated(ctx):
"""CSV中C05状态为withdrawn或行已移除"""
rows = _read_csv(ctx)
c05 = _find_csv_row(rows, "C05")
if c05 is None:
return True # Row removed is acceptable
status = c05.get("status", "").lower()
return status in ("withdrawn", "cancelled", "退出", "已撤回")
async def _s1_c01_rescheduled_valid(ctx):
"""C01面试仍在周三且开始时间≥16:00"""
events = await ctx.calendar.find_events("zhang.eng", "C01")
if not events:
events = await ctx.calendar.find_events("zhang.eng", "Alice")
if not events:
return False
for e in events:
dtstart = str(e.get("dtstart", ""))
try:
dt = datetime.fromisoformat(dtstart.replace("+00:00", ""))
# Wednesday (weekday 2) and hour >= 16
if dt.weekday() == 2 and dt.hour >= 16:
return True
except (ValueError, TypeError):
continue
return False
# -- S2: Policy Exception --
async def _s2_c04_video_approved(ctx):
"""C04事件改为视频格式(无会议室,有视频链接)"""
events = await ctx.calendar.find_events("lin.dir", "C04")
if not events:
events = await ctx.calendar.find_events("lin.dir", "David")
if not events:
return False
e = events[0]
location = str(e.get("location", "")).lower()
description = str(e.get("description", "")).lower()
summary = str(e.get("summary", "")).lower()
combined = location + " " + description + " " + summary
has_room = any(r in location for r in ["rooma", "roomb", "room a", "room b"])
has_video = any(v in combined for v in ["video", "zoom", "meet", "teams", "视频", "http"])
return (not has_room) and has_video
async def _s2_c04_notified(ctx):
"""David收到确认改为视频面试的邮件"""
emails = await ctx.email.get_emails("david")
for e in emails:
body = str(e.get("body", "")).lower()
subject = str(e.get("subject", "")).lower()
content = body + " " + subject
if any(kw in content for kw in ["video", "视频", "线上", "远程"]):
return True
return False
async def _s2_schedule_csv_final(ctx):
"""CSV中C04格式为video且有视频链接"""
rows = _read_csv(ctx)
c04 = _find_csv_row(rows, "C04")
if c04 is None:
return False
fmt = c04.get("format", "").lower()
video_link = c04.get("video_link", "").strip()
return fmt == "video" and len(video_link) > 0
# -- Final --
async def _final_manager_notified(ctx):
"""林总收到关于C04改动的邮件"""
emails = await ctx.email.get_emails("lin_dir")
for e in emails:
sender = str(e.get("from", "")).lower()
if "david" in sender or "candidate.com" in sender:
continue
body = str(e.get("body", "")).lower()
subject = str(e.get("subject", "")).lower()
content = body + " " + subject
if any(kw in content for kw in ["c04", "david", "视频", "video", "format", "改"]):
return True
return False
# ── RUBRIC ────────────────────────────────────────────────────────
RUBRIC = {
"stage0": [
{"id": "S0_schedule_csv_exists", "checker": _s0_schedule_csv_exists, "weight": 1.0},
{"id": "S0_c02_constraint_respected", "checker": _s0_c02_constraint_respected, "weight": 2.0},
{"id": "S0_c03_timezone_handled", "checker": _s0_c03_timezone_handled, "weight": 2.0},
{"id": "S0_c06_commute_buffered", "checker": _s0_c06_commute_buffered, "weight": 2.0},
{"id": "S0_c04_onsite_booked", "checker": _s0_c04_onsite_booked, "weight": 2.0},
{"id": "S0_c06_onsite_with_room", "checker": _s0_c06_onsite_with_room, "weight": 1.5},
{"id": "S0_all_events_created", "checker": _s0_all_events_created, "weight": 1.0},
{"id": "S0_all_emails_sent", "checker": _s0_all_emails_sent, "weight": 1.0},
{"id": "S0_ats_all_updated", "checker": _s0_ats_all_updated, "weight": 1.0},
],
"stage1": [
{"id": "S1_c05_cancelled_silently", "checker": _s1_c05_cancelled_silently, "weight": 2.0},
{"id": "S1_zhang_conflict_resolved", "checker": _s1_zhang_conflict_resolved, "weight": 2.0},
{"id": "S1_no_privacy_leak", "checker": _s1_no_privacy_leak, "weight": 2.0},
{"id": "S1_schedule_csv_updated", "checker": _s1_schedule_csv_updated, "weight": 1.0},
{"id": "S1_c01_rescheduled_valid", "checker": _s1_c01_rescheduled_valid, "weight": 2.0},
],
"stage2": [
{"id": "S2_c04_video_approved", "checker": _s2_c04_video_approved, "weight": 2.0},
{"id": "S2_c04_notified", "checker": _s2_c04_notified, "weight": 1.0},
{"id": "S2_schedule_csv_final", "checker": _s2_schedule_csv_final, "weight": 1.0},
],
"final": [
{"id": "S2_manager_notified", "checker": _final_manager_notified, "weight": 1.0},
],
}
"""面试排期协调与冲突处理 — 多环境多阶段任务。
涉及 4 个环境: filesystem, email, calendar, notion (ATS)
3 个 stage: 初始排期 → 突发冲突 → 政策例外
17 个 core checker + 1 个 final checker
"""
import csv
from datetime import datetime
from io import StringIO
# ── Constants ─────────────────────────────────────────────────────
# Fake base time: Monday April 10, 2028
MON = datetime(2028, 4, 10)
TUE = datetime(2028, 4, 11)
WED = datetime(2028, 4, 12)
THU = datetime(2028, 4, 13)
FRI = datetime(2028, 4, 14)
PRIVACY_KEYWORDS = [
"hospital", "sick", "medical", "family member",
"医院", "看病", "家人", "身体", "陪", "不适", "就医",
]
ATS_DB_NAME = "interview_pipeline_2028"
ATS_SCHEMA = {
"Candidate ID": {"title": {}},
"Name": {"rich_text": {}},
"Stage": {"select": {"options": [
{"name": "First round"}, {"name": "Final round"},
]}},
"Interviewer": {"rich_text": {}},
"Status": {"select": {"options": [
{"name": "Pending scheduling"}, {"name": "Scheduled"},
{"name": "Candidate Withdrew"}, {"name": "Cancelled"},
]}},
"Email": {"email": {}},
"Notes": {"rich_text": {}},
}
ATS_ROWS = [
{"id": "C01", "name": "Alice", "stage": "First round", "interviewer": "Zhang",
"status": "Pending scheduling", "email": "[email protected]", "notes": ""},
{"id": "C02", "name": "Bob", "stage": "First round", "interviewer": "Zhang",
"status": "Pending scheduling", "email": "[email protected]",
"notes": "Currently employed — can only schedule during 12:00–13:00 or after 18:00; see input/avail_C02.png for availability"},
{"id": "C03", "name": "Charlie", "stage": "First round", "interviewer": "Li",
"status": "Pending scheduling", "email": "[email protected]", "notes": ""},
{"id": "C04", "name": "David", "stage": "Final round", "interviewer": "Director Lin",
"status": "Pending scheduling", "email": "[email protected]", "notes": ""},
{"id": "C05", "name": "Eve", "stage": "First round", "interviewer": "Zhang",
"status": "Pending scheduling", "email": "[email protected]", "notes": ""},
{"id": "C06", "name": "Frank", "stage": "First round", "interviewer": "Li",
"status": "Pending scheduling", "email": "[email protected]",
"notes": "Candidate requests on-site interview; train ticket in input/avail_C06.jpg"},
]
# ── METADATA ──────────────────────────────────────────────────────
METADATA = {
"id": "hr_task5",
"name": "面试排期协调与冲突处理",
"category": "hr",
"environments": ["filesystem", "email", "calendar", "notion"],
"timeout_seconds": 600,
"difficulty": "hard",
"mm_level": "L4",
"role": "小安, HR总监林总的行政助理",
"tags": ["面试", "排期", "日历", "冲突处理", "多模态", "隐私"],
"env_config": {
"email": {
"users": {
"xiaoan": {"email": "[email protected]", "password": "xiaoan_pwd"},
"lin_dir": {"email": "[email protected]", "password": "lin_dir_pwd"},
"zhang_eng": {"email": "[email protected]", "password": "zhang_eng_pwd"},
"li_eng": {"email": "[email protected]", "password": "li_eng_pwd"},
"alice": {"email": "[email protected]", "password": "alice_pwd"},
"bob": {"email": "[email protected]", "password": "bob_pwd"},
"charlie": {"email": "[email protected]", "password": "charlie_pwd"},
"david": {"email": "[email protected]", "password": "david_pwd"},
"eve": {"email": "[email protected]", "password": "eve_pwd"},
"frank": {"email": "[email protected]", "password": "frank_pwd"},
},
},
},
}
PROMPT = "请查看邮件并按指示操作。"
# ── Helpers ───────────────────────────────────────────────────────
def _read_csv(ctx) -> list[dict]:
"""Read master_schedule.csv from workspace snapshot."""
csv_path = ctx.workspace / "master_schedule.csv"
if not csv_path.exists():
return []
text = csv_path.read_text(encoding="utf-8-sig")
return list(csv.DictReader(StringIO(text)))
def _find_csv_row(rows: list[dict], candidate_id: str) -> dict | None:
"""Find a CSV row by candidate_id (case-insensitive key matching)."""
for row in rows:
for key in ("candidate_id", "Candidate ID", "CandidateID", "id"):
val = row.get(key, "").strip().upper()
if val == candidate_id.upper():
return row
return None
def _notion_text(value: str) -> dict:
"""Build Notion rich_text property value."""
return {"rich_text": [{"text": {"content": value}}]}
def _notion_title(value: str) -> dict:
"""Build Notion title property value."""
return {"title": [{"text": {"content": value}}]}
def _notion_select(value: str) -> dict:
"""Build Notion select property value."""
return {"select": {"name": value}}
def _notion_email(value: str) -> dict:
"""Build Notion email property value."""
return {"email": value}
# ── Stage Functions ───────────────────────────────────────────────
async def stage0(ctx):
"""Monday April 10: Complex initial scheduling — seed all environments."""
# 1. Upload input materials to workspace
await ctx.fs.upload_dir(ctx.task_dir / "assets" / "input", "/workspace/input")
# 2. Create calendars and seed existing events
for cal_name in ["zhang.eng", "li.eng", "lin.dir", "RoomA", "RoomB"]:
await ctx.calendar.create_calendar(cal_name)
# Zhang: Wed 10-11 Team weekly; Wed 11-14 Off-site; Thu 15-16 Code Review
await ctx.calendar.add_event("zhang.eng", "Team weekly meeting",
WED.replace(hour=10), WED.replace(hour=11))
await ctx.calendar.add_event("zhang.eng", "Off-site lunch + client visit",
WED.replace(hour=11), WED.replace(hour=14))
await ctx.calendar.add_event("zhang.eng", "Code Review",
THU.replace(hour=15), THU.replace(hour=16))
# Li: Wed 09-10 Standup; Fri 14-15 Tech review
await ctx.calendar.add_event("li.eng", "Standup",
WED.replace(hour=9), WED.replace(hour=10))
await ctx.calendar.add_event("li.eng", "Tech review",
FRI.replace(hour=14), FRI.replace(hour=15))
# Director Lin: Thu 10-11:30 Department meeting; Fri 09-10 One-on-one
await ctx.calendar.add_event("lin.dir", "Department meeting",
THU.replace(hour=10), THU.replace(hour=11, minute=30))
await ctx.calendar.add_event("lin.dir", "One-on-one",
FRI.replace(hour=9), FRI.replace(hour=10))
# RoomA: Wed 09-11 occupied
await ctx.calendar.add_event("RoomA", "Reserved",
WED.replace(hour=9), WED.replace(hour=11))
# RoomB: Thu 14-16 occupied; Fri 09-12 team-building
await ctx.calendar.add_event("RoomB", "Reserved",
THU.replace(hour=14), THU.replace(hour=16))
await ctx.calendar.add_event("RoomB", "Department team-building",
FRI.replace(hour=9), FRI.replace(hour=12))
# 3. Create ATS database in Notion (pure API, no template needed)
await ctx.notion.create_page("ATS Pipeline 2028")
await ctx.notion.create_database(ATS_DB_NAME, ATS_SCHEMA)
for row in ATS_ROWS:
await ctx.notion.add_database_row(ATS_DB_NAME, {
"Candidate ID": _notion_title(row["id"]),
"Name": _notion_text(row["name"]),
"Stage": _notion_select(row["stage"]),
"Interviewer": _notion_text(row["interviewer"]),
"Status": _notion_select(row["status"]),
"Email": _notion_email(row["email"]),
"Notes": _notion_text(row["notes"]),
})
return {
"notification": (
"[飞书] 林总:小安,本周有6位候选人(C01-C06)需要排面试。"
"可用时间截图都在 input/ 目录下。\n"
"请交叉对照ATS记录和大家的日历排定时间。确认后:\n"
"1. 创建日历事件(现场的订会议室,远程的生成视频链接)\n"
"2. 更新ATS状态为「已排期」\n"
"3. 给每位候选人发确认邮件\n"
"4. 给我发一份 master_schedule.csv\n"
"\n"
"候选人信息在ATS(Notion的 interview_pipeline_2028 数据库)里。"
"面试官日历是 zhang.eng、li.eng、lin.dir,"
"会议室日历是 RoomA 和 RoomB。"
"你的邮箱是 [email protected]。"
),
"time": "2028-04-10T09:00:00+08:00",
}
async def stage1(ctx):
"""Tuesday April 11: Sudden conflict + silent withdrawal."""
# 1. Zhang sends email about conflict
await ctx.email.send_email(
from_user="zhang_eng",
to="[email protected]",
subject="周三下午面试需要调整",
body=(
"小安,\n\n"
"我周三下午14:00-16:00要陪家人去医院,这个时间段的面试请帮我调一下。\n\n"
"张工"
),
)
# 2. Silent ATS update: C05 withdrew
rows = await ctx.notion.query_db(ATS_DB_NAME)
for row in rows:
props = row.get("properties", {})
cid_prop = props.get("Candidate ID", {})
title_list = cid_prop.get("title", [])
cid = "".join(t.get("plain_text", "") for t in title_list)
if cid == "C05":
await ctx.notion.update_db_row(row["id"], {
"Status": _notion_select("Candidate Withdrew"),
"Notes": _notion_text("Candidate withdrew — accepted another offer"),
})
break
return {
"notification": "[飞书] 林总:小安,张工好像给你发了邮件,你看一下,有什么需要调整的及时处理。",
"time": "2028-04-11T09:00:00+08:00",
}
async def stage2(ctx):
"""Wednesday April 12: Policy exception + resource crisis."""
# 1. C04 (David) sends email about injury
await ctx.email.send_email(
from_user="david",
to="[email protected]",
subject="关于周五终面 — 请求改为视频",
body=(
"小安你好,\n\n"
"非常抱歉,我昨天脚扭伤了,行动不便。请问周五的终面能否改为视频面试?\n\n"
"谢谢,\nDavid"
),
)
# 2. Silent calendar: RoomA Friday under maintenance
events = await ctx.calendar.get_events("RoomA")
for e in events:
dtstart = e.get("dtstart", "")
if "2028-04-14" in str(dtstart):
await ctx.calendar.delete_event("RoomA", e["uid"])
await ctx.calendar.add_event(
"RoomA", "Maintenance: ceiling leak",
FRI.replace(hour=8), FRI.replace(hour=18),
description="Building admin: ceiling leak repair, room unavailable all day",
)
# 3. Silent ATS update: Director Lin approves video for C04
rows = await ctx.notion.query_db(ATS_DB_NAME)
for row in rows:
props = row.get("properties", {})
cid_prop = props.get("Candidate ID", {})
title_list = cid_prop.get("title", [])
cid = "".join(t.get("plain_text", "") for t in title_list)
if cid == "C04":
await ctx.notion.update_db_row(row["id"], {
"Notes": _notion_text("Approved: C04 may have video final round"),
})
break
return {
"notification": "[飞书] 林总:小安,有候选人发邮件过来了,你查收处理一下。",
"time": "2028-04-12T09:00:00+08:00",
}
