DocUA commited on
Commit
953a0b1
Β·
1 Parent(s): f3fbc65

feat: add conversation logging and statistics features with export options

Browse files
.gitignore CHANGED
@@ -91,6 +91,7 @@ demos/
91
  deployment/
92
  docs/
93
  scripts/
 
94
 
95
  # User/runtime profiles
96
  lifestyle_profile.json
 
91
  deployment/
92
  docs/
93
  scripts/
94
+ conversation_logs/
95
 
96
  # User/runtime profiles
97
  lifestyle_profile.json
CONVERSATION_LOGGING_FEATURES.md ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Нові Ρ„ΡƒΠ½ΠΊΡ†Ρ–Ρ— логування Ρ€ΠΎΠ·ΠΌΠΎΠ² Ρ‚Π° Π²Ρ–Π·ΡƒΠ°Π»ΡŒΠ½ΠΈΡ… Ρ–Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ–Π²
2
+
3
+ ## Огляд
4
+
5
+ Π”ΠΎΠ΄Π°Π½ΠΎ Π½ΠΎΠ²Ρ– Ρ„ΡƒΠ½ΠΊΡ†Ρ–Ρ— для покращСння ΠΊΠΎΡ€ΠΈΡΡ‚ΡƒΠ²Π°Ρ†ΡŒΠΊΠΎΠ³ΠΎ досвіду Ρ‚Π° Π°Π½Π°Π»Ρ–Π·Ρƒ Ρ€ΠΎΠ·ΠΌΠΎΠ²:
6
+
7
+ 1. **ΠšΠΎΠ»ΡŒΠΎΡ€ΠΎΠ²Ρ– Ρ–Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΈ класифікації** - ΠΏΠΎΠΊΠ°Π·ΡƒΡŽΡ‚ΡŒ Ρ€Ρ–Π²Π΅Π½ΡŒ Π΄ΡƒΡ…ΠΎΠ²Π½ΠΎΠ³ΠΎ дистрСсу
8
+ 2. **АвтоматичнС логування Ρ€ΠΎΠ·ΠΌΠΎΠ²** - Π·Π±Π΅Ρ€Ρ–Π³Π°Ρ” всі Ρ€ΠΎΠ·ΠΌΠΎΠ²ΠΈ Π² JSON Ρ‚Π° CSV Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…
9
+ 3. **Бтатистика Ρ€ΠΎΠ·ΠΌΠΎΠ² Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌΡƒ часі** - ΠΏΠΎΠΊΠ°Π·ΡƒΡ” Π°Π½Π°Π»Ρ–Ρ‚ΠΈΠΊΡƒ ΠΏΠΎΡ‚ΠΎΡ‡Π½ΠΎΡ— сСсії
10
+ 4. **Кнопки завантаТСння** - Π΄ΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ΡŒ Π·Π°Π²Π°Π½Ρ‚Π°ΠΆΠΈΡ‚ΠΈ Π»ΠΎΠ³ΠΈ Ρ€ΠΎΠ·ΠΌΠΎΠ²
11
+
12
+ ## ΠšΠΎΠ»ΡŒΠΎΡ€ΠΎΠ²Ρ– Ρ–Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΈ
13
+
14
+ ### Π€ΠΎΡ€ΠΌΠ°Ρ‚ відобраТСння
15
+ КоТна Π²Ρ–Π΄ΠΏΠΎΠ²Ρ–Π΄ΡŒ асистСнта Ρ‚Π΅ΠΏΠ΅Ρ€ ΠΏΠΎΡ‡ΠΈΠ½Π°Ρ”Ρ‚ΡŒΡΡ Π· ΠΊΠΎΠ»ΡŒΠΎΡ€ΠΎΠ²ΠΎΠ³ΠΎ Ρ–Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€Π°:
16
+
17
+ ```
18
+ 🟒 **GREEN** (95%)
19
+
20
+ 🟑 **YELLOW** (85%)
21
+ *Indicators: anxiety, worry, work-related stress*
22
+
23
+ πŸ”΄ **RED** (90%)
24
+ *Indicators: suicidal ideation, hopelessness*
25
+ ```
26
+
27
+ ### ЗначСння ΠΊΠΎΠ»ΡŒΠΎΡ€Ρ–Π²
28
+ - **🟒 GREEN** - НСмає ΠΎΠ·Π½Π°ΠΊ Π΄ΡƒΡ…ΠΎΠ²Π½ΠΎΠ³ΠΎ дистрСсу, Π·Π²ΠΈΡ‡Π°ΠΉΠ½Π° ΠΌΠ΅Π΄ΠΈΡ‡Π½Π° Ρ€ΠΎΠ·ΠΌΠΎΠ²Π°
29
+ - **🟑 YELLOW** - ΠŸΠΎΡ‚Π΅Π½Ρ†Ρ–ΠΉΠ½ΠΈΠΉ дистрСс, ΠΏΠΎΡ‚Ρ€Π΅Π±ΡƒΡ” ΡƒΠ²Π°Π³ΠΈ Ρ‚Π° ΠΏΡ–Π΄Ρ‚Ρ€ΠΈΠΌΠΊΠΈ
30
+ - **πŸ”΄ RED** - Π‘Π΅Ρ€ΠΉΠΎΠ·Π½ΠΈΠΉ дистрСс Π°Π±ΠΎ ΠΊΡ€ΠΈΠ·Π°, ΠΏΠΎΡ‚Ρ€Π΅Π±ΡƒΡ” Π½Π΅Π³Π°ΠΉΠ½ΠΎΠ³ΠΎ втручання
31
+
32
+ ### Π”ΠΎΠ΄Π°Ρ‚ΠΊΠΎΠ²Π° інформація
33
+ - **Відсоток впСвнСності** - ΠΏΠΎΠΊΠ°Π·ΡƒΡ” Π½Π°ΡΠΊΡ–Π»ΡŒΠΊΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Π° систСма Π² класифікації
34
+ - **Π†Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΈ** - ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ– ΠΎΠ·Π½Π°ΠΊΠΈ дистрСсу, які Π±ΡƒΠ»ΠΈ виявлСні
35
+
36
+ ## Логування Ρ€ΠΎΠ·ΠΌΠΎΠ²
37
+
38
+ ### АвтоматичнС збСрСТСння
39
+ - КоТна Ρ€ΠΎΠ·ΠΌΠΎΠ²Π° Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΡ‡Π½ΠΎ Π·Π±Π΅Ρ€Ρ–Π³Π°Ρ”Ρ‚ΡŒΡΡ Π² ΠΏΠ°ΠΏΡ†Ρ– `conversation_logs/`
40
+ - Π‘Ρ‚Π²ΠΎΡ€ΡŽΡŽΡ‚ΡŒΡΡ Ρ„Π°ΠΉΠ»ΠΈ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ… JSON Ρ‚Π° CSV
41
+ - Логування Π²Ρ–Π΄Π±ΡƒΠ²Π°Ρ”Ρ‚ΡŒΡΡ Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌΡƒ часі після ΠΊΠΎΠΆΠ½ΠΎΠ³ΠΎ повідомлСння
42
+
43
+ ### Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° JSON Π»ΠΎΠ³Ρƒ
44
+ ```json
45
+ {
46
+ "session_id": "session_20251212_114257",
47
+ "start_time": "2025-12-12T11:42:57.849982",
48
+ "patient_name": "Test Patient",
49
+ "total_messages": 4,
50
+ "entries": [
51
+ {
52
+ "timestamp": "2025-12-12T11:43:06.931133",
53
+ "user_message": "ΠŸΡ€ΠΈΠ²Ρ–Ρ‚, як справи?",
54
+ "assistant_response": "ΠŸΡ€ΠΈΠ²Ρ–Ρ‚! Π”ΡΠΊΡƒΡŽ...",
55
+ "spiritual_classification": "GREEN",
56
+ "classification_confidence": 0.95,
57
+ "classification_indicators": [],
58
+ "classification_reasoning": "Simple greeting...",
59
+ "session_id": "session_20251212_114257",
60
+ "message_index": 1
61
+ }
62
+ ],
63
+ "session_summary": {
64
+ "total_exchanges": 4,
65
+ "classification_counts": {
66
+ "green": 2,
67
+ "yellow": 2,
68
+ "red": 0
69
+ },
70
+ "classification_percentages": {
71
+ "green": 50.0,
72
+ "yellow": 50.0,
73
+ "red": 0.0
74
+ },
75
+ "average_confidence": 0.9,
76
+ "top_indicators": {
77
+ "anxiety": 1,
78
+ "worry": 1,
79
+ "work-related stress": 1
80
+ },
81
+ "session_duration_minutes": 5.2
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### CSV Ρ„ΠΎΡ€ΠΌΠ°Ρ‚
87
+ CSV Ρ„Π°ΠΉΠ» ΠΌΡ–ΡΡ‚ΠΈΡ‚ΡŒ Ρ‚Ρ– ΠΆ Π΄Π°Π½Ρ– Π² Ρ‚Π°Π±Π»ΠΈΡ‡Π½ΠΎΠΌΡƒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ– для Π°Π½Π°Π»Ρ–Π·Ρƒ Π² Excel Π°Π±ΠΎ Ρ–Π½ΡˆΠΈΡ… ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ°Ρ….
88
+
89
+ ## ІнтСрфСйс користувача
90
+
91
+ ### Нові Π΅Π»Π΅ΠΌΠ΅Π½Ρ‚ΠΈ Π² Ρ‡Π°Ρ‚Ρ–
92
+
93
+ #### Кнопки завантаТСння
94
+ ```
95
+ πŸ“Š Conversation Logs:
96
+ [πŸ“₯ Download JSON] [πŸ“Š Download CSV]
97
+ ```
98
+
99
+ #### Бтатистика Ρ€ΠΎΠ·ΠΌΠΎΠ²ΠΈ
100
+ ```
101
+ πŸ“ˆ Conversation Stats
102
+ **πŸ“Š Conversation Statistics**
103
+
104
+ **Messages:** 4 exchanges
105
+ **Duration:** 5.2 minutes
106
+
107
+ **Classifications:**
108
+ 🟒 Green: 2 (50.0%)
109
+ 🟑 Yellow: 2 (50.0%)
110
+ πŸ”΄ Red: 0 (0.0%)
111
+
112
+ **Average Confidence:** 0.9
113
+
114
+ **Top Indicators:**
115
+ β€’ anxiety: 1
116
+ β€’ worry: 1
117
+ β€’ work-related stress: 1
118
+ ```
119
+
120
+ #### Кнопка оновлСння статистики
121
+ ```
122
+ [πŸ”„ Refresh Stats]
123
+ ```
124
+
125
+ ## Π’Π΅Ρ…Π½Ρ–Ρ‡Π½Π° рСалізація
126
+
127
+ ### Нові класи
128
+ - `ConversationLogger` - основний клас для логування
129
+ - `ConversationEntry` - структура для ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΎΠ±ΠΌΡ–Π½Ρƒ повідомлСннями
130
+ - `ConversationSession` - структура для ΠΏΠΎΠ²Π½ΠΎΡ— сСсії
131
+
132
+ ### ІнтСграція Π· Ρ–ΡΠ½ΡƒΡŽΡ‡ΠΎΡŽ ΡΠΈΡΡ‚Π΅ΠΌΠΎΡŽ
133
+ - Логування Ρ–Π½Ρ‚Π΅Π³Ρ€ΠΎΠ²Π°Π½ΠΎ Π² `SimplifiedMedicalApp`
134
+ - Π†Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΈ Π΄ΠΎΠ΄Π°ΡŽΡ‚ΡŒΡΡ Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΡ‡Π½ΠΎ Π΄ΠΎ всіх Π²Ρ–Π΄ΠΏΠΎΠ²Ρ–Π΄Π΅ΠΉ
135
+ - Бтатистика ΠΎΠ½ΠΎΠ²Π»ΡŽΡ”Ρ‚ΡŒΡΡ після ΠΊΠΎΠΆΠ½ΠΎΠ³ΠΎ повідомлСння
136
+
137
+ ### Π€Π°ΠΉΠ»ΠΎΠ²Π° структура
138
+ ```
139
+ conversation_logs/
140
+ β”œβ”€β”€ session_20251212_114257.json
141
+ β”œβ”€β”€ session_20251212_114257.csv
142
+ β”œβ”€οΏ½οΏ½οΏ½ session_20251212_115430.json
143
+ └── session_20251212_115430.csv
144
+ ```
145
+
146
+ ## Використання
147
+
148
+ ### Для користувачів
149
+ 1. **БпостСрігайтС Π·Π° Ρ–Π½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€Π°ΠΌΠΈ** - ΠΊΠΎΠ»ΡŒΠΎΡ€ΠΎΠ²Ρ– ΠΏΠΎΠ·Π½Π°Ρ‡ΠΊΠΈ ΠΏΠΎΠΊΠ°Π·ΡƒΡŽΡ‚ΡŒ Ρ€Ρ–Π²Π΅Π½ΡŒ дистрСсу
150
+ 2. **Π—Π°Π²Π°Π½Ρ‚Π°ΠΆΡƒΠΉΡ‚Π΅ Π»ΠΎΠ³ΠΈ** - використовуйтС ΠΊΠ½ΠΎΠΏΠΊΠΈ для збСрСТСння Ρ€ΠΎΠ·ΠΌΠΎΠ²
151
+ 3. **ΠŸΠ΅Ρ€Π΅Π³Π»ΡΠ΄Π°ΠΉΡ‚Π΅ статистику** - слідкуйтС Π·Π° Π΄ΠΈΠ½Π°ΠΌΡ–ΠΊΠΎΡŽ Ρ€ΠΎΠ·ΠΌΠΎΠ²ΠΈ
152
+
153
+ ### Для Π°Π½Π°Π»Ρ–Ρ‚ΠΈΠΊΡ–Π²
154
+ 1. **JSON Ρ„Π°ΠΉΠ»ΠΈ** - для ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ½ΠΎΠ³ΠΎ Π°Π½Π°Π»Ρ–Π·Ρƒ Ρ‚Π° ΠΎΠ±Ρ€ΠΎΠ±ΠΊΠΈ Π΄Π°Π½ΠΈΡ…
155
+ 2. **CSV Ρ„Π°ΠΉΠ»ΠΈ** - для Π°Π½Π°Π»Ρ–Π·Ρƒ Π² Excel, Google Sheets Ρ‚ΠΎΡ‰ΠΎ
156
+ 3. **Бтатистика сСсій** - для розуміння ΠΏΠ°Ρ‚Π΅Ρ€Π½Ρ–Π² Ρ‚Π° Ρ‚Ρ€Π΅Π½Π΄Ρ–Π²
157
+
158
+ ## ΠŸΡ€ΠΈΠΊΠ»Π°Π΄ΠΈ використання
159
+
160
+ ### ΠœΠ΅Π΄ΠΈΡ‡Π½ΠΈΠΉ пСрсонал
161
+ - ВідстСТСння Π΅ΠΌΠΎΡ†Ρ–ΠΉΠ½ΠΎΠ³ΠΎ стану ΠΏΠ°Ρ†Ρ–Ρ”Π½Ρ‚Ρ–Π²
162
+ - ВиявлСння ΠΏΠ°Ρ‚Π΅Ρ€Π½Ρ–Π² дистрСсу
163
+ - ДокумСнтування для ΠΌΠ΅Π΄ΠΈΡ‡Π½ΠΈΡ… записів
164
+
165
+ ### Дослідники
166
+ - Аналіз СфСктивності класифікації
167
+ - ВивчСння ΠΏΠ°Ρ‚Π΅Ρ€Π½Ρ–Π² Π΄ΡƒΡ…ΠΎΠ²Π½ΠΎΠ³ΠΎ дистрСсу
168
+ - ΠŸΠΎΠΊΡ€Π°Ρ‰Π΅Π½Π½Ρ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΡ–Π²
169
+
170
+ ### Адміністратори
171
+ - ΠœΠΎΠ½Ρ–Ρ‚ΠΎΡ€ΠΈΠ½Π³ використання систСми
172
+ - ΠžΡ†Ρ–Π½ΠΊΠ° якості Π²Π·Π°Ρ”ΠΌΠΎΠ΄Ρ–Ρ—
173
+ - ΠŸΠ»Π°Π½ΡƒΠ²Π°Π½Π½Ρ рСсурсів ΠΏΡ–Π΄Ρ‚Ρ€ΠΈΠΌΠΊΠΈ
174
+
175
+ ## ΠšΠΎΠ½Ρ„Ρ–Π΄Π΅Π½Ρ†Ρ–ΠΉΠ½Ρ–ΡΡ‚ΡŒ Ρ‚Π° Π±Π΅Π·ΠΏΠ΅ΠΊΠ°
176
+
177
+ ### Π›ΠΎΠΊΠ°Π»ΡŒΠ½Π΅ збСрігання
178
+ - Всі Π»ΠΎΠ³ΠΈ Π·Π±Π΅Ρ€Ρ–Π³Π°ΡŽΡ‚ΡŒΡΡ локально
179
+ - НСмає ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡Ρ– Π΄Π°Π½ΠΈΡ… Π½Π° Π·ΠΎΠ²Π½Ρ–ΡˆΠ½Ρ– сСрвСри
180
+ - ΠšΠΎΡ€ΠΈΡΡ‚ΡƒΠ²Π°Ρ‡ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŽΡ” свої Π΄Π°Π½Ρ–
181
+
182
+ ### Анонімізація
183
+ - ΠœΠΎΠΆΠ»ΠΈΠ²Ρ–ΡΡ‚ΡŒ використання псСвдонімів
184
+ - ВидалСння особистої Ρ–Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†Ρ–Ρ— ΠΏΡ€ΠΈ ΠΏΠΎΡ‚Ρ€Π΅Π±Ρ–
185
+ - ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ доступу Π΄ΠΎ Ρ„Π°ΠΉΠ»Ρ–Π² Π»ΠΎΠ³Ρ–Π²
186
+
187
+ ## ΠœΠ°ΠΉΠ±ΡƒΡ‚Π½Ρ– покращСння
188
+
189
+ ### ΠŸΠ»Π°Π½ΡƒΡ”Ρ‚ΡŒΡΡ Π΄ΠΎΠ΄Π°Ρ‚ΠΈ
190
+ - Експорт Π² Ρ–Π½ΡˆΡ– Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΈ (PDF, Word)
191
+ - Π€Ρ–Π»ΡŒΡ‚Ρ€Π°Ρ†Ρ–Ρ Ρ‚Π° ΠΏΠΎΡˆΡƒΠΊ Π² Π»ΠΎΠ³Π°Ρ…
192
+ - Візуалізація статистики (Π³Ρ€Π°Ρ„Ρ–ΠΊΠΈ, Π΄Ρ–Π°Π³Ρ€Π°ΠΌΠΈ)
193
+ - Автоматичні Π·Π²Ρ–Ρ‚ΠΈ Ρ‚Π° Π°Π½Π°Π»Ρ–Ρ‚ΠΈΠΊΠ°
194
+ - ІнтСграція Π· ΠΌΠ΅Π΄ΠΈΡ‡Π½ΠΈΠΌΠΈ систСмами
195
+
196
+ ### ΠœΠΎΠΆΠ»ΠΈΠ²Ρ– Ρ€ΠΎΠ·ΡˆΠΈΡ€Π΅Π½Π½Ρ
197
+ - ΠŸΠΎΡ€Ρ–Π²Π½ΡΠ½Π½Ρ сСсій
198
+ - Π’Ρ€Π΅Π½Π΄ΠΎΠ²ΠΈΠΉ Π°Π½Π°Π»Ρ–Π·
199
+ - АлСрти ΠΏΡ€ΠΈ ΠΊΡ€ΠΈΡ‚ΠΈΡ‡Π½ΠΈΡ… ситуаціях
200
+ - ІнтСграція Π· ΠΊΠ°Π»Π΅Π½Π΄Π°Ρ€Π΅ΠΌ
201
+ - Нагадування ΠΏΡ€ΠΎ ΠΏΠΎΠ΄Π°Π»ΡŒΡˆΡ– Π΄Ρ–Ρ—
src/core/conversation_logger.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Conversation Logger for Medical Assistant.
4
+
5
+ Logs conversations with spiritual classification indicators for analysis.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from datetime import datetime
11
+ from typing import Dict, List, Any, Optional
12
+ from dataclasses import dataclass, asdict
13
+ from src.core.spiritual_state import SpiritualState, SpiritualAssessment
14
+
15
+ @dataclass
16
+ class ConversationEntry:
17
+ """Single conversation entry with classification data."""
18
+ timestamp: str
19
+ user_message: str
20
+ assistant_response: str
21
+ spiritual_classification: str # GREEN, YELLOW, RED
22
+ classification_confidence: float
23
+ classification_indicators: List[str]
24
+ classification_reasoning: str
25
+ session_id: str
26
+ message_index: int
27
+
28
+ @dataclass
29
+ class ConversationSession:
30
+ """Complete conversation session."""
31
+ session_id: str
32
+ start_time: str
33
+ end_time: Optional[str]
34
+ patient_name: str
35
+ total_messages: int
36
+ entries: List[ConversationEntry]
37
+ session_summary: Dict[str, Any]
38
+
39
+ class ConversationLogger:
40
+ """Logger for conversation sessions with spiritual classification data."""
41
+
42
+ def __init__(self, session_id: str = None, patient_name: str = "Anonymous"):
43
+ """Initialize conversation logger."""
44
+ self.session_id = session_id or self._generate_session_id()
45
+ self.patient_name = patient_name
46
+ self.start_time = datetime.now().isoformat()
47
+ self.entries: List[ConversationEntry] = []
48
+ self.message_counter = 0
49
+
50
+ # Create logs directory if it doesn't exist
51
+ self.logs_dir = "conversation_logs"
52
+ os.makedirs(self.logs_dir, exist_ok=True)
53
+
54
+ def _generate_session_id(self) -> str:
55
+ """Generate unique session ID."""
56
+ return f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
57
+
58
+ def log_exchange(
59
+ self,
60
+ user_message: str,
61
+ assistant_response: str,
62
+ assessment: SpiritualAssessment
63
+ ) -> None:
64
+ """
65
+ Log a conversation exchange with spiritual classification.
66
+
67
+ Args:
68
+ user_message: User's message
69
+ assistant_response: Assistant's response
70
+ assessment: Spiritual assessment of the user message
71
+ """
72
+ self.message_counter += 1
73
+
74
+ entry = ConversationEntry(
75
+ timestamp=datetime.now().isoformat(),
76
+ user_message=user_message,
77
+ assistant_response=assistant_response,
78
+ spiritual_classification=assessment.state.value.upper(),
79
+ classification_confidence=assessment.confidence,
80
+ classification_indicators=assessment.indicators,
81
+ classification_reasoning=assessment.reasoning,
82
+ session_id=self.session_id,
83
+ message_index=self.message_counter
84
+ )
85
+
86
+ self.entries.append(entry)
87
+
88
+ # Auto-save after each entry
89
+ self._save_session()
90
+
91
+ def get_classification_indicator(self, state: SpiritualState) -> str:
92
+ """Get colored emoji indicator for spiritual state."""
93
+ indicators = {
94
+ SpiritualState.GREEN: "🟒",
95
+ SpiritualState.YELLOW: "🟑",
96
+ SpiritualState.RED: "πŸ”΄"
97
+ }
98
+ return indicators.get(state, "βšͺ")
99
+
100
+ def get_classification_text(self, assessment: SpiritualAssessment) -> str:
101
+ """Get formatted classification text for display."""
102
+ indicator = self.get_classification_indicator(assessment.state)
103
+ confidence_percent = int(assessment.confidence * 100)
104
+
105
+ classification_text = f"{indicator} **{assessment.state.value.upper()}** ({confidence_percent}%)"
106
+
107
+ if assessment.indicators:
108
+ indicators_text = ", ".join(assessment.indicators[:3]) # Show max 3 indicators
109
+ if len(assessment.indicators) > 3:
110
+ indicators_text += f" +{len(assessment.indicators) - 3} more"
111
+ classification_text += f"\n*Indicators: {indicators_text}*"
112
+
113
+ return classification_text
114
+
115
+ def _save_session(self) -> None:
116
+ """Save current session to JSON file."""
117
+ session = ConversationSession(
118
+ session_id=self.session_id,
119
+ start_time=self.start_time,
120
+ end_time=None, # Will be set when session ends
121
+ patient_name=self.patient_name,
122
+ total_messages=self.message_counter,
123
+ entries=self.entries,
124
+ session_summary=self._generate_session_summary()
125
+ )
126
+
127
+ filename = f"{self.session_id}.json"
128
+ filepath = os.path.join(self.logs_dir, filename)
129
+
130
+ try:
131
+ with open(filepath, 'w', encoding='utf-8') as f:
132
+ json.dump(asdict(session), f, ensure_ascii=False, indent=2)
133
+ except Exception as e:
134
+ print(f"Error saving conversation log: {e}")
135
+
136
+ def _generate_session_summary(self) -> Dict[str, Any]:
137
+ """Generate summary statistics for the session."""
138
+ if not self.entries:
139
+ return {}
140
+
141
+ # Count classifications
142
+ green_count = sum(1 for e in self.entries if e.spiritual_classification == "GREEN")
143
+ yellow_count = sum(1 for e in self.entries if e.spiritual_classification == "YELLOW")
144
+ red_count = sum(1 for e in self.entries if e.spiritual_classification == "RED")
145
+
146
+ # Calculate average confidence
147
+ avg_confidence = sum(e.classification_confidence for e in self.entries) / len(self.entries)
148
+
149
+ # Collect all indicators
150
+ all_indicators = []
151
+ for entry in self.entries:
152
+ all_indicators.extend(entry.classification_indicators)
153
+
154
+ # Count unique indicators
155
+ indicator_counts = {}
156
+ for indicator in all_indicators:
157
+ indicator_counts[indicator] = indicator_counts.get(indicator, 0) + 1
158
+
159
+ return {
160
+ "total_exchanges": len(self.entries),
161
+ "classification_counts": {
162
+ "green": green_count,
163
+ "yellow": yellow_count,
164
+ "red": red_count
165
+ },
166
+ "classification_percentages": {
167
+ "green": round(green_count / len(self.entries) * 100, 1),
168
+ "yellow": round(yellow_count / len(self.entries) * 100, 1),
169
+ "red": round(red_count / len(self.entries) * 100, 1)
170
+ },
171
+ "average_confidence": round(avg_confidence, 3),
172
+ "top_indicators": dict(sorted(indicator_counts.items(), key=lambda x: x[1], reverse=True)[:5]),
173
+ "session_duration_minutes": self._calculate_session_duration()
174
+ }
175
+
176
+ def _calculate_session_duration(self) -> float:
177
+ """Calculate session duration in minutes."""
178
+ if not self.entries:
179
+ return 0.0
180
+
181
+ start = datetime.fromisoformat(self.start_time)
182
+ last_entry = datetime.fromisoformat(self.entries[-1].timestamp)
183
+ duration = (last_entry - start).total_seconds() / 60
184
+ return round(duration, 1)
185
+
186
+ def end_session(self) -> str:
187
+ """End the session and return final log file path."""
188
+ # Update end time in the last save
189
+ if self.entries:
190
+ self.entries[-1].timestamp = datetime.now().isoformat()
191
+
192
+ self._save_session()
193
+
194
+ filename = f"{self.session_id}.json"
195
+ return os.path.join(self.logs_dir, filename)
196
+
197
+ def get_session_summary(self) -> Dict[str, Any]:
198
+ """Get current session summary."""
199
+ return self._generate_session_summary()
200
+
201
+ def export_csv(self) -> str:
202
+ """Export conversation to CSV format."""
203
+ import csv
204
+
205
+ csv_filename = f"{self.session_id}.csv"
206
+ csv_filepath = os.path.join(self.logs_dir, csv_filename)
207
+
208
+ try:
209
+ with open(csv_filepath, 'w', newline='', encoding='utf-8') as csvfile:
210
+ fieldnames = [
211
+ 'timestamp', 'message_index', 'user_message', 'assistant_response',
212
+ 'spiritual_classification', 'classification_confidence',
213
+ 'classification_indicators', 'classification_reasoning'
214
+ ]
215
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
216
+
217
+ writer.writeheader()
218
+ for entry in self.entries:
219
+ writer.writerow({
220
+ 'timestamp': entry.timestamp,
221
+ 'message_index': entry.message_index,
222
+ 'user_message': entry.user_message,
223
+ 'assistant_response': entry.assistant_response,
224
+ 'spiritual_classification': entry.spiritual_classification,
225
+ 'classification_confidence': entry.classification_confidence,
226
+ 'classification_indicators': '; '.join(entry.classification_indicators),
227
+ 'classification_reasoning': entry.classification_reasoning
228
+ })
229
+
230
+ return csv_filepath
231
+ except Exception as e:
232
+ print(f"Error exporting to CSV: {e}")
233
+ return ""
src/core/simplified_medical_app.py CHANGED
@@ -23,6 +23,7 @@ from src.core.spiritual_state import (
23
  from src.core.spiritual_monitor import SpiritualMonitor
24
  from src.core.soft_triage_manager import SoftTriageManager
25
  from src.core.ai_client import AIClientManager
 
26
  from src.core.core_classes import (
27
  ClinicalBackground, ChatMessage, SessionState,
28
  PatientDataLoader, MedicalAssistant, SoftMedicalTriage
@@ -82,6 +83,11 @@ class SimplifiedMedicalApp:
82
  self.spiritual_state = SessionSpiritualState()
83
  self.session_active = False
84
 
 
 
 
 
 
85
  logger.info("βœ… SimplifiedMedicalApp initialized")
86
 
87
  def process_message(
@@ -146,20 +152,44 @@ class SimplifiedMedicalApp:
146
  # GREEN - normal medical dialog (Requirement 2.2)
147
  response = self._handle_green_state(message)
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  # Add assistant response to history
150
  assistant_msg = ChatMessage(
151
  timestamp=datetime.now().strftime("%H:%M"),
152
  role="assistant",
153
- message=response,
154
  mode=f"medical_{self.spiritual_state.spiritual_state.value}"
155
  )
156
  self.chat_history.append(assistant_msg)
157
 
 
 
 
 
 
 
 
158
  # Update Gradio history
159
  if history is None:
160
  history = []
161
  history.append({"role": "user", "content": message})
162
- history.append({"role": "assistant", "content": response})
163
 
164
  return history, self._get_status_info()
165
 
@@ -475,12 +505,33 @@ RECOMMENDED ACTION: Immediate spiritual care outreach
475
  """
476
  logger.info("Resetting session")
477
 
 
 
 
 
478
  self.chat_history = []
479
  self.spiritual_state.reset()
480
  self.session_active = False
481
 
 
 
 
 
 
482
  return [], self._get_status_info()
483
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  def _get_status_info(self) -> str:
485
  """Get current status information."""
486
  state_emoji = {
 
23
  from src.core.spiritual_monitor import SpiritualMonitor
24
  from src.core.soft_triage_manager import SoftTriageManager
25
  from src.core.ai_client import AIClientManager
26
+ from src.core.conversation_logger import ConversationLogger
27
  from src.core.core_classes import (
28
  ClinicalBackground, ChatMessage, SessionState,
29
  PatientDataLoader, MedicalAssistant, SoftMedicalTriage
 
83
  self.spiritual_state = SessionSpiritualState()
84
  self.session_active = False
85
 
86
+ # Conversation logging
87
+ self.conversation_logger = ConversationLogger(
88
+ patient_name=self.clinical_background.patient_name
89
+ )
90
+
91
  logger.info("βœ… SimplifiedMedicalApp initialized")
92
 
93
  def process_message(
 
152
  # GREEN - normal medical dialog (Requirement 2.2)
153
  response = self._handle_green_state(message)
154
 
155
+ # Get the assessment for display and logging
156
+ current_assessment = self.spiritual_state.last_assessment
157
+ if current_assessment is None:
158
+ # Create default assessment for GREEN state
159
+ current_assessment = SpiritualAssessment(
160
+ state=SpiritualState.GREEN,
161
+ indicators=[],
162
+ confidence=0.95,
163
+ reasoning="Normal medical conversation"
164
+ )
165
+
166
+ # Create classification indicator text
167
+ classification_text = self.conversation_logger.get_classification_text(current_assessment)
168
+
169
+ # Add classification indicator to response
170
+ response_with_indicator = f"{classification_text}\n\n{response}"
171
+
172
  # Add assistant response to history
173
  assistant_msg = ChatMessage(
174
  timestamp=datetime.now().strftime("%H:%M"),
175
  role="assistant",
176
+ message=response_with_indicator,
177
  mode=f"medical_{self.spiritual_state.spiritual_state.value}"
178
  )
179
  self.chat_history.append(assistant_msg)
180
 
181
+ # Log the conversation exchange
182
+ self.conversation_logger.log_exchange(
183
+ user_message=message,
184
+ assistant_response=response,
185
+ assessment=current_assessment
186
+ )
187
+
188
  # Update Gradio history
189
  if history is None:
190
  history = []
191
  history.append({"role": "user", "content": message})
192
+ history.append({"role": "assistant", "content": response_with_indicator})
193
 
194
  return history, self._get_status_info()
195
 
 
505
  """
506
  logger.info("Resetting session")
507
 
508
+ # End current conversation log
509
+ if hasattr(self, 'conversation_logger'):
510
+ self.conversation_logger.end_session()
511
+
512
  self.chat_history = []
513
  self.spiritual_state.reset()
514
  self.session_active = False
515
 
516
+ # Start new conversation log
517
+ self.conversation_logger = ConversationLogger(
518
+ patient_name=self.clinical_background.patient_name
519
+ )
520
+
521
  return [], self._get_status_info()
522
 
523
+ def get_conversation_log_path(self) -> str:
524
+ """Get path to current conversation log file."""
525
+ return self.conversation_logger.end_session()
526
+
527
+ def get_conversation_summary(self) -> dict:
528
+ """Get summary of current conversation."""
529
+ return self.conversation_logger.get_session_summary()
530
+
531
+ def export_conversation_csv(self) -> str:
532
+ """Export conversation to CSV format."""
533
+ return self.conversation_logger.export_csv()
534
+
535
  def _get_status_info(self) -> str:
536
  """Get current status information."""
537
  state_emoji = {
src/interface/simplified_gradio_app.py CHANGED
@@ -296,6 +296,12 @@ def create_simplified_interface():
296
  with gr.Row():
297
  clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", scale=1)
298
 
 
 
 
 
 
 
299
  # Quick examples
300
  gr.Markdown("### ⚑ Quick Start:")
301
  with gr.Row():
@@ -311,6 +317,15 @@ def create_simplified_interface():
311
 
312
  refresh_btn = gr.Button("πŸ”„ Refresh Status", size="sm")
313
 
 
 
 
 
 
 
 
 
 
314
  # Debug info (only in debug mode)
315
  if debug_mode:
316
  gr.Markdown("### πŸ”§ Debug Info")
@@ -680,7 +695,11 @@ Changes apply only to your current session.
680
 
681
  session.update_activity()
682
  new_history, status = session.app_instance.process_message(message, history)
683
- return new_history, status, session, ""
 
 
 
 
684
 
685
  def handle_clear(session: SimplifiedSessionData):
686
  """Handle clear chat."""
@@ -703,6 +722,62 @@ Changes apply only to your current session.
703
  """Send example message."""
704
  return handle_message(example_text, history, session)
705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
  # Prompt editing handlers
707
  def format_prompt_with_html(prompt_text: str) -> str:
708
  """Format prompt with HTML tags for better visualization."""
@@ -1742,13 +1817,13 @@ To revert, use "Reset to Default" button.
1742
  send_btn.click(
1743
  handle_message,
1744
  inputs=[msg, chatbot, session_data],
1745
- outputs=[chatbot, status_box, session_data, msg]
1746
  )
1747
 
1748
  msg.submit(
1749
  handle_message,
1750
  inputs=[msg, chatbot, session_data],
1751
- outputs=[chatbot, status_box, session_data, msg]
1752
  )
1753
 
1754
  # Clear chat
@@ -1766,22 +1841,47 @@ To revert, use "Reset to Default" button.
1766
  )
1767
 
1768
  # Example buttons
 
 
 
 
 
1769
  example_medical.click(
1770
- lambda h, s: send_example("I have a headache and feel tired", h, s),
1771
  inputs=[chatbot, session_data],
1772
- outputs=[chatbot, status_box, session_data, msg]
1773
  )
1774
 
1775
  example_wellness.click(
1776
- lambda h, s: send_example("I'm feeling stressed and overwhelmed lately", h, s),
1777
  inputs=[chatbot, session_data],
1778
- outputs=[chatbot, status_box, session_data, msg]
1779
  )
1780
 
1781
  example_help.click(
1782
- lambda h, s: send_example("How can you help me with my health?", h, s),
1783
  inputs=[chatbot, session_data],
1784
- outputs=[chatbot, status_box, session_data, msg]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1785
  )
1786
 
1787
  # Prompt editing events
 
296
  with gr.Row():
297
  clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", scale=1)
298
 
299
+ # Conversation logging section
300
+ gr.Markdown("### πŸ“Š Conversation Logs:")
301
+ with gr.Row():
302
+ download_json_btn = gr.DownloadButton("πŸ“₯ Download JSON", scale=1, size="sm")
303
+ download_csv_btn = gr.DownloadButton("πŸ“Š Download CSV", scale=1, size="sm")
304
+
305
  # Quick examples
306
  gr.Markdown("### ⚑ Quick Start:")
307
  with gr.Row():
 
317
 
318
  refresh_btn = gr.Button("πŸ”„ Refresh Status", size="sm")
319
 
320
+ # Conversation statistics
321
+ gr.Markdown("### πŸ“ˆ Conversation Stats")
322
+ conversation_stats = gr.Markdown(
323
+ value="No conversation yet",
324
+ label="Statistics"
325
+ )
326
+
327
+ refresh_stats_btn = gr.Button("πŸ”„ Refresh Stats", size="sm")
328
+
329
  # Debug info (only in debug mode)
330
  if debug_mode:
331
  gr.Markdown("### πŸ”§ Debug Info")
 
695
 
696
  session.update_activity()
697
  new_history, status = session.app_instance.process_message(message, history)
698
+
699
+ # Get updated conversation stats
700
+ stats = get_conversation_stats(session)
701
+
702
+ return new_history, status, session, "", stats
703
 
704
  def handle_clear(session: SimplifiedSessionData):
705
  """Handle clear chat."""
 
722
  """Send example message."""
723
  return handle_message(example_text, history, session)
724
 
725
+ def get_conversation_stats(session: SimplifiedSessionData):
726
+ """Get conversation statistics."""
727
+ if session is None or not hasattr(session.app_instance, 'conversation_logger'):
728
+ return "No conversation yet"
729
+
730
+ try:
731
+ summary = session.app_instance.get_conversation_summary()
732
+ if not summary or summary.get('total_exchanges', 0) == 0:
733
+ return "No conversation yet"
734
+
735
+ stats_text = f"""**πŸ“Š Conversation Statistics**
736
+
737
+ **Messages:** {summary['total_exchanges']} exchanges
738
+ **Duration:** {summary['session_duration_minutes']} minutes
739
+
740
+ **Classifications:**
741
+ 🟒 Green: {summary['classification_counts']['green']} ({summary['classification_percentages']['green']}%)
742
+ 🟑 Yellow: {summary['classification_counts']['yellow']} ({summary['classification_percentages']['yellow']}%)
743
+ πŸ”΄ Red: {summary['classification_counts']['red']} ({summary['classification_percentages']['red']}%)
744
+
745
+ **Average Confidence:** {summary['average_confidence']}
746
+
747
+ **Top Indicators:**"""
748
+
749
+ for indicator, count in list(summary['top_indicators'].items())[:3]:
750
+ stats_text += f"\nβ€’ {indicator}: {count}"
751
+
752
+ return stats_text
753
+
754
+ except Exception as e:
755
+ return f"Error getting stats: {str(e)}"
756
+
757
+ def download_conversation_json(session: SimplifiedSessionData):
758
+ """Download conversation as JSON."""
759
+ if session is None or not hasattr(session.app_instance, 'conversation_logger'):
760
+ return None
761
+
762
+ try:
763
+ log_path = session.app_instance.get_conversation_log_path()
764
+ return log_path
765
+ except Exception as e:
766
+ print(f"Error downloading JSON: {e}")
767
+ return None
768
+
769
+ def download_conversation_csv(session: SimplifiedSessionData):
770
+ """Download conversation as CSV."""
771
+ if session is None or not hasattr(session.app_instance, 'conversation_logger'):
772
+ return None
773
+
774
+ try:
775
+ csv_path = session.app_instance.export_conversation_csv()
776
+ return csv_path
777
+ except Exception as e:
778
+ print(f"Error downloading CSV: {e}")
779
+ return None
780
+
781
  # Prompt editing handlers
782
  def format_prompt_with_html(prompt_text: str) -> str:
783
  """Format prompt with HTML tags for better visualization."""
 
1817
  send_btn.click(
1818
  handle_message,
1819
  inputs=[msg, chatbot, session_data],
1820
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats]
1821
  )
1822
 
1823
  msg.submit(
1824
  handle_message,
1825
  inputs=[msg, chatbot, session_data],
1826
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats]
1827
  )
1828
 
1829
  # Clear chat
 
1841
  )
1842
 
1843
  # Example buttons
1844
+ def send_example_with_stats(example_text: str, history, session: SimplifiedSessionData):
1845
+ """Send example message and return stats."""
1846
+ new_history, status, updated_session, msg, stats = handle_message(example_text, history, session)
1847
+ return new_history, status, updated_session, msg, stats
1848
+
1849
  example_medical.click(
1850
+ lambda h, s: send_example_with_stats("I have a headache and feel tired", h, s),
1851
  inputs=[chatbot, session_data],
1852
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats]
1853
  )
1854
 
1855
  example_wellness.click(
1856
+ lambda h, s: send_example_with_stats("I'm feeling stressed and overwhelmed lately", h, s),
1857
  inputs=[chatbot, session_data],
1858
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats]
1859
  )
1860
 
1861
  example_help.click(
1862
+ lambda h, s: send_example_with_stats("How can you help me with my health?", h, s),
1863
  inputs=[chatbot, session_data],
1864
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats]
1865
+ )
1866
+
1867
+ # Conversation logging buttons
1868
+ download_json_btn.click(
1869
+ download_conversation_json,
1870
+ inputs=[session_data],
1871
+ outputs=[download_json_btn]
1872
+ )
1873
+
1874
+ download_csv_btn.click(
1875
+ download_conversation_csv,
1876
+ inputs=[session_data],
1877
+ outputs=[download_csv_btn]
1878
+ )
1879
+
1880
+ # Refresh conversation stats
1881
+ refresh_stats_btn.click(
1882
+ get_conversation_stats,
1883
+ inputs=[session_data],
1884
+ outputs=[conversation_stats]
1885
  )
1886
 
1887
  # Prompt editing events