ワンクリックで
cocos-analytics-specialist
Use when working on analytics implementation, KPI tracking, A/B testing, and data-driven optimization.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Use when working on analytics implementation, KPI tracking, A/B testing, and data-driven optimization.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use this skill when the user asks to release the iOS app, upload a build, submit to App Store Connect review, or "在 App Store 上架/提交审核" for WordMagicGame.
Seed a lesson-import draft on a deployed Happyword API (Preview or prod) and optionally run the extract-pending cron so it reaches `pending` (待复核). Uses tools starting with `hw_`. Invoke when the user wants to upload a textbook photo for review against a specific Vercel URL, mirror E2E `test_lesson_import_cron_e2e`, or smoke-test lesson import + cron on a branch deployment.
Investigate why a Vercel preview / production deployment of the FastAPI server is broken (404 on every path, 500 on every path, e2e CI red, etc.). Pulls the right log surface for the symptom and maps HTTP error headers to root causes. Use whenever the deployed server returns wrong responses, the server-ci e2e job fails, or autofix loops keep retrying without converging.
Health-check every active Vercel Preview deployment listed in the live manifest at `https://happyword.cool/api/v1/public/preview-urls.json`. Probes `GET /api/v1/public/health` through Vercel Deployment Protection (`x-vercel-protection-bypass` header), prints a one-line status per preview, and exits non-zero if any are sick. Use when the user asks to "test preview health", "check previews", "are PR previews alive?", before merging anything that touches `server/`, after a fleet-wide redeploy or env-var rotation, or after rotating `VERCEL_AUTOMATION_BYPASS_SECRET` — do NOT loop ad-hoc `curl` calls or use `tools/vercel/smoke-prod.sh` (production-only) for this.
Drives a per-feature visual + spec-anchored gap scout across HarmonyOS / iOS / Android using tools/parity_scout/. Use when asked to "find iOS / Android gaps vs HarmonyOS main", "check parity for <feature>", or "screenshot the three platforms and tell me what's different".
Drives a per-feature visual + spec-anchored gap scout across HarmonyOS / iOS / Android using tools/parity_scout/. Use when asked to "find iOS / Android gaps vs HarmonyOS main", "check parity for <feature>", or "screenshot the three platforms and tell me what's different".
| name | cocos-analytics-specialist |
| description | Use when working on analytics implementation, KPI tracking, A/B testing, and data-driven optimization. |
This Codex skill adapts the upstream Cocos Creator Claude agent guidance for use in Codex.
Expert in game analytics, data tracking, and performance monitoring for Cocos Creator games. Use this skill for analytics implementation, KPI tracking, A/B testing, and data-driven optimization.
Context: Need comprehensive game analytics
User: "Implement analytics tracking for player progression and monetization"
Assistant: "I will use $cocos-analytics-specialist"
Commentary: Creates privacy-compliant tracking with actionable insights
Context: Need to optimize onboarding flow
User: "Set up A/B testing for tutorial completion rates"
Assistant: "I will use $cocos-analytics-specialist"
Commentary: Implements statistical framework with proper test design
Context: Need to monitor game performance in production
User: "Create real-time monitoring for crashes and performance issues"
Assistant: "I will use $cocos-analytics-specialist"
Commentary: Sets up alerting and automated issue detection
export interface AnalyticsEvent {
eventName: string;
parameters: { [key: string]: any };
timestamp: number;
sessionId: string;
userId?: string;
platform: string;
version: string;
}
export interface AnalyticsConfig {
enableTracking: boolean;
batchSize: number;
flushInterval: number;
maxRetries: number;
debugMode: boolean;
privacyCompliant: boolean;
providers: AnalyticsProvider[];
}
@ccclass('AnalyticsManager')
export class AnalyticsManager extends Component {
@property
config: AnalyticsConfig = {
enableTracking: true,
batchSize: 20,
flushInterval: 30000, // 30 seconds
maxRetries: 3,
debugMode: false,
privacyCompliant: true,
providers: []
};
private _eventQueue: AnalyticsEvent[] = [];
private _sessionId: string = '';
private _userId: string = '';
private _sessionStartTime: number = 0;
private _isInitialized: boolean = false;
onLoad() {
this.initializeAnalytics();
this.startSession();
this.setupEventListeners();
this.schedule(this.flushEvents, this.config.flushInterval / 1000);
}
private initializeAnalytics() {
// Generate session ID
this._sessionId = this.generateSessionId();
this._sessionStartTime = Date.now();
// Initialize providers
for (const provider of this.config.providers) {
this.initializeProvider(provider);
}
this._isInitialized = true;
// Track initialization
this.trackEvent('analytics_initialized', {
providers: this.config.providers.map(p => p.name),
privacy_compliant: this.config.privacyCompliant
});
}
trackEvent(eventName: string, parameters: any = {}) {
if (!this._isInitialized || !this.config.enableTracking) {
return;
}
// Privacy compliance check
if (this.config.privacyCompliant && !this.hasUserConsent()) {
return;
}
const event: AnalyticsEvent = {
eventName,
parameters: {
...parameters,
session_duration: Date.now() - this._sessionStartTime,
game_version: this.getGameVersion(),
device_info: this.getDeviceInfo()
},
timestamp: Date.now(),
sessionId: this._sessionId,
userId: this._userId,
platform: sys.platform,
version: this.getAnalyticsVersion()
};
this._eventQueue.push(event);
if (this.config.debugMode) {
console.log('Analytics Event:', event);
}
// Flush immediately for critical events
if (this.isCriticalEvent(eventName)) {
this.flushEvents();
} else if (this._eventQueue.length >= this.config.batchSize) {
this.flushEvents();
}
}
private async flushEvents() {
if (this._eventQueue.length === 0) return;
const events = [...this._eventQueue];
this._eventQueue = [];
for (const provider of this.config.providers) {
try {
await this.sendEventsToProvider(provider, events);
} catch (error) {
console.error(`Failed to send events to ${provider.name}:`, error);
// Re-queue events for retry
this._eventQueue.unshift(...events);
}
}
}
// Player Behavior Tracking
trackPlayerAction(action: string, context: any = {}) {
this.trackEvent('player_action', {
action,
...context,
screen: this.getCurrentScreen(),
level: this.getCurrentLevel(),
playtime: this.getSessionDuration()
});
}
trackLevelEvent(eventType: 'start' | 'complete' | 'fail', levelId: string, data: any = {}) {
this.trackEvent(`level_${eventType}`, {
level_id: levelId,
level_name: data.levelName,
difficulty: data.difficulty,
attempt_number: data.attemptNumber,
duration: data.duration,
score: data.score,
...data
});
}
trackProgressionEvent(progressionType: string, progressionId: string, status: string, data: any = {}) {
this.trackEvent('progression', {
progression_type: progressionType,
progression_id: progressionId,
progression_status: status,
player_level: data.playerLevel,
experience_gained: data.experienceGained,
currency_earned: data.currencyEarned,
...data
});
}
// Monetization Tracking
trackPurchaseEvent(productId: string, currency: string, amount: number, success: boolean, data: any = {}) {
const eventName = success ? 'purchase_completed' : 'purchase_failed';
this.trackEvent(eventName, {
product_id: productId,
currency,
amount,
transaction_id: data.transactionId,
payment_method: data.paymentMethod,
first_purchase: data.firstPurchase,
player_level: this.getPlayerLevel(),
session_number: this.getSessionNumber(),
...data
});
}
trackAdEvent(adType: 'banner' | 'interstitial' | 'rewarded', adProvider: string, eventType: string, data: any = {}) {
this.trackEvent('ad_event', {
ad_type: adType,
ad_provider: adProvider,
ad_event_type: eventType,
ad_placement: data.placement,
ad_network: data.network,
revenue: data.revenue,
...data
});
}
// Performance Tracking
trackPerformanceMetric(metricName: string, value: number, context: any = {}) {
this.trackEvent('performance_metric', {
metric_name: metricName,
metric_value: value,
device_tier: this.getDeviceTier(),
memory_usage: this.getMemoryUsage(),
fps: this.getCurrentFPS(),
...context
});
}
trackCrash(error: Error, context: any = {}) {
this.trackEvent('crash', {
error_message: error.message,
error_stack: error.stack,
error_type: error.name,
game_state: this.getGameState(),
memory_usage: this.getMemoryUsage(),
device_info: this.getDeviceInfo(),
...context
});
// Flush immediately for crashes
this.flushEvents();
}
}
export interface ABTest {
id: string;
name: string;
hypothesis: string;
variants: ABVariant[];
trafficAllocation: number; // 0-1
startDate: Date;
endDate: Date;
targetMetric: string;
minimumSampleSize: number;
confidenceLevel: number;
status: 'draft' | 'running' | 'completed' | 'paused';
}
export interface ABVariant {
id: string;
name: string;
trafficSplit: number; // 0-1
parameters: { [key: string]: any };
}
@ccclass('ABTestingManager')
export class ABTestingManager extends Component {
@property
tests: ABTest[] = [];
private _activeTests: Map<string, ABTest> = new Map();
private _userAssignments: Map<string, string> = new Map(); // testId -> variantId
private _analyticsManager: AnalyticsManager = null;
onLoad() {
this._analyticsManager = this.getComponent(AnalyticsManager);
this.loadActiveTests();
this.assignUserToTests();
}
private loadActiveTests() {
const now = Date.now();
this.tests.forEach(test => {
if (test.status === 'running' &&
now >= test.startDate.getTime() &&
now <= test.endDate.getTime()) {
this._activeTests.set(test.id, test);
}
});
}
private assignUserToTests() {
const userId = this.getUserId();
this._activeTests.forEach(test => {
// Check if user should be in this test
if (Math.random() > test.trafficAllocation) {
return; // User not in test
}
// Assign to variant based on consistent hashing
const variantIndex = this.hashUserId(userId, test.id) % test.variants.length;
const variant = test.variants[variantIndex];
this._userAssignments.set(test.id, variant.id);
// Track assignment
this._analyticsManager.trackEvent('ab_test_assignment', {
test_id: test.id,
test_name: test.name,
variant_id: variant.id,
variant_name: variant.name
});
});
}
getVariant(testId: string): ABVariant | null {
const test = this._activeTests.get(testId);
const variantId = this._userAssignments.get(testId);
if (!test || !variantId) return null;
return test.variants.find(v => v.id === variantId) || null;
}
getParameter(testId: string, parameterName: string, defaultValue: any = null): any {
const variant = this.getVariant(testId);
if (!variant || !variant.parameters.hasOwnProperty(parameterName)) {
return defaultValue;
}
return variant.parameters[parameterName];
}
trackConversion(testId: string, metricName: string, value: number = 1) {
const variant = this.getVariant(testId);
if (!variant) return;
this._analyticsManager.trackEvent('ab_test_conversion', {
test_id: testId,
variant_id: variant.id,
metric_name: metricName,
metric_value: value
});
}
private hashUserId(userId: string, testId: string): number {
// Simple hash function for consistent assignment
const str = userId + testId;
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash);
}
}
export interface AnalyticsDashboard {
kpis: KPIMetric[];
charts: ChartConfig[];
alerts: AlertConfig[];
refreshInterval: number;
}
export interface KPIMetric {
id: string;
name: string;
query: string;
format: 'number' | 'percentage' | 'currency' | 'duration';
target?: number;
critical?: number;
}
@ccclass('AnalyticsDashboard')
export class AnalyticsDashboard extends Component {
@property
dashboardConfig: AnalyticsDashboard = null;
@property(Label)
dauLabel: Label = null;
@property(Label)
retentionLabel: Label = null;
@property(Label)
arppuLabel: Label = null;
private _realTimeData: Map<string, number> = new Map();
private _alertThresholds: Map<string, number> = new Map();
onLoad() {
this.setupDashboard();
this.schedule(this.refreshDashboard, this.dashboardConfig.refreshInterval);
}
private setupDashboard() {
// Initialize KPI displays
this.dashboardConfig.kpis.forEach(kpi => {
this.setupKPIDisplay(kpi);
});
// Setup alert thresholds
this.dashboardConfig.alerts.forEach(alert => {
this._alertThresholds.set(alert.metric, alert.threshold);
});
}
private async refreshDashboard() {
try {
// Fetch latest metrics
const metrics = await this.fetchLatestMetrics();
// Update displays
this.updateKPIDisplays(metrics);
// Check alerts
this.checkAlerts(metrics);
} catch (error) {
console.error('Failed to refresh dashboard:', error);
}
}
private async fetchLatestMetrics(): Promise<Map<string, number>> {
const metrics = new Map<string, number>();
// This would typically call your analytics API
// For now, we'll simulate with local calculations
metrics.set('dau', await this.calculateDAU());
metrics.set('retention_day1', await this.calculateRetention(1));
metrics.set('retention_day7', await this.calculateRetention(7));
metrics.set('arppu', await this.calculateARPPU());
metrics.set('session_length', await this.calculateAverageSessionLength());
metrics.set('crash_rate', await this.calculateCrashRate());
return metrics;
}
private updateKPIDisplays(metrics: Map<string, number>) {
// Update DAU
const dau = metrics.get('dau') || 0;
if (this.dauLabel) {
this.dauLabel.string = this.formatMetric(dau, 'number');
}
// Update Retention
const retention = metrics.get('retention_day1') || 0;
if (this.retentionLabel) {
this.retentionLabel.string = this.formatMetric(retention, 'percentage');
}
// Update ARPPU
const arppu = metrics.get('arppu') || 0;
if (this.arppuLabel) {
this.arppuLabel.string = this.formatMetric(arppu, 'currency');
}
}
private checkAlerts(metrics: Map<string, number>) {
metrics.forEach((value, metric) => {
const threshold = this._alertThresholds.get(metric);
if (threshold && value < threshold) {
this.triggerAlert(metric, value, threshold);
}
});
}
private triggerAlert(metric: string, value: number, threshold: number) {
console.warn(`Alert: ${metric} is ${value}, below threshold of ${threshold}`);
// Send alert to monitoring system
this.sendAlert({
metric,
current_value: value,
threshold,
severity: 'warning',
timestamp: Date.now()
});
}
private formatMetric(value: number, format: string): string {
switch (format) {
case 'percentage':
return `${(value * 100).toFixed(1)}%`;
case 'currency':
return `$${value.toFixed(2)}`;
case 'duration':
return `${Math.floor(value / 60)}:${(value % 60).toFixed(0).padStart(2, '0')}`;
default:
return value.toLocaleString();
}
}
}
@ccclass('PrivacyCompliantAnalytics')
export class PrivacyCompliantAnalytics extends Component {
@property
enableGDPRCompliance: boolean = true;
@property
enableCCPACompliance: boolean = true;
@property
dataRetentionDays: number = 365;
private _userConsent: ConsentStatus = null;
private _anonymizedTracking: boolean = false;
onLoad() {
this.checkPrivacyCompliance();
this.setupConsentManagement();
}
private checkPrivacyCompliance() {
const userLocation = this.getUserLocation();
// GDPR applies to EU users
if (this.enableGDPRCompliance && this.isEUUser(userLocation)) {
this.enableGDPRMode();
}
// CCPA applies to California users
if (this.enableCCPACompliance && this.isCaliforniaUser(userLocation)) {
this.enableCCPAMode();
}
}
private enableGDPRMode() {
// Request explicit consent
this.requestUserConsent([
'analytics',
'personalization',
'marketing'
]);
}
async requestUserConsent(purposes: string[]): Promise<ConsentStatus> {
// Implementation would show consent dialog
// This is a simplified version
const consent = await this.showConsentDialog(purposes);
this._userConsent = consent;
// Configure analytics based on consent
this.configureAnalyticsBasedOnConsent(consent);
return consent;
}
private configureAnalyticsBasedOnConsent(consent: ConsentStatus) {
if (!consent.analytics) {
// Disable all analytics
this.disableAnalytics();
} else if (!consent.personalization) {
// Enable anonymous analytics only
this._anonymizedTracking = true;
}
// Save consent record
this.saveConsentRecord(consent);
}
trackEventWithPrivacy(eventName: string, parameters: any = {}) {
// Check consent first
if (!this.hasAnalyticsConsent()) {
return;
}
// Anonymize data if required
if (this._anonymizedTracking) {
parameters = this.anonymizeParameters(parameters);
}
// Add privacy metadata
parameters._privacy_compliant = true;
parameters._anonymized = this._anonymizedTracking;
parameters._consent_version = this._userConsent?.version;
// Track the event
this.trackEvent(eventName, parameters);
}
private anonymizeParameters(parameters: any): any {
const anonymized = { ...parameters };
// Remove or hash PII
delete anonymized.user_id;
delete anonymized.email;
delete anonymized.device_id;
// Hash IP address
if (anonymized.ip_address) {
anonymized.ip_address = this.hashIP(anonymized.ip_address);
}
return anonymized;
}
async handleDataDeletionRequest(userId: string): Promise<boolean> {
try {
// Remove user data from analytics
await this.deleteUserAnalyticsData(userId);
// Remove from local storage
this.clearUserLocalData(userId);
// Log deletion for compliance
this.logDataDeletion(userId);
return true;
} catch (error) {
console.error('Failed to delete user data:', error);
return false;
}
}
}
Trigger: Backend analytics integration needed Handoff: "Analytics events defined. Backend integration needed for: [data collection/API endpoints]"
Trigger: Analytics-driven UX optimization needed Handoff: "Analytics data collected. UX optimization needed based on: [user behavior patterns]"
Trigger: Analytics security needed Handoff: "Analytics system implemented. Security measures needed for: [data protection/privacy]"
Trigger: Analytics performance optimization needed Handoff: "Analytics tracking active. Performance optimization needed for: [tracking overhead/data processing]"