Upload 8 files
Browse files- app.py +240 -0
- display_listings.py +8 -0
- get_real_home_listing.py +117 -0
- home_listing_display.py +61 -0
- location_map.json +0 -0
- preprocess_pipeline.py +164 -0
- random_forest.joblib +3 -0
- requirements.txt +0 -0
app.py
ADDED
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import joblib
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
from preprocess_pipeline import SingleInstancePreprocessor
|
6 |
+
from get_real_home_listing import get_home_listings
|
7 |
+
from home_listing_display import display_home_listings
|
8 |
+
|
9 |
+
model = joblib.load('random_forest.joblib')
|
10 |
+
preprocessor = SingleInstancePreprocessor()
|
11 |
+
|
12 |
+
|
13 |
+
temp = {}
|
14 |
+
st.set_page_config(
|
15 |
+
page_title="🏠 Ev Fiyatı Tahmin ve Öneri aracı",
|
16 |
+
page_icon="🏠",
|
17 |
+
layout="wide",
|
18 |
+
initial_sidebar_state="expanded",
|
19 |
+
)
|
20 |
+
|
21 |
+
st.markdown("""
|
22 |
+
<style>
|
23 |
+
:root {
|
24 |
+
--primary: #2A3950;
|
25 |
+
--secondary: #00C0A3;
|
26 |
+
--background: #1A1A1A;
|
27 |
+
--surface: #2D2D2D;
|
28 |
+
--text-primary: #FFFFFF;
|
29 |
+
}
|
30 |
+
|
31 |
+
body {
|
32 |
+
font-family: 'Arial', sans-serif;
|
33 |
+
background-color: var(--background);
|
34 |
+
color: var(--text-primary);
|
35 |
+
}
|
36 |
+
|
37 |
+
.stApp {
|
38 |
+
background: var(--background);
|
39 |
+
color: var(--text-primary);
|
40 |
+
font-family: 'Inter', sans-serif;
|
41 |
+
}
|
42 |
+
|
43 |
+
.header-container {
|
44 |
+
background: var(--surface);
|
45 |
+
padding: 3rem;
|
46 |
+
border-radius: 15px;
|
47 |
+
margin-bottom: 2.5rem;
|
48 |
+
box-shadow: 0 6px 25px rgba(0,0,0,0.3);
|
49 |
+
text-align: center;
|
50 |
+
}
|
51 |
+
|
52 |
+
.input-card {
|
53 |
+
background: var(--surface);
|
54 |
+
border-radius: 12px;
|
55 |
+
padding: 2rem;
|
56 |
+
margin: 1.2rem 0;
|
57 |
+
border: 1px solid rgba(255,255,255,0.1);
|
58 |
+
transition: all 0.3s ease;
|
59 |
+
}
|
60 |
+
|
61 |
+
.input-card:hover {
|
62 |
+
transform: translateY(-5px);
|
63 |
+
box-shadow: 0 8px 20px rgba(0,0,0,0.4);
|
64 |
+
}
|
65 |
+
|
66 |
+
.stSelectbox, .stNumberInput, .stTextInput {
|
67 |
+
background: var(--primary) !important;
|
68 |
+
border-radius: 10px !important;
|
69 |
+
border: none !important;
|
70 |
+
color: var(--text-primary) !important;
|
71 |
+
padding: 0.75rem !important;
|
72 |
+
}
|
73 |
+
|
74 |
+
.stButton>button {
|
75 |
+
background: var(--secondary) !important;
|
76 |
+
color: var(--text-primary) !important;
|
77 |
+
border: none;
|
78 |
+
padding: 16px 36px;
|
79 |
+
border-radius: 10px;
|
80 |
+
font-weight: 600;
|
81 |
+
transition: all 0.3s;
|
82 |
+
box-shadow: 0 5px 15px rgba(0,192,163,0.4);
|
83 |
+
}
|
84 |
+
|
85 |
+
.stButton>button:hover {
|
86 |
+
opacity: 0.9;
|
87 |
+
transform: translateY(-2px);
|
88 |
+
box-shadow: 0 6px 20px rgba(0,192,163,0.5);
|
89 |
+
}
|
90 |
+
|
91 |
+
.prediction-card {
|
92 |
+
background: linear-gradient(135deg, var(--primary) 0%, #1B2838 100%);
|
93 |
+
border-radius: 15px;
|
94 |
+
padding: 2.5rem;
|
95 |
+
text-align: center;
|
96 |
+
margin: 2.5rem 0;
|
97 |
+
border: 1px solid rgba(255,255,255,0.15);
|
98 |
+
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
|
99 |
+
}
|
100 |
+
|
101 |
+
.section-title {
|
102 |
+
font-size: 1.4rem;
|
103 |
+
font-weight: 700;
|
104 |
+
color: var(--secondary);
|
105 |
+
margin: 1.8rem 0;
|
106 |
+
display: flex;
|
107 |
+
align-items: center;
|
108 |
+
gap: 1rem;
|
109 |
+
text-transform: uppercase;
|
110 |
+
letter-spacing: 1px;
|
111 |
+
}
|
112 |
+
|
113 |
+
.footer {
|
114 |
+
text-align: center;
|
115 |
+
padding: 2rem;
|
116 |
+
color: rgba(255,255,255,0.7);
|
117 |
+
font-size: 0.95rem;
|
118 |
+
margin-top: 3.5rem;
|
119 |
+
border-top: 1px solid rgba(255,255,255,0.1);
|
120 |
+
}
|
121 |
+
|
122 |
+
/* Ek olarak, mobil uyumluluk için medya sorguları ekleyebilirsiniz */
|
123 |
+
@media (max-width: 768px) {
|
124 |
+
.header-container {
|
125 |
+
padding: 2rem;
|
126 |
+
margin-bottom: 2rem;
|
127 |
+
}
|
128 |
+
.section-title {
|
129 |
+
font-size: 1.2rem;
|
130 |
+
margin: 1.5rem 0;
|
131 |
+
}
|
132 |
+
}
|
133 |
+
</style>
|
134 |
+
""", unsafe_allow_html=True)
|
135 |
+
|
136 |
+
with st.container():
|
137 |
+
st.markdown("""
|
138 |
+
<div class="header-container">
|
139 |
+
<h1 style="margin:0;font-size:2.5rem">🏠 Ev Tahmin</h1>
|
140 |
+
<p style="color:rgba(255,255,255,0.8);margin-top:0.5rem">Ev Fiyatı Tahmin Ve Öneri Aracı</p>
|
141 |
+
</div>
|
142 |
+
""", unsafe_allow_html=True)
|
143 |
+
|
144 |
+
with st.container():
|
145 |
+
col1, col2 = st.columns([1, 1.2])
|
146 |
+
|
147 |
+
with col1:
|
148 |
+
st.markdown('<div class="section-title">📍 Lokasyon Bilgileri</div>', unsafe_allow_html=True)
|
149 |
+
st.markdown('<div style="color:rgba(255,255,255,0.8);margin-bottom:1rem">Ev fiyatı tahmini yapmak için lütfen aşağıdaki bilgileri doldurun.</div>', unsafe_allow_html=True)
|
150 |
+
|
151 |
+
with st.container():
|
152 |
+
st.markdown('<div class="input-card">', unsafe_allow_html=True)
|
153 |
+
il_listesi = list(preprocessor.location_map.keys())
|
154 |
+
selected_il = st.selectbox('İl', options=il_listesi)
|
155 |
+
ilceler = list(preprocessor.location_map.get(selected_il, {}).keys())
|
156 |
+
selected_ilce = st.selectbox('İlçe', options=ilceler)
|
157 |
+
mahalleler = preprocessor.location_map.get(selected_il, {}).get(selected_ilce, [])
|
158 |
+
selected_mahalle = st.selectbox('Mahalle', options=mahalleler)
|
159 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
160 |
+
|
161 |
+
st.markdown('<div class="section-title">📐 Temel Özellikler</div>', unsafe_allow_html=True)
|
162 |
+
with st.container():
|
163 |
+
st.markdown('<div class="input-card">', unsafe_allow_html=True)
|
164 |
+
tipi = st.selectbox('Konut Tipi', options=['Daire', 'Residence', 'Villa', 'Müstakil Ev', 'Yazlık'])
|
165 |
+
brut_metrekare = st.number_input('Brüt Metrekare (m²)', min_value=30, max_value=500, value=100)
|
166 |
+
binanin_yasi = st.number_input('Bina Yaşı', min_value=0, max_value=100, value=5)
|
167 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
168 |
+
|
169 |
+
with col2:
|
170 |
+
st.markdown('<div class="section-title">📊 Detaylı Özellikler</div>', unsafe_allow_html=True)
|
171 |
+
with st.container():
|
172 |
+
st.markdown('<div class="input-card">', unsafe_allow_html=True)
|
173 |
+
cols = st.columns(2)
|
174 |
+
with cols[0]:
|
175 |
+
oda_sayisi = st.selectbox('Oda', options=['1+1', '2+1', '3+1', '4+1', '5+1'])
|
176 |
+
banyo_sayisi = st.number_input('Banyo', min_value=1, max_value=5, value=1)
|
177 |
+
isitma_tipi = st.selectbox('Isıtma Tipi', options=['Doğalgaz', 'Merkezi Sistem', 'Elektrikli', 'Soba'])
|
178 |
+
with cols[1]:
|
179 |
+
bulundugu_kat = 3 # Ortalama veya tipik bir değer
|
180 |
+
binanin_kat_sayisi = 10 # Ortalama veya tipik bir değer
|
181 |
+
esya_durumu = st.selectbox('Eşya Durumu', options=['Eşyalı', 'Eşyasız'])
|
182 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
183 |
+
|
184 |
+
st.markdown('<div class="section-title">⚙️ Diğer Ayarlar</div>', unsafe_allow_html=True)
|
185 |
+
with st.container():
|
186 |
+
st.markdown('<div class="input-card">', unsafe_allow_html=True)
|
187 |
+
cols = st.columns(2)
|
188 |
+
with cols[0]:
|
189 |
+
site_icerisinde = st.selectbox('Site İçinde', options=['Evet', 'Hayır'])
|
190 |
+
with cols[1]:
|
191 |
+
kullanim_durumu = st.selectbox('Kullanım Durumu', options=['Konut', 'İş Yeri', 'Diğer'])
|
192 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
193 |
+
|
194 |
+
if st.button('💵 Tahmin Yap', use_container_width=True):
|
195 |
+
input_data = {
|
196 |
+
'Oda Sayısı': oda_sayisi,
|
197 |
+
'Bulunduğu Kat': bulundugu_kat,
|
198 |
+
'Isıtma Tipi': isitma_tipi,
|
199 |
+
'Eşya Durumu': esya_durumu,
|
200 |
+
'Site İçerisinde': site_icerisinde,
|
201 |
+
'Tipi': tipi,
|
202 |
+
'Brüt Metrekare': brut_metrekare,
|
203 |
+
'Binanın Yaşı': binanin_yasi,
|
204 |
+
'Binanın Kat Sayısı': binanin_kat_sayisi,
|
205 |
+
'Kullanım Durumu': kullanim_durumu,
|
206 |
+
'Banyo Sayısı': banyo_sayisi,
|
207 |
+
'İl': selected_il,
|
208 |
+
'İlçe': selected_ilce,
|
209 |
+
'Mahalle': selected_mahalle
|
210 |
+
}
|
211 |
+
|
212 |
+
|
213 |
+
with st.spinner('AI analiz yapıyor...'):
|
214 |
+
input_array = preprocessor.transform(input_data)
|
215 |
+
|
216 |
+
tahmin = model.predict(input_array)
|
217 |
+
|
218 |
+
st.markdown(f'''
|
219 |
+
<div class="prediction-card">
|
220 |
+
<h3 style="margin:0 0 1rem 0;color:rgba(255,255,255,0.9)">TAHMİN EDİLEN DEĞER</h3>
|
221 |
+
<div style="font-size:2.8rem;font-weight:700;color:var(--secondary);margin:1rem 0">{tahmin[0]:,.0f} TL</div>
|
222 |
+
<p style="color:rgba(255,255,255,0.7);margin:0">*Gerçek değerler piyasa koşullarına göre değişiklik gösterebilir</p>
|
223 |
+
</div>
|
224 |
+
''', unsafe_allow_html=True)
|
225 |
+
|
226 |
+
predicted_price = tahmin[0]
|
227 |
+
with st.spinner("Seçim ile benzer özelliğe sahip ilanlar getiriliyor..."):
|
228 |
+
home_listings = get_home_listings(selected_il, predicted_price)
|
229 |
+
display_home_listings(home_listings)
|
230 |
+
|
231 |
+
st.markdown('''
|
232 |
+
<div class="footer">
|
233 |
+
<div> @yusufenes || Konut Fiyatı Tahmin ve Konut Öneri Sistemi || Tüm veriler İnternet Üzerinden Alınmıştır Ticari Kullanım Hakkı Yoktur.</div>
|
234 |
+
<div style="margin-top:0.5rem;font-size:0.8rem">
|
235 |
+
<span style="opacity:0.7">Geliştirici: Yusuf Enes</span> •
|
236 |
+
<span style="opacity:0.7">🧑💻 <a href="https://github.com/yusuffenes">Github</a> </span>
|
237 |
+
</div>
|
238 |
+
</div>
|
239 |
+
''', unsafe_allow_html=True)
|
240 |
+
|
display_listings.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def display_listings(listings):
|
2 |
+
for i in range(0, len(listings), 2):
|
3 |
+
listing1 = listings[i]
|
4 |
+
print(f"Listing 1: {listing1['title']}, {listing1['url']}")
|
5 |
+
if i + 1 < len(listings):
|
6 |
+
listing2 = listings[i + 1]
|
7 |
+
print(f"Listing 2: {listing2['title']}, {listing2['url']}")
|
8 |
+
print("-" * 50)
|
get_real_home_listing.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from selenium import webdriver
|
2 |
+
from selenium.webdriver.chrome.service import Service
|
3 |
+
from selenium.webdriver.chrome.options import Options
|
4 |
+
from selenium.webdriver.common.by import By
|
5 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.common.exceptions import NoSuchElementException
|
8 |
+
import json
|
9 |
+
import pandas as pd
|
10 |
+
import requests
|
11 |
+
from bs4 import BeautifulSoup
|
12 |
+
|
13 |
+
def format_price(price):
|
14 |
+
# Remove dots and convert to integer
|
15 |
+
price = str(price)[0:-2]
|
16 |
+
clean_price = price.replace('.', '')
|
17 |
+
return int(clean_price)
|
18 |
+
|
19 |
+
|
20 |
+
def get_home_listings(selected_il, price_value):
|
21 |
+
# Set up Chrome options
|
22 |
+
chrome_options = Options()
|
23 |
+
chrome_options.add_argument('--headless') # Run Chrome in headless mode
|
24 |
+
chrome_options.add_argument('--start-maximized')
|
25 |
+
|
26 |
+
# Initialize the Chrome driver
|
27 |
+
driver = webdriver.Chrome(options=chrome_options)
|
28 |
+
|
29 |
+
# Navigate to the website
|
30 |
+
driver.get('https://www.emlakjet.com/')
|
31 |
+
|
32 |
+
# Wait for the element to be present and then send input data
|
33 |
+
search_input = WebDriverWait(driver, 10).until(
|
34 |
+
EC.presence_of_element_located((By.XPATH, '//*[@id="headlessui-tabs-panel-:r9:"]/div/div[2]/div/div/div/input'))
|
35 |
+
)
|
36 |
+
search_input.send_keys(f'{selected_il}')
|
37 |
+
# Click the dropdown to open the price range options
|
38 |
+
dropdown_button = driver.find_element(By.XPATH, '//*[@id="headlessui-listbox-button-:rh:"]')
|
39 |
+
dropdown_button.click()
|
40 |
+
|
41 |
+
# Calculate lower and upper bounds
|
42 |
+
# Remove periods from the price string before calculation
|
43 |
+
price_value = format_price(price_value)
|
44 |
+
lower_bound = price_value - 500000
|
45 |
+
upper_bound = price_value + 500000
|
46 |
+
|
47 |
+
# Locate the first input element and set it to the lower bound
|
48 |
+
first_input = WebDriverWait(driver, 10).until(
|
49 |
+
EC.presence_of_element_located((By.XPATH, '//*[@id="headlessui-listbox-options-:ri:"]/ul[1]/div[1]/div/div[1]/input'))
|
50 |
+
)
|
51 |
+
first_input.clear()
|
52 |
+
first_input.send_keys(str(lower_bound))
|
53 |
+
|
54 |
+
# Locate the second input element and set it to the upper bound
|
55 |
+
second_input = WebDriverWait(driver, 10).until(
|
56 |
+
EC.presence_of_element_located((By.XPATH, '//*[@id="headlessui-listbox-options-:ri:"]/ul[2]/div[1]/div/div[1]/input'))
|
57 |
+
)
|
58 |
+
second_input.clear()
|
59 |
+
second_input.send_keys(str(upper_bound))
|
60 |
+
|
61 |
+
# Click the "Find" button to perform the search
|
62 |
+
find_button = WebDriverWait(driver, 10).until(
|
63 |
+
EC.element_to_be_clickable((By.XPATH, '//*[@id="headlessui-tabs-panel-:r9:"]/div/div[5]/div/button'))
|
64 |
+
)
|
65 |
+
find_button.click()
|
66 |
+
|
67 |
+
i = 1
|
68 |
+
data = []
|
69 |
+
|
70 |
+
while i<=10:
|
71 |
+
WebDriverWait(driver,10).until(EC.presence_of_element_located((By.XPATH, f'//*[@id="content-wrapper"]/div[1]/div[4]/div[2]/div[3]/div[{""+str(i)+""}]/div/a')))
|
72 |
+
link = driver.find_element(By.XPATH, f'//*[@id="content-wrapper"]/div[1]/div[4]/div[2]/div[3]/div[{""+str(i)+""}]/div/a')
|
73 |
+
driver.get(link.get_attribute('href'))
|
74 |
+
detail_url = driver.current_url
|
75 |
+
# Wait for the features list to load
|
76 |
+
WebDriverWait(driver, 3).until(
|
77 |
+
EC.presence_of_element_located((By.ID, "ilan-hakkinda"))
|
78 |
+
)
|
79 |
+
|
80 |
+
try:
|
81 |
+
ul = driver.find_element(By.XPATH, '//*[@id="ilan-hakkinda"]/div/div/ul')
|
82 |
+
list_items = ul.find_elements(By.TAG_NAME, 'li')
|
83 |
+
|
84 |
+
details = {}
|
85 |
+
for item in list_items:
|
86 |
+
try:
|
87 |
+
key = item.find_element(By.CLASS_NAME, 'styles_key__VqMhC').text
|
88 |
+
value = item.find_element(By.CLASS_NAME, 'styles_value__3QmL3').text
|
89 |
+
details[key] = value
|
90 |
+
except NoSuchElementException:
|
91 |
+
continue
|
92 |
+
|
93 |
+
# Extract the title
|
94 |
+
title = driver.find_element(By.XPATH, '//*[@id="content-wrapper"]/div[2]/div[1]/div/h1').text
|
95 |
+
resim_url = driver.find_element(By.XPATH, '//*[@id="content-wrapper"]/div[2]/div[2]/div[2]/img').get_attribute('src')
|
96 |
+
fiyat = driver.find_element(By.XPATH, '//*[@id="genel-bakis"]/div[1]/div[1]/div[1]/div/span').text
|
97 |
+
fiyat = int(fiyat.replace('.','').replace('TL',''))
|
98 |
+
# Add the URL and title to the details
|
99 |
+
details['url'] = detail_url
|
100 |
+
details['title'] = title
|
101 |
+
details['resim_url'] = resim_url
|
102 |
+
details['price'] = fiyat
|
103 |
+
|
104 |
+
|
105 |
+
|
106 |
+
data.append(details)
|
107 |
+
|
108 |
+
except NoSuchElementException as e:
|
109 |
+
print(f"Element not found: {e}")
|
110 |
+
except Exception as e:
|
111 |
+
print(f"An error occurred: {e}")
|
112 |
+
|
113 |
+
driver.execute_script("window.history.go(-1)")
|
114 |
+
i += 1
|
115 |
+
|
116 |
+
driver.quit()
|
117 |
+
return data
|
home_listing_display.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
def display_home_listings(listings):
|
4 |
+
st.markdown("""
|
5 |
+
<style>
|
6 |
+
.listing-card {
|
7 |
+
background: #2D2D2D;
|
8 |
+
border-radius: 10px;
|
9 |
+
padding: 1rem;
|
10 |
+
margin: 1rem 0;
|
11 |
+
border: 1px solid rgba(255,255,255,0.1);
|
12 |
+
transition: transform 0.2s;
|
13 |
+
}
|
14 |
+
.listing-card:hover {
|
15 |
+
transform: translateY(-2px);
|
16 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
17 |
+
}
|
18 |
+
.price-tag {
|
19 |
+
color: #00C0A3;
|
20 |
+
font-size: 1.4rem;
|
21 |
+
font-weight: bold;
|
22 |
+
margin: 0.5rem 0;
|
23 |
+
}
|
24 |
+
.property-info {
|
25 |
+
color: rgba(255,255,255,0.8);
|
26 |
+
font-size: 0.9rem;
|
27 |
+
margin: 0.3rem 0;
|
28 |
+
}
|
29 |
+
.listing-link {
|
30 |
+
display: inline-block;
|
31 |
+
background: #2A3950;
|
32 |
+
color: white;
|
33 |
+
padding: 0.5rem 1rem;
|
34 |
+
border-radius: 5px;
|
35 |
+
text-decoration: none;
|
36 |
+
margin-top: 0.5rem;
|
37 |
+
transition: background 0.2s;
|
38 |
+
}
|
39 |
+
.listing-link:hover {
|
40 |
+
background: #374863;
|
41 |
+
}
|
42 |
+
</style>
|
43 |
+
""", unsafe_allow_html=True)
|
44 |
+
|
45 |
+
st.markdown("### 🏠 Benzer Fiyatlı İlanlar", unsafe_allow_html=True)
|
46 |
+
|
47 |
+
col1, col2 = st.columns(2)
|
48 |
+
|
49 |
+
for idx, listing in enumerate(listings):
|
50 |
+
with col1 if idx % 2 == 0 else col2:
|
51 |
+
st.markdown(f"""
|
52 |
+
<div class="listing-card">
|
53 |
+
<img src="{listing['resim_url']}" style="width: 100%; height: 200px; object-fit: cover; object-position: center; border-radius: 5px; margin-bottom: 0.5rem;">
|
54 |
+
<div style="font-size:1.2rem; font-weight:bold">{listing['title']}</div>
|
55 |
+
<div class="price-tag">{listing['price']:,} TL</div>
|
56 |
+
<div class="property-info">📏 {listing['Brüt Metrekare']}</div>
|
57 |
+
<div class="property-info">🏘️ {listing.get('Oda Sayısı', 'Belirtilmemiş')}</div>
|
58 |
+
<div class="property-info">♨️ {listing.get('Isıtma Tipi', 'Belirtilmemiş')}</div>
|
59 |
+
<a href="{listing['url']}" target="_blank" class="listing-link">İlanı Görüntüle →</a>
|
60 |
+
</div>
|
61 |
+
""", unsafe_allow_html=True)
|
location_map.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
preprocess_pipeline.py
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import re
|
3 |
+
import json
|
4 |
+
import pandas as pd
|
5 |
+
|
6 |
+
class SingleInstancePreprocessor:
|
7 |
+
def __init__(self, location_map_path='location_map.json'):
|
8 |
+
self.isitma_mapping = {
|
9 |
+
"Doğalgaz": 5,
|
10 |
+
"Merkezi Sistem": 6,
|
11 |
+
"Elektrikli": 3,
|
12 |
+
"Soba": 1,
|
13 |
+
"Isıtma Yok": 0,
|
14 |
+
"Other": 4,
|
15 |
+
"Klimalı": 3,
|
16 |
+
"Doğalgaz Sobalı": 4,
|
17 |
+
"Merkezi (Pay Ölçer)": 6,
|
18 |
+
"Merkezi Doğalgaz": 6,
|
19 |
+
"Güneş Enerjisi": 9,
|
20 |
+
"Yerden Isıtma": 7,
|
21 |
+
"Merkezi Kömür": 8,
|
22 |
+
"Şömine": 2,
|
23 |
+
"Jeotermal": 10,
|
24 |
+
"Kat Kaloriferi": 11,
|
25 |
+
"Isı Pompası": 12
|
26 |
+
}
|
27 |
+
self.esya_mapping = {
|
28 |
+
"Eşyalı": 1,
|
29 |
+
"Eşyasız": 0,
|
30 |
+
"Belirtilmemiş": 2,
|
31 |
+
"Boşluk": 2
|
32 |
+
}
|
33 |
+
self.tipi_mapping = {
|
34 |
+
"Daire": 3,
|
35 |
+
"Residence": 5,
|
36 |
+
"Villa": 8,
|
37 |
+
"Müstakil Ev": 6,
|
38 |
+
"Yazlık": 4,
|
39 |
+
"Bina": 3,
|
40 |
+
"Köy Evi": 1,
|
41 |
+
"Çiftlik Evi": 2,
|
42 |
+
"Devremülk": 5,
|
43 |
+
"Yalı Dairesi": 7,
|
44 |
+
"Köşk": 9,
|
45 |
+
"Prefabrik": 1,
|
46 |
+
"Yalı": 12,
|
47 |
+
"Dağ Evi": 4,
|
48 |
+
"Kooperatif": 3
|
49 |
+
}
|
50 |
+
self.kullanim_mapping = {
|
51 |
+
"Konut": 0,
|
52 |
+
"İş Yeri": 1,
|
53 |
+
"Diğer": 2,
|
54 |
+
"Boş": 3,
|
55 |
+
"Boşluk2": 3
|
56 |
+
}
|
57 |
+
self.yatirim_mapping = {
|
58 |
+
'Yatırıma Uygun':'10',
|
59 |
+
'Yatırıma Uygun Değil':'0',
|
60 |
+
'Bilinmiyor':'1',
|
61 |
+
'Belirtilmemiş':'1'
|
62 |
+
}
|
63 |
+
|
64 |
+
self.oturma_mapping = {
|
65 |
+
'Kendisi' : 0,
|
66 |
+
'Kiracı': 1,
|
67 |
+
}
|
68 |
+
|
69 |
+
try:
|
70 |
+
with open(location_map_path, 'r', encoding='utf-8') as f:
|
71 |
+
self.location_map = json.load(f)
|
72 |
+
except FileNotFoundError:
|
73 |
+
print(f"Hata: {location_map_path} dosyası bulunamadı.")
|
74 |
+
self.location_map = {}
|
75 |
+
|
76 |
+
def transform(self, input_data):
|
77 |
+
"""
|
78 |
+
Tek bir örnek için girdi verilerini dönüştürür.
|
79 |
+
"""
|
80 |
+
input_series = pd.Series(input_data)
|
81 |
+
|
82 |
+
oda = input_series.get("Oda Sayısı")
|
83 |
+
if isinstance(oda, str):
|
84 |
+
oda = oda.strip()
|
85 |
+
if "Stüdyo" in oda:
|
86 |
+
oda_val = 0.0
|
87 |
+
elif "+" in oda:
|
88 |
+
try:
|
89 |
+
parts = oda.split("+")
|
90 |
+
oda_val = float(parts[0]) + float(parts[1])
|
91 |
+
except Exception:
|
92 |
+
oda_val = float(oda.replace("Oda", "").strip())
|
93 |
+
else:
|
94 |
+
oda_val = float(oda.replace("Oda", "").strip())
|
95 |
+
else:
|
96 |
+
oda_val = float(oda)
|
97 |
+
|
98 |
+
kat = input_series.get("Bulunduğu Kat")
|
99 |
+
if isinstance(kat, str):
|
100 |
+
kat_val = int(re.sub(r'\D', '', kat))
|
101 |
+
else:
|
102 |
+
kat_val = int(kat)
|
103 |
+
|
104 |
+
isitma = input_series.get("Isıtma Tipi")
|
105 |
+
isitma_val = self.isitma_mapping.get(isitma, 4)
|
106 |
+
|
107 |
+
esya = input_series.get("Eşya Durumu")
|
108 |
+
esya_val = self.esya_mapping.get(esya, 2)
|
109 |
+
|
110 |
+
site = input_series.get("Site İçerisinde", "Hayır")
|
111 |
+
site_val = 1 if site == "Evet" else 0
|
112 |
+
|
113 |
+
tipi = input_series.get("Tipi")
|
114 |
+
tipi_val = self.tipi_mapping.get(tipi, 3)
|
115 |
+
|
116 |
+
metrekare = input_series.get("Brüt Metrekare")
|
117 |
+
if isinstance(metrekare, str):
|
118 |
+
metrekare_val = float(re.sub(r'[^0-9]', '', metrekare))
|
119 |
+
else:
|
120 |
+
metrekare_val = float(metrekare)
|
121 |
+
|
122 |
+
yas = input_series.get("Binanın Yaşı")
|
123 |
+
if isinstance(yas, str):
|
124 |
+
yas_val = int(re.sub(r'\D', '', yas))
|
125 |
+
else:
|
126 |
+
yas_val = int(yas)
|
127 |
+
|
128 |
+
bina_kat = input_series.get("Binanın Kat Sayısı")
|
129 |
+
bina_kat_val = int(bina_kat)
|
130 |
+
|
131 |
+
kullanim = input_series.get("Kullanım Durumu")
|
132 |
+
kullanim_val = self.kullanim_mapping.get(kullanim, 0)
|
133 |
+
|
134 |
+
yatirim = input_series.get("Yatırıma Uygunluk")
|
135 |
+
yatirim_val = self.yatirim_mapping.get(yatirim, 1)
|
136 |
+
|
137 |
+
banyo = input_series.get("Banyo Sayısı")
|
138 |
+
banyo_val = int(banyo)
|
139 |
+
|
140 |
+
il = input_series.get("İl")
|
141 |
+
ilce = input_series.get("İlçe")
|
142 |
+
mahalle = input_series.get("Mahalle")
|
143 |
+
|
144 |
+
il_val = 0
|
145 |
+
ilce_val = 0
|
146 |
+
mahalle_val = 0
|
147 |
+
|
148 |
+
if il in self.location_map:
|
149 |
+
il_val = list(self.location_map.keys()).index(il)
|
150 |
+
if ilce in self.location_map[il]:
|
151 |
+
ilce_val = list(self.location_map[il].keys()).index(ilce)
|
152 |
+
if mahalle in self.location_map[il][ilce]:
|
153 |
+
mahalle_val = self.location_map[il][ilce].index(mahalle)
|
154 |
+
|
155 |
+
oturma_durumu = input_series.get('oturma_durumu')
|
156 |
+
oturma_val = self.oturma_mapping.get(oturma_durumu,0)
|
157 |
+
|
158 |
+
|
159 |
+
features = [oda_val, kat_val, isitma_val, esya_val, site_val, tipi_val,
|
160 |
+
metrekare_val, yas_val, bina_kat_val, kullanim_val, yatirim_val, banyo_val,
|
161 |
+
il_val, ilce_val, mahalle_val,oturma_val]
|
162 |
+
|
163 |
+
X = np.array(features).reshape(1, -1)
|
164 |
+
return X
|
random_forest.joblib
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:b2c594e9fea936e8b651e8b7098dd8174af415960507933fb9f19ad1a51f5dc0
|
3 |
+
size 833520225
|
requirements.txt
ADDED
Binary file (5.75 kB). View file
|
|