evliyapp_backend / scripts /normalization.py
GulAkkoca
backend entegration ver1
38a13e6
Raw
History Blame Contribute Delete
30.3 kB
import pandas as pd
import re
import numpy as np
# ==============================================================================
# STAGE 1: DATA LOADING & MERGING
# ==============================================================================
# Veri setleri okunuyor (Dosyaların çalışma dizininde olduğu varsayılmıştır)
try:
df1 = pd.read_json("first400.json")
df2 = pd.read_json("222veri.json")
print("Veri setleri başarıyla yüklendi.")
except FileNotFoundError:
print("Hata: JSON dosyaları bulunamadı. Lütfen dosya yollarını kontrol edin.")
# Kaynak takibi için etiketleme ve birleştirme
df1['source_file'], df2['source_file'] = 'V1', 'V2'
df_master = pd.concat([df1, df2], ignore_index=True)
# Tüm sütun isimlerini liste halinde gör
print("--- Master Dataset Sütunları ---")
print(list(df_master.columns))
# Toplam kaç sütun olduğunu da yazdırma
print(f"\nToplam Sütun Sayısı: {len(df_master.columns)}")
# ==============================================================================
# STAGE 2: COLUMN PRUNING (CLEANING)
# ==============================================================================
# Uygulama için katma değeri olmayan teknik ve reklam odaklı sütunların temizlenmesi
redundant_columns = [
'claimThisBusiness', 'googleFoodUrl', 'hotelAds', 'gasPrices',
'searchPageUrl', 'isAdvertisement', 'phoneUnformatted',
'additionalOpeningHours', 'updatesFromCustomers', 'checkInDate',
'checkOutDate', 'hotelStars', 'hotelDescription', 'reviewsDistribution',
'scrapedAt', 'locatedIn', 'language', 'countryCode', 'price',
'peopleAlsoSearch', 'placesTags', 'reviewsTags', 'imageCategories', 'reserveTableUrl'
]
# Mevcut olan sütunları filtrele ve sil
df_master = df_master.drop(columns=[col for col in redundant_columns if col in df_master.columns])
print(f"Gereksiz sütunlar temizlendi. Kalan sütun: {df_master.shape[1]}")
print(f"Temizlik sonrası kalan sütun sayısı: {df_master.shape[1]}")
# ==============================================================================
# STAGE 3: CATEGORICAL & KEYWORD FILTERING (NOISE REDUCTION)
# ==============================================================================
# Ticari, tıbbi ve gezi rehberi dışı kategorilerin elenmesi
excluded_categories = [
'Travel agency', 'Tour agency', 'Sightseeing tour agency', 'Tour operator',
'Tourist information center', 'Car rental agency', 'Limousine service',
'Parking lot', 'Clinic', 'Medical center', 'Corporate office',
'Lodging', 'Hotel', 'Real estate agency'
]
# Başlıkta (Title) geçtiğinde kaydı geçersiz kılan anahtar kelimeler
excluded_keywords = [
'Turizm', 'Travel', 'Agency', 'Acenta', 'Clinic', 'Klinik',
'Rent a Car', 'Transfer', 'Rota', 'Medical', 'Hizmetleri',
'Dog', 'Tour Poınt', 'Toplanma Salonu', 'Silivrikapı Geçiş', 'Mother and baby'
]
# Filtreleme operasyonu
df_master = df_master[~df_master['categoryName'].isin(excluded_categories)]
pattern = '|'.join(excluded_keywords)
df_master = df_master[~df_master['title'].str.contains(pattern, case=False, na=False)]
print(f"Ticari ve ilgisiz yerler elendi. Güncel mekan sayısı: {len(df_master)}")
# ==============================================================================
# STAGE 4: GEO-NORMALIZATION (CITY MAPPING)
# ==============================================================================
def final_city_cleaner(text):
"""Karmaşık konum verilerini standart ilçe isimlerine map eder."""
if pd.isna(text): return "Diğer"
text = str(text).lower().strip()
# Manuel düzeltme gerektiren spesifik lokasyonlar
special_mapping = {
"haskoy": "Beyoğlu", "bulbol area": "Beyoğlu", "galataport": "Beyoğlu",
"molla güarani": "Fatih", "sultanahmet/fatih": "Fatih",
"vezirhan/fatih/fatih": "Fatih", "eyüp/eyüpsultan": "Eyüpsultan"
}
if text in special_mapping: return special_mapping[text]
# İlçe anahtar kelime taraması
districts = ["fatih", "beyoğlu", "beyoglu", "kadıköy", "kadikoy", "beşiktaş",
"besiktas", "üsküdar", "uskudar", "şişli", "sisli", "eyüpsultan",
"eyüp", "zeytinburnu"]
for d in districts:
if d in text:
return d.replace('beyoglu', 'beyoğlu').replace('besiktas', 'beşiktaş').title()
return text.title()
df_master['city'] = df_master['city'].apply(final_city_cleaner)
# Sonuçları kontrol et
print("--- Temizlenmiş Şehir Dağılımı ---")
print(df_master['city'].value_counts())
# ==============================================================================
# STAGE 5: DATA IMPUTATION (NULL FILLING)
# ==============================================================================
# Puan ve yorum sayısı sütunlarındaki boşlukları 0 ile doldur
df_master['totalScore'] = df_master['totalScore'].fillna(0)
df_master['reviewsCount'] = df_master['reviewsCount'].fillna(0)
# Telefon ve web sitesi sütunlarını standart yer tutucularla doldur
df_master['phone'] = df_master['phone'].fillna("No contact information")
df_master['website'] = df_master['website'].fillna("Website not available")
# Eğer street hala null ise neighborhood ile doldur, o da yoksa boş bırak
df_master['street'] = df_master['street'].fillna(df_master['neighborhood']).fillna("")
# additionalInfo Null olanları boş liste ile doldurma
df_master['additionalInfo'] = df_master['additionalInfo'].apply(lambda x: x if isinstance(x, (list, dict)) else [])
# Değişikliği doğrula
print(f"Puan ve yorum sayısı boş olan satırlar 0 ile dolduruldu.")
print(f"Kalan boş totalScore sayısı: {df_master['totalScore'].isna().sum()}")
print("'phone' ve 'website' sütunlarındaki boşluklar yer tutucu metinlerle dolduruldu.")
print("Street sütunu için fallback (neighborhood) uygulandı.")
# ==============================================================================
# STAGE 6: FEATURE ENGINEERING (BAYESIAN RATING)
# ==============================================================================
def calculate_weighted_rating(df):
"""Düşük yorum sayılı yüksek puanlı mekanları dengelemek için Bayesian Average kullanır."""
C = df['totalScore'].mean() # Global ortalama puan
m = df['reviewsCount'].quantile(0.25) # Minimum güvenilirlik eşiği (Q1)
return df.apply(lambda row: (row['totalScore'] * row['reviewsCount'] + C * m) /
(row['reviewsCount'] + m), axis=1)
# Eksik puanları 0 ile normalize etme ve ağırlıklı puanı hesaplama
df_master[['totalScore', 'reviewsCount']] = df_master[['totalScore', 'reviewsCount']].fillna(0)
df_master['weighted_score'] = calculate_weighted_rating(df_master)
print("Bayesian Ağırlıklı Puanlama (weighted_score) hesaplandı.")
# ==============================================================================
# STAGE 7: DYNAMIC FEATURE EXTRACTION (ONE-HOT ENCODING)
# ==============================================================================
def discover_all_features(df):
"""additionalInfo içindeki tüm hiyerarşik özellikleri keşfeder."""
unique_features = set()
for info in df['additionalInfo']:
if isinstance(info, dict):
for category, items in info.items():
if isinstance(items, list):
for item in items:
for feature_name in item.keys():
unique_features.add(f"{category}_{feature_name}")
return list(unique_features)
# Keşfedilen her özellik için dinamik sütun oluşturma (True/False)
all_possible_columns = discover_all_features(df_master)
def dynamic_filler(row, feature_path):
category, feature_name = feature_path.split('_', 1)
info = row.get('additionalInfo', {})
if not isinstance(info, dict): return False
category_list = info.get(category, [])
if isinstance(category_list, list):
for item in category_list:
if item.get(feature_name) is True: return True
return False
for feature in all_possible_columns:
clean_col_name = feature.replace(" ", "_").replace(":", "").lower()
df_master[clean_col_name] = df_master.apply(lambda r: dynamic_filler(r, feature), axis=1)
print(f"Dinamik Özellik Çıkarımı Tamamlandı. {len(all_possible_columns)} yeni özellik eklendi.")
print("Veri işleme süreci başarıyla sonuçlandı.")
# ==============================================================================
# STAGE 8: TEMPORAL ANALYSIS & OPENING HOURS IMPUTATION
# ==============================================================================
def check_24_7(hours_list):
"""Mekanın 7/24 açık olup olmadığını kontrol eder."""
if not isinstance(hours_list, list) or len(hours_list) == 0:
return False
return all("Open 24 hours" in str(day.get('hours', '')) for day in hours_list)
def impute_opening_hours(row):
"""Eksik çalışma saatlerini kategori bazlı tahmini verilerle doldurur."""
hours = row.get('openingHours')
if isinstance(hours, list) and len(hours) > 0:
return hours
cat = str(row.get('main_category', ''))
# Kategori bazlı standart çalışma saatleri ataması
mapping = {
'Religious & Spiritual': "Open according to prayer times (Generally 05:00 - 22:00)",
'Museum & Art': "Generally 09:00 - 17:00 (Check before visiting, may be closed on Mondays)",
'History & Heritage': "Generally 09:00 - 17:00 (Check before visiting, may be closed on Mondays)",
'Nature & Parks': "Open 24 hours every day of the week",
'Squares & Plazas': "Open 24 hours every day of the week",
'Shopping & Traditional Bazaar': "Generally 09:00 - 19:00 (May vary on Sundays)",
'Food & Drink': "Generally 08:00 - 22:00 (Varies by establishment)"
}
return mapping.get(cat, "Opening hours not specified")
# 24/7 bayrağı ve UI için görüntüleme sütunları oluşturuluyor
df_master['is_24_7'] = df_master['openingHours'].apply(check_24_7)
df_master['openingHours_display'] = df_master.apply(impute_opening_hours, axis=1)
df_master['is_hours_estimated'] = df_master['openingHours'].apply(lambda x: not (isinstance(x, list) and len(x) > 0))
# ==============================================================================
# STAGE 9: LOCALIZATION & SMART TITLE SYNTHESIS
# ==============================================================================
def tr_title(text):
"""Türkçe karakter duyarlı Title Case dönüşümü yapar."""
if pd.isna(text): return ""
words = str(text).split()
fixed_words = []
for w in words:
if w.startswith('i'): fixed_words.append('İ' + w[1:].lower())
elif w.startswith('ı'): fixed_words.append('I' + w[1:].lower())
else: fixed_words.append(w.capitalize())
return " ".join(fixed_words)
def smart_title_fix(row):
"""TR ve ENG başlıkları akıllıca birleştirerek 'display_title' oluşturur."""
eng_title = str(row.get('title', '')).strip()
tr_title_val = str(row.get('subTitle', '')).strip()
if not tr_title_val or tr_title_val.lower() == eng_title.lower() or tr_title_val == "None":
return eng_title
return f"{tr_title_val} ({eng_title})"
df_master['subTitle'] = df_master['subTitle'].apply(tr_title)
df_master['display_title'] = df_master.apply(smart_title_fix, axis=1)
# ==============================================================================
# STAGE 10: SPATIAL DATA EXTRACTION (COORDINATES)
# ==============================================================================
# Karmaşık location sözlüğü latitude ve longitude sütunlarına ayrıştırılıyor
coords = df_master['location'].apply(lambda x: pd.Series(x) if isinstance(x, dict) else pd.Series({'lat': np.nan, 'lng': np.nan}))
df_master['latitude'], df_master['longitude'] = coords['lat'], coords['lng']
df_master = df_master.drop(columns=['location'])
# ==============================================================================
# STAGE 11: QUALITY SCORING & DEDUPLICATION
# ==============================================================================
def calculate_record_score(row):
"""Veri doluluğuna göre kaydın kalite puanını hesaplar (Max: 35)."""
score = 0
if pd.notna(row.get('description')) and len(str(row['description'])) > 5: score += 10
if isinstance(row.get('additionalInfo'), (dict, list)) and len(row['additionalInfo']) > 0: score += 8
if isinstance(row.get('openingHours'), list) and len(row['openingHours']) > 0: score += 7
if pd.notna(row.get('subTitle')) and len(str(row['subTitle'])) > 2: score += 5
if pd.notna(row.get('reviewsCount')): score += 3
if pd.notna(row.get('totalScore')): score += 2
return score
# Skorlama ve en kaliteli kaydı üstte tutacak şekilde sıralama
df_master['quality_score'] = df_master.apply(calculate_record_score, axis=1)
df_master = df_master.sort_values(by=['placeId', 'quality_score'], ascending=[True, False])
# Mükerrer kayıtların (Duplicate) temizlenmesi
duplicate_count = df_master['placeId'].duplicated().sum()
df_master = df_master.drop_duplicates(subset=['placeId'], keep='first')
print(f"Tekilleştirme: {duplicate_count} mükerrer kayıt elendi.")
print(f"Benzersiz mekan sayısı: {len(df_master)}")
# ==============================================================================
# STAGE 12: CHARACTER SET VALIDATION (FOREIGN LANGUAGE FILTER)
# ==============================================================================
# Latin ve Türkçe alfabesi dışındaki (Arapça, Kiril vb.) karakterleri yakalayan Regex
non_latin_regex = r'[^\x00-\x7FİıĞğÜüŞşÖöÇçâîûÂÎÛ\s\d\.,\-\(\)\&\'\!]'
foreign_data = df_master[df_master['title'].str.contains(non_latin_regex, na=False, regex=True)].copy()
print(f"--- Toplam {len(foreign_data)} Adet Yabancı Alfabeli/Karakterli Veri Bulundu ---\n")
if len(foreign_data) > 0:
# Tüm listeyi görmek için kısıtlamayı kaldıralım
pd.set_option('display.max_rows', None)
print("--- Silinecek Yabancı Kayıtlar ---")
# Listeyi ekrana yazdır (Örn: Arapça, Çince veya Kiril isimli mekanlar)
print(foreign_data[['title', 'city', 'source_file']])
# Hangi dosyadan ne kadar yabancı veri geldiğini görelim
print("\n--- Kaynak Dosya Dağılımı ---")
print(foreign_data['source_file'].value_counts())
# 3. TEMİZLİK: Bu yabancı kayıtları ana veriden (df_master) silelim
df_master = df_master.drop(foreign_data.index)
print(f"\n✅ Yabancı alfabeli {len(foreign_data)} kayıt temizlendi.")
pd.reset_option('display.max_rows')
else:
print("Latin dışı hiçbir karakter bulunamadı.")
# ==============================================================================
# STAGE 13: CATEGORY MAPPING & CLEANING
# ==============================================================================
# 1. Kara Liste Tanımlama (Gezi rehberinde asla olmaması gerekenler)
kara_liste = [
'Turizm', 'Tourism', 'Agency', 'Travel', 'Clinic', 'Coiffeur', 'Halı', 'Carpet',
'Workshop', 'Medical', 'Acente', 'Gayrimenkul', 'Emlak', 'Acentesi', 'Global', 'AGN TURİZM','otobüsleri', 'kalkış noktası', 'bus station', 'departure'
]
# 2. Gelişmiş Mapping Kuralları (Sıralama Önceliği Korunarak Güncellendi)
# NOT: 'Food & Drink' en sondadır. Böylece tarihi bir kafe önce 'History' olarak yakalanır.
mapping_rules = {
'Food & Drink': [
'restaurant', 'cafe', 'coffee', 'kebab', 'bakery', 'pub', 'bar', 'breakfast', 'lokanta', 'kahve', 'fırın',
'tatlıcı', 'pastane', 'meyhane', 'döner', 'pizza', 'steakhouse', 'grill', 'bistro', 'patisserie', 'brasserie',
'cafeteria', 'mutfağı', 'sofrası', 'kebap', 'köfte', 'dürüm', 'restorant', 'winery', 'gastronomi'
],
'Museum & Art': [
'museum', 'art', 'gallery', 'exhibition', 'müze','müzesi', 'galeri', 'sergi', 'sanat', 'atolye', 'atölye',
'theater', 'opera', 'sinema', 'kütüphane', 'library', 'kültür merkezi', 'cultural center','mural', 'murral', 'murales', 'streetart', 'graffiti', 'stairs', 'staircase', 'merdiven'
],
'Religious & Spiritual': [
'mosque', 'church', 'synagogue', 'tomb', 'hazire', 'cemetery', 'cami','camii' 'kilise', 'havra',
'türbe', 'hazire', 'mezarlık', 'dergah', 'tekke', 'namazgah', 'kabri', 'mezarı', 'kabristan',
'mausoleum', 'cathedral'
],
'History & Heritage': [
'historical', 'monument', 'castle', 'palace', 'bridge', 'tower', 'landmark', 'saray', 'kale', 'kule', 'köprü', 'anıt',
'tarihi', 'sarnıç', 'cistern', 'hamam', 'bath', 'aqueduct', 'su kemeri', 'fountain', 'çeşme', 'sebil', 'terazi',
'obelisk', 'dikilitaş', 'köşk', 'pavilion', 'mansion', 'kasrı', 'sur', 'kapısı', 'gate', 'fortress', 'walls',
'medrese', 'madrasa', 'taşı', 'tekfur', 'bedesten','feneri', 'lighthouse', 'anıtı', 'memorial', 'statue', 'heykel', r'\bhanı\b' # \bhanı\b ile hancı kelimesini engelledik
],
'Shopping & Traditional Bazaar': ['bazaar', 'market', 'han', 'çarşı', 'pazar', 'shopping', 'mall', 'bedesten', 'arasta', 'pasaj'],
'Nature & Parks': [
'park', 'garden', 'scenic', 'nature', 'forest', 'island', 'bahçe', 'koru', 'ada', 'doğa', 'manzara',
'hill', 'tepe', 'sahil', 'coast', 'plaj', 'yürüyüş yolu', 'köy yolu','sunset', 'günbatımı', 'spot', 'seyir' # 'yol' kelimesi adreslerle karışmaması için spesifikleştirildi
],
'Squares & Plazas': ['plaza', 'square', 'meydan', 'alanı', 'iskelesi', 'iskeleye', 'eminönü', 'beşiktaş']
}
def evliyapp_mapper(row):
title = str(row.get('title', '')).lower()
sub_title = str(row.get('subTitle', '')).lower()
cat_main = str(row.get('categoryName', '')).lower()
# Categories listesini güvenli şekilde birleştir
cats_list = row.get('categories', [])
cats_joined = " ".join(cats_list).lower() if isinstance(cats_list, list) else ""
# --- ADIM 1: Kara Liste Kontrolü (Sadece Başlık ve subTitle üzerinden) ---
if any(word.lower() in title or word.lower() in sub_title for word in kara_liste):
return "DELETE", "Blacklist"
# --- ADIM 2: Süper Birleşik Metin (Combined) ---
# Başlık + Türkçe İsim + Ana Kategori + Kategori Listesi
combined = f"{title} {sub_title} {cat_main} {cats_joined}"
# --- ADIM 3: Kategori Mapping ---
for main_cat, keywords in mapping_rules.items():
for word in keywords:
# Eğer kelime zaten regex (\b) içeriyorsa doğrudan kullan, yoksa oluştur
if r'\b' in word:
pattern = word
elif len(word) <= 4:
pattern = r'\b' + re.escape(word) + r'\b'
else:
pattern = re.escape(word)
if re.search(pattern, combined):
return main_cat, word.title()
return 'Sightseeing', 'General'
results = df_master.apply(lambda r: pd.Series(evliyapp_mapper(r)), axis=1)
df_master['main_category'] = results[0]
df_master['sub_category'] = results[1]
df_master = df_master[df_master['main_category'] != "DELETE"].copy()
# ==============================================================================
# STAGE 14: UPDATING THE DESCRIPTION COLUMN
# ==============================================================================
def generate_display_description(row):
# 1. Mevcut ve yeterli bir açıklama varsa onu koru
original = str(row.get('description', '')).strip()
if pd.notna(row.get('description')) and len(original) > 25:
return original
# 2. Temel Değişkenler
title = row.get('title', 'This location')
city = row.get('city', 'Istanbul')
main_cat = str(row.get('main_category', 'point of interest')).lower()
score = row.get('totalScore')
# 3. Kategoriye Özel Estetik Cümleler
if main_cat == 'food & drink':
sentence = f"Discover the local flavors at {title}, a popular spot in {city} known for its inviting atmosphere."
elif main_cat == 'religious & spiritual':
sentence = f"{title} stands as a serene spiritual site in {city}, offering visitors a peaceful retreat and historical depth."
elif main_cat == 'museum & art':
sentence = f"Immerse yourself in cultural heritage at {title}, where {city}'s artistic and historical legacy comes to life."
elif main_cat == 'history & heritage':
sentence = f"{title} is a landmark of historical significance in {city}, reflecting the rich architectural tapestry of the area."
elif main_cat == 'shopping & traditional bazaar':
sentence = f"Experience the authentic vibe of {city} at {title}, a vibrant destination perfect for traditional shopping and local crafts."
elif main_cat == 'nature & parks':
sentence = f"Enjoy a breath of fresh air at {title}, a beautiful green space in {city} ideal for relaxation and outdoor moments."
elif main_cat == 'squares & plazzas': # Yazım hatası olasılığına karşı kontrol
sentence = f"{title} is a central landmark in {city}, serving as a lively meeting point and a great spot to observe city life."
else:
sentence = f"{title} is a must-visit {main_cat} in {city}, contributing to the unique and diverse charm of the district."
# 4. SOSYAL KANIT EKLEME (Social Proof)
# Eğer mekanın puanı yüksekse açıklamaya bir 'güven' cümlesi ekler
if pd.notna(score) and score >= 4.2:
sentence += f" It is highly recommended by visitors with a remarkable {score} rating."
return sentence
df_master['description'] = df_master.apply(generate_display_description, axis=1)
# ==============================================================================
# STAGE 15: CREATING A TAGS COLUMN
# ==============================================================================
# Eşleşme garantisi için anahtarları (keys) küçük harf yapılır
raw_mapping = {
# Accessibility
"Accessibility_Wheelchair accessible entrance": "Wheelchair Accessible",
"Accessibility_Wheelchair-accessible entrance": "Wheelchair Accessible",
"Accessibility_Wheelchair accessible parking lot": "Wheelchair Parking",
"Accessibility_Wheelchair-accessible toilet": "Wheelchair Accessible",
"Accessibility_Wheelchair accessible restroom": "Wheelchair Accessible",
# Atmosphere
"Atmosphere_Cosy": "Cozy",
"Atmosphere_Cozy": "Cozy",
"Atmosphere_Trending": "Trendy",
"Atmosphere_Trendy": "Trendy",
"Atmosphere_Historic": "Historic",
"Atmosphere_History": "Historic",
# Amenities & Service
"Amenities_Toilet": "Restroom",
"Amenities_Public restroom": "Restroom",
"Amenities_Wi-Fi": "Free Wi-Fi",
"Service options_Takeaway": "Takeout",
"Service options_Takeout": "Takeout",
# Children
"Children_Good for kids": "Child Friendly",
"Children_Kid-friendly activities": "Child Friendly",
"Children_Good for kids birthday": "Child Friendly",
# Payments & Other
"Payments_Admission fee": "Entry Fee Required", # İki girişin vardı, 'Required' olanı seçtim
"Offerings_Halal food": "Halal Options",
"Highlights_Rooftop seating": "Rooftop",
"Parking_lot": "Parking Available",
"Parking_Paid_parking_lot": "Paid Parking",
"Payments_Credit_cards": "Credit Cards",
"Pets_Dogs allowed inside": "Dog Friendly",
"Pets_Dogs allowed outside": "Dog Friendly"
}
# Kodun içindeki karşılaştırma için mapping'i normalize etme
tag_mapping = {k.lower().replace(" ", "_").replace(":", ""): v for k, v in raw_mapping.items()}
# UI'da gizlenilecek ama veride tuttuğumuz liste
noise_tags = ["Credit Cards", "Debit Cards", "Nfc Mobile Payments",
"Dine-In", "Table Service", "Seating", "Groups", "Tourists",
"Lunch", "Dinner", "Brunch",'Food', 'Service', 'Dining']
def generate_all_tags(row, feature_columns, rules):
tags = set()
# --- BÖLÜM A: KATEGORİ MAPPING (Kelimelerden kategori yakalama) ---
title = str(row.get('title', '')).lower()
sub_title = str(row.get('subTitle', '')).lower()
cat_name = str(row.get('categoryName', '')).lower()
cats_list = row.get('categories', [])
cats_joined = " ".join(cats_list).lower() if isinstance(cats_list, list) else ""
combined_text = f"{title} {sub_title} {cat_name} {cats_joined}"
for main_cat, keywords in rules.items():
for word in keywords:
pattern = r'\b' + re.escape(word) + r'\b' if len(word) <= 4 else re.escape(word)
if re.search(pattern, combined_text):
tags.add(main_cat)
break
# --- BÖLÜM B: OPTİMİZE EDİLMİŞ DİNAMİK ÖZELLİKLER (144 Sütun İşleme) ---
for col in feature_columns:
if row.get(col) == True:
# 1. ADIM: Önce sözlükte (tag_mapping) özel bir karşılığı var mı?
# Sütun isimleri küçük harf olduğu için tag_mapping de küçük harf bakıyor
if col in tag_mapping:
clean_tag = tag_mapping[col]
else:
# 2. ADIM: Sözlükte yoksa, dinamik temizlik yap.
if "_" in col:
clean_tag = col.split("_", 1)[1]
else:
clean_tag = col
# Alt tireleri boşluğa çevir ve Baş Harflerini Büyüt
clean_tag = clean_tag.replace("_", " ").title().strip()
# Özel düzeltmeler (lot -> Parking Lot gibi)
if clean_tag == "Lot": clean_tag = "Parking Lot"
if clean_tag == "Cards": clean_tag = "Credit Cards"
# 3. ADIM: Son kontrol (Anlamsız çok kısa tagleri engelle)
if len(clean_tag) > 2:
tags.add(clean_tag)
return list(tags)
# --- UYGULAMA ---
# Dinamik sütun listesini hazırla - BURAYA .lower() EKLENDİ (Kritik Düzeltme)
dynamic_cols = [c.replace(" ", "_").replace(":", "").lower() for c in all_possible_columns]
existing_dynamic_cols = [col for col in dynamic_cols if col in df_master.columns]
print(f"Eşleşen dinamik sütun sayısı: {len(existing_dynamic_cols)}")
# Tags sütununu oluştur
df_master['tags'] = df_master.apply(lambda r: generate_all_tags(r, existing_dynamic_cols, mapping_rules), axis=1)
# Sadece tags listesi boş olan ve kategorisi 'General' kalan 'noise' mekanları temizle
df_master = df_master[~((df_master['tags'].map(len) == 0) & (df_master['main_category'] == 'Sightseeing (General)'))]
print("'tags' sütunu başarıyla zenginleştirildi ve temizlik yapıldı.")
# ==============================================================================
# STAGE 16: ENRICHED DESCRIPTION FOR PGVECTOR
# ==============================================================================
def synthesize_enriched_description(row):
# 1. Temel Kimlik
title = str(row.get('display_title', row.get('title', '')))
main_cat = str(row.get('main_category', ''))
sub_cat = str(row.get('sub_category', ''))
# 2. Etiketler
tags_list = row.get('tags', [])
tags_text = ", ".join(tags_list) if isinstance(tags_list, list) else ""
# 3. Saat Bilgisi
hours = row.get('openingHours_display', '')
if isinstance(hours, list):
hours = "Open 24 hours" if row.get('is_24_7') else "Check hours online"
score = str(row.get('totalScore', ''))
original_desc = str(row.get('description', ''))
# 4. SENTEZ
enriched = f"{title}. Category: {main_cat} ({sub_cat}). "
if tags_text:
enriched += f"Features: {tags_text}. "
if hours:
enriched += f"Status: {hours}. "
enriched += f"Info: {original_desc} "
if score and score != '0':
enriched += f"Rating: {score}/5."
return enriched
df_master['enriched_description'] = df_master.apply(synthesize_enriched_description, axis=1)
print("enriched_description sütunu dinamik özelliklerle birlikte oluşturuldu.")
# ==============================================================================
# STAGE 17: Refined Version
# ==============================================================================
final_cols = [col for col in df_master.columns if col not in existing_dynamic_cols]
df_final = df_master[final_cols]
print(f"Eski sütun sayısı: {len(df_master.columns)}")
print(f"Yeni sütun sayısı: {len(df_final.columns)}")
print(f"Kalan Sütunlar: {df_final.columns.tolist()}")
# AYRIŞTIRMA (Refined Version)
df_food_app = df_master[df_master['main_category'] == 'Food & Drink'].copy().reset_index(drop=True)
# Geri kalan her şeyi Sightseeing'e atıyoruz ama Food & Drink olmayanları filtreleyerek
df_sightseeing_app = df_master[df_master['main_category'] != 'Food & Drink'].copy().reset_index(drop=True)
# EKSTRA KONTROL: Kategori Atanamamış Kayıtlar
# Eğer main_category null kaldıysa bu kayıtlar 'Genel' bile olamamış demektir
null_cats = df_master[df_master['main_category'].isna()]
# RAPORLAMA VE HATA AYIKLAMA (DEBUGGING)
print(f"--- FİNAL OPERASYON RAPORU ---")
print(f"Toplam İşlenen Kayıt: {len(df_master)}")
print(f"Yemek Yerleri: {len(df_food_app)}")
print(f"Gezi Rehberi: {len(df_sightseeing_app)}")
print(f"Kategori Atanamayan (Null): {len(null_cats)}")
print(f"\n--- Gezi Rehberi Alt Kategori Detayları ---")
print(df_sightseeing_app['main_category'].value_counts())
# 8. SIGHTSEEING (GENEL) ANALİZİ
genel_sayisi = len(df_sightseeing_app[df_sightseeing_app['main_category'] == 'Sightseeing (Genel)'])
if genel_sayisi > 0:
print(f"\n'Sightseeing (Genel)' grubunda {genel_sayisi} mekan var.")
print("En sık rastlanan kategoriName örnekleri:")
print(df_sightseeing_app[df_sightseeing_app['main_category'] == 'Sightseeing (Genel)']['categoryName'].value_counts().head(10))
# DOSYALARI KAYDET
df_food_app.to_json('data_food_ready.json', orient='records', force_ascii=False)
df_sightseeing_app.to_json('data_sightseeing_ready.json', orient='records', force_ascii=False)
print("\nDosyalar 'ready' formatında kaydedildi.")