Daily Itinerary Overview

L3
ModelContextProtocolNotionJapan Travel Planner

Create a comprehensive daily itinerary overview page to organize Japan travel plans with structured day-by-day activities.

Created by Xiangyan Liu
2025-07-27
Database ManipulationData AggregationReport GenerationVisual FormattingStatus Tracking

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
OpenAI
gpt-5-high
1
/4
922.9s
7.8
414,556
41,197
455,753
OpenAI
gpt-5-low
1
/4
647.8s
10.0
673,432
28,625
702,056
Claude
claude-opus-4-1
0
/1
--
127.2s
5.0
369,809
3,078
372,887
Claude
claude-sonnet-4
0
/4
116.8s
8.0
460,737
4,206
464,942
Claude
claude-sonnet-4-high
0
/4
136.2s
9.3
956,043
4,341
960,384
Claude
claude-sonnet-4-low
0
/4
207.6s
14.3
1,539,207
5,979
1,545,186
DeepSeek
deepseek-chat
0
/4
280.5s
11.8
782,394
2,623
785,017
Gemini
gemini-2-5-flash
0
/4
12.3s
2.5
21,544
1,062
22,607
Gemini
gemini-2-5-pro
0
/4
49.1s
4.0
181,661
3,727
185,388
Z.ai
glm-4-5
0
/4
130.9s
5.5
151,744
3,715
155,459
OpenAI
gpt-4-1
0
/4
50.8s
4.8
115,356
1,735
117,091
OpenAI
gpt-4-1-mini
0
/4
28.7s
4.8
166,509
666
167,175
OpenAI
gpt-4-1-nano
0
/4
11.9s
4.0
20,533
223
20,755
OpenAI
gpt-5-medium
0
/4
446.7s
8.0
378,175
20,361
398,535
OpenAI
gpt-5-mini-high
0
/4
553.0s
11.0
1,185,292
44,516
1,229,808
OpenAI
gpt-5-mini-low
0
/4
53.9s
3.8
112,634
3,849
116,483
OpenAI
gpt-5-mini-medium
0
/4
129.1s
6.3
499,859
10,980
510,839
OpenAI
gpt-5-nano-high
0
/4
341.0s
6.8
429,817
71,410
501,226
OpenAI
gpt-5-nano-low
0
/4
68.6s
5.5
48,713
10,370
59,084
OpenAI
gpt-5-nano-medium
0
/4
168.1s
6.8
434,057
29,150
463,206
OpenAI
gpt-oss-120b
0
/4
10.1s
2.5
9,198
667
9,864
Grok
grok-4
0
/4
1226.3s
5.3
212,664
6,329
218,993
Grok
grok-code-fast-1
0
/4
671.6s
10.5
345,604
5,399
352,938
MoonshotAI
kimi-k2-0711
0
/4
150.0s
4.8
141,308
2,160
143,468
MoonshotAI
kimi-k2-0905
0
/4
372.0s
15.8
649,360
3,618
652,977
OpenAI
o3
0
/4
116.7s
6.0
185,878
6,543
192,421
OpenAI
o4-mini
0
/4
221.6s
6.0
177,184
12,835
190,018
Qwen
qwen-3-coder-plus
0
/4
86.2s
11.5
885,557
1,797
887,354
Qwen
qwen-3-max
0
/4
121.7s
12.3
753,980
1,883
755,863

Task State


Instruction

Create a comprehensive daily itinerary overview page to help organize my Japan travel plans. I need you to create a new page called 'Daily Itinerary Overview' as a child of the main Japan Travel Planner page.

Task Requirements:

  1. Create a new page titled 'Daily Itinerary Overview' as a child page of the main Japan Travel Planner page
  2. Query the Travel Itinerary database to retrieve all activities
  3. Structure the page with the following specific format:
    • Add a heading_1 block with text "📅 Daily Itinerary Overview"
    • Add a heading_2 block with text "📊 Trip Summary"
    • Under Trip Summary, add a paragraph listing the total number of visited activities
    • Create heading_2 blocks for "🌅 Day 1", "🌆 Day 2", and "🌃 Day 3"
    • Under each day heading, list the activities scheduled for that day in to do list
    • Each activity (use To-do list) should show: Activity Name - City (if available), for example, "Osaka Castle - Osaka". Check it if it's visited.
  4. The summary paragraph must contain the exact text "Total activities visited (from Day 1 to Day 3): [NUMBER]" where [NUMBER] is the actual count.
  5. Ensure all headings use the exact emoji and text format specified above


Verify

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


def verify_todo_database_correspondence(all_blocks, activities_by_day, _):
    """
    Verify that to-do items in the overview page correspond exactly to database activities.
    """
    # Extract to-do items organized by day from the overview page
    todos_by_day = {"Day 1": [], "Day 2": [], "Day 3": []}
    current_day = None
    checked_todos_count = 0

    for block in all_blocks:
        block_type = block.get("type")
        block_text = notion_utils.get_block_plain_text(block)

        # Track which day section we're in
        if block_type == "heading_2":
            if "🌅 Day 1" in block_text:
                current_day = "Day 1"
            elif "🌆 Day 2" in block_text:
                current_day = "Day 2"
            elif "🌃 Day 3" in block_text:
                current_day = "Day 3"
            else:
                current_day = None  # Reset for non-day headings

        # Collect to-do items under day headings
        elif block_type == "to_do" and current_day:
            to_do_data = block.get("to_do", {})
            is_checked = to_do_data.get("checked", False)

            if is_checked:
                checked_todos_count += 1

            todos_by_day[current_day].append(
                {"text": block_text, "checked": is_checked}
            )

    # Verify each day's activities match
    for day in ["Day 1", "Day 2", "Day 3"]:
        db_activities = activities_by_day[day]
        page_todos = todos_by_day[day]

        # Check if counts match
        if len(db_activities) != len(page_todos):
            print(
                f"Error: {day} activity count mismatch. Database has {len(db_activities)} activities, page has {len(page_todos)} to-dos.",
                file=sys.stderr,
            )
            return False

        # Verify each database activity has corresponding to-do
        for db_activity in db_activities:
            expected_format = f"{db_activity['name']}"
            if db_activity["city"]:
                expected_format += f" - {db_activity['city']}"

            # Find matching to-do item
            matching_todo = None
            for todo in page_todos:
                if (
                    expected_format in todo["text"]
                    or db_activity["name"] in todo["text"]
                ):
                    matching_todo = todo
                    break

            if not matching_todo:
                print(
                    f"Error: {day} - Database activity '{expected_format}' not found in to-do list.",
                    file=sys.stderr,
                )
                return False

            # Verify checked status matches visited status
            if db_activity["visited"] != matching_todo["checked"]:
                status_desc = "checked" if db_activity["visited"] else "unchecked"
                actual_desc = "checked" if matching_todo["checked"] else "unchecked"
                print(
                    f"Error: {day} - Activity '{db_activity['name']}' should be {status_desc} but is {actual_desc}.",
                    file=sys.stderr,
                )
                return False

    # Verify summary count matches checked to-dos
    for block in all_blocks:
        if block.get("type") == "paragraph":
            block_text = notion_utils.get_block_plain_text(block)
            if "Total activities visited (from Day 1 to Day 3): 8" in block_text:
                print(
                    f"Success: Daily Itinerary Overview page created with correct structure. All {checked_todos_count} visited activities match database."
                )
                return True

    print(
        f"Error: Summary shows incorrect visited activity count. Expected: {checked_todos_count} (based on checked to-do items)",
        file=sys.stderr,
    )
    return False


def verify(notion: Client, main_id: str = None) -> bool:
    """
    Verifies that the Daily Itinerary Overview page has been created correctly.
    """
    # Find the main Japan Travel Planner page
    page_id = None
    if main_id:
        found_id, object_type = notion_utils.find_page_or_database_by_id(
            notion, main_id
        )
        if found_id and object_type == "page":
            page_id = found_id

    if not page_id:
        page_id = notion_utils.find_page(notion, "Japan Travel Planner")
    if not page_id:
        print("Error: Main 'Japan Travel Planner' page not found.", file=sys.stderr)
        return False

    # Find the Daily Itinerary Overview child page
    overview_page_id = None
    try:
        # Get all child pages of the main page
        response = notion.search(
            query="Daily Itinerary Overview",
            filter={"property": "object", "value": "page"},
        )

        for result in response.get("results", []):
            # Check if this page is a child of the main page
            parent = result.get("parent", {})
            if parent.get("type") == "page_id" and parent.get("page_id") == page_id:
                overview_page_id = result["id"]
                break

        if not overview_page_id:
            # Alternative method: check page title directly
            for result in response.get("results", []):
                title_list = (
                    result.get("properties", {}).get("title", {}).get("title", [])
                )
                for title_obj in title_list:
                    if "Daily Itinerary Overview" in title_obj.get("plain_text", ""):
                        overview_page_id = result["id"]
                        break
                if overview_page_id:
                    break

    except Exception as e:
        print(
            f"Error searching for Daily Itinerary Overview page: {e}", file=sys.stderr
        )
        return False

    if not overview_page_id:
        print(
            "Error: 'Daily Itinerary Overview' page not found as child of main page.",
            file=sys.stderr,
        )
        return False

    # Get all blocks from the overview page
    all_blocks = notion_utils.get_all_blocks_recursively(notion, overview_page_id)

    # Required content to verify - must appear in this exact order
    required_headings_sequence = [
        ("📅 Daily Itinerary Overview", "heading_1"),
        ("📊 Trip Summary", "heading_2"),
        ("🌅 Day 1", "heading_2"),
        ("🌆 Day 2", "heading_2"),
        ("🌃 Day 3", "heading_2"),
    ]

    found_headings_in_order = []
    found_summary = False
    summary_has_correct_format = False
    found_todo_items = False

    # Check each block and track heading sequence
    for block in all_blocks:
        block_text = notion_utils.get_block_plain_text(block)
        block_type = block.get("type")

        # Check for required headings in sequence
        for heading_text, expected_type in required_headings_sequence:
            if heading_text in block_text and block_type == expected_type:
                found_headings_in_order.append((heading_text, expected_type))

        # Check for trip summary paragraph
        if (
            block_type == "paragraph"
            and "Total activities visited (from Day 1 to Day 3):" in block_text
        ):
            found_summary = True
            # Check if the format is correct (contains a number)
            if re.search(
                r"Total activities visited \(from Day 1 to Day 3\):\s*\d+", block_text
            ):
                summary_has_correct_format = True

        # Check for to-do list items (activities under day headings)
        if block_type == "to_do":
            found_todo_items = True
            # Check if to-do items follow the format "Activity Name - City"
            if " - " in block_text:
                # Format appears to be correct (contains dash separator)
                pass

    # Verify all required headings are found in correct sequence
    if len(found_headings_in_order) != len(required_headings_sequence):
        missing_headings = []
        for heading_text, heading_type in required_headings_sequence:
            if (heading_text, heading_type) not in found_headings_in_order:
                missing_headings.append(f"{heading_text} ({heading_type})")
        print(f"Error: Missing required headings: {missing_headings}", file=sys.stderr)
        return False

    # Verify headings appear in correct order
    for i, (found_heading, found_type) in enumerate(found_headings_in_order):
        expected_heading, expected_type = required_headings_sequence[i]
        if found_heading != expected_heading or found_type != expected_type:
            print(
                f"Error: Headings not in correct order. Expected '{expected_heading}' ({expected_type}) at position {i + 1}, but found '{found_heading}' ({found_type})",
                file=sys.stderr,
            )
            return False

    # Verify trip summary exists and has correct format
    if not found_summary:
        print(
            "Error: Trip summary paragraph with 'Total activities visite' not found.",
            file=sys.stderr,
        )
        return False

    if not summary_has_correct_format:
        print(
            "Error: Trip summary does not have correct format 'Total activities visited: [NUMBER]'.",
            file=sys.stderr,
        )
        return False

    # Verify to-do list items exist (activities should be in to-do format)
    if not found_todo_items:
        print(
            "Error: No to-do list items found. Activities should be listed as to-do items under day headings.",
            file=sys.stderr,
        )
        return False

    # Additional verification: Check if Travel Itinerary database exists and has data
    try:
        itinerary_db_id = notion_utils.find_database_in_block(
            notion, page_id, "Travel Itinerary"
        )
        if not itinerary_db_id:
            itinerary_db_id = notion_utils.find_database(notion, "Travel Itinerary")

        if itinerary_db_id:
            # Query the database to get all activities
            db_response = notion.databases.query(database_id=itinerary_db_id)
            db_activities = db_response.get("results", [])

            # Organize database activities by day
            activities_by_day = {"Day 1": [], "Day 2": [], "Day 3": []}
            visited_count = 0

            for result in db_activities:
                properties = result.get("properties", {})

                # Extract activity info
                activity_info = {"name": "", "city": "", "visited": False, "day": None}

                for prop_name, prop_value in properties.items():
                    prop_type = prop_value.get("type")

                    # Get activity name (usually from title property)
                    if prop_type == "title" and prop_value.get("title"):
                        activity_info["name"] = prop_value["title"][0]["plain_text"]

                    # Get city info
                    elif "city" in prop_name.lower() and prop_type in [
                        "rich_text",
                        "select",
                    ]:
                        if prop_type == "rich_text" and prop_value.get("rich_text"):
                            activity_info["city"] = prop_value["rich_text"][0][
                                "plain_text"
                            ]
                        elif prop_type == "select" and prop_value.get("select"):
                            activity_info["city"] = prop_value["select"]["name"]

                    # Get visited status
                    elif prop_type == "checkbox":
                        if prop_value.get("checkbox"):
                            activity_info["visited"] = True
                            visited_count += 1

                    # Get day info
                    elif "day" in prop_name.lower() and prop_type in [
                        "select",
                        "rich_text",
                    ]:
                        if prop_type == "select" and prop_value.get("select"):
                            day_value = prop_value["select"]["name"]
                            if day_value in activities_by_day:
                                activity_info["day"] = day_value
                        elif prop_type == "rich_text" and prop_value.get("rich_text"):
                            day_value = prop_value["rich_text"][0]["plain_text"]
                            if day_value in activities_by_day:
                                activity_info["day"] = day_value

                # Add to appropriate day if day is specified
                if activity_info["day"] and activity_info["name"]:
                    activities_by_day[activity_info["day"]].append(activity_info)

            # Now verify to-do items match database activities
            return verify_todo_database_correspondence(
                all_blocks, activities_by_day, visited_count
            )
        else:
            print(
                "Warning: Travel Itinerary database not found, using to-do items for count verification."
            )
            # Count checked to-do items in the overview page even without database
            checked_todos_count = 0
            for block in all_blocks:
                if block.get("type") == "to_do":
                    to_do_data = block.get("to_do", {})
                    if to_do_data.get("checked", False):
                        checked_todos_count += 1

            # Verify the summary shows the correct visited count based on checked to-dos
            for block in all_blocks:
                if block.get("type") == "paragraph":
                    block_text = notion_utils.get_block_plain_text(block)
                    if f"Total activities visited: {checked_todos_count}" in block_text:
                        print(
                            f"Success: Daily Itinerary Overview page created with correct structure and {checked_todos_count} visited activities."
                        )
                        return True

            print(
                f"Error: Summary shows incorrect visited activity count. Expected: {checked_todos_count} (based on checked to-do items)",
                file=sys.stderr,
            )
            return False

    except Exception as e:
        print(f"Warning: Could not verify activity count: {e}")
        print("Success: Daily Itinerary Overview page created with correct structure.")
        return True


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):
        sys.exit(0)
    else:
        sys.exit(1)


if __name__ == "__main__":
    main()