wraty commited on
Commit
a9739be
·
verified ·
1 Parent(s): 65bb1e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +414 -362
app.py CHANGED
@@ -1,8 +1,9 @@
1
  # =============================================================================
2
- # TANI KOYAI - V1.76.0 (MATH FIX)
3
- # - HATA DÜZELTMESİ: Matematik formülleri HTML Entity'ye çevrildi (Render garantisi).
4
- # - GÖRÜNÜM: "Kod" gibi görünen yazı tipi "Times New Roman" (Matematiksel) yapıldı.
5
- # - Önceki tüm özellikler (Mavi butonlar, Uyarılar, Zeka) aynen korundu.
 
6
  # =============================================================================
7
 
8
  import streamlit as st
@@ -18,145 +19,173 @@ import time
18
  from datetime import datetime
19
  import difflib
20
 
 
 
 
 
 
 
 
 
21
  warnings.filterwarnings("ignore")
22
 
23
  # PROJE VERSİYON NUMARASI
24
- PROJECT_VERSION = "V1.76.0 (Math Fix)"
25
 
26
  # DOSYA İSİMLERİ
27
  COMPLAINTS_FILE = 'feedback_detailed.json'
28
  DATA_FILE = 'hastalik_verisi.json'
29
 
 
 
 
 
 
 
 
 
 
 
30
  # --- CSS STİL TANIMLAMASI ---
31
  STYLE_CONFIG = f"""
32
  <style>
33
- /* 1. GENEL AYARLAR */
34
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;800;900&display=swap');
35
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap');
36
 
37
- html, body, [class*="css"] {{
38
- font-family: 'Poppins', sans-serif !important;
39
- color: #FFFFFF !important;
 
 
 
 
 
40
  }}
41
 
42
  /* 2. ARKA PLAN */
43
  .stApp {{
44
- background-color: #0F151C;
45
- background-image: radial-gradient(at 50% 50%, #1a2634 0%, #0F151C 100%);
46
  background-attachment: fixed;
47
  }}
48
 
49
- /* --- SIDEBAR RENKLERİ --- */
50
- [data-testid="stSidebar"] h1, [data-testid="stSidebar"] h2, [data-testid="stSidebar"] h3 {{
51
- color: #00BFFF !important;
52
- font-family: 'Orbitron', sans-serif !important;
53
- text-shadow: 0 0 10px rgba(0, 191, 255, 0.4);
54
- font-weight: 800 !important;
55
  }}
56
- [data-testid="stSidebar"] label {{ color: #00BFFF !important; font-weight: bold !important; }}
57
- [data-testid="stExpander"] summary p {{ color: #00BFFF !important; font-weight: 900 !important; }}
58
- [data-testid="stExpander"] summary svg {{ fill: #00BFFF !important; }}
59
-
60
- /* --- KUTUCUKLAR (SELECTBOX) --- */
61
- .stSelectbox div[data-baseweb="select"] > div {{
62
- background-color: #1E2A35 !important;
63
- color: #FFFFFF !important;
64
- border: 2px solid #00BFFF !important;
65
- border-radius: 8px !important;
66
- font-weight: 600 !important;
67
  }}
68
- .stSelectbox svg {{ fill: #00BFFF !important; }}
69
- div[data-baseweb="popover"] {{ background-color: #1E2A35 !important; border: 1px solid #00BFFF !important; }}
70
- div[data-baseweb="option"] {{ color: #FFFFFF !important; }}
71
- div[data-baseweb="option"]:hover, div[aria-selected="true"] {{
72
- background-color: #00BFFF !important; color: #000000 !important;
 
 
 
 
 
 
 
73
  }}
74
 
75
- /* --- BUTONLAR --- */
76
- .stButton>button, .stFormSubmitButton>button {{
77
- background: linear-gradient(90deg, #00BFFF, #1E90FF) !important;
78
- color: #FFFFFF !important; font-weight: 800 !important; border: none; border-radius: 6px;
79
- box-shadow: 0 4px 15px rgba(0, 191, 255, 0.3); transition: transform 0.2s;
80
- }}
81
- .stButton>button:hover {{ transform: scale(1.02); box-shadow: 0 0 20px rgba(0, 191, 255, 0.6); }}
82
 
83
- /* --- DİĞER ARAYÜZ --- */
84
- .stNumberInput label, .stTextInput label, .stSelectbox label, .stTextArea label {{
85
- color: #00BFFF !important; font-weight: 800 !important; text-transform: uppercase;
86
- text-shadow: 0 0 5px rgba(0, 191, 255, 0.3); font-size: 1rem !important;
87
  }}
88
 
89
- .dna-container {{
90
- padding: 25px; background: rgba(20, 30, 40, 0.95);
91
- border: 2px solid #00E0C6; border-radius: 20px; text-align: center;
92
- box-shadow: 0 0 30px rgba(0, 224, 198, 0.2); margin-bottom: 25px;
 
 
 
 
 
 
 
 
 
 
 
 
93
  }}
94
- .title-text {{
95
- font-family: 'Orbitron', sans-serif; font-size: 2.8em;
96
- background: linear-gradient(90deg, #FFFFFF, #00E0C6, #FFFFFF);
97
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
98
- text-shadow: 0 0 20px rgba(0, 224, 198, 0.4); animation: shine 4s linear infinite;
99
  }}
100
- @keyframes shine {{ to {{ background-position: 200% center; }} }}
101
 
102
- .mini-dna-title {{
103
- font-family: 'Poppins', sans-serif; font-weight: 800; font-size: 1.6rem;
104
- color: #00BFFF !important; text-shadow: 0 0 15px rgba(0, 191, 255, 0.5);
105
- margin-bottom: 20px; display: inline-block;
 
 
 
 
 
106
  }}
107
-
108
- .fire-title {{
109
- font-family: 'Poppins', sans-serif; font-weight: 900; font-size: 2rem;
110
- background: linear-gradient(to right, #00AEEF, #00E0C6);
111
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
112
- margin-top: 30px; margin-bottom: 10px; display: inline-block;
113
  }}
114
 
115
- .stTextInput input, .stTextArea textarea, .stNumberInput input {{
116
- background-color: #1E2A35 !important; color: #FFFFFF !important; border: 1px solid #34495E !important;
 
 
 
 
 
117
  }}
118
-
119
- [data-testid="stSidebar"] {{ background-color: #121B24; border-right: 1px solid #2C3E50; }}
120
- [data-testid="stSidebar"] p, [data-testid="stSidebar"] span {{ color: #FFFFFF !important; }}
121
-
122
- /* MODAL (BEYAZ KUTU) */
123
- .math-box {{ background-color: #FFFFFF !important; padding: 25px; color: #000 !important; border-radius: 12px; font-family: 'Poppins', sans-serif; }}
124
- .math-box h4 {{ color: #000 !important; font-weight: 800; margin-top: 20px; margin-bottom: 5px; }}
125
-
126
- /* MATEMATİKSEL FORMÜL KUTUSU (GÜNCELLENDİ: TIMES NEW ROMAN) */
127
- .fraction {{ display: inline-block; vertical-align: middle; text-align: center; font-size: 0.9em; }}
128
- .numerator {{ border-bottom: 1px solid #000; display: block; padding: 0 2px; }}
129
- .denominator {{ display: block; padding: 0 2px; }}
130
- .math-formula-box {{
131
- background: #f8f8f8;
132
- padding: 15px;
133
- border-left: 5px solid #000;
134
- margin: 10px 0;
135
- color: #000;
136
- text-align: center;
137
- font-family: 'Times New Roman', Times, serif; /* Matematiksel Yazı Tipi */
138
- font-size: 1.3rem; /* Biraz daha büyük */
139
- font-style: italic;
140
- font-weight: bold;
141
  }}
142
 
143
- /* YASAL UYARI KUTUSU */
144
- .legal-warning {{
145
- border: 2px solid #FF8C00;
146
- background-color: rgba(255, 140, 0, 0.15);
147
- color: #FFD700;
148
- padding: 15px;
149
- border-radius: 10px;
150
- font-size: 0.95rem;
151
- text-align: center;
152
- margin-top: 20px;
153
- font-weight: bold;
154
- box-shadow: 0 0 15px rgba(255, 140, 0, 0.2);
155
  }}
156
-
157
- .version-tag {{ position: fixed; top: 60px; right: 20px; background: rgba(0,0,0,0.8); border: 1px solid #00E0C6; color: #00E0C6 !important; padding: 4px 8px; font-size: 10px; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  </style>
159
- <div class="version-tag">SÜRÜM: {PROJECT_VERSION}</div>
160
  """
161
 
162
  # --- YARDIMCI FONKSİYONLAR ---
@@ -164,51 +193,49 @@ def load_complaints():
164
  if os.path.exists(COMPLAINTS_FILE):
165
  try:
166
  with open(COMPLAINTS_FILE, 'r', encoding='utf-8') as f:
167
- content = f.read()
168
- return json.loads(content) if content else []
169
  except: return []
170
  return []
171
 
172
- def save_complaints(complaints_list):
173
  try:
174
  with open(COMPLAINTS_FILE, 'w', encoding='utf-8') as f:
175
- json.dump(complaints_list, f, indent=4, ensure_ascii=False)
176
  except: pass
177
 
178
- # --- SESSION STATE ---
179
- if 'user_complaints' not in st.session_state: st.session_state['user_complaints'] = load_complaints()
180
- if 'user_profile' not in st.session_state: st.session_state['user_profile'] = {"cinsiyet": "E", "yas": 30, "bmi": 25.0, "boy": 175, "kilo": 75, "cinsiyet_str": "Erkek"}
181
- if 'secilen_hastalik' not in st.session_state: st.session_state['secilen_hastalik'] = None
182
- if 'analiz_durumu' not in st.session_state: st.session_state['analiz_durumu'] = None
183
- if 'top_results' not in st.session_state: st.session_state['top_results'] = []
184
- if 'sikayet_metni' not in st.session_state: st.session_state['sikayet_metni'] = ""
 
 
 
185
 
186
  # --- UI AYARLARI ---
187
- st.set_page_config(page_title="Tanı KoyAI", layout="wide", page_icon="🧬")
188
  st.markdown(STYLE_CONFIG, unsafe_allow_html=True)
189
 
190
- # --- MODEL VE VERİ YÜKLEME ---
 
 
 
 
 
 
 
 
 
 
 
 
191
  SEVK_TABLOSU = {
192
- "Nöroloji": "Nörolog veya Aile Hekimi'ne başvurmanız önerilir.",
193
- "KBB": "Kulak Burun Boğaz (KBB) uzmanına görünmelisiniz.",
194
- "Göz": "ACİL GÖZ DOKTORU GEREKLİDİR.",
195
- "Diş": "Diş Hekimi'ne randevu almanız tavsiye edilir.",
196
- "Kardiyoloji": "ACİL 112 VEYA EN YAKIN HASTANE!",
197
- "Psikoloji": "Bir Psikolog/Psikiyatr ile görüşmeniz tavsiye edilir.",
198
- "Dahiliye": "Dahiliye (İç Hastalıkları) uzmanına başvurmanız gerekir.",
199
- "Acil": "ACİL HASTANE GEREKLİDİR.",
200
- "Üroloji": "Üroloji uzmanına danışmanız önerilir.",
201
- "Cildiye": "Cilt Hastalıkları (Dermatoloji) uzmanına danışın.",
202
- "Ortopedi": "Ortopedi ve Travmatoloji uzmanına görünmelisiniz.",
203
- "Gastroenteroloji": "Gastroenteroloji veya Dahiliye uzmanına başvurunuz.",
204
- "Endokrinoloji": "Endokrinoloji uzmanına başvurunuz.",
205
- "Romatoloji": "Romatoloji veya Fizik Tedavi uzmanına başvurunuz.",
206
- "Kadın Doğum": "Kadın Hastalıkları ve Doğum uzmanına başvurunuz.",
207
- "Göğüs Hastalıkları": "Göğüs Hastalıkları uzmanına başvurunuz.",
208
- "Travmatoloji": "ACİL HASTANE GEREKLİDİR.",
209
- "Enfeksiyon": "Enfeksiyon Hastalıkları uzmanına görünün.",
210
- "Genel Cerrahi": "Genel Cerrahi uzmanına görünün.",
211
- "Hematoloji": "Hematoloji uzmanına görünün."
212
  }
213
 
214
  @st.cache_resource
@@ -216,24 +243,20 @@ def load_model_and_data():
216
  model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-mpnet-base-v2')
217
  TIBBI_SOZLUK = ["ağrı"]
218
  vocab_embeddings = model.encode(TIBBI_SOZLUK, convert_to_tensor=True)
219
-
220
  VERI_BANKASI = []
221
  if os.path.exists(DATA_FILE):
222
  try:
223
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
224
  VERI_BANKASI = json.load(f)
225
- except Exception as e: st.error(f"Veri hatası: {e}")
226
- else: st.error(f"{DATA_FILE} bulunamadı!")
227
-
228
  for item in VERI_BANKASI:
229
  if 'cinsiyet_kist' not in item: item['cinsiyet_kist'] = 'genel'
230
- if 'tedavi' not in item: item['tedavi'] = "Uzman kontrolü gerekir."
231
- if 'aciklama' not in item: item['aciklama'] = "Tıbbi değerlendirme şarttır."
232
 
233
  if VERI_BANKASI:
234
  db_embeddings = model.encode([item["anahtarlar"] for item in VERI_BANKASI], convert_to_tensor=True)
235
  else: db_embeddings = None
236
-
237
  return model, VERI_BANKASI, TIBBI_SOZLUK, vocab_embeddings, db_embeddings
238
 
239
  model, VERI_BANKASI, TIBBI_SOZLUK, vocab_embeddings, db_embeddings = load_model_and_data()
@@ -243,50 +266,43 @@ def kelime_match(sikayet, db_item):
243
  puan = 0.0
244
  sikayet_lower = sikayet.lower()
245
  db_anahtarlar = db_item["anahtarlar"].lower()
246
-
247
- kritik_organlar = ["kalp", "göğüs", "nefes", "damar"]
248
- for k in kritik_organlar:
249
- if k in sikayet_lower and k in db_anahtarlar: puan += 80.0
250
-
251
- baglam_kelimeleri = ["sabah", "gece", "akşam", "yemek", "yürürken", "spor", "uyku", "ani", "savaş", "stres", "yatak", "basarken"]
252
- for k in baglam_kelimeleri:
253
- if k in sikayet_lower:
254
- if k in db_anahtarlar: puan += 40.0
255
- else: puan -= 10.0
256
-
257
  words = sikayet_lower.split()
258
- db_words_list = db_anahtarlar.split()
259
  for w in words:
260
  if len(w) < 3: continue
261
- yakin_eslesmeler = difflib.get_close_matches(w, db_words_list, n=1, cutoff=0.75)
262
- if yakin_eslesmeler or w in db_anahtarlar: puan += 15.0
263
-
264
  return puan
265
 
266
  def evrensel_filtre(sikayet, db_item):
267
  text = sikayet.lower()
268
  db_bolge = db_item['bolge']
 
269
 
270
- if "kalp" in text or "sıkış" in text:
271
- if db_item['kat'] == "Kardiyoloji": return 100.0
272
-
273
- anatomi_puan = 0.0
274
- bolge_sozlugu = {
275
- "ust_uzuv": ["kol", "omuz", "dirsek", "el", "bilek", "parmak"],
276
- "alt_uzuv": ["bacak", "diz", "ayak", "kalça", "topuk", "bilek", "uyluk"],
277
- "bas": ["baş", "kafa", "kulak", "burun", "yüz", "göz", "alın", "çene", "diş", "beyin"],
278
- "gogus": ["göğüs", "kalp", "nefes", "akciğer", "kaburga", "sırt"],
279
- "karin": ["karın", "mide", "bağırsak", "göbek", "böbrek", "yan", "bel"],
280
- "genital": ["kasık", "idrar", "makat", "vajina", "testis", "adet"]
 
 
 
 
 
281
  }
282
- bulunan_bolgeler = []
283
- for b, kelimeler in bolge_sozlugu.items():
284
- if any(k in text for k in kelimeler): bulunan_bolgeler.append(b)
285
- if len(bulunan_bolgeler) > 0:
286
- if db_bolge == "genel": anatomi_puan += 5.0
287
- elif db_bolge in bulunan_bolgeler: anatomi_puan += 40.0
288
- else: return -500.0
289
- return anatomi_puan
290
 
291
  def anamnez_filtre(user_profile, db_item):
292
  if db_item['cinsiyet_kist'] == 'K' and user_profile['cinsiyet'] == 'E': return -5000.0
@@ -294,226 +310,262 @@ def anamnez_filtre(user_profile, db_item):
294
  return 0.0
295
 
296
  def teshis_motoru(sikayet, user_profile):
297
- if not VERI_BANKASI: return "Veri Yok", []
298
- sikayet_lower = sikayet.lower()
299
- if not sikayet_lower.strip(): return "Gecersiz", []
300
  input_vec = model.encode(sikayet, convert_to_tensor=True)
301
  sonuclar = []
302
  for i, item in enumerate(VERI_BANKASI):
303
- cos_sim = util.cos_sim(input_vec, db_embeddings[i])[0].item() * 20
304
- anat_puan = evrensel_filtre(sikayet, item)
305
- if anat_puan < -400: continue
306
- anamnez_puan = anamnez_filtre(user_profile, item)
307
- if anamnez_puan < -400: continue
308
- match_puan = kelime_match(sikayet, item)
309
- total_score = cos_sim + anat_puan + anamnez_puan + match_puan
310
- prob = max(0, min(99.0, total_score))
311
  if prob > 20: sonuclar.append({**item, "olasilik": prob})
312
  sonuclar.sort(key=lambda x: x["olasilik"], reverse=True)
313
- return ("Basarili", sonuclar[:5]) if sonuclar else ("Düşük Güven", [])
314
 
315
  # --- GRAFİKLER ---
316
- C_BG = '#16202A'
317
- C_TEXT = '#FFFFFF'
318
 
319
  def ciz_genel_bakis(top_results, sikayet):
320
- fig = make_subplots(rows=1, cols=2, specs=[[{"type": "xy"}, {"type": "scene"}]], subplot_titles=("Olasılık Dağılımı", "Semantik Uzay"))
321
- fig.add_trace(go.Bar(x=[r['tani'] for r in top_results], y=[r['olasilik'] for r in top_results], marker_color=[r['renk'] for r in top_results], text=[f"%{r['olasilik']:.1f}" for r in top_results], textposition='outside', textfont=dict(color=C_TEXT)), row=1, col=1)
322
- fig.add_trace(go.Scatter3d(x=[0], y=[0], z=[0], mode='markers', marker=dict(size=10, color=C_TEXT, symbol='diamond'), name="Siz", showlegend=False), row=1, col=2)
323
  for r in top_results:
324
- dist = (105 - r['olasilik']) / 10
325
- x, y, z = dist * np.cos(random.uniform(0,6)), dist * np.sin(random.uniform(0,6)), dist * random.uniform(-0.5,0.5)
326
- fig.add_trace(go.Scatter3d(x=[x], y=[y], z=[z], mode='markers+text', text=[r['tani']], textfont=dict(color=C_TEXT), marker=dict(size=18, color=r['renk']), showlegend=False), row=1, col=2)
327
- fig.update_layout(title_text=f"Analiz: '{sikayet}'", template="plotly_dark", paper_bgcolor=C_BG, plot_bgcolor=C_BG, font=dict(family="Poppins", color=C_TEXT), height=450, margin=dict(l=20,r=20,t=60,b=20))
328
  fig.update_scenes(xaxis_visible=False, yaxis_visible=False, zaxis_visible=False, bgcolor=C_BG)
329
  return fig
330
 
331
  def ciz_galaksi(hastalik):
332
  fig = go.Figure()
333
- fig.add_trace(go.Scatter3d(x=[0], y=[0], z=[0], mode='markers+text', text=[hastalik['tani'].upper()], textfont=dict(color='#00E0C6', size=18, family="Orbitron"), marker=dict(size=50, color='#00C4B4', symbol='diamond', line=dict(width=2, color='white'))))
334
- anahtarlar = hastalik['anahtarlar'].split()
335
- for i, kelime in enumerate(anahtarlar):
336
- if len(kelime) < 3: continue
337
- dist = 12 + random.uniform(-2, 5)
338
- theta = random.uniform(0, 2*np.pi)
339
- phi = random.uniform(0, np.pi)
340
- x = dist * np.sin(phi) * np.cos(theta)
341
- y = dist * np.sin(phi) * np.sin(theta)
342
- z = dist * np.cos(phi)
343
- fig.add_trace(go.Scatter3d(x=[x], y=[y], z=[z], mode='markers+text', text=[kelime], textfont=dict(size=12, color='#F0F8FF', family="Poppins"), marker=dict(size=10, color='#00AEEF', opacity=0.8), showlegend=False))
344
- fig.add_trace(go.Scatter3d(x=[0, x], y=[0, y], z=[0, z], mode='lines', line=dict(color='rgba(255,255,255,0.2)', width=1), showlegend=False))
345
- fig.update_layout(title_text=f"SEMANTİK AĞ: {hastalik['tani']}", height=600, template="plotly_dark", paper_bgcolor=C_BG, font=dict(family="Poppins", color=C_TEXT), scene=dict(xaxis=dict(visible=False), yaxis=dict(visible=False), zaxis=dict(visible=False), bgcolor=C_BG))
346
  return fig
347
 
348
  def update_profile(c, y, b, k):
349
  st.session_state['user_profile'] = {"cinsiyet": c[0], "yas": y, "bmi": k/((b/100)**2) if b>0 else 25, "boy": b, "kilo": k, "cinsiyet_str": c}
350
- st.toast("Profil Güncellendi", icon="✅")
351
- time.sleep(0.5)
352
- st.rerun()
353
 
354
- # --- DIALOG (HTML ENTITY & GİRİNTİSİZ - KOD GİBİ GÖRÜNMEZ) ---
355
  @st.dialog(" ")
356
  def show_about_dialog():
357
  st.markdown("""
358
  <div class="math-box">
359
- <div style="color:#000;font-family:'Orbitron';font-size:1.5rem;font-weight:900;text-align:center;border-bottom:2px solid #000;padding-bottom:10px;">ℹ️ SİSTEM MİMARİSİ VE MATEMATİKSEL MODEL</div>
360
-
361
- <h4 style="color:#000;">1. Vektör Uzayı ve Dönüşüm ($R^n$)</h4>
362
- <p style="color:#333;">Sistem, şikayetleri <b>768 boyutlu vektörlere</b> dönüştürür. ($mpnet-base$)</p>
363
- <div class="math-formula-box">V<sub>input</sub> &isin; &#8477;<sup>768</sup></div>
364
-
365
- <h4 style="color:#000;">2. Anlamsal Benzerlik (Cosine Similarity)</h4>
366
- <p style="color:#333;">İki vektör arasındaki açı ölçülerek benzerlik hesaplanır.</p>
367
- <div class="math-formula-box">
368
  Sim(A, B) = cos(&theta;) =
369
- <div class="fraction">
370
- <span class="numerator">A &middot; B</span>
371
- <span class="denominator">||A|| &middot; ||B||</span>
372
  </div>
373
  </div>
374
-
375
- <h4 style="color:#000;">3. Öklid Uzaklığı (Euclidean Distance)</h4>
376
- <p style="color:#333;">Alternatif olarak uzaysal uzaklık kontrolü yapılır.</p>
377
- <div class="math-formula-box">
378
  d(p, q) = &radic;<span style="border-top:1px solid #000; padding-top:2px;">&sum; (q<sub>i</sub> - p<sub>i</sub>)<sup>2</sup></span>
379
  </div>
380
-
381
- <h4 style="color:#000;">4. Hibrit Skorlama Algoritması</h4>
382
- <p style="color:#333;">Nihai tanı skoru için ağırlıklı toplama yapılır:</p>
383
- <div class="math-formula-box">
384
- Score = (w<sub>1</sub> &middot; S<sub>cos</sub>) + (w<sub>2</sub> &middot; S<sub>context</sub>) + (w<sub>3</sub> &middot; S<sub>fuzzy</sub>)
385
- </div>
386
-
387
- <hr style="border-color:#999;margin:20px 0;">
388
- <h4 style="color:#000;margin-bottom:5px;">📚 Referanslar</h4>
389
- <p style="font-size:0.85rem;color:#333;">
390
  Referans Kaynağımız:<br>
391
- <a href="https://www.matematikdunyasi.org/2025/04/dogaldilislememodellerininmatematikseltemelleri/" target="_blank" style="color:#0066cc;text-decoration:none;font-weight:bold;">🔗 Matematik Dünyası: Doğal Dil İşleme Modellerinin Matematiksel Temelleri</a>
392
  </p>
393
  </div>
394
  """, unsafe_allow_html=True)
395
 
396
- # --- HEADER ---
397
- c_h1, c_h2 = st.columns([8, 2])
398
- with c_h1: st.markdown('<div class="dna-container"><div class="title-text">Tanı KoyAI</div></div>', unsafe_allow_html=True)
399
- with c_h2:
400
- if st.button("SİSTEM MİMARİSİ"): show_about_dialog()
401
-
402
- # --- PROFİL ---
403
- with st.container():
404
- st.markdown('<div class="profile-card">', unsafe_allow_html=True)
405
- st.markdown('<div class="mini-dna-title">👤 Hasta Profili</div>', unsafe_allow_html=True)
406
- c1, c2, c3, c4, c5 = st.columns([1.5, 1, 1, 1, 1])
407
- c_sec = c1.selectbox("Cinsiyet", ["Kadın", "Erkek"], index=["Kadın", "Erkek"].index(st.session_state['user_profile']['cinsiyet_str']))
408
- y_gir = c2.number_input("Yaş", 1, 99, st.session_state['user_profile']['yas'])
409
- b_gir = c3.number_input("Boy", 50, 250, st.session_state['user_profile']['boy'])
410
- k_gir = c4.number_input("Kilo", 10, 300, st.session_state['user_profile']['kilo'])
411
- if c5.button("KAYDET"): update_profile(c_sec, y_gir, b_gir, k_gir)
412
- st.markdown('</div>', unsafe_allow_html=True)
413
 
414
- # --- SIDEBAR ---
415
- with st.sidebar:
416
- st.header("📢 Geri Bildirim")
417
- with st.form("fb_detailed"):
418
- fb_topic = st.selectbox("Konu Seçiniz", ["Hata Bildirimi 🐛", "Öneri/İstek 💡", "Memnuniyet ⭐", "Diğer"])
419
- fb_txt = st.text_area("Mesajınız...", placeholder="Lütfen detaylı bilgi verin.", height=120)
420
- if st.form_submit_button("GÖNDER"):
421
- st.session_state['user_complaints'].append({"zaman": datetime.now().strftime("%Y-%m-%d %H:%M"), "konu": fb_topic, "mesaj": fb_txt})
422
- save_complaints(st.session_state['user_complaints'])
423
- st.success("Geri bildirim alındı!")
424
-
425
- st.markdown("---")
426
- st.subheader("📋 Geçmiş Bildirimler")
427
-
428
- if st.session_state['user_complaints']:
429
- with st.expander("Geçmişi Göster"):
430
- all_feedbacks = list(reversed(st.session_state['user_complaints']))
431
- topics = set()
432
- for item in all_feedbacks:
433
- if isinstance(item, dict): topics.add(item['konu'])
434
-
435
- for topic in sorted(list(topics)):
436
- st.markdown(f'<div style="color:#00BFFF; margin-top:15px; margin-bottom:5px; font-weight:bold; border-bottom:1px solid #2C3E50; font-size:1rem;">{topic}</div>', unsafe_allow_html=True)
437
- for item in all_feedbacks:
438
- if isinstance(item, dict) and item['konu'] == topic:
439
- st.markdown(f'<div style="background:#1E2A35; border-left:3px solid #00BFFF; padding:10px; border-radius:4px; margin-bottom:8px; font-size:0.9rem; color:#FFFFFF;">'
440
- f'<div style="color:#B0C4DE; font-weight:bold; font-size:0.75rem; margin-bottom:4px;">{item["zaman"]}</div>{item["mesaj"]}</div>', unsafe_allow_html=True)
441
-
442
- # ANA EKRAN
443
- if st.session_state.secilen_hastalik is None:
444
- st.markdown('<h3 class="fire-title">🔥 ŞİKAYET ANALİZİ</h3>', unsafe_allow_html=True)
445
- with st.form("main"):
446
- txt = st.text_area("Şikayetinizi detaylıca yazınız...", height=150, placeholder="Örn: Kalbim sıkışıyor, nefes alamıyorum...")
447
- btn = st.form_submit_button("ANALİZ BAŞLAT ▶️")
448
- if btn:
449
- with st.spinner("Yapay Zeka Analiz Ediyor..."):
450
- time.sleep(1)
451
- durum, res = teshis_motoru(txt, st.session_state['user_profile'])
452
- st.session_state.analiz_durumu = durum
453
- st.session_state.top_results = res
454
- st.session_state.sikayet_metni = txt
455
- st.rerun()
456
-
457
- if st.session_state.analiz_durumu == "Basarili":
458
- st.success("Analiz Tamamlandı.")
459
- st.plotly_chart(ciz_genel_bakis(st.session_state.top_results, st.session_state.sikayet_metni), use_container_width=True)
460
- cols = st.columns(len(st.session_state.top_results))
461
- for i, r in enumerate(st.session_state.top_results):
462
- if cols[i].button(f"{r['tani']}\n%{r['olasilik']:.0f}", key=f"b{i}"):
463
- st.session_state.secilen_hastalik = r
464
- st.rerun()
465
- elif st.session_state.analiz_durumu == "Düşük Güven":
466
- st.warning("Eşleşen belirgin bir hastalık bulunamadı.")
467
- if st.session_state.top_results:
468
- st.write("Olası tahminler:")
469
- for r in st.session_state.top_results: st.write(f"- {r['tani']} (%{r['olasilik']:.1f})")
470
-
471
- # DETAY EKRANI
472
- if st.session_state.secilen_hastalik:
473
- sh = st.session_state.secilen_hastalik
474
- st.markdown("---")
475
- st.info(f"**Seçilen Tanı:** {sh['tani']} - {sh['aciklama']}")
476
- c_g1, c_g2 = st.columns(2)
477
- with c_g1: st.plotly_chart(ciz_galaksi(sh), use_container_width=True)
478
- with c_g2:
479
- with st.expander("📄 DETAYLI TIBBİ RAPOR", expanded=True):
480
- anahtar_kelimeler = sh['anahtarlar'].split()
481
- etiketler_html = ""
482
- for kelime in anahtar_kelimeler:
483
- if len(kelime) > 3:
484
- etiketler_html += f"<span style='display:inline-block;background-color:#2C3E50;color:#FFFFFF;padding:5px 12px;margin:3px;border-radius:15px;font-size:0.9rem;border:1px solid #00E0C6;'>{kelime}</span>"
485
-
486
- rapor_html = ""
487
- rapor_html += f'<div style="background-color:rgba(30,45,60,0.9);border:2px solid #2C3E50;border-radius:12px;padding:25px;margin-top:10px;box-shadow:inset 0 0 20px rgba(0,0,0,0.3);">'
488
- rapor_html += f'<span style="font-size:1.1rem;font-weight:900;margin-bottom:8px;display:block;border-left:4px solid #00E0C6;padding-left:10px;text-transform:uppercase;color:#00E0C6;">📌 TANI TANIMI</span>'
489
- rapor_html += f'<div style="color:#FFFFFF;font-size:1.1rem;line-height:1.6;font-weight:500;margin-bottom:15px;">{sh["aciklama"]}</div>'
490
- rapor_html += f'<span style="font-size:1.1rem;font-weight:900;margin-bottom:8px;display:block;border-left:4px solid #00E0C6;padding-left:10px;text-transform:uppercase;color:#00E0C6;">🗝️ İLİŞKİLİ BELİRTİLER</span>'
491
- rapor_html += f'<div style="margin-top:5px;margin-bottom:15px;">{etiketler_html}</div>'
492
- rapor_html += f'<span style="font-size:1.1rem;font-weight:900;margin-bottom:8px;display:block;border-left:4px solid #00E0C6;padding-left:10px;text-transform:uppercase;color:#00E0C6;">💊 ÖNERİLEN TEDAVİ VE YAKLAŞIM</span>'
493
- rapor_html += f'<div style="color:#FFFFFF;font-size:1.1rem;line-height:1.6;font-weight:500;margin-bottom:15px;">{sh["tedavi"]}</div>'
494
- rapor_html += f'<hr style="border-color:#2C3E50;">'
495
- rapor_html += f'<span style="font-size:1.1rem;font-weight:900;margin-bottom:8px;display:block;border-left:4px solid #00E0C6;padding-left:10px;text-transform:uppercase;color:#00E0C6;">🏥 BAŞVURULMASI GEREKEN BÖLÜM</span>'
496
- rapor_html += f'<div style="color:#39FF14;font-weight:bold;font-size:1.3rem;">{SEVK_TABLOSU.get(sh["kat"], "Uzman Hekim")}<br><span style="color:#FFD700;font-size:0.9rem;font-weight:bold;">(Bölüm: {sh["kat"]})</span></div>'
497
- rapor_html += f'</div>'
498
-
499
- st.markdown(rapor_html, unsafe_allow_html=True)
500
 
501
- # --- YASAL UYARI KUTUSU ---
502
- st.markdown("<br>", unsafe_allow_html=True)
503
  st.markdown("""
504
- <div class="legal-warning">
505
- ⚠️ YASAL UYARI: Bu uygulama yalnızca ön bilgilendirme ve eğitim amaçlıdır.<br>
506
- Kesinlikle tıbbi tanı veya tedavi yerine geçmez.<br>
507
- Lütfen şikayetleriniz için en yakın sağlık kuruluşuna veya uzman bir hekime başvurunuz.<br>
508
- Sistem şu an DEMO aşamasındadır ve <u>sınırlı veriyle çalışmaktadır</u>.
 
509
  </div>
510
  """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
 
512
- st.markdown('<div style="margin-top:20px;padding:20px;background:rgba(30,45,60,0.3);border-radius:10px;text-align:center;border:1px solid rgba(100,150,200,0.2);">', unsafe_allow_html=True)
513
- col_ask, col_btn = st.columns([3, 1])
514
- with col_ask: st.markdown("### Başka bir şikayetiniz var mı?"); st.caption("Sağlıklı günler dileriz.")
515
- with col_btn:
516
- if st.button("EVET, YENİ SORGU", use_container_width=True):
517
- st.session_state.secilen_hastalik = None; st.session_state.analiz_durumu = None; st.session_state.top_results = []
 
 
 
 
 
 
 
 
 
 
518
  st.rerun()
519
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # =============================================================================
2
+ # TANI KOYAI - V7.0.0 (VOICE COMMAND EDITION)
3
+ # - YENİLİK: "Sesle Yaz (Speech-to-Text)" özelliği eklendi.
4
+ # - GEREKSİNİM: 'pip install streamlit-mic-recorder' kütüphanesi gereklidir.
5
+ # - ENTEGRASYON: Mikrofon verisi otomatik olarak şikayet kutusuna aktarılır.
6
+ # - TASARIM: Kırmızı/Pastel Medikal tema ve tüm animasyonlar korundu.
7
  # =============================================================================
8
 
9
  import streamlit as st
 
19
  from datetime import datetime
20
  import difflib
21
 
22
+ # --- YENİ KÜTÜPHANE (SES İÇİN) ---
23
+ # Eğer hata alırsanız terminale: pip install streamlit-mic-recorder yazın.
24
+ try:
25
+ from streamlit_mic_recorder import speech_to_text
26
+ except ImportError:
27
+ st.error("Lütfen ses özelliği için kütüphaneyi kurun: pip install streamlit-mic-recorder")
28
+ st.stop()
29
+
30
  warnings.filterwarnings("ignore")
31
 
32
  # PROJE VERSİYON NUMARASI
33
+ PROJECT_VERSION = "V7.0.0 (Voice Edition)"
34
 
35
  # DOSYA İSİMLERİ
36
  COMPLAINTS_FILE = 'feedback_detailed.json'
37
  DATA_FILE = 'hastalik_verisi.json'
38
 
39
+ # --- SESSION STATE ---
40
+ if 'app_loaded' not in st.session_state: st.session_state['app_loaded'] = False
41
+ if 'user_complaints' not in st.session_state: st.session_state['user_complaints'] = []
42
+ if 'user_profile' not in st.session_state: st.session_state['user_profile'] = {"cinsiyet": "E", "yas": 30, "bmi": 25.0, "boy": 175, "kilo": 75, "cinsiyet_str": "Erkek"}
43
+ if 'secilen_hastalik' not in st.session_state: st.session_state['secilen_hastalik'] = None
44
+ if 'analiz_durumu' not in st.session_state: st.session_state['analiz_durumu'] = "Giris"
45
+ if 'top_results' not in st.session_state: st.session_state['top_results'] = []
46
+ if 'sikayet_metni' not in st.session_state: st.session_state['sikayet_metni'] = "" # Bu değişkeni ses için kullanacağız
47
+ if 'soru_adimi' not in st.session_state: st.session_state['soru_adimi'] = False
48
+
49
  # --- CSS STİL TANIMLAMASI ---
50
  STYLE_CONFIG = f"""
51
  <style>
52
+ /* 1. FONT VE GENEL ATMOSFER */
53
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap');
54
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=swap');
55
 
56
+ h1, h2, h3, h4, h5, h6 {{
57
+ font-family: 'Outfit', sans-serif !important;
58
+ color: #D63031 !important;
59
+ }}
60
+
61
+ p, label, button, input, textarea, li, a, .stMarkdown, div[data-testid="stText"] {{
62
+ font-family: 'DM Sans', sans-serif !important;
63
+ color: #2D3436;
64
  }}
65
 
66
  /* 2. ARKA PLAN */
67
  .stApp {{
68
+ background: linear-gradient(135deg, #FFF5F5 0%, #FFE3E3 100%);
 
69
  background-attachment: fixed;
70
  }}
71
 
72
+ /* --- SIDEBAR --- */
73
+ section[data-testid="stSidebar"] {{
74
+ background-color: rgba(255, 255, 255, 0.7) !important;
75
+ border-right: 1px solid rgba(255, 255, 255, 0.5);
76
+ backdrop-filter: blur(20px);
 
77
  }}
78
+
79
+ .st-emotion-cache-1f3w014 {{ font-family: "Material Icons" !important; }}
80
+
81
+ /* Sidebar Başlık */
82
+ .sidebar-brand {{
83
+ background: linear-gradient(135deg, #FF7675, #D63031);
84
+ padding: 20px; border-radius: 15px; text-align: center;
85
+ margin-bottom: 20px;
86
+ box-shadow: 0 10px 20px rgba(214, 48, 49, 0.3);
 
 
87
  }}
88
+ .sidebar-brand h3 {{ color: white !important; margin: 0; }}
89
+
90
+ /* --- CAM KARTLAR VE ANİMASYONLAR --- */
91
+ .glass-panel {{
92
+ background: rgba(255, 255, 255, 0.85);
93
+ backdrop-filter: blur(16px);
94
+ border: 1px solid #FFFFFF;
95
+ border-radius: 20px;
96
+ padding: 30px;
97
+ box-shadow: 0 8px 32px 0 rgba(214, 48, 49, 0.05);
98
+ margin-bottom: 25px;
99
+ animation: slideUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) both;
100
  }}
101
 
102
+ .delay-1 {{ animation-delay: 0.1s; }}
103
+ .delay-2 {{ animation-delay: 0.25s; }}
104
+ .delay-3 {{ animation-delay: 0.4s; }}
 
 
 
 
105
 
106
+ @keyframes slideUp {{
107
+ from {{ opacity: 0; transform: translateY(40px); }}
108
+ to {{ opacity: 1; transform: translateY(0); }}
 
109
  }}
110
 
111
+ /* Grafik Kutusu */
112
+ .chart-box {{
113
+ background: white; border-radius: 15px; padding: 10px;
114
+ box-shadow: 0 10px 30px rgba(0,0,0,0.05); margin-bottom: 20px; border: 1px solid #FFE3E3;
115
+ animation: popIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
116
+ }}
117
+
118
+ @keyframes popIn {{ from {{ opacity: 0; transform: scale(0.95); }} to {{ opacity: 1; transform: scale(1); }} }}
119
+
120
+ /* --- INPUT ALANLARI --- */
121
+ .stTextInput input, .stTextArea textarea, .stNumberInput input, .stSelectbox div[data-baseweb="select"] > div {{
122
+ background-color: #FFFFFF !important;
123
+ border: 1px solid #E3E3E3 !important;
124
+ border-radius: 12px !important;
125
+ color: #2D3436 !important;
126
+ padding: 10px !important;
127
  }}
128
+ .stTextInput input:focus, .stTextArea textarea:focus {{
129
+ border-color: #FF7675 !important;
130
+ box-shadow: 0 0 0 3px rgba(255, 118, 117, 0.2) !important;
 
 
131
  }}
 
132
 
133
+ /* --- BUTONLAR --- */
134
+ .stButton > button {{
135
+ background: linear-gradient(135deg, #FF7675 0%, #D63031 100%) !important;
136
+ color: #FFFFFF !important;
137
+ font-family: 'Outfit', sans-serif !important; font-weight: 700 !important;
138
+ border: none; border-radius: 12px;
139
+ padding: 16px 24px; font-size: 1.1rem;
140
+ box-shadow: 0 10px 20px rgba(214, 48, 49, 0.2);
141
+ transition: all 0.2s ease;
142
  }}
143
+ .stButton > button:hover {{
144
+ transform: translateY(-2px);
145
+ box-shadow: 0 15px 30px rgba(214, 48, 49, 0.4);
 
 
 
146
  }}
147
 
148
+ /* AYARLAR BUTONU */
149
+ div[data-testid="stPopover"] > button {{
150
+ background: #FFFFFF !important;
151
+ color: #2D3436 !important;
152
+ border: 2px solid #E3E3E3 !important;
153
+ width: 50px !important; height: 50px !important; padding: 0 !important;
154
+ font-size: 1.5rem !important; box-shadow: none !important;
155
  }}
156
+ div[data-testid="stPopover"] > button:hover {{
157
+ border-color: #D63031 !important; color: #D63031 !important; background: #FFF5F5 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  }}
159
 
160
+ /* --- YÜKLEME EKRANLARI --- */
161
+ #initial-loader, #loading-overlay {{
162
+ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
163
+ background: #FFF5F5; z-index: 1000000;
164
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
 
 
 
 
 
 
 
165
  }}
166
+ #initial-loader {{ animation: fadeOutLoader 0.5s ease-out 2.5s forwards; }}
167
+ #loading-overlay {{ background: linear-gradient(135deg, #FFF5F5, #FFFFFF); z-index: 999999; }}
168
+ @keyframes fadeOutLoader {{ to {{ opacity: 0; visibility: hidden; }} }}
169
+ .loader-pulse, .heart-rate {{
170
+ width: 120px; height: 120px;
171
+ background: url('https://img.icons8.com/ios-filled/100/ff7675/heart-with-pulse.png') no-repeat center;
172
+ background-size: contain;
173
+ animation: heartbeat 1.2s infinite;
174
+ }}
175
+ @keyframes heartbeat {{ 0% {{ transform: scale(1); }} 15% {{ transform: scale(1.2); }} 30% {{ transform: scale(1); }} 45% {{ transform: scale(1.1); }} 60% {{ transform: scale(1); }} }}
176
+ .loading-text {{ font-family: 'Outfit'; font-size: 2rem; color: #D63031 !important; font-weight: bold; letter-spacing: 2px; }}
177
+
178
+ /* --- SORU EKRANI --- */
179
+ .focus-bg {{ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: linear-gradient(135deg, #FFF5F5, #FFFFFF); z-index: 0; }}
180
+ .question-box {{ position: relative; z-index: 100; background: white; border-radius: 20px; padding: 50px; box-shadow: 0 20px 60px rgba(214, 48, 49, 0.15); text-align: center; border: 2px solid #FFF0F0; max-width: 800px; margin: 10vh auto 30px auto; animation: slideUp 0.6s ease-out; }}
181
+ .question-title {{ color: #FF7675 !important; font-size: 1.1rem; margin-bottom: 20px; font-weight: bold; letter-spacing: 2px; }}
182
+ .question-text {{ font-size: 2.2rem; font-weight: bold; color: #2D3436 !important; line-height: 1.3; }}
183
+
184
+ .version-tag {{ position: fixed; bottom: 15px; right: 20px; background: white; padding: 5px 15px; border-radius: 20px; box-shadow: 0 5px 15px rgba(0,0,0,0.05); color: #FF7675 !important; font-weight: bold; font-size: 0.8rem; }}
185
+ .math-box {{ background-color: #FFFFFF !important; padding: 25px; border-radius: 12px; }}
186
+ .math-box * {{ color: #2D3436 !important; }}
187
  </style>
188
+ <div class="version-tag">{PROJECT_VERSION}</div>
189
  """
190
 
191
  # --- YARDIMCI FONKSİYONLAR ---
 
193
  if os.path.exists(COMPLAINTS_FILE):
194
  try:
195
  with open(COMPLAINTS_FILE, 'r', encoding='utf-8') as f:
196
+ return json.loads(f.read())
 
197
  except: return []
198
  return []
199
 
200
+ def save_complaints(data):
201
  try:
202
  with open(COMPLAINTS_FILE, 'w', encoding='utf-8') as f:
203
+ json.dump(data, f, indent=4, ensure_ascii=False)
204
  except: pass
205
 
206
+ def clear_data():
207
+ st.session_state['user_profile'] = {"cinsiyet": "E", "yas": 30, "bmi": 25.0, "boy": 175, "kilo": 75, "cinsiyet_str": "Erkek"}
208
+ st.session_state['user_complaints'] = []
209
+ st.session_state['secilen_hastalik'] = None
210
+ st.session_state['analiz_durumu'] = "Giris"
211
+ st.session_state['top_results'] = []
212
+ st.session_state['sikayet_metni'] = ""
213
+ st.toast("Hasta verileri sıfırlandı!", icon="♻️")
214
+ time.sleep(1)
215
+ st.rerun()
216
 
217
  # --- UI AYARLARI ---
218
+ st.set_page_config(page_title="Tanı KoyAI", layout="wide", page_icon="🏥")
219
  st.markdown(STYLE_CONFIG, unsafe_allow_html=True)
220
 
221
+ # --- İLK AÇILIŞ PRE-LOADER ---
222
+ if not st.session_state['app_loaded']:
223
+ st.markdown("""
224
+ <div id="initial-loader">
225
+ <div class="loader-pulse"></div>
226
+ <div style="font-family:'Outfit'; font-size:1.5rem; color:#D63031; font-weight:bold; margin-top:20px;">SİSTEM BAŞLATILIYOR...</div>
227
+ </div>
228
+ """, unsafe_allow_html=True)
229
+ time.sleep(2.5)
230
+ st.session_state['app_loaded'] = True
231
+ st.rerun()
232
+
233
+ # --- MODEL VE VERİ ---
234
  SEVK_TABLOSU = {
235
+ "Nöroloji": "Nörolog / Aile Hekimi", "KBB": "KBB Uzmanı", "Göz": "Göz Doktoru (Acil)",
236
+ "Kardiyoloji": "ACİL 112 / HASTANE", "Dahiliye": "Dahiliye Uzmanı", "Acil": "ACİL SERVİS",
237
+ "Üroloji": "Ürolog", "Cildiye": "Dermatolog", "Ortopedi": "Ortopedist",
238
+ "Genel Cerrahi": "Genel Cerrah", "Enfeksiyon": "Enfeksiyon Uzmanı"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  }
240
 
241
  @st.cache_resource
 
243
  model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-mpnet-base-v2')
244
  TIBBI_SOZLUK = ["ağrı"]
245
  vocab_embeddings = model.encode(TIBBI_SOZLUK, convert_to_tensor=True)
 
246
  VERI_BANKASI = []
247
  if os.path.exists(DATA_FILE):
248
  try:
249
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
250
  VERI_BANKASI = json.load(f)
251
+ except: pass
252
+
 
253
  for item in VERI_BANKASI:
254
  if 'cinsiyet_kist' not in item: item['cinsiyet_kist'] = 'genel'
255
+ if 'soru' not in item: item['soru'] = None
 
256
 
257
  if VERI_BANKASI:
258
  db_embeddings = model.encode([item["anahtarlar"] for item in VERI_BANKASI], convert_to_tensor=True)
259
  else: db_embeddings = None
 
260
  return model, VERI_BANKASI, TIBBI_SOZLUK, vocab_embeddings, db_embeddings
261
 
262
  model, VERI_BANKASI, TIBBI_SOZLUK, vocab_embeddings, db_embeddings = load_model_and_data()
 
266
  puan = 0.0
267
  sikayet_lower = sikayet.lower()
268
  db_anahtarlar = db_item["anahtarlar"].lower()
269
+ if any(k in sikayet_lower and k in db_anahtarlar for k in ["kalp", "göğüs", "nefes", "inme"]): puan += 80.0
270
+ if any(k in sikayet_lower for k in ["sabah", "gece", "spor", "uyku"]):
271
+ puan += 40.0 if any(k in db_anahtarlar for k in ["sabah", "gece", "spor", "uyku"]) else -10.0
 
 
 
 
 
 
 
 
272
  words = sikayet_lower.split()
273
+ db_words = db_anahtarlar.split()
274
  for w in words:
275
  if len(w) < 3: continue
276
+ if difflib.get_close_matches(w, db_words, n=1, cutoff=0.75): puan += 15.0
 
 
277
  return puan
278
 
279
  def evrensel_filtre(sikayet, db_item):
280
  text = sikayet.lower()
281
  db_bolge = db_item['bolge']
282
+ if ("kalp" in text or "sıkış" in text) and db_item['kat'] == "Kardiyoloji": return 100.0
283
 
284
+ # ANATOMİ HAKEMİ
285
+ testis_keywords = ["testis", "torba", "skrotum", "yumurta"]
286
+ if any(k in text for k in testis_keywords):
287
+ if db_item.get('kat') != "Üroloji" and db_bolge != "genital": return -10000.0
288
+
289
+ makat_keywords = ["makat", "anüs", "dışkı", "basur"]
290
+ if any(k in text for k in makat_keywords):
291
+ if db_bolge not in ["karin", "genital"] and db_item.get('kat') not in ["Genel Cerrahi", "Gastroenteroloji"]: return -10000.0
292
+
293
+ bolgeler = {
294
+ "ust_uzuv": ["kol", "omuz", "el", "bilek"],
295
+ "alt_uzuv": ["bacak", "diz", "ayak", "topuk"],
296
+ "bas": ["baş", "göz", "kulak", "burun", "boğaz", "yüz"],
297
+ "gogus": ["göğüs", "nefes", "sırt", "kaburga"],
298
+ "karin": ["karın", "mide", "bağırsak", "böğür"],
299
+ "genital": ["idrar", "kasık", "testis", "vajina", "yumurtalık", "penis", "cinsel"]
300
  }
301
+ bulunan = [b for b, k in bolgeler.items() if any(x in text for x in k)]
302
+ if bulunan:
303
+ if db_bolge != "genel" and db_bolge not in bulunan: return -2000.0
304
+ if db_bolge in bulunan: return 50.0
305
+ return 0.0
 
 
 
306
 
307
  def anamnez_filtre(user_profile, db_item):
308
  if db_item['cinsiyet_kist'] == 'K' and user_profile['cinsiyet'] == 'E': return -5000.0
 
310
  return 0.0
311
 
312
  def teshis_motoru(sikayet, user_profile):
313
+ if not VERI_BANKASI: return []
 
 
314
  input_vec = model.encode(sikayet, convert_to_tensor=True)
315
  sonuclar = []
316
  for i, item in enumerate(VERI_BANKASI):
317
+ anatomi_puan = evrensel_filtre(sikayet, item)
318
+ if anatomi_puan < -5000: continue
319
+ score = (util.cos_sim(input_vec, db_embeddings[i])[0].item() * 20) + \
320
+ anatomi_puan + anamnez_filtre(user_profile, item) + kelime_match(sikayet, item)
321
+ prob = max(0, min(99.0, score))
 
 
 
322
  if prob > 20: sonuclar.append({**item, "olasilik": prob})
323
  sonuclar.sort(key=lambda x: x["olasilik"], reverse=True)
324
+ return sonuclar[:5] if sonuclar else []
325
 
326
  # --- GRAFİKLER ---
327
+ C_BG = '#FFF5F5'
328
+ C_TEXT = '#2D3436'
329
 
330
  def ciz_genel_bakis(top_results, sikayet):
331
+ fig = make_subplots(rows=1, cols=2, specs=[[{"type": "xy"}, {"type": "scene"}]], subplot_titles=("OLASILIK DAĞILIMI", "SEMANTİK ANALİZ"))
332
+ fig.add_trace(go.Bar(x=[r['tani'] for r in top_results], y=[r['olasilik'] for r in top_results], marker_color='#FF7675', text=[f"%{r['olasilik']:.1f}" for r in top_results], textposition='outside', textfont=dict(color=C_TEXT)), row=1, col=1)
 
333
  for r in top_results:
334
+ fig.add_trace(go.Scatter3d(x=[random.uniform(-5,5)], y=[random.uniform(-5,5)], z=[random.uniform(-5,5)], mode='markers+text', text=[r['tani']], textfont=dict(color=C_TEXT), marker=dict(size=15, color='#D63031'), showlegend=False), row=1, col=2)
335
+ fig.update_layout(template="plotly_white", paper_bgcolor=C_BG, plot_bgcolor=C_BG, font=dict(family="DM Sans", color=C_TEXT), height=450, margin=dict(l=20,r=20,t=60,b=20))
 
 
336
  fig.update_scenes(xaxis_visible=False, yaxis_visible=False, zaxis_visible=False, bgcolor=C_BG)
337
  return fig
338
 
339
  def ciz_galaksi(hastalik):
340
  fig = go.Figure()
341
+ fig.add_trace(go.Scatter3d(x=[0], y=[0], z=[0], mode='markers+text', text=[hastalik['tani'].upper()], textfont=dict(color='#D63031', size=18, family="Outfit"), marker=dict(size=50, color='#FF7675', line=dict(color='#D63031', width=2))))
342
+ for i, k in enumerate(hastalik['anahtarlar'].split()):
343
+ if len(k) < 3: continue
344
+ x, y, z = (12+random.uniform(-2,5)) * np.sin(i), (12+random.uniform(-2,5)) * np.cos(i), random.uniform(-5,5)
345
+ fig.add_trace(go.Scatter3d(x=[x], y=[y], z=[z], mode='markers+text', text=[k], textfont=dict(size=12, color='#636E72'), marker=dict(size=8, color='#FF7675'), showlegend=False))
346
+ fig.add_trace(go.Scatter3d(x=[0, x], y=[0, y], z=[0, z], mode='lines', line=dict(color='rgba(214, 48, 49, 0.2)', width=1), showlegend=False))
347
+ fig.update_layout(title_text=f"SEMANTİK AĞ: {hastalik['tani']}", height=600, template="plotly_white", paper_bgcolor=C_BG, scene=dict(xaxis_visible=False, yaxis=dict(visible=False), zaxis=dict(visible=False), bgcolor=C_BG))
 
 
 
 
 
 
348
  return fig
349
 
350
  def update_profile(c, y, b, k):
351
  st.session_state['user_profile'] = {"cinsiyet": c[0], "yas": y, "bmi": k/((b/100)**2) if b>0 else 25, "boy": b, "kilo": k, "cinsiyet_str": c}
352
+ st.toast("Profil Güncellendi", icon="✅"); time.sleep(0.5); st.rerun()
 
 
353
 
354
+ # --- DIALOG ---
355
  @st.dialog(" ")
356
  def show_about_dialog():
357
  st.markdown("""
358
  <div class="math-box">
359
+ <h3 style="color:#D63031; text-align:center;">SİSTEM MİMARİSİ & MATEMATİKSEL MODEL</h3>
360
+ <p style="color:#2D3436;">Bu sistem, Transformer tabanlı NLP modelleri ve Vektör Uzayı matematiği kullanarak semantik analiz yapar.</p>
361
+ <hr>
362
+ <h4 style="color:#2D3436;">1. Vektör Uzayı ($R^n$)</h4>
363
+ <p style="color:#636E72;">Sistem, girilen şikayetleri <b>768 boyutlu yüksek yoğunluklu vektörlere</b> dönüştürür. ($mpnet-base-v2$)</p>
364
+ <div style="background:#FFF5F5; padding:10px; border-radius:5px; text-align:center; font-family:monospace; color:#D63031;">V<sub>input</sub> &isin; &#8477;<sup>768</sup></div>
365
+ <h4 style="color:#2D3436;">2. Kosinüs Benzerliği (Cosine Similarity)</h4>
366
+ <p style="color:#636E72;">İki vektör arasındaki açı ölçülerek anlamsal benzerlik hesaplanır.</p>
367
+ <div style="background:#FFF5F5; padding:10px; border-radius:5px; text-align:center; font-family:monospace; color:#D63031;">
368
  Sim(A, B) = cos(&theta;) =
369
+ <div style="display:inline-block; vertical-align:middle; text-align:center;">
370
+ <span style="border-bottom:1px solid #000; display:block;">A &middot; B</span>
371
+ <span>||A|| &middot; ||B||</span>
372
  </div>
373
  </div>
374
+ <h4 style="color:#2D3436;">3. Öklid Uzaklığı (Euclidean Distance)</h4>
375
+ <p style="color:#636E72;">Alternatif doğrulama için uzaysal uzaklık metrikleri kullanılır.</p>
376
+ <div style="background:#FFF5F5; padding:10px; border-radius:5px; text-align:center; font-family:monospace; color:#D63031;">
 
377
  d(p, q) = &radic;<span style="border-top:1px solid #000; padding-top:2px;">&sum; (q<sub>i</sub> - p<sub>i</sub>)<sup>2</sup></span>
378
  </div>
379
+ <h4 style="color:#2D3436;">4. Hibrit Skorlama</h4>
380
+ <p style="color:#636E72;">Sonuçlar, vektör benzerliği, kelime eşleşmesi ve demografik filtrelerin ağırlıklı toplamıdır.</p>
381
+ <hr>
382
+ <h4 style="color:#2D3436;margin-bottom:5px;">📚 Referanslar</h4>
383
+ <p style="font-size:0.85rem;color:#636E72;">
 
 
 
 
 
384
  Referans Kaynağımız:<br>
385
+ <a href="https://www.matematikdunyasi.org/2025/04/dogaldilislememodellerininmatematikseltemelleri/" target="_blank" style="color:#D63031;text-decoration:none;font-weight:bold;">🔗 Matematik Dünyası: Doğal Dil İşleme Modellerinin Matematiksel Temelleri</a>
386
  </p>
387
  </div>
388
  """, unsafe_allow_html=True)
389
 
390
+ # --- ANA AKIŞ KONTROLÜ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
+ if st.session_state.analiz_durumu in ["Animasyon_1", "Animasyon_2", "SoruSor", "Sonuc"]:
393
+ st.markdown("""<style>[data-testid="stSidebar"] { display: none; } [data-testid="stHeader"] { display: none; } .stApp { padding-top: 0px; }</style>""", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
+ # 1. ANİMASYON MODU
396
+ if "Animasyon" in str(st.session_state.analiz_durumu):
397
  st.markdown("""
398
+ <div id="loading-overlay">
399
+ <div class="heart-rate"></div>
400
+ <div class="loading-text">ANALİZ EDİLİYOR...</div>
401
+ <div style="color:#636E72; margin-top:15px; font-weight:500;">
402
+ Semptomlar taranıyor | Tıbbi veritabanı eşleştiriliyor
403
+ </div>
404
  </div>
405
  """, unsafe_allow_html=True)
406
+ time.sleep(2.0)
407
+
408
+ if st.session_state.analiz_durumu == "Animasyon_1":
409
+ res = teshis_motoru(st.session_state.sikayet_metni, st.session_state['user_profile'])
410
+ st.session_state.top_results = res
411
+ if res: st.session_state.analiz_durumu = "SoruSor"
412
+ else: st.session_state.analiz_durumu = "DusukGuven"
413
+ elif st.session_state.analiz_durumu == "Animasyon_2":
414
+ st.session_state.analiz_durumu = "Sonuc"
415
+ st.rerun()
416
+
417
+ # 2. SORU SORMA MODU
418
+ elif st.session_state.analiz_durumu == "SoruSor":
419
+ top_hastalik = st.session_state.top_results[0]
420
+ if top_hastalik.get('soru') and not st.session_state.soru_adimi:
421
+
422
+ st.markdown('<div class="focus-bg"></div>', unsafe_allow_html=True)
423
+ st.markdown(f"""
424
+ <div class="question-box">
425
+ <div class="question-title">DOKTOR ASİSTANI SORUYOR</div>
426
+ <div class="question-text">"{top_hastalik['soru']}"</div>
427
+ </div>
428
+ """, unsafe_allow_html=True)
429
+
430
+ st.markdown("<br><br>", unsafe_allow_html=True)
431
+ col1, col2, col3 = st.columns([1, 2, 1])
432
+ with col2:
433
+ c1, c2 = st.columns(2)
434
+ if c1.button("EVET", use_container_width=True):
435
+ st.session_state.top_results[0]['olasilik'] = min(99, st.session_state.top_results[0]['olasilik'] + 20)
436
+ st.session_state.soru_adimi = True
437
+ st.session_state.analiz_durumu = "Animasyon_2"
438
+ st.rerun()
439
+
440
+ if c2.button("HAYIR", use_container_width=True):
441
+ st.session_state.top_results[0]['olasilik'] = max(10, st.session_state.top_results[0]['olasilik'] - 20)
442
+ st.session_state.top_results.sort(key=lambda x: x["olasilik"], reverse=True)
443
+ st.session_state.soru_adimi = True
444
+ st.session_state.analiz_durumu = "Animasyon_2"
445
+ st.rerun()
446
+ else:
447
+ st.session_state.analiz_durumu = "Animasyon_2"
448
+ st.rerun()
449
 
450
+ # 3. SONUÇ EKRANI
451
+ elif st.session_state.analiz_durumu == "Sonuc":
452
+ if not st.session_state.get('soru_adimi'):
453
+ st.markdown('<div class="chart-box">', unsafe_allow_html=True)
454
+ st.plotly_chart(ciz_genel_bakis(st.session_state.top_results, st.session_state.sikayet_metni), use_container_width=True)
455
+ st.markdown('</div>', unsafe_allow_html=True)
456
+
457
+ st.success(f"Analiz Tamamlandı. En olası tanı: {st.session_state.top_results[0]['tani']}")
458
+ st.markdown('<div class="chart-box">', unsafe_allow_html=True)
459
+ st.plotly_chart(ciz_genel_bakis(st.session_state.top_results, st.session_state.sikayet_metni), use_container_width=True)
460
+ st.markdown('</div>', unsafe_allow_html=True)
461
+
462
+ cols = st.columns(len(st.session_state.top_results))
463
+ for i, r in enumerate(st.session_state.top_results):
464
+ if cols[i].button(f"{r['tani']}\n%{r['olasilik']:.0f}", key=f"b{i}"):
465
+ st.session_state.secilen_hastalik = r
466
  st.rerun()
467
+
468
+ if st.session_state.secilen_hastalik:
469
+ sh = st.session_state.secilen_hastalik
470
+ st.markdown("---")
471
+ c_g1, c_g2 = st.columns(2)
472
+ with c_g1:
473
+ st.markdown('<div class="chart-box" style="animation-delay:0.2s;">', unsafe_allow_html=True)
474
+ st.plotly_chart(ciz_galaksi(sh), use_container_width=True)
475
+ st.markdown('</div>', unsafe_allow_html=True)
476
+ with c_g2:
477
+ with st.expander("📄 DETAYLI TIBBİ RAPOR", expanded=True):
478
+ rapor_html = f"""
479
+ <div style="background:white; padding:25px; border-radius:15px; border:1px solid #FF7675; box-shadow:0 10px 30px rgba(214, 48, 49, 0.1);">
480
+ <h3 style="color:#D63031; border-bottom:2px solid #FFE3E3; padding-bottom:10px; margin-bottom:15px;">{sh['tani']}</h3>
481
+ <p style="color:#2D3436; font-size:1.1rem;">{sh['aciklama']}</p>
482
+ <div style="margin-top:20px; padding:15px; background:#FFF5F5; border-radius:10px; border-left:5px solid #D63031;">
483
+ <strong style="color:#D63031;">TEDAVİ PROTOKOLÜ:</strong> {sh['tedavi']}
484
+ </div>
485
+ <div style="margin-top:15px; font-weight:bold; color:#FF7675; font-size:1.2rem;">
486
+ 🏥 SEVK: {SEVK_TABLOSU.get(sh["kat"], "Uzman Hekim")}
487
+ </div>
488
+ </div>
489
+ """
490
+ st.markdown(rapor_html, unsafe_allow_html=True)
491
+
492
+ st.markdown("<br>", unsafe_allow_html=True)
493
+ if st.button("⬅️ ANA EKRANA DÖN", use_container_width=True):
494
+ st.session_state.secilen_hastalik = None; st.session_state.analiz_durumu = "Giris"; st.session_state.top_results = []
495
+ st.rerun()
496
+
497
+ # 4. NORMAL MOD
498
+ else:
499
+ with st.sidebar:
500
+ st.markdown('<div class="sidebar-brand"><h3>🏥 TANI KOYAI</h3><p style="color:white !important; font-size:0.8rem; margin:0;">v7.0 Voice Edition</p></div>', unsafe_allow_html=True)
501
+
502
+ with st.expander("📝 İletişim & Geri Bildirim"):
503
+ with st.form("fb"):
504
+ tp = st.selectbox("Konu", ["Hata Bildir", "Öneri", "Destek"])
505
+ tx = st.text_area("Mesajınız", height=80)
506
+ if st.form_submit_button("GÖNDER"):
507
+ st.session_state['user_complaints'].append({"zaman": datetime.now().strftime("%H:%M"), "konu": tp, "mesaj": tx})
508
+ save_complaints(st.session_state['user_complaints']); st.success("Gönderildi")
509
+
510
+ st.markdown('<div style="margin-top:30px; font-weight:bold; color:#D63031;">GEÇMİŞ İŞLEMLER</div>', unsafe_allow_html=True)
511
+ for i in reversed(st.session_state['user_complaints'][-3:]):
512
+ st.info(f"{i['zaman']} - {i['konu']}")
513
+
514
+ # HEADER (AYARLAR BUTONU İLE)
515
+ c_head1, c_head2, c_head3 = st.columns([5, 2, 1])
516
+ with c_head1:
517
+ st.markdown('<div class="glass-panel delay-1" style="padding:15px; margin-bottom:0;"><h1 style="font-size:2.5rem; margin:0;">TANI KOYAI</h1><p style="color:#636E72; margin:0;">Yapay Zeka Destekli Yeni Nesil Tıbbi Asistan</p></div>', unsafe_allow_html=True)
518
+ with c_head2:
519
+ st.write("") # Boşluk
520
+ if st.button("SİSTEM MİMARİSİ", use_container_width=True): show_about_dialog()
521
+ with c_head3:
522
+ st.write("")
523
+ # KARE AYARLAR BUTONU
524
+ with st.popover("⚙️", use_container_width=True):
525
+ st.markdown("### ⚙️ Ayarlar")
526
+ if st.button("🗑️ Yeni Hasta (Sıfırla)", use_container_width=True): clear_data()
527
+ if st.button("💾 Önbellek Temizle", use_container_width=True): st.cache_data.clear(); st.toast("Temizlendi!")
528
+
529
+ with st.container():
530
+ st.markdown('<div class="glass-panel delay-2" style="margin-top:20px;">', unsafe_allow_html=True)
531
+ st.markdown('<h4 style="margin-bottom:20px; color:#D63031;">👤 HASTA KİMLİK & BİYOMETRİ</h4>', unsafe_allow_html=True)
532
+ c1, c2, c3, c4, c5 = st.columns([1.5, 1, 1, 1, 1])
533
+ c_sec = c1.selectbox("Cinsiyet", ["Kadın", "Erkek"], index=["Kadın", "Erkek"].index(st.session_state['user_profile']['cinsiyet_str']))
534
+ y_gir = c2.number_input("Yaş", 1, 99, st.session_state['user_profile']['yas'])
535
+ b_gir = c3.number_input("Boy (cm)", 50, 250, st.session_state['user_profile']['boy'])
536
+ k_gir = c4.number_input("Kilo (kg)", 10, 300, st.session_state['user_profile']['kilo'])
537
+ if c5.button("KAYDET"): update_profile(c_sec, y_gir, b_gir, k_gir)
538
+ st.markdown('</div>', unsafe_allow_html=True)
539
+
540
+ if st.session_state.analiz_durumu == "Giris":
541
+ st.markdown('<div class="glass-panel delay-3">', unsafe_allow_html=True)
542
+
543
+ # SESLİ KOMUT BUTONU (FORM DIŞINDA)
544
+ col_mic, col_space = st.columns([1, 10])
545
+ with col_mic:
546
+ st.write("🎤 Sesli Anlat")
547
+ # Speech-to-text komponenti
548
+ # Not: 'just_once=True' önemlidir, yoksa sürekli döngüye girer.
549
+ text_from_voice = speech_to_text(language='tr', start_prompt="🎙️", stop_prompt="⏹️", just_once=True, use_container_width=True)
550
+
551
+ # Eğer ses gelirse session state'i güncelle
552
+ if text_from_voice:
553
+ st.session_state.sikayet_metni = text_from_voice
554
+ st.toast("Ses algılandı!", icon="🗣️")
555
+
556
+ with st.form("main"):
557
+ st.markdown('<h3 style="color:#D63031;">🔥 ŞİKAYET ANALİZİ</h3>', unsafe_allow_html=True)
558
+ # Text area değerini session state'den al (Ses gelince burası dolacak)
559
+ txt = st.text_area("Şikayetinizi detaylıca anlatın...", value=st.session_state.sikayet_metni, height=150, placeholder="Örn: Sol kolumda uyuşma var ve başım dönüyor...")
560
+ st.markdown("<br>", unsafe_allow_html=True)
561
+
562
+ if st.form_submit_button("ANALİZİ BAŞLAT"):
563
+ st.session_state.sikayet_metni = txt
564
+ st.session_state.analiz_durumu = "Animasyon_1"
565
+ st.rerun()
566
+ st.markdown('</div>', unsafe_allow_html=True)
567
+
568
+ elif st.session_state.analiz_durumu == "DusukGuven":
569
+ st.warning("Veri eşleşmesi bulunamadı. Lütfen daha detaylı açıklayın.")
570
+ if st.button("TEKRAR DENE"):
571
+ st.session_state.analiz_durumu = "Giris"