Restaurant Expenses Sync

L3
ModelContextProtocolNotionJapan Travel Planner

Find restaurants from Day 1 Travel Itinerary and create corresponding entries in the Expenses database.

Created by Xiangyan Liu
2025-07-27
Conditional FilteringDatabase ManipulationCross Reference LinkingTemplate Population

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-medium
4
/4
256.9s
4.5
162,801
10,453
173,254
OpenAI
gpt-5-high
2
/4
515.6s
6.8
238,535
23,264
261,799
OpenAI
o3
2
/4
138.7s
9.3
193,726
4,375
198,101
Gemini
gemini-2-5-flash
1
/4
21.2s
6.0
48,456
2,401
50,857
OpenAI
gpt-5-low
1
/4
240.5s
4.3
130,476
10,424
140,900
OpenAI
gpt-5-mini-high
1
/4
181.1s
7.5
515,148
12,342
527,489
OpenAI
gpt-5-mini-low
1
/4
36.4s
6.0
54,838
2,166
57,004
OpenAI
gpt-5-mini-medium
1
/4
93.1s
6.5
525,163
5,844
531,007
OpenAI
o4-mini
1
/4
247.7s
9.5
77,787
5,166
82,953
Qwen
qwen-3-coder-plus
1
/4
44.8s
12.8
224,481
1,598
226,079
Qwen
qwen-3-max
1
/4
51.6s
11.8
194,778
1,272
196,050
Claude
claude-opus-4-1
0
/1
--
125.1s
10.0
255,917
2,431
258,348
Claude
claude-sonnet-4
0
/4
109.0s
11.8
173,033
2,716
175,749
Claude
claude-sonnet-4-high
0
/4
148.3s
11.3
1,916,913
2,395
1,919,308
Claude
claude-sonnet-4-low
0
/4
218.4s
10.3
1,363,680
2,860
1,366,540
DeepSeek
deepseek-chat
0
/4
141.6s
12.0
165,474
1,439
166,912
Gemini
gemini-2-5-pro
0
/4
135.3s
8.5
208,697
12,496
221,193
Z.ai
glm-4-5
0
/4
129.9s
13.3
203,446
2,823
206,269
OpenAI
gpt-4-1
0
/4
52.8s
6.8
217,696
1,551
219,247
OpenAI
gpt-4-1-mini
0
/4
39.8s
9.8
126,768
809
127,578
OpenAI
gpt-4-1-nano
0
/4
33.1s
13.3
121,508
1,089
122,597
OpenAI
gpt-5-nano-high
0
/4
304.5s
7.5
663,313
63,019
726,332
OpenAI
gpt-5-nano-low
0
/4
49.7s
4.8
92,086
7,603
99,690
OpenAI
gpt-5-nano-medium
0
/4
180.8s
7.5
339,573
31,212
370,786
OpenAI
gpt-oss-120b
0
/4
82.6s
9.0
247,233
3,166
250,399
Grok
grok-4
0
/4
271.4s
18.8
593,746
8,414
602,160
Grok
grok-code-fast-1
0
/4
214.8s
11.0
335,453
3,620
341,805
MoonshotAI
kimi-k2-0711
0
/4
96.7s
10.3
115,704
2,756
118,460
MoonshotAI
kimi-k2-0905
0
/4
387.7s
34.0
708,702
4,062
712,764

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

Please find the restaurants that appear in Day 1 of the Travel Itinerary database, then create corresponding entries in the Expenses database, one restaurant per entry. Set the date uniformly to Jan 1, 2025, and the cost uniformly to $120. Display the restaurant name in the Expense field. Set Category to Dining. For Comment, use the Description from the corresponding restaurant page. Leave other properties empty.



Verify

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


def verify(notion: Client, main_id: str = None) -> bool:
    """
    Verifies that restaurants from Day 1 of Travel Itinerary have corresponding expense entries.
    """
    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: Page 'Japan Travel Planner' not found.", file=sys.stderr)
        return False

    # Find Travel Itinerary database
    itinerary_db_id = notion_utils.find_database_in_block(
        notion, page_id, "Travel Itinerary"
    )
    if not itinerary_db_id:
        print("Error: Database 'Travel Itinerary' not found.", file=sys.stderr)
        return False

    # Find Expenses database
    expenses_db_id = notion_utils.find_database_in_block(notion, page_id, "Expenses")
    if not expenses_db_id:
        print("Error: Database 'Expenses' not found.", file=sys.stderr)
        return False

    # Find Japan Places to Visit database
    places_db_id = notion_utils.find_database_in_block(
        notion, page_id, "Travel Itinerary"
    )
    if not places_db_id:
        print("Error: Database 'Japan Places to Visit' not found.", file=sys.stderr)
        return False

    # Query Day 1 restaurants from Travel Itinerary
    try:
        itinerary_results = notion.databases.query(
            database_id=itinerary_db_id,
            filter={
                "and": [
                    {"property": "Day", "select": {"equals": "Day 1"}},
                    {"property": "Type", "multi_select": {"contains": "Food"}},
                ]
            },
        ).get("results", [])
    except Exception as e:
        print(f"Error querying Travel Itinerary database: {e}", file=sys.stderr)
        return False

    if not itinerary_results:
        print(
            "Error: No restaurants found for Day 1 in Travel Itinerary.",
            file=sys.stderr,
        )
        return False

    # Extract restaurant names
    restaurant_names = []
    for entry in itinerary_results:
        props = entry.get("properties", {})
        name_prop = props.get("Name", {})
        name_text = "".join(t.get("plain_text", "") for t in name_prop.get("title", []))
        if name_text:
            restaurant_names.append(name_text.strip())

    if not restaurant_names:
        print("Error: No restaurant names found in Day 1 entries.", file=sys.stderr)
        return False

    # Get descriptions from Japan Places to Visit database
    try:
        places_results = notion.databases.query(database_id=places_db_id).get(
            "results", []
        )
    except Exception as e:
        print(f"Error querying Japan Places to Visit database: {e}", file=sys.stderr)
        return False

    # Create a map of restaurant names to descriptions
    restaurant_descriptions = {}
    for place in places_results:
        props = place.get("properties", {})
        name_prop = props.get("Name", {})
        name_text = "".join(t.get("plain_text", "") for t in name_prop.get("title", []))

        desc_prop = props.get("Description", {})
        desc_text = "".join(
            t.get("plain_text", "") for t in desc_prop.get("rich_text", [])
        )

        if name_text and desc_text:
            restaurant_descriptions[name_text.strip()] = desc_text.strip()

    # Query Expenses database
    try:
        expenses_results = notion.databases.query(database_id=expenses_db_id).get(
            "results", []
        )
    except Exception as e:
        print(f"Error querying Expenses database: {e}", file=sys.stderr)
        return False

    # Verify each restaurant has a corresponding expense entry
    verified_restaurants = []
    for restaurant_name in restaurant_names:
        found_matching_expense = False
        expected_description = restaurant_descriptions.get(restaurant_name, "")

        for expense in expenses_results:
            props = expense.get("properties", {})

            # Check Expense field (title)
            expense_prop = props.get("Expense", {})
            expense_text = "".join(
                t.get("plain_text", "") for t in expense_prop.get("title", [])
            )
            if expense_text.strip() != restaurant_name:
                continue

            # Check Date
            date_prop = props.get("Date", {})
            date_start = date_prop.get("date", {}).get("start")
            if date_start != "2025-01-01":
                continue

            # Check Transaction Amount
            amount_prop = props.get("Transaction Amount", {})
            amount = amount_prop.get("number")
            if amount != 120:
                continue

            # Check Category contains Dining
            category_prop = props.get("Category", {})
            categories = [c.get("name") for c in category_prop.get("multi_select", [])]
            if "Dining" not in categories:
                continue

            # Check Comment matches description (if description exists)
            if expected_description:
                comment_prop = props.get("Comment", {})
                comment_text = "".join(
                    t.get("plain_text", "") for t in comment_prop.get("rich_text", [])
                )
                if comment_text.strip().replace(
                    "\u202f", " "
                ) != expected_description.replace("\u202f", " "):
                    continue

            found_matching_expense = True
            verified_restaurants.append(restaurant_name)
            break

        if not found_matching_expense:
            print(
                f"Error: No matching expense entry found for restaurant '{restaurant_name}'.",
                file=sys.stderr,
            )
            return False

    if len(verified_restaurants) == len(restaurant_names):
        print(
            f"Success: Found matching expense entries for all {len(restaurant_names)} Day 1 restaurants."
        )
        return True
    else:
        print(
            f"Error: Only {len(verified_restaurants)} out of {len(restaurant_names)} restaurants have matching expense entries.",
            file=sys.stderr,
        )
        return False


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