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
1
/4
647.8s
10.0
673,432
28,625
702,056
Claude
claude-4-1-opus
0
/1
--
127.2s
5.0
369,809
3,078
372,887
Claude
claude-4-sonnet
0
/4
116.8s
8.0
460,737
4,206
464,942
DeepSeek
deepseek-chat
0
/4
280.5s
11.8
782,394
2,623
785,017
Gemini
gemini-2-5-pro
0
/4
36.0s
1.8
55,665
1,896
57,561
Grok
grok-4
0
/4
-
-
-
-
-
MoonshotAI
k2
0
/4
145.4s
5.0
163,726
1,095
164,821
OpenAI
o3
0
/4
116.7s
6.0
185,878
6,543
192,421
Qwen
qwen-3-coder
0
/4
247.0s
18.8
1,673,351
3,009
1,676,361

Task State


Instruction



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()