# 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ı iş - **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 | |---------|----------|--------| | `_` | Action / lifecycle event | `plan_selected`, `vin_decode_initiated` | | `_` | State change event | `subscription_paused`, `trial_active` | | `__` | Multi-step process | `payment_initiated`, `payment_completed`, `payment_failed` | | `_` | 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, groups?: Record, }) { 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): Record { 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(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ı açı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: `_` 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**