Files
sp/sase-posthog-events-prd.md
Semih fc6a3f7a39 feat(phase6a-v1.2): dual-mode custom-event enrichment (v1.0 + v2.0 aliases)
- Prisma: posthog_person_cache (24h TTL), posthog_group_cache, session_custom_events
- SessionMeta: +posthogDistinctId, +groupKey, +customEventCount
- PostHog client: listSessionEvents, getPerson, getGroup
- posthog-cache.ts: cache-with-stale-refresh for person + group properties
- event-taxonomy.ts: dual-mode alias map (vin_decoded↔vin_decode_initiated,
  vin_decode_success↔vin_decode_succeeded, vin_decode_error↔vin_decode_failed,
  payment_success↔payment_succeeded). 56 tracked event names total.
- enrich.ts: per-session custom events fetch, persist to session_custom_events,
  attach user_properties + group_properties via cache.
- tagger.ts: 14 new custom-event tags
  (vin_decode_fail_pattern, provider_reliability_issue, payment_friction,
   payment_ui_silent_failure (P0), payment_failed_session, checkout_abandonment,
   downgrade_pending, compatibility_quality_gap, parts_export_abandoned,
   search_validation_friction, onboarding_stuck, upgrade_hesitation,
   power_user_path, at_risk_active_session, provider_mismatch,
   api_key_friction, webhook_setup_struggle); group-context priority bump.
- heuristic.ts: customEventPromoteReasons() — 10 custom-event promote signals.
- compress.ts: merge_rrweb_and_custom_events interleaving with ⤷ EVENT: prefix,
  ±1.5s grouping, standalone flush for events with no nearby rrweb. Adds
  company_context header line; key_events lists top-5 custom event counts.
- /insights page: +Events column. /insights/sessions/[id]: collapsible custom
  events table + group key display.

Graceful: when Sase.tr emits 0 custom events the pipeline falls back to
rrweb-only tagging from Phase 6a v1.1.

PRD: behavioral-insight-pipeline-prd-1.md (v1.2),
     sase-posthog-events-prd.md (Sase.tr-side, separate codebase).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 22:17:18 +00:00

1340 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sase.tr — PostHog Event Taxonomy & Implementation PRD
**Proje Kodu:** SASE-PH-001
**Pilot:** Süper Panel Behavioral Insight Pipeline (BIP-001) Phase 6 Prerequisite
**Doküman Türü:** Implementation PRD (Sase.tr codebase work)
**Durum:** Draft v1.0
**Sahip:** Founder (Thinxtra SaaS Studio)
**Hedef Branch:** `feature/posthog-event-expansion`
---
## İçindekiler
1. [Vizyon ve Bağlam](#1-vizyon-ve-bağlam)
2. [Hedefler ve Hedef Olmayanlar](#2-hedefler-ve-hedef-olmayanlar)
3. [Mevcut Event Inventory ve Değerlendirme](#3-mevcut-event-inventory-ve-değerlendirme)
4. [Identification Strategy (User + Group)](#4-identification-strategy-user--group)
5. [Yeni Event Inventory](#5-yeni-event-inventory)
6. [Event Schema Standardı](#6-event-schema-standardı)
7. [Mevcut Event İyileştirmeleri](#7-mevcut-event-iyileştirmeleri)
8. [Data Privacy ve KVKK Uyumu](#8-data-privacy-ve-kvkk-uyumu)
9. [Implementation Plan](#9-implementation-plan)
10. [Testing Strategy](#10-testing-strategy)
11. [Migration ve Backward Compatibility](#11-migration-ve-backward-compatibility)
12. [PostHog Dashboard Setup](#12-posthog-dashboard-setup)
13. [Documentation Standardı](#13-documentation-standardı)
14. [Riskler](#14-riskler)
15. [Karar Logu](#15-karar-logu)
---
## 1. Vizyon ve Bağlam
### Bu PRD Neden Var
Süper Panel Behavioral Insight Pipeline (BIP) Phase 6 pilot'u **Sase.tr** üzerinde çalışacak. Pilot başlangıcında pipeline'ın aşağı yukarı 4 katmandan oluşacağı planlandı: ingestion → tagging → compression → LLM. Ancak gerçek değer üretmek için **giriş katmanındaki sinyal kalitesi** belirleyici.
Sase.tr'de mevcut PostHog event yapısı **iskelet olarak hazır** ama insight pipeline için **6 kritik alanda gap** var:
1. VIN decode performance context yok (timing, retry, provider detail)
2. Parts/compatibility layer'da neredeyse hiç event yok
3. Multi-provider fallback visibility yok
4. Search behavior pre-submit görünmez
5. Feature discovery pattern'leri yok
6. User identification + group analytics belirsiz
Pipeline bu gap'ler kapatılmadan başlatılırsa:
- Insight'lar generic ve actionable olmaz
- LLM hallucination riski artar (eksik context'i tahminle doldurmaya çalışır)
- Cost-per-validated-insight hedefi tutturulamaz
- KVKK compliance riski (data-private audit yapılmadan)
### Kapsam
Bu PRD **sadece Sase.tr codebase work**'tür. Süper Panel tarafı (ingestion, compression, LLM) ayrı PRD'de (BIP-001) ele alınır.
İçerik:
- Identification flow audit ve update
- 35+ yeni event eklemesi (3 fazda)
- 8 mevcut event'in property zenginleştirmesi
- `data-private` attribute audit
- PostHog dashboard ve funnel kurulumu
### Bağımlılık Zinciri
```
[Bu PRD - Sase.tr Event Work]
↓ (output: zengin event stream)
[BIP Phase 6a - Ingestion + Filter]
[BIP Phase 6b - LLM Analysis MVP]
[Insight pipeline value]
```
Bu PRD'nin **Faz 0** (blocker) kısmı tamamlanmadan BIP Phase 6a başlatılmamalı. Faz 1 (high-ROI) ise BIP Phase 6a ile **paralel** yürüyebilir.
---
## 2. Hedefler ve Hedef Olmayanlar
### Hedefler
| # | Hedef | Ölçü |
|---|-------|------|
| G1 | BIP pilot'un yüksek-kalite insight üretmesi için gereken event'leri eklemek | Event taxonomy %100 dokümante, 0 ad-hoc capture |
| G2 | B2B account-level analiz için group analytics kurulumu | `posthog.group('company')` tüm authenticated session'larda çalışır |
| G3 | User cohort segmentation kapasitesi | identify() çağrısı plan_tier, signup_date, company_id, total_queries içerir |
| G4 | KVKK uyumlu sensitive data handling | Tüm PII alanları `data-private` veya PostHog mask config'i ile korunur |
| G5 | Mevcut event'lerin geriye dönük kırılmaması | Production funnels/dashboards bozulmaz |
| G6 | Faz 0 (blocker) 2 gün içinde tamamlanır | Phase 6a başlangıç tarihini bloklamaz |
| G7 | Tüm yeni event'ler tek bir taxonomy kaynağında dokümante | `docs/posthog-events.md` tek doğruluk kaynağı |
### Hedef Olmayanlar
- **PostHog SDK migration**: posthog-js / posthog-node mevcut versiyonlar kalır, upgrade ayrı
- **PostHog → diğer analytics geçişi**: Bu PRD'de PostHog tercihi sabit
- **Tüm autocapture event'lerinin manual'a dönüştürülmesi**: PostHog autocapture (pageview, autocapture, rageclick) korunur
- **Backend event coverage'ın frontend'e eşitlenmesi**: Bazı event'ler doğal olarak sadece frontend (UI interaction) veya sadece backend (transaction confirmation)
- **Mobile app event coverage**: Sase.tr mobile yok, varsa ayrı PRD
- **Real-time event processing**: PostHog ingestion latency'si kabul edilir
- **A/B test infrastructure**: Bu PRD'de feature flag setup yok (BIP Phase 6d'de ele alınır)
- **Custom property dictionary'nin uluslararasılaştırılması**: Property anahtarları İngilizce kalır
---
## 3. Mevcut Event Inventory ve Değerlendirme
### 3.1 Mevcut Event Dökümü
Aşağıdaki event'ler şu an Sase.tr'de production'da gönderiliyor:
#### Auth & Kullanıcı Yaşam Döngüsü
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `user_signed_up` | `method: "email" \| "google"` | `_auth/register.tsx:41,78` | ✅ İyi durumda |
| `user_logged_in` | `method: "email" \| "google"` | `_auth/login.tsx:42,65` | ✅ İyi durumda |
| `user_logged_out` | `reason?: "account_deleted"` | `dashboard.tsx:148`, `settings-content.tsx:136` | ✅ İyi durumda |
#### VIN Decode (Search)
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `vin_decoded` | `vin` | `search.tsx:142` | ⚠️ Naming muğlak (initiated mi success mi?), context fakir |
| `vin_decode_success` | `vin, vehicle_id, source?` | `search.tsx:166`, `index.tsx:529` | ⚠️ Performance context yok, source belirsiz |
| `vin_decode_error` | `vin, error, source?` | `search.tsx:174`, `index.tsx:534` | ⚠️ Error code yok, retry context yok |
| `vin_decode_candidates` | (çoklu eşleşme modal) | `search.tsx:157` | ⚠️ Candidate sayısı/yapısı yok |
| `vin_decode_candidate_selected` | (seçim) | `search.tsx:225` | ⚠️ Hangi candidate seçildi (index, confidence) yok |
#### Subscription / Pricing Funnel
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `plan_selected` | `plan` | `subscription/index.tsx:326` | ⚠️ Context fakir (price, period?) |
| `yearly_toggle_clicked` | `period: "monthly" \| "yearly"` | `:1039, 1050` | ✅ |
| `checkout_started` | `plan, period` | `:344` | ⚠️ Coming-from context yok |
| `trial_started` | yok | `:936` | ⚠️ Plan offered yok |
| `subscription_cancelled` | yok | `:897` | ⚠️ Cancellation reason yok |
| `subscription_resumed` | yok | `:737` | ⚠️ Time-away yok |
| `cancel_flow_viewed` | yok | `:876` | ✅ |
| `cancel_save_clicked` | yok | `:268` | ⚠️ Hangi save offer kabul edildi? |
| `downgrade_offer_shown` | yok | `:364` | ⚠️ Which offer? |
| `downgrade_offer_accepted` | yok | `:264` | ⚠️ Which? |
| `downgrade_offer_declined` | yok | `:827` | ⚠️ Which? |
#### Trial Urgency Banner (FN-306)
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `trial_urgency_banner_viewed` | yok | `subscription/index.tsx:430`, `trial-urgency-banner.tsx:77` | ⚠️ Days left to trial end yok |
| `trial_urgency_banner_cta_clicked` | yok | `:492, :97` | ⚠️ Same |
| `trial_urgency_banner_dismissed` | yok | `:501, :87` | ⚠️ Same |
#### Payment
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `payment_initiated` | `method: "iyzico" \| "eft", plan, period, amount` | `payment-content.tsx:196,202` | ✅ İyi |
| `payment_success` | (dual: FE+BE) | `payment-content.tsx:73,135`, `payments.service.ts:122,214` | ⚠️ Dual-capture clarify gerekli |
| `payment_failed` | (dual: FE+BE) | `payment-content.tsx:76`, `payments.service.ts:130,237` | ⚠️ Failure reason zayıf |
| `receipt_uploaded` | `payment_id` | `payment-content.tsx:209` | ✅ |
#### Schema / Parts
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `oem_code_copied` | `oem_code, part_id, ...` | `parts-panel.tsx:52` | ⚠️ **Tek parts event'i**, layer'ın geri kalanı görünmez |
#### Social Proof
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|-------|------------|---------|---------------------------|
| `social_proof_impression` | `page, section?` | `_auth.tsx:23`, `index.tsx:572,589,606`, `subscription/index.tsx:456,473` | ✅ |
| `social_proof_engaged` | `page, section?` | `_auth.tsx:33`, `index.tsx:616`, `subscription/index.tsx:483,487` | ✅ |
### 3.2 Genel Değerlendirme
**Güçlü yönler:**
- Subscription/pricing funnel disiplinli (downgrade offer zinciri özellikle değerli)
- VIN decode için ayrı success/error/candidates ayrımı doğru pattern
- Auth events çift kanal değil (sadece frontend, duplicate yok)
- Payment dual-capture infrastructure değerli (reliability için), kullanımı netleşince güçlenir
**Genel gap'ler:**
- 11 event'in 0 properties olması (boş capture)
- VIN decode family'sinde performance/context fakirliği
- Parts layer'da single event (Sase.tr'nin core value layer'ı)
- Provider multi-source görünmezliği
- Identification + group analytics belirsizliği
### 3.3 Identification Audit (Önce Doğrulanmalı)
**Bilinmeyenler — Faz 0 öncesi audit gerekli:**
- `posthog.identify(userId, properties)` çağrılıyor mu? Nerede?
- Hangi user property'leri set ediliyor?
- `posthog.group('company', companyId)` çağrılıyor mu? **(Büyük ihtimalle hayır, çünkü event listesinde company_id görünmüyor)**
- Anonymous → authenticated geçişte `posthog.alias()` çağrılıyor mu?
- `posthog.reset()` logout'ta çağrılıyor mu?
Bu beş soruyu Faz 0 başlangıcında 30 dakikalık codebase audit ile cevapla.
---
## 4. Identification Strategy (User + Group)
### 4.1 User Identification
**Hedef:** Her authenticated session başlangıcında `posthog.identify()` çağrılır ve aşağıdaki property setlenir/güncellenir.
#### User Properties Schema
```typescript
type PostHogUserProperties = {
// Identity (set once, $set_once kullan)
signup_date: string; // ISO 8601
signup_method: 'email' | 'google';
// Mutable state (set every login, $set kullan)
email_domain: string; // "domain.com" (full email değil — PII)
plan_tier: 'free' | 'starter' | 'brand_specific' | 'full';
plan_period: 'monthly' | 'yearly' | null;
subscription_status: 'trial' | 'active' | 'cancelled' | 'past_due' | 'none';
trial_ends_at: string | null; // ISO 8601
// B2B
company_id: string | null;
company_role: 'owner' | 'admin' | 'member' | null;
// Behavior aggregates (periyodik update, daily cron)
total_vin_queries: number;
total_vin_queries_last_30d: number;
preferred_provider: 'PL24' | 'PCAT' | 'RMEX' | 'TecDoc' | null;
last_query_at: string | null;
total_logins: number;
days_since_signup: number;
// Feature flags / preferences
language: 'tr' | 'en';
timezone: string; // "Europe/Istanbul"
has_api_keys: boolean;
has_webhooks: boolean;
// Locked properties (asla değişmez)
user_id_internal: string; // Sase.tr internal ID (same as distinct_id genellikle)
}
```
**Önemli kurallar:**
- Email **asla** ham olarak set edilmez (sadece `email_domain`)
- `$set_once` ile setlenen property'ler `signup_date`, `signup_method`
- `$set` ile her identify'da update edilenler diğerleri
- `total_vin_queries` gibi aggregate'ler **backend'den periyodik update** (her event'te güncelleme PostHog hızını yavaşlatır)
#### Identify Çağrı Pattern'i
```typescript
// frontend: src/lib/posthog/identify.ts
import posthog from 'posthog-js'
import { hashEmail } from '@/lib/crypto'
export function identifyUserToPostHog(user: User, company: Company | null) {
posthog.identify(user.id, {
// $set_once
$set_once: {
signup_date: user.createdAt,
signup_method: user.signupMethod,
},
// $set (every call updates)
$set: {
email_domain: user.email.split('@')[1],
plan_tier: user.subscription?.tier ?? 'free',
plan_period: user.subscription?.period ?? null,
subscription_status: user.subscription?.status ?? 'none',
trial_ends_at: user.subscription?.trialEndsAt ?? null,
company_id: company?.id ?? null,
company_role: user.companyRole ?? null,
total_vin_queries: user.stats.totalQueries,
total_vin_queries_last_30d: user.stats.queries30d,
preferred_provider: user.stats.preferredProvider,
last_query_at: user.stats.lastQueryAt,
total_logins: user.stats.totalLogins,
days_since_signup: daysSince(user.createdAt),
language: user.language,
timezone: user.timezone,
has_api_keys: user.apiKeyCount > 0,
has_webhooks: user.webhookCount > 0,
user_id_internal: user.id,
},
})
// Group identification - B2B için kritik
if (company) {
posthog.group('company', company.id, {
name: company.name,
tier: company.tier,
seat_count: company.seatCount,
seats_used: company.seatsUsed,
industry: company.industry ?? 'unknown',
country: company.country ?? 'TR',
signup_date: company.createdAt,
mrr_usd: company.mrrUsd,
total_queries_last_30d: company.stats.queries30d,
})
}
}
```
#### Identify Trigger Noktaları
| Trigger | Çağrı |
|---------|-------|
| Login başarılı | `identifyUserToPostHog(user, company)` |
| User profile update | `identifyUserToPostHog(user, company)` |
| Subscription change | `identifyUserToPostHog(user, company)` |
| Company tier change | `identifyUserToPostHog(user, company)` |
| Logout | `posthog.reset()` ← önemli, anonymous distinct_id yenilenir |
| Anonymous → authenticated geçiş | `posthog.alias(user.id, posthog.get_distinct_id())` çağrısı ardından identify |
### 4.2 Group Analytics (B2B Backbone)
`posthog.group('company', companyId, properties)` çağrısı PostHog'a "bu session bir company context'inde yapılıyor" der. Bu kurulum **B2B insight kalitesini ikiye katlar**.
#### Company Properties Schema
```typescript
type PostHogCompanyGroupProperties = {
name: string;
tier: 'free' | 'starter' | 'brand_specific' | 'full';
seat_count: number;
seats_used: number;
industry: string;
country: 'TR' | 'DE' | 'others';
signup_date: string; // ISO 8601
mrr_usd: number; // monthly recurring revenue
total_queries_last_30d: number;
primary_use_case: 'parts_dealer' | 'auto_service' | 'fleet_manager' | 'unknown';
// Computed nightly (admin DB job)
active_user_count_last_7d: number;
active_user_count_last_30d: number;
churn_risk_score: number; // 0-100, BIP pipeline'a değerli
}
```
#### Group Identification Trigger'ları
Her authenticated event sırasında posthog-js otomatik olarak son group'u event'e ekler. Yani bir kez `posthog.group()` çağrılırsa, sonraki tüm event'ler bu company context'inde gider.
**Kritik nokta:** Multi-tenant kullanıcılar (1 user, multiple companies) varsa **company switch** sırasında re-group çağrılmalı:
```typescript
function switchCompany(newCompanyId: string) {
// ... company switch logic
posthog.group('company', newCompanyId, companyProperties)
posthog.capture('company_switched', {
from_company_id: oldCompanyId,
to_company_id: newCompanyId
})
}
```
### 4.3 Anonymous → Authenticated Continuity
PostHog `posthog.alias()` ile anonymous session'ı authenticated user'a bağlar. Bu signup funnel analizi için kritik:
```typescript
async function onSignupSuccess(newUser: User) {
const anonymousDistinctId = posthog.get_distinct_id()
posthog.alias(newUser.id, anonymousDistinctId)
posthog.identify(newUser.id, /* properties */)
posthog.capture('user_signed_up', { method: 'email' })
}
```
Bu olmadan signup öncesi `/pricing` ziyareti ile signup sonrası ilk VIN sorgusu **ayrı user** gibi görünür, funnel kırılır.
---
## 5. Yeni Event Inventory
Event'ler üç fazda gruplandı: Blocker (Faz 0), High-ROI (Faz 1), Optimization (Faz 2).
### 5.1 Faz 0 — Pilot Blocker Events (ZORUNLU, 2 gün)
Bu event'ler olmadan BIP pipeline anlamlı insight üretmez.
#### Identification Audit + Update
| Item | Description |
|------|-------------|
| Audit | Mevcut `identify()` çağrı noktaları belirlenir |
| Update | Bölüm 4.1'deki property schema'ya göre identify güncellenir |
| Add | `posthog.group('company', ...)` çağrısı eklenir |
| Add | `posthog.alias()` signup flow'a eklenir |
| Add | `posthog.reset()` logout'a eklenir (yoksa) |
#### Parts/Compatibility Events (5 adet, en kritik)
Sase.tr'nin core value layer'ı, mevcut tek event (`oem_code_copied`) yetersiz.
```typescript
// Kullanıcı parts panel'i ilk gördüğünde
posthog.capture('parts_panel_viewed', {
vehicle_id: string,
parts_count: number,
load_time_ms: number,
source_provider: 'PL24' | 'PCAT' | 'RMEX' | 'TecDoc' | 'multi',
})
// Filter uygulandığında
posthog.capture('parts_filter_applied', {
filter_type: 'category' | 'brand' | 'oem_code' | 'price_range' | 'search',
filter_value: string, // sanitize edilmiş (uzun text varsa truncate)
results_count_before: number,
results_count_after: number,
})
// Compatibility check başlatıldığında
posthog.capture('compatibility_check_initiated', {
part_id: string,
target_vehicle_id: string,
source_vehicle_id: string,
})
// Compatibility check tamamlandığında
posthog.capture('compatibility_check_completed', {
part_id: string,
target_vehicle_id: string,
result: 'compatible' | 'incompatible' | 'unknown' | 'partial',
confidence: number, // 0-1
duration_ms: number,
})
// Parts export başlatıldığında
posthog.capture('parts_export_initiated', {
format: 'csv' | 'xlsx' | 'pdf',
row_count: number,
columns_selected: number,
vehicle_id: string,
has_filters_applied: boolean,
})
```
#### Data Privacy Audit
| Item | Effort | Description |
|------|--------|-------------|
| VIN input fields | 1 saat | Tüm VIN input'larına `data-private="true"` |
| Email display | 1 saat | User email render edilen tüm yerlere `data-private` |
| Customer data | 2 saat | B2B customer info render'ları |
| API key display | 30 dk | Generated API key gösterimi |
| `posthog-js` config update | 1 saat | maskAllInputs, maskTextSelector ayarları (BIP PRD Bölüm 15.1) |
### 5.2 Faz 1 — High-ROI Events (Phase 6a paralelinde, 5-7 gün)
#### VIN Decode Performance Context (mevcut events expansion)
Bölüm 7.1'de detaylı.
#### Provider Events (4 adet)
Multi-provider arsenal'inin görünür olması için:
```typescript
posthog.capture('provider_response_received', {
vehicle_id: string,
provider: 'PL24' | 'PCAT' | 'RMEX' | 'TecDoc',
response_time_ms: number,
status: 'success' | 'timeout' | 'error' | 'partial',
data_completeness: number, // 0-1, kaç beklenen field geldi
cached: boolean,
})
posthog.capture('provider_fallback_triggered', {
vehicle_id: string,
from_provider: string,
to_provider: string,
reason: 'timeout' | 'error' | 'incomplete_data' | 'manual',
auto: boolean, // otomatik fallback mi manuel mi
attempt_number: number, // kaçıncı denemede fallback
})
posthog.capture('provider_data_quality_flag', {
vehicle_id: string,
provider: string,
quality_score: number, // 0-100
missing_fields: string[], // ["weight", "year_range"] gibi
warnings: string[],
})
posthog.capture('multi_provider_search_initiated', {
vehicle_id: string,
providers_queried: string[],
parallel: boolean, // parallel mi sequential mi
})
```
#### Search Behavior Events (5 adet)
VIN submit öncesi friction'ı yakalamak için:
```typescript
posthog.capture('search_input_focused', {
field: 'vin' | 'plate' | 'brand_model',
empty: boolean,
})
posthog.capture('search_input_validation_failed', {
field: 'vin' | 'plate',
error_type: 'invalid_format' | 'too_short' | 'too_long' | 'invalid_chars' | 'check_digit_fail',
input_length: number, // VIN'in kendisi DEĞİL, sadece uzunluk
})
posthog.capture('search_paste_detected', {
field: string,
length: number,
source_hint?: 'multiline' | 'with_spaces' | 'clean',
})
posthog.capture('search_history_opened', {
history_count: number,
})
posthog.capture('search_history_item_selected', {
age_days: number, // ne kadar eski bir history item
position: number, // listede kaçıncı
})
```
#### Feature Discovery Events (5 adet)
```typescript
posthog.capture('feature_discovered', {
feature_name: string, // "api_keys", "webhooks", "csv_export"
days_since_signup: number,
discovery_source: 'menu' | 'dashboard' | 'docs' | 'tutorial' | 'modal',
})
posthog.capture('help_clicked', {
context: string, // route path
topic?: string,
})
posthog.capture('docs_link_clicked', {
destination_url: string, // path only, not query
context: string, // route
})
posthog.capture('tutorial_started', {
tutorial_id: string,
trigger: 'auto' | 'manual',
})
posthog.capture('tutorial_step_completed', {
tutorial_id: string,
step_index: number,
step_name: string,
})
posthog.capture('tutorial_abandoned', {
tutorial_id: string,
last_step_index: number,
duration_ms: number,
})
```
#### API Key / Webhook Events (4 adet, integration users için kritik)
```typescript
posthog.capture('api_key_created', {
tier: string,
scope: string[],
expiration_days: number | null,
})
posthog.capture('api_key_revoked', {
age_days: number,
reason?: 'rotation' | 'compromise' | 'unused' | 'manual',
})
posthog.capture('webhook_configured', {
event_types: string[],
endpoint_domain: string, // full URL değil, sadece domain
})
posthog.capture('webhook_delivery_failed', {
webhook_id: string,
event_type: string,
failure_reason: 'timeout' | 'http_error' | 'ssl_error' | 'auth',
retry_count: number,
})
```
### 5.3 Faz 2 — Optimization Events (Phase 6c sonrası, fırsatçı)
Bu event'ler pilot'tan **insight'lar geldiğinde** eklenir, proaktif değil reaktif. LLM "şu event olsaydı daha iyi analiz ederdim" demeye başladığında listeye eklenir.
İlk kestirim listesi:
```typescript
// Feature interaction patterns
posthog.capture('keyboard_shortcut_used', { shortcut, context })
posthog.capture('bulk_action_initiated', { action, item_count })
posthog.capture('settings_changed', { setting_key, value_changed: true })
// Engagement signals
posthog.capture('page_visibility_changed', { hidden: boolean, time_on_page_ms })
posthog.capture('long_idle_detected', { idle_ms })
// Discovery & SEO
posthog.capture('shared_link_opened', { share_source: string })
posthog.capture('search_engine_referral', { engine, query? })
```
---
## 6. Event Schema Standardı
### 6.1 Naming Convention
| Pattern | Kullanım | Örnek |
|---------|----------|--------|
| `<noun>_<verb_past>` | Action / lifecycle event | `plan_selected`, `vin_decode_initiated` |
| `<noun>_<state>` | State change event | `subscription_paused`, `trial_active` |
| `<noun>_<process>_<lifecycle>` | Multi-step process | `payment_initiated`, `payment_completed`, `payment_failed` |
| `<noun>_<ui_action>` | Passive UI interaction | `banner_viewed`, `modal_dismissed`, `cta_clicked` |
#### Lifecycle Verb Standardı
Process event'leri için tutarlı lifecycle verbleri:
| Lifecycle | Anlamı |
|-----------|--------|
| `_initiated` | İşlem başlatıldı, henüz tamamlanmadı |
| `_started` | _initiated ile aynı; tercih: _initiated (network/API context'inde) veya _started (user time context'inde) |
| `_succeeded` | Backend confirmation ile başarılı |
| `_completed` | Multi-step process'in son adımı (success ima eder) |
| `_failed` | Hata ile sonlandı |
| `_cancelled` | Kullanıcı tarafından iptal edildi |
| `_abandoned` | Kullanıcı sessizce ayrıldı (active cancel değil) |
| `_dismissed` | UI element manuel kapatıldı |
### 6.2 Property Naming
```
snake_case kullan (PostHog convention)
boolean property'ler `is_`, `has_`, `was_` prefix'i ile başlamaz; doğal okunur
zamanları ISO 8601 string olarak gönder (`_at` suffix'i)
süreleri `_ms` veya `_seconds` suffix'i ile gönder
count'lar `_count` suffix'i
liste property'leri çoğul (`providers_queried`)
```
### 6.3 Required vs Optional Properties
Her event en az şu **implicit** property'lerle gelir (PostHog otomatik ekler):
- `$lib` (posthog-js vs posthog-node)
- `$browser`, `$os`, `$device_type`
- `$current_url`, `$pathname`
- `distinct_id`, `$session_id`
- `timestamp`
Bizim event-specific property'lerimiz:
- **Required** property'ler her event'te zorunlu, eksikse capture **silinmez** (gönderilmemiş gibi davran)
- **Optional** property'ler bilinmiyorsa **omit edilir** (null göndermek yerine)
### 6.4 PII / Sensitive Data Rules
| Veri | İzin | Saklama Şekli |
|------|------|----------------|
| VIN | ✅ Property olabilir | Property'de full VIN; rrweb recording'de masked |
| Email | ❌ Full email yok | `email_domain` property'si veya hash |
| Telefon | ❌ Full yok | Sadece operator code `5XX` |
| TC Kimlik | ❌ Asla | Hiçbir event'te kullanılmaz |
| Plaka | ⚠️ Conditional | Property'de masked (`34***123`) |
| Auth token / API key | ❌ Asla | Hiçbir event'te kullanılmaz |
| Kredi kartı | ❌ Asla | Hiçbir event'te kullanılmaz |
| Password | ❌ Asla | Hiçbir event'te kullanılmaz |
| IP address | ✅ PostHog otomatik | Geo'ya çevrilir, raw IP saklanmaz |
| User Agent | ✅ PostHog otomatik | |
| URL Path | ✅ | Query string KVKK risk taşırsa filtrele |
| Query string | ⚠️ Conditional | `?vin=XXX` gibi PII içerebilir, filtrele |
**Önemli:** PostHog SDK config'inde `property_denylist` ile bazı property'ler otomatik silinir:
```typescript
posthog.init(KEY, {
property_denylist: ['$ip', 'email', 'phone', 'tc_kimlik', 'password', 'token'],
})
```
VIN property olarak event'te kalabilir çünkü ürünün core'u, ama rrweb recording'inde mask edilir.
### 6.5 Property Value Length Limits
PostHog property limit'i 8KB. Pratikte:
- String property < 500 karakter (long text varsa truncate + `_truncated: true` flag)
- Array property < 50 element
- Nested object property mümkün ama düz tutmak tercih edilir (PostHog filter UX için)
### 6.6 Versioning
Event schema breaking change yapılırsa **rename** edilir, asla aynı isimle property değiştirmez:
```
v1: payment_succeeded { amount, currency }
v2: payment_succeeded_v2 { amount_cents, currency, provider }
```
1 ay paralel kalır, sonra v1 deprecated. PostHog'da v1 event'leri kapanır (kayıt edilmez), dashboard v2'ye migrate edilir.
---
## 7. Mevcut Event İyileştirmeleri
### 7.1 `vin_decode_*` Family Expansion
| Mevcut | Yeni |
|--------|------|
| `vin_decoded { vin }` | `vin_decode_initiated { vin, vin_brand?, query_source: 'manual' \| 'history' \| 'paste' \| 'qr', has_provider_preference: boolean }` |
| `vin_decode_success { vin, vehicle_id, source? }` | `vin_decode_succeeded { vin, vehicle_id, provider, response_time_ms, cache_hit: boolean, ml_decoder_used: boolean, ml_confidence?: number, retry_count: number, partial_result: boolean, fallback_used: boolean }` |
| `vin_decode_error { vin, error, source? }` | `vin_decode_failed { vin, error_code, error_message, provider_attempted, response_time_ms, retry_count, will_auto_retry: boolean }` |
| `vin_decode_candidates` (no props) | `vin_decode_candidates_shown { vin, candidates_count, top_confidence: number }` |
| `vin_decode_candidate_selected` (no props) | `vin_decode_candidate_selected { vin, selected_index: number, selected_confidence: number, time_to_select_ms: number }` |
**Migration:** Bölüm 11'deki rename stratejisi (paralel kalma → deprecate).
### 7.2 Subscription Funnel Granularity
| Mevcut | Yeni |
|--------|------|
| `plan_selected { plan }` | `plan_selected { plan, period, price_usd, position_in_grid: number, source: 'pricing_page' \| 'modal' \| 'upgrade_prompt' }` |
| `checkout_started { plan, period }` | `checkout_started { plan, period, coming_from, plan_comparison_viewed: boolean, time_on_pricing_page_seconds: number, trial_available: boolean }` |
| `trial_started` | `trial_started { plan_offered, source: 'signup' \| 'pricing_page' \| 'modal', trial_days_granted: number }` |
| `subscription_cancelled` | `subscription_cancelled { plan, cancellation_reason: 'too_expensive' \| 'not_using_enough' \| 'missing_feature' \| 'found_alternative' \| 'temporary' \| 'other', cancellation_reason_text?: string, days_since_signup: number, total_vin_queries_lifetime: number, refund_requested: boolean }` |
| `subscription_resumed` | `subscription_resumed { plan, days_away: number, came_back_from_offer: boolean }` |
| `downgrade_offer_shown` | `downgrade_offer_shown { from_plan, to_plan, discount_pct: number, offer_type: 'discount' \| 'plan_downgrade' \| 'pause' }` |
| `downgrade_offer_accepted/declined` | Aynı detail seti |
| `cancel_save_clicked` | `cancel_save_offer_accepted { offer_type, savings_usd }` |
### 7.3 Trial Urgency Banner
| Mevcut | Yeni |
|--------|------|
| `trial_urgency_banner_viewed` | `trial_urgency_banner_viewed { days_until_trial_end: number, banner_variant: string, plan: string }` |
| `trial_urgency_banner_cta_clicked` | `trial_urgency_banner_cta_clicked { days_until_trial_end: number, cta_destination: string }` |
| `trial_urgency_banner_dismissed` | `trial_urgency_banner_dismissed { days_until_trial_end: number, dismiss_method: 'x_button' \| 'esc' \| 'overlay_click' }` |
### 7.4 Payment Dual-Capture Strategy
**Karar:** Frontend + backend dual capture **korunur**, ama event isimleri ayrıştırılır.
| Mevcut | Yeni |
|--------|------|
| `payment_success` (frontend) | `payment_success_ui` (frontend confirmation görüntülendi) |
| `payment_success` (backend) | `payment_succeeded` (server source of truth) |
| `payment_failed` (frontend) | `payment_failed_ui` (kullanıcıya gösterilen hata) |
| `payment_failed` (backend) | `payment_failed { failure_code, failure_reason, gateway_response, amount, plan }` |
Pipeline kullanımı:
- `payment_succeeded` (backend) **conversion truth**
- `payment_success_ui` olmadan `payment_succeeded` varsa → **UI bug sinyali** (kullanıcı confirmation görmemiş)
- `payment_failed` reason'ları cancellation_reason gibi pattern detection için zengin
### 7.5 Schema/Parts Layer
| Mevcut | Yeni (Faz 0'da eklenenler — Bölüm 5.1) |
|--------|----------------------------------------|
| `oem_code_copied { oem_code, part_id }` | Aynı + `parts_panel_viewed`, `parts_filter_applied`, `compatibility_check_*`, `parts_export_initiated` |
### 7.6 Social Proof Attribution
| Mevcut | Yeni |
|--------|------|
| `social_proof_impression { page, section? }` | + `variant_id?: string` (A/B test ready) |
| `social_proof_engaged { page, section? }` | Aynı |
| (yeni) | `social_proof_followed_by_action { impression_id?: string, action: 'signup' \| 'plan_selected' \| 'checkout_started', time_to_action_seconds: number }` |
Bu üçüncü event social proof'un **gerçek attribution'ını** ölçer.
### 7.7 User Lifecycle
| Mevcut | Yeni |
|--------|------|
| `user_signed_up { method }` | `user_signed_up { method, referrer?: string, utm_source?: string, utm_campaign?: string, came_from_pricing: boolean }` |
| `user_logged_in { method }` | `user_logged_in { method, was_remember_me: boolean, days_since_last_login: number }` |
| `user_logged_out { reason? }` | Aynı |
---
## 8. Data Privacy ve KVKK Uyumu
### 8.1 `data-private` Attribute Audit
PostHog SDK config'inde `maskTextSelector: '[data-private]'` ayarlandığında rrweb recording bu element'leri mask'ler. Tüm sensitive content render'ları işaretlenmeli.
#### Audit Checklist (Faz 0'da yapılacak)
| Component / Sayfa | Sensitive Content | Action |
|-------------------|-------------------|--------|
| `search.tsx` VIN input | VIN string | `data-private` attribute |
| `index.tsx` landing VIN demo | Demo VIN (gerçek değil) | Skip |
| `parts-panel.tsx` | Müşteri spec'i, fiyat (eğer var) | `data-private` |
| `settings-content.tsx` | User email, phone | `data-private` |
| `dashboard.tsx` user header | User email render | `data-private` |
| `payment-content.tsx` | Card form, amount? | Card → `data-private`, amount OK |
| API keys view | Generated key string | `data-private="strict"` (asla rec yapılmasın) |
| Webhook secret view | Webhook secret | `data-private="strict"` |
| `_auth/register.tsx`, `_auth/login.tsx` | Password input | PostHog SDK `maskInputOptions.password: true` zaten korur, ama yine `data-private` |
### 8.2 PostHog SDK Configuration
`apps/web/src/lib/posthog/init.ts` (veya equivalent) güncellenir:
```typescript
import posthog from 'posthog-js'
export function initPostHog() {
posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
api_host: 'https://eu.i.posthog.com', // EU region zorunlu (KVKK)
// Session recording config
session_recording: {
maskAllInputs: true,
maskInputOptions: {
password: true,
email: false, // email property zaten denylist'te
tel: true,
number: false,
},
maskTextSelector: '[data-private], .sensitive',
blockSelector: '[data-private="strict"], [data-block]',
blockClass: 'rrweb-block',
ignoreClass: 'rrweb-ignore',
recordCrossOriginIframes: false,
recordNetworkPayloads: false, // payload yok, sadece metadata
recordHeaders: false, // auth header asla
recordBody: false, // request body asla
},
// PII denylist
property_denylist: [
'$ip', // PostHog handle'lar geo'ya
'email', 'phone', 'tc_kimlik',
'password', 'token', 'api_key',
'credit_card', 'cvv',
],
// Defaults
capture_pageview: true,
capture_pageleave: true,
autocapture: {
capture_copied_text: false, // VIN copy event ayrı capture'da
},
persistence: 'localStorage',
cross_subdomain_cookie: true,
secure_cookie: true,
// Performance
request_batching: true,
// KVKK
opt_out_capturing_by_default: false, // consent flow Sase.tr login öncesi
})
}
```
### 8.3 Backend SDK (posthog-node) Configuration
```typescript
// apps/api/src/lib/posthog.ts
import { PostHog } from 'posthog-node'
export const posthogServer = new PostHog(
process.env.POSTHOG_KEY!,
{
host: 'https://eu.i.posthog.com',
flushAt: 20, // 20 event'te bir flush
flushInterval: 10000, // 10 saniyede bir flush
}
)
// Wrapper: PII filtering at the source
export function captureFromBackend(params: {
distinctId: string,
event: string,
properties: Record<string, any>,
groups?: Record<string, string>,
}) {
const sanitized = sanitizeProperties(params.properties)
posthogServer.capture({
distinctId: params.distinctId,
event: params.event,
properties: { ...sanitized, $lib: 'posthog-node' },
groups: params.groups,
})
}
function sanitizeProperties(props: Record<string, any>): Record<string, any> {
const denylist = ['email', 'phone', 'password', 'token', 'api_key', 'credit_card']
return Object.fromEntries(
Object.entries(props).filter(([k]) => !denylist.includes(k))
)
}
```
### 8.4 KVKK Aydınlatma Metni Güncelleme
Sase.tr KVKK aydınlatma metnine eklenecek madde (Bölüm 6.x):
> **Ürün Analitiği ve Davranış Analizi**
>
> Sase.tr kullanım deneyiminizi iyileştirmek için PostHog (PostHog Inc., Hollanda) tarafından sağlanan ürün analitiği hizmetini kullanır. Bu hizmet kapsamında:
>
> - Hangi sayfaları ziyaret ettiğiniz, hangi özellikleri kullandığınız
> - VIN sorgu, parça arama gibi platforma özgü etkileşimleriniz
> - Cihaz ve tarayıcı bilgileriniz
> - Anonimleştirilmiş davranış kayıtları (session recording)
>
> verileri işlenir. Bu veriler AB Birliği'nde (Frankfurt) saklanır.
>
> İşlenen verilerden anonimleştirilmiş özetler, ürün geliştirme amacıyla yapay zeka destekli analiz için DeepSeek (DeepSeek Inc., Çin Halk Cumhuriyeti) üzerinden işlenebilir. Bu işlemden önce kişisel veri içeriği maskelenir.
>
> Bu işleme faaliyetinin yasal dayanağı KVKK Madde 5/2-f kapsamında **meşru menfaat**tir.
Hukuk danışmanı ile kontrol edilmeli.
### 8.5 Consent Flow (Opsiyonel, Phase 2'de)
Şu an default `opt_out_capturing_by_default: false`. Eğer Sase.tr **EU müşterileri** (GDPR) çekmek istiyorsa cookie banner consent flow eklenmeli:
```typescript
// Kullanıcı consent verince
posthog.opt_in_capturing()
// Reddederse
posthog.opt_out_capturing()
```
Bu pilot scope'unda değil, Türkiye merkezli kullanıcı varsayımı.
---
## 9. Implementation Plan
### Faz 0 — Pilot Blocker (2 gün, mecburi)
#### Gün 1: Identification + Privacy
**Sabah (4 saat)**
- [ ] Mevcut `posthog.identify()` çağrıları audit edilir
- [ ] User properties schema implement edilir (Bölüm 4.1)
- [ ] `posthog.group('company')` çağrısı eklenir
- [ ] `posthog.alias()` signup flow'a eklenir
- [ ] `posthog.reset()` logout'a eklenir
**Öğlen (4 saat)**
- [ ] `data-private` audit (Bölüm 8.1 checklist)
- [ ] PostHog SDK config update (Bölüm 8.2 + 8.3)
- [ ] EU region confirmation (`https://eu.i.posthog.com`)
- [ ] Local test: identify + group + recording mask
#### Gün 2: Parts Layer Events
**Sabah (4 saat)**
- [ ] `parts_panel_viewed` event implementation
- [ ] `parts_filter_applied` event implementation
- [ ] `compatibility_check_initiated` + `compatibility_check_completed`
**Öğlen (4 saat)**
- [ ] `parts_export_initiated` event
- [ ] Existing `oem_code_copied` property zenginleştirme
- [ ] Documentation: `docs/posthog-events.md` ilk versiyonu
- [ ] PR review + merge
**Çıkış kriteri:** Faz 0 PR merge edildiğinde:
- Tüm authenticated session'lar `posthog.identify()` ve `posthog.group()` çağırıyor
- Parts layer'ı görünür
- rrweb recording'lerde VIN/email mask'lenmiş
- KVKK aydınlatma metni güncel
### Faz 1 — High-ROI Events (5-7 gün, BIP Phase 6a paralelinde)
| Gün | Konu |
|-----|------|
| 1 | VIN decode performance expansion (Bölüm 7.1) |
| 2 | Provider events (4 adet, Bölüm 5.2) |
| 3 | Search behavior events (5 adet, Bölüm 5.2) |
| 4 | Feature discovery events (5 adet, Bölüm 5.2) |
| 5 | API key/webhook events + Subscription funnel granularity (Bölüm 7.2) |
| 6 | Payment dual-capture rename (Bölüm 7.4) + Trial banner expansion |
| 7 | QA, documentation update, deploy |
**Strateji:** Tek bir feature branch (`feature/posthog-event-expansion-phase1`), günlük commit'ler, sonunda tek büyük PR. Production'a deploy edilir, 1 hafta baseline ölçülür, sonra BIP pipeline Faz 6b'ye geçilir.
### Faz 2 — Optimization (Phase 6c sonrası, ad-hoc)
BIP'ten gelen meta-insight'lara göre event ekleme. Plan değil pattern.
### Effort Özeti
| Faz | Süre | Effort | Çıktı |
|-----|------|--------|--------|
| Faz 0 | 2 gün | Solo founder full-time | Pilot blocker'lar |
| Faz 1 | 5-7 gün | Solo founder full-time | 30+ yeni event, mevcut iyileştirme |
| Faz 2 | Sürekli | 1-2 saat/event | Insight-driven additions |
| **Toplam (Faz 0+1)** | **~2 hafta** | | Pilot için event hazır |
---
## 10. Testing Strategy
### 10.1 Local Development
```typescript
// posthog dev environment
if (import.meta.env.DEV) {
posthog.init(import.meta.env.VITE_POSTHOG_KEY_DEV, {
api_host: 'https://eu.i.posthog.com',
debug: true, // console'da event log
disable_session_recording: true, // dev'de recording yok
})
}
```
Browser console'da event flow gözle takip edilir.
### 10.2 Event Payload Validation
Type-safe wrapper ile yanlış property kullanımı compile-time'da yakalanır:
```typescript
// src/lib/posthog/events.ts
import posthog from 'posthog-js'
type PostHogEvent =
| { name: 'vin_decode_initiated'; props: VinDecodeInitiatedProps }
| { name: 'vin_decode_succeeded'; props: VinDecodeSucceededProps }
| { name: 'parts_panel_viewed'; props: PartsPanelViewedProps }
// ... 50+ event type
export function capture<E extends PostHogEvent>(event: E) {
posthog.capture(event.name, event.props)
}
// Kullanım: compile-time safety
capture({
name: 'vin_decode_initiated',
props: { vin: '...', query_source: 'manual' } // missing prop → TS error
})
```
### 10.3 Production Validation
PR merge sonrası 24 saat içinde:
- [ ] PostHog dashboard'unda her yeni event tipi en az 1 kez görünüyor
- [ ] Event property'leri schema'ya uyuyor
- [ ] No errors in browser console (PostHog SDK errors)
- [ ] rrweb recordings'te masked content masked
- [ ] User properties identify sonrası set ediliyor (PostHog person page'de görünüyor)
### 10.4 Regression Test (Mevcut Funnel'ları Bozmama)
Faz 1'de mevcut event'leri rename ederken (vin_decoded → vin_decode_initiated gibi):
1. Eski event 1 ay daha capture edilir (parallel)
2. PostHog'da mevcut funnel/dashboard duplicate edilir (v2 versiyonları)
3. v2'ler yeni event isimleriyle kurulur
4. 1 ay sonra v1 capture durur, v1 dashboard'lar arşivlenir
Bu adımlar Bölüm 11'de detaylı.
---
## 11. Migration ve Backward Compatibility
### 11.1 Rename Stratejisi
Event rename gerektiren durumlar:
- `vin_decoded``vin_decode_initiated`
- `vin_decode_success``vin_decode_succeeded`
- `vin_decode_error``vin_decode_failed`
- `payment_success` (FE) → `payment_success_ui`
- `payment_success` (BE) → `payment_succeeded`
- `payment_failed` (BE) → daha detaylı properties
#### Migration Steps
**Adım 1: Dual-emit phase (1 ay)**
```typescript
// Both old and new emitted
posthog.capture('vin_decoded', { vin }) // legacy
posthog.capture('vin_decode_initiated', {
vin, vin_brand, query_source: 'manual', has_provider_preference: false
}) // new
```
**Adım 2: PostHog dashboard migration (1-3 gün, 1 ay sürecin başında)**
- Mevcut dashboard'ları duplicate et, "_v2" ekle
- v2'leri yeni event isimleri ile re-create et
- 1 hafta paralel izle, data tutuyor mu kontrol et
- Eski dashboard'lar `[DEPRECATED]` ile prefix'lenir
**Adım 3: Funnel migration**
- Mevcut funnels duplicate et
- v2 funnels yeni event isimleriyle kurulur
- Alert'leri v2'ye taşı
**Adım 4: Legacy capture kapanır (1 ay sonra)**
```typescript
// Sadece new event
posthog.capture('vin_decode_initiated', { ... })
```
Eski dashboard'lar arşivlenir (silinmez, history için).
### 11.2 Payment Event Migration (Özel Durum)
Payment event'leri **production critical** (finansal alert'ler buna bağlı olabilir). Daha dikkatli:
**Adım 1:** Backend'de `payment_succeeded` **yeni** event olarak başlatılır. `payment_success` capture'ı korunur.
**Adım 2:** PostHog'da yeni alert kurulur (`payment_succeeded` failure rate).
**Adım 3:** 2 hafta paralel izle. Eski ve yeni event count'u %95+ matching olmalı.
**Adım 4:** Eski `payment_success` (backend) deprecate edilir, sadece frontend `payment_success_ui` kalır.
### 11.3 Property Addition Backward Compat
Yeni property eklemek backward compatible:
- Eski filter'lar yeni property'siz çalışmaya devam eder
- Yeni filter'lar property eksik event'leri sayar veya saymaz (filter behavior)
Bu adım için migration gerekmez, sadece event implementation.
### 11.4 Mevcut Dashboard / Funnel İnvanteri
Migration başlamadan önce mevcut PostHog asset'leri envanterlenmeli:
| Asset Type | Identify | Action |
|------------|----------|--------|
| Funnels | List all | v2 duplicate plan |
| Insights / Trends | List all using affected events | v2 duplicate plan |
| Dashboards | List all | Component update plan |
| Alerts | List all | Recreate on v2 |
| Cohorts | List all using affected events | Verify still valid |
Bu envanter Faz 1 başlamadan önce 1 saatlik audit ile çıkarılır.
---
## 12. PostHog Dashboard Setup
Yeni event'lerin maximum değer üretmesi için PostHog UI'da kurulacak dashboard ve funnel'lar:
### 12.1 Critical Funnels
#### Activation Funnel
```
user_signed_up
→ user_logged_in (within 1 day)
→ vin_decode_initiated
→ vin_decode_succeeded
→ parts_panel_viewed
→ oem_code_copied OR parts_export_initiated
```
#### Conversion Funnel
```
$pageview (/pricing)
→ plan_selected
→ checkout_started
→ payment_initiated
→ payment_succeeded
```
#### Trial → Paid Funnel
```
trial_started
→ vin_decode_succeeded (during trial)
→ trial_urgency_banner_cta_clicked
→ checkout_started
→ payment_succeeded
```
#### Retention Funnel (Weekly)
```
Week 1: user_signed_up
Week 2: vin_decode_succeeded
Week 3: vin_decode_succeeded
Week 4: vin_decode_succeeded
```
### 12.2 Critical Cohorts
| Cohort | Definition | Use |
|--------|-----------|-----|
| `power_users` | Last 30d: >100 vin_decode_succeeded | BIP segment context |
| `at_risk` | Last 14d: 0 vin_decode_initiated AND plan_tier != 'free' | Churn prevention |
| `new_users_week1` | signup_date within last 7 days | Onboarding analysis |
| `provider_heavy_pl24` | Last 30d: >70% provider = 'PL24' | Provider reliability impact |
| `multi_brand_users` | distinct vin_brand count >= 3 in last 30d | Feature usage analysis |
| `api_integrators` | api_key_created event exists | Power users, different needs |
| `failed_payment_recovery` | payment_failed exists, but no payment_succeeded within 7d | Recovery target |
### 12.3 Insights (Trends)
| Insight | Description |
|---------|-------------|
| Daily VIN queries by provider | Provider load distribution |
| VIN decode success rate by provider | Quality metric |
| Average response time by provider | Performance metric |
| Onboarding completion rate (weekly) | Activation health |
| Trial → paid conversion (monthly) | Revenue health |
| Feature discovery rate by feature | Discoverability gap |
| Compatibility check accuracy distribution | Product quality |
### 12.4 Alerts
| Alert | Trigger | Notification |
|-------|---------|--------------|
| Payment success rate drops | < 90% in last hour | Telegram immediate |
| VIN decode failure spike | > 3x baseline in 15 min | Telegram |
| Provider PL24 timeout > 20% | Continuous 30 min | Email |
| Daily active users drop | < 80% of 7-day avg | Daily digest |
| New signup → first VIN query | < 50% in 24h | Weekly review |
Bu alert'ler PostHog'un kendi alert sisteminden, BIP pipeline'a webhook olarak da gönderilebilir (BIP PRD Bölüm "Pattern-Level Otomatik Insight Tetikleme").
---
## 13. Documentation Standardı
### 13.1 Single Source of Truth
`docs/posthog-events.md` — tüm event taxonomy'sinin tek kaynağı:
```markdown
# Sase.tr PostHog Event Taxonomy
> Last updated: [date]
> Version: 2.0
## Conventions
[Bölüm 6'dan kopyala]
## Event Catalog
### Auth & Lifecycle
#### user_signed_up
- **Description:** User completed signup
- **Trigger:** After successful account creation
- **Source:** `apps/web/src/routes/_auth/register.tsx`
- **Properties:**
- `method` (required): "email" | "google"
- `referrer` (optional): string
- `utm_source` (optional): string
- `utm_campaign` (optional): string
- `came_from_pricing` (required): boolean
- **Example:**
```ts
capture({ name: 'user_signed_up', props: { method: 'email', came_from_pricing: true } })
```
#### user_logged_in
... [her event için aynı format]
```
### 13.2 Inline Code Documentation
Her capture noktasııklayıcı yorumla:
```typescript
/**
* VIN decode başlatıldığında çağrılır.
* Pipeline kullanımı: vin_decode_initiated → vin_decode_succeeded/failed funnel.
*
* @see docs/posthog-events.md#vin_decode_initiated
*/
capture({
name: 'vin_decode_initiated',
props: { vin, query_source: 'manual', has_provider_preference: false }
})
```
### 13.3 Schema Versioning
`docs/posthog-events.md` header'ında version. Breaking change'lerde version bump:
- v1.x: Mevcut taxonomy
- v2.0: Faz 1 sonrası
### 13.4 Onboarding Yeni Developer
Yeni dev (gelecekte) için checklist:
- [ ] `docs/posthog-events.md` oku
- [ ] Type-safe wrapper kullan (`src/lib/posthog/events.ts`)
- [ ] Yeni event ekleyince:
- [ ] Type tanımı `events.ts`'e ekle
- [ ] `docs/posthog-events.md` güncelle
- [ ] PR description'da event listele
---
## 14. Riskler
| Risk | Olasılık | Etki | Azaltma |
|------|----------|------|---------|
| Mevcut funnel/dashboard'lar rename ile kırılır | Yüksek | Orta | Dual-emit phase + v2 dashboard parallel; Bölüm 11 |
| Property denylist eksik kalır, PII PostHog'a gider | Orta | Yüksek | Bölüm 6.4 + 8.2 review; Faz 0'da audit |
| Identify çağrısı login dışı yerde unutulur | Orta | Orta | Type-safe wrapper + lint rule |
| Group analytics company switch'te update edilmez | Yüksek | Orta | Multi-tenant flow test; Bölüm 4.2 |
| Yeni event'ler PostHog cost'unu artırır | Düşük | Düşük | Free tier 1M event/ay yeterli pilot için |
| Payment dual-capture geçişinde alert'ler kaybolur | Orta | Yüksek | Alert recreate Bölüm 12.4 + audit |
| rrweb mask config bozulur, VIN recording'lerde görünür | Düşük | Yüksek | Faz 0 test + production smoke test |
| Backend event'leri server crash'te kaybolur | Düşük | Düşük | posthog-node flush on shutdown; SIGTERM handler |
| Type-safe wrapper adoption düşük olur | Orta | Düşük | Lint rule: direct posthog.capture() block edilir |
| Faz 1 implementation 7 günde tamamlanmaz | Orta | Orta | Her event independent ship edilebilir; BIP Phase 6a paralel |
---
## 15. Karar Logu
| Tarih | Karar | Gerekçe |
|-------|-------|---------|
| 2026-05-13 | PostHog Cloud EU region kullanılacak | KVKK uyumu; AWS Frankfurt veri konumu |
| 2026-05-13 | Group analytics (`posthog.group('company')`) zorunlu | B2B insight kalitesi için belkemiği |
| 2026-05-13 | Email asla full olarak property gönderilmez, sadece domain | KVKK + property denylist enforcement |
| 2026-05-13 | VIN property'de full kalır, rrweb recording'de masked | Ürün analitiği için gerekli; PII risk recording'de değil property'de düşük |
| 2026-05-13 | Frontend + backend payment dual-capture korunur, rename edilir | Reliability + UI bug detection sinyali |
| 2026-05-13 | Event naming: `<noun>_<verb_past>` convention | Tutarlı arama, PostHog UI ergonomics |
| 2026-05-13 | Property naming: snake_case | PostHog convention |
| 2026-05-13 | Type-safe wrapper zorunlu (`src/lib/posthog/events.ts`) | Yanlış event/property compile-time yakalama |
| 2026-05-13 | `docs/posthog-events.md` single source of truth | Drift önleme |
| 2026-05-13 | Faz 0 (2 gün) BIP Phase 6a önkoşul | Pilot insight kalitesi için zorunlu |
| 2026-05-13 | Faz 1 (5-7 gün) BIP Phase 6a paralel | Time-to-pilot kısalır |
| 2026-05-13 | Faz 2 ad-hoc, insight-driven | Premature optimization önleme |
| 2026-05-13 | Rename'ler 1 ay dual-emit phase ile yapılır | Production funnel'ları bozmamak |
---
**Doküman Sonu**