Fix : 수정

This commit is contained in:
lsh9672 2025-06-13 01:10:25 +09:00
parent a13ca2e4be
commit 60ee715d3e
11 changed files with 326 additions and 303 deletions

View File

@ -1,40 +1,16 @@
package com.ktds.hi.recommend.biz.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 위치 도메인 클래스
* 위치 정보를 담는 도메인 객체
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Location {
private Long id;
private String address;
private Long id; // 추가
private Double latitude;
private Double longitude;
private String city;
private String district;
private String country;
/**
* 좌표 업데이트
*/
public Location updateCoordinates(Double newLatitude, Double newLongitude) {
return Location.builder()
.id(this.id)
.address(this.address)
.latitude(newLatitude)
.longitude(newLongitude)
.city(this.city)
.district(this.district)
.country(this.country)
.build();
}
private String address;
private String city; // 추가
private String district; // 추가
private String country; // 추가
}

View File

@ -4,17 +4,24 @@ import lombok.Builder;
import lombok.Getter;
import java.time.LocalDateTime;
import java.util.List;
@Getter
@Builder
public class RecommendHistory {
private Long id;
private Long memberId;
private Long storeId;
private String recommendType;
private Long storeId; // 기존 필드
private String recommendType; // 기존 필드 (String)
private Double score;
private String reason;
private LocalDateTime recommendedAt;
private Boolean clicked;
private Boolean visited;
// 추가 필드들 (Adapter에서 사용)
private List<Long> recommendedStoreIds;
private RecommendType recommendTypeEnum;
private String criteria;
private LocalDateTime createdAt;
}

View File

@ -21,4 +21,7 @@ public class RecommendStore {
private Double recommendScore;
private Double distance;
private String priceRange;
// 추가 필드
private RecommendType recommendType;
}

View File

@ -1,16 +1,11 @@
package com.ktds.hi.recommend.biz.domain;
/**
* 추천 유형 열거형
* 추천의 종류를 정의
*/
public enum RecommendType {
TASTE_BASED("취향 기반"),
AI_RECOMMENDATION("AI 추천"),
LOCATION_BASED("위치 기반"),
POPULARITY_BASED("인기 기반"),
COLLABORATIVE_FILTERING("협업 필터링"),
AI_RECOMMENDATION("AI 추천"),
SIMILAR_USER("유사 사용자 기반");
PREFERENCE_BASED("취향 기반");
private final String description;

View File

@ -1,22 +1,16 @@
package com.ktds.hi.recommend.biz.domain;
/**
* 취향 카테고리 열거형
* 음식 카테고리를 정의
*/
public enum TasteCategory {
KOREAN("한식"),
CHINESE("중식"),
JAPANESE("일식"),
WESTERN("양식"),
FAST_FOOD("패스트푸드"),
CAFE("카페"),
FASTFOOD("패스트푸드"),
DESSERT("디저트"),
CHICKEN("치킨"),
PIZZA("피자"),
ASIAN("아시안"),
VEGETARIAN("채식"),
SEAFOOD("해산물");
OTHER("기타");
private final String description;

View File

@ -5,16 +5,26 @@ import lombok.Getter;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Getter
@Builder
public class TasteProfile {
private Long id;
private Long memberId;
private List<String> cuisinePreferences;
private String priceRange;
private Integer distancePreference;
private List<String> tasteTags;
private List<String> cuisinePreferences; // 기존 유지
private String priceRange; // 기존 유지
private Integer distancePreference; // 기존 유지
private List<String> tasteTags; // 기존 유지
// 추가 필드들 (Adapter에서 사용)
private List<TasteCategory> preferredCategories;
private Map<String, Double> categoryScores;
private List<String> preferredTags;
private Map<String, Object> behaviorPatterns;
private Double pricePreference;
private Double distancePreferenceDouble; // distancePreference와 구분
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@ -3,17 +3,15 @@ package com.ktds.hi.recommend.infra.gateway;
import com.ktds.hi.recommend.biz.usecase.out.AiRecommendRepository;
import com.ktds.hi.recommend.biz.domain.RecommendStore;
import com.ktds.hi.recommend.biz.domain.RecommendType;
import com.ktds.hi.recommend.biz.domain.TasteProfile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* AI 추천 어댑터 클래스
* AI 기반 추천 기능을 구현 (현재는 Mock 구현)
*/
@Component
@RequiredArgsConstructor
@Slf4j
@ -21,37 +19,22 @@ public class AiRecommendAdapter implements AiRecommendRepository {
@Override
public List<RecommendStore> recommendStoresByAI(Long memberId, Map<String, Object> preferences) {
log.info("AI 기반 매장 추천 요청: memberId={}, preferences={}", memberId, preferences);
log.info("AI 기반 매장 추천 요청: memberId={}", memberId);
// Mock 구현 - 실제로는 AI 모델 API 호출
return List.of(
RecommendStore.builder()
.storeId(1L)
.storeName("AI 추천 매장 1")
.address("서울시 강남구 역삼동")
.category("한식")
.tags(List.of("맛집", "깔끔", "한식"))
.rating(4.5)
.reviewCount(150)
.distance(500.0)
.recommendScore(92.0)
.recommendType(RecommendType.AI_RECOMMENDATION)
.recommendReason("사용자 취향과 92% 일치")
.build(),
RecommendStore.builder()
.storeId(2L)
.storeName("AI 추천 매장 2")
.address("서울시 강남구 논현동")
.category("일식")
.tags(List.of("초밥", "신선", "일식"))
.rating(4.3)
.reviewCount(89)
.distance(800.0)
.recommendScore(87.0)
.recommendType(RecommendType.AI_RECOMMENDATION)
.recommendReason("사용자가 선호하는 일식 카테고리")
.build()
return Arrays.asList(
RecommendStore.builder()
.storeId(1L)
.storeName("AI 추천 매장 1")
.address("서울시 강남구 역삼동")
.category("한식")
.tags(Arrays.asList("맛집", "깔끔", "한식"))
.rating(4.5)
.reviewCount(150)
.distance(500.0)
.recommendScore(92.0)
.recommendType(RecommendType.AI_RECOMMENDATION)
.recommendReason("사용자 취향과 92% 일치")
.build()
);
}
@ -59,43 +42,47 @@ public class AiRecommendAdapter implements AiRecommendRepository {
public List<RecommendStore> recommendStoresBySimilarUsers(Long memberId) {
log.info("유사 사용자 기반 추천 요청: memberId={}", memberId);
// Mock 구현
return List.of(
RecommendStore.builder()
.storeId(3L)
.storeName("유사 취향 추천 매장")
.address("서울시 서초구 서초동")
.category("양식")
.tags(List.of("파스타", "분위기", "양식"))
.rating(4.4)
.reviewCount(203)
.distance(1200.0)
.recommendScore(85.0)
.recommendType(RecommendType.SIMILAR_USER)
.recommendReason("비슷한 취향의 사용자들이 좋아하는 매장")
.build()
return Arrays.asList(
RecommendStore.builder()
.storeId(3L)
.storeName("유사 취향 추천 매장")
.address("서울시 서초구 서초동")
.category("양식")
.tags(Arrays.asList("파스타", "분위기", "데이트"))
.rating(4.2)
.reviewCount(78)
.distance(1200.0)
.recommendScore(85.0)
.recommendType(RecommendType.COLLABORATIVE_FILTERING)
.recommendReason("비슷한 취향의 사용자들이 선호")
.build()
);
}
@Override
public List<RecommendStore> recommendStoresByCollaborativeFiltering(Long memberId) {
log.info("협업 필터링 추천 요청: memberId={}", memberId);
return recommendStoresBySimilarUsers(memberId);
}
// Mock 구현
return List.of(
RecommendStore.builder()
.storeId(4L)
.storeName("협업 필터링 추천 매장")
.address("서울시 마포구 홍대입구")
.category("카페")
.tags(List.of("커피", "디저트", "분위기"))
.rating(4.2)
.reviewCount(127)
.distance(2500.0)
.recommendScore(82.0)
.recommendType(RecommendType.COLLABORATIVE_FILTERING)
.recommendReason("사용자 행동 패턴 기반 추천")
.build()
);
@Override
public List<RecommendStore> filterByPreferences(List<RecommendStore> stores, TasteProfile tasteProfile, String tags) {
log.info("취향 기반 매장 필터링: 매장 수={}, 태그={}", stores.size(), tags);
// 간단한 필터링 로직 구현
return stores.stream()
.filter(store -> {
if (tasteProfile.getPreferredTags() != null) {
return store.getTags().stream()
.anyMatch(tag -> tasteProfile.getPreferredTags().contains(tag));
}
return true;
})
.toList();
}
@Override
public String getStoreSummary(Long storeId) {
log.info("매장 AI 요약 조회: storeId={}", storeId);
return "AI가 분석한 매장 요약: 고객들이 맛과 서비스를 높이 평가하는 매장입니다.";
}
}

View File

@ -8,12 +8,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 위치 서비스 어댑터 클래스
* 위치 기반 서비스 기능을 구현 (현재는 Mock 구현)
*/
@Component
@RequiredArgsConstructor
@Slf4j
@ -23,84 +20,79 @@ public class LocationServiceAdapter implements LocationRepository {
public Location saveLocation(Location location) {
log.info("위치 정보 저장: {}", location.getAddress());
// Mock 구현
return Location.builder()
.id(1L)
.address(location.getAddress())
.latitude(location.getLatitude())
.longitude(location.getLongitude())
.city("서울시")
.district("강남구")
.country("대한민국")
.build();
.id(1L)
.address(location.getAddress())
.latitude(location.getLatitude())
.longitude(location.getLongitude())
.city("서울시")
.district("강남구")
.country("대한민국")
.build();
}
@Override
public List<RecommendStore> findStoresWithinRadius(Double latitude, Double longitude, Integer radius) {
public List<RecommendStore> findStoresWithinDistance(Double latitude, Double longitude, Integer radius) {
log.info("반경 내 매장 조회: lat={}, lon={}, radius={}", latitude, longitude, radius);
// Mock 구현
return List.of(
RecommendStore.builder()
.storeId(5L)
.storeName("근처 매장 1")
.address("서울시 강남구 역삼동 123-45")
.category("한식")
.tags(List.of("근처", "맛집"))
.rating(4.1)
.reviewCount(95)
.distance(300.0)
.recommendScore(78.0)
.recommendType(RecommendType.LOCATION_BASED)
.recommendReason("현재 위치에서 300m 거리")
.build(),
return Arrays.asList(
RecommendStore.builder()
.storeId(5L)
.storeName("근처 매장 1")
.address("서울시 강남구 역삼동 123-45")
.category("한식")
.tags(Arrays.asList("근처", "맛집"))
.rating(4.1)
.reviewCount(95)
.distance(300.0)
.recommendScore(78.0)
.recommendType(RecommendType.LOCATION_BASED)
.recommendReason("현재 위치에서 300m 거리")
.build(),
RecommendStore.builder()
.storeId(6L)
.storeName("근처 매장 2")
.address("서울시 강남구 역삼동 678-90")
.category("카페")
.tags(List.of("커피", "디저트"))
.rating(4.0)
.reviewCount(67)
.distance(450.0)
.recommendScore(75.0)
.recommendType(RecommendType.LOCATION_BASED)
.recommendReason("현재 위치에서 450m 거리")
.build()
RecommendStore.builder()
.storeId(6L)
.storeName("근처 매장 2")
.address("서울시 강남구 역삼동 678-90")
.category("카페")
.tags(Arrays.asList("커피", "디저트"))
.rating(4.0)
.reviewCount(67)
.distance(450.0)
.recommendScore(75.0)
.recommendType(RecommendType.LOCATION_BASED)
.recommendReason("현재 위치에서 450m 거리")
.build()
);
}
@Override
public Double calculateDistance(Double lat1, Double lon1, Double lat2, Double lon2) {
// Haversine 공식을 용한 거리 계산
final int R = 6371; // 지구 반지름 (km)
// 하버사인 공식을 용한 거리 계산
final int R = 6371; // 지구 반지름 (km)
double latDistance = Math.toRadians(lat2 - lat1);
double lonDistance = Math.toRadians(lon2 - lon1);
double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
* Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
* Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double distance = R * c * 1000; // 미터로 변환
return distance;
return R * c * 1000; // 미터 단위로 반환
}
@Override
public Location geocodeAddress(String address) {
log.info("주소 좌표 변환 요청: {}", address);
log.info("주소 좌표 변환: {}", address);
// Mock 구현 - 실제로는 Google Maps API 사용
// Mock 구현 - 실제로는 지도 API 호출
return Location.builder()
.address(address)
.latitude(37.5665)
.longitude(126.9780)
.city("서울시")
.district("중구")
.country("대한민국")
.build();
.address(address)
.latitude(37.5665)
.longitude(126.9780)
.city("서울시")
.district("중구")
.country("대한민국")
.build();
}
}

View File

@ -1,110 +1,178 @@
package com.ktds.hi.recommend.infra.gateway;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ktds.hi.recommend.biz.usecase.out.RecommendRepository;
import com.ktds.hi.recommend.biz.domain.RecommendHistory;
import com.ktds.hi.recommend.biz.domain.RecommendStore;
import com.ktds.hi.recommend.biz.domain.TasteProfile;
import com.ktds.hi.recommend.biz.domain.RecommendType;
import com.ktds.hi.recommend.infra.gateway.repository.RecommendHistoryJpaRepository;
import com.ktds.hi.recommend.infra.gateway.repository.TasteProfileJpaRepository;
import com.ktds.hi.recommend.infra.gateway.entity.RecommendHistoryEntity;
import com.ktds.hi.recommend.infra.gateway.entity.TasteProfileEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 추천 리포지토리 어댑터 클래스
* 도메인 리포지토리 인터페이스를 JPA 리포지토리에 연결
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class RecommendRepositoryAdapter implements RecommendRepository {
private final RecommendHistoryJpaRepository recommendHistoryJpaRepository;
private final TasteProfileJpaRepository tasteProfileJpaRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public RecommendHistory saveRecommendHistory(RecommendHistory history) {
RecommendHistoryEntity entity = toRecommendHistoryEntity(history);
RecommendHistoryEntity savedEntity = recommendHistoryJpaRepository.save(entity);
return toRecommendHistory(savedEntity);
try {
RecommendHistoryEntity entity = toRecommendHistoryEntity(history);
RecommendHistoryEntity savedEntity = recommendHistoryJpaRepository.save(entity);
return toRecommendHistory(savedEntity);
} catch (Exception e) {
log.error("추천 히스토리 저장 실패", e);
throw new RuntimeException("추천 히스토리 저장 실패", e);
}
}
@Override
public List<RecommendHistory> findRecommendHistoriesByMemberId(Long memberId) {
List<RecommendHistoryEntity> entities = recommendHistoryJpaRepository.findByMemberIdOrderByCreatedAtDesc(memberId);
return entities.stream()
.map(this::toRecommendHistory)
.collect(Collectors.toList());
.map(this::toRecommendHistory)
.toList();
}
@Override
public TasteProfile saveTasteProfile(TasteProfile profile) {
TasteProfileEntity entity = toTasteProfileEntity(profile);
TasteProfileEntity savedEntity = tasteProfileJpaRepository.save(entity);
return toTasteProfile(savedEntity);
try {
TasteProfileEntity entity = toTasteProfileEntity(profile);
TasteProfileEntity savedEntity = tasteProfileJpaRepository.save(entity);
return toTasteProfile(savedEntity);
} catch (Exception e) {
log.error("취향 프로필 저장 실패", e);
throw new RuntimeException("취향 프로필 저장 실패", e);
}
}
@Override
public Optional<TasteProfile> findTasteProfileByMemberId(Long memberId) {
return tasteProfileJpaRepository.findByMemberId(memberId)
.map(this::toTasteProfile);
.map(this::toTasteProfile);
}
/**
* 엔티티를 도메인으로 변환
*/
@Override
public Optional<RecommendStore> findStoreById(Long storeId) {
// Mock 구현 - 실제로는 Store 서비스 호출 또는 캐시에서 조회
return Optional.of(RecommendStore.builder()
.storeId(storeId)
.storeName("매장 " + storeId)
.address("서울시 강남구")
.category("한식")
.rating(4.5)
.tags(Arrays.asList("맛집", "친절"))
.distance(500.0)
.priceRange("중간")
.build());
}
@Override
public List<RecommendStore> findPopularStores(String category, Integer limit) {
// Mock 구현
return Arrays.asList(
RecommendStore.builder()
.storeId(1L)
.storeName("인기 매장 1")
.category(category != null ? category : "한식")
.rating(4.8)
.tags(Arrays.asList("인기", "맛집"))
.build()
);
}
@Override
public void logStoreClick(Long memberId, Long storeId) {
log.info("매장 클릭 로그: memberId={}, storeId={}", memberId, storeId);
// 비동기 로깅 구현
}
@Override
public void logRecommendation(Long memberId, List<Long> storeIds, String context) {
try {
RecommendHistory history = RecommendHistory.builder()
.memberId(memberId)
.recommendedStoreIds(storeIds)
.criteria(context)
.recommendTypeEnum(RecommendType.PREFERENCE_BASED)
.createdAt(LocalDateTime.now())
.build();
saveRecommendHistory(history);
} catch (Exception e) {
log.error("추천 로그 저장 실패", e);
}
}
// 변환 메서드들
private RecommendHistory toRecommendHistory(RecommendHistoryEntity entity) {
return RecommendHistory.builder()
.id(entity.getId())
.memberId(entity.getMemberId())
.recommendedStoreIds(entity.getRecommendedStoreIdsList())
.recommendType(entity.getRecommendType())
.criteria(entity.getCriteria())
.createdAt(entity.getCreatedAt())
.build();
.id(entity.getId())
.memberId(entity.getMemberId())
.recommendedStoreIds(entity.getRecommendedStoreIdsList())
.recommendTypeEnum(entity.getRecommendType())
.criteria(entity.getCriteria())
.createdAt(entity.getCreatedAt())
.build();
}
private TasteProfile toTasteProfile(TasteProfileEntity entity) {
return TasteProfile.builder()
.id(entity.getId())
.memberId(entity.getMemberId())
.preferredCategories(entity.getPreferredCategoriesList())
.categoryScores(entity.getCategoryScoresMap())
.preferredTags(entity.getPreferredTagsList())
.behaviorPatterns(entity.getBehaviorPatternsMap())
.pricePreference(entity.getPricePreference())
.distancePreference(entity.getDistancePreference())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
.id(entity.getId())
.memberId(entity.getMemberId())
.preferredCategories(entity.getPreferredCategoriesList())
.categoryScores(entity.getCategoryScoresMap())
.preferredTags(entity.getPreferredTagsList())
.behaviorPatterns(entity.getBehaviorPatternsMap())
.pricePreference(entity.getPricePreference())
.distancePreferenceDouble(entity.getDistancePreference())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
}
/**
* 도메인을 엔티티로 변환
*/
private RecommendHistoryEntity toRecommendHistoryEntity(RecommendHistory domain) {
return RecommendHistoryEntity.builder()
try {
return RecommendHistoryEntity.builder()
.id(domain.getId())
.memberId(domain.getMemberId())
.recommendedStoreIdsJson(domain.getRecommendedStoreIds().toString()) // JSON 변환 필요
.recommendType(domain.getRecommendType())
.recommendedStoreIdsJson(objectMapper.writeValueAsString(domain.getRecommendedStoreIds()))
.recommendType(domain.getRecommendTypeEnum())
.criteria(domain.getCriteria())
.build();
} catch (Exception e) {
throw new RuntimeException("Entity 변환 실패", e);
}
}
private TasteProfileEntity toTasteProfileEntity(TasteProfile domain) {
return TasteProfileEntity.builder()
try {
return TasteProfileEntity.builder()
.id(domain.getId())
.memberId(domain.getMemberId())
.preferredCategoriesJson(domain.getPreferredCategories().toString()) // JSON 변환 필요
.categoryScoresJson(domain.getCategoryScores().toString()) // JSON 변환 필요
.preferredTagsJson(domain.getPreferredTags().toString()) // JSON 변환 필요
.behaviorPatternsJson(domain.getBehaviorPatterns().toString()) // JSON 변환 필요
.preferredCategoriesJson(objectMapper.writeValueAsString(domain.getPreferredCategories()))
.categoryScoresJson(objectMapper.writeValueAsString(domain.getCategoryScores()))
.preferredTagsJson(objectMapper.writeValueAsString(domain.getPreferredTags()))
.behaviorPatternsJson(objectMapper.writeValueAsString(domain.getBehaviorPatterns()))
.pricePreference(domain.getPricePreference())
.distancePreference(domain.getDistancePreference())
.distancePreference(domain.getDistancePreferenceDouble())
.build();
} catch (Exception e) {
throw new RuntimeException("Entity 변환 실패", e);
}
}
}

View File

@ -3,26 +3,21 @@ package com.ktds.hi.recommend.infra.gateway;
import com.ktds.hi.recommend.biz.usecase.out.UserPreferenceRepository;
import com.ktds.hi.recommend.biz.domain.TasteProfile;
import com.ktds.hi.recommend.biz.domain.TasteCategory;
import com.ktds.hi.recommend.infra.gateway.repository.TasteProfileJpaRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 사용자 선호도 어댑터 클래스
* 사용자 취향 데이터 처리 기능을 구현
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class UserPreferenceAdapter implements UserPreferenceRepository {
private final TasteProfileJpaRepository tasteProfileJpaRepository;
private final RecommendRepositoryAdapter recommendRepositoryAdapter;
@Override
@ -34,53 +29,49 @@ public class UserPreferenceAdapter implements UserPreferenceRepository {
public Map<String, Object> analyzePreferencesFromReviews(Long memberId) {
log.info("리뷰 기반 취향 분석 시작: memberId={}", memberId);
// Mock 구현 - 실제로는 리뷰 서비스 API 호출하여 분석
return Map.of(
"preferredCategories", List.of(TasteCategory.KOREAN, TasteCategory.JAPANESE),
"categoryScores", Map.of(
"한식", 85.0,
"일식", 78.0,
"양식", 65.0
),
"preferredTags", List.of("맛집", "깔끔", "친절"),
"pricePreference", 60.0, // 0-100 점수
"distancePreference", 70.0,
"behaviorPatterns", Map.of(
"weekendDining", true,
"avgRating", 4.2,
"reviewFrequency", "medium"
)
"preferredCategories", Arrays.asList(TasteCategory.KOREAN, TasteCategory.JAPANESE),
"categoryScores", Map.of(
"한식", 85.0,
"일식", 78.0,
"양식", 65.0
),
"preferredTags", Arrays.asList("맛집", "깔끔", "친절"),
"pricePreference", 60.0,
"distancePreference", 70.0,
"behaviorPatterns", Map.of(
"weekendDining", true,
"avgRating", 4.2,
"reviewFrequency", "medium"
)
);
}
@Override
public List<Long> findSimilarTasteMembers(Long memberId) {
log.info("유사 취향 사용자 조회: memberId={}", memberId);
// Mock 구현 - 실제로는 ML 모델 또는 유사도 계산 알고리즘 사용
return List.of(123L, 456L, 789L);
return Arrays.asList(123L, 456L, 789L);
}
@Override
public TasteProfile updateTasteProfile(Long memberId, Map<String, Object> analysisData) {
log.info("취향 프로필 업데이트: memberId={}", memberId);
// 기존 프로필 조회 또는 새로 생성
Optional<TasteProfile> existingProfile = getMemberPreferences(memberId);
TasteProfile.TasteProfileBuilder builder = TasteProfile.builder()
.memberId(memberId)
.preferredCategories((List<TasteCategory>) analysisData.get("preferredCategories"))
.categoryScores((Map<String, Double>) analysisData.get("categoryScores"))
.preferredTags((List<String>) analysisData.get("preferredTags"))
.behaviorPatterns((Map<String, Object>) analysisData.get("behaviorPatterns"))
.pricePreference((Double) analysisData.get("pricePreference"))
.distancePreference((Double) analysisData.get("distancePreference"))
.updatedAt(LocalDateTime.now());
.memberId(memberId)
.preferredCategories((List<TasteCategory>) analysisData.get("preferredCategories"))
.categoryScores((Map<String, Double>) analysisData.get("categoryScores"))
.preferredTags((List<String>) analysisData.get("preferredTags"))
.behaviorPatterns((Map<String, Object>) analysisData.get("behaviorPatterns"))
.pricePreference((Double) analysisData.get("pricePreference"))
.distancePreferenceDouble((Double) analysisData.get("distancePreference"))
.updatedAt(LocalDateTime.now());
if (existingProfile.isPresent()) {
builder.id(existingProfile.get().getId())
.createdAt(existingProfile.get().getCreatedAt());
.createdAt(existingProfile.get().getCreatedAt());
} else {
builder.createdAt(LocalDateTime.now());
}