improvement: 대시보드 KPI 변화량/변화율 필드 추가 (#262) #263

Merged
seonkyu.kim merged 1 commits from improvement/#262-dashboard-kpi-change-fields into develop 2026-02-28 14:06:14 +00:00
2 changed files with 50 additions and 0 deletions

View File

@ -46,6 +46,15 @@ public class DashboardKpiDto
[JsonPropertyName("active_service_count")] [JsonPropertyName("active_service_count")]
public int ActiveServiceCount { get; set; } public int ActiveServiceCount { get; set; }
[JsonPropertyName("success_rate_change")]
public double SuccessRateChange { get; set; }
[JsonPropertyName("device_count_change")]
public int DeviceCountChange { get; set; }
[JsonPropertyName("today_sent_change_rate")]
public double TodaySentChangeRate { get; set; }
[JsonPropertyName("today")] [JsonPropertyName("today")]
public PeriodStatDto Today { get; set; } = new(); public PeriodStatDto Today { get; set; } = new();

View File

@ -433,6 +433,44 @@ public class StatsService : IStatsService
var activeServiceCount = await _serviceRepository.CountAsync(s => s.Status == ServiceStatus.Active && !s.IsDeleted); var activeServiceCount = await _serviceRepository.CountAsync(s => s.Status == ServiceStatus.Active && !s.IsDeleted);
var topMessages = await _messageRepository.GetTopBySendCountAsync(serviceId, 5); var topMessages = await _messageRepository.GetTopBySendCountAsync(serviceId, 5);
// KPI 변화량 계산
var (startDate, endDate) = ParseDateRange(request.StartDate, request.EndDate);
// 1) success_rate_change: 직전 동일 기간 대비 성공률 변화 (pp)
var duration = endDate.DayNumber - startDate.DayNumber + 1;
var prevEnd = startDate.AddDays(-1);
var prevStart = prevEnd.AddDays(-(duration - 1));
var prevStats = await _dailyStatRepository.GetByDateRangeAsync(serviceId, prevStart, prevEnd);
var currentSend = daily.Summary.TotalSend;
var currentSuccess = daily.Summary.TotalSuccess;
var prevSend = prevStats.Sum(s => (long)s.SentCnt);
var prevSuccess = prevStats.Sum(s => (long)s.SuccessCnt);
var currentRate = currentSend > 0 ? (double)currentSuccess / currentSend * 100 : 0;
var prevRate = prevSend > 0 ? (double)prevSuccess / prevSend * 100 : 0;
var successRateChange = prevSend > 0 ? Math.Round(currentRate - prevRate, 1) : 0;
// 2) device_count_change: 오늘 신규 등록 디바이스 수
var todayDateTime = DateTime.UtcNow.Date;
var tomorrowDateTime = todayDateTime.AddDays(1);
var deviceCountChange = serviceId.HasValue
? await _deviceRepository.CountAsync(d => d.ServiceId == serviceId.Value
&& d.CreatedAt >= todayDateTime && d.CreatedAt < tomorrowDateTime)
: await _deviceRepository.CountAsync(d =>
d.CreatedAt >= todayDateTime && d.CreatedAt < tomorrowDateTime);
// 3) today_sent_change_rate: 오늘 vs 어제 발송 수 변화율 (%)
var today = DateOnly.FromDateTime(DateTime.UtcNow);
var yesterday = today.AddDays(-1);
var yesterdayStats = await _dailyStatRepository.GetByDateRangeAsync(serviceId, yesterday, yesterday);
var yesterdaySent = yesterdayStats.Sum(s => s.SentCnt);
var todaySent = summary.Today.SendCount;
var todaySentChangeRate = yesterdaySent > 0
? Math.Round((double)(todaySent - yesterdaySent) / yesterdaySent * 100, 1)
: 0;
return new DashboardResponseDto return new DashboardResponseDto
{ {
Kpi = new DashboardKpiDto Kpi = new DashboardKpiDto
@ -445,6 +483,9 @@ public class StatsService : IStatsService
TotalOpen = summary.TotalOpen, TotalOpen = summary.TotalOpen,
AvgCtr = summary.AvgCtr, AvgCtr = summary.AvgCtr,
ActiveServiceCount = activeServiceCount, ActiveServiceCount = activeServiceCount,
SuccessRateChange = successRateChange,
DeviceCountChange = deviceCountChange,
TodaySentChangeRate = todaySentChangeRate,
Today = summary.Today, Today = summary.Today,
ThisMonth = summary.ThisMonth ThisMonth = summary.ThisMonth
}, },