cacheToStats method
Convert a PersistedStatsCache to NeomageStats by computing derived fields, optionally merging today's live stats.
Implementation
NeomageStats cacheToStats(
PersistedStatsCache cache,
ProcessedStats? todayStats,
) {
// Merge cache with today's stats.
final dailyActivityMap = <String, DailyActivity>{};
for (final day in cache.dailyActivity) {
dailyActivityMap[day.date] = day.copyWith();
}
if (todayStats != null) {
for (final day in todayStats.dailyActivity) {
final existing = dailyActivityMap[day.date];
if (existing != null) {
existing.messageCount += day.messageCount;
existing.sessionCount += day.sessionCount;
existing.toolCallCount += day.toolCallCount;
} else {
dailyActivityMap[day.date] = day.copyWith();
}
}
}
final dailyModelTokensMap = <String, Map<String, int>>{};
for (final day in cache.dailyModelTokens) {
dailyModelTokensMap[day.date] = Map<String, int>.from(day.tokensByModel);
}
if (todayStats != null) {
for (final day in todayStats.dailyModelTokens) {
final existing = dailyModelTokensMap[day.date];
if (existing != null) {
for (final entry in day.tokensByModel.entries) {
existing[entry.key] = (existing[entry.key] ?? 0) + entry.value;
}
} else {
dailyModelTokensMap[day.date] = Map<String, int>.from(
day.tokensByModel,
);
}
}
}
// Merge model usage.
final modelUsage = Map<String, ModelUsage>.from(cache.modelUsage);
if (todayStats != null) {
for (final entry in todayStats.modelUsage.entries) {
final existing = modelUsage[entry.key];
if (existing != null) {
modelUsage[entry.key] = existing.merge(entry.value);
} else {
modelUsage[entry.key] = entry.value;
}
}
}
// Merge hour counts.
final hourCountsMap = <int, int>{};
for (final entry in cache.hourCounts.entries) {
hourCountsMap[entry.key] = entry.value;
}
if (todayStats != null) {
for (final entry in todayStats.hourCounts.entries) {
hourCountsMap[entry.key] =
(hourCountsMap[entry.key] ?? 0) + entry.value;
}
}
// Calculate derived stats.
final dailyActivityArray = dailyActivityMap.values.toList()
..sort((a, b) => a.date.compareTo(b.date));
final streaks = calculateStreaks(dailyActivityArray);
final dailyModelTokens =
dailyModelTokensMap.entries
.map((e) => DailyModelTokens(date: e.key, tokensByModel: e.value))
.toList()
..sort((a, b) => a.date.compareTo(b.date));
final totalSessions =
cache.totalSessions + (todayStats?.sessionStats.length ?? 0);
final totalMessages =
cache.totalMessages + (todayStats?.totalMessages ?? 0);
SessionStats? longestSession = cache.longestSession;
if (todayStats != null) {
for (final session in todayStats.sessionStats) {
if (longestSession == null ||
session.duration > longestSession.duration) {
longestSession = session;
}
}
}
String? firstSessionDate = cache.firstSessionDate;
String? lastSessionDate;
if (todayStats != null) {
for (final session in todayStats.sessionStats) {
if (firstSessionDate == null ||
session.timestamp.compareTo(firstSessionDate) < 0) {
firstSessionDate = session.timestamp;
}
if (lastSessionDate == null ||
session.timestamp.compareTo(lastSessionDate) > 0) {
lastSessionDate = session.timestamp;
}
}
}
if (lastSessionDate == null && dailyActivityArray.isNotEmpty) {
lastSessionDate = dailyActivityArray.last.date;
}
final peakActivityDay = dailyActivityArray.isNotEmpty
? dailyActivityArray
.reduce((m, d) => d.messageCount > m.messageCount ? d : m)
.date
: null;
final peakActivityHour = hourCountsMap.isNotEmpty
? hourCountsMap.entries.reduce((m, e) => e.value > m.value ? e : m).key
: null;
final totalDays = (firstSessionDate != null && lastSessionDate != null)
? ((DateTime.parse(
lastSessionDate,
).difference(DateTime.parse(firstSessionDate)).inDays) +
1)
: 0;
final totalSpeculationTimeSavedMs =
cache.totalSpeculationTimeSavedMs +
(todayStats?.totalSpeculationTimeSavedMs ?? 0);
Map<int, int>? shotDistribution;
int? oneShotRate;
if (shotStatsEnabled.value) {
shotDistribution = Map<int, int>.from(cache.shotDistribution ?? {});
if (todayStats?.shotDistribution != null) {
for (final entry in todayStats!.shotDistribution!.entries) {
shotDistribution[entry.key] =
(shotDistribution[entry.key] ?? 0) + entry.value;
}
}
final totalWithShots = shotDistribution.values.fold<int>(
0,
(s, n) => s + n,
);
oneShotRate = totalWithShots > 0
? ((shotDistribution[1] ?? 0) / totalWithShots * 100).round()
: 0;
}
return NeomageStats(
totalSessions: totalSessions,
totalMessages: totalMessages,
totalDays: totalDays,
activeDays: dailyActivityMap.length,
streaks: streaks,
dailyActivity: dailyActivityArray,
dailyModelTokens: dailyModelTokens,
longestSession: longestSession,
modelUsage: modelUsage,
firstSessionDate: firstSessionDate,
lastSessionDate: lastSessionDate,
peakActivityDay: peakActivityDay,
peakActivityHour: peakActivityHour,
totalSpeculationTimeSavedMs: totalSpeculationTimeSavedMs,
shotDistribution: shotDistribution,
oneShotRate: oneShotRate,
);
}