-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[#92] 전체, 연간, 월간 행복 종합 리포트 API 구축
- Loading branch information
Showing
9 changed files
with
345 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 36 additions & 1 deletion
37
src/main/java/com/hobak/happinessql/domain/report/api/ReportController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, "행복 종합 리포트(월간)를 성공적으로 조회했습니다."); | ||
} | ||
|
||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/com/hobak/happinessql/domain/report/application/ActivityHappinessAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
|
57 changes: 57 additions & 0 deletions
57
src/main/java/com/hobak/happinessql/domain/report/application/LocationHappinessAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
src/main/java/com/hobak/happinessql/domain/report/application/ReportSummaryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...ain/java/com/hobak/happinessql/domain/report/application/TimePeriodHappinessAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()))); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/main/java/com/hobak/happinessql/domain/report/converter/ReportConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/main/java/com/hobak/happinessql/domain/report/domain/TimePeriod.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/hobak/happinessql/domain/report/dto/ReportSummaryResponseDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |