KART Sistemi — Geliştirme Planı
Oluşturulma: 2026-05-14 | Güncelleme: 2026-05-15 | Durum: Kararlar Netleşti — Geliştirmeye Hazır
1. Amaç ve Genel Çerçeve
Ne inşa ediyoruz?
Ham mobil sinyal verisini, her seferinde trilyonlarca satır işlemek yerine bir kez hesaplayıp cache'lenen, makine tarafından okunabilir, standardize edilmiş JSON paketlerine (KART) dönüştüren bir sistemdir.
İki kart tipi:
| Kart | Hedef | Tetikleyici Soru |
|---|---|---|
| Kişi Kartı (Person Card) | Bir device_id'nin davranışsal DNA'sı |
"Bu kişi kim? Nerede yaşıyor, ne zaman aktif, nereleri ziyaret ediyor?" |
| Mekan Kartı (Place Card) | Bir poi_id'nin ziyaretçi profili ve rekabet durumu |
"Bu mekana kim geliyor, ne zaman dolu, rakip yoğunluğu ne?" |
Neden KART?
- Maliyet: Her sorguda Spark/DuckDB tetiklenmez; DB'den JSON çekilir — milisaniyeler.
- Kesinlik: LLM (Profil aşaması) soyut yorum yapmak yerine sayısal gerçekler üzerine çalışır.
- Karşılaştırılabilirlik: 30 günlük kart ile 90 günlük kartı matematiksel olarak trend analizi için yanyana koyabilmek.
Geliştirme Önceliği:
Önce Kişi Kartları biriktirilecek, ardından Mekan Kartlarının visitor_category_affinity
alanı bu kartlardan beslenecek. Mekan Kartları Kişi Kartı olmadan da üretilebilir (affinity = null).
2. Hedef Çıktı Formatları
Kişi Kartı
{
"id": "aaaa-bbbb-cccc-dddd",
"type": "person",
"period": "30d",
"data": {
"activity_metrics": {
"total_signals": 120,
"unique_days_active": 18,
"frequency_score": 0.6
},
"location_dna": {
"home_neighborhood": "Akatlar",
"work_neighborhood_inferred": "Levent",
"top_visited_neighborhoods": ["Akatlar", "Etiler", "Bebek"],
"mobility_radius_km": 4.2
},
"top_categories": {
"Modern Pub & Bistro": 15,
"Kebap": 8,
"Kokteyl Bar": 3,
"Geleneksel Meyhane": 5
},
"time_segments": {
"morning_07_11": 0.1,
"afternoon_11_15": 0.2,
"late_day_15_19": 0.3,
"prime_time_19_23": 0.35,
"night_23_07": 0.05
}
}
}
Not:
reliability_indexkaldırıldı — formülü belirsiz, somut değer katmıyor.top_categoriesanahtarlarımaster.poi.categorydeğerlerini (Mapin standart isimleri) kullanır.
Mekan Kartı
{
"id": "POI_9988",
"type": "place",
"period": "90d",
"data": {
"store_info": {
"name": "Kaan Tekel",
"category": "Off-Trade",
"sub_category": "Tekel Bayii",
"extra_data": {
"package_home": "yes",
"segment": "Diamond",
"operation_hours": "09:00-01:00"
}
},
"visitor_stats": {
"total_unique_visitors": 450,
"loyalty_rate": 0.12,
"visitor_category_affinity": null
},
"neighborhood": {
"name": "Beşiktaş/Sinanpaşa",
"ses_score": null,
"nearby_competitors": 4,
"neighboring_attractions": ["University", "Ferry_Port"]
},
"density_analytics": {
"signal_density_index": 0.56,
"peak_hour_window": "19:00-21:00",
"hourly_density_index": [0.1, 0.1, 0.2, 0.5, 0.9, 0.8]
}
}
}
Not:
avg_occupancy→signal_density_indexolarak yeniden adlandırıldı (kapasite verisi yok).visitor_category_affinityveses_scoreşimdiliknull; veri birikmesiyle doldurulacak.
3. Sinyal Verisi Şeması (S3 Parquet)
Teyit edildi — fetchDeviceRecords / read_parquet ile çekilen kolon yapısı:
| Kolon | Tip | Açıklama |
|---|---|---|
timestamp |
int64 (Unix epoch, saniye) |
Sinyal zamanı. datetime.utcfromtimestamp(ts) ile dönüştürülür |
device_aid |
string |
Cihaz kimliği (UUID formatı) |
latitude |
float64 |
Enlem (WGS-84) |
longitude |
float64 |
Boylam (WGS-84) |
horizontal_accuracy |
float64 |
Konum hassasiyeti (metre) |
neighborhood |
string |
Mahalle (genellikle boş — cleaned.neighborhoods dolmadan güvenilmez) |
h3_res9_id |
int64 |
H3 res=9 hücre ID (int formatı) |
Timestamp dönüşümü:
import datetime
dt = datetime.datetime.utcfromtimestamp(1735499320)
# → datetime(2025, 12, 29, 13, 28, 40)
4. Mevcut SDK ile Ne Örtüşüyor?
Aşağıdakiler zaten var — KART sistemi bunları tüketecek, yeniden yazmayacak.
| KART İhtiyacı | Mevcut Bileşen | Durum |
|---|---|---|
| Ziyaretçi sayımı (radius bazlı) | FootfallEngine.getCountByRadius() |
✅ Hazır |
| Ziyaretçi cihaz listesi | FootfallEngine.getDeviceList() |
✅ Hazır |
| Cihaz sinyal kayıtları | FootfallEngine.fetchDeviceRecords() |
✅ Hazır |
| Koordinat → POI etiketleme | CoordinateIdentifier.identify() |
✅ Hazır |
| PostgreSQL bağlantısı | PgClient |
✅ Hazır |
| DuckDB S3 erişimi | DuckDBClient |
✅ Hazır |
| Ortam / config yönetimi | ConfigManager |
✅ Hazır |
| H3 hücre hesaplama | geo_utils.h3CentroidCell |
✅ Hazır |
| Haversine mesafe | geo_utils.haversineDistance |
✅ Hazır |
| Rakip POI listesi (DB) | PgClient + master.poi |
✅ Hazır |
| Mahalle reverse geocode | cleaned.neighborhoods.geom (ST_Contains) |
⚠️ Tablo boş — önce doldurulmalı |
| SES skoru | analytics.neighborhood_analytics.ses_distribution |
⚠️ Tablo boş — null ile ilerlenir |
5. Veritabanı Şeması — Yeni identity Schema
Tüm KART sistemi tabloları identity adlı yeni schema altında toplanacak.
public.coordinate_cache da buraya taşınacak (user public'tekini siler, kod identity altında açar).
-- Yeni schema
CREATE SCHEMA IF NOT EXISTS identity;
-- Koordinat önbelleği (coordinate_cache public'ten taşındı)
CREATE TABLE IF NOT EXISTS identity.coordinate_cache (
id SERIAL PRIMARY KEY,
geom GEOMETRY(Point, 4326) NOT NULL,
lat NUMERIC(10, 7) NOT NULL,
lon NUMERIC(10, 7) NOT NULL,
place_id TEXT,
name TEXT,
primary_type TEXT,
types JSONB,
google_maps_uri TEXT,
cid TEXT,
address TEXT,
queried_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_coord_cache_geom ON identity.coordinate_cache USING GIST(geom);
-- Kişi ve Mekan Kartları
CREATE TABLE IF NOT EXISTS identity.cards (
id TEXT NOT NULL,
type TEXT NOT NULL CHECK (type IN ('person', 'place')),
period TEXT NOT NULL, -- '30d', '90d' vb.
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
json_content JSONB NOT NULL,
PRIMARY KEY (id, period)
);
CREATE INDEX IF NOT EXISTS idx_cards_type_period ON identity.cards (id, type, period);
-- Mekan ↔ Kişi ilişki tablosu
CREATE TABLE IF NOT EXISTS identity.card_links (
place_id TEXT NOT NULL,
person_id TEXT NOT NULL,
period TEXT NOT NULL,
visit_date DATE NOT NULL,
PRIMARY KEY (place_id, person_id, period, visit_date)
);
CREATE INDEX IF NOT EXISTS idx_card_links_place ON identity.card_links (place_id, period);
CREATE INDEX IF NOT EXISTS idx_card_links_person ON identity.card_links (person_id, period);
poi.py güncellenmesi gerekiyor: _saveToCache ve _lookupCache metodları
public.coordinate_cache yerine identity.coordinate_cache kullanacak şekilde değiştirilmeli.
6. Geliştirilecek Yeni Modüller
6.1 core.time_windows — Zaman Dilimi Sabitleri
Ne yapacak: Period parametresini datetime aralığına çeviren yardımcı + 5 zaman dilimi sabitleri.
TIME_SEGMENTS = {
"morning": (7, 11),
"afternoon": (11, 15),
"late_day": (15, 19),
"prime_time": (19, 23),
"night": (23, 7), # gece yarısı geçişi: 23'ten 7'ye
}
def periodToRange(period: str) -> tuple[datetime, datetime]:
# "30d" → (now - 30gün, now)
# "90d" → (now - 90gün, now)
Bağımlılık: Yok. İlk yazılacak.
6.2 mobility.home_detector — Ev/İş Tespiti
Ne yapacak: Cihazın gece/gündüz sinyal yoğunluğuna bakarak en olası ev ve iş mahallesini tahmin eder.
Giriş: device_id, sinyal DataFrame (timestamp Unix epoch, latitude, longitude, h3_res9_id)
Mantık:
- timestamp → datetime dönüşümü (utcfromtimestamp)
- 00:00–06:00 sinyalleri → en yoğun H3 hücre centroid → ST_Contains ile mahalle
- 09:00–18:00 sinyalleri → en yoğun H3 hücre centroid → ST_Contains ile mahalle
Çıkış: {"home": "Akatlar", "work": "Levent", "top_neighborhoods": [...]}
⚠️ Ön koşul: cleaned.neighborhoods.geom kolonunun dolu olması şart.
Tablo şu an boş — H3 centroid koordinatı için ST_Contains çalışmayacak.
Bu modül implement edilebilir ama cleaned.neighborhoods doldurulmadan test edilemez.
Bağımlılık: core.time_windows, geo_utils.h3CentroidCell, PgClient.
6.3 mobility.time_segmenter — Zaman & Aktivite Metrikleri
Ne yapacak: Sinyal dağılımını 5 zaman dilimine normalize eder; aktivite metriklerini hesaplar.
Giriş: sinyal DataFrame (timestamp Unix epoch), period_days
Mantık:
- Her satır: timestamp → utcfromtimestamp → saat → hangi zaman dilimi?
- Zaman dilimi başına sinyal sayısı → normalize (toplam = 1.0)
- unique_days_active: date(utcfromtimestamp(ts)) distinct count
- frequency_score = unique_days_active / period_days
Çıkış: time_segments dict, activity_metrics dict (reliability_index yok)
Bağımlılık: core.time_windows.
6.4 analytics.visitor_flow — Sadakat & Yoğunluk Metrikleri
Ne yapacak: Mekan kartının sayısal analitik kısmını hesaplar.
Giriş: poi koordinatı, radius (200m varsayılan), sinyal DataFrame/DuckDB, period
Mantık:
- loyalty_rate:
period içinde 3+ farklı günde görülen cihaz / total_unique_visitors
- hourly_density_index (6 dilim, 4'er saat):
her 4 saatlik dilime düşen sinyal sayısı → normalize → [0..1]
- signal_density_index: period ortalamasına göre relatif yoğunluk
- peak_hour_window: max yoğunluk dilimine karşılık gelen saat aralığı
Çıkış: density_analytics dict (avg_occupancy YOK, signal_density_index var), loyalty_rate float
Bağımlılık: FootfallEngine, core.time_windows.
6.5 analytics.competitor_analysis — Rakip Sayımı
Ne yapacak: Mekanın 500m çevresindeki aynı kategoriden POI sayısını döndürür.
Giriş: lat, lon, radius_m (500m), target_category
Mantık: master.poi tablosunu PostGIS ST_DWithin sorgusu
Çıkış: nearby_competitors int, neighboring_attractions list[str]
ses_score: analytics.neighborhood_analytics.ses_distribution'dan — boşsa null
Bağımlılık: PgClient.
6.6 data.identity.store — Card Store (CRUD)
Ne yapacak: Üretilen kartları identity.cards tablosuna yazar; TTL yönetimi yapar.
CardStore.get(id, period) → dict | None
CardStore.save(card: dict, ttlDays: int = 30) → None # expires_at = now + ttlDays
CardStore.invalidate(id, period) → None
CardStore.linkVisitor(placeId, personId, period, visitDate) → None
CardStore.getVisitors(placeId, period) → list[str] # person_id listesi
TTL: Parametre ile özelleştirilebilir, varsayılan 30 gün.
Bağımlılık: PgClient, identity schema'nın mevcut olması.
6.7 data.identity.transformers — Orkestrasyon Katmanı
Ne yapacak: Tüm modülleri koordine ederek ham girdiyi standart KART JSON'una dönüştürür.
PersonCardBuilder.build(device_id, period_days=30, ttlDays=30) → dict
1. Sinyalleri DuckDB'den çek (period filtresi: now - period_days)
2. time_segmenter çalıştır → activity_metrics, time_segments
3. home_detector çalıştır → location_dna (neighborhoods boşsa null döner)
4. CoordinateIdentifier ile her benzersiz koordinat → top_categories hesapla
5. JSON şemasına serialize et
6. CardStore.save(ttlDays) ile kaydet
PlaceCardBuilder.build(poi_id, period_days=90, ttlDays=30) → dict
1. POI koordinatını PgClient'tan çek (master.poi)
2. FootfallEngine ile visitor device_id listesi al
3. visitor_flow çalıştır → density_analytics, loyalty_rate
4. competitor_analysis çalıştır → nearby_competitors, ses_score (null olabilir)
5. visitor_category_affinity:
→ visitor device_id'leri için identity.cards'ta Kişi Kartı var mı?
→ Varsa: top_categories'leri oranla
→ Yoksa: null bırak (tavuk-yumurta — Kişi Kartları birikmesi beklenir)
6. JSON şemasına serialize et
7. CardStore.save + linkVisitor ile kaydet
Bağımlılık: Hepsi.
7. Geliştirme Sırası
[DB Hazırlığı — Geliştirme öncesi]
├── identity schema oluştur
├── identity.cards, identity.card_links, identity.coordinate_cache tabloları
├── poi.py → coordinate_cache referansını public → identity'ye güncelle
└── cleaned.neighborhoods.geom doldurma görevi (home_detector için — ayrı sprint)
Aşama 1 (Temel):
└── core.time_windows
Aşama 2 (Kişi Kartı Motorları):
├── mobility.time_segmenter (neighborhoods olmadan çalışır)
└── mobility.home_detector (⚠️ neighborhoods dolu değilse home/work null döner)
Aşama 3 (Mekan Kartı Motorları):
├── analytics.visitor_flow
└── analytics.competitor_analysis
Aşama 4 (Saklama):
└── data.identity.store
Aşama 5 (Orkestrasyon — ÖNCELİK: Kişi Kartı):
├── PersonCardBuilder ← önce bu
└── PlaceCardBuilder ← Kişi Kartları birikmeden visitor_category_affinity null kalır
Her aşama kendi testleriyle birlikte teslim edilir; bir sonraki aşama önceki testler yeşil olmadan başlamaz.
8. Alınan Kararlar (Tüm Sorular Yanıtlandı)
| # | Soru | Karar |
|---|---|---|
| S1 | reliability_index formülü |
Kaldırıldı — belirsiz formül, katma değeri yok |
| S2 | avg_occupancy / kapasite |
signal_density_index olarak yeniden adlandırıldı |
| S3 | Affinity tavuk-yumurta | İlk çalıştırmada null; Kişi Kartları birikmesiyle doldurulur |
| S4 | Paket path | mapindata.data.identity — PgClient ile aynı pakette |
| S5 | SES skoru | analytics.neighborhood_analytics.ses_distribution var ama boş; null ile ilerlenir, doldurmak ayrı sprint |
| S6 | KART TTL | Parametre ile özelleştirilebilir, default 30 gün |
| S7 | mobility_radius_km formülü |
Tüm sinyallerin ağırlık merkezine olan max mesafenin ortalaması |
| S8 | Visitor_Logs şeması |
Ayrı identity.card_links(place_id, person_id, period, visit_date) tablosu |
Açık Kalan Altyapı Görevleri
| Görev | Etkisi | Sprint |
|---|---|---|
cleaned.neighborhoods.geom doldurulması |
home_detector home/work tespiti |
Ayrı sprint |
analytics.neighborhood_analytics.ses_distribution doldurulması |
Mekan Kartı ses_score | Ayrı sprint |
public.coordinate_cache silinmesi (user tarafından) |
poi.py migration tamamlanır |
DB Hazırlığı |
9. Kabul Kriterleri (Definition of Done)
Her modül şu kriterleri sağladığında "tamamlandı" sayılır:
- [ ] Modül implement edildi
- [ ] Birim testleri yazıldı ve yeşil
- [ ]
docstringmevcut - [ ]
pyproject.toml'da paket path kayıtlı - [ ] Bağımlı modüllerin testleri hâlâ yeşil (regresyon yok)
1. Amaç ve Genel Çerçeve
Ne inşa ediyoruz?
Ham mobil sinyal verisini, her seferinde trilyonlarca satır işlemek yerine bir kez hesaplayıp cache'lenen, makine tarafından okunabilir, standardize edilmiş JSON paketlerine (KART) dönüştüren bir sistemdir.
İki kart tipi:
| Kart | Hedef | Tetikleyici Soru |
|---|---|---|
| Kişi Kartı (Person Card) | Bir device_id'nin davranışsal DNA'sı |
"Bu kişi kim? Nerede yaşıyor, ne zaman aktif, nereleri ziyaret ediyor?" |
| Mekan Kartı (Place Card) | Bir poi_id'nin ziyaretçi profili ve rekabet durumu |
"Bu mekana kim geliyor, ne zaman dolu, rakip yoğunluğu ne?" |
Neden KART?
- Maliyet: Her sorguda Spark/DuckDB tetiklenmez; DB'den JSON çekilir — milisaniyeler.
- Kesinlik: LLM (Profil aşaması) soyut yorum yapmak yerine sayısal gerçekler üzerine çalışır.
- Karşılaştırılabilirlik: 30 günlük kart ile 90 günlük kartı matematiksel olarak trend analizi için yanyana koyabilmek.
2. Hedef Çıktı Formatları
Kişi Kartı
{
"id": "aaaa-bbbb-cccc-dddd",
"type": "person",
"period": "30d",
"data": {
"activity_metrics": {
"total_signals": 120,
"unique_days_active": 18,
"frequency_score": 0.6,
"reliability_index": 0.85
},
"location_dna": {
"home_neighborhood": "Akatlar",
"work_neighborhood_inferred": "Levent",
"top_visited_neighborhoods": ["Akatlar", "Etiler", "Bebek"],
"mobility_radius_km": 4.2
},
"top_categories": {
"modern_retail": 15,
"gym": 8,
"tekel": 3,
"pub_bar": 5
},
"time_segments": {
"morning_07_11": 0.1,
"afternoon_11_15": 0.2,
"late_day_15_19": 0.3,
"prime_time_19_23": 0.35,
"night_23_07": 0.05
}
}
}
Mekan Kartı
{
"id": "POI_9988",
"type": "place",
"period": "90d",
"data": {
"store_info": {
"name": "Kaan Tekel",
"category": "Off-Trade",
"sub_category": "Tekel Bayii",
"extra_data": {
"package_home": "yes",
"segment": "Diamond",
"operation_hours": "09:00-01:00"
}
},
"visitor_stats": {
"total_unique_visitors": 450,
"loyalty_rate": 0.12,
"visitor_category_affinity": {
"premium_horeca": 0.4,
"fitness": 0.3,
"fast_food": 0.3
}
},
"neighborhood": {
"name": "Beşiktaş/Sinanpaşa",
"ses_score": "A-",
"nearby_competitors": 4,
"neighboring_attractions": ["University", "Ferry_Port"]
},
"density_analytics": {
"avg_occupancy": 0.56,
"peak_hour_window": "19:00-21:00",
"hourly_density_index": [0.1, 0.1, 0.2, 0.5, 0.9, 0.8]
}
}
}
3. Mevcut SDK ile Ne Örtüşüyor?
Aşağıdakiler zaten var — KART sistemi bunları tüketecek, yeniden yazmayacak.
| KART İhtiyacı | Mevcut Bileşen | Notlar |
|---|---|---|
| Ziyaretçi sayımı (radius bazlı) | FootfallEngine.getCountByRadius() |
200m radius doğrudan kullanılabilir |
| Ziyaretçi cihaz listesi | FootfallEngine.getDeviceList() |
Mekan kartı "Visitor IDs" için |
| Koordinat → POI etiketleme | CoordinateIdentifier.identify() |
top_categories için |
| PostgreSQL bağlantısı | PgClient |
Card Store altyapısı |
| DuckDB S3 erişimi | DuckDBClient |
Ham sinyal okuma |
| Ortam / config yönetimi | ConfigManager |
Tüm credentiallar buradan |
| H3 hücre hesaplama | geo_utils.h3CentroidCell |
home/work tespiti için temel |
| Haversine mesafe | geo_utils.haversineDistance |
mobility_radius için |
4. Geliştirilecek Yeni Modüller
4.1 core.time_windows — Zaman Dilimi Sabitleri
Ne yapacak: Period parametresini ("30d", "90d") datetime aralığına çeviren yardımcı + 5 zaman diliminin saat sabitlerini tutan modül.
TIME_SEGMENTS = {
"morning": (7, 11),
"afternoon": (11, 15),
"late_day": (15, 19),
"prime_time": (19, 23),
"night": (23, 7), # gece yarısı geçişi dikkat
}
period_to_range("30d") → (start_dt, end_dt)
Bağımlılık: Yok. En basit modül — ilk yazılacak.
4.2 mobility.home_detector — Ev/İş Tespiti
Ne yapacak: Cihazın gece/gündüz sinyal yoğunluğuna bakarak en olası ev ve iş mahallesini tahmin eder.
Giriş: device_id, sinyal DataFrame (timestamp, lat, lon, h3_res9_id)
Mantık:
- 00:00–06:00 sinyalleri → en yoğun H3 hücre → reverse geocode → mahalle
- 09:00–18:00 sinyalleri → en yoğun H3 hücre → reverse geocode → mahalle
Çıkış: {"home": "Akatlar", "work": "Levent", "top_neighborhoods": [...]}
Bağımlılık: core.time_windows, geo_utils.h3CentroidCell, DuckDB sorgu altyapısı.
4.3 mobility.time_segmenter — Zaman & Aktivite Metrikleri
Ne yapacak: Cihazın sinyal dağılımını 5 zaman dilimine normalize eder; aktivite metriklerini hesaplar.
Giriş: device_id, sinyal DataFrame, period_days
Mantık:
- Her sinyal → hangi zaman dilimi?
- Zaman dilimi başına sinyal sayısı → normalize (toplam = 1.0)
- unique_days_active say
- frequency_score = unique_days_active / period_days
Çıkış: time_segments dict, activity_metrics dict
Bağımlılık: core.time_windows.
4.4 analytics.visitor_flow — Sadakat & Yoğunluk Metrikleri
Ne yapacak: Mekan kartının sayısal analitik kısmını hesaplar.
Giriş: poi koordinatı, radius (200m varsayılan), sinyal DataFrame/DuckDB, period
Mantık:
- loyalty_rate:
period içinde 3+ farklı günde görülen cihaz / total_unique_visitors
- hourly_density_index (6 dilim, 4'er saat):
her 4 saatlik dilime düşen sinyal sayısı → normalize
- peak_hour_window:
max yoğunluk dilimine karşılık gelen saat aralığı
Çıkış: density_analytics dict, loyalty_rate float
Bağımlılık: FootfallEngine, core.time_windows.
4.5 analytics.competitor_analysis — Rakip Sayımı
Ne yapacak: Mekanın 500m çevresindeki aynı kategoriden POI sayısını döndürür.
Giriş: lat, lon, radius_m (500m), target_category
Mantık: master.poi tablosunu PostGIS / haversine sorgusu
Çıkış: nearby_competitors int, neighboring_attractions list[str]
Bağımlılık: PgClient, geo_utils.haversineDistance.
4.6 data.identity.store — Card Store (CRUD)
Ne yapacak: Üretilen kartları PostgreSQL'e yazar, cache kontrolü yapar.
Tablo şeması:
cards(id TEXT, type TEXT, period TEXT, created_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ, json_content JSONB)
PRIMARY KEY (id, period)
INDEX ON (id, type, period)
API:
CardStore.get(id, period) → dict | None
CardStore.save(card: dict) → None
CardStore.invalidate(id, period) → None
Bağımlılık: PgClient.
4.7 data.identity.transformers — Orkestrasyon Katmanı
Ne yapacak: Yukarıdaki tüm modülleri koordine ederek ham girdiyi standart KART JSON'una dönüştürür.
PersonCardBuilder.build(device_id, period_days) → dict
1. Sinyalleri DuckDB'den çek (period filtresi)
2. home_detector çalıştır
3. time_segmenter çalıştır
4. CoordinateIdentifier ile top_categories hesapla
5. JSON şemasına serialize et
6. CardStore.save() ile kaydet
PlaceCardBuilder.build(poi_id, period_days) → dict
1. POI koordinatını PgClient'tan çek
2. FootfallEngine ile visitor listesi al
3. visitor_flow çalıştır
4. competitor_analysis çalıştır
5. visitor_category_affinity: visitor Kişi Kartlarından topla
6. JSON şemasına serialize et
7. CardStore.save() ile kaydet
Bağımlılık: Hepsi.
5. Geliştirme Sırası
Bağımlılık zinciri gözetilerek aşağıdaki sırayla ilerlenecek:
Aşama 1 (Temel):
└── core.time_windows
Aşama 2 (Kişi Kartı Motorları):
├── mobility.home_detector
└── mobility.time_segmenter
Aşama 3 (Mekan Kartı Motorları):
├── analytics.visitor_flow
└── analytics.competitor_analysis
Aşama 4 (Saklama):
└── data.identity.store (+ DB migration: cards tablosu)
Aşama 5 (Orkestrasyon):
└── data.identity.transformers
├── PersonCardBuilder
└── PlaceCardBuilder
Her aşama kendi testleriyle birlikte teslim edilir; bir sonraki aşama önceki testler yeşil olmadan başlamaz.
6. Geliştirmeye Başlamadan Önce Netleşmesi Gereken Sorular
Aşağıdaki 8 belirsizlik, doğrudan implement kararlarını etkiliyor. Yanıtlanmadan kod yazılmaya başlanmamalı.
S1 — reliability_index formülü nedir?
Etkilediği alan: activity_metrics.reliability_index
Dokümanda "Verinin ne kadar temiz olduğu" yazıyor ama somut formül yok. Üç kandidat var:
| Opsiyon | Formül | Açıklama |
|---|---|---|
| A | mean(horizontal_accuracy < eşik) |
Konum hassasiyeti bazlı |
| B | 1 - (boş_timestamp_oranı) |
Veri bütünlüğü bazlı |
| C | unique_days / toplam_aktif_saat |
Düzenlilik bazlı |
→ Hangi formül kullanılacak? Yoksa kompozit mi?
S2 — avg_occupancy ve hourly_density_index kapasitesiz hesaplanabilir mi?
Etkilediği alan: density_analytics.avg_occupancy
"Doluluk" gerçek anlamıyla fiziksel kapasiteye göre oran demek. Ama kapasite verisi elimizde yok. İki seçenek:
| Opsiyon | Açıklama |
|---|---|
| A | Relatif yoğunluk: sinyal sayısı / period ortalaması (kapasite yok, karşılaştırmalı) |
| B | Alan adını signal_density_index olarak değiştir, avg_occupancy hesaplanmaz |
→ Kapasite verisi nereden gelecek? Yoksa alan adı değiştirilmeli mi?
S3 — visitor_category_affinity tavuk-yumurta problemi nasıl çözülecek?
Etkilediği alan: visitor_stats.visitor_category_affinity
"Gelen ziyaretçilerin diğer ziyaret ettiği kategori ağırlıkları" → bu bilgi visitor'ların Kişi Kartlarından geliyor. Ama ilk çalıştırmada hiç Kişi Kartı yok.
| Opsiyon | Açıklama |
|---|---|
| A | İlk çalıştırmada bu alan null / boş bırakılır, kartlar birikmesi beklenir |
| B | Ham sinyal üzerinden CoordinateIdentifier ile anlık hesaplanır (yavaş) |
| C | Mekan kartı önce affinity hesaplamadan oluşturulur, sonraki güncellemede doldurulur |
→ Hangi strateji benimseniyor?
S4 — data.identity mi, identity.card mı? — Paket path çelişkisi
Etkilediği alan: Tüm modül dizin yapısı
Dokümanın farklı bölümlerinde iki farklı path geçiyor:
- Bölüm 3: mapindata.data.identity
- Bölüm 5: mapindata.identity.card.transformers
Bu iki farklı paket yapısı — hangisi seçilirse pyproject.toml ve tüm importlar buna göre kurgulanacak.
| Opsiyon | Yapı | Neden? |
|---|---|---|
| A | mapindata.data.identity |
Saklama odaklı, PgClient ile aynı pakette |
| B | mapindata.identity (ayrı top-level) |
Gelecekte identity.profile (LLM) ile aynı namespace |
→ Hangi path kullanılacak?
S5 — ses_score tablosu kimin sorumluluğunda ve ne zaman hazır olacak?
Etkilediği alan: neighborhood.ses_score, dolaylı olarak competitor_analysis
ses_score için şu an elimizde tablo yok. Bu alan:
- Kim hazırlayacak?
- Hangi metodoloji (TÜİK verisi, POI yoğunluğu, sinyal demografisi)?
- Hangi DB'ye, hangi şemaya yazılacak?
- competitor_analysis buna depend ediyor — tablo yoksa bu modül kısmi çalışır.
→ SES tablosu bu sprint'e dahil mi, yoksa ses_score: null ile ilerlenecek mi?
S6 — KART TTL (geçerlilik süresi) ne olacak?
Etkilediği alan: data.identity.store — expires_at alanı
cards tablosuna expires_at ekleneceği net, ama süre belirsiz:
| Opsiyon | Kural |
|---|---|
| A | Sabit: 30d kart 7 gün geçerli, 90d kart 30 gün geçerli |
| B | Dinamik: kart periyodunun %25'i kadar geçerli |
| C | Manuel: CardStore.invalidate() çağrılmadıkça süresiz geçerli |
→ TTL politikası nedir?
S7 — mobility_radius_km formülü ne olacak?
Etkilediği alan: location_dna.mobility_radius_km
Dokümanda "H3 gridleri arası geçiş sayısı" yazıyor — ama bu km değil. İki kandidat:
| Opsiyon | Formül |
|---|---|
| A | Günlük bounding box köşegen uzunluğunun ortalaması (haversine) |
| B | Tüm sinyallerin ağırlık merkezine olan max mesafenin ortalaması |
→ Hangi formül?
S8 — Visitor_Logs ilişki tablosunun şeması nedir?
Etkilediği alan: data.identity.store — Mekan ↔ Kişi ilişkisi
"Bu mekana gelen 500 kişinin kartını getir" akışı için ilişki haritası gerekiyor. İki mimari seçenek:
| Opsiyon | Şema | Avantaj / Dezavantaj |
|---|---|---|
| A | cards tablosuna visitor_ids JSONB[] ekle |
Basit, tek tablo |
| B | Ayrı card_links(place_id, person_id, period, visit_date) tablosu |
İndekslenebilir, sorgulama esnek ama 2 tablo |
→ Hangi şema?
7. Özet Karar Matrisi
| # | Soru | Öncelik | Beklenen Yanıtlayan |
|---|---|---|---|
| S1 | reliability_index formülü |
Yüksek — Aşama 2 öncesi | Data Science |
| S2 | avg_occupancy / kapasite |
Yüksek — Aşama 3 öncesi | Ürün + Data |
| S3 | Affinity tavuk-yumurta | Yüksek — Aşama 5 öncesi | Mimari karar |
| S4 | Paket path seçimi | Kritik — Aşama 1 öncesi | Teknik karar |
| S5 | SES tablosu hazırlığı | Orta — bu sprint'e dahil mi? | Proje yönetimi |
| S6 | KART TTL politikası | Orta — Aşama 4 öncesi | Ürün |
| S7 | mobility_radius_km formülü |
Orta — Aşama 2 öncesi | Data Science |
| S8 | Visitor_Logs şeması |
Yüksek — Aşama 4 öncesi | Teknik karar |
8. Kabul Kriterleri (Definition of Done)
Her modül şu kriterleri sağladığında "tamamlandı" sayılır:
- [ ] Modül implement edildi
- [ ] Birim testleri yazıldı ve yeşil
- [ ]
docstringmevcut (tek kaynak olarakkartlarin_tanimi.mdreferanslı) - [ ]
pyproject.toml'da paket path kayıtlı - [ ] Bağımlı modüllerin testleri hâlâ yeşil (regresyon yok)