# BaoLife Phase 6: Analytics & Testing

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Observe user behavior, catch crashes, validate quality before launch.

**Architecture:** Backend analytics events + crash reporting. Frontend Firebase integration + testing infrastructure.

**Tech Stack:**
- Backend: Mixpanel/custom analytics, Sentry
- Frontend: Firebase Analytics, Crashlytics, XCTest

**Total Time Estimate:** 30-35 hours (Week 9)

---

## Backend Components (../lichun)

### Component 54: Event Analytics System

**Files:**
- Create: `../lichun/analytics/events.py`
- Modify: Database schema

**Database Schema:**
```sql
CREATE TABLE analytics_events (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    player_id INT,
    event_name VARCHAR(100) NOT NULL,
    properties JSON,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    session_id VARCHAR(100),
    INDEX idx_event_name (event_name),
    INDEX idx_player_timestamp (player_id, timestamp)
);
```

**Implementation:**
```python
import json
from datetime import datetime

def track_event(player_id: int, event_name: str, properties: dict = None):
    """Track analytics event"""
    db.execute_query(
        """INSERT INTO analytics_events (player_id, event_name, properties, session_id)
           VALUES (%s, %s, %s, %s)""",
        (player_id, event_name, json.dumps(properties or {}), get_session_id(player_id)),
        fetch=False
    )
    
    logging.info(f"Analytics: {player_id} - {event_name}")

# Track key events throughout codebase
def handle_purchase_energy_refill(player_id: int, message_data: dict):
    track_event(player_id, 'purchase_initiated', {
        'product_type': 'energy_refill',
        'refill_type': message_data['refillType']
    })
    
    # ... purchase logic ...
    
    track_event(player_id, 'purchase_completed', {
        'product_type': 'energy_refill',
        'diamond_cost': cost,
        'success': True
    })

# Track engagement
def handle_send_message(player_id: int, message_data: dict):
    track_event(player_id, 'conversation_sent', {
        'target_id': message_data['targetId'],
        'message_length': len(message_data['message'])
    })

# Track progression
def handle_level_up(player_id: int, new_level: int):
    track_event(player_id, 'level_up', {
        'new_level': new_level,
        'age_years': get_player(player_id)['age_years']
    })
```

**Key Events to Track:**
- Lifecycle: app_open, app_close, session_duration
- Monetization: purchase_initiated, purchase_completed, purchase_failed
- Engagement: activity_started, conversation_sent, date_completed
- Retention: daily_login, achievement_unlocked, quest_completed
- Progression: level_up, relationship_milestone, career_change

**Commit:**
```bash
git add analytics/events.py database_schema.sql
git commit -m "feat(analytics): add event tracking system

- Custom analytics events table
- Track lifecycle, monetization, engagement
- Session tracking
- JSON properties for flexibility

🤖 Generated with Claude Code"
```

**Estimated Time:** 5-6 hours

---

### Component 55: Error & Crash Reporting

**Files:**
- Create: `../lichun/monitoring/sentry_setup.py`
- Modify: `../lichun/server.py`

**Implementation:**
```python
import sentry_sdk
from config import Config

sentry_sdk.init(
    dsn=Config.SENTRY_DSN,
    traces_sample_rate=0.1,  # 10% of transactions
    environment="production",
    release=Config.VERSION
)

# Add context to errors
def set_player_context(player_id: int):
    """Set Sentry context for player"""
    player = get_player(player_id)
    sentry_sdk.set_user({
        "id": player_id,
        "username": player['first_name']
    })
    sentry_sdk.set_context("game_state", {
        "level": player['age_years'],
        "energy": player['energy'],
        "diamonds": player['diamonds'],
        "money": player['money']
    })

# Capture custom errors
def handle_critical_error(player_id: int, error_type: str, details: dict):
    """Capture non-exception errors"""
    with sentry_sdk.push_scope() as scope:
        scope.set_context("error_details", details)
        sentry_sdk.capture_message(
            f"{error_type} for player {player_id}",
            level="error"
        )
```

**Commit:**
```bash
git add monitoring/sentry_setup.py server.py
git commit -m "feat(monitoring): add Sentry crash reporting

- Automatic exception capture
- User context and game state
- Performance traces (10% sampling)
- Custom error messages

🤖 Generated with Claude Code"
```

**Estimated Time:** 3-4 hours

---

### Component 56-57: Performance Monitoring & A/B Testing

**Performance Metrics:**
```python
import time
from functools import wraps

def measure_performance(operation_name: str):
    """Decorator to measure operation duration"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            duration = time.time() - start
            
            # Log to analytics
            track_event(None, 'performance_metric', {
                'operation': operation_name,
                'duration_ms': duration * 1000
            })
            
            return result
        return wrapper
    return decorator

@measure_performance("purchase_energy")
def purchase_energy_refill(player_id: int, refill_type: str):
    # ... implementation ...
```

**A/B Testing:**
```python
# Simple feature flags
FEATURE_FLAGS = {
    'new_energy_pricing': {
        'enabled': True,
        'rollout_percentage': 50,
        'variants': {
            'control': {'small': 10, 'medium': 20, 'full': 35},
            'test': {'small': 15, 'medium': 25, 'full': 40}
        }
    }
}

def get_variant(player_id: int, flag_name: str) -> str:
    """Get A/B test variant for player"""
    # Hash player ID for consistent assignment
    import hashlib
    hash_val = int(hashlib.md5(str(player_id).encode()).hexdigest(), 16)
    
    flag = FEATURE_FLAGS[flag_name]
    if hash_val % 100 < flag['rollout_percentage']:
        return 'test'
    return 'control'
```

**Estimated Time:** 6-8 hours combined

---

## Frontend Components (lichunWebsocket)

### Component 58-59: Firebase Analytics & Crashlytics

**Files:**
- Modify: `lichunWebsocket/lichunWebsocketApp.swift`
- Add Firebase SDK via SPM

**Implementation:**

#### Step 1: Install Firebase

Add Firebase to Swift Package Manager:
- File → Add Packages
- URL: `https://github.com/firebase/firebase-ios-sdk`
- Select: FirebaseAnalytics, FirebaseCrashlytics

---

#### Step 2: Initialize Firebase

Modify: `lichunWebsocketApp.swift`

```swift
import SwiftUI
import FirebaseCore
import FirebaseAnalytics
import FirebaseCrashlytics

@main
struct lichunWebsocketApp: App {
    @StateObject private var webSocketService = WebSocketService()
    @StateObject private var appViewModel = AppViewModel()
    @StateObject private var storeManager = StoreManager()
    
    init() {
        // Configure Firebase
        FirebaseApp.configure()
        
        // Enable crash reporting
        Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(webSocketService)
                .environmentObject(appViewModel)
                .environmentObject(storeManager)
                .onAppear {
                    setupAnalytics()
                }
        }
    }
    
    func setupAnalytics() {
        // Set user ID
        if let playerId = webSocketService.person.id {
            Analytics.setUserID("\(playerId)")
            Crashlytics.crashlytics().setUserID("\(playerId)")
        }
        
        // Set user properties
        Analytics.setUserProperty("\(webSocketService.person.ageYears)", forName: "age_years")
        Analytics.setUserProperty("\(webSocketService.person.diamonds)", forName: "diamonds")
    }
}
```

---

#### Step 3: Track Events

Create: `AnalyticsManager.swift`

```swift
import FirebaseAnalytics
import FirebaseCrashlytics

class AnalyticsManager {
    static let shared = AnalyticsManager()
    
    func logEvent(_ name: String, parameters: [String: Any]? = nil) {
        Analytics.logEvent(name, parameters: parameters)
        
        // Also log to Crashlytics breadcrumbs
        let paramString = parameters?.description ?? "none"
        Crashlytics.crashlytics().log("\(name): \(paramString)")
    }
    
    func logScreenView(_ screenName: String) {
        Analytics.logEvent(AnalyticsEventScreenView, parameters: [
            AnalyticsParameterScreenName: screenName,
            AnalyticsParameterScreenClass: screenName
        ])
    }
    
    func logPurchase(productId: String, value: Double, currency: String) {
        Analytics.logEvent(AnalyticsEventPurchase, parameters: [
            AnalyticsParameterItemID: productId,
            AnalyticsParameterValue: value,
            AnalyticsParameterCurrency: currency
        ])
    }
    
    func setUserProperty(_ value: String, forName name: String) {
        Analytics.setUserProperty(value, forName: name)
    }
    
    // Non-fatal errors
    func recordError(_ error: Error, context: [String: Any]? = nil) {
        Crashlytics.crashlytics().record(error: error)
        
        if let context = context {
            for (key, value) in context {
                Crashlytics.crashlytics().setCustomValue(value, forKey: key)
            }
        }
    }
}
```

---

#### Step 4: Add Analytics Throughout App

Example in `EnergyRefillView.swift`:

```swift
func purchaseRefill(_ tier: RefillTier) {
    // Log purchase initiation
    AnalyticsManager.shared.logEvent("purchase_initiated", parameters: [
        "product_type": "energy_refill",
        "refill_type": tier.rawValue,
        "diamond_cost": tier.diamondCost
    ])
    
    isProcessing = true
    
    webSocketService.sendMessage(message: [
        "type": "purchaseEnergyRefill",
        "refillType": tier.rawValue
    ])
    
    // Listen for response
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        isProcessing = false
        
        // Log completion
        AnalyticsManager.shared.logEvent("purchase_completed", parameters: [
            "product_type": "energy_refill",
            "success": true
        ])
        
        dismiss()
    }
}
```

Example in `HomeView.swift`:

```swift
var body: some View {
    ScrollView {
        // ... content ...
    }
    .onAppear {
        AnalyticsManager.shared.logScreenView("Home")
    }
}
```

**Commit:**
```bash
git add lichunWebsocketApp.swift AnalyticsManager.swift
git commit -m "feat(analytics): add Firebase Analytics and Crashlytics

- Firebase SDK integration
- Event tracking throughout app
- Screen view logging
- Purchase tracking
- Non-fatal error recording
- User properties and custom keys

🤖 Generated with Claude Code"
```

**Estimated Time:** 6-7 hours combined

---

### Component 60-62: Testing Infrastructure

**Files:**
- Create: `lichunWebsocketTests/ViewModelTests.swift`
- Create: `lichunWebsocketUITests/OnboardingUITests.swift`
- Create test schemes

**Unit Tests Example:**

```swift
import XCTest
@testable import lichunWebsocket

class WebSocketServiceTests: XCTestCase {
    var service: WebSocketService!
    
    override func setUp() {
        super.setUp()
        service = WebSocketService()
    }
    
    func testEnergyCalculation() {
        service.person.energy = 50
        service.person.maxEnergy = 100
        
        XCTAssertEqual(service.person.calcEnergy, 50)
    }
    
    func testDiamondUpdate() {
        service.person.diamonds = 100
        
        let expectation = XCTestExpectation(description: "Diamond update")
        
        service.handleDiamondEarned(["amount": 25, "reason": "test", "total": 125])
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            XCTAssertEqual(self.service.person.diamonds, 125)
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 1.0)
    }
}
```

**UI Tests Example:**

```swift
import XCTest

class OnboardingUITests: XCTestCase {
    var app: XCUIApplication!
    
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()
    }
    
    func testOnboardingFlow() {
        // Welcome screen
        XCTAssertTrue(app.staticTexts["Welcome to BaoLife"].exists)
        
        // Tap start button
        app.buttons["Start Your Life"].tap()
        
        // Should see character creation
        XCTAssertTrue(app.textFields["First Name"].exists)
        
        // Complete character creation
        let firstNameField = app.textFields["First Name"]
        firstNameField.tap()
        firstNameField.typeText("TestUser")
        
        let lastNameField = app.textFields["Last Name"]
        lastNameField.tap()
        lastNameField.typeText("McTest")
        
        app.buttons["Continue"].tap()
        
        // Should reach UI tour
        XCTAssertTrue(app.staticTexts["Your Life Dashboard"].exists)
    }
    
    func testEnergyRefillPurchase() {
        // Navigate to energy refill
        app.images["bolt.fill"].tap()
        
        // Should see refill options
        XCTAssertTrue(app.staticTexts["Small Refill"].exists)
        
        // Tap small refill
        app.buttons["Small Refill"].tap()
        
        // Confirm purchase
        app.buttons["Buy for 10 💎"].tap()
        
        // Should see success (or insufficient diamonds message)
        XCTAssertTrue(app.alerts.count > 0 || app.staticTexts["Energy Refill"].exists)
    }
}
```

**Testing Checklist:**

```markdown
## Device Testing
- [ ] iPhone SE (small screen)
- [ ] iPhone 15 Pro (current flagship)
- [ ] iPhone 15 Pro Max (large screen)

## iOS Version Testing
- [ ] iOS 16
- [ ] iOS 17
- [ ] iOS 18

## Network Testing
- [ ] Wi-Fi strong
- [ ] Cellular LTE
- [ ] Slow 3G (Network Link Conditioner)
- [ ] Offline mode
- [ ] Connection drops mid-action

## State Testing
- [ ] Cold launch
- [ ] Background → Foreground
- [ ] Phone call interruption
- [ ] Low battery mode
- [ ] Low storage
- [ ] Memory warnings

## Gameplay Testing
- [ ] Complete onboarding
- [ ] All purchase types
- [ ] Unlock achievements
- [ ] Complete daily quests
- [ ] Full life (birth to death)
```

**Commit:**
```bash
git add lichunWebsocketTests/ lichunWebsocketUITests/
git commit -m "test: add comprehensive test coverage

- Unit tests for view models
- UI tests for critical flows
- Device and network testing
- Test checklists

🤖 Generated with Claude Code"
```

**Estimated Time:** 9-10 hours combined

---

## Phase 6 Summary

**Total Time:** 30-35 hours (Week 9)

**Backend (15-18h):**
- ✅ Component 54: Analytics events (5-6h)
- ✅ Component 55: Crash reporting (3-4h)
- ✅ Component 56-57: Performance & A/B tests (6-8h)

**Frontend (15-17h):**
- ✅ Component 58-59: Firebase integration (6-7h)
- ✅ Component 60-62: Testing infrastructure (9-10h)

**What's Working:**
- Full event tracking
- Crash reporting with context
- Performance monitoring
- A/B test infrastructure
- Comprehensive test coverage
- TestFlight ready

**Next Phase:** App Store Preparation (Weeks 10-11)

