|
|
@@ -0,0 +1,298 @@
|
|
|
+package com.demo.seecanalysisbackend.service;
|
|
|
+
|
|
|
+import com.demo.seecanalysisbackend.model.D4Metrics;
|
|
|
+import com.demo.seecanalysisbackend.model.LogEntry;
|
|
|
+import com.demo.seecanalysisbackend.repository.LogEntryRepository;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.data.domain.Page;
|
|
|
+import org.springframework.data.domain.PageRequest;
|
|
|
+import org.springframework.data.domain.Pageable;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+@Slf4j
|
|
|
+public class D4MetricsService {
|
|
|
+
|
|
|
+ private final LogEntryRepository logEntryRepository;
|
|
|
+
|
|
|
+ private static final Set<String> D4_EVENTS = Set.of(
|
|
|
+ "D4_TASK_ASSIGNED", "D4_TASK_COMPLETED", "D4_COLLAB_EFFECTIVE",
|
|
|
+ "D4_COLLAB_CONFLICT_CREATED", "D4_COLLAB_CONFLICT_RESOLVED",
|
|
|
+ "D4_COMM_MESSAGE_SENT", "D4_COMM_MESSAGE_RESPONSE"
|
|
|
+ );
|
|
|
+
|
|
|
+ public D4Metrics calculateD4Metrics(LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ log.info("Calculating D4 metrics from {} to {}", startTime, endTime);
|
|
|
+
|
|
|
+ D4Metrics.D1Metrics d1Metrics = calculateD1Metrics(startTime, endTime);
|
|
|
+ D4Metrics.D2Metrics d2Metrics = calculateD2Metrics(startTime, endTime);
|
|
|
+
|
|
|
+ return D4Metrics.builder()
|
|
|
+ .startTime(startTime)
|
|
|
+ .endTime(endTime)
|
|
|
+ .d1Metrics(d1Metrics)
|
|
|
+ .d2Metrics(d2Metrics)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private D4Metrics.D1Metrics calculateD1Metrics(LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ Pageable pageable = PageRequest.of(0, 10000);
|
|
|
+ Page<LogEntry> allLogs = logEntryRepository.findByTimestampBetween(startTime, endTime, pageable);
|
|
|
+
|
|
|
+ List<LogEntry> taskAssignedLogs = allLogs.getContent().stream()
|
|
|
+ .filter(log -> "D4_TASK_ASSIGNED".equals(log.getBizName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<LogEntry> taskCompletedLogs = allLogs.getContent().stream()
|
|
|
+ .filter(log -> "D4_TASK_COMPLETED".equals(log.getBizName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ long totalAssigned = taskAssignedLogs.size();
|
|
|
+ long totalCompleted = taskCompletedLogs.size();
|
|
|
+ double completionRate = totalAssigned > 0 ? (double) totalCompleted / totalAssigned : 0.0;
|
|
|
+
|
|
|
+ Map<String, Double> completionRateByTaskType = calculateCompletionRateByTaskType(taskAssignedLogs, taskCompletedLogs);
|
|
|
+ Map<String, Double> completionRateByProject = calculateCompletionRateByProject(taskAssignedLogs, taskCompletedLogs);
|
|
|
+ Map<String, Double> completionRateByUser = calculateCompletionRateByUser(taskAssignedLogs, taskCompletedLogs);
|
|
|
+
|
|
|
+ List<D4Metrics.TaskCompletionDetail> recentCompletions = taskCompletedLogs.stream()
|
|
|
+ .limit(10)
|
|
|
+ .map(this::convertToTaskCompletionDetail)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ double onTimeCompletionRate = calculateOnTimeCompletionRate(taskCompletedLogs);
|
|
|
+
|
|
|
+ return D4Metrics.D1Metrics.builder()
|
|
|
+ .taskCompletionRate(completionRate)
|
|
|
+ .totalAssignedTasks(totalAssigned)
|
|
|
+ .totalCompletedTasks(totalCompleted)
|
|
|
+ .onTimeCompletionRate(onTimeCompletionRate)
|
|
|
+ .completionRateByTaskType(completionRateByTaskType)
|
|
|
+ .completionRateByProject(completionRateByProject)
|
|
|
+ .completionRateByUser(completionRateByUser)
|
|
|
+ .recentCompletions(recentCompletions)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private D4Metrics.D2Metrics calculateD2Metrics(LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ Pageable pageable = PageRequest.of(0, 10000);
|
|
|
+ Page<LogEntry> allLogs = logEntryRepository.findByTimestampBetween(startTime, endTime, pageable);
|
|
|
+
|
|
|
+ List<LogEntry> messageResponseLogs = allLogs.getContent().stream()
|
|
|
+ .filter(log -> "D4_COMM_MESSAGE_RESPONSE".equals(log.getBizName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<LogEntry> collabEffectiveLogs = allLogs.getContent().stream()
|
|
|
+ .filter(log -> "D4_COLLAB_EFFECTIVE".equals(log.getBizName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<LogEntry> conflictCreatedLogs = allLogs.getContent().stream()
|
|
|
+ .filter(log -> "D4_COLLAB_CONFLICT_CREATED".equals(log.getBizName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<LogEntry> conflictResolvedLogs = allLogs.getContent().stream()
|
|
|
+ .filter(log -> "D4_COLLAB_CONFLICT_RESOLVED".equals(log.getBizName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ double avgResponseTimeMs = calculateAvgResponseTime(messageResponseLogs);
|
|
|
+ long totalEffectiveCollaborations = collabEffectiveLogs.size();
|
|
|
+ long totalConflictResolutions = conflictResolvedLogs.size();
|
|
|
+ double conflictResolutionRate = conflictCreatedLogs.size() > 0 ?
|
|
|
+ (double) totalConflictResolutions / conflictCreatedLogs.size() : 0.0;
|
|
|
+
|
|
|
+ Map<String, Long> collaborationsBySource = calculateCollaborationsBySource(collabEffectiveLogs);
|
|
|
+ Map<String, Long> conflictsByType = calculateConflictsByType(conflictCreatedLogs);
|
|
|
+
|
|
|
+ List<D4Metrics.ResponseTimeDetail> recentResponseTimes = messageResponseLogs.stream()
|
|
|
+ .limit(10)
|
|
|
+ .map(this::convertToResponseTimeDetail)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<D4Metrics.ConflictResolutionDetail> recentResolutions = conflictResolvedLogs.stream()
|
|
|
+ .limit(10)
|
|
|
+ .map(this::convertToConflictResolutionDetail)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ return D4Metrics.D2Metrics.builder()
|
|
|
+ .avgResponseTimeMs(avgResponseTimeMs)
|
|
|
+ .totalEffectiveCollaborations(totalEffectiveCollaborations)
|
|
|
+ .totalConflictResolutions(totalConflictResolutions)
|
|
|
+ .conflictResolutionRate(conflictResolutionRate)
|
|
|
+ .collaborationsBySource(collaborationsBySource)
|
|
|
+ .conflictsByType(conflictsByType)
|
|
|
+ .recentResponseTimes(recentResponseTimes)
|
|
|
+ .recentResolutions(recentResolutions)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Double> calculateCompletionRateByTaskType(List<LogEntry> assignedLogs, List<LogEntry> completedLogs) {
|
|
|
+ Map<String, Long> assignedByType = assignedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("taskType"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> (String) log.getContext().get("taskType"),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ Map<String, Long> completedByType = completedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("taskType"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> (String) log.getContext().get("taskType"),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ return assignedByType.entrySet().stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ Map.Entry::getKey,
|
|
|
+ entry -> {
|
|
|
+ String taskType = entry.getKey();
|
|
|
+ long assigned = entry.getValue();
|
|
|
+ long completed = completedByType.getOrDefault(taskType, 0L);
|
|
|
+ return assigned > 0 ? (double) completed / assigned : 0.0;
|
|
|
+ }
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Double> calculateCompletionRateByProject(List<LogEntry> assignedLogs, List<LogEntry> completedLogs) {
|
|
|
+ Map<String, Long> assignedByProject = assignedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("projectId"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> String.valueOf(log.getContext().get("projectId")),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ Map<String, Long> completedByProject = completedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("projectId"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> String.valueOf(log.getContext().get("projectId")),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ return assignedByProject.entrySet().stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ Map.Entry::getKey,
|
|
|
+ entry -> {
|
|
|
+ String projectId = entry.getKey();
|
|
|
+ long assigned = entry.getValue();
|
|
|
+ long completed = completedByProject.getOrDefault(projectId, 0L);
|
|
|
+ return assigned > 0 ? (double) completed / assigned : 0.0;
|
|
|
+ }
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Double> calculateCompletionRateByUser(List<LogEntry> assignedLogs, List<LogEntry> completedLogs) {
|
|
|
+ Map<String, Long> assignedByUser = assignedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("creatorId"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> String.valueOf(log.getContext().get("creatorId")),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ Map<String, Long> completedByUser = completedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null &&
|
|
|
+ (log.getContext().containsKey("reviewerId") || log.getContext().containsKey("operatorId")))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> {
|
|
|
+ String userId = (String) log.getContext().getOrDefault("reviewerId",
|
|
|
+ log.getContext().get("operatorId"));
|
|
|
+ return String.valueOf(userId);
|
|
|
+ },
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ return assignedByUser.entrySet().stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ Map.Entry::getKey,
|
|
|
+ entry -> {
|
|
|
+ String userId = entry.getKey();
|
|
|
+ long assigned = entry.getValue();
|
|
|
+ long completed = completedByUser.getOrDefault(userId, 0L);
|
|
|
+ return assigned > 0 ? (double) completed / assigned : 0.0;
|
|
|
+ }
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ private double calculateOnTimeCompletionRate(List<LogEntry> completedLogs) {
|
|
|
+ final long SLA_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24小时
|
|
|
+
|
|
|
+ long onTimeCount = completedLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("taskDurationMs"))
|
|
|
+ .mapToLong(log -> {
|
|
|
+ Long duration = (Long) log.getContext().get("taskDurationMs");
|
|
|
+ return duration != null && duration <= SLA_THRESHOLD_MS ? 1L : 0L;
|
|
|
+ })
|
|
|
+ .sum();
|
|
|
+
|
|
|
+ return completedLogs.size() > 0 ? (double) onTimeCount / completedLogs.size() : 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double calculateAvgResponseTime(List<LogEntry> messageResponseLogs) {
|
|
|
+ return messageResponseLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("responseMs"))
|
|
|
+ .mapToLong(log -> {
|
|
|
+ Long responseMs = (Long) log.getContext().get("responseMs");
|
|
|
+ return responseMs != null ? responseMs : 0L;
|
|
|
+ })
|
|
|
+ .average()
|
|
|
+ .orElse(0.0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Long> calculateCollaborationsBySource(List<LogEntry> collabLogs) {
|
|
|
+ return collabLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("source"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> (String) log.getContext().get("source"),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Long> calculateConflictsByType(List<LogEntry> conflictLogs) {
|
|
|
+ return conflictLogs.stream()
|
|
|
+ .filter(log -> log.getContext() != null && log.getContext().containsKey("conflictType"))
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ log -> (String) log.getContext().get("conflictType"),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ private D4Metrics.TaskCompletionDetail convertToTaskCompletionDetail(LogEntry log) {
|
|
|
+ return D4Metrics.TaskCompletionDetail.builder()
|
|
|
+ .taskId(log.getContext() != null ? String.valueOf(log.getContext().get("taskId")) : null)
|
|
|
+ .taskType(log.getContext() != null ? (String) log.getContext().get("taskType") : null)
|
|
|
+ .projectId(log.getContext() != null ? String.valueOf(log.getContext().get("projectId")) : null)
|
|
|
+ .userId(log.getContext() != null ? String.valueOf(log.getContext().getOrDefault("reviewerId",
|
|
|
+ log.getContext().get("operatorId"))) : null)
|
|
|
+ .completedTime(log.getTimestamp())
|
|
|
+ .durationMs(log.getContext() != null ? (Long) log.getContext().get("taskDurationMs") : null)
|
|
|
+ .onTime(log.getContext() != null && log.getContext().containsKey("taskDurationMs") ?
|
|
|
+ ((Long) log.getContext().get("taskDurationMs")) <= 24 * 60 * 60 * 1000 : null)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private D4Metrics.ResponseTimeDetail convertToResponseTimeDetail(LogEntry log) {
|
|
|
+ return D4Metrics.ResponseTimeDetail.builder()
|
|
|
+ .messageId(log.getContext() != null ? String.valueOf(log.getContext().get("messageId")) : null)
|
|
|
+ .creatorId(log.getContext() != null ? String.valueOf(log.getContext().get("creatorId")) : null)
|
|
|
+ .receiverId(log.getContext() != null ? String.valueOf(log.getContext().get("receiverId")) : null)
|
|
|
+ .responseTime(log.getTimestamp())
|
|
|
+ .responseMs(log.getContext() != null ? (Long) log.getContext().get("responseMs") : null)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private D4Metrics.ConflictResolutionDetail convertToConflictResolutionDetail(LogEntry log) {
|
|
|
+ return D4Metrics.ConflictResolutionDetail.builder()
|
|
|
+ .conflictId(log.getContext() != null ? String.valueOf(log.getContext().get("taskId")) : null)
|
|
|
+ .conflictType(log.getContext() != null ? (String) log.getContext().get("conflictType") : null)
|
|
|
+ .projectId(log.getContext() != null ? String.valueOf(log.getContext().get("projectId")) : null)
|
|
|
+ .resolverId(log.getContext() != null ? String.valueOf(log.getContext().get("resolverId")) : null)
|
|
|
+ .resolvedTime(log.getTimestamp())
|
|
|
+ .resolutionTimeMs(null)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+}
|