Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devkor.ifive.nadab.domain.stats.application;

import com.devkor.ifive.nadab.domain.stats.application.helper.PeakStatsTracker;
import com.devkor.ifive.nadab.domain.stats.core.dto.daily.DailyPeriodStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.daily.DailyStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.daily.DateCountDto;
import com.devkor.ifive.nadab.domain.stats.core.dto.peak.PeakMetric;
Expand All @@ -26,33 +27,34 @@ public class DailyStatsService {
private static final ZoneId SEOUL = ZoneId.of("Asia/Seoul");
private static final DateTimeFormatter FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final int DAILY_CHART_SIZE = 7;

public DailyStatsViewModel getDailyStatsLast7Days() {
public DailyStatsViewModel getDailyStats(LocalDate selectedDate) {
LocalDate today = TodayDateTimeProvider.getTodayDate();
LocalDate startDate = today.minusDays(6);
LocalDate startDate = selectedDate.minusDays(DAILY_CHART_SIZE - 1L);

// 라벨 7개 고정 생성
List<LocalDate> days = new ArrayList<>();
for (int i = 0; i < 7; i++) days.add(startDate.plusDays(i));
for (int i = 0; i < DAILY_CHART_SIZE; i++) days.add(startDate.plusDays(i));

List<String> labels = days.stream().map(LocalDate::toString).toList();

// 1) 가입자 수
Map<LocalDate, Long> signupMap = new HashMap<>();
for (Object[] row : repo.findSignupCountsLast7Days(startDate, today)) {
for (Object[] row : repo.findSignupCountsLast7Days(startDate, selectedDate)) {
DateCountDto dto = DailyStatsRepository.toDateCountDto(row);
signupMap.put(dto.date(), dto.count());
}

// 2) 할당 질문 수
Map<LocalDate, Long> assignedMap = new HashMap<>();
for (DateCountDto dto : repo.findAssignedQuestionCountsLast7Days(startDate, today)) {
for (DateCountDto dto : repo.findAssignedQuestionCountsLast7Days(startDate, selectedDate)) {
assignedMap.put(dto.date(), dto.count());
}

// 3) COMPLETED 리포트 수
Map<LocalDate, Long> completedMap = new HashMap<>();
for (DateCountDto dto : repo.findCompletedDailyReportCountsLast7Days(startDate, today)) {
for (DateCountDto dto : repo.findCompletedDailyReportCountsLast7Days(startDate, selectedDate)) {
completedMap.put(dto.date(), dto.count());
}

Expand All @@ -71,17 +73,26 @@ public DailyStatsViewModel getDailyStatsLast7Days() {
PeakMetric.DAU, days, completedCounts, today
);

long sharedNow = repo.countSharedDailyReportsNow();
int selectedIndex = days.size() - 1;
long sharedDailyReportCount = repo.countSharedDailyReports(selectedDate);
DailyPeriodStatsViewModel selectedPeriod = new DailyPeriodStatsViewModel(
selectedDate.toString(),
selectedDate.toString(),
signupCounts.get(selectedIndex),
assignedCounts.get(selectedIndex),
completedCounts.get(selectedIndex),
sharedDailyReportCount
);

return new DailyStatsViewModel(
labels,
signupCounts,
assignedCounts,
completedCounts,
selectedPeriod,
signupPeak,
assignedQuestionPeak,
dauPeak,
sharedNow,
OffsetDateTime.now(SEOUL).format(FMT)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.devkor.ifive.nadab.domain.stats.application.helper.PeakStatsTracker;
import com.devkor.ifive.nadab.domain.stats.core.dto.daily.DateCountDto;
import com.devkor.ifive.nadab.domain.stats.core.dto.monthly.MonthlyStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.monthly.MonthlyPeriodStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.peak.PeakMetric;
import com.devkor.ifive.nadab.domain.stats.core.dto.peak.PeakStatViewModel;
import com.devkor.ifive.nadab.domain.stats.core.repository.MonthlyStatsRepository;
Expand Down Expand Up @@ -34,13 +35,13 @@ public class MonthlyStatsService {
DateTimeFormatter.ofPattern("yyyy-MM");
private static final int MONTHLY_CHART_SIZE = 5;

public MonthlyStatsViewModel getMonthlyStatsLast5Months() {
public MonthlyStatsViewModel getMonthlyStats(YearMonth selectedMonth) {
LocalDate today = TodayDateTimeProvider.getTodayDate();
YearMonth currentMonth = YearMonth.from(today);
YearMonth startMonth = currentMonth.minusMonths(MONTHLY_CHART_SIZE - 1L);
YearMonth startMonth = selectedMonth.minusMonths(MONTHLY_CHART_SIZE - 1L);

LocalDate startDate = startMonth.atDay(1);
LocalDate endDateInclusive = currentMonth.atEndOfMonth();
LocalDate endDateInclusive = selectedMonth.atEndOfMonth();

List<YearMonth> months = new ArrayList<>();
for (int i = 0; i < MONTHLY_CHART_SIZE; i++) {
Expand Down Expand Up @@ -99,6 +100,18 @@ public MonthlyStatsViewModel getMonthlyStatsLast5Months() {
long inProgressV1Now = repo.countInProgressMonthlyReportV1Now();
long inProgressV2Now = repo.countInProgressMonthlyReportV2Now();
long inProgressTotalNow = inProgressV1Now + inProgressV2Now;
int selectedIndex = months.size() - 1;
MonthlyPeriodStatsViewModel selectedPeriod = new MonthlyPeriodStatsViewModel(
selectedMonth.toString(),
selectedMonth.toString(),
signupCounts.get(selectedIndex),
assignedCounts.get(selectedIndex),
completedDailyCounts.get(selectedIndex),
completedV1Counts.get(selectedIndex),
completedV2Counts.get(selectedIndex),
completedTotalCounts.get(selectedIndex),
mauCounts.get(selectedIndex)
);

return new MonthlyStatsViewModel(
labels,
Expand All @@ -109,6 +122,7 @@ public MonthlyStatsViewModel getMonthlyStatsLast5Months() {
completedV2Counts,
completedTotalCounts,
mauCounts,
selectedPeriod,
signupPeak,
assignedQuestionPeak,
completedDailyReportPeak,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package com.devkor.ifive.nadab.domain.stats.application;

import com.devkor.ifive.nadab.domain.stats.core.dto.total.LabelCountDto;
import com.devkor.ifive.nadab.domain.stats.core.dto.type.TypeReportDateInterestCountDto;
import com.devkor.ifive.nadab.domain.stats.core.dto.type.TypeReportInterestCountDto;
import com.devkor.ifive.nadab.domain.stats.core.dto.type.TypeReportInterestSeriesViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.type.TypeStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.repository.TypeStatsRepository;
import com.devkor.ifive.nadab.domain.user.core.entity.InterestCode;
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
Expand All @@ -20,22 +29,70 @@ public class TypeStatsService {
private static final ZoneId SEOUL = ZoneId.of("Asia/Seoul");
private static final DateTimeFormatter FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final int RECENT_DAYS = 7;

public TypeStatsViewModel getTypeStats() {
LocalDate today = TodayDateTimeProvider.getTodayDate();
LocalDate startDate = today.minusDays(RECENT_DAYS - 1L);

long inProgressTypeReportCount = repo.countInProgressTypeReportsNow();
List<LabelCountDto> completedByInterest = repo.countCompletedTypeReportsByInterest();
List<TypeReportInterestCountDto> activeCompletedByInterest =
repo.countActiveCompletedTypeReportsByInterest();
List<TypeReportInterestCountDto> completedHistoryByInterest =
repo.countCompletedTypeReportHistoryByInterest();
List<TypeReportDateInterestCountDto> completedByDateAndInterest =
repo.countCompletedTypeReportsByDateAndInterest(startDate, today);

Map<InterestCode, Long> activeCompletedCountMap = new EnumMap<>(InterestCode.class);
for (TypeReportInterestCountDto dto : activeCompletedByInterest) {
activeCompletedCountMap.put(dto.interestCode(), dto.count());
}

List<String> interestLabels = completedByInterest.stream()
.map(LabelCountDto::label)
Map<InterestCode, Long> completedHistoryCountMap = new EnumMap<>(InterestCode.class);
for (TypeReportInterestCountDto dto : completedHistoryByInterest) {
completedHistoryCountMap.put(dto.interestCode(), dto.count());
}

List<InterestCode> interests = List.of(InterestCode.values());
List<String> interestLabels = interests.stream()
.map(InterestCode::displayNameKo)
.toList();
List<Long> activeCompletedTypeReportCounts = interests.stream()
.map(interest -> activeCompletedCountMap.getOrDefault(interest, 0L))
.toList();
List<Long> completedTypeReportCounts = completedByInterest.stream()
.map(LabelCountDto::count)
List<Long> cumulativeCompletedTypeReportCounts = interests.stream()
.map(interest -> completedHistoryCountMap.getOrDefault(interest, 0L))
.toList();

List<LocalDate> recentDates = new ArrayList<>(RECENT_DAYS);
for (int i = 0; i < RECENT_DAYS; i++) {
recentDates.add(startDate.plusDays(i));
}

Map<LocalDate, Map<InterestCode, Long>> dailyCountMap = new HashMap<>();
for (TypeReportDateInterestCountDto dto : completedByDateAndInterest) {
dailyCountMap.computeIfAbsent(dto.date(), ignored -> new EnumMap<>(InterestCode.class))
.put(dto.interestCode(), dto.count());
}

List<TypeReportInterestSeriesViewModel> completedTypeReportSeries = interests.stream()
.map(interest -> new TypeReportInterestSeriesViewModel(
interest.name(),
interest.displayNameKo(),
recentDates.stream()
.map(date -> dailyCountMap.getOrDefault(date, Map.of())
.getOrDefault(interest, 0L))
.toList()
))
.toList();

return new TypeStatsViewModel(
inProgressTypeReportCount,
interestLabels,
completedTypeReportCounts,
activeCompletedTypeReportCounts,
cumulativeCompletedTypeReportCounts,
recentDates.stream().map(LocalDate::toString).toList(),
completedTypeReportSeries,
OffsetDateTime.now(SEOUL).format(FMT)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.devkor.ifive.nadab.domain.stats.application;

import com.devkor.ifive.nadab.domain.stats.application.helper.PeakStatsTracker;
import com.devkor.ifive.nadab.domain.stats.application.helper.StatsPeriodResolver;
import com.devkor.ifive.nadab.domain.stats.core.dto.daily.DateCountDto;
import com.devkor.ifive.nadab.domain.stats.core.dto.peak.PeakMetric;
import com.devkor.ifive.nadab.domain.stats.core.dto.peak.PeakStatViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.weekly.WeeklyStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.dto.weekly.WeeklyPeriodStatsViewModel;
import com.devkor.ifive.nadab.domain.stats.core.repository.WeeklyStatsRepository;
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -33,15 +35,17 @@ public class WeeklyStatsService {
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter WEEK_LABEL_FMT =
DateTimeFormatter.ofPattern("MM-dd");
private static final int WEEKLY_CHART_SIZE = 5;

public WeeklyStatsViewModel getWeeklyStatsLast7Weeks() {
public WeeklyStatsViewModel getWeeklyStats(LocalDate selectedWeek) {
LocalDate today = TodayDateTimeProvider.getTodayDate();
LocalDate currentWeekStart = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate startWeekStart = currentWeekStart.minusWeeks(6);
LocalDate endDateInclusive = currentWeekStart.plusDays(6);
LocalDate selectedWeekStart = selectedWeek.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate startWeekStart = selectedWeekStart.minusWeeks(WEEKLY_CHART_SIZE - 1L);
LocalDate endDateInclusive = selectedWeekStart.plusDays(6);

List<LocalDate> weekStarts = new ArrayList<>();
for (int i = 0; i < 7; i++) {
for (int i = 0; i < WEEKLY_CHART_SIZE; i++) {
weekStarts.add(startWeekStart.plusWeeks(i));
}

Expand Down Expand Up @@ -89,6 +93,16 @@ public WeeklyStatsViewModel getWeeklyStatsLast7Weeks() {
);

long inProgressNow = repo.countInProgressWeeklyReportsNow();
int selectedIndex = weekStarts.size() - 1;
WeeklyPeriodStatsViewModel selectedPeriod = new WeeklyPeriodStatsViewModel(
StatsPeriodResolver.formatIsoWeek(selectedWeekStart),
selectedWeekStart + " ~ " + selectedWeekStart.plusDays(6),
signupCounts.get(selectedIndex),
assignedCounts.get(selectedIndex),
completedDailyCounts.get(selectedIndex),
completedCounts.get(selectedIndex),
wauCounts.get(selectedIndex)
);

return new WeeklyStatsViewModel(
labels,
Expand All @@ -97,6 +111,7 @@ public WeeklyStatsViewModel getWeeklyStatsLast7Weeks() {
completedDailyCounts,
completedCounts,
wauCounts,
selectedPeriod,
signupPeak,
assignedQuestionPeak,
completedDailyReportPeak,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.devkor.ifive.nadab.domain.stats.application.helper;

import com.devkor.ifive.nadab.global.core.response.ErrorCode;
import com.devkor.ifive.nadab.global.exception.BadRequestException;
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.IsoFields;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;

public final class StatsPeriodResolver {

private static final DateTimeFormatter ISO_WEEK_FORMATTER = new DateTimeFormatterBuilder()
.appendValue(IsoFields.WEEK_BASED_YEAR, 4)
.appendLiteral("-W")
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter(Locale.ROOT);

private StatsPeriodResolver() {}

public static LocalDate resolveDaily(String value) {
return resolveDaily(value, TodayDateTimeProvider.getTodayDate());
}

public static LocalDate resolveWeekly(String value) {
return resolveWeekly(value, TodayDateTimeProvider.getTodayDate());
}

public static YearMonth resolveMonthly(String value) {
return resolveMonthly(value, TodayDateTimeProvider.getTodayDate());
}

public static String formatIsoWeek(LocalDate weekStart) {
return ISO_WEEK_FORMATTER.format(
weekStart.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
);
}

static LocalDate resolveDaily(String value, LocalDate today) {
LocalDate selectedDate = isBlank(value) ? today : parseDate(value);
if (selectedDate.isAfter(today)) {
throw invalidPeriod();
}
return selectedDate;
}

static LocalDate resolveWeekly(String value, LocalDate today) {
LocalDate currentWeekStart = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate selectedWeekStart = isBlank(value) ? currentWeekStart : parseWeekStart(value);
if (selectedWeekStart.isAfter(currentWeekStart)) {
throw invalidPeriod();
}
return selectedWeekStart;
}

static YearMonth resolveMonthly(String value, LocalDate today) {
YearMonth currentMonth = YearMonth.from(today);
YearMonth selectedMonth = isBlank(value) ? currentMonth : parseMonth(value);
if (selectedMonth.isAfter(currentMonth)) {
throw invalidPeriod();
}
return selectedMonth;
}

private static LocalDate parseDate(String value) {
try {
return LocalDate.parse(value);
} catch (DateTimeParseException e) {
throw invalidPeriod();
}
}

private static LocalDate parseWeekStart(String value) {
try {
return LocalDate.parse(value + "-1", DateTimeFormatter.ISO_WEEK_DATE);
} catch (DateTimeParseException e) {
throw invalidPeriod();
}
}

private static YearMonth parseMonth(String value) {
try {
return YearMonth.parse(value);
} catch (DateTimeParseException e) {
throw invalidPeriod();
}
}

private static boolean isBlank(String value) {
return value == null || value.isBlank();
}

private static BadRequestException invalidPeriod() {
return new BadRequestException(ErrorCode.VALIDATION_FAILED);
}
}
Loading
Loading