Skip to content

Commit

Permalink
Merge pull request #93 from Happy-HOBAK/feat/#92
Browse files Browse the repository at this point in the history
[#92] 전체, 연간, 월간 행복 종합 리포트 API 구축
  • Loading branch information
KkomSang authored May 26, 2024
2 parents b5ff369 + 63f8e30 commit d71f917
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public interface RecordRepository extends JpaRepository<Record, Long> {
Page<Record> findByUserOrderByRecordIdDesc(User user, Pageable pageRequest);
Page<Record> findByRecordIdLessThanAndUserOrderByRecordIdDesc(Long recordId, User user, Pageable pageRequest);
List<Record> findAllByCreatedAtBetweenAndUser(LocalDateTime startOfMonth, LocalDateTime endOfMonth, User user);
List<Record> findAllByUser(User user);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
package com.hobak.happinessql.domain.report.api;


import com.hobak.happinessql.domain.report.application.ReportSummaryService;
import com.hobak.happinessql.domain.report.dto.ReportSummaryResponseDto;
import com.hobak.happinessql.domain.user.application.UserFindService;
import com.hobak.happinessql.domain.user.domain.User;
import com.hobak.happinessql.global.response.DataResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name="Report", description = "행복 분석 리포트 관련 REST API에 대한 명세를 제공합니다.")
@RestController
@RequiredArgsConstructor
@RequestMapping("api/my-report")
@RequestMapping("api/report")
public class ReportController {

private final UserFindService userFindService;
private final ReportSummaryService reportSummaryService;
@Operation(summary = "[전체] 행복 종합 리포트", description = "언제, 어디에서, 무엇을 할 때 행복했는지에 대한 종합적인 리포트를 제공합니다.")
@GetMapping("/all/summary")
public DataResponseDto<ReportSummaryResponseDto> getAllSummary(@AuthenticationPrincipal UserDetails userDetails) {
User user = userFindService.findByUserDetails(userDetails);
ReportSummaryResponseDto responseDto = reportSummaryService.getAllSummary(user);
return DataResponseDto.of(responseDto, "행복 종합 리포트(전체)를 성공적으로 조회했습니다.");
}

@Operation(summary = "[연간] 행복 종합 리포트", description = "이번 해 언제, 어디에서, 무엇을 할 때 행복했는지에 대한 종합적인 리포트를 제공합니다.")
@GetMapping("/year/summary")
public DataResponseDto<ReportSummaryResponseDto> getAnnualSummary(@AuthenticationPrincipal UserDetails userDetails) {
User user = userFindService.findByUserDetails(userDetails);
ReportSummaryResponseDto responseDto = reportSummaryService.getAnnualSummary(user);
return DataResponseDto.of(responseDto, "행복 종합 리포트(연간)를 성공적으로 조회했습니다.");
}

@Operation(summary = "[월간] 행복 종합 리포트", description = "이번 달 언제, 어디에서, 무엇을 할 때 행복했는지에 대한 종합적인 리포트를 제공합니다.")
@GetMapping("/month/summary")
public DataResponseDto<ReportSummaryResponseDto> getMonthlySummary(@AuthenticationPrincipal UserDetails userDetails) {
User user = userFindService.findByUserDetails(userDetails);
ReportSummaryResponseDto responseDto = reportSummaryService.getMonthlySummary(user);
return DataResponseDto.of(responseDto, "행복 종합 리포트(월간)를 성공적으로 조회했습니다.");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.hobak.happinessql.domain.report.application;

import com.hobak.happinessql.domain.record.domain.Record;

import java.util.*;
import java.util.stream.Collectors;

public class ActivityHappinessAnalyzer {

public static String getHappiestActivity(List<Record> records) {

if (records == null || records.isEmpty()) {
return null;
}

// Activity를 기준으로 Record 그룹화
Map<String, List<Record>> activityRecordsMap = records.stream()
.filter(record -> record.getActivity() != null)
.collect(Collectors.groupingBy(record -> record.getActivity().getName()));

// 각 Activity별 평균 행복도와 빈도 계산
Map<String, Double> activityAverageHappiness = new HashMap<>();
Map<String, Integer> activityFrequency = new HashMap<>();

activityRecordsMap.forEach((activity, recordList) -> {
activityAverageHappiness.put(activity, recordList.stream()
.mapToInt(Record::getHappiness)
.average()
.orElse(Double.NaN));
activityFrequency.put(activity, recordList.size());
});

// 평균 행복도가 가장 높은 Activity 찾기
double maxHappiness = Collections.max(activityAverageHappiness.values());



// 평균 행복도가 가장 높은 Activity들 중 빈도가 가장 높은 Activity 찾기
Optional<String> happiestActivity = activityAverageHappiness.entrySet().stream()
.filter(entry -> entry.getValue() == maxHappiness)
.max(Comparator.comparing(entry -> activityFrequency.get(entry.getKey())))
.map(Map.Entry::getKey);

System.out.println("평균 행복도가 높은 Activity : " + happiestActivity + "행복도 : " + maxHappiness);
// 평균 행복도와 빈도가 같은 Activity가 여러 개인 경우, 랜덤으로 선택
List<String> happiestActivities = activityAverageHappiness.entrySet().stream()
.filter(entry -> entry.getValue() == maxHappiness)
.map(Map.Entry::getKey)
.collect(Collectors.toList());

if (happiestActivities.size() > 1) {
Collections.shuffle(happiestActivities);
return happiestActivities.get(0);
} else {
return happiestActivity.orElse(null);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.hobak.happinessql.domain.report.application;

import com.hobak.happinessql.domain.record.domain.Record;

import java.util.*;
import java.util.stream.Collectors;

public class LocationHappinessAnalyzer {

public static String getHappiestLocation(List<Record> records) {

if (records == null || records.isEmpty()) {
return null;
}

// 도시와 구를 기준으로 Record 그룹화
Map<String, List<Record>> locationRecordsMap = records.stream()
.filter(record -> record.getLocation() != null)
.collect(Collectors.groupingBy(record ->
record.getLocation().getCity() + " " + record.getLocation().getDistrict()));

// 각 위치별 평균 행복도와 빈도 계산
Map<String, Double> locationAverageHappiness = new HashMap<>();
Map<String, Integer> locationFrequency = new HashMap<>();

locationRecordsMap.forEach((location, recordList) -> {
locationAverageHappiness.put(location, recordList.stream()
.mapToInt(Record::getHappiness)
.average()
.orElse(Double.NaN));
locationFrequency.put(location, recordList.size());
});

// 평균 행복도가 가장 높은 위치 찾기
double maxHappiness = Collections.max(locationAverageHappiness.values());

// 평균 행복도가 가장 높은 위치들 중 빈도가 가장 높은 위치를 찾기
Optional<String> happiestLocation = locationAverageHappiness.entrySet().stream()
.filter(entry -> entry.getValue() == maxHappiness)
.max(Comparator.comparing(entry -> locationFrequency.get(entry.getKey())))
.map(Map.Entry::getKey);

// 평균 행복도와 빈도가 같다면, 랜덤으로 선택
List<String> happiestLocations = locationAverageHappiness.entrySet().stream()
.filter(entry -> entry.getValue() == maxHappiness)
.map(Map.Entry::getKey)
.collect(Collectors.toList());

if (happiestLocations.size() > 1) {
Collections.shuffle(happiestLocations);
return happiestLocations.get(0);
} else {
return happiestLocation.orElse(null);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.hobak.happinessql.domain.report.application;

import com.hobak.happinessql.domain.record.domain.Record;
import com.hobak.happinessql.domain.record.repository.RecordRepository;
import com.hobak.happinessql.domain.report.converter.ReportConverter;
import com.hobak.happinessql.domain.report.domain.TimePeriod;
import com.hobak.happinessql.domain.report.dto.ReportSummaryResponseDto;
import com.hobak.happinessql.domain.user.domain.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;

import java.util.List;

@Service
@RequiredArgsConstructor
public class ReportSummaryService {

private final RecordRepository recordRepository;

public ReportSummaryResponseDto getAllSummary(User user) {
List<Record> records = recordRepository.findAllByUser(user);
return generateReportSummary(records);
}

public ReportSummaryResponseDto getAnnualSummary(User user) {
int currentYear = LocalDate.now().getYear();
LocalDateTime startOfYear = LocalDateTime.of(currentYear, 1, 1, 0, 0);
LocalDateTime endOfYear = LocalDateTime.of(currentYear, 12, 31, 23, 59, 59);

List<Record> annualRecords = recordRepository.findAllByCreatedAtBetweenAndUser(startOfYear, endOfYear, user);
return generateReportSummary(annualRecords);
}

public ReportSummaryResponseDto getMonthlySummary(User user) {
LocalDate today = LocalDate.now();
LocalDateTime startOfMonth = today.withDayOfMonth(1).atStartOfDay();
LocalDateTime endOfMonth = today.withDayOfMonth(today.lengthOfMonth()).atTime(23, 59, 59);

List<Record> monthlyRecords = recordRepository.findAllByCreatedAtBetweenAndUser(startOfMonth, endOfMonth, user);
return generateReportSummary(monthlyRecords);
}

private ReportSummaryResponseDto generateReportSummary(List<Record> records) {
String location = LocationHappinessAnalyzer.getHappiestLocation(records);
TimePeriod timePeriod = TimePeriodHappinessAnalyzer.getHappiestTimePeriod(records);
String activity = ActivityHappinessAnalyzer.getHappiestActivity(records);

return ReportConverter.toReportSummaryResponseDto(timePeriod, location, activity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.hobak.happinessql.domain.report.application;

import com.hobak.happinessql.domain.record.domain.Record;
import com.hobak.happinessql.domain.report.domain.TimePeriod;

import java.util.*;
import java.util.stream.Collectors;

public class TimePeriodHappinessAnalyzer {

public static TimePeriod getHappiestTimePeriod(List<Record> records) {

if (records == null || records.isEmpty()) {
return null;
}

Map<TimePeriod, List<Integer>> timePeriodHappinessMap = new HashMap<>();

// 시간대별 행복도를 매핑
for (Record record : records) {
TimePeriod timePeriod = TimePeriod.of(record.getCreatedAt().getHour());
Integer happiness = record.getHappiness();

timePeriodHappinessMap.computeIfAbsent(timePeriod, k -> new ArrayList<>()).add(happiness);
}

// 평균 행복도 계산
Map<TimePeriod, Double> averageHappinessMap = timePeriodHappinessMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> entry.getValue().stream().mapToInt(Integer::intValue).average().orElse(0)));

// 최대 평균 행복도를 가진 시간대 찾기
double maxAverageHappiness = averageHappinessMap.values().stream()
.max(Double::compare)
.orElse(Double.MIN_VALUE);

// 최대 평균 행복도를 가진 시간대 후보들 필터링
List<TimePeriod> candidates = averageHappinessMap.entrySet().stream()
.filter(entry -> entry.getValue() == maxAverageHappiness)
.map(Map.Entry::getKey)
.toList();

if (candidates.isEmpty()) {
return null;
}

// 후보 중 빈도수가 가장 높은 시간대 찾기, 동일 빈도시 랜덤 선택
return candidates.stream()
.max(Comparator.comparingInt(tp -> timePeriodHappinessMap.get(tp).size()))
.orElseGet(() -> candidates.get(new Random().nextInt(candidates.size())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hobak.happinessql.domain.report.converter;

import com.hobak.happinessql.domain.report.domain.TimePeriod;
import com.hobak.happinessql.domain.report.dto.ReportSummaryResponseDto;

public class ReportConverter {
public static ReportSummaryResponseDto toReportSummaryResponseDto(TimePeriod timePeriod, String location, String activity) {
return ReportSummaryResponseDto.builder()
.activity(activity)
.location(location)
.timePeriod(timePeriod)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.hobak.happinessql.domain.report.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public enum TimePeriod {
DAWN("새벽"),
MORNING("아침"),
AFTERNOON("낮"),
EVENING("저녁"),
NIGHT("밤");

private final String viewName;

public static TimePeriod of(int hour) {
if (hour >= 0 && hour < 5) {
return DAWN;
} else if (hour >= 5 && hour < 9) {
return MORNING;
} else if (hour >= 9 && hour < 17) {
return AFTERNOON;
} else if (hour >= 17 && hour < 21) {
return EVENING;
} else {
return NIGHT;
}
}

@JsonCreator
public static TimePeriod from(String value) {
for (TimePeriod status : TimePeriod.values()) {
if (status.getViewName().equals(value)) {
return status;
}
}
return null;
}

@JsonValue
public String getViewName() {
return viewName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.hobak.happinessql.domain.report.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.hobak.happinessql.domain.report.domain.TimePeriod;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;


@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReportSummaryResponseDto {

@JsonProperty("time_period")
private TimePeriod timePeriod;

private String location;

private String activity;

@Builder
public ReportSummaryResponseDto(TimePeriod timePeriod, String location, String activity) {
this.timePeriod = timePeriod;
this.location = location;
this.activity = activity;
}
}

0 comments on commit d71f917

Please sign in to comment.