21 KiB
Demo Session Service
Overview
The Demo Session Service creates ephemeral, isolated demo environments for sales demonstrations and prospect trials. It provisions temporary tenants with pre-seeded realistic bakery data, allowing prospects to explore the full platform without affecting production data. Demo sessions automatically expire after a configurable period (default: 24 hours) and are completely isolated from real customer tenants, making it safe for prospects to experiment freely.
Key Features
Demo Environment Provisioning
- One-Click Demo Creation - Create demo tenant in seconds
- Pre-Seeded Data - Realistic sales, inventory, forecast data
- Isolated Tenants - Complete separation from production
- Temporary Credentials - Auto-generated demo user accounts
- Configurable Duration - 1 hour to 7 days (default: 24 hours)
- Instant Access - No email verification required
Realistic Demo Data
- 90 Days Sales History - Realistic transaction patterns
- Product Catalog - 20+ common bakery products
- Inventory - Current stock levels and movements
- Forecasts - Pre-generated 7-day forecasts
- Production Schedules - Sample production plans
- Suppliers - 5+ sample supplier profiles
- Team Members - Sample staff with different roles
Demo Scenarios
- Standard Bakery - Small neighborhood bakery (1 location)
- Multi-Location - Bakery chain (3 locations)
- High-Volume - Large production bakery
- Custom Scenario - Configurable for specific prospects
- Spanish Locale - Madrid-based bakery examples
- Feature Showcase - Highlight specific capabilities
Session Management
- Auto-Expiration - Automatic cleanup after expiry
- Session Extension - Extend active demos
- Session Termination - Manually end demo
- Session Analytics - Track demo engagement
- Concurrent Limits - Prevent resource abuse
- IP-Based Tracking - Monitor demo usage
Sales Enablement
- Demo Link Generation - Shareable demo URLs
- Sales Dashboard - Track active demos
- Usage Analytics - Feature engagement metrics
- Lead Tracking - Connect demos to CRM
- Conversion Tracking - Demo to trial to paid
- Performance Metrics - Demo success rates
Security & Isolation
- Tenant Isolation - Complete data separation
- Resource Limits - Prevent abuse
- Auto-Cleanup - Remove expired demos
- No Production Access - Isolated database/environment
- Rate Limiting - Prevent demo spam
- Audit Logging - Track all demo activities
Business Value
For Sales Team
- Instant Demos - No setup time, always ready
- Realistic Experience - Prospects see real functionality
- Risk-Free - Prospects can't break anything
- Consistent - Every demo shows same quality data
- Scalable - Handle 100+ concurrent demos
- Self-Service - Prospects can explore independently
Quantifiable Impact
- Sales Cycle: 30-50% shorter with live demos
- Conversion Rate: 2-3× higher vs. screenshots/videos
- Demo Setup Time: 0 minutes vs. 15-30 minutes manual
- Lead Quality: Higher engagement indicates serious interest
- Sales Efficiency: 5-10× more demos per sales rep
- Cost Savings: €500-1,500/month (sales time saved)
For Prospects
- Try Before Buy: Experience platform hands-on
- No Commitment: No credit card, no sign-up friction
- Immediate Access: Start exploring in 30 seconds
- Realistic Data: Understand real-world value
- Self-Paced: Explore at own speed
- Safe Environment: Can't break or affect anything
Technology Stack
- Framework: FastAPI (Python 3.11+) - Async web framework
- Database: PostgreSQL 17 - Demo session tracking
- Demo DB: Separate PostgreSQL - Isolated demo data
- Caching: Redis 7.4 - Session cache, rate limiting
- Messaging: RabbitMQ 4.1 - Cleanup events
- Data Seeding: Faker, custom data generators
- ORM: SQLAlchemy 2.0 (async) - Database abstraction
- Logging: Structlog - Structured JSON logging
- Metrics: Prometheus Client - Demo metrics
API Endpoints (Key Routes)
Demo Session Management
POST /api/v1/demo-sessions- Create new demo sessionGET /api/v1/demo-sessions/{session_id}- Get session detailsPOST /api/v1/demo-sessions/{session_id}/extend- Extend sessionDELETE /api/v1/demo-sessions/{session_id}- Terminate sessionGET /api/v1/demo-sessions/{session_id}/credentials- Get login credentialsGET /api/v1/demo-sessions/active- List active sessions
Demo Scenarios
GET /api/v1/demo-sessions/scenarios- List available scenariosGET /api/v1/demo-sessions/scenarios/{scenario_id}- Get scenario detailsPOST /api/v1/demo-sessions/scenarios/{scenario_id}/create- Create session from scenario
Sales Dashboard (Internal)
GET /api/v1/demo-sessions/analytics/dashboard- Demo analyticsGET /api/v1/demo-sessions/analytics/usage- Usage patternsGET /api/v1/demo-sessions/analytics/conversion- Demo to signup conversion
Health & Monitoring
GET /api/v1/demo-sessions/health- Service healthGET /api/v1/demo-sessions/cleanup/status- Cleanup job status
Database Schema
Main Tables
demo_sessions
CREATE TABLE demo_sessions (
id UUID PRIMARY KEY,
session_token VARCHAR(255) UNIQUE NOT NULL,
demo_tenant_id UUID NOT NULL, -- Demo tenant in separate DB
-- Configuration
scenario_name VARCHAR(100) NOT NULL, -- standard_bakery, multi_location, etc.
duration_hours INTEGER DEFAULT 24,
-- Status
status VARCHAR(50) DEFAULT 'active', -- active, extended, expired, terminated
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL,
extended_count INTEGER DEFAULT 0,
terminated_at TIMESTAMP,
termination_reason VARCHAR(255),
-- Tracking
created_by_ip INET,
user_agent TEXT,
referrer VARCHAR(500),
utm_source VARCHAR(100),
utm_campaign VARCHAR(100),
utm_medium VARCHAR(100),
-- Usage analytics
login_count INTEGER DEFAULT 0,
last_activity_at TIMESTAMP,
page_views INTEGER DEFAULT 0,
features_used JSONB, -- Array of feature names
-- Lead info (if provided)
lead_email VARCHAR(255),
lead_name VARCHAR(255),
lead_phone VARCHAR(50),
lead_company VARCHAR(255),
INDEX idx_sessions_status ON (status, expires_at),
INDEX idx_sessions_token ON (session_token)
);
demo_scenarios
CREATE TABLE demo_scenarios (
id UUID PRIMARY KEY,
scenario_name VARCHAR(100) UNIQUE NOT NULL,
display_name VARCHAR(255) NOT NULL,
description TEXT,
-- Configuration
business_name VARCHAR(255),
location_count INTEGER DEFAULT 1,
product_count INTEGER DEFAULT 20,
days_of_history INTEGER DEFAULT 90,
-- Features to highlight
featured_capabilities JSONB,
-- Data generation settings
seed_data_config JSONB,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
demo_session_events
CREATE TABLE demo_session_events (
id UUID PRIMARY KEY,
session_id UUID REFERENCES demo_sessions(id) ON DELETE CASCADE,
event_type VARCHAR(100) NOT NULL, -- login, page_view, feature_used, action_performed
event_data JSONB,
ip_address INET,
occurred_at TIMESTAMP DEFAULT NOW(),
INDEX idx_session_events_session (session_id, occurred_at)
);
demo_session_metrics
CREATE TABLE demo_session_metrics (
id UUID PRIMARY KEY,
metric_date DATE NOT NULL,
scenario_name VARCHAR(100),
-- Volume
sessions_created INTEGER DEFAULT 0,
sessions_completed INTEGER DEFAULT 0, -- Not terminated early
sessions_expired INTEGER DEFAULT 0,
sessions_terminated INTEGER DEFAULT 0,
-- Engagement
avg_duration_minutes INTEGER,
avg_login_count DECIMAL(5, 2),
avg_page_views DECIMAL(5, 2),
avg_features_used DECIMAL(5, 2),
-- Conversion
demo_to_signup_count INTEGER DEFAULT 0,
conversion_rate_percentage DECIMAL(5, 2),
calculated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(metric_date, scenario_name)
);
Indexes for Performance
CREATE INDEX idx_sessions_expires ON demo_sessions(expires_at) WHERE status = 'active';
CREATE INDEX idx_sessions_scenario ON demo_sessions(scenario_name, created_at DESC);
CREATE INDEX idx_events_session_type ON demo_session_events(session_id, event_type);
Business Logic Examples
Demo Session Creation
async def create_demo_session(
scenario_name: str = 'standard_bakery',
duration_hours: int = 24,
lead_info: dict = None,
request_info: dict = None
) -> DemoSession:
"""
Create new demo session with pre-seeded data.
"""
# Get scenario configuration
scenario = await db.query(DemoScenario).filter(
DemoScenario.scenario_name == scenario_name,
DemoScenario.is_active == True
).first()
if not scenario:
raise ValueError("Invalid scenario")
# Check concurrent demo limit
active_demos = await db.query(DemoSession).filter(
DemoSession.status == 'active',
DemoSession.expires_at > datetime.utcnow()
).count()
if active_demos >= MAX_CONCURRENT_DEMOS:
raise Exception("Maximum concurrent demos reached")
try:
# Generate session token
session_token = secrets.token_urlsafe(32)
# Create demo tenant in separate database
demo_tenant = await create_demo_tenant(scenario)
# Seed demo data
await seed_demo_data(demo_tenant.id, scenario)
# Create session record
session = DemoSession(
session_token=session_token,
demo_tenant_id=demo_tenant.id,
scenario_name=scenario_name,
duration_hours=duration_hours,
expires_at=datetime.utcnow() + timedelta(hours=duration_hours),
created_by_ip=request_info.get('ip'),
user_agent=request_info.get('user_agent'),
referrer=request_info.get('referrer'),
utm_source=request_info.get('utm_source'),
utm_campaign=request_info.get('utm_campaign'),
lead_email=lead_info.get('email') if lead_info else None,
lead_name=lead_info.get('name') if lead_info else None
)
db.add(session)
# Log event
event = DemoSessionEvent(
session_id=session.id,
event_type='session_created',
event_data={'scenario': scenario_name},
ip_address=request_info.get('ip')
)
db.add(event)
await db.commit()
logger.info("Demo session created",
session_id=str(session.id),
scenario=scenario_name,
duration_hours=duration_hours)
# Publish event
await publish_event('demo_sessions', 'demo.session_created', {
'session_id': str(session.id),
'scenario': scenario_name
})
return session
except Exception as e:
logger.error("Demo session creation failed",
scenario=scenario_name,
error=str(e))
raise
async def create_demo_tenant(scenario: DemoScenario) -> DemoTenant:
"""
Create isolated demo tenant in demo database.
"""
# Use separate database connection for demo data
demo_db = get_demo_database_connection()
tenant = DemoTenant(
tenant_name=scenario.business_name or "Demo Bakery",
email=f"demo_{uuid.uuid4().hex[:8]}@bakery-ia.com",
status='demo',
subscription_tier='pro', # Always show Pro features in demo
is_demo=True
)
demo_db.add(tenant)
await demo_db.commit()
return tenant
async def seed_demo_data(tenant_id: UUID, scenario: DemoScenario):
"""
Seed demo tenant with realistic data.
"""
demo_db = get_demo_database_connection()
# Seed configuration
config = scenario.seed_data_config or {}
product_count = config.get('product_count', 20)
days_of_history = config.get('days_of_history', 90)
# 1. Seed product catalog
products = await seed_products(demo_db, tenant_id, product_count)
# 2. Seed suppliers
suppliers = await seed_suppliers(demo_db, tenant_id, 5)
# 3. Seed inventory
await seed_inventory(demo_db, tenant_id, products, suppliers)
# 4. Seed sales history (90 days)
await seed_sales_history(demo_db, tenant_id, products, days_of_history)
# 5. Generate forecasts
await seed_forecasts(demo_db, tenant_id, products)
# 6. Seed production schedules
await seed_production_schedules(demo_db, tenant_id, products)
# 7. Seed team members
await seed_team_members(demo_db, tenant_id)
logger.info("Demo data seeded",
tenant_id=str(tenant_id),
products=len(products),
suppliers=len(suppliers))
async def seed_sales_history(
demo_db,
tenant_id: UUID,
products: list,
days: int = 90
) -> list:
"""
Generate realistic sales history using patterns.
"""
from faker import Faker
fake = Faker('es_ES') # Spanish locale
sales_records = []
start_date = date.today() - timedelta(days=days)
for day_offset in range(days):
current_date = start_date + timedelta(days=day_offset)
is_weekend = current_date.weekday() >= 5
is_holiday = await is_spanish_holiday(current_date)
# Adjust volume based on day type
base_transactions = 50
if is_weekend:
base_transactions = int(base_transactions * 1.4) # 40% more on weekends
if is_holiday:
base_transactions = int(base_transactions * 0.7) # 30% less on holidays
# Add randomness
daily_transactions = int(base_transactions * random.uniform(0.8, 1.2))
for _ in range(daily_transactions):
# Random product
product = random.choice(products)
# Realistic quantity (most orders are 1-5 units)
quantity = random.choices([1, 2, 3, 4, 5, 6, 10], weights=[40, 25, 15, 10, 5, 3, 2])[0]
# Calculate price with small variance
unit_price = product.price * random.uniform(0.95, 1.05)
sale = DemoSale(
tenant_id=tenant_id,
sale_date=current_date,
sale_time=fake.time(),
product_id=product.id,
product_name=product.name,
quantity=quantity,
unit_price=unit_price,
total_amount=quantity * unit_price,
channel='pos'
)
sales_records.append(sale)
# Bulk insert
demo_db.bulk_save_objects(sales_records)
await demo_db.commit()
return sales_records
Auto-Cleanup Job
async def cleanup_expired_demos():
"""
Background job to cleanup expired demo sessions.
Runs every hour.
"""
# Find expired sessions
expired_sessions = await db.query(DemoSession).filter(
DemoSession.status == 'active',
DemoSession.expires_at <= datetime.utcnow()
).all()
for session in expired_sessions:
try:
# Mark session as expired
session.status = 'expired'
session.terminated_at = datetime.utcnow()
# Delete demo tenant and all data
await delete_demo_tenant(session.demo_tenant_id)
# Log event
event = DemoSessionEvent(
session_id=session.id,
event_type='session_expired',
occurred_at=datetime.utcnow()
)
db.add(event)
logger.info("Demo session cleaned up",
session_id=str(session.id),
duration_hours=(session.terminated_at - session.created_at).total_seconds() / 3600)
except Exception as e:
logger.error("Demo cleanup failed",
session_id=str(session.id),
error=str(e))
continue
await db.commit()
logger.info("Demo cleanup completed",
expired_count=len(expired_sessions))
Events & Messaging
Published Events (RabbitMQ)
Exchange: demo_sessions
Routing Keys: demo.session_created, demo.session_converted
Demo Session Created Event
{
"event_type": "demo_session_created",
"session_id": "uuid",
"scenario": "standard_bakery",
"duration_hours": 24,
"lead_email": "prospect@example.com",
"utm_source": "google_ads",
"timestamp": "2025-11-06T10:00:00Z"
}
Demo Converted to Signup
{
"event_type": "demo_session_converted",
"session_id": "uuid",
"tenant_id": "uuid",
"scenario": "standard_bakery",
"demo_duration_hours": 2.5,
"features_used": ["forecasting", "inventory", "production"],
"timestamp": "2025-11-06T12:30:00Z"
}
Custom Metrics (Prometheus)
# Demo session metrics
demo_sessions_created_total = Counter(
'demo_sessions_created_total',
'Total demo sessions created',
['scenario']
)
demo_sessions_active = Gauge(
'demo_sessions_active',
'Current active demo sessions',
[]
)
demo_session_duration_hours = Histogram(
'demo_session_duration_hours',
'Demo session duration',
['scenario'],
buckets=[0.5, 1, 2, 4, 8, 12, 24, 48]
)
demo_to_signup_conversions_total = Counter(
'demo_to_signup_conversions_total',
'Demo sessions that converted to signup',
['scenario']
)
demo_feature_usage_total = Counter(
'demo_feature_usage_total',
'Feature usage in demos',
['feature_name']
)
Configuration
Environment Variables
Service Configuration:
PORT- Service port (default: 8019)DATABASE_URL- Main PostgreSQL connectionDEMO_DATABASE_URL- Isolated demo databaseREDIS_URL- Redis connection stringRABBITMQ_URL- RabbitMQ connection string
Demo Configuration:
DEFAULT_DEMO_DURATION_HOURS- Default duration (default: 24)MAX_DEMO_DURATION_HOURS- Maximum duration (default: 168/7 days)MAX_CONCURRENT_DEMOS- Concurrent limit (default: 100)CLEANUP_INTERVAL_MINUTES- Cleanup frequency (default: 60)
Data Seeding:
DEMO_SALES_HISTORY_DAYS- Sales history length (default: 90)DEMO_PRODUCT_COUNT- Number of products (default: 20)DEMO_SUPPLIER_COUNT- Number of suppliers (default: 5)
Development Setup
Prerequisites
- Python 3.11+
- PostgreSQL 17 (2 databases: main + demo)
- Redis 7.4
- RabbitMQ 4.1
Local Development
cd services/demo_session
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export DATABASE_URL=postgresql://user:pass@localhost:5432/demo_session
export DEMO_DATABASE_URL=postgresql://user:pass@localhost:5432/demo_data
export REDIS_URL=redis://localhost:6379/0
export RABBITMQ_URL=amqp://guest:guest@localhost:5672/
alembic upgrade head
python main.py
Integration Points
Dependencies
- Separate Demo Database - Isolated demo tenant data
- Auth Service - Demo user credentials
- Data Generators - Realistic data seeding
- PostgreSQL - Session tracking
- Redis - Rate limiting, caching
- RabbitMQ - Event publishing
Dependents
- Sales Team - Demo creation
- Marketing - Landing page demos
- Frontend - Demo UI access
- Analytics - Demo conversion tracking
Business Value for VUE Madrid
Problem Statement
Traditional sales demos are difficult:
- Time-consuming setup (15-30 minutes per demo)
- Risk of breaking things in front of prospects
- Inconsistent demo quality
- No self-service for prospects
- Hard to track engagement
- Limited by sales rep availability
Solution
Bakery-IA Demo Session Service provides:
- Instant Demos: Ready in 30 seconds
- Risk-Free: Isolated environments
- Self-Service: Prospects explore independently
- Consistent Quality: Same data every time
- Engagement Tracking: Know what prospects care about
- Scalable: Unlimited concurrent demos
Quantifiable Impact
Sales Efficiency:
- 30-50% shorter sales cycle with live demos
- 2-3× conversion rate vs. static presentations
- 5-10× more demos per sales rep
- 0 minutes setup time vs. 15-30 minutes
- €500-1,500/month sales time saved
Lead Quality:
- Higher engagement = more qualified leads
- Feature usage indicates specific needs
- Demo-to-trial conversion: 35-45%
- Trial-to-paid conversion: 25-35%
- Overall demo-to-paid: 12-16%
Marketing Value:
- Self-service demos on landing page
- 24/7 availability for global prospects
- Viral potential (shareable demo links)
- Lower customer acquisition cost
- Better understanding of product-market fit
Target Market Fit (Spanish Bakeries)
- Visual Learners: Spanish business culture values demonstrations
- Trust Building: Try-before-buy reduces risk perception
- Language: Demo data in Spanish increases resonance
- Realistic: Spanish products, Madrid locations feel authentic
ROI for Platform
Investment: €100-300/month (compute + storage for demos) Value Generated:
- 50+ demos/month → 20 trials → 6 paid customers
- 6 customers × €66 avg MRR = €396/month
- Payback: 1-3 months
- ROI: 30-400% depending on conversion rates
Copyright © 2025 Bakery-IA. All rights reserved.