agentsclimarketplace

Run1 email json parsing and meeting extraction

Skill cxcscmu/SkillLearnBench/skills/b3-teacher-feedback-claude-opus-4-6/schedule-planning/run1_email-json-parsing-and-meeting-extraction

Use this skill when you need to parse meeting schedule request emails from a JSON file, extract meeting duration, date/time constraints, sender information, and message IDs. Handles various natural language patterns for expressing meeting preferences.From its SKILL.md

Install
npx -y skills add cxcscmu/SkillLearnBench --skill run1_email-json-parsing-and-meeting-extraction

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

4.9 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Parsing Meeting Request Emails from JSON

Expected JSON Structure

The input file /root/test_input.json typically contains an array of email objects:

[
  {
    "messageID": "msg001",
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Meeting Request",
    "body": "Hi, I'd like to schedule a 1-hour meeting next Thursday..."
  }
]

Loading and Parsing

import json

with open("/root/test_input.json", "r") as f:
    emails = json.load(f)

# Handle both list and dict with key like "emails"
if isinstance(emails, dict):
    # Try common keys
    for key in ["emails", "messages", "data"]:
        if key in emails:
            emails = emails[key]
            break

Extracting Meeting Duration

Common patterns in natural language:

  • "1-hour meeting" / "1 hour meeting"
  • "30-minute meeting" / "30 min meeting"
  • "a meeting for 2 hours"
  • "1.5 hours" / "1.5-hour meeting"
  • "90 minutes"
import re

def extract_duration(text):
    """Extract meeting duration in minutes from text."""
    text = text.lower()
    
    # Pattern: X-hour or X hour(s)
    match = re.search(r'(\d+\.?\d*)\s*[-–]?\s*hours?', text)
    if match:
        return float(match.group(1)) * 60
    
    # Pattern: X-minute or X minute(s) or X min
    match = re.search(r'(\d+)\s*[-–]?\s*(?:minutes?|mins?)', text)
    if match:
        return int(match.group(1))
    
    # Pattern: "half hour" or "half an hour"
    if "half hour" in text or "half an hour" in text:
        return 30
    
    # Default assumption
    return 60  # 1 hour default

Extracting Date Constraints

from datetime import datetime, timedelta
import re

def extract_date_constraints(text):
    """Extract date preferences from email text."""
    text_lower = text.lower()
    
    # Explicit dates: "January 8, 2026", "01/08/2026", "2026-01-08"
    # Pattern for Month DD, YYYY
    match = re.search(
        r'(january|february|march|april|may|june|july|august|september|october|november|december)'
        r'\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(\d{4})',
        text_lower
    )
    if match:
        month_str, day, year = match.group(1), match.group(2), match.group(3)
        date_str = f"{month_str} {day} {year}"
        return datetime.strptime(date_str, "%B %d %Y").date()
    
    # ISO format
    match = re.search(r'(\d{4}-\d{2}-\d{2})', text)
    if match:
        return datetime.strptime(match.group(1), "%Y-%m-%d").date()
    
    # Relative dates: "next Monday", "this Friday"
    day_names = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
    for i, day_name in enumerate(day_names):
        if day_name in text_lower:
            return day_name  # Return as string for further processing
    
    return None

Extracting Time Preferences

def extract_time_preference(text):
    """Extract preferred time from email text."""
    text_lower = text.lower()
    
    # "morning", "afternoon", "after 2 PM", "before noon"
    if "morning" in text_lower:
        return ("morning", "09:00", "12:00")
    if "afternoon" in text_lower:
        return ("afternoon", "12:00", "17:00")
    
    # Specific time: "at 2 PM", "around 10:00 AM", "after 3:00 PM"
    match = re.search(r'(?:at|around|after|before|by)\s+(\d{1,2}):?(\d{2})?\s*(am|pm)', text_lower)
    if match:
        hour = int(match.group(1))
        minute = int(match.group(2)) if match.group(2) else 0
        ampm = match.group(3)
        return ("specific", f"{hour:02d}:{minute:02d} {ampm.upper()}")
    
    return None

Extracting Sender/Recipient Info

def extract_email_details(email_obj):
    """Extract all relevant fields from an email object."""
    return {
        "message_id": email_obj.get("messageID") or email_obj.get("message_id") or email_obj.get("id"),
        "sender": email_obj.get("from") or email_obj.get("sender"),
        "recipient": email_obj.get("to") or email_obj.get("recipient"),
        "subject": email_obj.get("subject", ""),
        "body": email_obj.get("body") or email_obj.get("message") or email_obj.get("content", ""),
    }

Full Extraction Pipeline

def parse_meeting_request(email_obj):
    details = extract_email_details(email_obj)
    body = details["subject"] + " " + details["body"]
    
    duration_minutes = extract_duration(body)
    date_constraint = extract_date_constraints(body)
    time_preference = extract_time_preference(body)
    
    return {
        **details,
        "duration_minutes": duration_minutes,
        "duration_hours": duration_minutes / 60,
        "date_constraint": date_constraint,
        "time_preference": time_preference,
    }

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.