"Bize bir yapay zekâ lead skoru lazım" talebi genellikle bir pazarlama toplantısında doğar ve iki hafta sonra bir Jupyter defterinde yüzde 94 doğrulukla ölür. Ben Koray Yalçın; lead yönetimi, CRM mimarisi, dijital pazarlama ve yapay zekâ destekli otomasyon projelerinde, özellikle gayrimenkul ve PropTech tarafında çalışıyorum. Bu alanda gördüğüm lead skorlama projelerinin çoğu algoritma seçimi yüzünden değil; yanlış tanımlanmış etiket, geleceği gören özellikler ve satış ekibinin güvenmediği puanlar yüzünden başarısız oluyor.
Bu yazı "AI lead scoring nedir" sorusunun pazarlama cevabı değil. Bir geliştirici ya da veri mühendisi olarak CRM verisinden çalışan, canlıda güvenilen bir skor üretmek için hangi kararları vermeniz gerektiğini anlatıyor: hedef değişkeni nasıl tanımlarsınız, zaman sızıntısını nasıl engellersiniz, olasılıkları nasıl kalibre edersiniz, skoru CRM'e nasıl geri yazarsınız ve modeli nasıl izlersiniz. Örnekler Python ve SQL ile; mantık her yığına taşınabilir.
1. Önce soru: Neyi tahmin ediyoruz?
Lead skorlamanın ilk ve en çok atlanan adımı hedef değişkeni yazılı hale getirmek. "Sıcak lead" bir hedef değildir. Hedef şu kalıpta olmalı:
Lead oluştuktan sonraki N gün içinde X olayı gerçekleşti mi?
Gayrimenkulde X genellikle "satış kapandı" olmaz, çünkü satış döngüsü aylar sürebilir ve pozitif örnek sayısı çok azdır. Pratikte daha işe yarayan ara hedefler:
- 30 gün içinde gerçekleşmiş randevu (showroom veya online sunum),
- 45 gün içinde rezervasyon/kapora,
- 14 gün içinde temsilcinin "nitelikli" olarak işaretlemesi.
Burada dikkat: "nitelikli" etiketi insan kararıdır ve temsilciden temsilciye değişir. Model, insanların etiketleme alışkanlığını öğrenir. Bu yüzden mümkünse davranışsal ve doğrulanabilir bir olayı (randevu gerçekleşti, kapora alındı) hedef seçiyorum. Pencere uzunluğunu da veriye bakarak belirliyorum: pozitif olayların yüzde 80–90'ının gerçekleştiği gün sayısı iyi bir başlangıçtır.
-- Randevuya dönüşen lead'lerin, lead oluşumundan kaç gün sonra dönüştüğü SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY d) AS p50, percentile_cont(0.8) WITHIN GROUP (ORDER BY d) AS p80, percentile_cont(0.9) WITHIN GROUP (ORDER BY d) AS p90 FROM ( SELECT EXTRACT(DAY FROM a.held_at - l.created_at) AS d FROM leads l JOIN appointments a ON a.lead_id = l.id AND a.status = 'held' ) t; Pencereyi seçtikten sonra olgunlaşmamış lead'leri eğitim setinden çıkarmak gerekir. Dün gelen bir lead'in henüz randevuya dönüşmemiş olması negatif örnek değildir; sadece süresi dolmamıştır.
2. Gözlem anı ve zaman sızıntısı
Lead skorlamada en sinsi hata, modelin tahmin anında bilinmeyen bilgiyi kullanmasıdır. CRM tabloları güncel durumu tutar: lead.status, lead.assigned_agent, lead.last_activity_at, lead.budget alanları lead'in ömrü boyunca değişir. Bu alanları "şimdiki halleriyle" alıp model eğitirseniz model geleceği görür.
Klasik örnekler:
-
call_count: Randevuya dönüşen lead'ler zaten daha çok aranmıştır, çünkü temsilci onlarla konuşmuştur. Sonuç sebebe karışır. -
budget: Çoğu zaman ilk görüşmede temsilci tarafından doldurulur. Form anında boş olan bir alan, eğitim setinde dolu görünür. -
stage_updated_at: Doğrudan hedefin kendisini sızdırır.
Çözüm, her lead için bir gözlem anı (observation time) tanımlamak ve tüm özellikleri o ana kadar bilinen veriden hesaplamaktır. Ben genellikle iki skor üretiyorum: lead oluştuğu andaki "ilk skor" (yönlendirme ve önceliklendirme için) ve 24. saatteki "güncel skor" (takip sırası için). Her biri ayrı model, ayrı özellik seti.
Bunun için CRM'in güncel tablosu yetmez; değişiklik geçmişi gerekir. Alan bazlı bir audit tablosu yoksa önce onu kurmanız gerekiyor. Eldeki geçmişle "o anki" değeri almak için:
-- Her lead için gözlem anındaki (oluşumdan 24 saat sonra) bütçe değeri SELECT DISTINCT ON (l.id) l.id AS lead_id, h.new_value AS budget_at_t24 FROM leads l LEFT JOIN field_history h ON h.record_id = l.id AND h.field = 'budget' AND h.changed_at <= l.created_at + INTERVAL '24 hours' ORDER BY l.id, h.changed_at DESC; Aynı ilke aktivite özellikleri için de geçerli: "toplam arama sayısı" değil, "ilk 24 saatte yapılan arama sayısı ve ilk yanıt süresi". İlk yanıt süresi zaten satış operasyonunun kendisiyle ilgili; modelin "biz hızlı aradığımız için dönüştü" sinyalini öğrendiğini bilerek kullanmak gerekir.
3. Özellik tasarımı: Gayrimenkule özgü sinyaller
Genel B2C şablonundan farklı olarak gayrimenkul lead'lerinde işe yarayan sinyaller şunlar oluyor:
- Kaynak ve kampanya: Portal, Meta form, web formu, referans. Kampanya kırılımında aşırı ayrıntı modelin ezber yapmasına yol açar; kampanya yerine kampanya tipi (lansman, yeniden hedefleme, marka) daha kararlı.
- Form kalitesi: Telefonun geçerli olup olmadığı, ülke kodu, e-postanın tek kullanımlık alan adı olup olmadığı, ad alanında anlamsız karakter.
- Niyet sinyalleri: Oda tipi seçimi, fiyat sayfası ziyareti, sanal tur izleme süresi, kat planı indirme.
- Zaman: Formun doldurulduğu saat dilimi ve haftanın günü, lead'in ülkesinin yerel saatine göre. Yabancı alıcılarda bu önemli.
- Tekrar: Aynı kişinin daha önce başka projeye form doldurmuş olması. Tekilleştirme iyi yapılmamışsa bu özellik sahte pozitif üretir.
Kategorik alanlarda nadir değerleri "diğer" kovasına toplamak ve kaynak eşlemesini kod içinde değil yapılandırma tablosunda tutmak bakım yükünü ciddi azaltıyor.
import pandas as pd def build_features(leads: pd.DataFrame, events: pd.DataFrame) -> pd.DataFrame: t_obs = leads["created_at"] + pd.Timedelta(hours=24) ev = events.merge(leads[["lead_id"]].assign(t_obs=t_obs), on="lead_id") ev = ev[ev["ts"] <= ev["t_obs"]] # gözlem anından sonrası yasak agg = ev.pivot_table(index="lead_id", columns="event", values="ts", aggfunc="count", fill_value=0).add_prefix("n_") f = leads.set_index("lead_id")[["source_type", "country", "room_type"]].join(agg) f["hour_local"] = leads.set_index("lead_id")["created_local"].dt.hour f["phone_valid"] = leads.set_index("lead_id")["phone_e164"].notna().astype(int) return f.fillna(0) 4. Doğrulama: Rastgele bölme değil, zamana göre bölme
Lead verisi durağan değildir: kampanyalar değişir, fiyatlar değişir, portal algoritmaları değişir. Rastgele train_test_split geleceğin bilgisini eğitime karıştırır ve iyimser sonuç verir. Ben her zaman zamana göre bölüyorum: örneğin ilk 9 ayla eğit, sonraki 1 ayla doğrula, son 2 ayla test et. Mümkünse kayan pencereyle birkaç kez tekrarlayıp metriklerin ne kadar oynadığına bakıyorum.
Metrik seçimi de önemli. Pozitif oran düşükse doğruluk (accuracy) anlamsızdır. Kullandıklarım:
- PR-AUC: Dengesiz sınıflarda ROC-AUC'dan daha dürüst.
- Üst dilim yakalama oranı: "Skoru en yüksek yüzde 20'lik dilim, tüm randevuların yüzde kaçını içeriyor?" Satış ekibine anlatılabilen tek metrik budur.
- Brier skoru: Olasılıkların ne kadar iyi kalibre olduğunu ölçer.
from sklearn.ensemble import HistGradientBoostingClassifier from sklearn.metrics import average_precision_score, brier_score_loss import numpy as np def top_decile_capture(y, p, q=0.2): cut = np.quantile(p, 1 - q) return y[p >= cut].sum() / max(y.sum(), 1) model = HistGradientBoostingClassifier(max_depth=4, learning_rate=0.05, categorical_features="from_dtype") model.fit(X_train, y_train) p = model.predict_proba(X_test)[:, 1] print("PR-AUC", average_precision_score(y_test, p)) print("Brier", brier_score_loss(y_test, p)) print("Üst %20 yakalama", top_decile_capture(y_test.values, p)) Basit bir lojistik regresyonu da her zaman temel çizgi olarak eğitin. Gradient boosting yalnızca birkaç puan daha iyiyse, açıklanabilirliği ve bakım kolaylığı yüzünden lojistik regresyonla başlamak çoğu zaman daha doğru karardır.
5. Kalibrasyon: "Skor 80" ne demek?
Satış ekibi skoru bir olasılık gibi okur. "80 puanlı lead'lerin yaklaşık yüzde 80'i randevuya dönüşür" sanır. Ağaç tabanlı modellerin çıktıları çoğu zaman böyle davranmaz. Ayrıca eğitimde sınıf dengeleme (oversampling, class_weight) yaptıysanız olasılıklar sistematik olarak şişer.
Bu yüzden ayrı bir kalibrasyon seti üzerinde izotonik ya da sigmoid kalibrasyon uyguluyorum ve kalibrasyon eğrisini her sürümde rapora koyuyorum:
from sklearn.calibration import CalibratedClassifierCV, calibration_curve cal = CalibratedClassifierCV(model, method="isotonic", cv="prefit") cal.fit(X_valid, y_valid) p_cal = cal.predict_proba(X_test)[:, 1] frac_pos, mean_pred = calibration_curve(y_test, p_cal, n_bins=10) CRM'e ham olasılığı yazmak yerine ekiplere anlaşılır kovalar sunmak daha iyi çalışıyor: A (çok yüksek), B, C, D. Kova sınırlarını olasılığa göre değil, operasyon kapasitesine göre belirliyorum: "A kovası, temsilcilerin ilk 15 dakikada arayabileceği kadar lead içermeli." Böylece skor doğrudan bir iş kuralına bağlanır.
6. Skoru CRM'e geri yazmak
Model ne kadar iyi olursa olsun, temsilci CRM ekranında görmüyorsa yoktur. Canlı akış genellikle şöyle:
- Lead oluştuğunda CRM bir webhook ya da olay kuyruğu mesajı gönderir.
- Skorlama servisi özellikleri aynı kodla hesaplar (eğitimle canlı arasında özellik kodu ayrışmasın diye tek bir paket kullanıyorum).
- Skor, kova, model sürümü ve en etkili 3 neden CRM'deki özel alanlara yazılır.
- Atama ve önceliklendirme kuralları bu alanları okur.
def score_lead(lead_id: str) -> dict: feats = feature_store.get(lead_id, as_of=now_utc()) p = float(calibrated_model.predict_proba(feats)[0, 1]) return { "lead_score": round(p * 100), "lead_tier": tier_for(p), "score_model_version": MODEL_VERSION, "score_reasons": top_reasons(feats, k=3), # örn. SHAP değerlerinden "scored_at": now_utc().isoformat(), } crm.update_lead(lead_id, score_lead(lead_id), idempotency_key=f"{lead_id}:{MODEL_VERSION}:t0") Birkaç pratik not:
- Model sürümünü alan olarak saklayın. Sonradan "bu ay dönüşüm düştü" tartışması çıktığında hangi lead'in hangi modelle puanlandığını bilmeniz gerekir.
- Nedenleri insan diline çevirin. "n_floorplan_download=1" yerine "Kat planı indirdi". Temsilcinin güveni burada kazanılır.
- Yeniden skorlamayı sınırlayın. Her aktivitede skoru değiştirmek, temsilcinin listesinin sürekli zıplamasına yol açar. Belirli kontrol noktaları (0. saat, 24. saat, 7. gün) yeterli.
- İzin durumunu saygıyla ele alın. KVKK ve GDPR kapsamında açık rıza vermemiş kişilerin verisini profil çıkarma amacıyla kullanmak ayrı bir hukuki değerlendirme gerektirir; skorlama hattına girmeden önce izin alanını kontrol eden bir filtre koyuyorum.
7. Geri bildirim döngüsü ve kendini besleyen önyargı
Skor canlıya girdiği anda veriyi değiştirmeye başlar. A kovasındaki lead'ler daha hızlı aranır, daha hızlı aranan lead'ler daha çok dönüşür, bir sonraki model "A kovası özelliklerinin" daha da güçlü olduğunu öğrenir. D kovasındaki lead'ler hiç aranmıyorsa, modelin onlar hakkında yanıldığını asla öğrenemezsiniz.
Bunu kırmak için iki yöntem kullanıyorum:
- Kontrol grubu: Lead'lerin küçük bir rastgele yüzdesi (örneğin yüzde 5–10) skordan bağımsız, standart sırayla işlenir. Hem modelin gerçek etkisini ölçer hem de tarafsız eğitim verisi üretir.
- Müdahale özelliğini kaydetmek: İlk yanıt süresi ve temas sayısı gibi operasyon değişkenlerini ayrı saklayıp yeni modelde ya hariç tutmak ya da bilinçli olarak kontrol etmek.
8. İzleme: Model sessizce bozulur
Canlıda izlediğim üç şey var:
- Girdi kayması: Kaynak dağılımı, ülke dağılımı, boş alan oranları haftalık olarak eğitim dönemine kıyasla (PSI gibi basit bir ölçüyle) izlenir. Yeni bir portal entegrasyonu ya da form değişikliği genelde ilk burada görünür.
- Skor dağılımı: A kovasının payı birden ikiye katlandıysa muhtemelen bir alan bozulmuştur, pazar değişmemiştir.
- Gecikmeli performans: Hedef pencere dolduktan sonra (örneğin 30 gün) kova bazında gerçek dönüşüm oranları hesaplanır ve kalibrasyon eğrisiyle karşılaştırılır.
Yeniden eğitimi takvime değil sinyale bağlamak daha sağlıklı: kayma eşiği aşıldığında ya da kova dönüşüm oranları belirli bir toleransın dışına çıktığında.
Kural tabanlı skordan modele geçiş
Her ekibin ilk gün makine öğrenmesine ihtiyacı yok. Yüz civarında pozitif örneğiniz yoksa, şeffaf bir kural tabanlı skor (geçerli telefon +20, kat planı indirme +15, bütçe alanı dolu +10 gibi) hem daha güvenilir hem de veri toplama disiplinini oturtmak için daha iyi bir başlangıç. Önemli olan, kural tabanlı skoru da aynı şekilde sürümlemek, CRM'e aynı alanlara yazmak ve aynı izleme metrikleriyle ölçmek. Böylece modele geçtiğinizde karşılaştırma yapabileceğiniz bir temel çizginiz olur ve geçiş, temsilcinin ekranında yalnızca daha iyi sıralanmış bir liste olarak görünür.
Lead skorlama kavramının kısa tanımı ve pazarlama açısından çerçevesi için Koray Yalçın'ın AI lead scoring sözlük maddesine bakabilirsiniz.
SSS
Lead skorlama modeli için ne kadar veri gerekir?
Kesin bir sayı yok, ama pozitif örnek sayısı belirleyici. Birkaç yüz pozitif olayın altında karmaşık modeller ezber yapar; kural tabanlı skor veya düzenlileştirilmiş lojistik regresyon daha güvenlidir.
Hedef olarak satışı mı, randevuyu mu seçmeliyim?
Satış döngüsü uzunsa ve satış sayısı azsa, gerçekleşmiş randevu gibi doğrulanabilir bir ara olay daha istikrarlı bir hedeftir. Satış verisi biriktikçe ikinci bir model eklenebilir.
Zaman sızıntısını nasıl test ederim?
Her özelliğin değerinin gözlem anında bilinip bilinmediğini tek tek sorgulayın. Tek bir özellik modele olağanüstü performans kazandırıyorsa, önce sızıntıdan şüphelenin; çoğu zaman haklı çıkarsınız.
Skoru CRM'de sayı olarak mı, kova olarak mı göstermeliyim?
Operasyon için kova (A/B/C/D) ve kısa nedenler daha kullanışlı. Ham olasılığı analiz ve raporlama için ayrı bir alanda saklamak yeterli.
Model ne sıklıkla yeniden eğitilmeli?
Sabit takvim yerine girdi kayması ve gecikmeli performans izlenmeli; eşik aşıldığında yeniden eğitim tetiklenmeli. Büyük kampanya veya yeni kaynak eklenmesi genellikle bu tetiklemeyi doğurur.
Kişisel veriyle skorlama yapmak KVKK açısından sorun mu?
Profil çıkarma ve otomatik karar süreçleri özel değerlendirme gerektirir. İzin durumunu hattın girişinde kontrol etmek, gereksiz kişisel alanları modele hiç sokmamak ve skoru yalnızca önceliklendirme amacıyla kullanmak iyi bir başlangıçtır; hukuki değerlendirme için uzman görüşü alınmalıdır.
Lead skorlamayı bir model projesi olarak değil, CRM ile satış operasyonu arasında çalışan bir veri ürünü olarak düşünmek, benim deneyimimde başarıyı belirleyen asıl fark. Etiket tanımı net, özellikler zamana sadık, olasılıklar kalibre ve geri bildirim döngüsü kontrollüyse, algoritma seçimi ikinci planda kalıyor.

