agentsclimarketplace

Product analytics

Skill vibeeval/vibecosystem/skills/product-analytics

AI software team for Claude Code - 138 agents, 295 skills, 73 hooks. Self-learning, multi-agent swarm, autonomous skill evolution.

Install
npx -y skills add vibeeval/vibecosystem --skill product-analytics

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Product analytics - event taxonomy, funnel analysis, A/B testing, retention metrikleri.

SKILL.md

19.1 KB, as published. Nobody here has run it

Product Analytics

Event Taxonomy Design

Event Naming Convention

<object>_<action>

Ornekler:
  user_signed_up
  page_viewed
  button_clicked
  feature_activated
  subscription_started
  payment_completed
  item_added_to_cart
  search_performed

Event Schema (TypeScript)

interface AnalyticsEvent {
  event_name: string;
  timestamp: string;           // ISO 8601
  user_id: string;
  anonymous_id?: string;       // pre-auth tracking
  session_id: string;
  properties: Record<string, unknown>;
  context: EventContext;
}

interface EventContext {
  app_version: string;
  platform: "web" | "ios" | "android";
  locale: string;
  timezone: string;
  page_url?: string;
  referrer?: string;
  utm?: UTMParams;
  device?: DeviceInfo;
}

interface UTMParams {
  source?: string;
  medium?: string;
  campaign?: string;
  term?: string;
  content?: string;
}

Event Categories

KategoriOrnek EventlerAmac
Identityuser_signed_up, user_logged_inKim?
Navigationpage_viewed, tab_switchedNerede?
Interactionbutton_clicked, form_submittedNe yapti?
Transactionpurchase_completed, subscription_startedPara akisi
Featurefeature_activated, feature_usedDeger bulma
Systemerror_occurred, api_timeoutSaglik

Tracking Plan Template

const trackingPlan = {
  "user_signed_up": {
    description: "Kullanici kayit tamamladi",
    properties: {
      method: { type: "string", enum: ["email", "google", "github"], required: true },
      referral_code: { type: "string", required: false },
      plan: { type: "string", enum: ["free", "pro", "enterprise"], required: true },
    },
    triggers: ["Registration form submit"],
    owner: "growth-team",
  },
  "feature_activated": {
    description: "Kullanici bir feature'u ilk kez kulandi",
    properties: {
      feature_name: { type: "string", required: true },
      activation_method: { type: "string", required: true },
      time_since_signup_hours: { type: "number", required: true },
    },
    triggers: ["First use of any tracked feature"],
    owner: "product-team",
  },
};

AARRR Funnel (Pirate Metrics)

Funnel Tanimlari

Acquisition  --> Activation  --> Retention  --> Revenue  --> Referral
(Edinme)        (Aktiflesme)    (Tutunma)     (Gelir)      (Yonlendirme)
StageTanimOrnek MetrikHedef
AcquisitionKullanici siteye geldiUnique visitors, signup rate%3-5 signup
Activation"Aha moment" yasandiOnboarding completion, first value%40-60 activation
RetentionGeri geldiD1/D7/D30 retentionD7 > %20
RevenuePara odediConversion to paid, ARPU%2-5 conversion
ReferralBaskasini getirdiInvite sent, viral coefficientK > 0.5

Funnel Analysis Query

-- Acquisition -> Activation -> Retention funnel
WITH funnel AS (
  SELECT
    user_id,
    MIN(CASE WHEN event = 'user_signed_up' THEN timestamp END) AS signed_up_at,
    MIN(CASE WHEN event = 'onboarding_completed' THEN timestamp END) AS activated_at,
    MIN(CASE WHEN event = 'feature_used' AND day_number >= 7 THEN timestamp END) AS retained_at,
    MIN(CASE WHEN event = 'subscription_started' THEN timestamp END) AS converted_at
  FROM events
  WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days'
  GROUP BY user_id
)
SELECT
  COUNT(signed_up_at) AS acquisitions,
  COUNT(activated_at) AS activations,
  ROUND(100.0 * COUNT(activated_at) / NULLIF(COUNT(signed_up_at), 0), 1) AS activation_rate,
  COUNT(retained_at) AS retained,
  ROUND(100.0 * COUNT(retained_at) / NULLIF(COUNT(activated_at), 0), 1) AS retention_rate,
  COUNT(converted_at) AS converted,
  ROUND(100.0 * COUNT(converted_at) / NULLIF(COUNT(retained_at), 0), 1) AS conversion_rate
FROM funnel;

Cohort Analysis

Retention Cohort Query

-- Weekly retention cohort
WITH user_cohort AS (
  SELECT
    user_id,
    DATE_TRUNC('week', MIN(timestamp)) AS cohort_week
  FROM events
  WHERE event = 'user_signed_up'
  GROUP BY user_id
),
user_activity AS (
  SELECT
    e.user_id,
    uc.cohort_week,
    DATE_TRUNC('week', e.timestamp) AS activity_week,
    (DATE_TRUNC('week', e.timestamp) - uc.cohort_week) / 7 AS week_number
  FROM events e
  JOIN user_cohort uc ON e.user_id = uc.user_id
)
SELECT
  cohort_week,
  week_number,
  COUNT(DISTINCT user_id) AS active_users,
  ROUND(100.0 * COUNT(DISTINCT user_id) /
    FIRST_VALUE(COUNT(DISTINCT user_id)) OVER (
      PARTITION BY cohort_week ORDER BY week_number
    ), 1) AS retention_pct
FROM user_activity
GROUP BY cohort_week, week_number
ORDER BY cohort_week, week_number;

Cohort Visualization Data

interface CohortData {
  cohort: string;         // "2026-W01"
  size: number;           // cohort buyuklugu
  retention: number[];    // [100, 45, 32, 28, 25, 23, 22, 21]
}

function buildCohortTable(cohorts: CohortData[]): string[][] {
  const header = ["Cohort", "Size", "W0", "W1", "W2", "W3", "W4", "W5", "W6", "W7"];
  const rows = cohorts.map(c => [
    c.cohort,
    String(c.size),
    ...c.retention.map(r => `${r}%`),
  ]);
  return [header, ...rows];
}

A/B Testing Framework

Experiment Design

interface Experiment {
  id: string;
  name: string;
  hypothesis: string;                    // "X degisikligi Y metrigini Z kadar arttirir"
  primary_metric: string;                // tek bir karar metrigi
  secondary_metrics: string[];
  guardrail_metrics: string[];           // bozulmamasi gereken metrikler
  variants: Variant[];
  traffic_allocation: number;            // %10-50 arasi basla
  min_sample_size: number;
  min_duration_days: number;
  status: "draft" | "running" | "analyzing" | "completed";
}

interface Variant {
  id: string;
  name: string;                          // "control" | "treatment_a" | "treatment_b"
  weight: number;                        // 0.5 = %50
  description: string;
}

Sample Size Calculator

function calculateSampleSize(
  baselineRate: number,       // mevcut conversion rate (0.05 = %5)
  mde: number,                // minimum detectable effect (0.10 = %10 relative)
  alpha: number = 0.05,       // significance level
  power: number = 0.80        // statistical power
): number {
  const p1 = baselineRate;
  const p2 = baselineRate * (1 + mde);
  const zAlpha = 1.96;        // two-tailed
  const zBeta = 0.84;

  const pooledP = (p1 + p2) / 2;
  const numerator = Math.pow(
    zAlpha * Math.sqrt(2 * pooledP * (1 - pooledP)) +
    zBeta * Math.sqrt(p1 * (1 - p1) + p2 * (1 - p2)),
    2
  );
  const denominator = Math.pow(p1 - p2, 2);

  return Math.ceil(numerator / denominator);
}

// Ornek: %5 baseline, %10 relative MDE
// calculateSampleSize(0.05, 0.10) => ~31,000 per variant

Statistical Significance Check

function checkSignificance(
  controlConversions: number,
  controlTotal: number,
  treatmentConversions: number,
  treatmentTotal: number
): { significant: boolean; pValue: number; lift: number; ci: [number, number] } {
  const p1 = controlConversions / controlTotal;
  const p2 = treatmentConversions / treatmentTotal;
  const pooledP = (controlConversions + treatmentConversions) / (controlTotal + treatmentTotal);
  const se = Math.sqrt(pooledP * (1 - pooledP) * (1 / controlTotal + 1 / treatmentTotal));
  const z = (p2 - p1) / se;
  const pValue = 2 * (1 - normalCDF(Math.abs(z)));
  const lift = (p2 - p1) / p1;
  const liftSE = Math.sqrt(p2 * (1 - p2) / treatmentTotal + p1 * (1 - p1) / controlTotal) / p1;
  const ci: [number, number] = [lift - 1.96 * liftSE, lift + 1.96 * liftSE];

  return {
    significant: pValue < 0.05,
    pValue: Math.round(pValue * 10000) / 10000,
    lift: Math.round(lift * 10000) / 10000,
    ci,
  };
}

Feature Adoption Metrics

Adoption Funnel

Aware --> Tried --> Adopted --> Power User
  |        |         |            |
  v        v         v            v
Feature   First    Regular     Advanced
exposed   use      use (3+)    patterns

Adoption Tracking

interface FeatureAdoption {
  feature_name: string;
  aware_users: number;          // feature'u goren
  tried_users: number;          // 1 kez kullanan
  adopted_users: number;        // 3+ kez kullanan (haftalik)
  power_users: number;          // advanced kullanim yapan
  trial_rate: number;           // tried / aware
  adoption_rate: number;        // adopted / tried
  time_to_adopt_median: number; // gun cinsinden
}

Feature Adoption Query

SELECT
  feature_name,
  COUNT(DISTINCT CASE WHEN times_used >= 1 THEN user_id END) AS tried,
  COUNT(DISTINCT CASE WHEN times_used >= 3 THEN user_id END) AS adopted,
  COUNT(DISTINCT CASE WHEN times_used >= 10 THEN user_id END) AS power_users,
  ROUND(AVG(CASE WHEN times_used >= 3
    THEN EXTRACT(EPOCH FROM adopted_at - first_used_at) / 86400
  END), 1) AS median_days_to_adopt
FROM (
  SELECT
    user_id,
    properties->>'feature_name' AS feature_name,
    COUNT(*) AS times_used,
    MIN(timestamp) AS first_used_at,
    MIN(CASE WHEN rn >= 3 THEN timestamp END) AS adopted_at
  FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id, properties->>'feature_name' ORDER BY timestamp) AS rn
    FROM events WHERE event = 'feature_used'
  ) sub
  GROUP BY user_id, properties->>'feature_name'
) adoption
GROUP BY feature_name;

User Segmentation

RFM Segmentation

-- Recency, Frequency, Monetary segmentation
WITH rfm AS (
  SELECT
    user_id,
    CURRENT_DATE - MAX(event_date)::date AS recency_days,
    COUNT(DISTINCT event_date) AS frequency,
    COALESCE(SUM(revenue), 0) AS monetary
  FROM events
  WHERE timestamp >= CURRENT_DATE - INTERVAL '90 days'
  GROUP BY user_id
),
rfm_scored AS (
  SELECT *,
    NTILE(5) OVER (ORDER BY recency_days DESC) AS r_score,
    NTILE(5) OVER (ORDER BY frequency) AS f_score,
    NTILE(5) OVER (ORDER BY monetary) AS m_score
  FROM rfm
)
SELECT
  user_id,
  CASE
    WHEN r_score >= 4 AND f_score >= 4 THEN 'Champion'
    WHEN r_score >= 3 AND f_score >= 3 THEN 'Loyal'
    WHEN r_score >= 4 AND f_score <= 2 THEN 'New Customer'
    WHEN r_score <= 2 AND f_score >= 3 THEN 'At Risk'
    WHEN r_score <= 2 AND f_score <= 2 THEN 'Hibernating'
    ELSE 'Potential Loyalist'
  END AS segment,
  r_score, f_score, m_score
FROM rfm_scored;

Behavioral Segments

SegmentTanimAksiyon
Power UsersGunluk aktif, 5+ feature kullananFeedback al, beta tester yap
RegularHaftalik aktif, core feature kullananYeni feature'lari tanitit
CasualAylik aktif, tek feature kullananOnboarding iyilestir
At Risk14+ gun inaktif, onceden aktiftiWin-back email gonder
Dormant30+ gun inaktifRe-engagement kampanyasi
NewSon 7 gunde kayit olmusOnboarding optimize et

Mixpanel/Amplitude/PostHog Event Schema

Provider-Agnostic Tracker

interface AnalyticsProvider {
  track(event: string, properties?: Record<string, unknown>): void;
  identify(userId: string, traits?: Record<string, unknown>): void;
  page(name: string, properties?: Record<string, unknown>): void;
  group(groupId: string, traits?: Record<string, unknown>): void;
  reset(): void;
}

class Analytics {
  private providers: AnalyticsProvider[] = [];

  addProvider(provider: AnalyticsProvider): void {
    this.providers.push(provider);
  }

  track(event: string, properties?: Record<string, unknown>): void {
    const enriched = {
      ...properties,
      timestamp: new Date().toISOString(),
      session_id: this.getSessionId(),
      app_version: this.getAppVersion(),
    };
    this.providers.forEach(p => p.track(event, enriched));
  }

  identify(userId: string, traits?: Record<string, unknown>): void {
    this.providers.forEach(p => p.identify(userId, traits));
  }

  private getSessionId(): string { /* session management */ return ""; }
  private getAppVersion(): string { return process.env.APP_VERSION || "unknown"; }
}

// PostHog implementation
class PostHogProvider implements AnalyticsProvider {
  track(event: string, properties?: Record<string, unknown>): void {
    posthog.capture(event, properties);
  }
  identify(userId: string, traits?: Record<string, unknown>): void {
    posthog.identify(userId, traits);
  }
  page(name: string, properties?: Record<string, unknown>): void {
    posthog.capture("$pageview", { page_name: name, ...properties });
  }
  group(groupId: string, traits?: Record<string, unknown>): void {
    posthog.group("company", groupId, traits);
  }
  reset(): void {
    posthog.reset();
  }
}

DAU/MAU/WAU Tracking

Engagement Ratio Query

-- DAU/MAU ratio (stickiness)
WITH daily AS (
  SELECT DATE_TRUNC('day', timestamp) AS day, COUNT(DISTINCT user_id) AS dau
  FROM events WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days'
  GROUP BY 1
),
monthly AS (
  SELECT COUNT(DISTINCT user_id) AS mau
  FROM events WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days'
)
SELECT
  d.day,
  d.dau,
  m.mau,
  ROUND(100.0 * d.dau / m.mau, 1) AS stickiness_pct
FROM daily d CROSS JOIN monthly m
ORDER BY d.day;

-- Benchmark: stickiness > %20 iyi, > %50 mukemmel (social apps)

Retention Curves

Retention Query (Day-based)

SELECT
  day_number,
  COUNT(DISTINCT user_id) AS returning_users,
  ROUND(100.0 * COUNT(DISTINCT user_id) /
    (SELECT COUNT(DISTINCT user_id) FROM events
     WHERE event = 'user_signed_up'
     AND timestamp >= CURRENT_DATE - INTERVAL '90 days'), 1) AS retention_pct
FROM (
  SELECT
    e.user_id,
    (e.timestamp::date - u.signup_date::date) AS day_number
  FROM events e
  JOIN (
    SELECT user_id, MIN(timestamp) AS signup_date
    FROM events WHERE event = 'user_signed_up'
    AND timestamp >= CURRENT_DATE - INTERVAL '90 days'
    GROUP BY user_id
  ) u ON e.user_id = u.user_id
) days
WHERE day_number IN (0, 1, 3, 7, 14, 30, 60, 90)
GROUP BY day_number
ORDER BY day_number;

Retention Benchmarks

Urun TipiD1D7D30D90
SaaS B2B%80%60%45%35
SaaS B2C%40%20%10%5
Mobile App%35%15%6%3
E-commerce%25%12%5%2
Social/Community%50%30%15%10

LTV Calculation

Simple LTV

function calculateLTV(
  arpu: number,              // Average Revenue Per User (aylik)
  grossMargin: number,       // %70 = 0.70
  churnRate: number           // aylik churn %5 = 0.05
): number {
  // LTV = ARPU * Gross Margin / Churn Rate
  return (arpu * grossMargin) / churnRate;
}

// Ornek: $50 ARPU, %80 margin, %5 churn
// LTV = 50 * 0.80 / 0.05 = $800

Cohort-based LTV

SELECT
  cohort_month,
  months_since_signup,
  SUM(revenue) AS cumulative_revenue,
  COUNT(DISTINCT user_id) AS cohort_size,
  ROUND(SUM(revenue) / COUNT(DISTINCT user_id), 2) AS ltv_per_user
FROM (
  SELECT
    u.cohort_month,
    e.user_id,
    EXTRACT(MONTH FROM AGE(e.timestamp, u.signup_date)) AS months_since_signup,
    SUM(e.revenue) OVER (
      PARTITION BY e.user_id ORDER BY e.timestamp
    ) AS revenue
  FROM events e
  JOIN (
    SELECT user_id, MIN(timestamp) AS signup_date,
           DATE_TRUNC('month', MIN(timestamp)) AS cohort_month
    FROM events WHERE event = 'user_signed_up'
    GROUP BY user_id
  ) u ON e.user_id = u.user_id
  WHERE e.revenue > 0
) ltv
GROUP BY cohort_month, months_since_signup
ORDER BY cohort_month, months_since_signup;

LTV:CAC Ratio

RatioAnlamAksiyon
< 1:1Para kaybediyorsunAcil: CAC dusur veya retention artir
1:1 - 3:1Basabas veya az karliOptimize et
3:1 - 5:1SaglikliBuyumeye yatirim yap
> 5:1Cok iyi ama belki az harciyorsunDaha agresif buyume dene

Churn Prediction Signals

Early Warning Signals

interface ChurnSignal {
  signal: string;
  weight: number;        // 0-1, yuksek = guclu sinyal
  threshold: string;
  action: string;
}

const churnSignals: ChurnSignal[] = [
  {
    signal: "login_frequency_drop",
    weight: 0.9,
    threshold: "Son 7 gun login < onceki 7 gunun %50'si",
    action: "Re-engagement email + in-app mesaj",
  },
  {
    signal: "feature_usage_decline",
    weight: 0.8,
    threshold: "Core feature kullanimi %60 dustu",
    action: "Proaktif CS outreach",
  },
  {
    signal: "support_ticket_spike",
    weight: 0.7,
    threshold: "Son 14 gunde 3+ ticket",
    action: "CS manager escalation",
  },
  {
    signal: "no_team_invite",
    weight: 0.6,
    threshold: "30 gundur takim uyesi eklemedi",
    action: "Collaboration feature highlight",
  },
  {
    signal: "billing_page_visit",
    weight: 0.5,
    threshold: "Billing/cancel sayfasini 2+ kez ziyaret",
    action: "Retention offer popup",
  },
];

Churn Score Query

SELECT
  user_id,
  ROUND(
    0.3 * CASE WHEN days_since_last_login > 7 THEN 1 ELSE days_since_last_login / 7.0 END +
    0.25 * CASE WHEN feature_usage_change < -0.5 THEN 1 ELSE ABS(LEAST(feature_usage_change, 0)) * 2 END +
    0.20 * CASE WHEN support_tickets_14d >= 3 THEN 1 ELSE support_tickets_14d / 3.0 END +
    0.15 * CASE WHEN team_size <= 1 THEN 1 ELSE 0 END +
    0.10 * CASE WHEN visited_cancel_page THEN 1 ELSE 0 END
  , 2) AS churn_risk_score
FROM user_health_metrics
ORDER BY churn_risk_score DESC;

Dashboard KPI Template

Executive Dashboard

KategoriMetrikHedefFormul
GrowthMRR+10% MoMsum(active_subscriptions * price)
GrowthNew Signups+15% MoMcount(user_signed_up)
EngagementDAU/MAU> %25daily_active / monthly_active
EngagementAvg Session Duration> 5 minavg(session_end - session_start)
RetentionD7 Retention> %25returning_d7 / signed_up
RetentionNet Revenue Retention> %110(MRR + expansion - contraction - churn) / MRR_prev
RevenueLTV> 3x CACARPU * margin / churn_rate
RevenueARPU+5% QoQtotal_revenue / active_users
HealthNPS> 50promoters_pct - detractors_pct
HealthChurn Rate< %5churned_users / start_of_month_users

Anti-Patterns

Anti-PatternDogru Yol
Her seyi track etmekSorulari belirle, sonra event tanimla
Event isimlerinde tutarsizlikNaming convention + tracking plan
A/B test'i erken bitirmekSample size ve duration hesapla
Vanity metrics'e odaklanmakActionable metrikler sec
Segmentsiz analizHer metrigi segmentlere bol
Tek retention metrigiD1/D7/D30 + cohort bazli bak

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.