Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
# =============================================================================
|
| 2 |
-
# TANI KOYAI -
|
| 3 |
-
# -
|
| 4 |
-
# -
|
| 5 |
-
# -
|
|
|
|
| 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 = "
|
| 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
|
| 34 |
-
@import url('https://fonts.googleapis.com/css2?family=
|
| 35 |
-
@import url('https://fonts.googleapis.com/css2?family=
|
| 36 |
|
| 37 |
-
|
| 38 |
-
font-family: '
|
| 39 |
-
color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
}}
|
| 41 |
|
| 42 |
/* 2. ARKA PLAN */
|
| 43 |
.stApp {{
|
| 44 |
-
background
|
| 45 |
-
background-image: radial-gradient(at 50% 50%, #1a2634 0%, #0F151C 100%);
|
| 46 |
background-attachment: fixed;
|
| 47 |
}}
|
| 48 |
|
| 49 |
-
/* --- SIDEBAR
|
| 50 |
-
[data-testid="stSidebar"]
|
| 51 |
-
color:
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
font-weight: 800 !important;
|
| 55 |
}}
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
border-radius: 8px !important;
|
| 66 |
-
font-weight: 600 !important;
|
| 67 |
}}
|
| 68 |
-
.
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
background
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
}}
|
| 74 |
|
| 75 |
-
|
| 76 |
-
.
|
| 77 |
-
|
| 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 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
text-shadow: 0 0 5px rgba(0, 191, 255, 0.3); font-size: 1rem !important;
|
| 87 |
}}
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
box-shadow: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}}
|
| 94 |
-
.
|
| 95 |
-
|
| 96 |
-
|
| 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 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
}}
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 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 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
}}
|
| 118 |
-
|
| 119 |
-
|
| 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 |
-
/*
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
background
|
| 147 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
</style>
|
| 159 |
-
<div class="version-tag">
|
| 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 |
-
|
| 168 |
-
return json.loads(content) if content else []
|
| 169 |
except: return []
|
| 170 |
return []
|
| 171 |
|
| 172 |
-
def save_complaints(
|
| 173 |
try:
|
| 174 |
with open(COMPLAINTS_FILE, 'w', encoding='utf-8') as f:
|
| 175 |
-
json.dump(
|
| 176 |
except: pass
|
| 177 |
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
SEVK_TABLOSU = {
|
| 192 |
-
"Nöroloji": "Nörolog
|
| 193 |
-
"
|
| 194 |
-
"
|
| 195 |
-
"
|
| 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
|
| 226 |
-
|
| 227 |
-
|
| 228 |
for item in VERI_BANKASI:
|
| 229 |
if 'cinsiyet_kist' not in item: item['cinsiyet_kist'] = 'genel'
|
| 230 |
-
if '
|
| 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 |
-
|
| 248 |
-
|
| 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 |
-
|
| 259 |
for w in words:
|
| 260 |
if len(w) < 3: continue
|
| 261 |
-
|
| 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 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
}
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
if
|
| 285 |
-
|
| 286 |
-
|
| 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
|
| 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 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 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
|
| 314 |
|
| 315 |
# --- GRAFİKLER ---
|
| 316 |
-
C_BG = '#
|
| 317 |
-
C_TEXT = '#
|
| 318 |
|
| 319 |
def ciz_genel_bakis(top_results, sikayet):
|
| 320 |
-
fig = make_subplots(rows=1, cols=2, specs=[[{"type": "xy"}, {"type": "scene"}]], subplot_titles=("
|
| 321 |
-
fig.add_trace(go.Bar(x=[r['tani'] for r in top_results], y=[r['olasilik'] for r in top_results], marker_color=
|
| 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 |
-
|
| 325 |
-
|
| 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='#
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 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
|
| 355 |
@st.dialog(" ")
|
| 356 |
def show_about_dialog():
|
| 357 |
st.markdown("""
|
| 358 |
<div class="math-box">
|
| 359 |
-
<
|
| 360 |
-
|
| 361 |
-
<
|
| 362 |
-
<
|
| 363 |
-
<
|
| 364 |
-
|
| 365 |
-
<h4 style="color:#
|
| 366 |
-
<p style="color:#
|
| 367 |
-
<div
|
| 368 |
Sim(A, B) = cos(θ) =
|
| 369 |
-
<div
|
| 370 |
-
<span
|
| 371 |
-
<span
|
| 372 |
</div>
|
| 373 |
</div>
|
| 374 |
-
|
| 375 |
-
<
|
| 376 |
-
<
|
| 377 |
-
<div class="math-formula-box">
|
| 378 |
d(p, q) = √<span style="border-top:1px solid #000; padding-top:2px;">∑ (q<sub>i</sub> - p<sub>i</sub>)<sup>2</sup></span>
|
| 379 |
</div>
|
| 380 |
-
|
| 381 |
-
<
|
| 382 |
-
<
|
| 383 |
-
<
|
| 384 |
-
|
| 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:#
|
| 392 |
</p>
|
| 393 |
</div>
|
| 394 |
""", unsafe_allow_html=True)
|
| 395 |
|
| 396 |
-
# ---
|
| 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 |
-
|
| 415 |
-
|
| 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 |
-
|
| 502 |
-
|
| 503 |
st.markdown("""
|
| 504 |
-
<div
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
|
|
|
| 509 |
</div>
|
| 510 |
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
st.rerun()
|
| 519 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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> ∈ ℝ<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(θ) =
|
| 369 |
+
<div style="display:inline-block; vertical-align:middle; text-align:center;">
|
| 370 |
+
<span style="border-bottom:1px solid #000; display:block;">A · B</span>
|
| 371 |
+
<span>||A|| · ||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) = √<span style="border-top:1px solid #000; padding-top:2px;">∑ (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"
|