Lockey is an AI-powered corporate travel concierge designed to streamline the entire travel lifecycle. From the initial spark of planning to post-trip expense reporting, Lockey acts as a tireless, empathetic 2D mascot that guides travelers through policy compliance, live disruptions, and logistical hurdles.
Built for HackKU, Lockey leverages cutting-edge AI (Google Gemini 1.5 Pro), multimodal vision, and real-time data to ensure corporate travelers stay focused on their work, while the concierge handles the rest.
Lockey emerged as an answer to Lockton Companiesโ search for a solution to the stressful, often bureaucratic nightmare that business travel can become. Many people have stories about missed flights, confusing expense policies, or the frustration of waiting for a manager to approve a last-minute booking. Lockey is our attempt to turn those feared, disconnected tasks into a single, guided conversation with a companion that anticipates what you need before you even ask.
- ๐ค Proactive AI Concierge: A 2D mascot with emotional intelligence, powered by ElevenLabs TTS, that shifts its tone (neutral, excited, empathetic, urgent) based on the travel context.
- ๐บ๏ธ Intelligent Planning: A "Fair Grid" flight search algorithm that finds the best value by scanning multiple airports and dates, coupled with Geo-aware hotel search.
- ๐ก๏ธ Policy & Compliance: Real-time vector search against corporate travel handbooks to ensure every booking is within budget and compliant with visa requirements.
- โก Crisis Management: Real-time disruption handling using MongoDB TimeSeries collections. If a flight is delayed, Lockey automatically finds alternatives and drafts exception requests.
- ๐ธ Multimodal Expenses: Snap a photo of any receipt, and Gemini 1.5 Pro extracts the merchant, amount, and currency, automatically converting it to USD and filing it.
Lockey uses MongoDB Atlas Vector Search to perform Retrieval-Augmented Generation (RAG). Corporate policies and visa requirements are embedded using text-embedding-004 (now gemini-embedding-001) and stored in Atlas. When a user plans a trip, Lockey queries these embeddings to provide instant, context-aware policy summaries.
The Fair Grid algorithm expands the search radius up to 100 miles and explores a 5-day window. It calculates "Saturday-night savings" and presents three distinct bundles (Standard, Savings, and Value-Add) to the traveler.
Flight statuses are tracked in a MongoDB TimeSeries collection, allowing Lockey to monitor for delays or cancellations. When a disruption is detected, a crisis workflow is triggered, leveraging Gemini to synthesize a rebooking strategy and draft communications to managers.
The expense reporting system uses Gemini 1.5 Pro's vision capabilities to process receipts. It identifies PII, extracts financial data, and performs currency conversion in a single pass, ensuring high accuracy even for handwritten or poorly lit receipts.
graph TD
A[Next.js App Router] --> B(Mascot Context / useMascot)
B --> C[ElevenLabs TTS]
A --> D[Gemini 1.5 Pro]
D --> E[Atlas Vector Search]
A --> F[MongoDB Atlas]
F --> G[(TimeSeries: Flight Status)]
F --> H[(GeoJSON: Preferred Vendors)]
A --> I[External APIs]
I --> J[SerpAPI: Google Flights]
I --> K[OpenWeatherMap]
I --> L[Gmail API]
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS 4 |
| Database | MongoDB Atlas (Vector Search, GeoJSON, TimeSeries) |
| AI (Text/Vision) | Google Gemini 1.5 Pro |
| AI (Voice) | ElevenLabs TTS (4 emotional tones) |
| Auth | NextAuth.js + Google OAuth |
| APIs | SerpAPI, OpenWeatherMap, Gmail API |
- Node.js 20+
- MongoDB Atlas Cluster (Free Tier)
- API Keys: Google AI Studio, ElevenLabs, SerpAPI, OpenWeatherMap, Google Cloud Console
-
Clone & Install:
git clone https://github.com/AruneemB/lockey.git cd lockey npm install -
Environment Configuration: Create a
.env.localfile with the following:MONGODB_URI=your_mongodb_uri NEXTAUTH_SECRET=your_secret GOOGLE_CLIENT_ID=your_google_id GOOGLE_CLIENT_SECRET=your_google_secret GEMINI_API_KEY=your_gemini_key ELEVENLABS_API_KEY=your_elevenlabs_key ELEVENLABS_VOICE_ID=your_voice_id SERPAPI_KEY=your_serpapi_key OPENWEATHERMAP_KEY=your_openweathermap_key
-
Database Seeding:
npm run seed # Seeds users, policies, vendors, and visa info npm run create-index # Creates the Atlas Vector Search index
-
Run Dev Server:
npm run dev
- Onboarding: Sign in via Google. Lockey greets you and identifies an expiring passport.
- Planning: Search for a trip (e.g., Milan, Italy). Watch Lockey analyze 100+ flight combinations.
- Compliance: View the policy summary generated via Vector Search.
- Approval: Select a bundle. Lockey drafts a rationale and sends a Gmail approval request.
- Live Mode: Experience a simulated flight delay. Lockey's tone shifts to empathetic/urgent as it provides a rebooking solution.
- Post-Trip: Scan a physical receipt. Watch Gemini extract data and finalize the expense report.
Next.js runs both the frontend and backend in one unified codebase, keeping the app fast and architecturally consistent. The App Router handles React Server Components for data-heavy views, while API route handlers manage all server-side logic โ flight searches, Gemini calls, MongoDB writes โ without requiring a separate backend service. This means every page can fetch fresh data on the server before it reaches the browser, keeping the UI responsive even while AI features are processing in the background. Server Actions are used for form submissions and trip mutations so state stays synchronized without extra client-side fetch logic.
Gemini 1.5 Pro is the reasoning backbone of the entire product. Its large context window allows it to hold the full trip context โ destination, dates, policy findings, selected bundle, and receipt history โ across every workflow without losing coherence. In the planning phase, it parses natural-language input into structured trip parameters and ranks flight bundles against company policy and user constraints, producing readable explanations instead of raw data. During disruptions, it synthesizes a rebooking strategy and drafts manager communications grounded in the actual trip record. In the expense flow, its multimodal vision reads receipts โ including handwritten or poorly lit ones โ extracts merchant, amount, and currency, and flags PII for sanitization. All outputs are tied to structured prompts that include the live trip document, so Gemini always reasons from current state rather than general knowledge.
The embedding model (gemini-embedding-001, formerly text-embedding-004) generates 768-dimensional vectors from corporate policy text, which are stored in MongoDB Atlas and queried at planning time to surface the exact policy excerpt most relevant to the traveler's destination and budget.
MongoDB Atlas stores every trip as a single evolving document that grows richer at each stage. Rather than spreading trip state across relational tables, the trips collection holds the selected flight bundle, hotel, approval thread, rebooking record, and receipts all in one place. This means any API route can load the full trip context in one query and pass it directly to Gemini without joins or aggregation.
Atlas also powers three specialized data patterns used across the app:
- Vector Search: Policy text is embedded and stored in the
policiescollection. At planning time, a cosine similarity search over 768-dimensional vectors retrieves the most relevant policy excerpt for the destination country, enabling grounded RAG without a separate vector database. - GeoJSON &
$geoNear: Preferred hotels are stored with GeoJSON Point coordinates. The hotel search uses a$geoNearaggregation stage to return vendors sorted by distance from the destination office, with a 2dsphere index enabling the spatial query. - TimeSeries Collection: Flight status updates are written to a purpose-built TimeSeries collection with
timeField: "timestamp"andmetaField: { flightNumber, tripId }. This lets Lockey track delay progression over time and trigger the crisis workflow when delay minutes cross a threshold.
users
{
"name": "String (required)",
"email": "String (required, unique)",
"citizenship": "String (ISO 3166-1 alpha-2, e.g. 'US')",
"passport": {
"number": "String",
"expiry": "Date",
"country": "String (ISO alpha-2)"
},
"department": "String",
"managerId": "ObjectId (ref: User, optional)",
"homeAirports": ["String (IATA codes, e.g. 'MCI')"]
}trips
{
"userId": "ObjectId (ref: User)",
"status": "String (enum: draft | pending_approval | approved | rejected | active | archived)",
"destination": {
"city": "String",
"country": "String (ISO alpha-2)",
"officeLat": "Number",
"officeLng": "Number"
},
"dates": {
"departure": "Date",
"return": "Date"
},
"selectedBundle": {
"label": "String (A | B | C)",
"description": "String (Gemini-generated)",
"flight": "Object",
"hotel": "Object",
"totalCostUsd": "Number",
"savingsVsStandard": "Number",
"complianceFlags": ["String"]
},
"flights": ["Mixed"],
"hotels": ["Mixed"],
"receipts": [
{
"merchant": "String",
"category": "String (meal | transport | hotel | other)",
"total": "Number",
"currency": "String (ISO code)",
"originalAmount": "Number",
"date": "Date",
"sanitized": "Boolean",
"extractedByAI": "Boolean",
"imageUrl": "String | null"
}
],
"policyFindings": {
"visa": "VisaRequirement Object",
"hotelNightlyCapUsd": "Number",
"flightCapUsd": "Number",
"mealAllowancePerDayUsd": "Number",
"requiresManagerApproval": "Boolean",
"approvalReason": "String | null",
"mascotSummary": "String"
},
"approvalThread": {
"gmailThreadId": "String | null",
"status": "String (pending | approved | rejected | null)",
"reason": "String | null"
},
"exceptionRequest": {
"subject": "String | null",
"body": "String | null",
"requestedAt": "Date | null",
"status": "String (pending | approved | rejected | null)"
},
"rebooking": {
"originalFlightNumber": "String | null",
"originalCarrier": "String | null",
"originalPriceUsd": "Number | null",
"newFlightNumber": "String | null",
"newCarrier": "String | null",
"newPriceUsd": "Number | null",
"changeFeeUsd": "Number | null",
"overageUsd": "Number | null",
"reason": "String | null",
"rebookedAt": "Date | null"
},
"totalSpendUsd": "Decimal128",
"budgetCapUsd": "Decimal128"
}policies
{
"cityCode": "String (IATA city code, e.g. 'MIL')",
"country": "String (ISO alpha-2)",
"hotelNightlyCapUsd": "Number",
"flightCapUsd": "Number",
"mealAllowancePerDayUsd": "Number",
"preferredTransport": ["String"],
"requiresApprovalAboveUsd": "Number",
"handbookExcerpt": "String (raw text, source for embedding)",
"embedding": ["Number (768-dim vector, gemini-embedding-001)"]
}Atlas Vector Search index on embedding: type vector, dimensions 768, similarity cosine.
preferred_vendors
{
"type": "String (hotel | airline)",
"name": "String",
"city": "String",
"country": "String (ISO alpha-2)",
"location": {
"type": "String ('Point')",
"coordinates": ["Number (GeoJSON: [longitude, latitude])"]
},
"address": "String",
"amenities": {
"freeBreakfast": "Boolean",
"wifi": "Boolean",
"gym": "Boolean",
"parking": "Boolean"
},
"nightlyRateUsd": "Number",
"preferred": "Boolean"
}2dsphere index on location required for $geoNear queries.
visa_requirements
{
"destinationCountry": "String (ISO alpha-2, e.g. 'IT')",
"citizenship": "String (e.g. 'US')",
"visaRequired": "Boolean",
"visaType": "String | null (e.g. 'Schengen 90-day')",
"stayLimitDays": "Number",
"notes": "String",
"applicationUrl": "String | null",
"minApplicationLeadDays": "Number | null"
}Unique compound index on { destinationCountry, citizenship }.
flight_status (TimeSeries)
{
"timestamp": "Date (TimeSeries timeField)",
"metadata": {
"flightNumber": "String (e.g. 'AA2345')",
"tripId": "String | null"
},
"status": "String (on_time | delayed | cancelled)",
"gate": "String | null",
"delayMinutes": "Number",
"destination": "String (IATA code)"
}Created with timeField: 'timestamp', metaField: 'metadata', granularity: 'minutes'.
receipts
{
"tripId": "String",
"merchant": "String",
"category": "String (meal | transport | hotel | other)",
"total": "Decimal128",
"currency": "String (ISO code)",
"originalAmount": "Decimal128",
"date": "Date",
"sanitized": "Boolean",
"extractedByAI": "Boolean",
"imageUrl": "String | null"
}demo_alternatives
{
"destinationCountry": "String (ISO alpha-2)",
"flightNumber": "String",
"carrier": "String",
"origin": "String (IATA)",
"destination": "String (IATA)",
"departureTime": "Date",
"arrivalTime": "Date",
"priceUsd": "Number"
}Static fallback flights used by the crisis rebooking workflow when live search times out.
ElevenLabs handles all voice output and is the layer that makes Lockey feel like a companion rather than a chatbot. Rather than using a single flat voice, Lockey is configured with four emotional tones โ calm for planning, upbeat for successful approvals, empathetic when surfacing compliance issues or disruptions, and urgent when timing or action is required. The active tone is driven by the useMascot context, which tracks the current workflow state and mascot emotion. Each tone is a separately configured voice parameter set sent to the ElevenLabs API, so the same underlying sentence can feel reassuring in one context and pressing in another. This makes information land appropriately without requiring the user to parse emotional cues themselves.
Native Mobile App: A native iOS and Android app brings the full copilot experience into the traveler's pocket, designed for quick, one-handed use while moving through airports or on the road. Push notifications keep users updated on gate changes, check-ins, approvals, and expense deadlines so important details are never missed in real time.
Calendar Integration: Trip plans extend into the user's existing tools through Google Calendar, where confirmed flights, hotel stays, and meetings are automatically synced into a single view. Existing calendar events are also checked to prevent conflicts before anything is booked.
Live Flight Tracking: Each itinerary includes live flight tracking so status updates are always available without switching to external tools.
End-to-End Trip Execution: The system expands to support hotels and conference tickets alongside flights, enabling full travel planning and booking to happen within one continuous conversation instead of across separate tools and steps.
