detectMultiClauding function

({int overlapEvents, int sessionsInvolved, int userMessagesDuring}) detectMultiClauding(
  1. List<({String sessionId, List<String> timestamps})> sessions
)

Detect multi-session (using multiple Neomage sessions concurrently).

Uses a sliding window to find the pattern: session1 -> session2 -> session1 within a 30-minute window.

Implementation

({int overlapEvents, int sessionsInvolved, int userMessagesDuring})
detectMultiClauding(
  List<({String sessionId, List<String> timestamps})> sessions,
) {
  const overlapWindowMs = 30 * 60000;

  final allMessages = <({int ts, String sessionId})>[];
  for (final session in sessions) {
    for (final timestamp in session.timestamps) {
      try {
        final ts = DateTime.parse(timestamp).millisecondsSinceEpoch;
        allMessages.add((ts: ts, sessionId: session.sessionId));
      } catch (_) {
        // Skip invalid timestamps.
      }
    }
  }

  allMessages.sort((a, b) => a.ts.compareTo(b.ts));

  final multiNeomageSessionPairs = <String>{};
  final messagesDuringMultiNeomage = <String>{};

  int windowStart = 0;
  final sessionLastIndex = <String, int>{};

  for (int i = 0; i < allMessages.length; i++) {
    final msg = allMessages[i];

    // Shrink window from the left.
    while (windowStart < i &&
        msg.ts - allMessages[windowStart].ts > overlapWindowMs) {
      final expiring = allMessages[windowStart];
      if (sessionLastIndex[expiring.sessionId] == windowStart) {
        sessionLastIndex.remove(expiring.sessionId);
      }
      windowStart++;
    }

    // Check if this session appeared earlier in the window.
    final prevIndex = sessionLastIndex[msg.sessionId];
    if (prevIndex != null) {
      for (int j = prevIndex + 1; j < i; j++) {
        final between = allMessages[j];
        if (between.sessionId != msg.sessionId) {
          final pair = [msg.sessionId, between.sessionId]..sort();
          multiNeomageSessionPairs.add(pair.join(':'));
          messagesDuringMultiNeomage.add(
            '${allMessages[prevIndex].ts}:${msg.sessionId}',
          );
          messagesDuringMultiNeomage.add('${between.ts}:${between.sessionId}');
          messagesDuringMultiNeomage.add('${msg.ts}:${msg.sessionId}');
          break;
        }
      }
    }

    sessionLastIndex[msg.sessionId] = i;
  }

  final sessionsWithOverlaps = <String>{};
  for (final pair in multiNeomageSessionPairs) {
    final parts = pair.split(':');
    if (parts.length == 2) {
      sessionsWithOverlaps.add(parts[0]);
      sessionsWithOverlaps.add(parts[1]);
    }
  }

  return (
    overlapEvents: multiNeomageSessionPairs.length,
    sessionsInvolved: sessionsWithOverlaps.length,
    userMessagesDuring: messagesDuringMultiNeomage.length,
  );
}