mzekcy commited on
Commit
56ac446
·
verified ·
1 Parent(s): fc6ce2e

Upload 2 files

Browse files
Files changed (2) hide show
  1. requirements.txt +12 -0
  2. tanzerultimate.py +478 -0
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pandas
2
+ numpy
3
+ requests
4
+ matplotlib
5
+ seaborn
6
+ tqdm
7
+ scikit-learn
8
+ xgboost
9
+ lightgbm
10
+ catboost
11
+ tensorflow
12
+ openpyxl
tanzerultimate.py ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import requests
4
+ import time
5
+ import os
6
+ import warnings
7
+ import sys
8
+ import re
9
+ import matplotlib.pyplot as plt
10
+ import matplotlib.dates as mdates
11
+ import seaborn as sns
12
+ from datetime import datetime, timedelta
13
+ from tqdm import tqdm
14
+
15
+ # --- GEREKLİ KÜTÜPHANELER ---
16
+ from sklearn.model_selection import train_test_split
17
+ from sklearn.preprocessing import StandardScaler
18
+ from sklearn.metrics import f1_score
19
+ from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, HistGradientBoostingClassifier, \
20
+ AdaBoostClassifier
21
+ from sklearn.neighbors import KNeighborsClassifier
22
+ from sklearn.linear_model import SGDClassifier
23
+ from sklearn.neural_network import MLPClassifier
24
+ from sklearn.naive_bayes import GaussianNB
25
+ import xgboost as xgb
26
+ import lightgbm as lgb
27
+ import catboost as cb
28
+
29
+ # --- AYARLAR ---
30
+ pd.set_option('display.max_columns', None)
31
+ pd.set_option('display.width', 1000)
32
+ warnings.filterwarnings("ignore")
33
+ requests.packages.urllib3.disable_warnings()
34
+
35
+ # --- AKADEMİK RENK PALETİ TANIMI ---
36
+ # Bilimsel yayınlara uygun, ciddi ve net ayrım sağlayan renkler
37
+ ACADEMIC_COLORS = {
38
+ 'dusuk': '#2E7D32', # Koyu Yeşil (Orman Yeşili)
39
+ 'orta': '#F9A825', # Koyu Hardal Sarısı
40
+ 'riskli': '#EF6C00', # Koyu Turuncu (Kiremit)
41
+ 'yuksek': '#C62828', # Koyu Bordo
42
+ 'tehlikeli': '#37474F' # Koyu Antrasit Mavi/Siyah
43
+ }
44
+
45
+ # DOSYA VE KLASÖR
46
+ MGM_DATA_FILE = "mgm.csv"
47
+ FAULT_FILE = "faults.csv"
48
+ MAIN_FOLDER = "ariza_grafikleri"
49
+ SUB_FOLDER = "TANZER_PROHIBRIT_RESULTS"
50
+ OUTPUT_DIR = os.path.join(MAIN_FOLDER, SUB_FOLDER)
51
+
52
+ if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR)
53
+ OUTPUT_EXCEL = os.path.join(OUTPUT_DIR, "PROHIBRIT_RISK_RAPORU.xlsx")
54
+ PLOT_BAR = os.path.join(OUTPUT_DIR, "PROHIBRIT_BAR_CHART.png")
55
+ PLOT_LINE = os.path.join(OUTPUT_DIR, "PROHIBRIT_LINE_CHART.png")
56
+
57
+ MGM_MAPPING = {
58
+ "Tarih": "Zaman",
59
+ "Sicaklik": "Sıcaklık",
60
+ "Nem": "Nispi Nem",
61
+ "Yagis": "Toplam Yağış OMGI mm",
62
+ "Ruzgar_Hizi": "Rüzgar Yönü ve Hızı",
63
+ "Basinc": "Deniz Seviyesine İndirgenmiş Basınç hPa"
64
+ }
65
+
66
+ print("========================================================================")
67
+ print(" TANZER PROHİBRİT MODEL (AKADEMİK SÜRÜM)")
68
+ print(" (11 Model Ensemble + Gelişmiş Bilimsel Görselleştirme)")
69
+ print("========================================================================\n")
70
+
71
+
72
+ # ---------------------------------------------------------
73
+ # 1. VERİ İŞLEME
74
+ # ---------------------------------------------------------
75
+ def clean_number(val):
76
+ if pd.isna(val) or val == "": return 0.0
77
+ try:
78
+ s = str(val).replace(',', '.').strip()
79
+ nums = re.findall(r"[-+]?\d*\.\d+|\d+", s)
80
+ if nums: return float(nums[0])
81
+ return 0.0
82
+ except:
83
+ return 0.0
84
+
85
+
86
+ def convert_excel_date(val):
87
+ if pd.isna(val) or val == "": return pd.NaT
88
+ try:
89
+ s = str(val).replace(',', '.').strip()
90
+ if re.match(r'^\d+(\.\d+)?$', s):
91
+ serial = float(s)
92
+ if 30000 < serial < 60000:
93
+ return pd.Timestamp('1899-12-30') + pd.to_timedelta(serial, unit='D')
94
+ except:
95
+ pass
96
+ try:
97
+ return pd.to_datetime(val, dayfirst=True)
98
+ except:
99
+ pass
100
+ try:
101
+ return pd.to_datetime(val)
102
+ except:
103
+ return pd.NaT
104
+
105
+
106
+ def clean_coord(val):
107
+ try:
108
+ s = str(val).replace(',', '.')
109
+ s = re.sub(r"[^0-9\.\-]", "", s)
110
+ f = float(s)
111
+ if -90 <= f <= 90: return f
112
+ except:
113
+ return None
114
+
115
+
116
+ def calculate_features(df):
117
+ if 'Tarih' in df.columns: df = df.set_index('Tarih')
118
+ if not isinstance(df.index, pd.DatetimeIndex): df.index = pd.to_datetime(df.index, dayfirst=True, errors='coerce')
119
+ df = df[df.index.notnull()].sort_index()
120
+
121
+ if 'Yagis' in df.columns:
122
+ df['Yagis_7G'] = df['Yagis'].rolling('7d').sum().fillna(0)
123
+ else:
124
+ df['Yagis_7G'] = 0
125
+
126
+ if 'Basinc' in df.columns:
127
+ df['Basinc_Trend'] = df['Basinc'].diff(24).fillna(0)
128
+ df['Basinc_Stabilite'] = df['Basinc'].rolling('3d').std().fillna(0)
129
+ else:
130
+ df['Basinc_Trend'] = 0
131
+ df['Basinc_Stabilite'] = 0
132
+
133
+ if 'Sicaklik' in df.columns:
134
+ df['Sicaklik_Soku'] = df['Sicaklik'].diff(6).abs().fillna(0)
135
+ df['Donma_Indeksi'] = (df['Sicaklik'] < 0).astype(int) * df['Yagis_7G']
136
+ else:
137
+ df['Sicaklik_Soku'] = 0
138
+ df['Donma_Indeksi'] = 0
139
+
140
+ if 'Ruzgar_Hizi' in df.columns:
141
+ df['Ruzgar_Enerjisi'] = df['Ruzgar_Hizi'] ** 2
142
+ df['Firtina_Gucu'] = df['Ruzgar_Enerjisi'] * (df['Yagis_7G'] + 1).apply(np.log)
143
+ else:
144
+ df['Ruzgar_Enerjisi'] = 0
145
+ df['Firtina_Gucu'] = 0
146
+
147
+ return df.dropna()
148
+
149
+
150
+ def get_risk_cat(score):
151
+ if score < 40:
152
+ return "Düşük Risk"
153
+ elif 40 <= score < 60:
154
+ return "Orta Risk"
155
+ elif 60 <= score < 70:
156
+ return "RİSKLİ"
157
+ elif 70 <= score < 80:
158
+ return "YÜKSEK RİSKLİ"
159
+ else:
160
+ return "TEHLİKELİ"
161
+
162
+
163
+ # ---------------------------------------------------------
164
+ # 2. TANZER PROHİBRİT MODEL (MEGA ENSEMBLE)
165
+ # ---------------------------------------------------------
166
+ class TanzerProhibitModel:
167
+ def __init__(self):
168
+ self.models = {}
169
+ self.weights = {}
170
+ self.model_performance = []
171
+ self.scaler = StandardScaler()
172
+
173
+ def train(self, X, y):
174
+ X_scaled = self.scaler.fit_transform(X)
175
+ X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42, stratify=y)
176
+
177
+ print("\n--- PROHİBRİT MODEL EĞİTİM SÜRECİ ---")
178
+
179
+ models_to_train = {
180
+ "RandomForest": RandomForestClassifier(n_estimators=150, max_depth=12, class_weight='balanced', n_jobs=-1,
181
+ random_state=42),
182
+ "ExtraTrees": ExtraTreesClassifier(n_estimators=150, max_depth=12, class_weight='balanced', n_jobs=-1,
183
+ random_state=42),
184
+ "XGBoost": xgb.XGBClassifier(n_estimators=150, max_depth=6, learning_rate=0.1, n_jobs=-1,
185
+ eval_metric='logloss'),
186
+ "LightGBM": lgb.LGBMClassifier(n_estimators=150, learning_rate=0.1, class_weight='balanced', verbose=-1,
187
+ n_jobs=-1),
188
+ "CatBoost": cb.CatBoostClassifier(iterations=150, depth=6, learning_rate=0.1, auto_class_weights='Balanced',
189
+ verbose=0, thread_count=-1),
190
+ "HistGradient": HistGradientBoostingClassifier(learning_rate=0.1, max_iter=150, random_state=42),
191
+ "AdaBoost": AdaBoostClassifier(n_estimators=100, random_state=42),
192
+ "KNN": KNeighborsClassifier(n_neighbors=5, weights='distance', algorithm='kd_tree', leaf_size=40,
193
+ n_jobs=-1),
194
+ "FastSVM": SGDClassifier(loss='hinge', penalty='l2', alpha=0.0001, max_iter=1000, class_weight='balanced',
195
+ n_jobs=-1, random_state=42),
196
+ "NeuralNet": MLPClassifier(hidden_layer_sizes=(100, 50), max_iter=300, activation='relu', solver='adam',
197
+ early_stopping=True, random_state=42),
198
+ "NaiveBayes": GaussianNB()
199
+ }
200
+
201
+ pbar = tqdm(models_to_train.items(), desc="Model Eğitimi", unit="model", ncols=100,
202
+ bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}")
203
+
204
+ for name, model in pbar:
205
+ try:
206
+ model.fit(X_train, y_train)
207
+ y_pred = model.predict(X_test)
208
+ score = f1_score(y_test, y_pred)
209
+
210
+ tqdm.write(f" 🔹 {name:<15}: {score:.4f} (F1 Score)")
211
+
212
+ status = "✅ Aktif" if score > 0.35 else "❌ Elendi"
213
+ self.model_performance.append({"Model": name, "F1 Score": score, "Durum": status})
214
+
215
+ if score > 0.35:
216
+ self.models[name] = model
217
+ self.weights[name] = score
218
+ except Exception as e:
219
+ tqdm.write(f" ❌ {name} Hatası: {e}")
220
+ self.model_performance.append({"Model": name, "F1 Score": 0.0, "Durum": "HATA"})
221
+
222
+ total = sum(self.weights.values())
223
+ if total > 0:
224
+ for k in self.weights: self.weights[k] /= total
225
+ else:
226
+ rf = RandomForestClassifier()
227
+ rf.fit(X_train, y_train)
228
+ self.models['RF'] = rf
229
+ self.weights['RF'] = 1.0
230
+
231
+ print("\n" + "=" * 50)
232
+ print(" 🏆 TANZER PROHİBRİT - PERFORMANS KARNESİ 🏆")
233
+ print("=" * 50)
234
+ df_perf = pd.DataFrame(self.model_performance).sort_values("F1 Score", ascending=False)
235
+ print(df_perf.to_string(index=False, formatters={'F1 Score': '{:.4f}'.format}))
236
+ print("=" * 50 + "\n")
237
+
238
+ def predict(self, df_features):
239
+ X = self.scaler.transform(df_features)
240
+ final_prob = np.zeros(len(X))
241
+ for name, model in self.models.items():
242
+ try:
243
+ if hasattr(model, "predict_proba"):
244
+ prob = model.predict_proba(X)[:, 1]
245
+ elif hasattr(model, "decision_function"):
246
+ d = model.decision_function(X)
247
+ prob = 1 / (1 + np.exp(-d))
248
+ else:
249
+ prob = model.predict(X)
250
+ final_prob += prob * self.weights[name]
251
+ except:
252
+ pass
253
+ return final_prob * 100
254
+
255
+
256
+ # ---------------------------------------------------------
257
+ # 3. ANA AKIŞ
258
+ # ---------------------------------------------------------
259
+ def main():
260
+ print("⏳ Veri Tabanı Yükleniyor...")
261
+ if not os.path.exists(MGM_DATA_FILE): return
262
+ try:
263
+ use_cols = list(MGM_MAPPING.values())
264
+ df_mgm = pd.read_csv(MGM_DATA_FILE, sep=None, engine='python', encoding='utf-8-sig',
265
+ usecols=lambda c: c.strip() in use_cols)
266
+ df_mgm.columns = df_mgm.columns.str.strip()
267
+ clean_df = pd.DataFrame()
268
+ target_date_col = MGM_MAPPING["Tarih"]
269
+ if target_date_col in df_mgm.columns:
270
+ clean_df["Tarih"] = pd.to_datetime(df_mgm[target_date_col], dayfirst=True, errors='coerce')
271
+ for kod, dosya in MGM_MAPPING.items():
272
+ if kod == "Tarih": continue
273
+ if dosya in df_mgm.columns:
274
+ clean_df[kod] = pd.to_numeric(df_mgm[dosya].astype(str).str.replace(',', '.'), errors='coerce').fillna(
275
+ 0)
276
+ else:
277
+ clean_df[kod] = 0.0
278
+ clean_df = clean_df.dropna(subset=['Tarih']).sort_values('Tarih').reset_index(drop=True)
279
+ except:
280
+ return
281
+
282
+ if not os.path.exists(FAULT_FILE): return
283
+ try:
284
+ df_fault = pd.read_csv(FAULT_FILE, sep=None, engine='python')
285
+ target_col = [c for c in df_fault.columns if 'tarih' in c.lower() or 'date' in c.lower()][0]
286
+ df_fault['Tarih'] = df_fault[target_col].apply(convert_excel_date)
287
+ if 'Enlem' in df_fault.columns:
288
+ df_fault['Enlem'] = df_fault['Enlem'].apply(clean_coord)
289
+ df_fault['Boylam'] = df_fault['Boylam'].apply(clean_coord)
290
+ df_fault = df_fault.dropna(subset=['Tarih'])
291
+ except:
292
+ return
293
+
294
+ print("⏳ Veri Seti İşleniyor (Pencere: -7 / +5 Saat)...")
295
+ full_data = calculate_features(clean_df)
296
+ full_data['Ariza_Durumu'] = 0
297
+
298
+ for f_date in df_fault['Tarih']:
299
+ try:
300
+ start_risk = f_date - timedelta(days=7)
301
+ end_risk = f_date + timedelta(hours=5)
302
+ if start_risk < full_data.index.max() and end_risk > full_data.index.min():
303
+ full_data.loc[start_risk:end_risk, 'Ariza_Durumu'] = 1
304
+ except:
305
+ continue
306
+
307
+ pos = full_data[full_data['Ariza_Durumu'] == 1]
308
+ neg_pool = full_data[full_data['Ariza_Durumu'] == 0]
309
+ n_neg = min(len(pos) * 5, len(neg_pool))
310
+
311
+ if n_neg > 0:
312
+ neg = neg_pool.sample(n=n_neg, random_state=42)
313
+ train_set = pd.concat([pos, neg])
314
+ else:
315
+ return
316
+
317
+ features = ['Sicaklik', 'Nem', 'Yagis', 'Ruzgar_Hizi', 'Basinc',
318
+ 'Yagis_7G', 'Basinc_Trend', 'Basinc_Stabilite',
319
+ 'Sicaklik_Soku', 'Donma_Indeksi', 'Ruzgar_Enerjisi', 'Firtina_Gucu']
320
+
321
+ # EĞİTİM
322
+ print("🚀 TANZER PROHİBRİT MODEL EĞİTİLİYOR...")
323
+ ensemble = TanzerProhibitModel()
324
+ ensemble.train(train_set[features], train_set['Ariza_Durumu'])
325
+ print("✅ Eğitim Tamamlandı.")
326
+
327
+ # TAHMİN
328
+ print("\n⏳ 14 Hat İçin Analiz Başlıyor (Lütfen Bekleyiniz)...")
329
+ possible_names = ["Hat_Adı_2", "Hat Adı", "Hat_Adi", "HAT_ADI", "HAT ADI"]
330
+ hat_col = next((c for c in df_fault.columns if c in possible_names), df_fault.columns[0])
331
+ unique_lines = df_fault[[hat_col, 'Enlem', 'Boylam']].drop_duplicates(subset=[hat_col]).dropna().head(14)
332
+
333
+ results = []
334
+ line_data = []
335
+ session = requests.Session()
336
+
337
+ pbar = tqdm(unique_lines.iterrows(), total=len(unique_lines), unit="hat",
338
+ bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]")
339
+
340
+ for _, row in pbar:
341
+ hat_adi = row[hat_col]
342
+ lat, lon = row['Enlem'], row['Boylam']
343
+ pbar.set_description(f"Analiz: {str(hat_adi)[:20]}")
344
+
345
+ try:
346
+ url = "https://api.open-meteo.com/v1/forecast"
347
+ params = {
348
+ "latitude": lat, "longitude": lon,
349
+ "hourly": "temperature_2m,relative_humidity_2m,rain,wind_speed_10m,surface_pressure",
350
+ "past_days": 7, "forecast_days": 3, "timezone": "auto"
351
+ }
352
+ r = session.get(url, params=params, timeout=10, verify=False)
353
+ if r.status_code == 200:
354
+ data = r.json()
355
+ df_api = pd.DataFrame(data['hourly'])
356
+ df_api['time'] = pd.to_datetime(df_api['time'])
357
+ df_api = df_api.rename(
358
+ columns={'time': 'Tarih', 'temperature_2m': 'Sicaklik', 'relative_humidity_2m': 'Nem',
359
+ 'rain': 'Yagis', 'wind_speed_10m': 'Ruzgar_Hizi', 'surface_pressure': 'Basinc'})
360
+
361
+ df_proc = calculate_features(df_api)
362
+ df_proc.index = df_proc.index.tz_localize(None)
363
+ now = datetime.now()
364
+ future_df = df_proc[df_proc.index >= now].copy()
365
+
366
+ if not future_df.empty:
367
+ for c in features:
368
+ if c not in future_df.columns: future_df[c] = 0
369
+
370
+ risk = ensemble.predict(future_df[features])
371
+ future_df['Risk'] = risk
372
+
373
+ max_idx = future_df['Risk'].idxmax()
374
+ max_risk = future_df.loc[max_idx, 'Risk']
375
+ cat = get_risk_cat(max_risk)
376
+
377
+ tqdm.write(f"✅ {str(hat_adi)[:30]:<30} : %{max_risk:.1f} ({cat})")
378
+
379
+ results.append({"Hat": hat_adi, "Risk (%)": max_risk, "Kategori": cat, "Zaman": max_idx})
380
+ p_data = future_df[['Risk']].reset_index()
381
+ p_data['Hat'] = hat_adi
382
+ line_data.append(p_data)
383
+ else:
384
+ tqdm.write(f"❌ {str(hat_adi)[:30]} : API Hatası")
385
+ except:
386
+ tqdm.write(f"❌ {str(hat_adi)[:30]} : Bağlantı Hatası")
387
+
388
+ # --- AKADEMİK RAPORLAMA VE GÖRSELLEŞTİRME ---
389
+ if results:
390
+ df_res = pd.DataFrame(results).sort_values("Risk (%)", ascending=False)
391
+ df_res.to_excel(OUTPUT_EXCEL, index=False)
392
+
393
+ # Seaborn Akademik Tema Ayarı
394
+ sns.set_theme(style="whitegrid", font_scale=1.1, rc={"grid.linewidth": 0.6, "axes.linewidth": 1})
395
+
396
+ # --- GRAFİK 1: ÇUBUK (BAR) GRAFİĞİ ---
397
+ plt.figure(figsize=(14, 10))
398
+
399
+ # Renkleri skora göre akademik paletten seç
400
+ colors = [ACADEMIC_COLORS['dusuk'] if x < 40 else
401
+ ACADEMIC_COLORS['orta'] if x < 60 else
402
+ ACADEMIC_COLORS['riskli'] if x < 70 else
403
+ ACADEMIC_COLORS['yuksek'] if x < 80 else
404
+ ACADEMIC_COLORS['tehlikeli'] for x in df_res['Risk (%)']]
405
+
406
+ ax = sns.barplot(x='Risk (%)', y='Hat', data=df_res, palette=colors, edgecolor='.2', linewidth=0.8)
407
+
408
+ # Kritik Eşik Çizgisi (Daha belirgin)
409
+ plt.axvline(75, color=ACADEMIC_COLORS['yuksek'], linestyle='--', linewidth=2.5, label='Kritik Risk Eşiği (%75)')
410
+
411
+ # Değerleri çubukların ucuna yaz
412
+ for i, v in enumerate(df_res['Risk (%)']):
413
+ ax.text(v + 0.5, i, f"%{v:.1f}", fontweight='bold', va='center', fontsize=12, color='black')
414
+
415
+ plt.title('Enerji İletim Hatlarında Maksimum Risk Analizi\n(TANZER PROHİBRİT MODEL SONUÇLARI)',
416
+ fontweight='bold', fontsize=16, pad=20)
417
+ plt.xlabel('Hesaplanan Risk Skoru (%)', fontweight='bold', fontsize=12)
418
+ plt.ylabel('Hat Adı', fontweight='bold', fontsize=12)
419
+ plt.legend(loc='lower right', frameon=True)
420
+ plt.tight_layout()
421
+ plt.savefig(PLOT_BAR, dpi=300) # Yüksek çözünürlük
422
+
423
+ # --- GRAFİK 2: ÇİZGİ (LINE) GRAFİĞİ ---
424
+ if line_data:
425
+ all_lines = pd.concat(line_data)
426
+ plt.figure(figsize=(16, 10))
427
+
428
+ # Ana Çizgiler (Daha kalın ve profesyonel palet)
429
+ sns.lineplot(data=all_lines, x='Tarih', y='Risk', hue='Hat', palette='tab10', linewidth=3, alpha=0.9)
430
+
431
+ # Maksimum Noktaları İşaretle
432
+ for hat_name in df_res['Hat']:
433
+ hat_df = all_lines[all_lines['Hat'] == hat_name]
434
+ if not hat_df.empty:
435
+ max_row = hat_df.loc[hat_df['Risk'].idxmax()]
436
+ # Kırmızı nokta ve belirgin beyaz çerçeve
437
+ plt.scatter(max_row['Tarih'], max_row['Risk'], color=ACADEMIC_COLORS['yuksek'], s=120, zorder=5,
438
+ edgecolor='white', linewidth=2)
439
+ # Etiket kutusu
440
+ plt.annotate(f"%{max_row['Risk']:.0f}",
441
+ (max_row['Tarih'], max_row['Risk']),
442
+ textcoords="offset points", xytext=(0, 12), ha='center',
443
+ bbox=dict(boxstyle="round,pad=0.4", fc="white", ec=ACADEMIC_COLORS['yuksek'], lw=1.5),
444
+ fontsize=10, fontweight='bold')
445
+
446
+ # --- ARKA PLAN RİSK BÖLGELERİ (ÇOK DAHA BELİRGİN) ---
447
+ # Alpha değerleri 0.08'den 0.20-0.25 seviyesine çıkarıldı
448
+ plt.axhspan(0, 40, color=ACADEMIC_COLORS['dusuk'], alpha=0.20, label='Düşük Risk Bölgesi')
449
+ plt.axhspan(40, 60, color=ACADEMIC_COLORS['orta'], alpha=0.20, label='Orta Risk Bölgesi')
450
+ plt.axhspan(60, 70, color=ACADEMIC_COLORS['riskli'], alpha=0.25, label='Riskli Bölge')
451
+ plt.axhspan(70, 80, color=ACADEMIC_COLORS['yuksek'], alpha=0.25, label='Yüksek Risk Bölgesi')
452
+ plt.axhspan(80, 105, color=ACADEMIC_COLORS['tehlikeli'], alpha=0.30, label='Tehlikeli Bölge')
453
+
454
+ plt.ylim(0, 105)
455
+ plt.title('72 Saatlik Detaylı Zamansal Risk Değişimi', fontweight='bold', fontsize=18, pad=20)
456
+ plt.xlabel('Zaman (Gün/Saat)', fontweight='bold', fontsize=12)
457
+ plt.ylabel('Risk Yüzdesi (%)', fontweight='bold', fontsize=12)
458
+
459
+ # Tarih formatını iyileştir
460
+ plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d-%m %H:%M'))
461
+ plt.xticks(rotation=45)
462
+
463
+ # Lejantı dışarı al ve düzenle
464
+ plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left', title="Hatlar ve Risk Bölgeleri",
465
+ frameon=True, shadow=True, title_fontsize='12', fontsize='11')
466
+
467
+ plt.grid(True, linestyle='-', linewidth=0.8, alpha=0.7) # Gridleri belirginleştir
468
+ plt.tight_layout()
469
+ plt.savefig(PLOT_LINE, dpi=300) # Yüksek çözünürlük
470
+
471
+ print(f"\n✅ BAŞARILI! Akademik Rapor ve Yüksek Çözünürlüklü Grafikler Oluşturuldu.")
472
+ print(f" Çıktı Klasörü: {OUTPUT_DIR}")
473
+ else:
474
+ print("\n❌ Sonuç üretilemedi.")
475
+
476
+
477
+ if __name__ == "__main__":
478
+ main()