File size: 11,870 Bytes
8056e83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
"""

Conversation Analytics - Multi-session analysis and insights

"""
import json
import sys
import os
from typing import List, Dict, Optional
from collections import Counter
from datetime import datetime

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(__file__))

from conversation_session import ConversationSession
from llm_backend import LLMBackend


class ConversationAnalytics:
    """

    Analyze multiple conversation sessions to identify patterns,

    themes, and insights across interviews.

    """

    def __init__(self, llm_backend: Optional[LLMBackend] = None):
        self.llm = llm_backend
        self.sessions: List[ConversationSession] = []

    def load_sessions(self, session_data_list: List[Dict]) -> int:
        """

        Load multiple sessions from dictionaries.



        Args:

            session_data_list: List of session dictionaries



        Returns:

            Number of sessions loaded

        """
        self.sessions = []
        for session_data in session_data_list:
            try:
                session = ConversationSession.from_dict(session_data)
                self.sessions.append(session)
            except Exception as e:
                print(f"Error loading session: {e}")
                continue

        return len(self.sessions)

    def get_aggregate_stats(self) -> Dict:
        """Get aggregate statistics across all sessions"""
        if not self.sessions:
            return {}

        total_turns = sum(s.get_turn_count() for s in self.sessions)
        total_user_turns = sum(len([t for t in s.conversation_history if t.role == "user"])
                               for s in self.sessions)
        total_ai_turns = sum(len([t for t in s.conversation_history if t.role == "ai"])
                             for s in self.sessions)

        # Calculate response lengths
        all_user_responses = []
        for session in self.sessions:
            all_user_responses.extend([len(t.content) for t in session.conversation_history
                                       if t.role == "user"])

        avg_response_length = sum(all_user_responses) / len(all_user_responses) if all_user_responses else 0

        # Calculate durations
        all_durations = [s._calculate_duration_minutes() for s in self.sessions]
        avg_duration = sum(all_durations) / len(all_durations) if all_durations else 0

        # Status breakdown
        status_counts = Counter(s.status for s in self.sessions)

        return {
            "total_sessions": len(self.sessions),
            "total_turns": total_turns,
            "total_user_turns": total_user_turns,
            "total_ai_turns": total_ai_turns,
            "avg_turns_per_session": total_turns / len(self.sessions),
            "avg_response_length": avg_response_length,
            "avg_duration_minutes": avg_duration,
            "total_duration_minutes": sum(all_durations),
            "status_breakdown": dict(status_counts),
            "completed_sessions": status_counts.get("completed", 0),
            "active_sessions": status_counts.get("active", 0),
            "abandoned_sessions": status_counts.get("abandoned", 0)
        }

    def extract_all_responses(self) -> List[str]:
        """Extract all user responses from all sessions"""
        responses = []
        for session in self.sessions:
            for turn in session.conversation_history:
                if turn.role == "user":
                    responses.append(turn.content)
        return responses

    def identify_common_keywords(self, top_n: int = 20) -> List[tuple]:
        """

        Identify most common keywords across all user responses.



        Args:

            top_n: Number of top keywords to return



        Returns:

            List of (keyword, count) tuples

        """
        responses = self.extract_all_responses()

        # Simple keyword extraction (filter common words)
        stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
                      'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
                      'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
                      'should', 'may', 'might', 'can', 'it', 'this', 'that', 'these', 'those',
                      'i', 'you', 'he', 'she', 'we', 'they', 'my', 'your', 'his', 'her', 'our',
                      'their', 'me', 'him', 'her', 'us', 'them'}

        all_words = []
        for response in responses:
            words = response.lower().split()
            # Filter out stop words and short words
            filtered_words = [w.strip('.,!?;:"()[]{}') for w in words
                            if len(w) > 3 and w.lower() not in stop_words]
            all_words.extend(filtered_words)

        word_counts = Counter(all_words)
        return word_counts.most_common(top_n)

    def generate_cross_session_insights(self) -> str:
        """

        Generate AI-powered insights across all sessions.



        Returns:

            Markdown formatted insights report

        """
        if not self.llm:
            return "❌ LLM backend required for cross-session insights"

        if not self.sessions:
            return "❌ No sessions to analyze"

        # Collect all user responses
        all_responses = self.extract_all_responses()

        if len(all_responses) < 10:
            return "❌ Need at least 10 responses across sessions for meaningful analysis"

        # Sample responses (to avoid token limits)
        sample_size = min(50, len(all_responses))
        import random
        sampled_responses = random.sample(all_responses, sample_size) if len(all_responses) > sample_size else all_responses

        responses_text = "\n\n".join([f"Response {i+1}: {r}" for i, r in enumerate(sampled_responses)])

        system_prompt = """You are analyzing multiple qualitative research interview sessions.



Identify patterns, themes, and insights across all the responses provided. Focus on:

1. **Common Themes**: What topics come up repeatedly?

2. **Sentiment Patterns**: Overall sentiment and emotional tone

3. **Key Insights**: Important discoveries or patterns

4. **Notable Quotes**: Particularly insightful or representative responses

5. **Recommendations**: What actions should researchers take based on these findings?



Provide a comprehensive analysis in a professional report format."""

        user_prompt = f"""Analyze these {len(sampled_responses)} interview responses from {len(self.sessions)} different sessions:



{responses_text}



Generate a comprehensive cross-session analysis report."""

        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]

        try:
            insights = self.llm.generate(messages, max_tokens=1000, temperature=0.5)
            return insights.strip()
        except Exception as e:
            return f"❌ Error generating insights: {str(e)}"

    def generate_comprehensive_report(self) -> str:
        """

        Generate a comprehensive markdown report of multi-session analysis.



        Returns:

            Markdown formatted report

        """
        if not self.sessions:
            return "# Multi-Session Analysis Report\n\n❌ No sessions loaded for analysis."

        stats = self.get_aggregate_stats()
        keywords = self.identify_common_keywords(15)

        report = f"""# Multi-Session Conversation Analysis Report



**Generated:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}



---



## πŸ“Š Aggregate Statistics



**Session Overview:**

- Total Sessions Analyzed: **{stats['total_sessions']}**

- Completed Sessions: **{stats['completed_sessions']}**

- Active Sessions: **{stats['active_sessions']}**

- Abandoned Sessions: **{stats['abandoned_sessions']}**



**Conversation Metrics:**

- Total Conversation Turns: **{stats['total_turns']}**

- User Responses: **{stats['total_user_turns']}**

- AI Questions: **{stats['total_ai_turns']}**

- Average Turns per Session: **{stats['avg_turns_per_session']:.1f}**



**Quality Indicators:**

- Average Response Length: **{stats['avg_response_length']:.0f} characters**

- Average Session Duration: **{stats['avg_duration_minutes']:.1f} minutes**

- Total Interview Time: **{stats['total_duration_minutes']:.1f} minutes** ({stats['total_duration_minutes']/60:.1f} hours)



---



## πŸ”‘ Common Keywords & Topics



Top keywords mentioned across all sessions:



"""

        for i, (keyword, count) in enumerate(keywords, 1):
            report += f"{i}. **{keyword}** - mentioned {count} times\n"

        report += "\n---\n\n## πŸ’‘ Cross-Session Insights\n\n"

        if self.llm:
            report += "*Generating AI-powered insights...*\n\n"
            insights = self.generate_cross_session_insights()
            report += insights
        else:
            report += "*AI insights unavailable (LLM backend not configured)*\n\n"
            report += "**Manual Analysis Recommended:**\n"
            report += "- Review individual session transcripts\n"
            report += "- Look for patterns in the common keywords above\n"
            report += "- Compare responses across different respondent demographics\n"

        report += "\n\n---\n\n## πŸ“‹ Session Details\n\n"

        for i, session in enumerate(self.sessions, 1):
            stats = session.get_summary_stats()
            report += f"""### Session {i}: {session.flow_name}

- **Session ID:** `{session.id}`

- **Status:** {session.status}

- **Duration:** {stats['duration_minutes']:.1f} minutes

- **Turns:** {stats['total_turns']} ({stats['user_turns']} user, {stats['ai_turns']} AI)

- **Avg Response Length:** {stats['avg_user_response_length']:.0f} characters



"""

        report += "\n---\n\n## 🎯 Research Recommendations\n\n"
        report += f"""Based on analysis of {stats['total_sessions']} sessions:



1. **Data Quality:** {"βœ… Good" if stats['completed_sessions'] / stats['total_sessions'] > 0.8 else "⚠️ Review incomplete sessions"}

2. **Sample Size:** {"βœ… Sufficient" if stats['total_sessions'] >= 10 else "⚠️ Consider conducting more interviews"}

3. **Engagement:** {"βœ… High" if stats['avg_response_length'] > 100 else "⚠️ Consider probing strategies"}

4. **Duration:** {"βœ… Appropriate" if 10 <= stats['avg_duration_minutes'] <= 30 else "⚠️ Review interview length"}



**Next Steps:**

- Export this report for team review

- Identify 2-3 key themes for deep-dive analysis

- Plan follow-up questions based on insights

- Consider additional interviews to explore emerging themes



---



*This report was generated by Project Echo Multi-Session Analytics*

"""

        return report

    def export_aggregated_data(self) -> Dict:
        """

        Export aggregated data in JSON format for further analysis.



        Returns:

            Dictionary with all aggregated data

        """
        return {
            "generated_at": datetime.now().isoformat(),
            "statistics": self.get_aggregate_stats(),
            "keywords": self.identify_common_keywords(30),
            "sessions": [
                {
                    "id": s.id,
                    "flow_name": s.flow_name,
                    "status": s.status,
                    "started_at": s.started_at,
                    "ended_at": s.ended_at,
                    "turn_count": s.get_turn_count(),
                    "summary_stats": s.get_summary_stats()
                }
                for s in self.sessions
            ]
        }