sakthivikram/geo-location
0
1package com.georeport.service;2 3import com.georeport.dto.AnalyticsResponse;4import com.georeport.entity.Issue;5import com.georeport.entity.IssueStatus;6import com.georeport.repository.IssueRepository;7import org.springframework.beans.factory.annotation.Autowired;8import org.springframework.stereotype.Service;9import org.springframework.transaction.annotation.Transactional;10 11import org.springframework.data.domain.PageRequest;12import org.springframework.data.domain.Sort;13import java.time.Duration;14import java.time.LocalDate;15import java.time.LocalDateTime;16import java.util.*;17import java.util.stream.Collectors;18 19/**20 * Service for generating analytics data for the admin dashboard.21 */22@Service23public class AnalyticsService {24 25 @Autowired26 private IssueRepository issueRepository;27 28 /**29 * Get comprehensive analytics data30 */31 @Transactional(readOnly = true)32 public AnalyticsResponse getAnalytics(LocalDateTime startDate, LocalDateTime endDate) {33 // If no date range provided, use last 30 days as default for detailed metrics34 LocalDateTime actualStart = (startDate != null) ? startDate : LocalDateTime.now().minusDays(30);35 LocalDateTime actualEnd = (endDate != null) ? endDate : LocalDateTime.now();36 37 // Fetch metrics using optimized repository queries instead of fetching ALL38 // issues39 List<Object[]> statusCounts = issueRepository.getIssueCountByStatusSince(actualStart);40 Map<String, Long> byStatus = new HashMap<>();41 for (Object[] row : statusCounts) {42 byStatus.put(((IssueStatus) row[0]).name(), (Long) row[1]);43 }44 45 List<Issue> filteredIssues = issueRepository.findWithFilters(null, null, null, actualStart, actualEnd,46 PageRequest.of(0, 1000, Sort.by("createdAt").descending())).getContent();47 48 return AnalyticsResponse.builder()49 .byStatus(byStatus)50 .byCategory(getCountByCategory(filteredIssues))51 .byPriority(getCountByPriority(filteredIssues))52 .byWard(getCountByWard(filteredIssues))53 .issueTrends(getIssueTrends(filteredIssues))54 .avgResolutionTimeByCategory(getAvgResolutionTimeByCategory(filteredIssues))55 .hotspots(getHotspots(filteredIssues))56 .overall(getOverallStatsOptimized(actualStart)) // Use optimized stats57 .build();58 }59 60 /**61 * Get issue trends (issues per day for last 30 days)62 */63 @Transactional(readOnly = true)64 public Map<String, Long> getTrends(int days) {65 LocalDateTime startDate = LocalDateTime.now().minusDays(days);66 List<Issue> issues = issueRepository.findAll().stream()67 .filter(i -> i.getCreatedAt() != null && i.getCreatedAt().isAfter(startDate))68 .collect(Collectors.toList());69 70 return getIssueTrends(issues);71 }72 73 /**74 * Get category distribution75 */76 @Transactional(readOnly = true)77 public Map<String, Long> getCategoryDistribution() {78 return getCountByCategory(issueRepository.findAll());79 }80 81 /**82 * Get average resolution time by category83 */84 @Transactional(readOnly = true)85 public Map<String, Double> getResolutionTimes() {86 return getAvgResolutionTimeByCategory(issueRepository.findAll());87 }88 89 /**90 * Get hotspot data for heatmap91 */92 @Transactional(readOnly = true)93 public List<AnalyticsResponse.HotspotData> getHotspotData() {94 return getHotspots(issueRepository.findAll());95 }96 97 // Helper methods98 99 private Map<String, Long> getCountByStatus(List<Issue> issues) {100 return issues.stream()101 .filter(i -> i.getStatus() != null)102 .collect(Collectors.groupingBy(103 i -> i.getStatus().name(),104 Collectors.counting()));105 }106 107 private Map<String, Long> getCountByCategory(List<Issue> issues) {108 return issues.stream()109 .filter(i -> i.getCategory() != null)110 .collect(Collectors.groupingBy(111 i -> i.getCategory().getName(),112 Collectors.counting()));113 }114 115 private Map<String, Long> getCountByPriority(List<Issue> issues) {116 return issues.stream()117 .filter(i -> i.getPriority() != null)118 .collect(Collectors.groupingBy(119 i -> i.getPriority().name(),120 Collectors.counting()));121 }122 123 private Map<String, Long> getCountByWard(List<Issue> issues) {124 return issues.stream()125 .filter(i -> i.getWard() != null && !i.getWard().trim().isEmpty())126 .collect(Collectors.groupingBy(127 Issue::getWard,128 Collectors.counting()));129 }130 131 private Map<String, Long> getIssueTrends(List<Issue> issues) {132 Map<String, Long> trends = new LinkedHashMap<>();133 134 // Get last 30 days135 LocalDate today = LocalDate.now();136 for (int i = 29; i >= 0; i--) {137 LocalDate date = today.minusDays(i);138 trends.put(date.toString(), 0L);139 }140 141 // Count issues per day142 issues.stream()143 .filter(i -> i.getCreatedAt() != null)144 .forEach(issue -> {145 String dateKey = issue.getCreatedAt().toLocalDate().toString();146 if (trends.containsKey(dateKey)) {147 trends.put(dateKey, trends.get(dateKey) + 1);148 }149 });150 151 return trends;152 }153 154 private Map<String, Double> getAvgResolutionTimeByCategory(List<Issue> issues) {155 Map<String, List<Double>> resolutionTimes = new HashMap<>();156 157 issues.stream()158 .filter(i -> i.getStatus() == IssueStatus.RESOLVED &&159 i.getCreatedAt() != null &&160 i.getResolvedAt() != null &&161 i.getCategory() != null)162 .forEach(issue -> {163 Duration duration = Duration.between(issue.getCreatedAt(),164 issue.getResolvedAt());165 double hours = duration.toHours();166 String category = issue.getCategory().getName();167 168 resolutionTimes.computeIfAbsent(category, k -> new ArrayList<>()).add(hours);169 });170 171 return resolutionTimes.entrySet().stream()172 .collect(Collectors.toMap(173 Map.Entry::getKey,174 e -> e.getValue().stream().mapToDouble(Double::doubleValue).average()175 .orElse(0.0)));176 }177 178 private List<AnalyticsResponse.HotspotData> getHotspots(List<Issue> issues) {179 // Group issues by approximate location (grid-based clustering)180 Map<String, List<Issue>> locationGroups = new HashMap<>();181 double gridSize = 0.01; // ~1km grid182 183 issues.stream()184 .filter(i -> i.getLatitude() != null && i.getLongitude() != null)185 .forEach(issue -> {186 double gridLat = Math.round(issue.getLatitude() / gridSize) * gridSize;187 double gridLng = Math.round(issue.getLongitude() / gridSize) * gridSize;188 String key = gridLat + "," + gridLng;189 locationGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(issue);190 });191 192 return locationGroups.entrySet().stream()193 .map(entry -> {194 String[] coords = entry.getKey().split(",");195 return AnalyticsResponse.HotspotData.builder()196 .latitude(Double.parseDouble(coords[0]))197 .longitude(Double.parseDouble(coords[1]))198 .count((long) entry.getValue().size())199 .build();200 })201 .sorted((a, b) -> Long.compare(b.getCount(), a.getCount()))202 .limit(100) // Top 100 hotspots203 .collect(Collectors.toList());204 }205 206 private AnalyticsResponse.OverallStats getOverallStatsOptimized(LocalDateTime since) {207 long total = issueRepository.count();208 209 LocalDateTime monthStart = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0);210 LocalDateTime weekStart = LocalDateTime.now().minusDays(7);211 212 long resolvedThisMonth = issueRepository.countByStatusAndResolvedAtBetween(IssueStatus.RESOLVED,213 monthStart, LocalDateTime.now());214 long newThisWeek = issueRepository.countByCreatedAtBetween(weekStart, LocalDateTime.now());215 216 Double avgResolutionHours = issueRepository217 .getAverageResolutionTimeSince(LocalDateTime.now().minusMonths(6));218 219 long resolved = issueRepository.countByStatus(IssueStatus.RESOLVED);220 double resolutionRate = total > 0 ? (resolved * 100.0 / total) : 0.0;221 222 return AnalyticsResponse.OverallStats.builder()223 .totalIssues(total)224 .resolvedThisMonth(resolvedThisMonth)225 .newThisWeek(newThisWeek)226 .avgResolutionTimeHours(avgResolutionHours != null227 ? Math.round(avgResolutionHours * 10.0) / 10.0228 : 0.0)229 .resolutionRate(Math.round(resolutionRate * 10.0) / 10.0)230 .build();231 }232}233 