feat : 분석 api 수정

This commit is contained in:
lsh9672 2025-06-18 15:31:00 +09:00
parent 52c7e7decc
commit 23c1d9d244
2 changed files with 109 additions and 14 deletions

View File

@ -4,6 +4,7 @@ import com.ktds.hi.analytics.biz.domain.ActionPlan;
import com.ktds.hi.analytics.biz.domain.Analytics; import com.ktds.hi.analytics.biz.domain.Analytics;
import com.ktds.hi.analytics.biz.domain.AiFeedback; import com.ktds.hi.analytics.biz.domain.AiFeedback;
import com.ktds.hi.analytics.biz.domain.PlanStatus; import com.ktds.hi.analytics.biz.domain.PlanStatus;
import com.ktds.hi.analytics.biz.domain.SentimentType;
import com.ktds.hi.analytics.biz.usecase.in.AnalyticsUseCase; import com.ktds.hi.analytics.biz.usecase.in.AnalyticsUseCase;
import com.ktds.hi.analytics.biz.usecase.out.*; import com.ktds.hi.analytics.biz.usecase.out.*;
import com.ktds.hi.analytics.infra.dto.*; import com.ktds.hi.analytics.infra.dto.*;
@ -13,10 +14,14 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
/** /**
* 분석 서비스 구현 클래스 (수정버전) * 분석 서비스 구현 클래스 (수정버전)
@ -36,6 +41,7 @@ public class AnalyticsService implements AnalyticsUseCase {
private final EventPort eventPort; private final EventPort eventPort;
private final ActionPlanPort actionPlanPort; // 추가된 의존성 private final ActionPlanPort actionPlanPort; // 추가된 의존성
@Override @Override
// @Cacheable(value = "storeAnalytics", key = "#storeId") // @Cacheable(value = "storeAnalytics", key = "#storeId")
public StoreAnalyticsResponse getStoreAnalytics(Long storeId) { public StoreAnalyticsResponse getStoreAnalytics(Long storeId) {
@ -280,8 +286,10 @@ public class AnalyticsService implements AnalyticsUseCase {
} }
// 3. 응답 생성 // 3. 응답 생성
int positiveCount = countPositiveReviews(recentReviews); ReviewSentimentCount sentimentCount = analyzeReviewSentiments(recentReviews);
int negativeCount = countNegativeReviews(recentReviews); int positiveCount = sentimentCount.getPositiveCount();
int negativeCount = sentimentCount.getNegativeCount();
int neutralCount = sentimentCount.getNeutralCount();
int totalCount = recentReviews.size(); int totalCount = recentReviews.size();
ReviewAnalysisResponse response = ReviewAnalysisResponse.builder() ReviewAnalysisResponse response = ReviewAnalysisResponse.builder()
@ -289,8 +297,10 @@ public class AnalyticsService implements AnalyticsUseCase {
.totalReviews(totalCount) .totalReviews(totalCount)
.positiveReviewCount(positiveCount) .positiveReviewCount(positiveCount)
.negativeReviewCount(negativeCount) .negativeReviewCount(negativeCount)
.positiveRate(Math.floor((double) positiveCount / totalCount * 100) / 10.0) .neutralReviewCount(neutralCount)
.negativeRate(Math.floor((double) negativeCount / totalCount * 100) / 10.0) .positiveRate(Math.floor((double) positiveCount / totalCount * 1000) / 10.0)
.negativeRate(Math.floor((double) negativeCount / totalCount * 1000) / 10.0)
.neutralRate(Math.floor((double) neutralCount / totalCount * 1000) / 10.0)
.analysisDate(LocalDate.now()) .analysisDate(LocalDate.now())
.build(); .build();
@ -306,6 +316,98 @@ public class AnalyticsService implements AnalyticsUseCase {
} }
} }
/**
* LLM 기반 리뷰 감정 분석 - 번의 분석으로 긍정/부정/중립 모두 반환
*
* @param reviews 분석할 리뷰 목록
* @return ReviewSentimentCount 감정별 리뷰
*/
private ReviewSentimentCount analyzeReviewSentiments(List<String> reviews) {
log.info("LLM 기반 리뷰 감정 분석 시작: 총 리뷰 수={}", reviews.size());
try {
if (reviews.isEmpty()) {
return new ReviewSentimentCount(0, 0, 0);
}
// 유효한 리뷰만 필터링
List<String> validReviews = reviews.stream()
.filter(review -> review != null && !review.trim().isEmpty())
.collect(Collectors.toList());
if (validReviews.isEmpty()) {
return new ReviewSentimentCount(0, 0, 0);
}
int positiveCount = 0;
int negativeCount = 0;
int neutralCount = 0;
// 리뷰를 AI로 감정 분석
for (String review : validReviews) {
try {
SentimentType sentiment = aiServicePort.analyzeSentiment(review);
switch (sentiment) {
case POSITIVE:
positiveCount++;
break;
case NEGATIVE:
negativeCount++;
break;
case NEUTRAL:
default:
neutralCount++;
break;
}
} catch (Exception e) {
log.warn("개별 리뷰 감정 분석 실패, 중립으로 처리: {}",
review.substring(0, Math.min(30, review.length())), e);
neutralCount++; // 분석 실패 중립으로 처리
}
}
ReviewSentimentCount result = new ReviewSentimentCount(positiveCount, negativeCount, neutralCount);
log.info("리뷰 감정 분석 완료: 긍정={}, 부정={}, 중립={}, 전체={}",
positiveCount, negativeCount, neutralCount, validReviews.size());
return result;
} catch (Exception e) {
log.error("리뷰 감정 분석 중 전체 오류 발생, fallback 사용", e);
// 오류 기존 가정값 사용
int total = reviews.size();
return new ReviewSentimentCount(
(int) (total * 0.6), // 60% 긍정
(int) (total * 0.2), // 20% 부정
total - (int) (total * 0.6) - (int) (total * 0.2) // 나머지 중립
);
}
}
/**
* 리뷰 감정 분석 결과를 담는 내부 클래스
*/
public static class ReviewSentimentCount {
private final int positiveCount;
private final int negativeCount;
private final int neutralCount;
public ReviewSentimentCount(int positiveCount, int negativeCount, int neutralCount) {
this.positiveCount = positiveCount;
this.negativeCount = negativeCount;
this.neutralCount = neutralCount;
}
public int getPositiveCount() { return positiveCount; }
public int getNegativeCount() { return negativeCount; }
public int getNeutralCount() { return neutralCount; }
public int getTotalCount() { return positiveCount + negativeCount + neutralCount; }
}
// private 메서드들 // private 메서드들
@Transactional @Transactional
public Analytics generateNewAnalytics(Long storeId) { public Analytics generateNewAnalytics(Long storeId) {
@ -429,15 +531,6 @@ public class AnalyticsService implements AnalyticsUseCase {
return "추천사항이 없습니다."; return "추천사항이 없습니다.";
} }
private int countPositiveReviews(List<String> reviews) {
// 실제로는 AI 서비스를 통한 감정 분석 필요
return (int) (reviews.size() * 0.6); // 60% 가정
}
private int countNegativeReviews(List<String> reviews) {
// 실제로는 AI 서비스를 통한 감정 분석 필요
return (int) (reviews.size() * 0.2); // 20% 가정
}
@Override @Override
@Transactional @Transactional

View File

@ -20,7 +20,9 @@ public class ReviewAnalysisResponse {
private Integer totalReviews; private Integer totalReviews;
private Integer positiveReviewCount; private Integer positiveReviewCount;
private Integer negativeReviewCount; private Integer negativeReviewCount;
private Integer neutralReviewCount;
private Double positiveRate; private Double positiveRate;
private Double negativeRate; private Double negativeRate;
private Double neutralRate;
private LocalDate analysisDate; private LocalDate analysisDate;
} }