- 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>
1340 lines
50 KiB
Markdown
1340 lines
50 KiB
Markdown
# 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 |
|
||
|---------|----------|--------|
|
||
| `<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ı 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: `<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**
|