Remove Osaka Itinerary

L3
ModelContextProtocolNotionJapan Travel Planner

Remove the itinerary items in Osaka after 6 PM from Day 1 and Day 2 travel schedules.

Created by Xiangyan Liu
2025-07-27
Conditional FilteringAutomated Migration

Model Ranking

Click on the dots to view the trajectory of each task run
Model
Run Results
Pass@4
Pass^4
Avg Time
Avg Turns
Input Tokens
Output Tokens
Total Tokens
Claude
claude-sonnet-4
4
/4
122.8s
9.5
378,762
1,699
380,460
Claude
claude-sonnet-4-high
4
/4
96.7s
10.8
1,021,533
1,839
1,023,372
Claude
claude-sonnet-4-low
4
/4
107.2s
9.3
811,543
1,828
813,370
OpenAI
gpt-5-low
4
/4
141.3s
4.5
135,161
5,347
140,509
OpenAI
gpt-5-medium
4
/4
132.8s
4.0
132,038
5,662
137,700
MoonshotAI
kimi-k2-0711
4
/4
75.5s
7.3
174,954
736
175,690
MoonshotAI
kimi-k2-0905
4
/4
105.4s
8.3
186,945
1,093
188,038
Qwen
qwen-3-coder-plus
4
/4
42.5s
9.3
300,377
1,137
301,514
Gemini
gemini-2-5-pro
3
/4
35.6s
4.0
79,904
2,255
82,159
Z.ai
glm-4-5
3
/4
87.4s
8.8
247,707
2,193
249,899
OpenAI
gpt-5-high
3
/4
366.3s
7.8
194,660
14,847
209,506
Grok
grok-4
3
/4
87.3s
6.5
97,195
3,466
100,661
Grok
grok-code-fast-1
3
/4
34.4s
6.0
82,894
1,513
85,206
OpenAI
o4-mini
3
/4
192.8s
13.3
230,413
12,152
242,565
Qwen
qwen-3-max
3
/4
48.4s
6.8
142,126
349
142,475
Gemini
gemini-2-5-flash
2
/4
30.3s
6.3
96,509
3,781
100,290
OpenAI
gpt-5-mini-medium
2
/4
125.1s
6.8
526,468
8,132
534,600
Claude
claude-opus-4-1
1
/1
--
94.1s
8.0
792,892
1,289
794,181
OpenAI
gpt-4-1
1
/4
30.4s
4.5
108,433
319
108,752
OpenAI
gpt-5-mini-high
1
/4
440.1s
12.8
1,567,146
32,806
1,599,952
OpenAI
gpt-oss-120b
1
/4
40.2s
5.0
59,324
1,628
60,952
OpenAI
o3
1
/4
102.8s
11.3
231,743
4,417
236,160
DeepSeek
deepseek-chat
0
/4
112.2s
8.3
404,461
441
404,902
OpenAI
gpt-4-1-mini
0
/4
21.4s
3.8
55,411
316
55,726
OpenAI
gpt-4-1-nano
0
/4
19.0s
7.5
54,876
397
55,272
OpenAI
gpt-5-mini-low
0
/4
38.4s
5.8
78,034
2,412
80,446
OpenAI
gpt-5-nano-high
0
/4
226.1s
7.8
811,677
39,354
851,031
OpenAI
gpt-5-nano-low
0
/4
48.5s
4.5
114,643
6,164
120,806
OpenAI
gpt-5-nano-medium
0
/4
70.9s
4.3
266,932
10,432
277,363

Task State

Notion Workspace
This task is executed based on this Notion workspace
This workspace is cloned from notion official template marketplace.View Original Template

Instruction

Go to Japan Travel Planner and remove the itinerary in OSAKA after 6 PM (excluding 6 PM) in Day 1 and Day 2.



Verify

*.py
Python
import sys
from notion_client import Client
from tasks.utils import notion_utils

def get_page_title(page_result):
    """Extract title from a page result"""
    properties = page_result.get('properties', {})
    name_property = properties.get('Name', {})
    if name_property.get('type') == 'title':
        title_array = name_property.get('title', [])
        if title_array and len(title_array) > 0:
            return title_array[0].get('plain_text', '')
    return ''

def get_page_time(page_result):
    """Extract time from Notes field"""
    properties = page_result.get('properties', {})
    notes_property = properties.get('Notes', {})
    if notes_property.get('type') == 'rich_text':
        rich_text_array = notes_property.get('rich_text', [])
        if rich_text_array and len(rich_text_array) > 0:
            notes_text = rich_text_array[0].get('plain_text', '')
            return notes_text.strip()
    return ''

def get_page_group(page_result):
    """Extract group/location from page"""
    properties = page_result.get('properties', {})
    group_property = properties.get('Group', {})
    if group_property.get('type') == 'select':
        select = group_property.get('select')
        if select:
            return select.get('name', '')
    return ''

def get_page_day(page_result):
    """Extract day from page"""
    properties = page_result.get('properties', {})
    day_property = properties.get('Day', {})
    if day_property.get('type') == 'select':
        select = day_property.get('select')
        if select:
            return select.get('name', '')
    return ''

def parse_time_to_minutes(time_str):
    """Convert time string to minutes for comparison
    Returns None if time cannot be parsed"""
    if not time_str:
        return None
    
    # Clean the time string
    time_str = time_str.strip().upper()
    
    # Remove any text after the time (e.g., "7:30 PM\n" -> "7:30 PM")
    time_str = time_str.split('\n')[0].strip()
    
    # Extract time components
    try:
        if 'PM' in time_str:
            time_part = time_str.replace('PM', '').strip()
            if ':' in time_part:
                hours, minutes = time_part.split(':')
                hours = int(hours)
                minutes = int(minutes)
            else:
                hours = int(time_part)
                minutes = 0
            # Convert PM hours (add 12 for PM times except 12 PM)
            if hours != 12:
                hours += 12
            return hours * 60 + minutes
        elif 'AM' in time_str:
            time_part = time_str.replace('AM', '').strip()
            if ':' in time_part:
                hours, minutes = time_part.split(':')
                hours = int(hours)
                minutes = int(minutes)
            else:
                hours = int(time_part)
                minutes = 0
            # Handle 12 AM (midnight)
            if hours == 12:
                hours = 0
            return hours * 60 + minutes
    except:
        return None
    
    return None

def verify(notion: Client, main_id: str = None) -> bool:
    """
    Verifies that all OSAKA events after 6PM have been removed from Day 1 and Day 2 in the Japan Travel Planner.
    
    Expected items that should be deleted (all in OSAKA, after 6PM, on Day 1 or Day 2):
    1. Rikuro's Namba Main Branch - 7 PM (Day 1)
    2. Shin Sekai "New World" - 8 PM (Day 2)
    3. Katsudon Chiyomatsu - 7:30 PM (Day 2)
    4. Ebisubashi Bridge - 9 PM (Day 1)
    
    Note: Kuromon Ichiba Market at 6 PM should NOT be deleted (it's at 6PM, not after)
    Items after 6PM on other days (Day 3-8) should NOT be deleted
    """
    
    # Step 1: Find the main Japan Travel Planner page
    if main_id:
        found_id, object_type = notion_utils.find_page_or_database_by_id(notion, main_id)
        if not found_id or object_type != 'page':
            print("Error: Japan Travel Planner page not found.", file=sys.stderr)
            return False
    else:
        # Try to find the page by searching
        found_id = notion_utils.find_page(notion, "Japan Travel Planner")
        if not found_id:
            print("Error: Japan Travel Planner page not found.", file=sys.stderr)
            return False
    
    print(f"Found Japan Travel Planner page: {found_id}")
    
    # Step 2: Find the Travel Itinerary database
    all_blocks = notion_utils.get_all_blocks_recursively(notion, found_id)
    travel_itinerary_db_id = None
    
    for block in all_blocks:
        if block and block.get("type") == "child_database":
            title = block.get("child_database", {}).get("title", "")
            if "Travel Itinerary" in title:
                travel_itinerary_db_id = block.get("id")
                print(f"Found Travel Itinerary database: {travel_itinerary_db_id}")
                break
    
    if not travel_itinerary_db_id:
        print("Error: Travel Itinerary database not found", file=sys.stderr)
        return False
    
    # Step 3: Query the database for OSAKA items on Day 1 and Day 2
    try:
        query_result = notion.databases.query(
            database_id=travel_itinerary_db_id,
            filter={
                "and": [
                    {"property": "Group", "select": {"equals": "Osaka"}},
                    {"or": [
                        {"property": "Day", "select": {"equals": "Day 1"}},
                        {"property": "Day", "select": {"equals": "Day 2"}}
                    ]}
                ]
            }
        )
    except Exception as e:
        print(f"Error querying Travel Itinerary database: {e}", file=sys.stderr)
        return False
    
    # Step 4: Check for items that should have been deleted
    six_pm_minutes = 18 * 60  # 6 PM in minutes (18:00)
    
    # Expected deleted items (4 specific items after 6 PM on Day 1 and Day 2)
    expected_deleted = {
        "Rikuro's Namba Main Branch": {"time": "7 PM", "day": "Day 1", "found": False},
        "Shin Sekai \"New World\"": {"time": "8 PM", "day": "Day 2", "found": False},
        "Katsudon Chiyomatsu": {"time": "7:30 PM", "day": "Day 2", "found": False},
        "Ebisubashi Bridge": {"time": "9 PM", "day": "Day 1", "found": False}
    }
    
    # Items that should remain (at or before 6 PM)
    expected_remaining = {
        "Kuromon Ichiba Market": {"time": "6 PM", "found": False}
    }
    
    osaka_items_after_6pm = []
    osaka_items_at_or_before_6pm = []
    
    # Debug: Show total query results
    print(f"Debug: Found {len(query_result.get('results', []))} total OSAKA items on Day 1 and Day 2")
    
    # Process all OSAKA items on Day 1 and Day 2
    for page in query_result.get('results', []):
        page_title = get_page_title(page).strip()
        page_time = get_page_time(page)
        page_group = get_page_group(page)
        page_day = get_page_day(page)
        
        if page_group != "Osaka":
            continue
        
        # Parse time to check if after 6 PM
        time_minutes = parse_time_to_minutes(page_time)
        
        if time_minutes is not None and time_minutes > six_pm_minutes:
            osaka_items_after_6pm.append({
                "title": page_title,
                "time": page_time,
                "day": page_day,
                "id": page.get('id')
            })
            
            # Check if this is one of the expected deleted items
            for expected_title, expected_info in expected_deleted.items():
                # Clean up the titles for comparison
                clean_page_title = page_title.strip().lower()
                clean_expected_title = expected_title.strip().lower()
                
                # Check for "Rikuro's" or "Rikuro's" (different apostrophe types)
                if "rikuro" in clean_page_title and "rikuro" in clean_expected_title:
                    title_match = True
                elif clean_page_title == clean_expected_title:
                    title_match = True
                elif clean_expected_title in clean_page_title or clean_page_title in clean_expected_title:
                    title_match = True
                else:
                    title_match = False
                    
                if title_match and page_day == expected_info["day"]:
                    print(f"Debug: Found '{page_title}' on {page_day} at {page_time} - matches expected '{expected_title}'")
                    expected_deleted[expected_title]["found"] = True
                
        elif time_minutes is not None and time_minutes <= six_pm_minutes:
            osaka_items_at_or_before_6pm.append({
                "title": page_title,
                "time": page_time,
                "day": page_day,
                "id": page.get('id')
            })
            
            # Check if this is one of the expected remaining items
            for expected_title in expected_remaining:
                if expected_title.lower() in page_title.lower() or page_title.lower() in expected_title.lower():
                    expected_remaining[expected_title]["found"] = True
    
    # Step 5: Verify results
    print(f"\nVerification Summary:")
    print(f"=" * 50)
    
    all_passed = True
    
    # Check that the 4 expected items after 6 PM have been deleted
    print("\n4 Items that should be deleted (after 6 PM on Day 1 and Day 2):")
    for item_name, item_info in expected_deleted.items():
        if item_info["found"]:
            # If found = True, it means the item still exists (was not deleted)
            print(f"✗ {item_name} ({item_info['day']}, {item_info['time']}) - Still exists, should be deleted", file=sys.stderr)
            all_passed = False
        else:
            # If found = False, it means the item was deleted correctly
            print(f"✓ {item_name} ({item_info['day']}, {item_info['time']}) - Correctly deleted")
    
    
    # Check that items at or before 6 PM remain
    print("\nItems that should remain (at or before 6 PM on Day 1 and Day 2):")
    for item_name, item_info in expected_remaining.items():
        if item_info["found"]:
            print(f"✓ {item_name} ({item_info['time']}) - Correctly retained")
        else:
            print(f"✗ {item_name} ({item_info['time']}) - Missing, should not be deleted", file=sys.stderr)
            all_passed = False
    
    # Report any items after 6 PM that still exist
    if osaka_items_after_6pm:
        print(f"\n✗ Found {len(osaka_items_after_6pm)} OSAKA item(s) after 6 PM on Day 1/Day 2:", file=sys.stderr)
        for item in osaka_items_after_6pm:
            print(f"  - {item['title']} at {item['time']} ({item['day']})", file=sys.stderr)
    else:
        print(f"\n✓ No OSAKA items found after 6 PM on Day 1/Day 2 (all correctly deleted)")
    
    # Report count summary
    print(f"\nCount Summary:")
    print(f"- OSAKA items after 6 PM on Day 1/Day 2 found: {len(osaka_items_after_6pm)} (should be 0)")
    print(f"- OSAKA items at/before 6 PM on Day 1/Day 2 found: {len(osaka_items_at_or_before_6pm)}")
    print(f"- Expected deletions verified: {sum(1 for item in expected_deleted.values() if not item['found'])}/4")
    
    return all_passed

def main():
    """
    Executes the verification process and exits with a status code.
    """
    notion = notion_utils.get_notion_client()
    main_id = sys.argv[1] if len(sys.argv) > 1 else None
    
    if verify(notion, main_id):
        print("\nVerification passed: All 4 required OSAKA events after 6 PM on Day 1 and Day 2 have been removed")
        sys.exit(0)
    else:
        print("\nVerification failed: Some OSAKA events after 6 PM on Day 1/Day 2 still exist")
        sys.exit(1)

if __name__ == "__main__":
    main()