Cordobian commited on
Commit
414d659
·
verified ·
1 Parent(s): a2435c0

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +215 -51
src/streamlit_app.py CHANGED
@@ -20,11 +20,14 @@ st.set_page_config(
20
  )
21
 
22
  st.title("⛅ Open‑Meteo • Haritadan Konum Seç • Tahmin + Arşiv (CPU‑only)")
23
- st.caption("Haritaya tıkla → konumu seç → tahmin veya geçmiş verileri getir. "
24
- "Günlük maksimum sıcaklık (temperature_2m_max) vurgulanmıştır.")
 
 
 
25
 
26
  # ------------------------------
27
- # Yardımcı fonksiyonlar
28
  # ------------------------------
29
  FORECAST_BASE = "https://api.open-meteo.com/v1/forecast"
30
  ARCHIVE_BASE = "https://archive-api.open-meteo.com/v1/archive"
@@ -38,6 +41,7 @@ HOURLY_VARS = [
38
  "cloud_cover"
39
  ]
40
 
 
41
  DAILY_VARS = [
42
  "temperature_2m_max",
43
  "temperature_2m_min",
@@ -47,18 +51,28 @@ DAILY_VARS = [
47
  "sunset"
48
  ]
49
 
50
- def fetch_openmeteo(mode, lat, lon, start=None, end=None, forecast_days=7):
51
- """Open-Meteo'dan veri çeker ve saatlik/günlük Pandas DataFrame'leri döndürür."""
 
 
 
 
 
 
 
 
 
52
  params = {
53
  "latitude": lat,
54
  "longitude": lon,
55
  "timezone": "auto",
56
  "hourly": ",".join(HOURLY_VARS),
57
  "daily": ",".join(DAILY_VARS),
58
- "wind_speed_unit": "kmh",
59
- "precipitation_unit": "mm",
60
- "temperature_unit": "celsius",
61
  }
 
62
  if mode == "Tahmin":
63
  params["forecast_days"] = int(forecast_days)
64
  url = FORECAST_BASE
@@ -73,91 +87,164 @@ def fetch_openmeteo(mode, lat, lon, start=None, end=None, forecast_days=7):
73
 
74
  tz = data.get("timezone", "GMT")
75
 
76
- # Saatlik
77
  hourly = data.get("hourly", {})
78
- h_times = hourly.get("time", [])
79
  df_h = None
80
- if h_times:
81
  df_h = pd.DataFrame(hourly)
82
  df_h["time"] = pd.to_datetime(df_h["time"])
83
  df_h = df_h.set_index("time").sort_index()
84
 
85
- # Günlük
86
  daily = data.get("daily", {})
87
- d_times = daily.get("time", [])
88
  df_d = None
89
- if d_times:
90
  df_d = pd.DataFrame(daily)
91
  df_d["time"] = pd.to_datetime(df_d["time"])
 
 
 
 
92
  df_d = df_d.set_index("time").sort_index()
93
 
94
  return tz, df_h, df_d, data
95
 
96
- def fig_hourly(df_h, tz_name):
97
- """Saatlik grafik: sıcaklık (°C), yağış (mm), rüzgâr (km/sa)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  fig = make_subplots(specs=[[{"secondary_y": True}]])
99
- if "temperature_2m" in df_h:
 
 
 
 
 
 
 
 
 
100
  fig.add_trace(go.Scatter(
101
- x=df_h.index, y=df_h["temperature_2m"], mode="lines",
102
- name="Sıcaklık (°C)"
103
  ), secondary_y=False)
104
- if "precipitation" in df_h:
 
105
  fig.add_trace(go.Bar(
106
- x=df_h.index, y=df_h["precipitation"], name="Yağış (mm)", opacity=0.35
107
  ), secondary_y=True)
108
- if "wind_speed_10m" in df_h:
 
109
  fig.add_trace(go.Scatter(
110
- x=df_h.index, y=df_h["wind_speed_10m"], mode="lines",
111
- name="Rüzgâr (km/sa)"
112
  ), secondary_y=True)
113
 
114
  fig.update_layout(
115
  title=f"Saatlik Seri ({tz_name})",
116
  margin=dict(l=10, r=10, t=40, b=10),
117
- height=420,
118
- legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0)
119
  )
120
  fig.update_xaxes(title_text="Zaman")
121
- fig.update_yaxes(title_text="Sıcaklık (°C)", secondary_y=False)
122
- fig.update_yaxes(title_text="Yağış (mm) / Rüzgâr (km/sa)", secondary_y=True)
 
 
 
123
  return fig
124
 
125
- def fig_daily(df_d):
126
- """Günlük grafik: Min‑Max sıcaklık bandı + toplam yağış (mm)."""
 
127
  fig = make_subplots(specs=[[{"secondary_y": True}]])
128
- if {"temperature_2m_min", "temperature_2m_max"}.issubset(df_d.columns):
 
 
129
  fig.add_trace(go.Scatter(
130
  x=df_d.index, y=df_d["temperature_2m_min"], mode="lines",
131
- name="Günlük Min (°C)"
132
  ), secondary_y=False)
133
  fig.add_trace(go.Scatter(
134
  x=df_d.index, y=df_d["temperature_2m_max"], mode="lines",
135
- name="Günlük Max (°C)", fill="tonexty"
136
  ), secondary_y=False)
137
- if "precipitation_sum" in df_d:
 
138
  fig.add_trace(go.Bar(
139
- x=df_d.index, y=df_d["precipitation_sum"], name="Toplam Yağış (mm)", opacity=0.35
 
140
  ), secondary_y=True)
141
 
142
  fig.update_layout(
143
  title="Günlük Özet (Min‑Max Bandı & Yağış)",
144
  margin=dict(l=10, r=10, t=40, b=10),
145
- height=420,
146
- legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0)
147
  )
148
  fig.update_xaxes(title_text="Tarih")
149
- fig.update_yaxes(title_text="Sıcaklık (°C)", secondary_y=False)
150
- fig.update_yaxes(title_text="Yağış (mm)", secondary_y=True)
151
  return fig
152
 
 
153
  def csv_bytes(df: pd.DataFrame) -> bytes:
154
  return df.to_csv(index=True).encode("utf-8")
155
 
 
156
  # ------------------------------
157
  # Kenar çubuğu (ayarlar)
158
  # ------------------------------
159
  with st.sidebar:
160
  st.header("⚙️ Ayarlar")
 
161
  mode = st.radio("Veri tipi", ["Tahmin", "Geçmiş (Arşiv)"], horizontal=True)
162
 
163
  if mode == "Tahmin":
@@ -165,21 +252,52 @@ with st.sidebar:
165
  else:
166
  today = date.today()
167
  default_start = today - timedelta(days=14)
168
- # Open‑Meteo arşiv verilerinde tipik gecikme: 2–5 gün
169
- default_end = today - timedelta(days=5)
170
  start_date = st.date_input("Başlangıç tarihi", value=default_start, max_value=today)
171
  end_date = st.date_input("Bitiş tarihi", value=default_end, max_value=today)
172
  if start_date > end_date:
173
  st.error("Başlangıç tarihi, bitiş tarihinden büyük olamaz.")
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  # ------------------------------
176
- # Harita (konum seçimi)
177
  # ------------------------------
178
  st.subheader("🗺️ Konum seç")
179
  if "lat" not in st.session_state:
180
  st.session_state.lat = 41.0082 # İstanbul
181
  if "lon" not in st.session_state:
182
  st.session_state.lon = 28.9784
 
 
183
 
184
  col_map, col_info = st.columns([2.2, 1.0], vertical_alignment="top")
185
 
@@ -187,9 +305,7 @@ with col_map:
187
  m = folium.Map(location=[st.session_state.lat, st.session_state.lon], zoom_start=6, control_scale=True)
188
  folium.Marker(location=[st.session_state.lat, st.session_state.lon],
189
  tooltip="Seçili konum").add_to(m)
190
- # Tıklanınca koordinatları popup olarak gösterir, st_folium ile Python'a döner
191
  m.add_child(folium.LatLngPopup())
192
-
193
  map_state = st_folium(m, use_container_width=True, height=460, returned_objects=["last_clicked"])
194
 
195
  if map_state and map_state.get("last_clicked"):
@@ -200,7 +316,24 @@ with col_info:
200
  st.write("**Seçili koordinatlar**")
201
  st.metric("Enlem (lat)", st.session_state.lat)
202
  st.metric("Boylam (lon)", st.session_state.lon)
203
- st.caption("Haritaya tıklayarak güncelleyebilirsiniz.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  # ------------------------------
206
  # Veri çekme + görselleştirme
@@ -213,7 +346,10 @@ try:
213
  mode="Tahmin",
214
  lat=st.session_state.lat,
215
  lon=st.session_state.lon,
216
- forecast_days=forecast_days
 
 
 
217
  )
218
  else:
219
  tz, df_h, df_d, raw = fetch_openmeteo(
@@ -221,16 +357,44 @@ try:
221
  lat=st.session_state.lat,
222
  lon=st.session_state.lon,
223
  start=start_date.isoformat(),
224
- end=end_date.isoformat()
 
 
 
225
  )
226
 
 
 
227
  if df_h is not None and not df_h.empty:
228
- st.plotly_chart(fig_hourly(df_h, tz), use_container_width=True)
229
- st.download_button("Saatlik veriyi CSV indir", data=csv_bytes(df_h), file_name="hourly.csv", mime="text/csv")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  if df_d is not None and not df_d.empty:
232
- st.plotly_chart(fig_daily(df_d), use_container_width=True)
233
- st.download_button("Günlük veriyi CSV indir", data=csv_bytes(df_d), file_name="daily.csv", mime="text/csv")
 
 
 
 
 
 
 
 
 
234
 
235
  if (df_h is None or df_h.empty) and (df_d is None or df_d.empty):
236
  st.info("Seçili aralık/konum için veri bulunamadı. Aralığı daraltmayı veya farklı konum seçmeyi deneyin.")
 
20
  )
21
 
22
  st.title("⛅ Open‑Meteo • Haritadan Konum Seç • Tahmin + Arşiv (CPU‑only)")
23
+ st.caption(
24
+ "Haritaya tıklayıp konumu seçin. Lejand öğelerine tıklayarak izleri aç/kapatabilir, "
25
+ "yan menüden katmanları ve birimleri değiştirebilirsiniz. "
26
+ "Çift tıklama = yalnızca seçili iz."
27
+ )
28
 
29
  # ------------------------------
30
+ # API uçları ve varsayılan değişkenler
31
  # ------------------------------
32
  FORECAST_BASE = "https://api.open-meteo.com/v1/forecast"
33
  ARCHIVE_BASE = "https://archive-api.open-meteo.com/v1/archive"
 
41
  "cloud_cover"
42
  ]
43
 
44
+ # Günlükte güneş doğuş/batışını her koşulda tutuyoruz (gece-gündüz gölgeleme için gerekli)
45
  DAILY_VARS = [
46
  "temperature_2m_max",
47
  "temperature_2m_min",
 
51
  "sunset"
52
  ]
53
 
54
+ # ------------------------------
55
+ # Yardımcılar
56
+ # ------------------------------
57
+ def fetch_openmeteo(mode, lat, lon, *, start=None, end=None, forecast_days=7,
58
+ temp_unit="celsius", wind_unit="kmh", precip_unit="mm"):
59
+ """
60
+ Open-Meteo'dan veri çeker ve saatlik/günlük DataFrame döndürür.
61
+ temp_unit: celsius|fahrenheit
62
+ wind_unit: kmh|ms|mph|kn (Open-Meteo parametre adı: windspeed_unit)
63
+ precip_unit: mm|inch
64
+ """
65
  params = {
66
  "latitude": lat,
67
  "longitude": lon,
68
  "timezone": "auto",
69
  "hourly": ",".join(HOURLY_VARS),
70
  "daily": ",".join(DAILY_VARS),
71
+ "temperature_unit": temp_unit,
72
+ "windspeed_unit": wind_unit, # <— DÜZELTİLDİ: windspeed_unit
73
+ "precipitation_unit": precip_unit,
74
  }
75
+
76
  if mode == "Tahmin":
77
  params["forecast_days"] = int(forecast_days)
78
  url = FORECAST_BASE
 
87
 
88
  tz = data.get("timezone", "GMT")
89
 
90
+ # Saatlik DF
91
  hourly = data.get("hourly", {})
 
92
  df_h = None
93
+ if hourly.get("time"):
94
  df_h = pd.DataFrame(hourly)
95
  df_h["time"] = pd.to_datetime(df_h["time"])
96
  df_h = df_h.set_index("time").sort_index()
97
 
98
+ # Günlük DF
99
  daily = data.get("daily", {})
 
100
  df_d = None
101
+ if daily.get("time"):
102
  df_d = pd.DataFrame(daily)
103
  df_d["time"] = pd.to_datetime(df_d["time"])
104
+ # sunrise/sunset kolonlarını datetime'a çevir
105
+ for col in ("sunrise", "sunset"):
106
+ if col in df_d.columns:
107
+ df_d[col] = pd.to_datetime(df_d[col], errors="coerce")
108
  df_d = df_d.set_index("time").sort_index()
109
 
110
  return tz, df_h, df_d, data
111
 
112
+
113
+ def add_night_shading(fig, df_d, df_h):
114
+ """Gün doğumu/batımı kullanarak gece bölümlerini arkaya açık renk gölge olarak ekler."""
115
+ if df_d is None or df_h is None:
116
+ return fig
117
+ if not {"sunrise", "sunset"}.issubset(df_d.columns):
118
+ return fig
119
+ if df_h.empty or df_d.empty:
120
+ return fig
121
+
122
+ h_start = df_h.index.min()
123
+ h_end = df_h.index.max()
124
+ shapes = []
125
+
126
+ for _, row in df_d.iterrows():
127
+ sr, ss = row.get("sunrise"), row.get("sunset")
128
+ if pd.isna(sr) or pd.isna(ss):
129
+ continue
130
+ day_midnight = pd.Timestamp(sr.date())
131
+ next_midnight = day_midnight + pd.Timedelta(days=1)
132
+
133
+ # Gece 1: 00:00 -> Gün doğumu
134
+ left = max(day_midnight, h_start)
135
+ right = min(sr, h_end)
136
+ if left < right:
137
+ shapes.append(dict(
138
+ type="rect", xref="x", yref="paper",
139
+ x0=left, x1=right, y0=0, y1=1,
140
+ fillcolor="rgba(0,0,0,0.08)", line=dict(width=0), layer="below"
141
+ ))
142
+
143
+ # Gece 2: Gün batımı -> 24:00
144
+ left = max(ss, h_start)
145
+ right = min(next_midnight, h_end)
146
+ if left < right:
147
+ shapes.append(dict(
148
+ type="rect", xref="x", yref="paper",
149
+ x0=left, x1=right, y0=0, y1=1,
150
+ fillcolor="rgba(0,0,0,0.08)", line=dict(width=0), layer="below"
151
+ ))
152
+
153
+ if shapes:
154
+ fig.update_layout(shapes=shapes)
155
+ return fig
156
+
157
+
158
+ def fig_hourly(df_h, tz_name, *,
159
+ show_temp=True, show_precip=True, show_wind=True,
160
+ temp_label="°C", precip_label="mm", wind_label="km/sa",
161
+ smooth_window=1, df_d=None):
162
+ """Saatlik grafik: sıcaklık, yağış, rüzgâr. İzleri checkbox ile seçilebilir; lejand tıklamasıyla aç/kapa."""
163
  fig = make_subplots(specs=[[{"secondary_y": True}]])
164
+ dfp = df_h.copy()
165
+
166
+ # Yumuşatma (yalnız çizgi serilere)
167
+ if smooth_window and smooth_window > 1:
168
+ if "temperature_2m" in dfp.columns:
169
+ dfp["temperature_2m"] = dfp["temperature_2m"].rolling(smooth_window, min_periods=1).mean()
170
+ if "wind_speed_10m" in dfp.columns:
171
+ dfp["wind_speed_10m"] = dfp["wind_speed_10m"].rolling(smooth_window, min_periods=1).mean()
172
+
173
+ if show_temp and "temperature_2m" in dfp:
174
  fig.add_trace(go.Scatter(
175
+ x=dfp.index, y=dfp["temperature_2m"], mode="lines",
176
+ name=f"Sıcaklık ({temp_label})"
177
  ), secondary_y=False)
178
+
179
+ if show_precip and "precipitation" in dfp:
180
  fig.add_trace(go.Bar(
181
+ x=dfp.index, y=dfp["precipitation"], name=f"Yağış ({precip_label})", opacity=0.35
182
  ), secondary_y=True)
183
+
184
+ if show_wind and "wind_speed_10m" in dfp:
185
  fig.add_trace(go.Scatter(
186
+ x=dfp.index, y=dfp["wind_speed_10m"], mode="lines",
187
+ name=f"Rüzgâr ({wind_label})"
188
  ), secondary_y=True)
189
 
190
  fig.update_layout(
191
  title=f"Saatlik Seri ({tz_name})",
192
  margin=dict(l=10, r=10, t=40, b=10),
193
+ height=440,
194
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
195
  )
196
  fig.update_xaxes(title_text="Zaman")
197
+ fig.update_yaxes(title_text=f"Sıcaklık ({temp_label})", secondary_y=False)
198
+ fig.update_yaxes(title_text=f"Yağış ({precip_label}) / Rüzgâr ({wind_label})", secondary_y=True)
199
+
200
+ # Gece-gündüz gölgeleme
201
+ fig = add_night_shading(fig, df_d, dfp)
202
  return fig
203
 
204
+
205
+ def fig_daily(df_d, *, show_daily_precip=True, temp_label="°C", precip_label="mm"):
206
+ """Günlük grafik: min‑max sıcaklık bandı + (opsiyonel) toplam yağış."""
207
  fig = make_subplots(specs=[[{"secondary_y": True}]])
208
+ have_minmax = {"temperature_2m_min", "temperature_2m_max"}.issubset(df_d.columns)
209
+
210
+ if have_minmax:
211
  fig.add_trace(go.Scatter(
212
  x=df_d.index, y=df_d["temperature_2m_min"], mode="lines",
213
+ name=f"Günlük Min ({temp_label})"
214
  ), secondary_y=False)
215
  fig.add_trace(go.Scatter(
216
  x=df_d.index, y=df_d["temperature_2m_max"], mode="lines",
217
+ name=f"Günlük Max ({temp_label})", fill="tonexty"
218
  ), secondary_y=False)
219
+
220
+ if show_daily_precip and "precipitation_sum" in df_d:
221
  fig.add_trace(go.Bar(
222
+ x=df_d.index, y=df_d["precipitation_sum"],
223
+ name=f"Toplam Yağış ({precip_label})", opacity=0.35
224
  ), secondary_y=True)
225
 
226
  fig.update_layout(
227
  title="Günlük Özet (Min‑Max Bandı & Yağış)",
228
  margin=dict(l=10, r=10, t=40, b=10),
229
+ height=440,
230
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
231
  )
232
  fig.update_xaxes(title_text="Tarih")
233
+ fig.update_yaxes(title_text=f"Sıcaklık ({temp_label})", secondary_y=False)
234
+ fig.update_yaxes(title_text=f"Yağış ({precip_label})", secondary_y=True)
235
  return fig
236
 
237
+
238
  def csv_bytes(df: pd.DataFrame) -> bytes:
239
  return df.to_csv(index=True).encode("utf-8")
240
 
241
+
242
  # ------------------------------
243
  # Kenar çubuğu (ayarlar)
244
  # ------------------------------
245
  with st.sidebar:
246
  st.header("⚙️ Ayarlar")
247
+
248
  mode = st.radio("Veri tipi", ["Tahmin", "Geçmiş (Arşiv)"], horizontal=True)
249
 
250
  if mode == "Tahmin":
 
252
  else:
253
  today = date.today()
254
  default_start = today - timedelta(days=14)
255
+ default_end = today - timedelta(days=5) # arşiv birkaç gün gecikmeli olur
 
256
  start_date = st.date_input("Başlangıç tarihi", value=default_start, max_value=today)
257
  end_date = st.date_input("Bitiş tarihi", value=default_end, max_value=today)
258
  if start_date > end_date:
259
  st.error("Başlangıç tarihi, bitiş tarihinden büyük olamaz.")
260
 
261
+ st.markdown("---")
262
+ st.subheader("📊 Grafik katmanları")
263
+ show_temp = st.checkbox("Sıcaklık (saatlik)", value=True)
264
+ show_precip = st.checkbox("Yağış (saatlik)", value=True)
265
+ show_wind = st.checkbox("Rüzgâr hızı (saatlik)", value=True)
266
+ show_daily_precip = st.checkbox("Günlük toplam yağış", value=True)
267
+
268
+ st.markdown("---")
269
+ st.subheader("🧰 Görselleştirme seçenekleri")
270
+ smooth_window = st.slider("Saatlik çizgiler için yumuşatma (saat)", 1, 6, 1,
271
+ help="1 = yumuşatma yok. Sadece çizgi serilere uygulanır (sıcaklık, rüzgâr).")
272
+
273
+ st.markdown("---")
274
+ st.subheader("📏 Birimler")
275
+ colu1, colu2, colu3 = st.columns(3)
276
+ with colu1:
277
+ temp_choice = st.radio("Sıcaklık", ["°C", "°F"], horizontal=True, index=0)
278
+ with colu2:
279
+ wind_choice = st.radio("Rüzgâr", ["km/sa", "m/sn"], horizontal=True, index=0)
280
+ with colu3:
281
+ precip_choice = st.radio("Yağış", ["mm", "inç"], horizontal=True, index=0)
282
+
283
+ # UI -> API birim eşlemesi
284
+ temp_unit_api = "celsius" if temp_choice == "°C" else "fahrenheit"
285
+ wind_unit_api = "kmh" if wind_choice == "km/sa" else "ms"
286
+ precip_unit_api = "mm" if precip_choice == "mm" else "inch"
287
+ temp_label = "°C" if temp_unit_api == "celsius" else "°F"
288
+ wind_label = "km/sa" if wind_unit_api == "kmh" else "m/sn"
289
+ precip_label = "mm" if precip_unit_api == "mm" else "in"
290
+
291
  # ------------------------------
292
+ # Harita (konum seçimi) + Favoriler
293
  # ------------------------------
294
  st.subheader("🗺️ Konum seç")
295
  if "lat" not in st.session_state:
296
  st.session_state.lat = 41.0082 # İstanbul
297
  if "lon" not in st.session_state:
298
  st.session_state.lon = 28.9784
299
+ if "favorites" not in st.session_state:
300
+ st.session_state.favorites = [] # {"name": str, "lat": float, "lon": float}
301
 
302
  col_map, col_info = st.columns([2.2, 1.0], vertical_alignment="top")
303
 
 
305
  m = folium.Map(location=[st.session_state.lat, st.session_state.lon], zoom_start=6, control_scale=True)
306
  folium.Marker(location=[st.session_state.lat, st.session_state.lon],
307
  tooltip="Seçili konum").add_to(m)
 
308
  m.add_child(folium.LatLngPopup())
 
309
  map_state = st_folium(m, use_container_width=True, height=460, returned_objects=["last_clicked"])
310
 
311
  if map_state and map_state.get("last_clicked"):
 
316
  st.write("**Seçili koordinatlar**")
317
  st.metric("Enlem (lat)", st.session_state.lat)
318
  st.metric("Boylam (lon)", st.session_state.lon)
319
+
320
+ st.markdown("**⭐ Favoriler**")
321
+ fav_name = st.text_input("Bu konumu isimlendir", placeholder="ör. İstanbul-Ofis")
322
+ add_btn = st.button("Bu konumu favorilere ekle")
323
+ if add_btn and fav_name.strip():
324
+ st.session_state.favorites.append({"name": fav_name.strip(),
325
+ "lat": st.session_state.lat,
326
+ "lon": st.session_state.lon})
327
+ st.success(f"“{fav_name}” eklendi.")
328
+
329
+ if st.session_state.favorites:
330
+ names = [f["name"] for f in st.session_state.favorites]
331
+ sel = st.selectbox("Favori konumu yükle", names, index=None, placeholder="Seçin…")
332
+ if sel:
333
+ f = next(x for x in st.session_state.favorites if x["name"] == sel)
334
+ if st.button("Seçili favoriye git"):
335
+ st.session_state.lat = f["lat"]
336
+ st.session_state.lon = f["lon"]
337
 
338
  # ------------------------------
339
  # Veri çekme + görselleştirme
 
346
  mode="Tahmin",
347
  lat=st.session_state.lat,
348
  lon=st.session_state.lon,
349
+ forecast_days=forecast_days,
350
+ temp_unit=temp_unit_api,
351
+ wind_unit=wind_unit_api,
352
+ precip_unit=precip_unit_api
353
  )
354
  else:
355
  tz, df_h, df_d, raw = fetch_openmeteo(
 
357
  lat=st.session_state.lat,
358
  lon=st.session_state.lon,
359
  start=start_date.isoformat(),
360
+ end=end_date.isoformat(),
361
+ temp_unit=temp_unit_api,
362
+ wind_unit=wind_unit_api,
363
+ precip_unit=precip_unit_api
364
  )
365
 
366
+ cfg = {"displaylogo": False, "displayModeBar": True}
367
+
368
  if df_h is not None and not df_h.empty:
369
+ st.plotly_chart(
370
+ fig_hourly(
371
+ df_h, tz_name=tz,
372
+ show_temp=show_temp,
373
+ show_precip=show_precip,
374
+ show_wind=show_wind,
375
+ temp_label=temp_label,
376
+ precip_label=precip_label,
377
+ wind_label=wind_label,
378
+ smooth_window=smooth_window,
379
+ df_d=df_d
380
+ ),
381
+ use_container_width=True, config=cfg
382
+ )
383
+ st.download_button("Saatlik veriyi CSV indir", data=csv_bytes(df_h),
384
+ file_name="hourly.csv", mime="text/csv")
385
 
386
  if df_d is not None and not df_d.empty:
387
+ st.plotly_chart(
388
+ fig_daily(
389
+ df_d,
390
+ show_daily_precip=show_daily_precip,
391
+ temp_label=temp_label,
392
+ precip_label=precip_label
393
+ ),
394
+ use_container_width=True, config=cfg
395
+ )
396
+ st.download_button("Günlük veriyi CSV indir", data=csv_bytes(df_d),
397
+ file_name="daily.csv", mime="text/csv")
398
 
399
  if (df_h is None or df_h.empty) and (df_d is None or df_d.empty):
400
  st.info("Seçili aralık/konum için veri bulunamadı. Aralığı daraltmayı veya farklı konum seçmeyi deneyin.")