Jedi09 commited on
Commit
cd6b204
·
verified ·
1 Parent(s): bf646a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +312 -194
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  Danışman-Danışan Transkripsiyon Sistemi
3
  Speaker diarization + transcription pipeline.
4
- Zaman damgalı, konuşmacı ayrımlı çıktı + Psikolog Analiz Araçları.
5
  """
6
 
7
  import gradio as gr
@@ -40,34 +40,47 @@ print("✅ Whisper model yüklendi!")
40
  print("🔄 Diarization pipeline yükleniyor...")
41
  diarization_pipeline = get_diarization_pipeline()
42
 
43
- # ==================== EMOTION KEYWORDS (TURKISH) ====================
44
- EMOTION_KEYWORDS = {
45
- "Üzüntü": [
46
- "üzgün", "üzülüyorum", "ağlıyorum", "ağladım", "mutsuz", "kötü", "berbat",
47
- "acı", "acıyor", "kederli", "hüzünlü", "depresif", "bunalım", "çaresiz",
48
- "umutsuz", "yalnız", "yalnızlık", "terk", "kayıp", "ölüm", "öldü", "kaybettim"
49
- ],
50
- "Kaygı/Anksiyete": [
51
- "endişe", "endişeli", "kaygı", "kaygılı", "korku", "korkuyorum", "panik",
52
- "stres", "stresli", "gergin", "tedirgin", "huzursuz", "rahatsız", "bunaltı",
53
- "sıkıntı", "sıkıntılı", "belirsiz", "güvensiz", "tehlike", "tehdit"
54
  ],
55
- "Öfke": [
56
- "kızgın", "sinirli", "öfkeli", "öfke", "kızdım", "sinirlendim", "çıldırdım",
57
- "nefret", "kin", "intikam", "haksızlık", "adaletsiz", "ihanet", "hayal kırıklığı",
58
- "bıktım", "usandım", "yeter", "dayanamıyorum"
59
  ],
60
- "Mutluluk": [
61
- "mutlu", "sevinçli", "neşeli", "iyi", "güzel", "harika", "mükemmel",
62
- "seviyorum", "aşk", "heyecan", "heyecanlı", "umut", "umutlu", "başarı",
63
- "gurur", "gururlu", "şükür", "minnet", "rahat", "huzur"
64
  ],
65
- "Korku": [
66
- "korku", "korkuyorum", "korktum", "dehşet", "panik", "ürperti",
67
- "kaçmak", "saklanmak", "tehlike", "tehdit", "ölüm", "felaket"
68
  ]
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # Turkish stop words to exclude from word frequency
72
  TURKISH_STOP_WORDS = {
73
  "bir", "bu", "şu", "o", "ve", "ile", "için", "de", "da", "ki", "ne", "var", "yok",
@@ -96,176 +109,260 @@ def get_audio_duration(audio_path: str) -> float:
96
  return 0.0
97
 
98
 
99
- def analyze_emotions(text: str) -> dict:
100
- """Analyze emotions in text based on keyword matching."""
101
- text_lower = text.lower()
102
- words = re.findall(r'\b\w+\b', text_lower)
103
-
104
- emotion_counts = {}
105
- emotion_matched_words = {}
106
-
107
- for emotion, keywords in EMOTION_KEYWORDS.items():
108
- count = 0
109
- matched = []
110
- for keyword in keywords:
111
- # Count occurrences
112
- occurrences = text_lower.count(keyword)
113
- if occurrences > 0:
114
- count += occurrences
115
- matched.append(keyword)
116
- emotion_counts[emotion] = count
117
- emotion_matched_words[emotion] = matched
118
-
119
- total = sum(emotion_counts.values())
120
- emotion_percentages = {}
121
-
122
- if total > 0:
123
- for emotion, count in emotion_counts.items():
124
- emotion_percentages[emotion] = (count / total) * 100
125
- else:
126
- for emotion in emotion_counts:
127
- emotion_percentages[emotion] = 0
128
-
129
- return {
130
- "counts": emotion_counts,
131
- "percentages": emotion_percentages,
132
- "matched_words": emotion_matched_words,
133
- "total_emotional_words": total
134
- }
135
-
136
-
137
  def get_word_frequency(text: str, top_n: int = 15) -> list:
138
  """Get most frequent meaningful words."""
139
- # Clean and tokenize
140
  words = re.findall(r'\b[a-zA-ZçğıöşüÇĞİÖŞÜ]{3,}\b', text.lower())
141
-
142
- # Filter stop words
143
  meaningful_words = [w for w in words if w not in TURKISH_STOP_WORDS]
144
-
145
- # Count frequencies
146
  word_counts = Counter(meaningful_words)
147
-
148
  return word_counts.most_common(top_n)
149
 
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  def generate_psychology_report(transcript: str, client_speaker: str) -> str:
152
- """Generate a preliminary psychology report for the client."""
153
 
154
- # Extract client's text only
155
  lines = transcript.split('\n')
156
- client_text = []
157
  current_speaker = None
 
158
 
159
  for line in lines:
160
- # Check if this is a speaker line like "[00:00 → 00:05] Kişi 1:"
161
- speaker_match = re.search(r'\] (Kişi \d+):', line)
162
- if speaker_match:
163
- current_speaker = speaker_match.group(1)
 
 
 
 
 
 
 
 
 
 
 
 
164
  elif current_speaker == client_speaker and line.strip():
165
- # This is the client's text
166
- if not line.startswith('[') and not line.startswith('═') and not line.startswith('─') and not line.startswith('📊'):
167
- client_text.append(line.strip())
168
-
169
- client_full_text = ' '.join(client_text)
170
-
171
- if not client_full_text:
172
- return "⚠️ Seçilen kişiye ait metin bulunamadı."
173
-
174
- # Analyze emotions
175
- emotion_analysis = analyze_emotions(client_full_text)
176
 
177
- # Get word frequency
178
- word_freq = get_word_frequency(client_full_text)
179
 
180
- # Calculate speaking stats
181
- word_count = len(client_full_text.split())
182
- sentence_count = len(re.findall(r'[.!?]+', client_full_text)) or 1
183
- avg_sentence_length = word_count / sentence_count
184
-
185
- # Find dominant emotion
186
- dominant_emotion = max(emotion_analysis['percentages'], key=emotion_analysis['percentages'].get)
187
- dominant_percentage = emotion_analysis['percentages'][dominant_emotion]
188
-
189
- # Build report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  report = f"""
191
- ═══════════════════════════════════════════════════
192
- 🧠 PSİKOLOJİK ÖN RAPOR - {client_speaker}
193
- ═══════════════════════════════════════════════════
 
 
 
194
 
195
- 📊 GENEL İSTATİSTİKLER
196
- ───────────────────────────────────
197
- • Toplam kelime sayısı: {word_count}
198
- Cümle sayısı: {sentence_count}
199
- Ortalama cümle uzunluğu: {avg_sentence_length:.1f} kelime
200
- Duygusal ifade sayısı: {emotion_analysis['total_emotional_words']}
 
 
 
201
 
202
- 🎭 DUYGU ANALİZİ
203
- ───────────────────────────────────
 
 
 
 
 
 
 
 
204
  """
205
-
206
- # Sort emotions by percentage
207
- sorted_emotions = sorted(emotion_analysis['percentages'].items(), key=lambda x: x[1], reverse=True)
208
-
209
- for emotion, percentage in sorted_emotions:
210
- if percentage > 0:
211
- bar_length = int(percentage / 5) # Scale to max 20 chars
212
  bar = "█" * bar_length + "░" * (20 - bar_length)
213
- matched = ", ".join(emotion_analysis['matched_words'][emotion][:5])
214
- report += f"• {emotion}: {bar} {percentage:.1f}%\n"
215
- if matched:
216
- report += f" └─ Anahtar kelimeler: {matched}\n"
217
-
218
- if emotion_analysis['total_emotional_words'] > 0:
219
- report += f"\n🔍 Baskın Duygu: {dominant_emotion} ({dominant_percentage:.1f}%)\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  else:
221
- report += "\n🔍 Belirgin duygusal ifade tespit edilemedi.\n"
222
-
223
- report += """
224
- 📝 EN SIK KULLANILAN KELİMELER
225
- ───────────────────────────────────
 
226
  """
227
-
228
  for i, (word, count) in enumerate(word_freq[:10], 1):
229
  report += f"{i:2}. {word}: {count} kez\n"
230
-
231
- # Add interpretation notes
232
- report += """
233
- 💡 YORUMLAMA NOTLARI
234
- ───────────────────────────────────
235
  """
 
 
236
 
237
- if dominant_percentage > 40 and emotion_analysis['total_emotional_words'] > 3:
238
- if dominant_emotion == "Üzüntü":
239
- report += " Danışan belirgin düzeyde üzüntü/depresif belirtiler göstermektedir.\n"
240
- report += "• Kayıp, yalnızlık veya değersizlik temaları değerlendirilmelidir.\n"
241
- elif dominant_emotion == "Kaygı/Anksiyete":
242
- report += "• Danışan kaygı ve endişe belirtileri sergilemektedir.\n"
243
- report += "• Belirsizlik toleransı ve güvenlik ihtiyacı değerlendirilmelidir.\n"
244
- elif dominant_emotion == "Öfke":
245
- report += "• Danışan öfke ve kızgınlık ifadeleri kullanmaktadır.\n"
246
- report += "• Hayal kırıklığı kaynakları ve sınır ihlalleri değerlendirilmelidir.\n"
247
- elif dominant_emotion == "Korku":
248
- report += "• Danışan korku ve tehdit algısı belirtileri göstermektedir.\n"
249
- report += "• Güvenlik duygusu ve travma geçmişi değerlendirilmelidir.\n"
250
- elif dominant_emotion == "Mutluluk":
251
- report += "• Danışan olumlu duygular ifade etmektedir.\n"
252
- report += "��� Kaynak ve sürdürücü faktörler değerlendirilmelidir.\n"
253
- else:
254
- report += "• Belirgin bir duygusal örüntü tespit edilemedi.\n"
255
- report += "• Daha uzun görüşme örnekleri daha güvenilir analiz sağlayabilir.\n"
256
-
257
- if avg_sentence_length > 15:
258
- report += "• Uzun cümleler: Detaylı anlatım eğilimi veya düşünce karmaşası olabilir.\n"
259
- elif avg_sentence_length < 5:
260
- report += "• Kısa cümleler: Ketum davranış veya iletişim güçlüğü olabilir.\n"
261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  report += """
263
- ───────────────────────────────────
264
- ⚠️ NOT: Bu analiz otomatik olarak oluşturulmuştur ve
265
- profesyonel klinik değerlendirmenin yerini alamaz.
266
- ═══════════════════════════════════════════════════
 
 
 
 
 
 
 
267
  """
268
-
269
  return report
270
 
271
 
@@ -398,17 +495,17 @@ def process_audio(audio_path):
398
  def analyze_client(transcript: str, client_selection: str):
399
  """Analyze the selected client's speech."""
400
  if not transcript or transcript.startswith("⚠️") or transcript.startswith("❌"):
401
- return "⚠️ Önce bir transkript oluşturun."
402
 
403
  if not client_selection:
404
- return "⚠️ Lütfen danışanı seçin."
405
 
406
  report = generate_psychology_report(transcript, client_selection)
407
 
408
  # Create downloadable report
409
  report_file = tempfile.NamedTemporaryFile(
410
  mode='w',
411
- suffix='_rapor.txt',
412
  delete=False,
413
  encoding='utf-8'
414
  )
@@ -419,21 +516,21 @@ def analyze_client(transcript: str, client_selection: str):
419
 
420
 
421
  # ==================== GRADIO UI ====================
422
- with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
423
 
424
  gr.HTML("""
425
  <style>
426
  footer { display: none !important; }
427
- .gradio-container { max-width: 1000px !important; margin: auto !important; }
428
  </style>
429
  <div style="text-align: center; padding: 40px 20px 30px;
430
  background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%);
431
  border-radius: 20px; margin-bottom: 24px; color: white;">
432
  <h1 style="font-size: 2.2rem; font-weight: 700; margin: 0 0 8px 0;">
433
- 🎙️ Görüşme Transkripsiyon & Analiz
434
  </h1>
435
  <p style="font-size: 1rem; opacity: 0.95; margin: 0;">
436
- Danışman-Danışan görüşmelerini yazıya dökün ve psikolojik analiz yapın
437
  </p>
438
  </div>
439
  """)
@@ -464,9 +561,9 @@ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
464
  <p style="margin: 0; color: #0369a1; font-size: 14px;">
465
  ℹ️ <strong>Nasıl Çalışır:</strong><br>
466
  1. Ses dosyasını yükleyin<br>
467
- 2. AI konuşmacıları ayırır<br>
468
  3. Transkript oluşturulur<br>
469
- 4. Analiz sekmesinde danışanı seçip analiz yapın
470
  </p>
471
  </div>
472
  """)
@@ -478,7 +575,7 @@ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
478
  output_text = gr.Textbox(
479
  label="",
480
  placeholder="Transkript burada görünecek...",
481
- lines=20,
482
  interactive=False
483
  )
484
 
@@ -486,16 +583,16 @@ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
486
  label="📥 Transkripti İndir (.txt)"
487
  )
488
 
489
- # Tab 2: Psikolojik Analiz
490
- with gr.TabItem("🧠 Psikolojik Analiz"):
491
  gr.HTML("""
492
  <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
493
  border: 1px solid #f59e0b; border-radius: 12px;
494
  padding: 16px 20px; margin-bottom: 16px;">
495
  <p style="margin: 0; color: #92400e; font-size: 14px;">
496
- 🧠 <strong>Psikolojik Ön Rapor:</strong> Bu analiz danışanın konuşmasındaki
497
- duygusal ifadeleri, en sık kullandığı kelimeleri ve genel konuşma kalıplarını
498
- değerlendirir. Profesyonel klinik değerlendirmenin yerini almaz.
499
  </p>
500
  </div>
501
  """)
@@ -510,16 +607,33 @@ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
510
  )
511
 
512
  analyze_btn = gr.Button(
513
- "🔍 Analiz Et",
514
  variant="primary",
515
  size="lg"
516
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  with gr.Column(scale=2):
519
  analysis_output = gr.Textbox(
520
- label="📊 Analiz Raporu",
521
  placeholder="Analiz raporu burada görünecek...",
522
- lines=25,
523
  interactive=False
524
  )
525
 
@@ -529,26 +643,30 @@ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
529
 
530
  # Features
531
  gr.HTML("""
532
- <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-top: 24px;">
533
- <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
534
- <div style="font-size: 24px; margin-bottom: 6px;">🎭</div>
535
  <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Konuşmacı Ayrımı</div>
536
  </div>
537
- <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
538
- <div style="font-size: 24px; margin-bottom: 6px;">⏱️</div>
539
  <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Zaman Damgası</div>
540
  </div>
541
- <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
542
- <div style="font-size: 24px; margin-bottom: 6px;">🔒</div>
543
  <div style="font-size: 11px; color: #6b7280; font-weight: 500;">%100 Local</div>
544
  </div>
545
- <div style="text-align: center; padding: 16px; background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%); border-radius: 12px;">
546
- <div style="font-size: 24px; margin-bottom: 6px;">🧠</div>
547
- <div style="font-size: 11px; color: #5b21b6; font-weight: 500;">Duygu Analizi</div>
 
 
 
 
548
  </div>
549
- <div style="text-align: center; padding: 16px; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 12px;">
550
- <div style="font-size: 24px; margin-bottom: 6px;">📊</div>
551
- <div style="font-size: 11px; color: #92400e; font-weight: 500;">Ön Rapor</div>
552
  </div>
553
  </div>
554
  """)
@@ -565,7 +683,7 @@ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
565
 
566
  gr.HTML("""
567
  <div style="text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px;">
568
- <p>Powered by Faster-Whisper & Pyannote-Audio • Psikolog Asistanı</p>
569
  </div>
570
  """)
571
 
 
1
  """
2
  Danışman-Danışan Transkripsiyon Sistemi
3
  Speaker diarization + transcription pipeline.
4
+ Zaman damgalı, konuşmacı ayrımlı çıktı + Klinik Analiz Araçları.
5
  """
6
 
7
  import gradio as gr
 
40
  print("🔄 Diarization pipeline yükleniyor...")
41
  diarization_pipeline = get_diarization_pipeline()
42
 
43
+
44
+ # ==================== KLİNİK ANALİZ MATRİSLERİ ====================
45
+
46
+ # 1. DUYGU DURUM GÖSTERGELERİ (AFFECTIVE INDICATORS)
47
+ CLINICAL_INDICATORS = {
48
+ "Disforik/Depresif": [
49
+ "üzgün", "mutsuz", "çaresiz", "umutsuz", "bıktım", "karanlık", "boşluk",
50
+ "değersiz", "suçlu", "yorgun", "tükendim", "ölüm", "intihar", "bitse",
51
+ "hiçbir şey", "zevk almıyorum", "ağır", "çöküş", "ağlıyorum", "kayıp"
 
 
52
  ],
53
+ "Anksiyöz/Kaygılı": [
54
+ "korkuyorum", "endişe", "panik", "ne olacak", "ya olursa", "gerginim",
55
+ "kalbim", "nefes", "titreme", "kontrolü kaybetme", "tehlike", "felaket",
56
+ "huzursuz", "yerimde duramıyorum", "kafayı yiyeceğim", "stres"
57
  ],
58
+ "Öfke/Hostilite": [
59
+ "nefret", "kızgınım", "aptal", "haksızlık", "intikam", "dayanamıyorum",
60
+ "bağır", "vur", "kır", "sinir", "öfke", "düşman", "zarar", "sinirlendim"
 
61
  ],
62
+ "Ötimik/Pozitif": [
63
+ "iyi", "güzel", "mutlu", "başardım", "umutlu", "sakin", "huzurlu",
64
+ "keyifli", "planlıyorum", "seviyorum", "şükür", "rahat"
65
  ]
66
  }
67
 
68
+ # 2. BİLİŞSEL ÇARPITMALAR (COGNITIVE DISTORTIONS)
69
+ COGNITIVE_DISTORTIONS = {
70
+ "Aşırı Genelleme": ["her zaman", "asla", "hiçbir zaman", "herkes", "hiç kimse", "hep"],
71
+ "Zorunluluk (-meli/-malı)": ["yapmalıyım", "zorundayım", "mecburum", "etmeli", "olmalı", "gerekir"],
72
+ "Felaketleştirme": ["mahvoldu", "bitti", "felaket", "korkunç", "dayanılmaz", "berbat"]
73
+ }
74
+
75
+ # 3. ZAMAN ODAĞI (TEMPORAL FOCUS)
76
+ TIME_MARKERS = {
77
+ "Geçmiş": ["yaptım", "gitti", "oldu", "vardı", "eskiden", "keşke", "geçmişte"],
78
+ "Gelecek": ["olacak", "yapacağım", "gidecek", "gelecek", "belki", "acaba", "planlıyorum"]
79
+ }
80
+
81
+ # 4. TEREDDÜT VE DOLGU (HESITATION MARKERS)
82
+ FILLERS = ["şey", "yani", "ııı", "eee", "işte", "falan", "filan", "hani"]
83
+
84
  # Turkish stop words to exclude from word frequency
85
  TURKISH_STOP_WORDS = {
86
  "bir", "bu", "şu", "o", "ve", "ile", "için", "de", "da", "ki", "ne", "var", "yok",
 
109
  return 0.0
110
 
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  def get_word_frequency(text: str, top_n: int = 15) -> list:
113
  """Get most frequent meaningful words."""
 
114
  words = re.findall(r'\b[a-zA-ZçğıöşüÇĞİÖŞÜ]{3,}\b', text.lower())
 
 
115
  meaningful_words = [w for w in words if w not in TURKISH_STOP_WORDS]
 
 
116
  word_counts = Counter(meaningful_words)
 
117
  return word_counts.most_common(top_n)
118
 
119
 
120
+ def analyze_clinical_features(text: str, duration_seconds: float) -> dict:
121
+ """Metni klinik parametrelere göre analiz eder."""
122
+ text_lower = text.lower()
123
+ words = re.findall(r'\b\w+\b', text_lower)
124
+ word_count = len(words)
125
+
126
+ # 1. Konuşma Hızı (Words Per Minute)
127
+ speech_rate = (word_count / duration_seconds) * 60 if duration_seconds > 0 else 0
128
+
129
+ # 2. Klinik Gösterge Taraması
130
+ scores = {category: 0 for category in CLINICAL_INDICATORS}
131
+ matched_details = {category: [] for category in CLINICAL_INDICATORS}
132
+
133
+ for category, keywords in CLINICAL_INDICATORS.items():
134
+ for word in keywords:
135
+ count = text_lower.count(word)
136
+ if count > 0:
137
+ scores[category] += count
138
+ if word not in matched_details[category]:
139
+ matched_details[category].append(word)
140
+
141
+ # 3. Bilişsel Çarpıtmalar
142
+ distortions = {cat: 0 for cat in COGNITIVE_DISTORTIONS}
143
+ distortion_matches = {cat: [] for cat in COGNITIVE_DISTORTIONS}
144
+ for category, keywords in COGNITIVE_DISTORTIONS.items():
145
+ for word in keywords:
146
+ count = text_lower.count(word)
147
+ if count > 0:
148
+ distortions[category] += count
149
+ if word not in distortion_matches[category]:
150
+ distortion_matches[category].append(word)
151
+
152
+ # 4. Zaman Odağı
153
+ time_focus = {"Geçmiş": 0, "Gelecek": 0}
154
+ for category, keywords in TIME_MARKERS.items():
155
+ for word in keywords:
156
+ time_focus[category] += text_lower.count(word)
157
+
158
+ # 5. Benlik Odağı (Self-Reference Ratio)
159
+ self_refs = text_lower.count("ben") + text_lower.count("benim") + text_lower.count("bana") + text_lower.count("beni")
160
+ self_ratio = (self_refs / word_count) * 100 if word_count > 0 else 0
161
+
162
+ # 6. Dolgu kelime sayısı
163
+ filler_count = sum(text_lower.count(f) for f in FILLERS)
164
+
165
+ return {
166
+ "word_count": word_count,
167
+ "speech_rate": speech_rate,
168
+ "clinical_scores": scores,
169
+ "clinical_matches": matched_details,
170
+ "distortions": distortions,
171
+ "distortion_matches": distortion_matches,
172
+ "time_focus": time_focus,
173
+ "self_ratio": self_ratio,
174
+ "filler_count": filler_count
175
+ }
176
+
177
+
178
  def generate_psychology_report(transcript: str, client_speaker: str) -> str:
179
+ """Danışan için klinik formatta ön değerlendirme raporu oluşturur."""
180
 
181
+ # --- Danışan Verisini Ayıkla ve Süre Hesapla ---
182
  lines = transcript.split('\n')
183
+ client_text_parts = []
184
  current_speaker = None
185
+ total_client_duration = 0.0
186
 
187
  for line in lines:
188
+ # Örnek satır: "[00:05 → 00:12] Kişi 2:"
189
+ timestamp_match = re.search(r'\[(\d{2}:\d{2}) → (\d{2}:\d{2})\] (Kişi \d+):', line)
190
+
191
+ if timestamp_match:
192
+ start_str, end_str, spk = timestamp_match.groups()
193
+ current_speaker = spk
194
+
195
+ # Süreyi hesapla
196
+ def to_sec(t):
197
+ m, s = map(int, t.split(':'))
198
+ return m * 60 + s
199
+
200
+ if spk == client_speaker:
201
+ seg_dur = to_sec(end_str) - to_sec(start_str)
202
+ total_client_duration += seg_dur
203
+
204
  elif current_speaker == client_speaker and line.strip():
205
+ # Metni al (çizgiler ve meta veriler hariç)
206
+ clean_line = line.strip()
207
+ if not any(clean_line.startswith(c) for c in ['[', '═', '─', '📊', '•']):
208
+ client_text_parts.append(clean_line)
 
 
 
 
 
 
 
209
 
210
+ full_text = ' '.join(client_text_parts)
 
211
 
212
+ if not full_text:
213
+ return "⚠️ HATA: Seçilen kişiye ait yeterli veri bulunamadı. Lütfen doğru konuşmacıyı seçtiğinizden emin olun."
214
+
215
+ # --- Analizi Çalıştır ---
216
+ if total_client_duration == 0:
217
+ total_client_duration = len(full_text.split()) / 2.5 # Tahmini süre
218
+
219
+ analysis = analyze_clinical_features(full_text, total_client_duration)
220
+
221
+ # Kelime frekansı
222
+ word_freq = get_word_frequency(full_text)
223
+
224
+ # --- RAPOR YAZIMI (KLİNİK FORMAT) ---
225
+
226
+ # 1. Konuşma Hızı Yorumu
227
+ wpm = analysis['speech_rate']
228
+ speech_comment = "Olağan sınırlarda (100-150 wpm)"
229
+ if wpm < 90:
230
+ speech_comment = "⚠️ Bradilali (Yavaşlamış konuşma) - Depresif duygulanım veya yüksek bilişsel yük lehine."
231
+ elif wpm > 160:
232
+ speech_comment = "⚠️ Taşilali (Basınçlı konuşma) - Anksiyete veya manik dönem lehine."
233
+
234
+ # 2. Baskın Duygu Yorumu
235
+ scores = analysis['clinical_scores']
236
+ dominant_mood = max(scores, key=scores.get)
237
+ if scores[dominant_mood] == 0:
238
+ dominant_mood = "Nötr/Belirsiz"
239
+
240
  report = f"""
241
+ ══════════════════════════════════════════════════════════════
242
+ 🏥 KLİNİK GÖRÜŞME ÖN DEĞERLENDİRME RAPORU
243
+ ══════════════════════════════════════════════════════════════
244
+ 📅 TARİH: {time.strftime("%d.%m.%Y - %H:%M")}
245
+ 👤 DANIŞAN KODU: {client_speaker}
246
+ ⏱️ TOPLAM KONUŞMA SÜRESİ: {format_timestamp(total_client_duration)}
247
 
248
+ ══════════════════════════════════════════════════════════════
249
+ I. GENEL GÖRÜNÜM VE KONUŞMA DAVRANIŞI
250
+ ══════════════════════════════════════════════════════════════
251
+ Toplam Kelime Sayısı: {analysis['word_count']}
252
+ Konuşma Hızı: {wpm:.0f} kelime/dakika
253
+ └─ {speech_comment}
254
+ • Dolgu Kelime Kullanımı: {analysis['filler_count']} adet (şey, yani, işte vb.)
255
+ • Benlik Odağı: %{analysis['self_ratio']:.1f}
256
+ └─ {"⚠️ Yüksek - içe dönüklük veya ruminasyon riski" if analysis['self_ratio'] > 4 else "Normal sınırlarda"}
257
 
258
+ ══════════════════════════════════════════════════════════════
259
+ II. DUYGUDURUM VE DUYGULANIM (MOOD & AFFECT)
260
+ ══════════════════════════════════════════════════════════════
261
+ Yapay zeka dil örüntü analizine göre tespit edilen baskın duygulanım:
262
+
263
+ ╔══════════════════════════════════╗
264
+ ║ 👉 {dominant_mood.upper():^26} ║
265
+ ╚══════════════════════════════════╝
266
+
267
+ 📊 Duygu Dağılımı:
268
  """
269
+ # Duygu barları
270
+ total_emotion = sum(scores.values()) or 1
271
+ for category, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
272
+ if score > 0:
273
+ percentage = (score / total_emotion) * 100
274
+ bar_length = int(percentage / 5)
 
275
  bar = "█" * bar_length + "░" * (20 - bar_length)
276
+ words = ", ".join(analysis['clinical_matches'][category][:3])
277
+ report += f"• {category}: {bar} {percentage:.0f}%\n"
278
+ report += f" └─ Anahtar: {words}\n"
279
+
280
+ report += f"""
281
+ ══════════════════════════════════════════════════════════════
282
+ III. DÜŞÜNCE İÇERİĞİ VE BİLİŞSEL SÜREÇLER
283
+ ══════════════════════════════════════════════════════════════
284
+
285
+ A. BİLİŞSEL ÇARPITMALAR (Cognitive Distortions):
286
+ """
287
+ has_distortion = False
288
+ for dist, count in analysis['distortions'].items():
289
+ if count > 0:
290
+ has_distortion = True
291
+ words = ", ".join(analysis['distortion_matches'][dist][:3])
292
+ report += f"• {dist}: {count} kez\n"
293
+ report += f" └─ Örnek: \"{words}\"\n"
294
+ if not has_distortion:
295
+ report += "• ✓ Belirgin bir bilişsel çarpıtma dili tespit edilmedi.\n"
296
+
297
+ report += "\nB. ZAMAN YÖNELİMİ (Temporal Orientation):\n"
298
+ past = analysis['time_focus']['Geçmiş']
299
+ future = analysis['time_focus']['Gelecek']
300
+
301
+ if past > future * 1.5:
302
+ report += f"• ⚠️ Geçmiş Odaklı ({past} ifade vs {future} gelecek)\n"
303
+ report += " └─ Pişmanlık, yas veya depresif ruminasyon eğilimi gösterebilir.\n"
304
+ elif future > past * 1.5:
305
+ report += f"• ⚠️ Gelecek Odaklı ({future} ifade vs {past} geçmiş)\n"
306
+ report += " └─ Beklenti anksiyetesi eğilimi gösterebilir.\n"
307
  else:
308
+ report += f" Dengeli zaman yönelimi (Geçmiş: {past}, Gelecek: {future})\n"
309
+
310
+ report += f"""
311
+ ══════════════════════════════════════════════════════════════
312
+ IV. SIK KULLANILAN KELİMELER
313
+ ══════════════════════════════════════════════════════════════
314
  """
 
315
  for i, (word, count) in enumerate(word_freq[:10], 1):
316
  report += f"{i:2}. {word}: {count} kez\n"
317
+
318
+ report += f"""
319
+ ══════════════════════════════════════════════════════════════
320
+ V. KLİNİK İZLENİM VE ÖNERİLER
321
+ ══════════════════════════════════════════════════════════════
322
  """
323
+ # Dinamik Sonuç Çıkarımı
324
+ observations = []
325
 
326
+ if dominant_mood == "Disforik/Depresif" and past > future:
327
+ observations.append("🔴 DİKKAT: Depresif duygulanım ve geçmişe saplanma (ruminasyon) örüntüsü gözlenmiştir.")
328
+ observations.append(" └─ İntihar riski değerlendirmesi önerilir.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
+ if dominant_mood == "Anksiyöz/Kaygılı" and wpm > 140:
331
+ observations.append("🔴 DİKKAT: Yüksek anksiyete belirtileri (hızlı konuşma, kaygı ifadeleri).")
332
+ observations.append(" └─ Panik bozukluk veya yaygın anksiyete açısından değerlendirilmeli.")
333
+
334
+ if analysis['distortions']['Zorunluluk (-meli/-malı)'] >= 2:
335
+ observations.append("🟡 Terapötik Hedef: Mükemmeliyetçi şemalar ve '-meli/-malı' kuralları.")
336
+ observations.append(" └─ Bilişsel yeniden yapılandırma önerilir.")
337
+
338
+ if analysis['distortions']['Aşırı Genelleme'] >= 2:
339
+ observations.append("🟡 Terapötik Hedef: Aşırı genelleme eğilimi tespit edildi.")
340
+ observations.append(" └─ 'Her zaman', 'asla' gibi mutlaklaştırmalar sorgulanmalı.")
341
+
342
+ if analysis['self_ratio'] > 5:
343
+ observations.append("🟡 Yüksek benlik odağı: Ruminatif düşünce örüntüsü olabilir.")
344
+ observations.append(" └─ Dikkat odağını genişletme egzersizleri düşünülebilir.")
345
+
346
+ if not observations:
347
+ observations.append("🟢 Acil müdahale gerektiren belirgin bir risk örüntüsü (dilsel düzeyde) saptanmamıştır.")
348
+ observations.append(" └─ Rutin takip önerilir.")
349
+
350
+ for obs in observations:
351
+ report += f"{obs}\n"
352
+
353
  report += """
354
+ ══════════════════════════════════════════════════════════════
355
+ ⚠️ YASAL UYARI
356
+ ══════════════════════════════════════════════════════════════
357
+ Bu rapor Yapay Zeka (AI) algoritmaları tarafından sadece dilsel
358
+ verilere dayanarak otomatik olarak oluşturulmuştur.
359
+
360
+ • Tanı veya teşhis yerine geçmez.
361
+ • Klinik karar vermede tek başına kullanılamaz.
362
+ • Sadece klinisyene yardımcı veri olarak sunulmuştur.
363
+ • Profesyonel değerlendirme mutlaka gereklidir.
364
+ ══════════════════════════════════════════════════════════════
365
  """
 
366
  return report
367
 
368
 
 
495
  def analyze_client(transcript: str, client_selection: str):
496
  """Analyze the selected client's speech."""
497
  if not transcript or transcript.startswith("⚠️") or transcript.startswith("❌"):
498
+ return "⚠️ Önce bir transkript oluşturun.", None
499
 
500
  if not client_selection:
501
+ return "⚠️ Lütfen danışanı seçin.", None
502
 
503
  report = generate_psychology_report(transcript, client_selection)
504
 
505
  # Create downloadable report
506
  report_file = tempfile.NamedTemporaryFile(
507
  mode='w',
508
+ suffix='_klinik_rapor.txt',
509
  delete=False,
510
  encoding='utf-8'
511
  )
 
516
 
517
 
518
  # ==================== GRADIO UI ====================
519
+ with gr.Blocks(title="Klinik Görüşme Transkripsiyon & Analiz") as demo:
520
 
521
  gr.HTML("""
522
  <style>
523
  footer { display: none !important; }
524
+ .gradio-container { max-width: 1100px !important; margin: auto !important; }
525
  </style>
526
  <div style="text-align: center; padding: 40px 20px 30px;
527
  background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%);
528
  border-radius: 20px; margin-bottom: 24px; color: white;">
529
  <h1 style="font-size: 2.2rem; font-weight: 700; margin: 0 0 8px 0;">
530
+ 🏥 Klinik Görüşme Transkripsiyon & Analiz
531
  </h1>
532
  <p style="font-size: 1rem; opacity: 0.95; margin: 0;">
533
+ Danışman-Danışan görüşmelerini yazıya dökün ve AI destekli klinik ön değerlendirme alın
534
  </p>
535
  </div>
536
  """)
 
561
  <p style="margin: 0; color: #0369a1; font-size: 14px;">
562
  ℹ️ <strong>Nasıl Çalışır:</strong><br>
563
  1. Ses dosyasını yükleyin<br>
564
+ 2. AI konuşmacıları otomatik ayırır<br>
565
  3. Transkript oluşturulur<br>
566
+ 4. Klinik Analiz sekmesinde danışanı seçip rapor alın
567
  </p>
568
  </div>
569
  """)
 
575
  output_text = gr.Textbox(
576
  label="",
577
  placeholder="Transkript burada görünecek...",
578
+ lines=25,
579
  interactive=False
580
  )
581
 
 
583
  label="📥 Transkripti İndir (.txt)"
584
  )
585
 
586
+ # Tab 2: Klinik Analiz
587
+ with gr.TabItem("🏥 Klinik Analiz"):
588
  gr.HTML("""
589
  <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
590
  border: 1px solid #f59e0b; border-radius: 12px;
591
  padding: 16px 20px; margin-bottom: 16px;">
592
  <p style="margin: 0; color: #92400e; font-size: 14px;">
593
+ 🏥 <strong>Klinik Ön Değerlendirme:</strong> Bu modül danışanın konuşmasını
594
+ duygudurum, bilişsel çarpıtmalar, zaman yönelimi ve konuşma hızı açısından
595
+ analiz eder. Profesyonel klinik değerlendirmenin yerini almaz.
596
  </p>
597
  </div>
598
  """)
 
607
  )
608
 
609
  analyze_btn = gr.Button(
610
+ "🔍 Klinik Analiz Başlat",
611
  variant="primary",
612
  size="lg"
613
  )
614
+
615
+ gr.HTML("""
616
+ <div style="background: #f9fafb; border-radius: 12px;
617
+ padding: 16px 20px; margin-top: 16px;">
618
+ <p style="margin: 0 0 8px 0; color: #374151; font-size: 13px; font-weight: 600;">
619
+ 📊 Analiz Kapsamı:
620
+ </p>
621
+ <ul style="margin: 0; padding-left: 20px; color: #6b7280; font-size: 12px;">
622
+ <li>Duygudurum göstergeleri</li>
623
+ <li>Bilişsel çarpıtmalar</li>
624
+ <li>Zaman yönelimi</li>
625
+ <li>Konuşma hızı</li>
626
+ <li>Benlik odağı</li>
627
+ <li>Kelime frekansı</li>
628
+ </ul>
629
+ </div>
630
+ """)
631
 
632
  with gr.Column(scale=2):
633
  analysis_output = gr.Textbox(
634
+ label="📋 Klinik Ön Değerlendirme Raporu",
635
  placeholder="Analiz raporu burada görünecek...",
636
+ lines=30,
637
  interactive=False
638
  )
639
 
 
643
 
644
  # Features
645
  gr.HTML("""
646
+ <div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; margin-top: 24px;">
647
+ <div style="text-align: center; padding: 14px; background: #f9fafb; border-radius: 12px;">
648
+ <div style="font-size: 22px; margin-bottom: 4px;">🎭</div>
649
  <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Konuşmacı Ayrımı</div>
650
  </div>
651
+ <div style="text-align: center; padding: 14px; background: #f9fafb; border-radius: 12px;">
652
+ <div style="font-size: 22px; margin-bottom: 4px;">⏱️</div>
653
  <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Zaman Damgası</div>
654
  </div>
655
+ <div style="text-align: center; padding: 14px; background: #f9fafb; border-radius: 12px;">
656
+ <div style="font-size: 22px; margin-bottom: 4px;">🔒</div>
657
  <div style="font-size: 11px; color: #6b7280; font-weight: 500;">%100 Local</div>
658
  </div>
659
+ <div style="text-align: center; padding: 14px; background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%); border-radius: 12px;">
660
+ <div style="font-size: 22px; margin-bottom: 4px;">🧠</div>
661
+ <div style="font-size: 11px; color: #5b21b6; font-weight: 500;">Duygudurum</div>
662
+ </div>
663
+ <div style="text-align: center; padding: 14px; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 12px;">
664
+ <div style="font-size: 22px; margin-bottom: 4px;">💭</div>
665
+ <div style="font-size: 11px; color: #92400e; font-weight: 500;">Bilişsel Analiz</div>
666
  </div>
667
+ <div style="text-align: center; padding: 14px; background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); border-radius: 12px;">
668
+ <div style="font-size: 22px; margin-bottom: 4px;">📋</div>
669
+ <div style="font-size: 11px; color: #065f46; font-weight: 500;">Klinik Rapor</div>
670
  </div>
671
  </div>
672
  """)
 
683
 
684
  gr.HTML("""
685
  <div style="text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px;">
686
+ <p>Powered by Faster-Whisper & Pyannote-Audio • Klinisyen Asistanı v2.0</p>
687
  </div>
688
  """)
689