aggregateData function
Aggregate data from all sessions and facets into a single report.
Implementation
AggregatedData aggregateData(
List<SessionMeta> sessions,
Map<String, SessionFacets> facets,
) {
final result = AggregatedData(totalSessions: sessions.length);
result.sessionsWithFacets = facets.length;
final dates = <String>[];
final allResponseTimes = <double>[];
final allMessageHours = <int>[];
for (final session in sessions) {
dates.add(session.startTime);
result.totalMessages += session.userMessageCount;
result.totalDurationHours += session.durationMinutes / 60.0;
result.totalInputTokens += session.inputTokens;
result.totalOutputTokens += session.outputTokens;
result.gitCommits += session.gitCommits;
result.gitPushes += session.gitPushes;
// Aggregate new stats.
result.totalInterruptions += session.userInterruptions;
result.totalToolErrors += session.toolErrors;
for (final entry in session.toolErrorCategories.entries) {
result.toolErrorCategories[entry.key] =
(result.toolErrorCategories[entry.key] ?? 0) + entry.value;
}
allResponseTimes.addAll(session.userResponseTimes);
if (session.usesTaskAgent) result.sessionsUsingTaskAgent++;
if (session.usesMcp) result.sessionsUsingMcp++;
if (session.usesWebSearch) result.sessionsUsingWebSearch++;
if (session.usesWebFetch) result.sessionsUsingWebFetch++;
// Additional stats.
result.totalLinesAdded += session.linesAdded;
result.totalLinesRemoved += session.linesRemoved;
result.totalFilesModified += session.filesModified;
allMessageHours.addAll(session.messageHours);
// Merge tool and language counts.
for (final entry in session.toolCounts.entries) {
result.toolCounts[entry.key] =
(result.toolCounts[entry.key] ?? 0) + entry.value;
}
for (final entry in session.languages.entries) {
result.languages[entry.key] =
(result.languages[entry.key] ?? 0) + entry.value;
}
if (session.projectPath.isNotEmpty) {
result.projects[session.projectPath] =
(result.projects[session.projectPath] ?? 0) + 1;
}
// Merge facets.
final sessionFacets = facets[session.sessionId];
if (sessionFacets != null) {
for (final entry in sessionFacets.goalCategories.entries) {
if (entry.value > 0) {
result.goalCategories[entry.key] =
(result.goalCategories[entry.key] ?? 0) + entry.value;
}
}
result.outcomes[sessionFacets.outcome] =
(result.outcomes[sessionFacets.outcome] ?? 0) + 1;
for (final entry in sessionFacets.userSatisfactionCounts.entries) {
if (entry.value > 0) {
result.satisfaction[entry.key] =
(result.satisfaction[entry.key] ?? 0) + entry.value;
}
}
result.helpfulness[sessionFacets.neomageHelpfulness] =
(result.helpfulness[sessionFacets.neomageHelpfulness] ?? 0) + 1;
result.sessionTypes[sessionFacets.sessionType] =
(result.sessionTypes[sessionFacets.sessionType] ?? 0) + 1;
for (final entry in sessionFacets.frictionCounts.entries) {
if (entry.value > 0) {
result.friction[entry.key] =
(result.friction[entry.key] ?? 0) + entry.value;
}
}
if (sessionFacets.primarySuccess != 'none') {
result.success[sessionFacets.primarySuccess] =
(result.success[sessionFacets.primarySuccess] ?? 0) + 1;
}
}
// Collect session summaries (max 50).
if (result.sessionSummaries.length < 50) {
result.sessionSummaries.add({
'id': session.sessionId.length >= 8
? session.sessionId.substring(0, 8)
: session.sessionId,
'date': session.startTime.split('T').first,
'summary':
session.summary ??
(session.firstPrompt.length > 100
? session.firstPrompt.substring(0, 100)
: session.firstPrompt),
if (sessionFacets?.underlyingGoal != null)
'goal': sessionFacets!.underlyingGoal,
});
}
}
// Date range.
dates.sort();
if (dates.isNotEmpty) {
result.dateRange = (
start: dates.first.split('T').first,
end: dates.last.split('T').first,
);
}
// Response time statistics.
result.userResponseTimes = allResponseTimes;
if (allResponseTimes.isNotEmpty) {
final sorted = [...allResponseTimes]..sort();
result.medianResponseTime = sorted[sorted.length ~/ 2];
result.avgResponseTime =
allResponseTimes.reduce((a, b) => a + b) / allResponseTimes.length;
}
// Days active and messages per day.
final uniqueDays = dates.map((d) => d.split('T').first).toSet();
result.daysActive = uniqueDays.length;
result.messagesPerDay = result.daysActive > 0
? (result.totalMessages / result.daysActive * 10).round() / 10.0
: 0;
// Store message hours for time-of-day chart.
result.messageHours = allMessageHours;
// Multi-clauding detection.
result.multiClauding = detectMultiClauding(
sessions
.map(
(s) => (sessionId: s.sessionId, timestamps: s.userMessageTimestamps),
)
.toList(),
);
return result;
}