Tour Analytics
The analytics panel in the admin overlay provides real-time visibility into how users engage with tours and content. It surfaces completion rates, step-level engagement, drop-off analysis, and selector health metrics.
Data Architecture
Analytics data is stored in two DynamoDB tables using a two-tier rollup strategy:
Events.Interactions (Raw Events)
Append-only log of individual user interaction events. Each event has a 90-day TTL.
PK: tenant_id#content_id (e.g. "tenant_abc#tour_123")
SK: timestamp#event_id (e.g. "2025-05-10T14:22:00Z#uuid")Events.Aggregates (Pre-computed Counters)
Hourly and daily counters maintained atomically on every interaction:
PK: tenant_id#content_id (e.g. "tenant_abc#tour_123")
SK: date_metric (e.g. "2025-05-10#views" or "2025-05-10T14#completions")The sort key format determines granularity:
- Daily:
YYYY-MM-DD#metric-- used for trend analysis (weekly/monthly views). - Hourly:
YYYY-MM-DDTHH#metric-- used for fine-grained dashboards (last-24-hour view).
Both counters are incremented atomically on every interaction using DynamoDB ADD operations -- no batch jobs or delayed processing.
Tracked Metrics
The following events are captured and aggregated:
Tour Events
| Event | Metric Name | Description |
|---|---|---|
TourStarted | starts | User began the tour |
StepViewed | step_views | User saw a specific step |
StepCompleted | step_completions | User advanced past a step |
TourCompleted | completions | User finished all steps |
TourDismissed | dismissals | User closed the tour early |
TourRestarted | restarts | User restarted the tour |
Content Events
| Event | Metric Name | Description |
|---|---|---|
ContentViewed | views | User saw the content |
ContentDismissed | dismissals | User dismissed the content |
ContentCtaClicked | cta_clicks | User clicked the call-to-action |
Targeting Events
| Event | Metric Name | Description |
|---|---|---|
target_not_found | target_failures | SDK could not locate the target element |
Completion Rate
The primary metric for tour effectiveness. Calculated as:
completion_rate = completions / starts * 100The analytics panel displays this as a percentage with a trend indicator showing change over the selected time period.
Drop-off Analysis
The step-level funnel view shows where users abandon tours:
Step 1: "Welcome" ████████████████████ 100% (200 users)
Step 2: "Navigate to..." ██████████████████ 90% (180 users)
Step 3: "Click the..." ████████████████ 80% (160 users)
Step 4: "Configure..." █████████ 45% (90 users) <-- drop-off
Step 5: "Complete" ███████ 35% (70 users)The step with the largest percentage drop is highlighted to identify where the tour needs improvement. Common causes:
- The target element is not visible (lazy-loaded, behind a tab, requires scrolling).
- The step instructions are unclear.
- The step requires an action the user is not ready to take.
Querying Analytics
Daily Aggregates
curl -H "X-API-Key: sk_live_..." \
-H "Authorization: Bearer <jwt>" \
"https://api.example.com/v1/admin/analytics/tours/tour_123?date_from=2025-05-01&date_to=2025-05-10"{
"data": {
"tour_id": "tour_123",
"period": {
"from": "2025-05-01",
"to": "2025-05-10"
},
"totals": {
"starts": 1250,
"completions": 875,
"dismissals": 312,
"completion_rate": 70.0
},
"daily": [
{
"date": "2025-05-01",
"starts": 150,
"completions": 105,
"dismissals": 38,
"completion_rate": 70.0
}
]
}
}Hourly Aggregates
For fine-grained views (last 24 hours):
curl -H "X-API-Key: sk_live_..." \
-H "Authorization: Bearer <jwt>" \
"https://api.example.com/v1/admin/analytics/tours/tour_123?date=2025-05-10&granularity=hourly"{
"data": {
"tour_id": "tour_123",
"date": "2025-05-10",
"granularity": "hourly",
"hours": [
{ "hour": "00", "starts": 2, "completions": 1, "dismissals": 1 },
{ "hour": "01", "starts": 0, "completions": 0, "dismissals": 0 },
{ "hour": "09", "starts": 45, "completions": 32, "dismissals": 10 },
{ "hour": "10", "starts": 62, "completions": 44, "dismissals": 15 }
]
}
}Activity Feed
The Activity.UserEvents table stores a per-user event log with three access patterns:
| GSI | Question Answered | Key Structure |
|---|---|---|
| Primary key | "What has this user been doing?" | PK: tenant_user_id, SK: timestamp_event_id |
gsi-content | "Who interacted with this tour?" | PK: tenant_content_id, SK: timestamp |
gsi-session | "What happened in this session?" | PK: tenant_session_id, SK: timestamp |
Activity events have a configurable TTL (default: 90 days) to manage storage costs.
Admin Panel Views
The analytics panel in the admin overlay provides four views:
Overview Dashboard
Aggregate metrics across all tours and content for the tenant:
- Total active tours and content items
- Combined completion rate with trend
- Top-performing tour (highest completion rate)
- Tour with highest drop-off (needs attention)
Tour Detail View
Per-tour metrics with step-level funnel:
- Completion rate with daily trend chart
- Step-by-step funnel with drop-off markers
- User count by status (completed, in-progress, dismissed)
- Recent activity feed
Selector Health View
Aggregated targeting failure report (see Element Selector):
- Total failures in the analysis window
- Broken selectors ranked by failure count
- Drill-down to individual failure events per content item
Export
Analytics data can be exported via the admin API for integration with external BI tools. The raw Events.Interactions table data is queryable with date range filters.
Event Processing Pipeline
User Interaction (SDK)
|
v
POST /v1/sdk/events
|
v
Events.Outbox table (transactional write)
|
v
Outbox Processor (polls pending events)
|
v
EventBridge (spotlight-events bus)
|
+---> Activity Handler --> Activity.UserEvents
|
+---> Analytics Handler --> Events.Interactions
| Events.Aggregates
|
+---> Audit Handler --> Audit.AdminActions (admin events only)In local development, the outbox processor runs as a FastAPI background task (configured via ENABLE_LOCAL_PROCESSOR=true) instead of a separate Lambda function, polling every LOCAL_PROCESSOR_INTERVAL seconds.
In production, DynamoDB Streams on the outbox table trigger a Lambda function that publishes events to EventBridge, which routes them to the stream processor, analytics processor, and any configured webhook targets.