getAnalytics method

Future<UsageAnalytics> getAnalytics({
  1. required DateTime start,
  2. required DateTime end,
})

Compute usage analytics for a time period.

Implementation

Future<UsageAnalytics> getAnalytics({
  required DateTime start,
  required DateTime end,
}) async {
  final allEntries = <HistoryEntry>[];
  final sessionIds = <String>{};

  // Load all sessions in range.
  final dir = Directory(_baseDir);
  if (await dir.exists()) {
    await for (final entity in dir.list()) {
      if (entity is File && entity.path.endsWith('.jsonl')) {
        final sid = entity.uri.pathSegments.last.replaceAll('.jsonl', '');
        final entries = await getSessionHistory(sid);
        final inRange = entries.where(
          (e) => e.timestamp.isAfter(start) && e.timestamp.isBefore(end),
        );
        if (inRange.isNotEmpty) {
          allEntries.addAll(inRange);
          sessionIds.add(sid);
        }
      }
    }
  }

  // Compute metrics.
  final toolCounts = <String, int>{};
  final modelCounts = <String, int>{};
  final commandCounts = <String, int>{};
  int totalInput = 0;
  int totalOutput = 0;
  double totalCost = 0;
  int messages = 0;
  int toolUses = 0;

  for (final e in allEntries) {
    if (e.type == HistoryEntryType.message) messages++;
    if (e.type == HistoryEntryType.toolUse) {
      toolUses++;
      if (e.toolName != null) {
        toolCounts[e.toolName!] = (toolCounts[e.toolName!] ?? 0) + 1;
      }
    }
    if (e.type == HistoryEntryType.command) {
      final cmd = e.metadata?['command'] as String?;
      if (cmd != null) {
        commandCounts[cmd] = (commandCounts[cmd] ?? 0) + 1;
      }
    }
    totalInput += e.tokenCount ?? 0;
    totalCost += e.cost ?? 0;
  }

  // Daily breakdown.
  final dailyMap = <String, DailyUsage>{};
  for (final e in allEntries) {
    final dayKey =
        '${e.timestamp.year}-${e.timestamp.month.toString().padLeft(2, '0')}-${e.timestamp.day.toString().padLeft(2, '0')}';
    final existing = dailyMap[dayKey];
    dailyMap[dayKey] = DailyUsage(
      date: DateTime(e.timestamp.year, e.timestamp.month, e.timestamp.day),
      sessions: existing?.sessions ?? 0,
      messages:
          (existing?.messages ?? 0) +
          (e.type == HistoryEntryType.message ? 1 : 0),
      tokens: (existing?.tokens ?? 0) + (e.tokenCount ?? 0),
      cost: (existing?.cost ?? 0) + (e.cost ?? 0),
    );
  }

  return UsageAnalytics(
    periodStart: start,
    periodEnd: end,
    totalSessions: sessionIds.length,
    totalMessages: messages,
    totalToolUses: toolUses,
    totalInputTokens: totalInput,
    totalOutputTokens: totalOutput,
    totalCost: totalCost,
    toolUsageCounts: toolCounts,
    modelUsageCounts: modelCounts,
    commandUsageCounts: commandCounts,
    averageTokensPerMessage: messages > 0 ? totalInput / messages : 0,
    averageCostPerSession: sessionIds.isNotEmpty
        ? totalCost / sessionIds.length
        : 0,
    dailyBreakdown: dailyMap.values.toList()
      ..sort((a, b) => a.date.compareTo(b.date)),
  );
}