Fix : 수정
This commit is contained in:
parent
28d34dba8b
commit
4946be1f49
@ -16,6 +16,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 추천 인터랙터 클래스
|
* 매장 추천 인터랙터 클래스
|
||||||
* 사용자 취향 기반 매장 추천 기능을 구현
|
* 사용자 취향 기반 매장 추천 기능을 구현
|
||||||
@ -35,43 +39,43 @@ public class StoreRecommendInteractor implements StoreRecommendUseCase {
|
|||||||
public List<RecommendStoreResponse> recommendStores(Long memberId, RecommendStoreRequest request) {
|
public List<RecommendStoreResponse> recommendStores(Long memberId, RecommendStoreRequest request) {
|
||||||
// 사용자 취향 프로필 조회
|
// 사용자 취향 프로필 조회
|
||||||
TasteProfile tasteProfile = userPreferenceRepository.getMemberPreferences(memberId)
|
TasteProfile tasteProfile = userPreferenceRepository.getMemberPreferences(memberId)
|
||||||
.orElseThrow(() -> new BusinessException("사용자 취향 정보를 찾을 수 없습니다. 취향 등록을 먼저 해주세요."));
|
.orElseThrow(() -> new BusinessException("사용자 취향 정보를 찾을 수 없습니다. 취향 등록을 먼저 해주세요."));
|
||||||
|
|
||||||
// AI 기반 추천
|
// AI 기반 추천
|
||||||
Map<String, Object> preferences = Map.of(
|
Map<String, Object> preferences = Map.of(
|
||||||
"categories", tasteProfile.getPreferredCategories(),
|
"categories", tasteProfile.getPreferredCategories(),
|
||||||
"tags", tasteProfile.getPreferredTags(),
|
"tags", tasteProfile.getPreferredTags(),
|
||||||
"pricePreference", tasteProfile.getPricePreference(),
|
"pricePreference", tasteProfile.getPricePreference(),
|
||||||
"distancePreference", tasteProfile.getDistancePreference(),
|
"distancePreference", tasteProfile.getDistancePreference(),
|
||||||
"latitude", request.getLatitude(),
|
"latitude", request.getLatitude(),
|
||||||
"longitude", request.getLongitude()
|
"longitude", request.getLongitude()
|
||||||
);
|
);
|
||||||
|
|
||||||
List<RecommendStore> aiRecommendStores = aiRecommendRepository.recommendStoresByAI(memberId, preferences);
|
List<RecommendStore> aiRecommendStores = aiRecommendRepository.recommendStoresByAI(memberId, preferences);
|
||||||
|
|
||||||
// 위치 기반 추천 결합
|
// 위치 기반 추천 결합
|
||||||
List<RecommendStore> locationStores = locationRepository.findStoresWithinRadius(
|
List<RecommendStore> locationStores = locationRepository.findStoresWithinRadius(
|
||||||
request.getLatitude(), request.getLongitude(), request.getRadius());
|
request.getLatitude(), request.getLongitude(), request.getRadius());
|
||||||
|
|
||||||
// 추천 결과 통합 및 점수 계산
|
// 추천 결과 통합 및 점수 계산
|
||||||
List<RecommendStore> combinedStores = combineRecommendations(aiRecommendStores, locationStores, tasteProfile);
|
List<RecommendStore> combinedStores = combineRecommendations(aiRecommendStores, locationStores, tasteProfile);
|
||||||
|
|
||||||
// 추천 히스토리 저장
|
// 추천 히스토리 저장
|
||||||
RecommendHistory history = RecommendHistory.builder()
|
RecommendHistory history = RecommendHistory.builder()
|
||||||
.memberId(memberId)
|
.memberId(memberId)
|
||||||
.recommendedStoreIds(combinedStores.stream().map(RecommendStore::getStoreId).collect(Collectors.toList()))
|
.recommendedStoreIds(combinedStores.stream().map(RecommendStore::getStoreId).collect(Collectors.toList()))
|
||||||
.recommendType(RecommendType.TASTE_BASED)
|
.recommendType(RecommendType.TASTE_BASED)
|
||||||
.criteria("취향 + AI + 위치 기반 통합 추천")
|
.criteria("취향 + AI + 위치 기반 통합 추천")
|
||||||
.createdAt(LocalDateTime.now())
|
.createdAt(LocalDateTime.now())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
recommendRepository.saveRecommendHistory(history);
|
recommendRepository.saveRecommendHistory(history);
|
||||||
|
|
||||||
log.info("매장 추천 완료: memberId={}, 추천 매장 수={}", memberId, combinedStores.size());
|
log.info("매장 추천 완료: memberId={}, 추천 매장 수={}", memberId, combinedStores.size());
|
||||||
|
|
||||||
return combinedStores.stream()
|
return combinedStores.stream()
|
||||||
.map(this::toRecommendStoreResponse)
|
.map(this::toRecommendStoreResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -80,9 +84,9 @@ public class StoreRecommendInteractor implements StoreRecommendUseCase {
|
|||||||
List<RecommendStore> stores = locationRepository.findStoresWithinRadius(latitude, longitude, radius);
|
List<RecommendStore> stores = locationRepository.findStoresWithinRadius(latitude, longitude, radius);
|
||||||
|
|
||||||
return stores.stream()
|
return stores.stream()
|
||||||
.map(store -> store.updateRecommendReason("위치 기반 추천"))
|
.map(store -> store.updateRecommendReason("위치 기반 추천"))
|
||||||
.map(this::toRecommendStoreResponse)
|
.map(this::toRecommendStoreResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,41 +94,41 @@ public class StoreRecommendInteractor implements StoreRecommendUseCase {
|
|||||||
public List<RecommendStoreResponse> recommendPopularStores(String category, Integer limit) {
|
public List<RecommendStoreResponse> recommendPopularStores(String category, Integer limit) {
|
||||||
// Mock 구현 - 실제로는 인기도 기반 쿼리 필요
|
// Mock 구현 - 실제로는 인기도 기반 쿼리 필요
|
||||||
List<RecommendStore> popularStores = List.of(
|
List<RecommendStore> popularStores = List.of(
|
||||||
RecommendStore.builder()
|
RecommendStore.builder()
|
||||||
.storeId(1L)
|
.storeId(1L)
|
||||||
.storeName("인기 매장 1")
|
.storeName("인기 매장 1")
|
||||||
.address("서울시 강남구")
|
.address("서울시 강남구")
|
||||||
.category(category)
|
.category(category)
|
||||||
.rating(4.5)
|
.rating(4.5)
|
||||||
.reviewCount(100)
|
.reviewCount(100)
|
||||||
.recommendScore(95.0)
|
.recommendScore(95.0)
|
||||||
.recommendType(RecommendType.POPULARITY_BASED)
|
.recommendType(RecommendType.POPULARITY_BASED)
|
||||||
.recommendReason("높은 평점과 많은 리뷰")
|
.recommendReason("높은 평점과 많은 리뷰")
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
return popularStores.stream()
|
return popularStores.stream()
|
||||||
.limit(limit != null ? limit : 10)
|
.limit(limit != null ? limit : 10)
|
||||||
.map(this::toRecommendStoreResponse)
|
.map(this::toRecommendStoreResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 추천 결과 통합 및 점수 계산
|
* 추천 결과 통합 및 점수 계산
|
||||||
*/
|
*/
|
||||||
private List<RecommendStore> combineRecommendations(List<RecommendStore> aiStores,
|
private List<RecommendStore> combineRecommendations(List<RecommendStore> aiStores,
|
||||||
List<RecommendStore> locationStores,
|
List<RecommendStore> locationStores,
|
||||||
TasteProfile profile) {
|
TasteProfile profile) {
|
||||||
// AI 추천과 위치 기반 추천을 통합하여 최종 점수 계산
|
// AI 추천과 위치 기반 추천을 통합하여 최종 점수 계산
|
||||||
// 실제로는 더 복잡한 로직이 필요
|
// 실제로는 더 복잡한 로직이 필요
|
||||||
|
|
||||||
return aiStores.stream()
|
return aiStores.stream()
|
||||||
.map(store -> store.updateRecommendScore(
|
.map(store -> store.updateRecommendScore(
|
||||||
calculateFinalScore(store, profile)
|
calculateFinalScore(store, profile)
|
||||||
))
|
))
|
||||||
.sorted((s1, s2) -> Double.compare(s2.getRecommendScore(), s1.getRecommendScore()))
|
.sorted((s1, s2) -> Double.compare(s2.getRecommendScore(), s1.getRecommendScore()))
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,16 +148,16 @@ public class StoreRecommendInteractor implements StoreRecommendUseCase {
|
|||||||
*/
|
*/
|
||||||
private RecommendStoreResponse toRecommendStoreResponse(RecommendStore store) {
|
private RecommendStoreResponse toRecommendStoreResponse(RecommendStore store) {
|
||||||
return RecommendStoreResponse.builder()
|
return RecommendStoreResponse.builder()
|
||||||
.storeId(store.getStoreId())
|
.storeId(store.getStoreId())
|
||||||
.storeName(store.getStoreName())
|
.storeName(store.getStoreName())
|
||||||
.address(store.getAddress())
|
.address(store.getAddress())
|
||||||
.category(store.getCategory())
|
.category(store.getCategory())
|
||||||
.tags(store.getTags())
|
.tags(store.getTags())
|
||||||
.rating(store.getRating())
|
.rating(store.getRating())
|
||||||
.reviewCount(store.getReviewCount())
|
.reviewCount(store.getReviewCount())
|
||||||
.distance(store.getDistance())
|
.distance(store.getDistance())
|
||||||
.recommendScore(store.getRecommendScore())
|
.recommendScore(store.getRecommendScore())
|
||||||
.recommendReason(store.getRecommendReason())
|
.recommendReason(store.getRecommendReason())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,4 +52,7 @@ public class RecommendStoreResponse {
|
|||||||
|
|
||||||
@Schema(description = "태그 목록", example = "[\"매운맛\", \"혼밥\"]")
|
@Schema(description = "태그 목록", example = "[\"매운맛\", \"혼밥\"]")
|
||||||
private List<String> tags;
|
private List<String> tags;
|
||||||
|
|
||||||
|
@Schema(description = "가게 점수")
|
||||||
|
private Double rating;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,42 +17,66 @@ public class StoreDetailResponse {
|
|||||||
@Schema(description = "매장 ID", example = "1")
|
@Schema(description = "매장 ID", example = "1")
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
|
|
||||||
@Schema(description = "매장명", example = "맛있는 김치찌개")
|
@Schema(description = "매장명", example = "맛있는 한식당")
|
||||||
private String storeName;
|
private String storeName;
|
||||||
|
|
||||||
@Schema(description = "카테고리", example = "한식")
|
|
||||||
private String category;
|
|
||||||
|
|
||||||
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
|
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
@Schema(description = "전화번호", example = "02-1234-5678")
|
@Schema(description = "카테고리", example = "한식")
|
||||||
private String phoneNumber;
|
private String category;
|
||||||
|
|
||||||
@Schema(description = "위도", example = "37.5665")
|
@Schema(description = "평점", example = "4.5")
|
||||||
private Double latitude;
|
private Double rating;
|
||||||
|
|
||||||
@Schema(description = "경도", example = "126.9780")
|
@Schema(description = "리뷰 수", example = "256")
|
||||||
private Double longitude;
|
|
||||||
|
|
||||||
@Schema(description = "평균 평점", example = "4.5")
|
|
||||||
private Double averageRating;
|
|
||||||
|
|
||||||
@Schema(description = "리뷰 수", example = "127")
|
|
||||||
private Integer reviewCount;
|
private Integer reviewCount;
|
||||||
|
|
||||||
|
@Schema(description = "거리(미터)", example = "500")
|
||||||
|
private Integer distance;
|
||||||
|
|
||||||
|
@Schema(description = "태그 목록", example = "[\"맛집\", \"혼밥\", \"가성비\"]")
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
@Schema(description = "매장 이미지 URL 목록")
|
@Schema(description = "매장 이미지 URL 목록")
|
||||||
private List<String> imageUrls;
|
private List<String> images;
|
||||||
|
|
||||||
@Schema(description = "운영시간", example = "10:00-22:00")
|
@Schema(description = "매장 설명")
|
||||||
private String operatingHours;
|
private String description;
|
||||||
|
|
||||||
@Schema(description = "AI 요약", example = "친절한 서비스와 맛있는 김치찌개로 유명한 곳입니다")
|
@Schema(description = "개인화된 추천 이유", example = "당신이 좋아하는 '매운맛' 특성을 가진 매장입니다")
|
||||||
|
private String personalizedReason;
|
||||||
|
|
||||||
|
@Schema(description = "AI 요약", example = "고객들이 매운맛과 친절한 서비스를 칭찬하는 매장입니다")
|
||||||
private String aiSummary;
|
private String aiSummary;
|
||||||
|
|
||||||
@Schema(description = "긍정 키워드", example = "[\"맛있다\", \"친절하다\", \"깔끔하다\"]")
|
@Schema(description = "운영 시간")
|
||||||
private List<String> topPositiveKeywords;
|
private String operatingHours;
|
||||||
|
|
||||||
@Schema(description = "부정 키워드", example = "[\"시끄럽다\", \"대기시간\"]")
|
@Schema(description = "전화번호")
|
||||||
private List<String> topNegativeKeywords;
|
private String phoneNumber;
|
||||||
|
|
||||||
|
@Schema(description = "메뉴 정보")
|
||||||
|
private List<MenuInfo> menuList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 메뉴 정보 내부 클래스
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "메뉴 정보")
|
||||||
|
public static class MenuInfo {
|
||||||
|
|
||||||
|
@Schema(description = "메뉴명", example = "김치찌개")
|
||||||
|
private String menuName;
|
||||||
|
|
||||||
|
@Schema(description = "가격", example = "8000")
|
||||||
|
private Integer price;
|
||||||
|
|
||||||
|
@Schema(description = "메뉴 설명", example = "매콤한 김치찌개")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "인기 메뉴 여부", example = "true")
|
||||||
|
private Boolean isPopular;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +31,12 @@ public class TasteAnalysisResponse {
|
|||||||
@Schema(description = "선호 태그 목록", example = "[\"매운맛\", \"혼밥\"]")
|
@Schema(description = "선호 태그 목록", example = "[\"매운맛\", \"혼밥\"]")
|
||||||
private List<String> preferredTags;
|
private List<String> preferredTags;
|
||||||
|
|
||||||
|
@Schema(description = "가격 선호도 (0-100 점수)", example = "65.5")
|
||||||
|
private Double pricePreference;
|
||||||
|
|
||||||
|
@Schema(description = "거리 선호도 (0-100 점수)", example = "70.0")
|
||||||
|
private Double distancePreference;
|
||||||
|
|
||||||
@Schema(description = "분석 일시")
|
@Schema(description = "분석 일시")
|
||||||
private LocalDateTime analysisDate;
|
private LocalDateTime analysisDate;
|
||||||
|
|
||||||
@ -39,4 +45,5 @@ public class TasteAnalysisResponse {
|
|||||||
|
|
||||||
@Schema(description = "추천사항", example = "[\"한식 카테고리의 새로운 매장을 시도해보세요\"]")
|
@Schema(description = "추천사항", example = "[\"한식 카테고리의 새로운 매장을 시도해보세요\"]")
|
||||||
private List<String> recommendations;
|
private List<String> recommendations;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user