Spaces:
Sleeping
Sleeping
feat: add conversation logging and statistics features with export options
Browse files- .gitignore +1 -0
- CONVERSATION_LOGGING_FEATURES.md +201 -0
- src/core/conversation_logger.py +233 -0
- src/core/simplified_medical_app.py +53 -2
- src/interface/simplified_gradio_app.py +109 -9
.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=
|
| 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":
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 1771 |
inputs=[chatbot, session_data],
|
| 1772 |
-
outputs=[chatbot, status_box, session_data, msg]
|
| 1773 |
)
|
| 1774 |
|
| 1775 |
example_wellness.click(
|
| 1776 |
-
lambda 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:
|
| 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
|