Files
gds-mock-mcp/specs/001-mock-gds-server/data-model.md
Peter.Morton 35a98c6d4b feat: complete implementation plan for Mock GDS MCP server
Phase 0 (Research):
- Selected MCP SDK (@modelcontextprotocol/sdk) for protocol compliance
- Chose ioredis for Valkey client (Redis-compatible)
- Minimal dependencies: Pino logging, native test runner
- Docker buildx bake for multi-platform builds
- Embedded mock data with deterministic generation
- PNR format: TEST-{BASE32} for safety

Phase 1 (Design):
- Data model: 8 core entities (Session, PNR, FlightSegment, etc.)
- MCP contracts: 8 tools with JSON schemas
- Quickstart guide with complete workflow examples
- Constitution compliance verified

Technical stack:
- Node.js 20 LTS
- Valkey 8.0+ for persistence
- Docker containers (amd64/arm64)
- Performance: <2s search, 50+ concurrent sessions

Ready for task generation with /speckit.tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-07 11:27:53 -05:00

395 lines
13 KiB
Markdown

# Data Model: Mock GDS MCP Server
**Branch**: `001-mock-gds-server` | **Date**: 2026-04-07
## Overview
This document defines the core data entities, their relationships, validation rules, and state transitions for the Mock GDS MCP server.
## Core Entities
### 1. Session
Represents an MCP client connection with isolated booking context.
**Fields**:
```typescript
{
id: string; // Unique session identifier (UUID)
createdAt: number; // Unix timestamp (ms)
expiresAt: number; // Unix timestamp (ms), TTL-based
lastActivity: number; // Unix timestamp (ms)
bookingCount: number; // Number of bookings created in session
searchCount: number; // Number of searches performed
}
```
**Storage**: Valkey hash at key `gds:session:{sessionId}`
**Validation**:
- `id` must be valid UUID v4
- `expiresAt` must be > `createdAt`
- Automatic expiry via Valkey TTL (default 1 hour)
**State Transitions**:
```
[Created] → [Active] → [Expired]
[Ended]
```
### 2. Passenger
Represents a traveler in a booking.
**Fields**:
```typescript
{
id: string; // Unique within booking
type: enum; // 'adult' | 'child' | 'infant'
firstName: string; // Required
lastName: string; // Required
dateOfBirth: string; // ISO 8601 date (YYYY-MM-DD), optional
email: string; // Email address, optional
phone: string; // Phone number, optional
frequentFlyerNumber: string; // Airline loyalty number, optional
}
```
**Validation**:
- `firstName`, `lastName` must be 1-50 characters, alphabetic + spaces/hyphens
- `email` must match RFC 5322 pattern if provided
- `phone` must match E.164 pattern if provided
- `type` must be valid enum value
### 3. FlightSegment
Represents a single flight leg in an itinerary.
**Fields**:
```typescript
{
id: string; // Unique segment identifier
flightNumber: string; // e.g., "AA123"
airlineCode: string; // IATA 2-letter code (e.g., "AA")
airlineName: string; // Full airline name
originCode: string; // IATA 3-letter airport code (e.g., "JFK")
originName: string; // Airport name
destinationCode: string; // IATA 3-letter airport code
destinationName: string; // Airport name
departureTime: string; // ISO 8601 datetime
arrivalTime: string; // ISO 8601 datetime
duration: number; // Minutes
aircraftType: string; // e.g., "Boeing 737-800"
cabin: enum; // 'economy' | 'premium_economy' | 'business' | 'first'
price: number; // USD cents (e.g., 29900 = $299.00)
seatsAvailable: number; // Available seats count
bookingClass: string; // Fare class code (e.g., "Y", "J", "F")
status: enum; // 'available' | 'sold_out' | 'cancelled'
}
```
**Validation**:
- `airlineCode` must be valid IATA 2-letter code
- `originCode`, `destinationCode` must be valid IATA 3-letter codes
- `originCode``destinationCode`
- `departureTime` < `arrivalTime`
- `duration` must match calculated time difference
- `seatsAvailable` must be >= 0
- `price` must be > 0
**Business Rules**:
- Flight times must be realistic for route (e.g., JFK→LAX ~6 hours)
- Prices scale with distance and cabin class
- sold_out status when seatsAvailable = 0
### 4. HotelReservation
Represents a hotel booking segment.
**Fields**:
```typescript
{
id: string; // Unique reservation identifier
hotelCode: string; // Internal hotel identifier
hotelName: string; // Hotel property name
chainCode: string; // Hotel chain code (e.g., "MAR" for Marriott)
chainName: string; // Hotel chain name
address: string; // Full address
cityCode: string; // IATA city code (e.g., "LAX")
cityName: string; // City name
checkInDate: string; // ISO 8601 date (YYYY-MM-DD)
checkOutDate: string; // ISO 8601 date (YYYY-MM-DD)
nights: number; // Calculated night count
roomType: string; // e.g., "Standard King", "Deluxe Suite"
rateCode: string; // Rate plan code
starRating: number; // 1-5 stars
price: number; // USD cents, total for stay
pricePerNight: number; // USD cents
guestCount: number; // Number of guests
amenities: string[]; // List of amenities
status: enum; // 'available' | 'sold_out' | 'confirmed' | 'cancelled'
}
```
**Validation**:
- `checkInDate` < `checkOutDate`
- `nights` must equal date difference
- `starRating` must be 1-5
- `guestCount` must be >= 1
- `price` must equal `pricePerNight` * `nights`
**Business Rules**:
- Check-in date must not be in the past
- Minimum 1 night stay
- Prices vary by star rating and city
### 5. CarRental
Represents a car rental segment.
**Fields**:
```typescript
{
id: string; // Unique rental identifier
companyCode: string; // Rental company code (e.g., "ZE" for Hertz)
companyName: string; // Company name
pickupLocationCode: string; // Airport code or location ID
pickupLocationName: string; // Location name
dropoffLocationCode: string; // Airport code or location ID
dropoffLocationName: string; // Location name
pickupDate: string; // ISO 8601 datetime
dropoffDate: string; // ISO 8601 datetime
vehicleClass: enum; // 'economy' | 'compact' | 'midsize' | 'fullsize' | 'suv' | 'luxury'
vehicleModel: string; // e.g., "Toyota Camry or similar"
dailyRate: number; // USD cents per day
totalPrice: number; // USD cents
rentalDays: number; // Number of days
mileagePolicy: enum; // 'unlimited' | 'limited'
insuranceIncluded: boolean;
status: enum; // 'available' | 'confirmed' | 'cancelled'
}
```
**Validation**:
- `pickupDate` < `dropoffDate`
- `rentalDays` must match date calculation
- `totalPrice` must equal `dailyRate` * `rentalDays`
- `pickupLocationCode` should match airport/city code for traveler's destination
**Business Rules**:
- Same-location dropoff preferred (one-way rentals add surcharge)
- Pickup date should align with flight arrival
- Dropoff date should align with departure flight
### 6. PNR (Passenger Name Record)
Represents a complete booking with multiple service segments.
**Fields**:
```typescript
{
pnr: string; // Unique booking reference (format: TEST-{BASE32})
sessionId: string; // Session that created the booking
createdAt: number; // Unix timestamp (ms)
lastModified: number; // Unix timestamp (ms)
status: enum; // 'pending' | 'confirmed' | 'cancelled'
passengers: Passenger[]; // Array of passengers
flights: FlightSegment[]; // Array of flight segments
hotels: HotelReservation[]; // Array of hotel bookings
cars: CarRental[]; // Array of car rentals
totalPrice: number; // USD cents, sum of all segments
currency: string; // Always "USD" for mock data
contactEmail: string; // Primary contact email
contactPhone: string; // Primary contact phone
}
```
**Storage**: Valkey hash at key `gds:session:{sessionId}:booking:{pnr}`
**Validation**:
- `pnr` must match format `TEST-[A-Z0-9]{6}`
- Must have at least one passenger
- Must have at least one service (flight, hotel, or car)
- `totalPrice` must equal sum of all segment prices
- `contactEmail` or `contactPhone` required (at least one)
**State Transitions**:
```
[Pending] → [Confirmed] → [Cancelled]
[Modified] → [Confirmed]
```
**Business Rules**:
- Cannot modify after cancellation
- Hotel dates must overlap with flight dates
- Car pickup should align with flight arrival
- Multi-city bookings require connecting flights
### 7. SearchQuery
Represents a search request (flights, hotels, or cars).
**Fields**:
```typescript
{
id: string; // Unique search identifier
sessionId: string; // Session performing search
type: enum; // 'flight' | 'hotel' | 'car'
timestamp: number; // Unix timestamp (ms)
parameters: object; // Type-specific search params
resultCount: number; // Number of results returned
responseTime: number; // Milliseconds to generate results
}
```
**Storage**: Ephemeral (not persisted), tracked for statistics only
### 8. MockDataRecord
Represents a static mock data entry (airports, airlines, hotels, etc.).
**Fields**:
```typescript
{
type: enum; // 'airport' | 'airline' | 'hotel' | 'car_company'
code: string; // IATA/ICAO code or internal ID
name: string; // Full name
metadata: object; // Type-specific data (coordinates, address, etc.)
}
```
**Storage**: In-memory JavaScript modules, not in Valkey
## Relationships
### Session ↔ PNR
- **Type**: One-to-Many
- **Description**: A session can create multiple bookings
- **Key**: `sessionId` in PNR references Session
- **Cascade**: PNRs remain accessible after session expires (for retrieval)
### PNR ↔ Passengers
- **Type**: One-to-Many
- **Description**: A booking contains multiple passengers
- **Embedded**: Passengers stored within PNR document
- **Constraint**: Minimum 1 passenger per PNR
### PNR ↔ FlightSegments
- **Type**: One-to-Many
- **Description**: A booking can include multiple flight legs
- **Embedded**: Flights stored within PNR document
- **Ordering**: Flights ordered chronologically
### PNR ↔ HotelReservations
- **Type**: One-to-Many
- **Description**: A booking can include multiple hotel stays
- **Embedded**: Hotels stored within PNR document
### PNR ↔ CarRentals
- **Type**: One-to-Many
- **Description**: A booking can include multiple car rentals
- **Embedded**: Cars stored within PNR document
## Data Validation Rules
### Cross-Entity Validation
1. **Date Consistency**:
- Hotel check-in must be >= flight arrival date
- Hotel check-out must be <= return flight departure date
- Car pickup must be >= flight arrival date
- Car dropoff must be <= return flight departure date
2. **Location Consistency**:
- Hotel city should match flight destination
- Car pickup location should match airport or destination city
3. **Passenger Consistency**:
- All segments in a PNR share the same passenger list
- Passenger count must match across segments
4. **Pricing Integrity**:
- PNR total must equal sum of all segment prices
- Segment prices must be positive integers
### Validation Timing
- **Search-time**: Parameter validation (dates, codes, counts)
- **Booking-time**: Business rule validation (date logic, location consistency)
- **Retrieval-time**: PNR format validation
- **Modification-time**: State transition validation (no modification of cancelled bookings)
## Mock Data Generation Rules
### Deterministic Generation
- Same search inputs → same results (when MOCK_DATA_SEED=fixed)
- PNR generation uses session-scoped sequence + timestamp hash
- Flight schedules fixed based on route (JFK→LAX always ~6 hours)
### Realistic Constraints
- Flight prices: $200-$800 domestic economy, $800-$2000 business, $2500+ first
- Hotel prices: $80-$150 budget, $150-$300 midrange, $300-$800 luxury
- Car rental: $35-$50 economy, $50-$80 midsize, $100-$150 luxury
- Flight duration: Calculated from route distance
- Availability: 90% flights available, 10% sold out
### Data Coverage
- **Airports**: ~100 major airports (top 50 US + top 50 international)
- **Airlines**: ~30 major carriers
- **Hotels**: ~50 chains/properties across major cities
- **Car Companies**: ~6 major rental companies
## State Management (Valkey)
### Key Naming Convention
```
gds:session:{sessionId} # Session metadata
gds:session:{sessionId}:booking:{pnr} # Individual booking
gds:session:{sessionId}:bookings # Set of all PNRs in session
gds:session:{sessionId}:searches # List of search IDs
gds:stats:bookings:total # Global booking counter
gds:stats:sessions:active # Set of active session IDs
```
### TTL Strategy
- **Sessions**: 1 hour (configurable via MCP_SESSION_TIMEOUT)
- **Bookings**: No expiry (persist beyond session for retrieval)
- **Search history**: 10 minutes (ephemeral)
### Data Serialization
- **Format**: JSON strings for complex objects
- **Encoding**: UTF-8
- **Compression**: None (mock data is small)
## Performance Considerations
### Memory Footprint
- **Per Session**: ~5KB (metadata only)
- **Per Booking**: ~10-30KB (depends on segment count)
- **Mock Data**: ~2MB (embedded in code, not in Valkey)
### Query Patterns
- **Hot Path**: `GET gds:session:{sessionId}:booking:{pnr}` (booking retrieval)
- **Write Path**: `HSET gds:session:{sessionId}:booking:{pnr}` (booking creation)
- **Cleanup**: `SCAN` + `DEL` for expired sessions (background job)
### Indexing
No secondary indexes required - all queries use primary keys (sessionId, pnr)
## Next Steps
Proceed to contract definition (Phase 1 continued):
- Define MCP tool schemas in `/contracts/mcp-tools.md`
- Create quickstart guide with example usage