Merge branch 'main' of https://github.com/dg04-hi/hi-backend
This commit is contained in:
commit
f12d1d5a8c
@ -29,6 +29,9 @@ public class AiFeedback {
|
||||
private List<String> recommendations;
|
||||
private String sentimentAnalysis;
|
||||
private Double confidenceScore;
|
||||
|
||||
private String positiveSummary;
|
||||
|
||||
private LocalDateTime generatedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@ -537,6 +537,7 @@ public class AnalyticsService implements AnalyticsUseCase {
|
||||
.recommendations(aiFeedback.getRecommendations())
|
||||
.sentimentAnalysis(aiFeedback.getSentimentAnalysis())
|
||||
.confidenceScore(aiFeedback.getConfidenceScore())
|
||||
.positiveSummary(aiFeedback.getPositiveSummary())
|
||||
.totalReviewsAnalyzed(getTotalReviewsCount(storeId, request.getDays()))
|
||||
.actionPlans(actionPlans) //TODO : 사용하는 값은 아니지만 의존성을 위해 그대로 둠, 추후에 변경 필요.
|
||||
.analyzedAt(aiFeedback.getGeneratedAt())
|
||||
@ -581,6 +582,11 @@ public class AnalyticsService implements AnalyticsUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomerPositiveReviewResponse getCustomerPositiveReview(Long storeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 AI를 호출하는 개선된 피드백 생성 메서드
|
||||
* 기존 generateAIFeedback()의 하드코딩 부분을 실제 AI 호출로 수정
|
||||
@ -613,6 +619,7 @@ public class AnalyticsService implements AnalyticsUseCase {
|
||||
.recommendations(aiFeedback.getRecommendations())
|
||||
.sentimentAnalysis(aiFeedback.getSentimentAnalysis())
|
||||
.confidenceScore(aiFeedback.getConfidenceScore())
|
||||
.positiveSummary(aiFeedback.getPositiveSummary())
|
||||
.generatedAt(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
|
||||
@ -46,4 +46,8 @@ public interface AnalyticsUseCase {
|
||||
*/
|
||||
List<String> generateActionPlansFromFeedback(ActionPlanCreateRequest request,Long feedbackId);
|
||||
|
||||
|
||||
// 🔥 고객용 긍정 리뷰 조회 API 추가
|
||||
CustomerPositiveReviewResponse getCustomerPositiveReview(Long storeId);
|
||||
|
||||
}
|
||||
|
||||
@ -35,4 +35,12 @@ public interface AIServicePort {
|
||||
* 실행 계획 생성
|
||||
*/
|
||||
List<String> generateActionPlan(List<String> actionPlanSelect, AiFeedback feedback);
|
||||
|
||||
// 🔥 고객용 긍정 리뷰 요약 생성 메서드 추가
|
||||
/**
|
||||
* 긍정적인 리뷰만을 분석하여 고객용 요약 생성
|
||||
* @param positiveReviews 긍정적인 리뷰 목록
|
||||
* @return 고객에게 보여줄 긍정적인 요약
|
||||
*/
|
||||
String generateCustomerPositiveSummary(List<String> positiveReviews);
|
||||
}
|
||||
|
||||
@ -18,6 +18,14 @@ public interface ExternalReviewPort {
|
||||
*/
|
||||
List<String> getRecentReviews(Long storeId, Integer days);
|
||||
|
||||
// 🔥 긍정적인 리뷰만 조회하는 메서드 추가
|
||||
/**
|
||||
* 긍정적인 리뷰만 조회 (평점 4점 이상)
|
||||
* @param storeId 매장 ID
|
||||
* @param days 조회 기간 (일)
|
||||
* @return 긍정적인 리뷰 목록
|
||||
*/
|
||||
List<String> getPositiveReviews(Long storeId, Integer days);
|
||||
|
||||
/**
|
||||
* 리뷰 개수 조회
|
||||
|
||||
@ -43,6 +43,9 @@ public class AiAnalysisResponse {
|
||||
@Schema(description = "감정 분석 결과")
|
||||
private String sentimentAnalysis;
|
||||
|
||||
@Schema(description = "긍정 분석 요약")
|
||||
private String positiveSummary;
|
||||
|
||||
@Schema(description = "신뢰도 점수")
|
||||
private Double confidenceScore;
|
||||
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package com.ktds.hi.analytics.infra.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 고객용 긍정 리뷰 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "고객용 긍정 리뷰 응답")
|
||||
public class CustomerPositiveReviewResponse {
|
||||
|
||||
@Schema(description = "매장 ID")
|
||||
private Long storeId;
|
||||
|
||||
@Schema(description = "긍정적인 리뷰 요약")
|
||||
private String positiveSummary;
|
||||
|
||||
|
||||
@Schema(description = "분석된 총 리뷰 수")
|
||||
private Integer totalReviewsAnalyzed;
|
||||
|
||||
@Schema(description = "분석 일시")
|
||||
private LocalDateTime analyzedAt;
|
||||
}
|
||||
|
||||
@ -275,6 +275,11 @@ public class AIServiceAdapter implements AIServicePort {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateCustomerPositiveSummary(List<String> positiveReviews) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenAI API를 호출하여 전체 리뷰 분석 수행
|
||||
*/
|
||||
@ -286,13 +291,14 @@ public class AIServiceAdapter implements AIServicePort {
|
||||
다음은 한 매장의 고객 리뷰들입니다. 이를 분석하여 다음 JSON 형식으로 답변해주세요:
|
||||
|
||||
{
|
||||
"summary": "전체적인 분석 요약(2-3문장)",
|
||||
"summary": "전체적인 분석 요약(1-2문장)",
|
||||
"positivePoints": ["긍정적 요소1", "긍정적 요소2", "긍정적 요소3"],
|
||||
"negativePoints": ["부정적 요소1", "부정적 요소2", "부정적 요소3"],
|
||||
"improvementPoints": ["개선점1", "개선점2", "개선점3"],
|
||||
"recommendations": ["추천사항1", "추천사항2", "추천사항3"],
|
||||
"sentimentAnalysis": "전체적인 감정 분석 결과",
|
||||
"confidenceScore": 0.85
|
||||
"positiveSummary": "리뷰중에 긍정적인 내용만 분석 요약(1~2문장)"
|
||||
}
|
||||
|
||||
리뷰 목록:
|
||||
@ -306,6 +312,7 @@ public class AIServiceAdapter implements AIServicePort {
|
||||
4. 신뢰도 점수는 0.0-1.0 사이의 값으로 리뷰정보를 보고 적절히 판단.
|
||||
5. summary에는 전체적인 리뷰 분석에 대한 요약이 잘 담기게 작성하고 **같은 강조하는 문자 없이 텍스트로만 나타내주세요
|
||||
6. 분석한 내용에 `(백틱) 이 들어가지 않도록 해주세요.
|
||||
7. positiveSummary에는 긍정적인 내용만 있어야 합니다, summary에 있는 내용에서 긍정적인 부분만 작성해주세요.
|
||||
""",
|
||||
reviewsText
|
||||
);
|
||||
@ -402,6 +409,7 @@ public class AIServiceAdapter implements AIServicePort {
|
||||
.improvementPoints((List<String>) result.get("improvementPoints"))
|
||||
.recommendations((List<String>) result.get("recommendations"))
|
||||
.sentimentAnalysis((String) result.get("sentimentAnalysis"))
|
||||
.positiveSummary((String) result.get("positiveSummary"))
|
||||
.confidenceScore(((Number) result.get("confidenceScore")).doubleValue())
|
||||
.generatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
@ -109,6 +109,7 @@ public class AnalyticsRepositoryAdapter implements AnalyticsPort {
|
||||
.improvementPoints(parseJsonToList(entity.getImprovementPointsJson()))
|
||||
.recommendations(parseJsonToList(entity.getRecommendationsJson()))
|
||||
.sentimentAnalysis(entity.getSentimentAnalysis())
|
||||
.positiveSummary(entity.getPositiveSummary())
|
||||
.confidenceScore(entity.getConfidenceScore())
|
||||
.generatedAt(entity.getGeneratedAt())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
@ -128,6 +129,7 @@ public class AnalyticsRepositoryAdapter implements AnalyticsPort {
|
||||
.negativePointsJson(parseListToJson(domain.getNegativePoints()))
|
||||
.improvementPointsJson(parseListToJson(domain.getImprovementPoints()))
|
||||
.recommendationsJson(parseListToJson(domain.getRecommendations()))
|
||||
.positiveSummary(domain.getPositiveSummary())
|
||||
.sentimentAnalysis(domain.getSentimentAnalysis())
|
||||
.confidenceScore(domain.getConfidenceScore())
|
||||
.generatedAt(domain.getGeneratedAt())
|
||||
|
||||
@ -19,8 +19,13 @@ import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -71,23 +76,62 @@ public class ExternalReviewAdapter implements ExternalReviewPort {
|
||||
log.info("최근 리뷰 데이터 조회: storeId={}, days={}", storeId, days);
|
||||
|
||||
try {
|
||||
// String url = reviewServiceUrl + "/api/reviews/stores/" + storeId + "/recent?days=" + days;
|
||||
// String url = reviewServiceUrl + "/api/reviews/stores/" + storeId + "?size=100";
|
||||
|
||||
//최근 데이터를 가져오도록 변경
|
||||
String url = reviewServiceUrl + "/api/reviews/stores/recent/" + storeId + "?size=100&days=" + days;
|
||||
// String url = reviewServiceUrl + "/api/reviews/stores/recent/" + storeId + "?size=100&days=" + days;
|
||||
//
|
||||
// // ReviewListResponse 배열로 직접 받기
|
||||
// ReviewListResponse[] reviewArray = restTemplate.getForObject(url, ReviewListResponse[].class);
|
||||
|
||||
// ReviewListResponse 배열로 직접 받기
|
||||
ReviewListResponse[] reviewArray = restTemplate.getForObject(url, ReviewListResponse[].class);
|
||||
int totalSize = 200;
|
||||
int threadCount = 4;
|
||||
int pageSize = totalSize / threadCount; // 50개씩
|
||||
|
||||
if (reviewArray == null || reviewArray.length == 0) {
|
||||
log.info("매장에 최근 리뷰가 없습니다: storeId={}", storeId);
|
||||
return List.of();
|
||||
// ExecutorService 생성
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
|
||||
List<CompletableFuture<ReviewListResponse[]>> futures = new ArrayList<>();
|
||||
|
||||
// 4개의 비동기 요청 생성 (limit 50, offset 0/50/100/150)
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final int offset = i * pageSize;
|
||||
final int limit = pageSize;
|
||||
|
||||
CompletableFuture<ReviewListResponse[]> future = CompletableFuture.supplyAsync(() -> {
|
||||
String url = reviewServiceUrl + "/api/reviews/stores/recent/" + storeId
|
||||
+ "?size=" + limit + "&offset=" + offset + "&days=" + days;
|
||||
|
||||
log.debug("스레드 {}에서 URL 호출: {}", Thread.currentThread().getName(), url);
|
||||
|
||||
// 기존과 동일한 방식으로 API 호출
|
||||
ReviewListResponse[] reviewArray = restTemplate.getForObject(url, ReviewListResponse[].class);
|
||||
|
||||
if (reviewArray == null) {
|
||||
log.debug("스레드 {}에서 빈 응답 수신", Thread.currentThread().getName());
|
||||
return new ReviewListResponse[0];
|
||||
}
|
||||
|
||||
log.debug("스레드 {}에서 {} 개 리뷰 수신", Thread.currentThread().getName(), reviewArray.length);
|
||||
return reviewArray;
|
||||
|
||||
}, executorService);
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
// 모든 요청 완료 대기 및 결과 합치기
|
||||
List<ReviewListResponse> allReviewResponses = new ArrayList<>();
|
||||
for (CompletableFuture<ReviewListResponse[]> future : futures) {
|
||||
ReviewListResponse[] reviewArray = future.get(30, TimeUnit.SECONDS); // 30초 타임아웃
|
||||
allReviewResponses.addAll(Arrays.asList(reviewArray));
|
||||
}
|
||||
|
||||
executorService.shutdown();
|
||||
|
||||
|
||||
// 최근 N일 이내의 리뷰만 필터링
|
||||
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(days);
|
||||
|
||||
List<String> recentReviews = Arrays.stream(reviewArray)
|
||||
List<String> recentReviews = allReviewResponses.stream()
|
||||
.filter(review -> review.getCreatedAt() != null && review.getCreatedAt().isAfter(cutoffDate))
|
||||
.map(ReviewListResponse::getContent)
|
||||
.filter(content -> content != null && !content.trim().isEmpty())
|
||||
@ -108,7 +152,12 @@ public class ExternalReviewAdapter implements ExternalReviewPort {
|
||||
return getDummyRecentReviews(storeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> getPositiveReviews(Long storeId, Integer days) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getReviewCount(Long storeId) {
|
||||
log.info("리뷰 개수 조회: storeId={}", storeId);
|
||||
|
||||
@ -57,6 +57,10 @@ public class AiFeedbackEntity {
|
||||
|
||||
@Column(name = "confidence_score")
|
||||
private Double confidenceScore;
|
||||
|
||||
// 🔥 고객용 긍정 리뷰 요약 컬럼 추가
|
||||
@Column(name = "customer_positive_summary", columnDefinition = "TEXT")
|
||||
private String positiveSummary;
|
||||
|
||||
@Column(name = "generated_at")
|
||||
private LocalDateTime generatedAt;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user