Trip Planner MiniApp - Complete Documentation¶
Version: 2.0.0 Last Updated: December 3, 2025 Status: Production Ready ✅
Table of Contents¶
- Overview
- Architecture
- Features
- Use Cases
- API Reference
- Data Schema
- Routing Logic
- Test Coverage
- Future Enhancements
- Known Limitations
Overview¶
The Trip Planner MiniApp is an intelligent travel planning assistant that stores rich venue data and enables conversational queries during travel. It integrates seamlessly with the Archety orchestrator, supporting both 1:1 and group chat contexts.
Key Capabilities¶
- Venue Management: Add, track, and rate venues with rich metadata
- Natural Language Queries: "What's good for dinner in Jing'an?"
- Visit Tracking: Mark venues as visited with optional ratings and notes
- Smart Filtering: Filter by category, district, time of day, or visit status
- Hallucination Prevention: Only returns user-provided information, never invents data
Architecture¶
Component Diagram¶
┌─────────────────────────────────────────────────────────────┐
│ TwoStageHandler │
│ (two_stage_handler.py) │
└─────────────────────┬────────────────────────────────────────┘
│ _route_to_miniapp_if_applicable()
▼
┌─────────────────────────────────────────────────────────────┐
│ MiniApp Routing Layer │
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Detector │ │ SessionManager │ │ Registry │ │
│ │ (triggers) │ │ (persistence) │ │ (handlers) │ │
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ TripPlannerHandler │
│ (trip_planner.py - 784 lines) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Commands │ │
│ │ • set_destination • add_venue • mark_visited │ │
│ │ • query_venues • filter_by_district │ │
│ │ • get_must_try • show_unvisited • list_all │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Data Models │ │
│ │ • Venue (dataclass with 15+ fields) │ │
│ │ • TripState (destination, dates, venues, districts)│ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key Files¶
| File | Purpose |
|---|---|
app/miniapps/handlers/trip_planner.py |
Main handler (784 lines) |
app/miniapps/routing/session_manager.py |
Session persistence |
app/miniapps/routing/registry.py |
Handler registration |
app/miniapps/routing/base_handler.py |
Base classes & context |
app/orchestrator/miniapp_detector.py |
Trigger detection |
app/orchestrator/two_stage_handler.py |
Main routing (lines 1530-1750) |
Features¶
1. Venue Management¶
Adding Venues:
User: "add Yaya's Pasta Bar - trendy Italian spot in Jing'an"
Sage: "Added 🍴 Yaya's Pasta Bar in Jing'an"
Bulk Import:
User: "Jing'an District:
Yaya's Pasta Bar - 329 Tongren Road - trendy Italian
Bastard - No. 102, Lane 30 - neon-lit fusion"
Sage: "Added 2 venues to your Shanghai trip!
Jing'an: 2"
2. Smart Queries¶
| Query Type | Example | Result |
|---|---|---|
| Time-based | "what's good for dinner?" | Restaurants + dinner venues |
| Category | "any good bars?" | Bar-category venues |
| District | "in Jing'an" | Venues in that district |
| Unvisited | "what haven't I done?" | Planned venues only |
| Must-try | "what should I order at Yaya's?" | Saved must-try items |
3. Visit Tracking¶
User: "visited Yaya's, loved it, 5 stars"
Sage: "Marked Yaya's Pasta Bar as visited! ⭐⭐⭐⭐⭐
'loved it'
3/10 spots complete."
4. Escape Patterns (Group Chat Support)¶
When an active trip planning session exists, users can still get normal Sage responses:
User: "hey sage, how are you?" → Routes to GPT-5, not trip planner
User: "what's the weather?" → Routes to GPT-5
User: "@sage what time is it?" → Routes to GPT-5
Use Cases¶
Primary Use Case: Pre-Trip Research¶
- User discovers trip is being planned: "trip to Shanghai"
- User pastes venue research from social media/blogs
- Handler extracts venues with districts, categories, must-try items
- User queries: "dinner spots in Xuhui?"
- Handler returns filtered, organized results
Secondary Use Case: On-Trip Tracking¶
- User at venue: "visited Yaya's, amazing food, 5 stars"
- Handler updates status, records rating
- User later: "what haven't I done in Jing'an?"
- Handler shows remaining planned venues
Group Chat Use Case¶
- Group planning trip together
- Multiple users add venues
- Query: "what do we still need to visit?"
- Non-trip messages ("hey how's everyone?") bypass handler
API Reference¶
MiniAppContext (Input)¶
@dataclass
class MiniAppContext:
message: str # User's message text
sender: str # User phone number
chat_guid: str # Chat identifier
timestamp: datetime # Message timestamp
is_group: bool # Group vs 1:1 chat
user_id: str # User identifier
session_id: str # Active session ID
session_state: str # Current state (initial, active, etc.)
session_data: Dict # Persisted trip data
assistant_id: str # Persona (sage/echo)
participants: List[str] # Group participants (optional)
MiniAppResponse (Output)¶
@dataclass
class MiniAppResponse:
message: str = "" # Response text
bubbles: List[str] = [] # Multiple messages
state_data_updates: Dict = None # Data to persist
new_state: str = None # State transition
end_session: bool = False # Close session?
end_reason: str = None # Why ended
ui_update: bool = False # Refresh UI?
Handler Commands¶
| Command Pattern | Method | Description |
|---|---|---|
trip to {destination} |
_set_destination() |
Start trip planning |
add {venue} |
_add_single_venue() |
Add one venue |
| Multi-line text | _extract_and_add_venues() |
Bulk import |
visited {venue} |
_mark_visited() |
Mark as visited |
what's good for {time}? |
_query_venues() |
Time-based query |
in {district} |
_filter_by_district() |
District filter |
what should I order at {venue}? |
_get_must_try() |
Get recommendations |
what haven't I visited? |
_show_unvisited() |
Show planned venues |
list |
_list_all() |
Show all venues |
help |
_show_help() |
Show help |
Data Schema¶
Venue Dataclass¶
@dataclass
class Venue:
id: str # Unique ID (v_xxxxxxxx)
name: str # Venue name
district: str = "" # Jing'an, Xuhui, etc.
address: str = "" # Street address
category: str = "restaurant" # restaurant, bar, cafe, shop, landmark, hotel, activity
vibe: List[str] = [] # trendy, cozy, neon, etc.
must_try: List[str] = [] # Specific dishes/items
best_for: List[str] = [] # morning, dinner, photos, etc.
source_note: str = "" # "From XHS", etc.
status: str = "planned" # planned, visited, skipped
visited_at: str = None # ISO timestamp
user_rating: int = None # 1-5 stars
user_notes: str = "" # User comments
added_at: str = "" # ISO timestamp
TripState Dataclass¶
@dataclass
class TripState:
destination: str = "" # "Shanghai"
dates: Dict[str, str] = {} # {start: "2024-01-01", end: "2024-01-07"}
venues: List[Venue] = [] # All saved venues
districts: List[str] = [] # Auto-populated from venues
Session Persistence¶
Sessions are stored in PostgreSQL via MiniAppSession model:
class MiniAppSession(Base):
id: UUID # Primary key
chat_guid: str # Chat identifier
user_id: UUID # User foreign key
mini_app_id: str # "trip_planner"
assistant_id: str # "sage"
state: str # "initial", "active"
state_data: JSON # TripState as dict
started_at: datetime
last_activity: datetime
ended_at: datetime # NULL if active
end_reason: str # "completed", "timeout", etc.
message_count: int
turn_count: int
Routing Logic¶
Message Flow¶
┌──────────────────────────────────────────────────────────────┐
│ Incoming Message │
└──────────────────────────────┬───────────────────────────────┘
▼
┌──────────────────┐
│ Escape Pattern? │ "hey sage", "@sage", "weather"
└────────┬─────────┘
│
┌────────────────┴────────────────┐
│ Yes │ No
▼ ▼
┌───────────────┐ ┌──────────────────┐
│ Route to GPT-5│ │ Check Trigger │
└───────────────┘ └────────┬─────────┘
│
┌───────────────────┴───────────────────┐
│ Triggered? │ No
│ (keywords: trip, venue, planning) │
▼ ▼
┌───────────────┐ ┌──────────────────┐
│ Create/Update │ │ Active Session? │
│ Session │ └────────┬─────────┘
└───────┬───────┘ │
│ ┌───────────┴───────────┐
│ │ Yes │ No
│ ▼ ▼
│ ┌─────────────────┐ ┌───────────────┐
│ │ Message Relevant│ │ Route to GPT-5│
│ │ to Active App? │ └───────────────┘
│ └────────┬────────┘
│ │
│ ┌───────────┴───────────┐
│ │ Yes │ No
│ ▼ ▼
│ ┌───────────────┐ ┌───────────────┐
│ │ Route to │ │ Route to GPT-5│
│ │ Handler │ └───────────────┘
│ └───────┬───────┘
│ │
└───────────┼───────────────────────────────┐
▼ │
┌─────────────────┐ │
│ Handler.handle()│ │
└────────┬────────┘ │
▼ │
┌─────────────────┐ │
│ Update Session │ │
│ Send Response │ │
└─────────────────┘ │
Relevance Check Keywords¶
For trip_planner:
- Actions: add, remove, delete, visited, skip, list, show
- Queries: what's good, what should i, where, dinner, lunch, breakfast
- Categories: coffee, drinks, bar, restaurant, cafe, food, eat
- Trip-related: venue, place, spot, district, neighborhood, area
- Rating: rating, stars, rate, loved, hated, amazing
Test Coverage¶
Test File: tests/test_trip_planner_enhanced.py¶
Test Classes:
| Class | Tests | Purpose |
|---|---|---|
TestVenueDataclass |
3 | Venue creation, serialization |
TestTripState |
2 | TripState creation, round-trip |
TestHandlerCommands |
12 | All command handlers |
TestVenueExtraction |
5 | Natural language parsing |
TestUIConfig |
3 | UI configuration generation |
TestEdgeCases |
4 | Error handling |
TestHallucinationPrevention |
5 | Anti-hallucination |
Total: 34 tests
Hallucination Prevention Tests¶
class TestHallucinationPrevention:
"""
Critical tests ensuring the handler NEVER invents:
- Menu items or dish names
- Addresses or locations
- Ratings or reviews
- Opening hours or prices
"""
test_no_hallucinated_dishes_for_empty_must_try # ✅
test_no_hallucinated_venues_in_queries # ✅
test_only_returns_user_provided_must_try # ✅
test_nonexistent_venue_query_fails_gracefully # ✅
test_district_filter_no_hallucination # ✅
Running Tests¶
# All trip planner tests
python -m pytest tests/test_trip_planner_enhanced.py -v
# Hallucination tests only
python -m pytest tests/test_trip_planner_enhanced.py::TestHallucinationPrevention -v
# With coverage
python -m pytest tests/test_trip_planner_enhanced.py --cov=app/miniapps/handlers/trip_planner
Completed Features (December 2025)¶
The following features have been fully implemented:
Real-Time Intelligence ✅¶
- Google Places Integration: Venue validation, geocoding, place details
- Perplexity Integration: AI-powered venue descriptions and recommendations
- "Is it open?" queries: Real-time opening status via
_check_venue_open() - Walking time calculations: Distance between venues via
get_walking_time()
Collaborative Trip Planning ✅¶
- Group Session Scoping: Multiple users can contribute to the same trip in group chats
- User Attribution: Every venue tracks
created_by_user_idandcreated_by_name - Maps Links: Auto-generated Google Maps URLs for each venue
Location Features ✅¶
- Full Geocoding: Venues get coordinates and place_id via Google Places
- District Extraction: Auto-populated from Places API
- Enrichment Data: Opening hours, ratings, price level, phone, website
Future Enhancements¶
Medium Priority¶
- Photo Memory Integration
- Link photo memories to venues
- "Show me photos from Yaya's"
-
Auto-tag venues from photo analysis
-
Visual Map UI
- Interactive map with venue pins
- Cluster by district
-
Walking route visualization
-
Time-Based Itinerary
- "Plan my Tuesday"
- Auto-schedule based on best_for tags
-
Opening hours-aware scheduling
-
Export/Share
- Export as PDF/spreadsheet
- Share trip with non-users
- Import from Google Maps lists
Low Priority¶
- Venue Voting
- Multiple users vote on venues
-
Ranked preference for dinner spots
-
Reservation Integration
- OpenTable/Resy API integration
- "Book a table at Yaya's for Thursday"
Known Limitations¶
Current Limitations¶
- Location Context Not Available
- "What's near me?" requires client to send location
-
iOS app change needed to pass user coordinates
-
Limited Parsing Edge Cases
- Complex multi-venue formats may fail
-
Unusual address formats not always recognized
-
Proactive Suggestions Not Implemented
- "It's 7 PM, want dinner?" style triggers not built
- Requires background trigger infrastructure
Design Decisions¶
- Why No LLM for Responses?
- Handler uses rule-based responses for speed
- Prevents hallucination of venue details
-
Consistent, predictable output
-
Why Session-Based?
- Maintains context across messages
- Enables stateful operations (add → query → visit)
-
Supports group coordination
-
Why Escape Patterns?
- Group chats need normal Sage functionality
- Not all messages are trip-related
- Better user experience
Appendix: Code Quality Audit¶
Reviewed Files (November 30, 2025)¶
| File | Lines | Bugs Found | Notes |
|---|---|---|---|
trip_planner.py |
784 | 0 | Well-structured, handles edge cases |
session_manager.py |
314 | 0 | Proper cleanup, UUID handling |
two_stage_handler.py (routing) |
~220 | 0 | Fixed UUID issue, proper fallback |
Key Fixes Applied¶
- UUID Mismatch Bug (Fixed)
SessionManager.create_session()expected UUID- Was receiving phone-derived string
- Fix: Look up actual user UUID from database
-
Fallback: UUID5 deterministic generation
-
Authentication Issue (Fixed)
- Was using HMAC auth (X-Signature headers)
- Endpoint uses Bearer token auth
-
Fix: Use
Authorization: Bearer {secret} -
Group Chat Support (Implemented)
- Escape patterns bypass miniapp
- Relevance checking for active sessions
- Non-relevant messages route to GPT-5
Documentation generated by Claude Code | Last tested: All 34 tests passing